Fix reaction display

master
Nicolas Werner 4 years ago
parent d467568a65
commit 6f2bc908ba
  1. 4
      CMakeLists.txt
  2. 18
      resources/qml/Reactions.qml
  3. 35
      src/Cache.cpp
  4. 3
      src/Cache_p.h
  5. 70
      src/timeline/EventStore.cpp
  6. 5
      src/timeline/EventStore.h
  7. 1
      src/timeline/Reaction.cpp
  8. 24
      src/timeline/Reaction.h
  9. 98
      src/timeline/ReactionsModel.cpp
  10. 41
      src/timeline/ReactionsModel.h
  11. 3
      src/timeline/TimelineModel.cpp
  12. 1
      src/timeline/TimelineModel.h

@ -251,7 +251,7 @@ set(SRC_FILES
# Timeline # Timeline
src/timeline/EventStore.cpp src/timeline/EventStore.cpp
src/timeline/ReactionsModel.cpp src/timeline/Reaction.cpp
src/timeline/TimelineViewManager.cpp src/timeline/TimelineViewManager.cpp
src/timeline/TimelineModel.cpp src/timeline/TimelineModel.cpp
src/timeline/DelegateChooser.cpp src/timeline/DelegateChooser.cpp
@ -455,7 +455,7 @@ qt5_wrap_cpp(MOC_HEADERS
# Timeline # Timeline
src/timeline/EventStore.h src/timeline/EventStore.h
src/timeline/ReactionsModel.h src/timeline/Reaction.h
src/timeline/TimelineViewManager.h src/timeline/TimelineViewManager.h
src/timeline/TimelineModel.h src/timeline/TimelineModel.h
src/timeline/DelegateChooser.h src/timeline/DelegateChooser.h

@ -30,11 +30,11 @@ Flow {
implicitHeight: contentItem.childrenRect.height implicitHeight: contentItem.childrenRect.height
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: model.users ToolTip.text: modelData.users
onClicked: { onClicked: {
console.debug("Picked " + model.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + 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, model.key, model.selfReactedEvent) timelineManager.reactToMessage(reactionFlow.roomId, reactionFlow.eventId, modelData.key, modelData.selfReactedEvent)
} }
@ -49,13 +49,13 @@ Flow {
font.family: settings.emojiFont font.family: settings.emojiFont
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: 150 elideWidth: 150
text: model.key text: modelData.key
} }
Text { Text {
anchors.baseline: reactionCounter.baseline anchors.baseline: reactionCounter.baseline
id: reactionText id: reactionText
text: textMetrics.elidedText + (textMetrics.elidedText == model.key ? "" : "…") text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…")
font.family: settings.emojiFont font.family: settings.emojiFont
color: reaction.hovered ? colors.highlight : colors.text color: reaction.hovered ? colors.highlight : colors.text
maximumLineCount: 1 maximumLineCount: 1
@ -65,13 +65,13 @@ Flow {
id: divider id: divider
height: Math.floor(reactionCounter.implicitHeight * 1.4) height: Math.floor(reactionCounter.implicitHeight * 1.4)
width: 1 width: 1
color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text
} }
Text { Text {
anchors.verticalCenter: divider.verticalCenter anchors.verticalCenter: divider.verticalCenter
id: reactionCounter id: reactionCounter
text: model.counter text: modelData.count
font: reaction.font font: reaction.font
color: reaction.hovered ? colors.highlight : colors.text color: reaction.hovered ? colors.highlight : colors.text
} }
@ -82,8 +82,8 @@ Flow {
implicitWidth: reaction.implicitWidth implicitWidth: reaction.implicitWidth
implicitHeight: reaction.implicitHeight implicitHeight: reaction.implicitHeight
border.color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text
color: model.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : colors.base color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : colors.base
border.width: 1 border.width: 1
radius: reaction.height / 2.0 radius: reaction.height / 2.0
} }

@ -1353,6 +1353,37 @@ Cache::storeEvent(const std::string &room_id,
txn.commit(); txn.commit();
} }
std::vector<std::string>
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<std::string> 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<QString, RoomInfo> QMap<QString, RoomInfo>
Cache::roomInfo(bool withInvites) Cache::roomInfo(bool withInvites)
{ {
@ -2354,6 +2385,10 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message
std::string event_id_val; std::string event_id_val;
for (const auto &e : res.chunk) { for (const auto &e : res.chunk) {
if (std::holds_alternative<
mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(e))
continue;
auto event = mtx::accessors::serialize_event(e); auto event = mtx::accessors::serialize_event(e);
event_id_val = event["event_id"].get<std::string>(); event_id_val = event["event_id"].get<std::string>();
lmdb::val event_id = event_id_val; lmdb::val event_id = event_id_val;

@ -188,6 +188,9 @@ public:
void storeEvent(const std::string &room_id, void storeEvent(const std::string &room_id,
const std::string &event_id, const std::string &event_id,
const mtx::events::collections::TimelineEvent &event); const mtx::events::collections::TimelineEvent &event);
std::vector<std::string> relatedEvents(const std::string &room_id,
const std::string &event_id);
struct TimelineRange struct TimelineRange
{ {
uint64_t first, last; uint64_t first, last;

@ -3,12 +3,15 @@
#include <QThread> #include <QThread>
#include <QTimer> #include <QTimer>
#include "Cache.h"
#include "Cache_p.h" #include "Cache_p.h"
#include "EventAccessors.h" #include "EventAccessors.h"
#include "Logging.h" #include "Logging.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "Olm.h" #include "Olm.h"
Q_DECLARE_METATYPE(Reaction)
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::decryptedEvents_{ QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::decryptedEvents_{
1000}; 1000};
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{ QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{
@ -18,6 +21,9 @@ QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore::
EventStore::EventStore(std::string room_id, QObject *) EventStore::EventStore(std::string room_id, QObject *)
: room_id_(std::move(room_id)) : room_id_(std::move(room_id))
{ {
static auto reactionType = qRegisterMetaType<Reaction>();
(void)reactionType;
auto range = cache::client()->getTimelineRange(room_id_); auto range = cache::client()->getTimelineRange(room_id_);
if (range) { 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<std::string> users;
std::string reactedBySelf;
};
std::map<std::string, TempReaction> aggregation;
std::vector<Reaction> 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<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
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 * mtx::events::collections::TimelineEvents *
EventStore::event(int idx, bool decrypt) EventStore::event(int idx, bool decrypt)
{ {

@ -5,12 +5,15 @@
#include <QCache> #include <QCache>
#include <QObject> #include <QObject>
#include <QVariant>
#include <qhashfunctions.h> #include <qhashfunctions.h>
#include <mtx/events/collections.hpp> #include <mtx/events/collections.hpp>
#include <mtx/responses/messages.hpp> #include <mtx/responses/messages.hpp>
#include <mtx/responses/sync.hpp> #include <mtx/responses/sync.hpp>
#include "Reaction.h"
class EventStore : public QObject class EventStore : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -65,6 +68,8 @@ public:
// always returns a proper event as long as the idx is valid // always returns a proper event as long as the idx is valid
mtx::events::collections::TimelineEvents *event(int idx, bool decrypt = true); mtx::events::collections::TimelineEvents *event(int idx, bool decrypt = true);
QVariantList reactions(const std::string &event_id);
int size() const int size() const
{ {
return last != std::numeric_limits<uint64_t>::max() return last != std::numeric_limits<uint64_t>::max()

@ -0,0 +1 @@
#include "Reaction.h"

@ -0,0 +1,24 @@
#pragma once
#include <QObject>
#include <QString>
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_;
};

@ -1,98 +0,0 @@
#include "ReactionsModel.h"
#include <Cache.h>
#include <MatrixClient.h>
QHash<int, QByteArray>
ReactionsModel::roleNames() const
{
return {
{Key, "key"},
{Count, "counter"},
{Users, "users"},
{SelfReactedEvent, "selfReactedEvent"},
};
}
int
ReactionsModel::rowCount(const QModelIndex &) const
{
return static_cast<int>(reactions.size());
}
QVariant
ReactionsModel::data(const QModelIndex &index, int role) const
{
const int i = index.row();
if (i < 0 || i >= static_cast<int>(reactions.size()))
return {};
switch (role) {
case Key:
return QString::fromStdString(reactions[i].key);
case Count:
return static_cast<int>(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<mtx::events::msg::Reaction> &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<mtx::events::msg::Reaction> &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++;
}
}

@ -1,41 +0,0 @@
#pragma once
#include <QAbstractListModel>
#include <QHash>
#include <utility>
#include <vector>
#include <mtx/events/collections.hpp>
class ReactionsModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit ReactionsModel(QObject *parent = nullptr) { Q_UNUSED(parent); }
enum Roles
{
Key,
Count,
Users,
SelfReactedEvent,
};
QHash<int, QByteArray> 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<mtx::events::msg::Reaction> &reaction);
void removeReaction(const mtx::events::RoomEvent<mtx::events::msg::Reaction> &reaction);
private:
struct KeyReaction
{
std::string key;
std::map<std::string, mtx::events::RoomEvent<mtx::events::msg::Reaction>> reactions;
};
std::string room_id_;
std::vector<KeyReaction> reactions;
};

@ -366,7 +366,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
case ReplyTo: case ReplyTo:
return QVariant(QString::fromStdString(in_reply_to_event(event))); return QVariant(QString::fromStdString(in_reply_to_event(event)));
case Reactions: { case Reactions: {
return {}; auto id = event_id(event);
return QVariant::fromValue(events.reactions(id));
} }
case RoomId: case RoomId:
return QVariant(room_id_); return QVariant(room_id_);

@ -10,7 +10,6 @@
#include "CacheCryptoStructs.h" #include "CacheCryptoStructs.h"
#include "EventStore.h" #include "EventStore.h"
#include "ReactionsModel.h"
namespace mtx::http { namespace mtx::http {
using RequestErr = const std::optional<mtx::http::ClientError> &; using RequestErr = const std::optional<mtx::http::ClientError> &;

Loading…
Cancel
Save