summary refs log tree commit diff
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2023-05-19 03:15:55 +0200
committerNicolas Werner <nicolas.werner@hotmail.de>2023-05-20 00:57:47 +0200
commit0dfdba4316c18fe92f0d0b441236361091cdc758 (patch)
treeccc50e004bbe4f3e12692053e25fa556634c0ff1
parentMerge pull request #1456 from Nheko-Reborn/inputfocus (diff)
downloadnheko-0dfdba4316c18fe92f0d0b441236361091cdc758.tar.xz
Add rows to stickerpicker
-rw-r--r--CMakeLists.txt2
-rw-r--r--resources/qml/MessageInput.qml2
-rw-r--r--resources/qml/emoji/StickerPicker.qml52
-rw-r--r--src/GridImagePackModel.cpp88
-rw-r--r--src/GridImagePackModel.h72
-rw-r--r--src/MainWindow.cpp2
-rw-r--r--src/timeline/InputBar.cpp42
-rw-r--r--src/timeline/InputBar.h2
-rw-r--r--src/timeline/TimelineViewManager.cpp4
9 files changed, 238 insertions, 28 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 62db167f..dad4fb16 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -456,6 +456,8 @@ set(SRC_FILES
 	src/ColorImageProvider.h
 	src/CombinedImagePackModel.cpp
 	src/CombinedImagePackModel.h
+	src/GridImagePackModel.cpp
+	src/GridImagePackModel.h
 	src/CommandCompleter.cpp
 	src/CommandCompleter.h
 	src/CompletionModelRoles.h
diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml
index 6ddbb32e..14f27fff 100644
--- a/resources/qml/MessageInput.qml
+++ b/resources/qml/MessageInput.qml
@@ -433,7 +433,7 @@ Rectangle {
             ToolTip.visible: hovered
             ToolTip.text: qsTr("Stickers")
             onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function(row) {
-                room.input.sticker(stickerPopup.model.sourceModel, row);
+                room.input.sticker(row);
                 TimelineManager.focusMessageInput();
             })
 
diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml
index 1928dfa7..f84fe06f 100644
--- a/resources/qml/emoji/StickerPicker.qml
+++ b/resources/qml/emoji/StickerPicker.qml
@@ -102,19 +102,41 @@ Menu {
                 }
             }
 
