From b453b6578732a1d71d64ab0e08c924d31cb6b898 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 15 Jul 2021 17:56:32 +0200 Subject: Try to make scrolling emoji picker a bit smoother --- resources/qml/emoji/EmojiPicker.qml | 1 + 1 file changed, 1 insertion(+) (limited to 'resources/qml') diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml index 6f10a230..354e340c 100644 --- a/resources/qml/emoji/EmojiPicker.qml +++ b/resources/qml/emoji/EmojiPicker.qml @@ -130,6 +130,7 @@ Menu { boundsBehavior: Flickable.StopAtBounds clip: true currentIndex: -1 // prevent sorting from stealing focus + cacheBuffer: 500 // Individual emoji delegate: AbstractButton { -- cgit 1.4.1 From 0b864d948586d6cc3ef3968376a5b932e06b793e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 16 Jul 2021 11:47:49 +0200 Subject: Fix replies not reloading after fetching them --- resources/qml/TimelineRow.qml | 38 +++++++++++++++++++------------------- src/timeline/TimelineModel.cpp | 8 ++++++++ src/timeline/TimelineModel.h | 5 +---- 3 files changed, 28 insertions(+), 23 deletions(-) (limited to 'resources/qml') diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 58e367a0..70db08e7 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -86,29 +86,29 @@ Item { // fancy reply, if this is a reply Reply { function fromModel(role) { - return replyTo != "" ? room.dataById(replyTo, role) : null; + return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null; } visible: replyTo - userColor: TimelineManager.userColor(userId, Nheko.colors.base) - blurhash: fromModel(Room.Blurhash) ?? "" - body: fromModel(Room.Body) ?? "" - formattedBody: fromModel(Room.FormattedBody) ?? "" + userColor: replyTo, TimelineManager.userColor(userId, Nheko.colors.base) + blurhash: replyTo, fromModel(Room.Blurhash) ?? "" + body: replyTo, fromModel(Room.Body) ?? "" + formattedBody: replyTo, fromModel(Room.FormattedBody) ?? "" eventId: fromModel(Room.EventId) ?? "" - filename: fromModel(Room.Filename) ?? "" - filesize: fromModel(Room.Filesize) ?? "" - proportionalHeight: fromModel(Room.ProportionalHeight) ?? 1 - type: fromModel(Room.Type) ?? MtxEvent.UnknownMessage - typeString: fromModel(Room.TypeString) ?? "" - url: fromModel(Room.Url) ?? "" - originalWidth: fromModel(Room.OriginalWidth) ?? 0 - isOnlyEmoji: fromModel(Room.IsOnlyEmoji) ?? false - userId: fromModel(Room.UserId) ?? "" - userName: fromModel(Room.UserName) ?? "" - thumbnailUrl: fromModel(Room.ThumbnailUrl) ?? "" - roomTopic: fromModel(Room.RoomTopic) ?? "" - roomName: fromModel(Room.RoomName) ?? "" - callType: fromModel(Room.CallType) ?? "" + filename: replyTo, fromModel(Room.Filename) ?? "" + filesize: replyTo, fromModel(Room.Filesize) ?? "" + proportionalHeight: replyTo, fromModel(Room.ProportionalHeight) ?? 1 + type: replyTo, fromModel(Room.Type) ?? MtxEvent.UnknownMessage + typeString: replyTo, fromModel(Room.TypeString) ?? "" + url: replyTo, fromModel(Room.Url) ?? "" + originalWidth: replyTo, fromModel(Room.OriginalWidth) ?? 0 + isOnlyEmoji: replyTo, fromModel(Room.IsOnlyEmoji) ?? false + userId: replyTo, fromModel(Room.UserId) ?? "" + userName: replyTo, fromModel(Room.UserName) ?? "" + thumbnailUrl: replyTo, fromModel(Room.ThumbnailUrl) ?? "" + roomTopic: replyTo, fromModel(Room.RoomTopic) ?? "" + roomName: replyTo, fromModel(Room.RoomName) ?? "" + callType: replyTo, fromModel(Room.CallType) ?? "" } // actual message content diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index ab11f99b..5832f56e 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -710,6 +710,14 @@ TimelineModel::data(const QModelIndex &index, int role) const return data(*event, role); } +QVariant +TimelineModel::dataById(QString id, int role, QString relatedTo) +{ + if (auto event = events.get(id.toStdString(), relatedTo.toStdString())) + return data(*event, role); + return QVariant(); +} + bool TimelineModel::canFetchMore(const QModelIndex &) const { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index a3c973d6..b67234f2 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -215,10 +215,7 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const; - Q_INVOKABLE QVariant dataById(QString id, int role) - { - return data(index(idToIndex(id)), role); - } + Q_INVOKABLE QVariant dataById(QString id, int role, QString relatedTo); bool canFetchMore(const QModelIndex &) const override; void fetchMore(const QModelIndex &) override; -- cgit 1.4.1 From 8a1666bc889d963693b5dff8f0b4c7612319644a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 15 Jul 2021 20:37:52 +0200 Subject: Basic sticker support --- CMakeLists.txt | 2 + resources/icons/ui/sticky-note-solid.svg | 1 + resources/qml/MatrixText.qml | 8 +- resources/qml/MessageInput.qml | 29 +++++- resources/qml/MessageView.qml | 10 +- resources/qml/emoji/EmojiButton.qml | 23 ---- resources/qml/emoji/StickerPicker.qml | 174 +++++++++++++++++++++++++++++++ resources/res.qrc | 3 +- src/Cache.cpp | 7 ++ src/Cache_p.h | 6 ++ src/ImagePackModel.cpp | 91 ++++++++++++++++ src/ImagePackModel.h | 52 +++++++++ src/timeline/InputBar.cpp | 17 +++ src/timeline/InputBar.h | 2 + src/timeline/TimelineModel.cpp | 9 ++ src/timeline/TimelineModel.h | 15 ++- src/timeline/TimelineViewManager.cpp | 7 ++ 17 files changed, 419 insertions(+), 37 deletions(-) create mode 100644 resources/icons/ui/sticky-note-solid.svg delete mode 100644 resources/qml/emoji/EmojiButton.qml create mode 100644 resources/qml/emoji/StickerPicker.qml create mode 100644 src/ImagePackModel.cpp create mode 100644 src/ImagePackModel.h (limited to 'resources/qml') diff --git a/CMakeLists.txt b/CMakeLists.txt index 78900535..6b26b2e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/resources/icons/ui/sticky-note-solid.svg b/resources/icons/ui/sticky-note-solid.svg new file mode 100644 index 00000000..bc36d474 --- /dev/null +++ b/resources/icons/ui/sticky-note-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml index 9129b154..35e5f7e7 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml @@ -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) - } - } diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 24f9b0e8..d4f7ca62 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -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(); }) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 33dff122..4e605ad7 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -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 { diff --git a/resources/qml/emoji/EmojiButton.qml b/resources/qml/emoji/EmojiButton.qml deleted file mode 100644 index 5f4d23d3..00000000 --- a/resources/qml/emoji/EmojiButton.qml +++ /dev/null @@ -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(); - }) -} diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml new file mode 100644 index 00000000..3fe17ef2 --- /dev/null +++ b/resources/qml/emoji/StickerPicker.qml @@ -0,0 +1,174 @@ +// 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 + //height: columnView.implicitHeight + 4 + //width: columnView.implicitWidth + 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 + + // 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 + } + + } + + } + + } + +} diff --git a/resources/res.qrc b/resources/res.qrc index f41835f9..e9479e57 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -26,6 +26,7 @@ icons/ui/search@2x.png icons/ui/settings.png icons/ui/settings@2x.png + icons/ui/sticky-note-solid.svg icons/ui/smile.png icons/ui/smile@2x.png icons/ui/speech-bubbles-comment-option.png @@ -150,8 +151,8 @@ qml/ForwardCompleter.qml qml/TypingIndicator.qml qml/RoomSettings.qml - qml/emoji/EmojiButton.qml qml/emoji/EmojiPicker.qml + qml/emoji/StickerPicker.qml qml/UserProfile.qml qml/delegates/MessageDelegate.qml qml/delegates/TextMessage.qml diff --git a/src/Cache.cpp b/src/Cache.cpp index 7b6a6135..8c3d8c42 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3382,6 +3382,13 @@ Cache::getChildRoomIds(const std::string &room_id) return roomids; } +std::optional +Cache::getAccountData(mtx::events::EventType type, const std::string &room_id) +{ + auto txn = ro_txn(env_); + return getAccountData(txn, type, room_id); +} + std::optional Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id) { diff --git a/src/Cache_p.h b/src/Cache_p.h index d1f6307d..3752f5e4 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -88,6 +88,12 @@ public: //! Retrieve if the room is a space bool getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb); + //! retrieve a specific event from account data + //! pass empty room_id for global account data + std::optional getAccountData( + mtx::events::EventType type, + const std::string &room_id = ""); + //! Get a specific state event template std::optional> getStateEvent(const std::string &room_id, diff --git a/src/ImagePackModel.cpp b/src/ImagePackModel.cpp new file mode 100644 index 00000000..fb2599a5 --- /dev/null +++ b/src/ImagePackModel.cpp @@ -0,0 +1,91 @@ +// 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 accountpackV = + cache::client()->getAccountData(mtx::events::EventType::ImagePackInAccountData); + auto enabledRoomPacksV = + cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms); + + std::optional accountPack; + if (accountpackV) { + auto tmp = + std::get_if>( + &*accountpackV); + if (tmp) + accountPack = tmp->content; + } + // mtx::events::msc2545::ImagePackRooms *enabledRoomPacks = nullptr; + // if (enabledRoomPacksV) + // enabledRoomPacks = + // std::get_if(&*enabledRoomPacksV); + + if (accountPack && (!accountPack->pack || (stickers ? accountPack->pack->is_sticker() + : accountPack->pack->is_emoji()))) { + QString packname; + if (accountPack->pack) + packname = QString::fromStdString(accountPack->pack->display_name); + + for (const auto &img : accountPack->images) { + if (img.second.overrides_usage() && + (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) + continue; + + ImageDesc i{}; + i.shortcode = QString::fromStdString(img.first); + i.packname = packname; + i.image = img.second; + images.push_back(std::move(i)); + } + } +} + +QHash +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 {}; +} diff --git a/src/ImagePackModel.h b/src/ImagePackModel.h new file mode 100644 index 00000000..10e71b8f --- /dev/null +++ b/src/ImagePackModel.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include + +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 roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return (int)images.size(); + } + QVariant data(const QModelIndex &index, int role) const override; + + mtx::events::msc2545::PackImage imageAt(int row) + { + if (row < 0 || static_cast(row) >= images.size()) + return {}; + return images.at(static_cast(row)).image; + } + +private: + std::string room_id; + + struct ImageDesc + { + QString shortcode; + QString packname; + + mtx::events::msc2545::PackImage image; + }; + + std::vector images; +}; diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index b0747a7c..0f210722 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -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) { diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index c9728379..acedceb7 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -12,6 +12,7 @@ #include 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(); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 5832f56e..abfe28a9 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1300,6 +1300,14 @@ struct SendMessageVisitor sendRoomEvent(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(); diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index b67234f2..0e2895d4 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -410,10 +410,17 @@ template void TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType) { - mtx::events::RoomEvent msgCopy = {}; - msgCopy.content = content; - msgCopy.type = eventType; - emit newMessageToSend(msgCopy); + if constexpr (std::is_same_v) { + mtx::events::Sticker msgCopy = {}; + msgCopy.content = content; + msgCopy.type = eventType; + emit newMessageToSend(msgCopy); + } else { + mtx::events::RoomEvent msgCopy = {}; + msgCopy.content = content; + msgCopy.type = eventType; + emit newMessageToSend(msgCopy); + } resetReply(); resetEdit(); } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index b39ef615..ec1b3573 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -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(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); 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); + stickerModel->setParent(proxy); + return proxy; } return nullptr; } -- cgit 1.4.1 From 9d5ba4f681901a0ee241e542fa9be5e2b73f58db Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 19 Jul 2021 03:02:30 +0200 Subject: Move sticker parsing and enable room stickers --- resources/qml/emoji/StickerPicker.qml | 2 -- src/Cache.cpp | 68 +++++++++++++++++++++++++++++++++-- src/CacheStructs.h | 7 ++++ src/Cache_p.h | 8 ++--- src/ImagePackModel.cpp | 31 +++------------- 5 files changed, 78 insertions(+), 38 deletions(-) (limited to 'resources/qml') diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml index 3fe17ef2..ae1695df 100644 --- a/resources/qml/emoji/StickerPicker.qml +++ b/resources/qml/emoji/StickerPicker.qml @@ -38,8 +38,6 @@ Menu { modal: true focus: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - //height: columnView.implicitHeight + 4 - //width: columnView.implicitWidth width: stickersPerRow * stickerDimPad + 20 Rectangle { diff --git a/src/Cache.cpp b/src/Cache.cpp index 8c3d8c42..4321393c 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3382,11 +3382,73 @@ Cache::getChildRoomIds(const std::string &room_id) return roomids; } -std::optional -Cache::getAccountData(mtx::events::EventType type, const std::string &room_id) +std::vector +Cache::getImagePacks(const std::string &room_id, bool stickers) { auto txn = ro_txn(env_); - return getAccountData(txn, type, room_id); + std::vector 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>( + &*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>( + &*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( + txn, room_id2)) + addPack(pack->content); + } + } + } + } + + // packs from current room + if (auto pack = getStateEvent(txn, room_id)) { + addPack(pack->content); + } + for (const auto &pack : + getStateEventsWithType(txn, room_id)) { + addPack(pack.content); + } + + return infos; } std::optional diff --git a/src/CacheStructs.h b/src/CacheStructs.h index 28c70055..f274d70f 100644 --- a/src/CacheStructs.h +++ b/src/CacheStructs.h @@ -11,6 +11,7 @@ #include #include +#include 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 images; +}; diff --git a/src/Cache_p.h b/src/Cache_p.h index 3752f5e4..13fbc371 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -88,12 +88,6 @@ public: //! Retrieve if the room is a space bool getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb); - //! retrieve a specific event from account data - //! pass empty room_id for global account data - std::optional getAccountData( - mtx::events::EventType type, - const std::string &room_id = ""); - //! Get a specific state event template std::optional> getStateEvent(const std::string &room_id, @@ -231,6 +225,8 @@ public: std::vector getParentRoomIds(const std::string &room_id); std::vector getChildRoomIds(const std::string &room_id); + std::vector 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); diff --git a/src/ImagePackModel.cpp b/src/ImagePackModel.cpp index fb2599a5..4345e383 100644 --- a/src/ImagePackModel.cpp +++ b/src/ImagePackModel.cpp @@ -11,35 +11,12 @@ ImagePackModel::ImagePackModel(const std::string &roomId, bool stickers, QObject : QAbstractListModel(parent) , room_id(roomId) { - auto accountpackV = - cache::client()->getAccountData(mtx::events::EventType::ImagePackInAccountData); - auto enabledRoomPacksV = - cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms); + auto packs = cache::client()->getImagePacks(room_id, stickers); - std::optional accountPack; - if (accountpackV) { - auto tmp = - std::get_if>( - &*accountpackV); - if (tmp) - accountPack = tmp->content; - } - // mtx::events::msc2545::ImagePackRooms *enabledRoomPacks = nullptr; - // if (enabledRoomPacksV) - // enabledRoomPacks = - // std::get_if(&*enabledRoomPacksV); - - if (accountPack && (!accountPack->pack || (stickers ? accountPack->pack->is_sticker() - : accountPack->pack->is_emoji()))) { - QString packname; - if (accountPack->pack) - packname = QString::fromStdString(accountPack->pack->display_name); - - for (const auto &img : accountPack->images) { - if (img.second.overrides_usage() && - (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) - continue; + 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; -- cgit 1.4.1 From 60be0e8c09ab671cb50dfb8adeb48b15389aba3a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 19 Jul 2021 14:57:10 +0200 Subject: Make scrolling sticker picker bearable --- resources/qml/emoji/StickerPicker.qml | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'resources/qml') diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml index ae1695df..eca302eb 100644 --- a/resources/qml/emoji/StickerPicker.qml +++ b/resources/qml/emoji/StickerPicker.qml @@ -130,6 +130,12 @@ Menu { currentIndex: -1 // prevent sorting from stealing focus cacheBuffer: 500 + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + // Individual emoji delegate: AbstractButton { width: stickerDim -- cgit 1.4.1 From 11c96664089e1437be1eb0ae8bca6c051da5e950 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 19 Jul 2021 17:45:55 +0200 Subject: Cache sticker picker --- resources/qml/MessageInput.qml | 2 +- resources/qml/emoji/StickerPicker.qml | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'resources/qml') diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index d4f7ca62..415d67a7 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -331,7 +331,7 @@ Rectangle { 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) { + onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId(), function(row) { room.input.sticker(stickerPopup.model.sourceModel, row); TimelineManager.focusMessageInput(); }) diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml index eca302eb..a3d01d7a 100644 --- a/resources/qml/emoji/StickerPicker.qml +++ b/resources/qml/emoji/StickerPicker.qml @@ -15,7 +15,7 @@ Menu { property var callback property var colors - property alias model: gridView.model + property string roomid property var textArea property real highlightHue: Nheko.colors.highlight.hslHue property real highlightSat: Nheko.colors.highlight.hslSaturation @@ -24,9 +24,9 @@ Menu { readonly property int stickerDimPad: 128 + Nheko.paddingSmall readonly property int stickersPerRow: 3 - function show(showAt, model_, callback) { + function show(showAt, roomid_, callback) { console.debug("Showing sticker picker"); - model = model_; + roomid = roomid_; stickerPopup.callback = callback; popup(showAt ? showAt : null); } @@ -120,6 +120,8 @@ Menu { GridView { id: gridView + model: roomid ? TimelineManager.completerFor("stickers", roomid) : null + Layout.preferredHeight: cellHeight * 3.5 Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 Layout.leftMargin: 4 -- cgit 1.4.1 From 752ffa5c5187ae4e55c6cfb77ba90edd7e3848c8 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 19 Jul 2021 17:49:57 +0200 Subject: Model alias is still needed --- resources/qml/emoji/StickerPicker.qml | 1 + 1 file changed, 1 insertion(+) (limited to 'resources/qml') diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml index a3d01d7a..813c0b12 100644 --- a/resources/qml/emoji/StickerPicker.qml +++ b/resources/qml/emoji/StickerPicker.qml @@ -16,6 +16,7 @@ Menu { property var callback property var colors property string roomid + property alias model: gridView.model property var textArea property real highlightHue: Nheko.colors.highlight.hslHue property real highlightSat: Nheko.colors.highlight.hslSaturation -- cgit 1.4.1 From 6d169cea7dfb4554ef09aceada33ba715b1df09c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 19 Jul 2021 17:59:38 +0200 Subject: Fix reaction button again --- resources/qml/MessageView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'resources/qml') diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 4e605ad7..f56af237 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -101,7 +101,7 @@ ScrollView { image: ":/icons/icons/ui/smile.png" ToolTip.visible: hovered ToolTip.text: qsTr("React") - onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) { + onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, function(emoji) { var event_id = row.model ? row.model.eventId : ""; room.input.reaction(event_id, emoji); TimelineManager.focusMessageInput(); -- cgit 1.4.1