summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Cache.cpp7
-rw-r--r--src/Cache_p.h6
-rw-r--r--src/ImagePackModel.cpp91
-rw-r--r--src/ImagePackModel.h52
-rw-r--r--src/timeline/InputBar.cpp17
-rw-r--r--src/timeline/InputBar.h2
-rw-r--r--src/timeline/TimelineModel.cpp9
-rw-r--r--src/timeline/TimelineModel.h15
-rw-r--r--src/timeline/TimelineViewManager.cpp7
9 files changed, 202 insertions, 4 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 7b6a6135..8c3d8c42 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -3383,6 +3383,13 @@ Cache::getChildRoomIds(const std::string &room_id)
 }
 
 std::optional<mtx::events::collections::RoomAccountDataEvents>
+Cache::getAccountData(mtx::events::EventType type, const std::string &room_id)
+{
+        auto txn = ro_txn(env_);
+        return getAccountData(txn, type, room_id);
+}
+
+std::optional<mtx::events::collections::RoomAccountDataEvents>
 Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id)
 {
         try {
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<mtx::events::collections::RoomAccountDataEvents> getAccountData(
+          mtx::events::EventType type,
+          const std::string &room_id = "");
+
         //! Get a specific state event
         template<typename T>
         std::optional<mtx::events::StateEvent<T>> 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<mtx::events::msc2545::ImagePack> accountPack;
+        if (accountpackV) {
+                auto tmp =
+                  std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePack>>(
+                    &*accountpackV);
+                if (tmp)
+                        accountPack = tmp->content;
+        }
+        // mtx::events::msc2545::ImagePackRooms *enabledRoomPacks = nullptr;
+        // if (enabledRoomPacksV)
+        //        enabledRoomPacks =
+        //          std::get_if<mtx::events::msc2545::ImagePackRooms>(&*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<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 {};
+}
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 <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
+        {
+                (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<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;
+};
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"
@@ -502,6 +503,22 @@ InputBar::video(const QString &filename,
 }
 
 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)
 {
         if (command == "me") {
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 <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();
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<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();
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<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();
 }
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<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);
+                stickerModel->setParent(proxy);
+                return proxy;
         }
         return nullptr;
 }