mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-10-30 09:30:47 +03:00
commit
9a950c7f0e
18 changed files with 467 additions and 37 deletions
|
@ -355,6 +355,7 @@ set(SRC_FILES
|
|||
src/Olm.cpp
|
||||
src/RegisterPage.cpp
|
||||
src/SSOHandler.cpp
|
||||
src/ImagePackModel.cpp
|
||||
src/TrayIcon.cpp
|
||||
src/UserSettingsPage.cpp
|
||||
src/UsersModel.cpp
|
||||
|
@ -559,6 +560,7 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||
src/MxcImageProvider.h
|
||||
src/RegisterPage.h
|
||||
src/SSOHandler.h
|
||||
src/ImagePackModel.h
|
||||
src/TrayIcon.h
|
||||
src/UserSettingsPage.h
|
||||
src/UsersModel.h
|
||||
|
|
1
resources/icons/ui/sticky-note-solid.svg
Normal file
1
resources/icons/ui/sticky-note-solid.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="sticky-note" class="svg-inline--fa fa-sticky-note fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M312 320h136V56c0-13.3-10.7-24-24-24H24C10.7 32 0 42.7 0 56v400c0 13.3 10.7 24 24 24h264V344c0-13.2 10.8-24 24-24zm129 55l-98 98c-4.5 4.5-10.6 7-17 7h-6V352h128v6.1c0 6.3-2.5 12.4-7 16.9z"></path></svg>
|
After Width: | Height: | Size: 429 B |
|
@ -8,6 +8,7 @@ import im.nheko 1.0
|
|||
|
||||
TextEdit {
|
||||
id: r
|
||||
|
||||
textFormat: TextEdit.RichText
|
||||
readOnly: true
|
||||
focus: false
|
||||
|
@ -19,14 +20,13 @@ TextEdit {
|
|||
onLinkActivated: Nheko.openLink(link)
|
||||
ToolTip.visible: hoveredLink
|
||||
ToolTip.text: hoveredLink
|
||||
Component.onCompleted: {
|
||||
TimelineManager.fixImageRendering(r.textDocument, r);
|
||||
}
|
||||
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
TimelineManager.fixImageRendering(r.textDocument, r)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./emoji"
|
||||
import "./voip"
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.3
|
||||
|
@ -87,7 +88,7 @@ Rectangle {
|
|||
Layout.alignment: Qt.AlignBottom // | Qt.AlignHCenter
|
||||
Layout.maximumHeight: Window.height / 4
|
||||
Layout.minimumHeight: Settings.fontSize
|
||||
implicitWidth: inputBar.width - 4 * (22 + 16) - 24
|
||||
implicitWidth: inputBar.width - 5 * (22 + 16) - 24
|
||||
|
||||
TextArea {
|
||||
id: messageInput
|
||||
|
@ -319,6 +320,30 @@ Rectangle {
|
|||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: stickerButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.margins: 8
|
||||
hoverEnabled: true
|
||||
width: 22
|
||||
height: 22
|
||||
image: ":/icons/icons/ui/sticky-note-solid.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Stickers")
|
||||
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, TimelineManager.completerFor("stickers", room.roomId()), function(row) {
|
||||
room.input.sticker(stickerPopup.model.sourceModel, row);
|
||||
TimelineManager.focusMessageInput();
|
||||
})
|
||||
|
||||
StickerPicker {
|
||||
id: stickerPopup
|
||||
|
||||
colors: Nheko.colors
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: emojiButton
|
||||
|
||||
|
@ -330,7 +355,7 @@ Rectangle {
|
|||
image: ":/icons/icons/ui/smile.png"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Emoji")
|
||||
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) {
|
||||
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(function(emoji) {
|
||||
messageInput.insert(messageInput.cursorPosition, emoji);
|
||||
TimelineManager.focusMessageInput();
|
||||
})
|
||||
|
|
|
@ -92,16 +92,20 @@ ScrollView {
|
|||
}
|
||||
}
|
||||
|
||||
EmojiButton {
|
||||
ImageButton {
|
||||
id: reactButton
|
||||
|
||||
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
|
||||
width: 16
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/smile.png"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("React")
|
||||
emojiPicker: emojiPopup
|
||||
event_id: row.model ? row.model.eventId : ""
|
||||
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) {
|
||||
var event_id = row.model ? row.model.eventId : "";
|
||||
room.input.reaction(event_id, emoji);
|
||||
TimelineManager.focusMessageInput();
|
||||
})
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "../"
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.1
|
||||
import im.nheko 1.0
|
||||
import im.nheko.EmojiModel 1.0
|
||||
|
||||
ImageButton {
|
||||
id: emojiButton
|
||||
|
||||
property var colors: currentActivePalette
|
||||
property var emojiPicker
|
||||
property string event_id
|
||||
|
||||
image: ":/icons/icons/ui/smile.png"
|
||||
onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, function(emoji) {
|
||||
room.input.reaction(event_id, emoji);
|
||||
TimelineManager.focusMessageInput();
|
||||
})
|
||||
}
|
178
resources/qml/emoji/StickerPicker.qml
Normal file
178
resources/qml/emoji/StickerPicker.qml
Normal file
|
@ -0,0 +1,178 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "../"
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
import im.nheko 1.0
|
||||
import im.nheko.EmojiModel 1.0
|
||||
|
||||
Menu {
|
||||
id: stickerPopup
|
||||
|
||||
property var callback
|
||||
property var colors
|
||||
property alias model: gridView.model
|
||||
property var textArea
|
||||
property real highlightHue: Nheko.colors.highlight.hslHue
|
||||
property real highlightSat: Nheko.colors.highlight.hslSaturation
|
||||
property real highlightLight: Nheko.colors.highlight.hslLightness
|
||||
readonly property int stickerDim: 128
|
||||
readonly property int stickerDimPad: 128 + Nheko.paddingSmall
|
||||
readonly property int stickersPerRow: 3
|
||||
|
||||
function show(showAt, model_, callback) {
|
||||
console.debug("Showing sticker picker");
|
||||
model = model_;
|
||||
stickerPopup.callback = callback;
|
||||
popup(showAt ? showAt : null);
|
||||
}
|
||||
|
||||
margins: 0
|
||||
bottomPadding: 1
|
||||
leftPadding: 1
|
||||
rightPadding: 1
|
||||
modal: true
|
||||
focus: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
width: stickersPerRow * stickerDimPad + 20
|
||||
|
||||
Rectangle {
|
||||
color: Nheko.colors.window
|
||||
height: columnView.implicitHeight + 4
|
||||
width: stickersPerRow * stickerDimPad + 20
|
||||
|
||||
ColumnLayout {
|
||||
id: columnView
|
||||
|
||||
spacing: 0
|
||||
anchors.leftMargin: 3
|
||||
anchors.rightMargin: 3
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 2
|
||||
|
||||
// Search field
|
||||
TextField {
|
||||
id: emojiSearch
|
||||
|
||||
Layout.topMargin: 3
|
||||
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - 6
|
||||
palette: Nheko.colors
|
||||
background: null
|
||||
placeholderTextColor: Nheko.colors.buttonText
|
||||
color: Nheko.colors.text
|
||||
placeholderText: qsTr("Search")
|
||||
selectByMouse: true
|
||||
rightPadding: clearSearch.width
|
||||
onTextChanged: searchTimer.restart()
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
forceActiveFocus();
|
||||
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: searchTimer
|
||||
|
||||
interval: 350 // tweak as needed?
|
||||
onTriggered: stickerPopup.model.searchString = emojiSearch.text
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: clearSearch
|
||||
|
||||
visible: emojiSearch.text !== ''
|
||||
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? Nheko.colors.highlight : Nheko.colors.buttonText)
|
||||
focusPolicy: Qt.NoFocus
|
||||
onClicked: emojiSearch.clear()
|
||||
hoverEnabled: true
|
||||
background: null
|
||||
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.right
|
||||
}
|
||||
// clear the default hover effects.
|
||||
|
||||
Image {
|
||||
height: parent.height - 2 * Nheko.paddingSmall
|
||||
width: height
|
||||
source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? Nheko.colors.highlight : Nheko.colors.buttonText)
|
||||
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.right
|
||||
margins: Nheko.paddingSmall
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// emoji grid
|
||||
GridView {
|
||||
id: gridView
|
||||
|
||||
Layout.preferredHeight: cellHeight * 3.5
|
||||
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20
|
||||
Layout.leftMargin: 4
|
||||
cellWidth: stickerDimPad
|
||||
cellHeight: stickerDimPad
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
clip: true
|
||||
currentIndex: -1 // prevent sorting from stealing focus
|
||||
cacheBuffer: 500
|
||||
|
||||
ScrollHelper {
|
||||
flickable: parent
|
||||
anchors.fill: parent
|
||||
enabled: !Settings.mobileMode
|
||||
}
|
||||
|
||||
// Individual emoji
|
||||
delegate: AbstractButton {
|
||||
width: stickerDim
|
||||
height: stickerDim
|
||||
hoverEnabled: true
|
||||
ToolTip.text: ":" + model.shortcode + ": - " + model.body
|
||||
ToolTip.visible: hovered
|
||||
// TODO: maybe add favorites at some point?
|
||||
onClicked: {
|
||||
console.debug("Picked " + model.shortcode);
|
||||
stickerPopup.close();
|
||||
callback(model.originalRow);
|
||||
}
|
||||
|
||||
contentItem: Image {
|
||||
height: stickerDim
|
||||
width: stickerDim
|
||||
source: model.url.replace("mxc://", "image://MxcImage/")
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: hovered ? Nheko.colors.highlight : 'transparent'
|
||||
radius: 5
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: emojiScroll
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
<file>icons/ui/search@2x.png</file>
|
||||
<file>icons/ui/settings.png</file>
|
||||
<file>icons/ui/settings@2x.png</file>
|
||||
<file>icons/ui/sticky-note-solid.svg</file>
|
||||
<file>icons/ui/smile.png</file>
|
||||
<file>icons/ui/smile@2x.png</file>
|
||||
<file>icons/ui/speech-bubbles-comment-option.png</file>
|
||||
|
@ -150,8 +151,8 @@
|
|||
<file>qml/ForwardCompleter.qml</file>
|
||||
<file>qml/TypingIndicator.qml</file>
|
||||
<file>qml/RoomSettings.qml</file>
|
||||
<file>qml/emoji/EmojiButton.qml</file>
|
||||
<file>qml/emoji/EmojiPicker.qml</file>
|
||||
<file>qml/emoji/StickerPicker.qml</file>
|
||||
<file>qml/UserProfile.qml</file>
|
||||
<file>qml/delegates/MessageDelegate.qml</file>
|
||||
<file>qml/delegates/TextMessage.qml</file>
|
||||
|
|
|
@ -3382,6 +3382,75 @@ Cache::getChildRoomIds(const std::string &room_id)
|
|||
return roomids;
|
||||
}
|
||||
|
||||
std::vector<ImagePackInfo>
|
||||
Cache::getImagePacks(const std::string &room_id, bool stickers)
|
||||
{
|
||||
auto txn = ro_txn(env_);
|
||||
std::vector<ImagePackInfo> infos;
|
||||
|
||||
auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack) {
|
||||
if (!pack.pack || (stickers ? pack.pack->is_sticker() : pack.pack->is_emoji())) {
|
||||
ImagePackInfo info;
|
||||
if (pack.pack)
|
||||
info.packname = pack.pack->display_name;
|
||||
|
||||
for (const auto &img : pack.images) {
|
||||
if (img.second.overrides_usage() &&
|
||||
(stickers ? !img.second.is_sticker() : !img.second.is_emoji()))
|
||||
continue;
|
||||
|
||||
info.images.insert(img);
|
||||
}
|
||||
|
||||
if (!info.images.empty())
|
||||
infos.push_back(std::move(info));
|
||||
}
|
||||
};
|
||||
|
||||
// packs from account data
|
||||
if (auto accountpack =
|
||||
getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) {
|
||||
auto tmp =
|
||||
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePack>>(
|
||||
&*accountpack);
|
||||
if (tmp)
|
||||
addPack(tmp->content);
|
||||
}
|
||||
|
||||
// packs from rooms, that were enabled globally
|
||||
if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) {
|
||||
auto tmp =
|
||||
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
|
||||
&*roomPacks);
|
||||
if (tmp) {
|
||||
for (const auto &[room_id2, state_to_d] : tmp->content.rooms) {
|
||||
// don't add stickers from this room twice
|
||||
if (room_id2 == room_id)
|
||||
continue;
|
||||
|
||||
for (const auto &[state_id, d] : state_to_d) {
|
||||
(void)d;
|
||||
if (auto pack =
|
||||
getStateEvent<mtx::events::msc2545::ImagePack>(
|
||||
txn, room_id2, state_id))
|
||||
addPack(pack->content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// packs from current room
|
||||
if (auto pack = getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id)) {
|
||||
addPack(pack->content);
|
||||
}
|
||||
for (const auto &pack :
|
||||
getStateEventsWithType<mtx::events::msc2545::ImagePack>(txn, room_id)) {
|
||||
addPack(pack.content);
|
||||
}
|
||||
|
||||
return infos;
|
||||
}
|
||||
|
||||
std::optional<mtx::events::collections::RoomAccountDataEvents>
|
||||
Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id)
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <string>
|
||||
|
||||
#include <mtx/events/join_rules.hpp>
|
||||
#include <mtx/events/mscs/image_packs.hpp>
|
||||
|
||||
namespace cache {
|
||||
enum class CacheVersion : int
|
||||
|
@ -109,3 +110,9 @@ struct RoomSearchResult
|
|||
std::string room_id;
|
||||
RoomInfo info;
|
||||
};
|
||||
|
||||
struct ImagePackInfo
|
||||
{
|
||||
std::string packname;
|
||||
std::map<std::string, mtx::events::msc2545::PackImage> images;
|
||||
};
|
||||
|
|
|
@ -225,6 +225,8 @@ public:
|
|||
std::vector<std::string> getParentRoomIds(const std::string &room_id);
|
||||
std::vector<std::string> getChildRoomIds(const std::string &room_id);
|
||||
|
||||
std::vector<ImagePackInfo> getImagePacks(const std::string &room_id, bool stickers);
|
||||
|
||||
//! Mark a room that uses e2e encryption.
|
||||
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
|
||||
bool isRoomEncrypted(const std::string &room_id);
|
||||
|
|
74
src/ImagePackModel.cpp
Normal file
74
src/ImagePackModel.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "ImagePackModel.h"
|
||||
|
||||
#include "Cache_p.h"
|
||||
#include "CompletionModelRoles.h"
|
||||
|
||||
ImagePackModel::ImagePackModel(const std::string &roomId, bool stickers, QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, room_id(roomId)
|
||||
{
|
||||
auto packs = cache::client()->getImagePacks(room_id, stickers);
|
||||
|
||||
for (const auto &pack : packs) {
|
||||
QString packname = QString::fromStdString(pack.packname);
|
||||
|
||||
for (const auto &img : pack.images) {
|
||||
ImageDesc i{};
|
||||
i.shortcode = QString::fromStdString(img.first);
|
||||
i.packname = packname;
|
||||
i.image = img.second;
|
||||
images.push_back(std::move(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ImagePackModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return (int)images.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
ImagePackModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{CompletionModel::CompletionRole, "completionRole"},
|
||||
{CompletionModel::SearchRole, "searchRole"},
|
||||
{CompletionModel::SearchRole2, "searchRole2"},
|
||||
{Roles::Url, "url"},
|
||||
{Roles::ShortCode, "shortcode"},
|
||||
{Roles::Body, "body"},
|
||||
{Roles::PackName, "packname"},
|
||||
{Roles::OriginalRow, "originalRow"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
ImagePackModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case CompletionModel::CompletionRole:
|
||||
return QString::fromStdString(images[index.row()].image.url);
|
||||
case Roles::Url:
|
||||
return QString::fromStdString(images[index.row()].image.url);
|
||||
case CompletionModel::SearchRole:
|
||||
case Roles::ShortCode:
|
||||
return images[index.row()].shortcode;
|
||||
case CompletionModel::SearchRole2:
|
||||
case Roles::Body:
|
||||
return QString::fromStdString(images[index.row()].image.body);
|
||||
case Roles::PackName:
|
||||
return images[index.row()].packname;
|
||||
case Roles::OriginalRow:
|
||||
return index.row();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
48
src/ImagePackModel.h
Normal file
48
src/ImagePackModel.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <mtx/events/mscs/image_packs.hpp>
|
||||
|
||||
class ImagePackModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Url = Qt::UserRole,
|
||||
ShortCode,
|
||||
Body,
|
||||
PackName,
|
||||
OriginalRow,
|
||||
};
|
||||
|
||||
ImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
mtx::events::msc2545::PackImage imageAt(int row)
|
||||
{
|
||||
if (row < 0 || static_cast<size_t>(row) >= images.size())
|
||||
return {};
|
||||
return images.at(static_cast<size_t>(row)).image;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string room_id;
|
||||
|
||||
struct ImageDesc
|
||||
{
|
||||
QString shortcode;
|
||||
QString packname;
|
||||
|
||||
mtx::events::msc2545::PackImage image;
|
||||
};
|
||||
|
||||
std::vector<ImageDesc> images;
|
||||
};
|
|
@ -21,6 +21,7 @@
|
|||
#include "ChatPage.h"
|
||||
#include "CompletionProxyModel.h"
|
||||
#include "Config.h"
|
||||
#include "ImagePackModel.h"
|
||||
#include "Logging.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
|
@ -501,6 +502,22 @@ InputBar::video(const QString &filename,
|
|||
room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
|
||||
}
|
||||
|
||||
void
|
||||
InputBar::sticker(ImagePackModel *model, int row)
|
||||
{
|
||||
if (!model || row < 0)
|
||||
return;
|
||||
|
||||
auto img = model->imageAt(row);
|
||||
|
||||
mtx::events::msg::StickerImage sticker{};
|
||||
sticker.info = img.info.value_or(mtx::common::ImageInfo{});
|
||||
sticker.url = img.url;
|
||||
sticker.body = img.body;
|
||||
|
||||
room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
|
||||
}
|
||||
|
||||
void
|
||||
InputBar::command(QString command, QString args)
|
||||
{
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <mtx/responses/messages.hpp>
|
||||
|
||||
class TimelineModel;
|
||||
class ImagePackModel;
|
||||
class QMimeData;
|
||||
class QDropEvent;
|
||||
class QStringList;
|
||||
|
@ -57,6 +58,7 @@ public slots:
|
|||
MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
|
||||
bool rainbowify = false);
|
||||
void reaction(const QString &reactedEvent, const QString &reactionKey);
|
||||
void sticker(ImagePackModel *model, int row);
|
||||
|
||||
private slots:
|
||||
void startTyping();
|
||||
|
|
|
@ -1300,6 +1300,14 @@ struct SendMessageVisitor
|
|||
sendRoomEvent<mtx::events::msg::KeyVerificationCancel,
|
||||
mtx::events::EventType::KeyVerificationCancel>(msg);
|
||||
}
|
||||
void operator()(mtx::events::Sticker msg)
|
||||
{
|
||||
msg.type = mtx::events::EventType::Sticker;
|
||||
if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
|
||||
model_->sendEncryptedMessage(msg, mtx::events::EventType::Sticker);
|
||||
} else
|
||||
emit model_->addPendingMessageToStore(msg);
|
||||
}
|
||||
|
||||
TimelineModel *model_;
|
||||
};
|
||||
|
@ -1309,6 +1317,7 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
|
|||
{
|
||||
std::visit(
|
||||
[](auto &msg) {
|
||||
// gets overwritten for reactions and stickers in SendMessageVisitor
|
||||
msg.type = mtx::events::EventType::RoomMessage;
|
||||
msg.event_id = "m" + http::client()->generate_txn_id();
|
||||
msg.sender = http::client()->user_id().to_string();
|
||||
|
|
|
@ -410,10 +410,17 @@ template<class T>
|
|||
void
|
||||
TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType)
|
||||
{
|
||||
mtx::events::RoomEvent<T> msgCopy = {};
|
||||
msgCopy.content = content;
|
||||
msgCopy.type = eventType;
|
||||
emit newMessageToSend(msgCopy);
|
||||
if constexpr (std::is_same_v<T, mtx::events::msg::StickerImage>) {
|
||||
mtx::events::Sticker msgCopy = {};
|
||||
msgCopy.content = content;
|
||||
msgCopy.type = eventType;
|
||||
emit newMessageToSend(msgCopy);
|
||||
} else {
|
||||
mtx::events::RoomEvent<T> msgCopy = {};
|
||||
msgCopy.content = content;
|
||||
msgCopy.type = eventType;
|
||||
emit newMessageToSend(msgCopy);
|
||||
}
|
||||
resetReply();
|
||||
resetEdit();
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "DelegateChooser.h"
|
||||
#include "DeviceVerificationFlow.h"
|
||||
#include "EventAccessors.h"
|
||||
#include "ImagePackModel.h"
|
||||
#include "Logging.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
|
@ -144,6 +145,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
|||
qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
|
||||
qRegisterMetaType<ImagePackModel *>();
|
||||
|
||||
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
|
||||
"im.nheko",
|
||||
|
@ -593,6 +595,11 @@ TimelineViewManager::completerFor(QString completerName, QString roomId)
|
|||
auto proxy = new CompletionProxyModel(roomModel);
|
||||
roomModel->setParent(proxy);
|
||||
return proxy;
|
||||
} else if (completerName == "stickers") {
|
||||
auto stickerModel = new ImagePackModel(roomId.toStdString(), true);
|
||||
auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4);
|
||||
stickerModel->setParent(proxy);
|
||||
return proxy;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue