From 6f2bc908badc207754ff55d543d41d9e2b847c97 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 19 Jul 2020 12:22:54 +0200 Subject: [PATCH] Fix reaction display --- CMakeLists.txt | 4 +- resources/qml/Reactions.qml | 18 +++--- src/Cache.cpp | 35 ++++++++++++ src/Cache_p.h | 3 + src/timeline/EventStore.cpp | 70 +++++++++++++++++++++++ src/timeline/EventStore.h | 5 ++ src/timeline/Reaction.cpp | 1 + src/timeline/Reaction.h | 24 ++++++++ src/timeline/ReactionsModel.cpp | 98 --------------------------------- src/timeline/ReactionsModel.h | 41 -------------- src/timeline/TimelineModel.cpp | 3 +- src/timeline/TimelineModel.h | 1 - 12 files changed, 151 insertions(+), 152 deletions(-) create mode 100644 src/timeline/Reaction.cpp create mode 100644 src/timeline/Reaction.h delete mode 100644 src/timeline/ReactionsModel.cpp delete mode 100644 src/timeline/ReactionsModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d1441c1..658232e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -251,7 +251,7 @@ set(SRC_FILES # Timeline src/timeline/EventStore.cpp - src/timeline/ReactionsModel.cpp + src/timeline/Reaction.cpp src/timeline/TimelineViewManager.cpp src/timeline/TimelineModel.cpp src/timeline/DelegateChooser.cpp @@ -455,7 +455,7 @@ qt5_wrap_cpp(MOC_HEADERS # Timeline src/timeline/EventStore.h - src/timeline/ReactionsModel.h + src/timeline/Reaction.h src/timeline/TimelineViewManager.h src/timeline/TimelineModel.h src/timeline/DelegateChooser.h diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml index c06dc826..5b3bbc20 100644 --- a/resources/qml/Reactions.qml +++ b/resources/qml/Reactions.qml @@ -30,11 +30,11 @@ Flow { implicitHeight: contentItem.childrenRect.height ToolTip.visible: hovered - ToolTip.text: model.users + ToolTip.text: modelData.users onClicked: { - console.debug("Picked " + model.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + model.selfReactedEvent) - timelineManager.reactToMessage(reactionFlow.roomId, reactionFlow.eventId, model.key, model.selfReactedEvent) + console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + modelData.selfReactedEvent) + timelineManager.reactToMessage(reactionFlow.roomId, reactionFlow.eventId, modelData.key, modelData.selfReactedEvent) } @@ -49,13 +49,13 @@ Flow { font.family: settings.emojiFont elide: Text.ElideRight elideWidth: 150 - text: model.key + text: modelData.key } Text { anchors.baseline: reactionCounter.baseline id: reactionText - text: textMetrics.elidedText + (textMetrics.elidedText == model.key ? "" : "…") + text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…") font.family: settings.emojiFont color: reaction.hovered ? colors.highlight : colors.text maximumLineCount: 1 @@ -65,13 +65,13 @@ Flow { id: divider height: Math.floor(reactionCounter.implicitHeight * 1.4) width: 1 - color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text + color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text } Text { anchors.verticalCenter: divider.verticalCenter id: reactionCounter - text: model.counter + text: modelData.count font: reaction.font color: reaction.hovered ? colors.highlight : colors.text } @@ -82,8 +82,8 @@ Flow { implicitWidth: reaction.implicitWidth implicitHeight: reaction.implicitHeight - border.color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text - color: model.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : colors.base + border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text + color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : colors.base border.width: 1 radius: reaction.height / 2.0 } diff --git a/src/Cache.cpp b/src/Cache.cpp index 9464a546..0307bee1 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1353,6 +1353,37 @@ Cache::storeEvent(const std::string &room_id, txn.commit(); } +std::vector +Cache::relatedEvents(const std::string &room_id, const std::string &event_id) +{ + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + auto relationsDb = getRelationsDb(txn, room_id); + + std::vector related_ids; + + auto related_cursor = lmdb::cursor::open(txn, relationsDb); + lmdb::val related_to = event_id, related_event; + bool first = true; + + try { + if (!related_cursor.get(related_to, related_event, MDB_SET)) + return {}; + + while (related_cursor.get( + related_to, related_event, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { + first = false; + if (event_id != std::string_view(related_to.data(), related_to.size())) + break; + + related_ids.emplace_back(related_event.data(), related_event.size()); + } + } catch (const lmdb::error &e) { + nhlog::db()->error("related events error: {}", e.what()); + } + + return related_ids; +} + QMap Cache::roomInfo(bool withInvites) { @@ -2354,6 +2385,10 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message std::string event_id_val; for (const auto &e : res.chunk) { + if (std::holds_alternative< + mtx::events::RedactionEvent>(e)) + continue; + auto event = mtx::accessors::serialize_event(e); event_id_val = event["event_id"].get(); lmdb::val event_id = event_id_val; diff --git a/src/Cache_p.h b/src/Cache_p.h index 88308e45..61d91b0c 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -188,6 +188,9 @@ public: void storeEvent(const std::string &room_id, const std::string &event_id, const mtx::events::collections::TimelineEvent &event); + std::vector relatedEvents(const std::string &room_id, + const std::string &event_id); + struct TimelineRange { uint64_t first, last; diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 80e8d474..0bd7a97e 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -3,12 +3,15 @@ #include #include +#include "Cache.h" #include "Cache_p.h" #include "EventAccessors.h" #include "Logging.h" #include "MatrixClient.h" #include "Olm.h" +Q_DECLARE_METATYPE(Reaction) + QCache EventStore::decryptedEvents_{ 1000}; QCache EventStore::events_by_id_{ @@ -18,6 +21,9 @@ QCache EventStore:: EventStore::EventStore(std::string room_id, QObject *) : room_id_(std::move(room_id)) { + static auto reactionType = qRegisterMetaType(); + (void)reactionType; + auto range = cache::client()->getTimelineRange(room_id_); if (range) { @@ -223,6 +229,70 @@ EventStore::handleSync(const mtx::responses::Timeline &events) } } +QVariantList +EventStore::reactions(const std::string &event_id) +{ + auto event_ids = cache::client()->relatedEvents(room_id_, event_id); + + struct TempReaction + { + int count = 0; + std::vector users; + std::string reactedBySelf; + }; + std::map aggregation; + std::vector reactions; + + auto self = http::client()->user_id().to_string(); + for (const auto &id : event_ids) { + auto related_event = event(id, event_id); + if (!related_event) + continue; + + if (auto reaction = std::get_if>( + related_event)) { + auto &agg = aggregation[reaction->content.relates_to.key]; + + if (agg.count == 0) { + Reaction temp{}; + temp.key_ = + QString::fromStdString(reaction->content.relates_to.key); + reactions.push_back(temp); + } + + agg.count++; + agg.users.push_back(cache::displayName(room_id_, reaction->sender)); + if (reaction->sender == self) + agg.reactedBySelf = reaction->event_id; + } + } + + QVariantList temp; + for (auto &reaction : reactions) { + const auto &agg = aggregation[reaction.key_.toStdString()]; + reaction.count_ = agg.count; + reaction.selfReactedEvent_ = QString::fromStdString(agg.reactedBySelf); + + bool first = true; + for (const auto &user : agg.users) { + if (first) + first = false; + else + reaction.users_ += ", "; + + reaction.users_ += QString::fromStdString(user); + } + + nhlog::db()->debug("key: {}, count: {}, users: {}", + reaction.key_.toStdString(), + reaction.count_, + reaction.users_.toStdString()); + temp.append(QVariant::fromValue(reaction)); + } + + return temp; +} + mtx::events::collections::TimelineEvents * EventStore::event(int idx, bool decrypt) { diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h index 3a78cba8..5a792040 100644 --- a/src/timeline/EventStore.h +++ b/src/timeline/EventStore.h @@ -5,12 +5,15 @@ #include #include +#include #include #include #include #include +#include "Reaction.h" + class EventStore : public QObject { Q_OBJECT @@ -65,6 +68,8 @@ public: // always returns a proper event as long as the idx is valid mtx::events::collections::TimelineEvents *event(int idx, bool decrypt = true); + QVariantList reactions(const std::string &event_id); + int size() const { return last != std::numeric_limits::max() diff --git a/src/timeline/Reaction.cpp b/src/timeline/Reaction.cpp new file mode 100644 index 00000000..343c4649 --- /dev/null +++ b/src/timeline/Reaction.cpp @@ -0,0 +1 @@ +#include "Reaction.h" diff --git a/src/timeline/Reaction.h b/src/timeline/Reaction.h new file mode 100644 index 00000000..5f122e0a --- /dev/null +++ b/src/timeline/Reaction.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +struct Reaction +{ + Q_GADGET + Q_PROPERTY(QString key READ key) + Q_PROPERTY(QString users READ users) + Q_PROPERTY(QString selfReactedEvent READ selfReactedEvent) + Q_PROPERTY(int count READ count) + +public: + QString key() const { return key_; } + QString users() const { return users_; } + QString selfReactedEvent() const { return selfReactedEvent_; } + int count() const { return count_; } + + QString key_; + QString users_; + QString selfReactedEvent_; + int count_; +}; diff --git a/src/timeline/ReactionsModel.cpp b/src/timeline/ReactionsModel.cpp deleted file mode 100644 index 1200e2ba..00000000 --- a/src/timeline/ReactionsModel.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "ReactionsModel.h" - -#include -#include - -QHash -ReactionsModel::roleNames() const -{ - return { - {Key, "key"}, - {Count, "counter"}, - {Users, "users"}, - {SelfReactedEvent, "selfReactedEvent"}, - }; -} - -int -ReactionsModel::rowCount(const QModelIndex &) const -{ - return static_cast(reactions.size()); -} - -QVariant -ReactionsModel::data(const QModelIndex &index, int role) const -{ - const int i = index.row(); - if (i < 0 || i >= static_cast(reactions.size())) - return {}; - - switch (role) { - case Key: - return QString::fromStdString(reactions[i].key); - case Count: - return static_cast(reactions[i].reactions.size()); - case Users: { - QString users; - bool first = true; - for (const auto &reaction : reactions[i].reactions) { - if (!first) - users += ", "; - else - first = false; - users += QString::fromStdString( - cache::displayName(room_id_, reaction.second.sender)); - } - return users; - } - case SelfReactedEvent: - for (const auto &reaction : reactions[i].reactions) - if (reaction.second.sender == http::client()->user_id().to_string()) - return QString::fromStdString(reaction.second.event_id); - return QStringLiteral(""); - default: - return {}; - } -} - -void -ReactionsModel::addReaction(const std::string &room_id, - const mtx::events::RoomEvent &reaction) -{ - room_id_ = room_id; - - int idx = 0; - for (auto &storedReactions : reactions) { - if (storedReactions.key == reaction.content.relates_to.key) { - storedReactions.reactions[reaction.event_id] = reaction; - emit dataChanged(index(idx, 0), index(idx, 0)); - return; - } - idx++; - } - - beginInsertRows(QModelIndex(), idx, idx); - reactions.push_back( - KeyReaction{reaction.content.relates_to.key, {{reaction.event_id, reaction}}}); - endInsertRows(); -} - -void -ReactionsModel::removeReaction(const mtx::events::RoomEvent &reaction) -{ - int idx = 0; - for (auto &storedReactions : reactions) { - if (storedReactions.key == reaction.content.relates_to.key) { - storedReactions.reactions.erase(reaction.event_id); - - if (storedReactions.reactions.size() == 0) { - beginRemoveRows(QModelIndex(), idx, idx); - reactions.erase(reactions.begin() + idx); - endRemoveRows(); - } else - emit dataChanged(index(idx, 0), index(idx, 0)); - return; - } - idx++; - } -} diff --git a/src/timeline/ReactionsModel.h b/src/timeline/ReactionsModel.h deleted file mode 100644 index c839afc8..00000000 --- a/src/timeline/ReactionsModel.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -#include - -class ReactionsModel : public QAbstractListModel -{ - Q_OBJECT -public: - explicit ReactionsModel(QObject *parent = nullptr) { Q_UNUSED(parent); } - enum Roles - { - Key, - Count, - Users, - SelfReactedEvent, - }; - - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - -public slots: - void addReaction(const std::string &room_id, - const mtx::events::RoomEvent &reaction); - void removeReaction(const mtx::events::RoomEvent &reaction); - -private: - struct KeyReaction - { - std::string key; - std::map> reactions; - }; - std::string room_id_; - std::vector reactions; -}; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 470e3988..85d2eb4e 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -366,7 +366,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r case ReplyTo: return QVariant(QString::fromStdString(in_reply_to_event(event))); case Reactions: { - return {}; + auto id = event_id(event); + return QVariant::fromValue(events.reactions(id)); } case RoomId: return QVariant(room_id_); diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 9f9717df..cbe88fd2 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -10,7 +10,6 @@ #include "CacheCryptoStructs.h" #include "EventStore.h" -#include "ReactionsModel.h" namespace mtx::http { using RequestErr = const std::optional &;