summary refs log tree commit diff
path: root/src/timeline
diff options
context:
space:
mode:
authorJoseph Donofry <joedonofry@gmail.com>2020-06-11 22:27:17 -0400
committerGitHub <noreply@github.com>2020-06-11 22:27:17 -0400
commit813884ee0cbacf5b5b7facab445d3c7cdc712084 (patch)
treef31c6e9545b58d873484e35ea987d486046faa0d /src/timeline
parentMerge pull request #218 from z33ky/alert-notifications (diff)
parentUpdate translations (diff)
downloadnheko-813884ee0cbacf5b5b7facab445d3c7cdc712084.tar.xz
Merge pull request #217 from Nheko-Reborn/reactions
Reactions
Diffstat (limited to 'src/timeline')
-rw-r--r--src/timeline/ReactionsModel.cpp8
-rw-r--r--src/timeline/ReactionsModel.h2
-rw-r--r--src/timeline/TimelineModel.cpp65
-rw-r--r--src/timeline/TimelineModel.h1
-rw-r--r--src/timeline/TimelineViewManager.cpp44
-rw-r--r--src/timeline/TimelineViewManager.h14
6 files changed, 120 insertions, 14 deletions
diff --git a/src/timeline/ReactionsModel.cpp b/src/timeline/ReactionsModel.cpp

index 2e249819..1200e2ba 100644 --- a/src/timeline/ReactionsModel.cpp +++ b/src/timeline/ReactionsModel.cpp
@@ -10,7 +10,7 @@ ReactionsModel::roleNames() const {Key, "key"}, {Count, "counter"}, {Users, "users"}, - {SelfReacted, "selfReacted"}, + {SelfReactedEvent, "selfReactedEvent"}, }; } @@ -45,11 +45,11 @@ ReactionsModel::data(const QModelIndex &index, int role) const } return users; } - case SelfReacted: + case SelfReactedEvent: for (const auto &reaction : reactions[i].reactions) if (reaction.second.sender == http::client()->user_id().to_string()) - return true; - return false; + return QString::fromStdString(reaction.second.event_id); + return QStringLiteral(""); default: return {}; } diff --git a/src/timeline/ReactionsModel.h b/src/timeline/ReactionsModel.h
index 5f61cd42..c839afc8 100644 --- a/src/timeline/ReactionsModel.h +++ b/src/timeline/ReactionsModel.h
@@ -18,7 +18,7 @@ public: Key, Count, Users, - SelfReacted, + SelfReactedEvent, }; QHash<int, QByteArray> roleNames() const override; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index c454c6b2..16e4f207 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp
@@ -154,13 +154,25 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj connect(this, &TimelineModel::messageSent, this, [this](QString txn_id, QString event_id) { pending.removeOne(txn_id); + auto ev = events.value(txn_id); + + if (auto reaction = + std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(&ev)) { + QString reactedTo = + QString::fromStdString(reaction->content.relates_to.event_id); + auto &rModel = reactions[reactedTo]; + rModel.removeReaction(*reaction); + auto rCopy = *reaction; + rCopy.event_id = event_id.toStdString(); + rModel.addReaction(room_id_.toStdString(), rCopy); + } + int idx = idToIndex(txn_id); if (idx < 0) { // transaction already received via sync return; } eventOrder[idx] = event_id; - auto ev = events.value(txn_id); ev = std::visit( [event_id](const auto &e) -> mtx::events::collections::TimelineEvents { auto eventCopy = e; @@ -379,7 +391,7 @@ TimelineModel::data(const QString &id, int role) const else return {}; case RoomId: - return QVariant(QString::fromStdString(room_id(event))); + return QVariant(room_id_); case RoomName: return QVariant(QString::fromStdString(room_name(event))); case RoomTopic: @@ -683,6 +695,14 @@ TimelineModel::internalAddEvents( QString reactedTo = QString::fromStdString(reaction->content.relates_to.event_id); events.insert(id, e); + + // remove local echo + if (!txid.isEmpty()) { + auto rCopy = *reaction; + rCopy.event_id = txid.toStdString(); + reactions[reactedTo].removeReaction(rCopy); + } + reactions[reactedTo].addReaction(room_id_.toStdString(), *reaction); int idx = idToIndex(reactedTo); if (idx >= 0) @@ -1355,10 +1375,11 @@ struct SendMessageVisitor , model_(model) {} + // Do-nothing operator for all unhandled events template<typename T> void operator()(const mtx::events::Event<T> &) {} - + // Operator for m.room.message events that contain a msgtype in their content template<typename T, std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0> void operator()(const mtx::events::RoomEvent<T> &msg) @@ -1395,6 +1416,36 @@ struct SendMessageVisitor } } + // Special operator for reactions, which are a type of m.room.message, but need to be + // handled distinctly for their differences from normal room messages. Specifically, + // reactions need to have the relation outside of ciphertext, or synapse / the homeserver + // cannot handle it correctly. See the MSC for more details: + // https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption + void operator()(const mtx::events::RoomEvent<mtx::events::msg::Reaction> &msg) + + { + QString txn_id_qstr = txn_id_qstr_; + TimelineModel *model = model_; + http::client() + ->send_room_message<mtx::events::msg::Reaction, mtx::events::EventType::Reaction>( + model->room_id_.toStdString(), + txn_id_qstr.toStdString(), + msg.content, + [txn_id_qstr, model](const mtx::responses::EventId &res, + mtx::http::RequestErr err) { + if (err) { + const int status_code = static_cast<int>(err->status_code); + nhlog::net()->warn("[{}] failed to send message: {} {}", + txn_id_qstr.toStdString(), + err->matrix_error.error, + status_code); + emit model->messageFailed(txn_id_qstr); + } + emit model->messageSent( + txn_id_qstr, QString::fromStdString(res.event_id.to_string())); + }); + } + QString txn_id_qstr_; TimelineModel *model_; }; @@ -1426,10 +1477,12 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event) internalAddEvents({event}); QString txn_id_qstr = QString::fromStdString(mtx::accessors::event_id(event)); - beginInsertRows(QModelIndex(), 0, 0); pending.push_back(txn_id_qstr); - this->eventOrder.insert(this->eventOrder.begin(), txn_id_qstr); - endInsertRows(); + if (!std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(&event)) { + beginInsertRows(QModelIndex(), 0, 0); + this->eventOrder.insert(this->eventOrder.begin(), txn_id_qstr); + endInsertRows(); + } updateLastMessage(); emit nextPendingMessage(); diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index dc7b4985..a3b92f83 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h
@@ -217,7 +217,6 @@ public slots: } std::vector<QString> typingUsers() const { return typingUsers_; } bool paginationInProgress() const { return m_paginationInProgress; } - QString reply() const { return reply_; } void setReply(QString newReply) { diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 30abe506..d6f9fde1 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp
@@ -13,6 +13,8 @@ #include "MxcImageProvider.h" #include "UserSettingsPage.h" #include "dialogs/ImageOverlay.h" +#include "emoji/EmojiModel.h" +#include "emoji/Provider.h" Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) @@ -72,6 +74,18 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice"); qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser"); qRegisterMetaType<mtx::events::collections::TimelineEvents>(); + qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel"); + qmlRegisterType<emoji::EmojiProxyModel>("im.nheko.EmojiModel", 1, 0, "EmojiProxyModel"); + qmlRegisterUncreatableType<QAbstractItemModel>( + "im.nheko.EmojiModel", 1, 0, "QAbstractItemModel", "Used by proxy models"); + qmlRegisterUncreatableType<emoji::Emoji>( + "im.nheko.EmojiModel", 1, 0, "Emoji", "Used by emoji models"); + qmlRegisterUncreatableMetaObject(emoji::staticMetaObject, + "im.nheko.EmojiModel", + 1, + 0, + "EmojiCategory", + "Error: Only enums"); #ifdef USE_QUICK_VIEW view = new QQuickView(); @@ -284,6 +298,36 @@ TimelineViewManager::queueEmoteMessage(const QString &msg) } void +TimelineViewManager::reactToMessage(const QString &roomId, + const QString &reactedEvent, + const QString &reactionKey, + const QString &selfReactedEvent) +{ + // If selfReactedEvent is empty, that means we haven't previously reacted + if (selfReactedEvent.isEmpty()) { + queueReactionMessage(roomId, reactedEvent, reactionKey); + // Otherwise, we have previously reacted and the reaction should be redacted + } else { + auto model = models.value(roomId); + model->redactEvent(selfReactedEvent); + } +} + +void +TimelineViewManager::queueReactionMessage(const QString &roomId, + const QString &reactedEvent, + const QString &reactionKey) +{ + mtx::events::msg::Reaction reaction; + reaction.relates_to.rel_type = mtx::common::RelationType::Annotation; + reaction.relates_to.event_id = reactedEvent.toStdString(); + reaction.relates_to.key = reactionKey.toStdString(); + + auto model = models.value(roomId); + model->sendMessage(reaction); +} + +void TimelineViewManager::queueImageMessage(const QString &roomid, const QString &filename, const std::optional<mtx::crypto::EncryptedFile> &file, diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 45a603af..48505bc0 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h
@@ -1,5 +1,6 @@ #pragma once +#include <QHash> #include <QQuickView> #include <QQuickWidget> #include <QSharedPointer> @@ -12,6 +13,8 @@ #include "Logging.h" #include "TimelineModel.h" #include "Utils.h" +#include "emoji/EmojiModel.h" +#include "emoji/Provider.h" class MxcImageProvider; class BlurhashProvider; @@ -55,7 +58,13 @@ public slots: void setHistoryView(const QString &room_id); void updateColorPalette(); - + void queueReactionMessage(const QString &roomId, + const QString &reactedEvent, + const QString &reaction); + void reactToMessage(const QString &roomId, + const QString &reactedEvent, + const QString &reactionKey, + const QString &selfReactedEvent); void queueTextMessage(const QString &msg); void queueEmoteMessage(const QString &msg); void queueImageMessage(const QString &roomid, @@ -100,7 +109,8 @@ private: QHash<QString, QSharedPointer<TimelineModel>> models; TimelineModel *timeline_ = nullptr; - bool isInitialSync_ = true; + + bool isInitialSync_ = true; QSharedPointer<UserSettings> settings; QHash<QString, QColor> userColors;