From a9aed09d35baf71779e5444eee7abe551fe8374f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 May 2020 00:59:05 +0200 Subject: [PATCH 01/12] Add placeholder for reactions --- resources/qml/Reactions.qml | 74 +++++++++++++++++++++++++++++++++++ resources/qml/TimelineRow.qml | 3 ++ resources/res.qrc | 3 +- 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 resources/qml/Reactions.qml diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml new file mode 100644 index 0000000..86f0071 --- /dev/null +++ b/resources/qml/Reactions.qml @@ -0,0 +1,74 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.2 + +Flow { + anchors.left: parent.left + anchors.right: parent.right + spacing: 4 + Repeater { + model: ListModel { + id: nameModel + ListElement { key: "😊"; count: 5; reactedBySelf: true; users: "Nico, RedSky, AAA, BBB, CCC" } + ListElement { key: "🤠"; count: 6; reactedBySelf: false; users: "Nico, AAA, BBB, CCC" } + ListElement { key: "💘"; count: 1; reactedBySelf: true; users: "Nico" } + ListElement { key: "🙈"; count: 7; reactedBySelf: false; users: "Nico, RedSky, AAA, BBB, CCC, DDD" } + ListElement { key: "👻"; count: 6; reactedBySelf: false; users: "Nico, RedSky, BBB, CCC" } + } + Button { + id: reaction + //border.width: 1 + text: model.key + hoverEnabled: true + implicitWidth: contentItem.childrenRect.width + contentItem.padding*2 + implicitHeight: contentItem.childrenRect.height + contentItem.padding*2 + + ToolTip.visible: hovered + ToolTip.text: model.users + + contentItem: Row { + anchors.centerIn: parent + spacing: 2 + padding: 4 + + Text { + id: reactionText + text: reaction.text + font: reaction.font + opacity: enabled ? 1.0 : 0.3 + color: reaction.hovered ? colors.highlight : colors.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + Rectangle { + height: reactionText.implicitHeight + width: 1 + color: reaction.hovered ? colors.highlight : colors.buttonText + } + + Text { + text: model.count + font: reaction.font + opacity: enabled ? 1.0 : 0.3 + color: reaction.hovered ? colors.highlight : colors.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + } + + background: Rectangle { + anchors.centerIn: parent + implicitWidth: reaction.implicitWidth + implicitHeight: reaction.implicitHeight + opacity: enabled ? 1 : 0.3 + border.color: (reaction.hovered || model.reactedBySelf )? colors.highlight : colors.buttonText + color: colors.dark + border.width: 1 + radius: reaction.height / 2.0 + } + } + } +} + diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 05c6911..f3262fb 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -52,6 +52,9 @@ MouseArea { modelData: model } + + Reactions { + } } StatusIndicator { diff --git a/resources/res.qrc b/resources/res.qrc index c6daaa8..64a5b3c 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -117,8 +117,9 @@ qml/MatrixText.qml qml/StatusIndicator.qml qml/EncryptionIndicator.qml - qml/TimelineRow.qml + qml/Reactions.qml qml/ScrollHelper.qml + qml/TimelineRow.qml qml/delegates/MessageDelegate.qml qml/delegates/TextMessage.qml qml/delegates/NoticeMessage.qml From 54013e4a00b49721b79f141bca101523a1e82282 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 May 2020 13:14:54 +0200 Subject: [PATCH 02/12] Basic, broken reaction display --- CMakeLists.txt | 4 +- io.github.NhekoReborn.Nheko.json | 4 +- resources/qml/Reactions.qml | 20 +++---- resources/qml/TimelineRow.qml | 1 + resources/qml/TimelineView.qml | 1 + src/Olm.cpp | 4 +- src/timeline/ReactionsModel.cpp | 98 ++++++++++++++++++++++++++++++++ src/timeline/ReactionsModel.h | 39 +++++++++++++ src/timeline/TimelineModel.cpp | 29 ++++++++++ src/timeline/TimelineModel.h | 3 + 10 files changed, 186 insertions(+), 17 deletions(-) create mode 100644 src/timeline/ReactionsModel.cpp create mode 100644 src/timeline/ReactionsModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 210340a..10a49dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -249,6 +249,7 @@ set(SRC_FILES src/emoji/Provider.cpp # Timeline + src/timeline/ReactionsModel.cpp src/timeline/TimelineViewManager.cpp src/timeline/TimelineModel.cpp src/timeline/DelegateChooser.cpp @@ -335,7 +336,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG v0.3.0 + GIT_TAG 1893cd6171c40c250ca64d388c082789452340a8 ) FetchContent_MakeAvailable(MatrixClient) else() @@ -451,6 +452,7 @@ qt5_wrap_cpp(MOC_HEADERS src/emoji/PickButton.h # Timeline + src/timeline/ReactionsModel.h src/timeline/TimelineViewManager.h src/timeline/TimelineModel.h src/timeline/DelegateChooser.h diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 00e9430..fe3a4a2 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -146,9 +146,9 @@ "name": "mtxclient", "sources": [ { - "sha256": "0c2930b5861d93bab9a6515adca74ebaa78984119705d9b4372a9deb275dd30c", + "sha256": "a8c0239b7157fe8eadae8b06cd6c4e3531dcc61fc5a7f52dbb3c85106f70e3a5", "type": "archive", - "url": "https://github.com/Nheko-Reborn/mtxclient/archive/v0.3.0.tar.gz" + "url": "https://github.com/Nheko-Reborn/mtxclient/archive/1893cd6171c40c250ca64d388c082789452340a8.tar.gz" } ] }, diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml index 86f0071..c15f0b3 100644 --- a/resources/qml/Reactions.qml +++ b/resources/qml/Reactions.qml @@ -5,18 +5,14 @@ Flow { anchors.left: parent.left anchors.right: parent.right spacing: 4 + + property alias reactions: repeater.model + Repeater { - model: ListModel { - id: nameModel - ListElement { key: "😊"; count: 5; reactedBySelf: true; users: "Nico, RedSky, AAA, BBB, CCC" } - ListElement { key: "🤠"; count: 6; reactedBySelf: false; users: "Nico, AAA, BBB, CCC" } - ListElement { key: "💘"; count: 1; reactedBySelf: true; users: "Nico" } - ListElement { key: "🙈"; count: 7; reactedBySelf: false; users: "Nico, RedSky, AAA, BBB, CCC, DDD" } - ListElement { key: "👻"; count: 6; reactedBySelf: false; users: "Nico, RedSky, BBB, CCC" } - } + id: repeater + Button { id: reaction - //border.width: 1 text: model.key hoverEnabled: true implicitWidth: contentItem.childrenRect.width + contentItem.padding*2 @@ -33,7 +29,7 @@ Flow { Text { id: reactionText text: reaction.text - font: reaction.font + font.family: settings.emoji_font_family opacity: enabled ? 1.0 : 0.3 color: reaction.hovered ? colors.highlight : colors.buttonText horizontalAlignment: Text.AlignHCenter @@ -48,7 +44,7 @@ Flow { } Text { - text: model.count + text: model.counter font: reaction.font opacity: enabled ? 1.0 : 0.3 color: reaction.hovered ? colors.highlight : colors.buttonText @@ -63,7 +59,7 @@ Flow { implicitWidth: reaction.implicitWidth implicitHeight: reaction.implicitHeight opacity: enabled ? 1 : 0.3 - border.color: (reaction.hovered || model.reactedBySelf )? colors.highlight : colors.buttonText + border.color: (reaction.hovered || model.selfReacted )? colors.highlight : colors.buttonText color: colors.dark border.width: 1 radius: reaction.height / 2.0 diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index f3262fb..22222ef 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -54,6 +54,7 @@ MouseArea { } Reactions { + reactions: model.reactions } } diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 997f901..28d282a 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -25,6 +25,7 @@ Page { id: settings category: "user" property bool avatar_circles: true + property string emoji_font_family: "default" } Settings { diff --git a/src/Olm.cpp b/src/Olm.cpp index c8e4c13..8ea3956 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -164,8 +164,8 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, using namespace mtx::events; // relations shouldn't be encrypted... - mtx::common::RelatesTo relation; - if (body["content"].count("m.relates_to") != 0) { + mtx::common::ReplyRelatesTo relation; + if (body["content"]["m.relates_to"].contains("m.in_reply_to")) { relation = body["content"]["m.relates_to"]; body["content"].erase("m.relates_to"); } diff --git a/src/timeline/ReactionsModel.cpp b/src/timeline/ReactionsModel.cpp new file mode 100644 index 0000000..fd061b1 --- /dev/null +++ b/src/timeline/ReactionsModel.cpp @@ -0,0 +1,98 @@ +#include "ReactionsModel.h" + +#include + +#include "Logging.h" + +QHash +ReactionsModel::roleNames() const +{ + return { + {Key, "key"}, + {Count, "counter"}, + {Users, "users"}, + {SelfReacted, "selfReacted"}, + }; +} + +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; + for (size_t r = 0; r < reactions[i].reactions.size(); r++) { + if (r != 0) + users += ", "; + users += QString::fromStdString(reactions[i].reactions[r].sender); + } + return users; + } + case SelfReacted: + for (const auto &reaction : reactions[i].reactions) + if (reaction.sender == http::client()->user_id().to_string()) + return true; + return false; + default: + return {}; + } +} + +void +ReactionsModel::addReaction(const mtx::events::RoomEvent &reaction) +{ + int idx = 0; + for (auto &storedReactions : reactions) { + if (storedReactions.key == reaction.content.relates_to.key) { + storedReactions.reactions.push_back(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}}); + endInsertRows(); +} + +void +ReactionsModel::removeReaction(const mtx::events::RoomEvent &reaction) +{ + int idx = 0; + for (auto &storedReactions : reactions) { + if (storedReactions.key == reaction.content.relates_to.key) { + for (auto it = begin(storedReactions.reactions); + it != end(storedReactions.reactions); + ++it) { + if (it->event_id == reaction.event_id) { + storedReactions.reactions.erase(it); + break; + } + } + + 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 new file mode 100644 index 0000000..ba71f2b --- /dev/null +++ b/src/timeline/ReactionsModel.h @@ -0,0 +1,39 @@ +#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, + SelfReacted, + }; + + 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 mtx::events::RoomEvent &reaction); + void removeReaction(const mtx::events::RoomEvent &reaction); + +private: + struct KeyReaction + { + std::string key; + std::vector> reactions; + }; + std::vector reactions; +}; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 340bae3..0555d2b 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -223,6 +223,7 @@ TimelineModel::roleNames() const {State, "state"}, {IsEncrypted, "isEncrypted"}, {ReplyTo, "replyTo"}, + {Reactions, "reactions"}, {RoomId, "roomId"}, {RoomName, "roomName"}, {RoomTopic, "roomTopic"}, @@ -337,6 +338,11 @@ TimelineModel::data(const QString &id, int role) const } case ReplyTo: return QVariant(QString::fromStdString(in_reply_to_event(event))); + case Reactions: + if (reactions.count(id)) + return QVariant::fromValue((QObject *)&reactions.at(id)); + else + return {}; case RoomId: return QVariant(QString::fromStdString(room_id(event))); case RoomName: @@ -574,6 +580,18 @@ TimelineModel::internalAddEvents( QString redacts = QString::fromStdString(redaction->redacts); auto redacted = std::find(eventOrder.begin(), eventOrder.end(), redacts); + auto event = events.value(redacts); + if (auto reaction = + std::get_if>( + &event)) { + QString reactedTo = + QString::fromStdString(reaction->content.relates_to.event_id); + reactions[reactedTo].removeReaction(*reaction); + int idx = idToIndex(reactedTo); + if (idx >= 0) + emit dataChanged(index(idx, 0), index(idx, 0)); + } + if (redacted != eventOrder.end()) { auto redactedEvent = std::visit( [](const auto &ev) @@ -597,6 +615,17 @@ TimelineModel::internalAddEvents( continue; // don't insert redaction into timeline } + if (auto reaction = + std::get_if>(&e)) { + QString reactedTo = + QString::fromStdString(reaction->content.relates_to.event_id); + reactions[reactedTo].addReaction(*reaction); + int idx = idToIndex(reactedTo); + if (idx >= 0) + emit dataChanged(index(idx, 0), index(idx, 0)); + continue; // don't insert reaction into timeline + } + if (auto event = std::get_if>(&e)) { auto e_ = decryptEvent(*event).event; diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index cc63eca..ecb6469 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -9,6 +9,7 @@ #include #include "CacheCryptoStructs.h" +#include "ReactionsModel.h" namespace mtx::http { using RequestErr = const std::optional &; @@ -155,6 +156,7 @@ public: State, IsEncrypted, ReplyTo, + Reactions, RoomId, RoomName, RoomTopic, @@ -271,6 +273,7 @@ private: QSet read; QList pending; std::vector eventOrder; + std::map reactions; QString room_id_; QString prev_batch_token_; From 846ff33ed8ab4940e52b329431c61a5aa5109d30 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 May 2020 13:41:18 +0200 Subject: [PATCH 03/12] Position reaction emoji and text on the same baseline --- resources/qml/Reactions.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml index c15f0b3..a33d5f6 100644 --- a/resources/qml/Reactions.qml +++ b/resources/qml/Reactions.qml @@ -27,6 +27,7 @@ Flow { padding: 4 Text { + anchors.baseline: reactionCounter.baseline id: reactionText text: reaction.text font.family: settings.emoji_font_family @@ -44,6 +45,7 @@ Flow { } Text { + id: reactionCounter text: model.counter font: reaction.font opacity: enabled ? 1.0 : 0.3 From ca5490074accd61682b9cf11d92914e21efbc35e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 5 May 2020 23:43:28 +0200 Subject: [PATCH 04/12] Fix scroll to replied to message (somewhat) --- resources/qml/delegates/Reply.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index 1b1be34..90013de 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -15,7 +15,7 @@ Item { MouseArea { anchors.fill: parent preventStealing: true - onClicked: chat.positionViewAtIndex(chat.model.idToIndex(timelineManager.replyingEvent), ListView.Contain) + onClicked: chat.positionViewAtIndex(chat.model.idToIndex(modelData.id), ListView.Contain) cursorShape: Qt.PointingHandCursor } From e55a09906fa9dbb15a5e8f07d23f651ef246613d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 5 May 2020 23:43:43 +0200 Subject: [PATCH 05/12] Misc color fixes --- resources/qml/Avatar.qml | 4 ++-- resources/qml/Reactions.qml | 10 +++++----- resources/qml/TimelineView.qml | 4 ++-- resources/styles/system.qss | 6 +++--- src/UserSettingsPage.cpp | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index b100746..ed06527 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -19,7 +19,7 @@ Rectangle { verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter visible: img.status != Image.Ready - color: colors.brightText + color: colors.text } Image { @@ -43,5 +43,5 @@ Rectangle { } } } - color: colors.dark + color: colors.base } diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml index a33d5f6..c70519a 100644 --- a/resources/qml/Reactions.qml +++ b/resources/qml/Reactions.qml @@ -32,7 +32,7 @@ Flow { text: reaction.text font.family: settings.emoji_font_family opacity: enabled ? 1.0 : 0.3 - color: reaction.hovered ? colors.highlight : colors.buttonText + color: reaction.hovered ? colors.highlight : colors.text horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight @@ -41,7 +41,7 @@ Flow { Rectangle { height: reactionText.implicitHeight width: 1 - color: reaction.hovered ? colors.highlight : colors.buttonText + color: reaction.hovered ? colors.highlight : colors.text } Text { @@ -49,7 +49,7 @@ Flow { text: model.counter font: reaction.font opacity: enabled ? 1.0 : 0.3 - color: reaction.hovered ? colors.highlight : colors.buttonText + color: reaction.hovered ? colors.highlight : colors.text horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight @@ -61,8 +61,8 @@ Flow { implicitWidth: reaction.implicitWidth implicitHeight: reaction.implicitHeight opacity: enabled ? 1 : 0.3 - border.color: (reaction.hovered || model.selfReacted )? colors.highlight : colors.buttonText - color: colors.dark + border.color: (reaction.hovered || model.selfReacted )? colors.highlight : colors.text + color: colors.base border.width: 1 radius: reaction.height / 2.0 } diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 28d282a..a63ca04 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -211,7 +211,7 @@ Page { anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined visible: section.includes(" ") text: chat.model.formatDateSeparator(modelData.timestamp) - color: colors.brightText + color: colors.text height: fontMetrics.height * 1.4 width: contentWidth * 1.2 @@ -219,7 +219,7 @@ Page { horizontalAlignment: Text.AlignHCenter background: Rectangle { radius: parent.height / 2 - color: colors.dark + color: colors.base } } Row { diff --git a/resources/styles/system.qss b/resources/styles/system.qss index dd2a90e..01951af 100644 --- a/resources/styles/system.qss +++ b/resources/styles/system.qss @@ -98,15 +98,15 @@ UserMentionsWidget { qproperty-highlightedTitleColor: palette(highlighted-text); qproperty-highlightedSubtitleColor: palette(highlighted-text); - qproperty-hoverTitleColor: palette(highlightedtext); - qproperty-hoverSubtitleColor: palette(highlightedtext); + qproperty-hoverTitleColor: palette(dark); + qproperty-hoverSubtitleColor: palette(dark); qproperty-btnColor: palette(dark); qproperty-btnTextColor: palette(bright-text); qproperty-timestampColor: palette(text); qproperty-highlightedTimestampColor: palette(highlighted-text); - qproperty-hoverTimestampColor: palette(highlighted-text); + qproperty-hoverTimestampColor: palette(dark); qproperty-bubbleBgColor: palette(base); qproperty-bubbleFgColor: palette(text); diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 54bce52..e19aa87 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -115,7 +115,7 @@ UserSettings::applyTheme() /*mid*/ QColor(110, 110, 110), /*text*/ QColor("#333"), /*bright_text*/ QColor("#333"), - /*base*/ QColor("white"), + /*base*/ QColor("#eee"), /*window*/ QColor("white")); lightActive.setColor(QPalette::Highlight, QColor("#38a3d8")); lightActive.setColor(QPalette::ToolTipBase, lightActive.base().color()); From a1951056dacf43199319cc5c41c162d03fd25ea6 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 May 2020 03:40:24 +0200 Subject: [PATCH 06/12] Reaction and Button layout fixes --- resources/qml/ImageButton.qml | 8 +------- resources/qml/Reactions.qml | 36 +++++++++++++++++++---------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml index dc576e1..dd10050 100644 --- a/resources/qml/ImageButton.qml +++ b/resources/qml/ImageButton.qml @@ -1,17 +1,11 @@ import QtQuick 2.3 import QtQuick.Controls 2.3 -Button { +AbstractButton { property string image: undefined id: button - flat: true - - // disable background, because we don't want a border on hover - background: Item { - } - Image { id: buttonImg // Workaround, can't get icon.source working for now... diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml index c70519a..cb15b72 100644 --- a/resources/qml/Reactions.qml +++ b/resources/qml/Reactions.qml @@ -11,48 +11,53 @@ Flow { Repeater { id: repeater - Button { + AbstractButton { id: reaction text: model.key hoverEnabled: true - implicitWidth: contentItem.childrenRect.width + contentItem.padding*2 - implicitHeight: contentItem.childrenRect.height + contentItem.padding*2 + implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding*2 + implicitHeight: contentItem.childrenRect.height ToolTip.visible: hovered ToolTip.text: model.users + contentItem: Row { anchors.centerIn: parent - spacing: 2 - padding: 4 + spacing: reactionText.implicitHeight/4 + leftPadding: reactionText.implicitHeight / 2 + rightPadding: reactionText.implicitHeight / 2 + + TextMetrics { + id: textMetrics + font.family: settings.emoji_font_family + elide: Text.ElideRight + elideWidth: 150 + text: reaction.text + } Text { anchors.baseline: reactionCounter.baseline id: reactionText - text: reaction.text + text: textMetrics.elidedText + (textMetrics.elidedText == textMetrics.text ? "" : "…") font.family: settings.emoji_font_family - opacity: enabled ? 1.0 : 0.3 color: reaction.hovered ? colors.highlight : colors.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight + maximumLineCount: 1 } Rectangle { - height: reactionText.implicitHeight + id: divider + height: reactionCounter.implicitHeight * 1.4 width: 1 color: reaction.hovered ? colors.highlight : colors.text } Text { + anchors.verticalCenter: divider.verticalCenter id: reactionCounter text: model.counter font: reaction.font - opacity: enabled ? 1.0 : 0.3 color: reaction.hovered ? colors.highlight : colors.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight } } @@ -60,7 +65,6 @@ Flow { anchors.centerIn: parent implicitWidth: reaction.implicitWidth implicitHeight: reaction.implicitHeight - opacity: enabled ? 1 : 0.3 border.color: (reaction.hovered || model.selfReacted )? colors.highlight : colors.text color: colors.base border.width: 1 From 8348a6c35dfd49e1711488e8972db841c8daa137 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 May 2020 11:15:45 +0200 Subject: [PATCH 07/12] Fix reaction count --- src/timeline/ReactionsModel.cpp | 25 +++++++++++-------------- src/timeline/ReactionsModel.h | 2 +- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/timeline/ReactionsModel.cpp b/src/timeline/ReactionsModel.cpp index fd061b1..f21b1c5 100644 --- a/src/timeline/ReactionsModel.cpp +++ b/src/timeline/ReactionsModel.cpp @@ -35,16 +35,19 @@ ReactionsModel::data(const QModelIndex &index, int role) const return static_cast(reactions[i].reactions.size()); case Users: { QString users; - for (size_t r = 0; r < reactions[i].reactions.size(); r++) { - if (r != 0) + bool first = true; + for (const auto &[event_id, reaction] : reactions[i].reactions) { + if (!first) users += ", "; - users += QString::fromStdString(reactions[i].reactions[r].sender); + else + first = false; + users += QString::fromStdString(reaction.sender); } return users; } case SelfReacted: for (const auto &reaction : reactions[i].reactions) - if (reaction.sender == http::client()->user_id().to_string()) + if (reaction.second.sender == http::client()->user_id().to_string()) return true; return false; default: @@ -58,7 +61,7 @@ ReactionsModel::addReaction(const mtx::events::RoomEventevent_id == reaction.event_id) { - storedReactions.reactions.erase(it); - break; - } - } + storedReactions.reactions.erase(reaction.event_id); if (storedReactions.reactions.size() == 0) { beginRemoveRows(QModelIndex(), idx, idx); diff --git a/src/timeline/ReactionsModel.h b/src/timeline/ReactionsModel.h index ba71f2b..a0a8580 100644 --- a/src/timeline/ReactionsModel.h +++ b/src/timeline/ReactionsModel.h @@ -33,7 +33,7 @@ private: struct KeyReaction { std::string key; - std::vector> reactions; + std::map> reactions; }; std::vector reactions; }; From e045e3eb1cbb3785153ca7634f76ba80b9d16bb1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 May 2020 11:28:24 +0200 Subject: [PATCH 08/12] Show displayname on reactions --- src/timeline/ReactionsModel.cpp | 11 +++++++---- src/timeline/ReactionsModel.h | 4 +++- src/timeline/TimelineModel.cpp | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/timeline/ReactionsModel.cpp b/src/timeline/ReactionsModel.cpp index f21b1c5..b686c75 100644 --- a/src/timeline/ReactionsModel.cpp +++ b/src/timeline/ReactionsModel.cpp @@ -1,9 +1,8 @@ #include "ReactionsModel.h" +#include #include -#include "Logging.h" - QHash ReactionsModel::roleNames() const { @@ -41,7 +40,8 @@ ReactionsModel::data(const QModelIndex &index, int role) const users += ", "; else first = false; - users += QString::fromStdString(reaction.sender); + users += + QString::fromStdString(cache::displayName(room_id_, reaction.sender)); } return users; } @@ -56,8 +56,11 @@ ReactionsModel::data(const QModelIndex &index, int role) const } void -ReactionsModel::addReaction(const mtx::events::RoomEvent &reaction) +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) { diff --git a/src/timeline/ReactionsModel.h b/src/timeline/ReactionsModel.h index a0a8580..5f61cd4 100644 --- a/src/timeline/ReactionsModel.h +++ b/src/timeline/ReactionsModel.h @@ -26,7 +26,8 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void addReaction(const mtx::events::RoomEvent &reaction); + void addReaction(const std::string &room_id, + const mtx::events::RoomEvent &reaction); void removeReaction(const mtx::events::RoomEvent &reaction); private: @@ -35,5 +36,6 @@ private: 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 0555d2b..5e57952 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -619,7 +619,7 @@ TimelineModel::internalAddEvents( std::get_if>(&e)) { QString reactedTo = QString::fromStdString(reaction->content.relates_to.event_id); - reactions[reactedTo].addReaction(*reaction); + reactions[reactedTo].addReaction(room_id_.toStdString(), *reaction); int idx = idToIndex(reactedTo); if (idx >= 0) emit dataChanged(index(idx, 0), index(idx, 0)); From ff54ce9334b03f3e5b9fbee9c7198a99a408bbce Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 May 2020 11:37:40 +0200 Subject: [PATCH 09/12] Fix rooms with a lot of reactions not paginating correctly --- src/timeline/TimelineModel.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 5e57952..75f41d1 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -469,7 +469,6 @@ TimelineModel::fetchMore(const QModelIndex &) mtx::errors::to_string(err->matrix_error.errcode), err->matrix_error.error, err->parse_error); - emit oldMessagesRetrieved(std::move(res)); setPaginationInProgress(false); return; } @@ -701,6 +700,11 @@ TimelineModel::addBackwardsEvents(const mtx::responses::Messages &msgs) } prev_batch_token_ = QString::fromStdString(msgs.end); + + if (ids.empty() && !msgs.chunk.empty()) { + // no visible events fetched, prevent loading from stopping + fetchMore(QModelIndex()); + } } QString From e5a5a66716ba8190b3d6ae1689e7e1f721563777 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 May 2020 12:52:13 +0200 Subject: [PATCH 10/12] Fix reaction redaction for real this time --- src/timeline/TimelineModel.cpp | 3 +++ src/timeline/TimelineModel.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 75f41d1..836fd59 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -42,6 +42,8 @@ struct RoomEventType switch (e.type) { case EventType::RoomKeyRequest: return qml_mtx_events::EventType::KeyRequest; + case EventType::Reaction: + return qml_mtx_events::EventType::Reaction; case EventType::RoomAliases: return qml_mtx_events::EventType::Aliases; case EventType::RoomAvatar: @@ -618,6 +620,7 @@ TimelineModel::internalAddEvents( std::get_if>(&e)) { QString reactedTo = QString::fromStdString(reaction->content.relates_to.event_id); + events.insert(id, e); reactions[reactedTo].addReaction(room_id_.toStdString(), *reaction); int idx = idToIndex(reactedTo); if (idx >= 0) diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index ecb6469..a737aac 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -30,6 +30,8 @@ enum EventType Unsupported, /// m.room_key_request KeyRequest, + /// m.reaction, + Reaction, /// m.room.aliases Aliases, /// m.room.avatar From e48dfd15fe9ae1ca0dd27fd3fb69e5acaf564833 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 May 2020 13:33:13 +0200 Subject: [PATCH 11/12] Add shortcuts to select/deselect reply targets --- resources/qml/TimelineView.qml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index a63ca04..eca646d 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -134,6 +134,21 @@ Page { sequence: StandardKey.MoveToNextPage onActivated: { chat.contentY = chat.contentY + chat.height / 2; chat.returnToBounds(); } } + Shortcut { + sequence: StandardKey.Cancel + onActivated: chat.model.reply = undefined + } + Shortcut { + sequence: "Alt+Up" + onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply? chat.model.idToIndex(chat.model.reply) + 1 : 0) + } + Shortcut { + sequence: "Alt+Down" + onActivated: { + var idx = chat.model.reply? chat.model.idToIndex(chat.model.reply) - 1 : -1 + chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined + } + } ScrollBar.vertical: ScrollBar { id: scrollbar From 0b1d3a40f4e19d4c0257bfdbe276f373414542d6 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 May 2020 13:45:38 +0200 Subject: [PATCH 12/12] Remove unused binding name --- src/timeline/ReactionsModel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/timeline/ReactionsModel.cpp b/src/timeline/ReactionsModel.cpp index b686c75..2e24981 100644 --- a/src/timeline/ReactionsModel.cpp +++ b/src/timeline/ReactionsModel.cpp @@ -35,13 +35,13 @@ ReactionsModel::data(const QModelIndex &index, int role) const case Users: { QString users; bool first = true; - for (const auto &[event_id, reaction] : reactions[i].reactions) { + for (const auto &reaction : reactions[i].reactions) { if (!first) users += ", "; else first = false; - users += - QString::fromStdString(cache::displayName(room_id_, reaction.sender)); + users += QString::fromStdString( + cache::displayName(room_id_, reaction.second.sender)); } return users; }