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;
}
|