-            // emoji grid
-            GridView {
+            Component {
+                id: sectionHeading
+                Rectangle {
+                    width: gridView.width
+                    height: childrenRect.height
+                    color: Nheko.colors.alternateBase
+
+                    required property string section
+
+                    Text {
+                        anchors.left: parent.left
+                        anchors.right: parent.right
+                        text: parent.section
+                        color: Nheko.colors.text
+                        font.bold: true
+                    }
+                }
+            }
+
+            // sticker grid
+            ListView {
                 id: gridView
 
-                model: roomid ? TimelineManager.completerFor("stickers", roomid) : null
+                model: roomid ? TimelineManager.completerFor("stickergrid", roomid) : null
                 Layout.preferredHeight: cellHeight * 3.5
                 Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall
-                cellWidth: stickerDimPad
-                cellHeight: stickerDimPad
+                property int cellHeight: stickerDimPad
                 boundsBehavior: Flickable.StopAtBounds
                 clip: true
                 currentIndex: -1 // prevent sorting from stealing focus
-                cacheBuffer: 500
+
+                section.property: "packname"
+                section.criteria: ViewSection.FullString
+                section.delegate: sectionHeading
+                section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
 
                 ScrollHelper {
                     flickable: parent
@@ -123,23 +145,29 @@ Menu {
                 }
 
                 // Individual emoji
-                delegate: AbstractButton {
+                delegate: Row {
+                    required property var row;
+
+                    Repeater {
+                        model: row
+
+                    delegate: AbstractButton {
                     width: stickerDim
                     height: stickerDim
                     hoverEnabled: true
-                    ToolTip.text: ":" + model.shortcode + ": - " + model.body
+                    ToolTip.text: ":" + modelData.shortcode + ": - " + modelData.body
                     ToolTip.visible: hovered
                     // TODO: maybe add favorites at some point?
                     onClicked: {
-                        console.debug("Picked " + model.shortcode);
+                        console.debug("Picked " + modelData.descriptor);
                         stickerPopup.close();
-                        callback(model.originalRow);
+                        callback(modelData.descriptor);
                     }
 
                     contentItem: Image {
                         height: stickerDim
                         width: stickerDim
-                        source: model.url.replace("mxc://", "image://MxcImage/") + "?scale"
+                        source: modelData.url.replace("mxc://", "image://MxcImage/") + "?scale"
                         fillMode: Image.PreserveAspectFit
                     }
 
@@ -150,6 +178,8 @@ Menu {
                     }
 
                 }
+            }
+        }
 
                 ScrollBar.vertical: ScrollBar {
                     id: emojiScroll
diff --git a/src/GridImagePackModel.cpp b/src/GridImagePackModel.cpp
new file mode 100644
index 00000000..4fee086a
--- /dev/null
+++ b/src/GridImagePackModel.cpp
@@ -0,0 +1,88 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "GridImagePackModel.h"
+
+#include "Cache_p.h"
+#include "CompletionModelRoles.h"
+
+#include <algorithm>
+
+Q_DECLARE_METATYPE(StickerImage)
+
+GridImagePackModel::GridImagePackModel(const std::string &roomId, bool stickers, QObject *parent)
+  : QAbstractListModel(parent)
+  , room_id(roomId)
+{
+    [[maybe_unused]] static auto id = qRegisterMetaType<StickerImage>();
+
+    auto originalPacks = cache::client()->getImagePacks(room_id, stickers);
+
+    for (auto &pack : originalPacks) {
+        PackDesc newPack{};
+        newPack.packname =
+          pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : QString();
+        newPack.room_id   = pack.source_room;
+        newPack.state_key = pack.state_key;
+
+        newPack.images.resize(pack.pack.images.size());
+        std::ranges::transform(std::move(pack.pack.images), newPack.images.begin(), [](auto &&img) {
+            return std::pair(std::move(img.second), QString::fromStdString(img.first));
+        });
+
+        size_t packRowCount =
+          (newPack.images.size() / columns) + (newPack.images.size() % columns ? 1 : 0);
+        newPack.firstRow = rowToPack.size();
+        for (size_t i = 0; i < packRowCount; i++)
+            rowToPack.push_back(packs.size());
+        packs.push_back(std::move(newPack));
+    }
+}
+
+int
+GridImagePackModel::rowCount(const QModelIndex &) const
+{
+    return (int)rowToPack.size();
+}
+
+QHash<int, QByteArray>
+GridImagePackModel::roleNames() const
+{
+    return {
+      {Roles::PackName, "packname"},
+      {Roles::Row, "row"},
+    };
+}
+
+QVariant
+GridImagePackModel::data(const QModelIndex &index, int role) const
+{
+    if (index.row() < rowCount() && index.row() >= 0) {
+        const auto &pack = packs[rowToPack[index.row()]];
+        switch (role) {
+        case Roles::PackName:
+            return pack.packname;
+        case Roles::Row: {
+            std::size_t offset = static_cast<std::size_t>(index.row()) - pack.firstRow;
+            QList<StickerImage> imgs;
+            auto endOffset = std::min((offset + 1) * 3, pack.images.size());
+            for (std::size_t img = offset * 3; img < endOffset; img++) {
+                const auto &data = pack.images.at(img);
+                imgs.push_back({.url         = QString::fromStdString(data.first.url),
+                                .shortcode   = data.second,
+                                .body        = QString::fromStdString(data.first.body),
+                                .descriptor_ = std::vector{
+                                  pack.room_id,
+                                  pack.state_key,
+                                  data.second.toStdString(),
+                                }});
+            }
+            return QVariant::fromValue(imgs);
+        }
+        default:
+            return {};
+        }
+    }
+    return {};
+}
diff --git a/src/GridImagePackModel.h b/src/GridImagePackModel.h
new file mode 100644
index 00000000..1345b103
--- /dev/null
+++ b/src/GridImagePackModel.h
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <QAbstractListModel>
+#include <QObject>
+#include <QString>
+
+#include <mtx/events/mscs/image_packs.hpp>
+
+struct StickerImage
+{
+    Q_GADGET
+    Q_PROPERTY(QString url MEMBER url CONSTANT)
+    Q_PROPERTY(QString shortcode MEMBER shortcode CONSTANT)
+    Q_PROPERTY(QString body MEMBER body CONSTANT)
+    Q_PROPERTY(QStringList descriptor READ descriptor CONSTANT)
+
+public:
+    QStringList descriptor() const
+    {
+        if (descriptor_.size() == 3)
+            return QStringList{
+              QString::fromStdString(descriptor_[0]),
+              QString::fromStdString(descriptor_[1]),
+              QString::fromStdString(descriptor_[2]),
+            };
+        else
+            return {};
+    }
+
+    QString url;
+    QString shortcode;
+    QString body;
+
+    std::vector<std::string> descriptor_; // roomid, statekey, shortcode
+};
+
+class GridImagePackModel final : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    enum Roles
+    {
+        PackName = Qt::UserRole,
+        Row,
+    };
+
+    GridImagePackModel(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;
+
+private:
+    std::string room_id;
+
+    struct PackDesc
+    {
+        QString packname;
+        QString packavatar;
+        std::string room_id, state_key;
+
+        std::vector<std::pair<mtx::events::msc2545::PackImage, QString>> images;
+        std::size_t firstRow;
+    };
+
+    std::vector<PackDesc> packs;
+    std::vector<size_t> rowToPack;
+    int columns = 3;
+};
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 3ba40bb6..d9a03346 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -19,6 +19,7 @@
 #include "CompletionProxyModel.h"
 #include "Config.h"
 #include "EventAccessors.h"
+#include "GridImagePackModel.h"
 #include "ImagePackListModel.h"
 #include "InviteesModel.h"
 #include "JdenticonProvider.h"
@@ -150,6 +151,7 @@ MainWindow::registerQmlTypes()
     qRegisterMetaType<mtx::responses::User>();
     qRegisterMetaType<mtx::responses::Profile>();
     qRegisterMetaType<CombinedImagePackModel *>();
+    qRegisterMetaType<GridImagePackModel *>();
     qRegisterMetaType<RoomSettingsAllowedRoomsModel *>();
     qRegisterMetaType<mtx::events::collections::TimelineEvents>();
     qRegisterMetaType<std::vector<DeviceInfo>>();
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index dd6813c2..0c2e3752 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -836,28 +836,40 @@ InputBar::getCommandAndArgs(const QString &currentText) const
 }
 
 void
-InputBar::sticker(CombinedImagePackModel *model, int row)
+InputBar::sticker(QStringList descriptor)
 {
-    if (!model || row < 0)
+    if (descriptor.size() != 3)
         return;
 
-    auto img = model->imageAt(row);
+    auto originalPacks = cache::client()->getImagePacks(room->roomId().toStdString(), true);
 
-    mtx::events::msg::StickerImage sticker{};
-    sticker.info = img.info.value_or(mtx::common::ImageInfo{});
-    sticker.url  = img.url;
-    sticker.body = img.body.empty() ? model->shortcodeAt(row).toStdString() : img.body;
+    auto source_room = descriptor[0].toStdString();
+    auto state_key   = descriptor[1].toStdString();
+    auto short_code  = descriptor[2].toStdString();
 
-    // workaround for https://github.com/vector-im/element-ios/issues/2353
-    sticker.info.thumbnail_url           = sticker.url;
-    sticker.info.thumbnail_info.mimetype = sticker.info.mimetype;
-    sticker.info.thumbnail_info.size     = sticker.info.size;
-    sticker.info.thumbnail_info.h        = sticker.info.h;
-    sticker.info.thumbnail_info.w        = sticker.info.w;
+    for (auto &pack : originalPacks) {
+        if (pack.source_room == source_room && pack.state_key == state_key &&
+            pack.pack.images.contains(short_code)) {
+            auto img = pack.pack.images.at(short_code);
 
-    sticker.relations = generateRelations();
+            mtx::events::msg::StickerImage sticker{};
+            sticker.info = img.info.value_or(mtx::common::ImageInfo{});
+            sticker.url  = img.url;
+            sticker.body = img.body.empty() ? short_code : img.body;
 
-    room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
+            // workaround for https://github.com/vector-im/element-ios/issues/2353
+            sticker.info.thumbnail_url           = sticker.url;
+            sticker.info.thumbnail_info.mimetype = sticker.info.mimetype;
+            sticker.info.thumbnail_info.size     = sticker.info.size;
+            sticker.info.thumbnail_info.h        = sticker.info.h;
+            sticker.info.thumbnail_info.w        = sticker.info.w;
+
+            sticker.relations = generateRelations();
+
+            room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
+            break;
+        }
+    }
 }
 
 bool
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index b2db377f..1f1d6fe1 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -217,7 +217,7 @@ public slots:
                  MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
                  bool rainbowify              = false);
     void reaction(const QString &reactedEvent, const QString &reactionKey);
-    void sticker(CombinedImagePackModel *model, int row);
+    void sticker(QStringList descriptor);
 
     void acceptUploads();
     void declineUploads();
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 44f288c6..4b171dc4 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -17,6 +17,7 @@
 #include "CommandCompleter.h"
 #include "CompletionProxyModel.h"
 #include "EventAccessors.h"
+#include "GridImagePackModel.h"
 #include "ImagePackListModel.h"
 #include "InviteesModel.h"
 #include "Logging.h"
@@ -477,6 +478,9 @@ TimelineViewManager::completerFor(const QString &completerName, const QString &r
         auto proxy        = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4);
         stickerModel->setParent(proxy);
         return proxy;
+    } else if (completerName == QLatin1String("stickergrid")) {
+        auto stickerModel = new GridImagePackModel(roomId.toStdString(), true);
+        return stickerModel;
     } else if (completerName == QLatin1String("customEmoji")) {
         auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), false);
         auto proxy        = new CompletionProxyModel(stickerModel);