From 992cbaca87b3df1508c07cc3f62ac200decd1209 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 21 Jul 2019 13:01:33 +0800 Subject: [PATCH 01/16] Clean up reply. --- CMakeLists.txt | 2 ++ .../Component/Timeline/MessageDelegate.qml | 14 +++++------ include/libQuotient | 2 +- src/messageeventmodel.cpp | 24 +++++++------------ src/messageeventmodel.h | 8 +++---- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 30e5885ab..d665794f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,8 @@ if (Qt5_POSITION_INDEPENDENT_CODE) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() +set(QML_IMPORT_PATH ${CMAKE_SOURCE_DIR}/qml ${CMAKE_BINARY_DIR}/imports CACHE string "" FORCE) + if(WIN32) enable_language(RC) include(CMakeDetermineRCCompiler) diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index 597e46e77..2531ff9e6 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -15,7 +15,7 @@ ColumnLayout { readonly property bool avatarVisible: !sentByMe && showAuthor readonly property bool sentByMe: author === currentRoom.localUser readonly property bool darkBackground: !sentByMe - readonly property bool replyVisible: replyEventId || false + readonly property bool replyVisible: reply || false signal saveFileAs() signal openExternally() @@ -144,15 +144,15 @@ ColumnLayout { Layout.preferredHeight: 28 Layout.alignment: Qt.AlignTop - source: replyVisible ? replyAuthor.avatarMediaId : "" - hint: replyVisible ? replyAuthor.displayName : "H" + source: replyVisible ? reply.author.avatarMediaId : "" + hint: replyVisible ? reply.author.displayName : "H" RippleEffect { anchors.fill: parent circular: true - onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": replyAuthor}).open() + onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": reply.author}).open() } } @@ -160,7 +160,7 @@ ColumnLayout { Layout.fillWidth: true color: !sentByMe ? MPalette.foreground : "white" - text: "" + (replyDisplay || "") + text: "" + (replyVisible ? reply.display : "") wrapMode: Label.Wrap textFormat: Label.RichText @@ -168,13 +168,13 @@ ColumnLayout { } background: Rectangle { - color: replyAuthor && sentByMe ? replyAuthor.color : MPalette.background + color: replyVisible && sentByMe ? reply.author.color : MPalette.background radius: 18 AutoMouseArea { anchors.fill: parent - onClicked: goToEvent(replyEventId) + onClicked: goToEvent(reply.eventId) } } } diff --git a/include/libQuotient b/include/libQuotient index 74caea266..2fb22758c 160000 --- a/include/libQuotient +++ b/include/libQuotient @@ -1 +1 @@ -Subproject commit 74caea2669b8f76ca76507bc40321fdcd23dc522 +Subproject commit 2fb22758c1668d65e9400fe7d35857ddc54fcc6a diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 59577f4a9..c1f5a54ae 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -30,9 +30,7 @@ QHash MessageEventModel::roleNames() const { roles[LongOperationRole] = "progressInfo"; roles[AnnotationRole] = "annotation"; roles[EventResolvedTypeRole] = "eventResolvedType"; - roles[ReplyEventIdRole] = "replyEventId"; - roles[ReplyAuthorRole] = "replyAuthor"; - roles[ReplyDisplayRole] = "replyDisplay"; + roles[ReplyRole] = "reply"; roles[UserMarkerRole] = "userMarker"; roles[ShowAuthorRole] = "showAuthor"; roles[ShowSectionRole] = "showSection"; @@ -404,8 +402,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { return variantList; } - if (role == ReplyEventIdRole || role == ReplyDisplayRole || - role == ReplyAuthorRole) { + if (role == ReplyRole) { const QString& replyEventId = evt.contentJson()["m.relates_to"] .toObject()["m.in_reply_to"] .toObject()["event_id"] @@ -416,16 +413,13 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { if (replyIt == m_currentRoom->timelineEdge()) return {}; const auto& replyEvt = **replyIt; - switch (role) { - case ReplyEventIdRole: - return replyEventId; - case ReplyDisplayRole: - return utils::cleanHTML(utils::removeReply( - m_currentRoom->eventToString(replyEvt, Qt::RichText))); - case ReplyAuthorRole: - return QVariant::fromValue(m_currentRoom->user(replyEvt.senderId())); - } - return {}; + + return QVariantMap{ + {"eventId", replyEventId}, + {"display", utils::cleanHTML(utils::removeReply( + m_currentRoom->eventToString(replyEvt, Qt::RichText)))}, + {"author", QVariant::fromValue(m_currentRoom->user(replyEvt.senderId()))} + }; } if (role == ShowAuthorRole) { diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index 3def157f9..7933b4ea6 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -26,14 +26,14 @@ class MessageEventModel : public QAbstractListModel { LongOperationRole, AnnotationRole, UserMarkerRole, - // For reply - ReplyEventIdRole, - ReplyAuthorRole, - ReplyDisplayRole, + + ReplyRole, ShowAuthorRole, ShowSectionRole, + BubbleShapeRole, + // For debugging EventResolvedTypeRole, }; From 761943f98fa455da58bb2cacaa05689525038a6b Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 21 Jul 2019 16:11:26 +0800 Subject: [PATCH 02/16] Display reactions in MessageDelegate. --- .../Component/Timeline/MessageDelegate.qml | 6 +++ .../Component/Timeline/ReactionDelegate.qml | 28 +++++++++++++ imports/Spectral/Component/Timeline/qmldir | 1 + res.qrc | 1 + src/messageeventmodel.cpp | 39 +++++++++++++++++-- src/messageeventmodel.h | 2 + 6 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 imports/Spectral/Component/Timeline/ReactionDelegate.qml diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index 2531ff9e6..c6a77c8aa 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -220,6 +220,12 @@ ColumnLayout { cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor } } + + ReactionDelegate { + Layout.bottomMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + } } } } diff --git a/imports/Spectral/Component/Timeline/ReactionDelegate.qml b/imports/Spectral/Component/Timeline/ReactionDelegate.qml new file mode 100644 index 000000000..9865f5eb7 --- /dev/null +++ b/imports/Spectral/Component/Timeline/ReactionDelegate.qml @@ -0,0 +1,28 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import Spectral.Setting 0.1 + +RowLayout { + visible: reaction || false + + Repeater { + model: reaction + + delegate: Control { + horizontalPadding: 4 + verticalPadding: 0 + + background: Rectangle { + radius: height / 2 + color: MPalette.banner + } + + contentItem: Label { + text: modelData.reaction + " " + modelData.count + font.pixelSize: 10 + } + } + } +} + diff --git a/imports/Spectral/Component/Timeline/qmldir b/imports/Spectral/Component/Timeline/qmldir index 03ed87550..e1f5b7b4e 100644 --- a/imports/Spectral/Component/Timeline/qmldir +++ b/imports/Spectral/Component/Timeline/qmldir @@ -5,3 +5,4 @@ SectionDelegate 2.0 SectionDelegate.qml ImageDelegate 2.0 ImageDelegate.qml FileDelegate 2.0 FileDelegate.qml VideoDelegate 2.0 VideoDelegate.qml +ReactionDelegate 2.0 ReactionDelegate.qml diff --git a/res.qrc b/res.qrc index a65bdab65..0827f905b 100644 --- a/res.qrc +++ b/res.qrc @@ -57,5 +57,6 @@ imports/Spectral/Dialog/OpenFolderDialog.qml imports/Spectral/Component/Timeline/VideoDelegate.qml imports/Spectral/Component/AutoRectangle.qml + imports/Spectral/Component/Timeline/ReactionDelegate.qml diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index c1f5a54ae..bffae7ca2 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -35,6 +36,7 @@ QHash MessageEventModel::roleNames() const { roles[ShowAuthorRole] = "showAuthor"; roles[ShowSectionRole] = "showSection"; roles[BubbleShapeRole] = "bubbleShape"; + roles[ReactionRole] = "reaction"; return roles; } @@ -357,7 +359,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { return EventStatus::Hidden; } - if (is(evt)) + if (is(evt) || is(evt)) return EventStatus::Hidden; if (evt.isRedacted()) return EventStatus::Hidden; @@ -417,9 +419,9 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { return QVariantMap{ {"eventId", replyEventId}, {"display", utils::cleanHTML(utils::removeReply( - m_currentRoom->eventToString(replyEvt, Qt::RichText)))}, - {"author", QVariant::fromValue(m_currentRoom->user(replyEvt.senderId()))} - }; + m_currentRoom->eventToString(replyEvt, Qt::RichText)))}, + {"author", + QVariant::fromValue(m_currentRoom->user(replyEvt.senderId()))}}; } if (role == ShowAuthorRole) { @@ -478,6 +480,35 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { return BubbleShapes::MiddleShape; } + if (role == ReactionRole) { + const auto& annotations = + m_currentRoom->relatedEvents(evt, EventRelation::Annotation()); + if (annotations.isEmpty()) + return {}; + QMap> reactions; + for (const auto& a : annotations) { + if (auto e = eventCast(a)) + reactions[e->relation().key].append( + static_cast(m_currentRoom->user(e->senderId()))); + } + + QVariantList res; + QMap>::const_iterator i = + reactions.constBegin(); + while (i != reactions.constEnd()) { + QVariantList authors; + for (auto author : i.value()) { + authors.append(QVariant::fromValue(author)); + } + res.append(QVariantMap{{"reaction", i.key()}, + {"count", i.value().count()}, + {"authors", authors}}); + ++i; + } + + return res; + } + return {}; } diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index 7933b4ea6..38dd301b0 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -34,6 +34,8 @@ class MessageEventModel : public QAbstractListModel { BubbleShapeRole, + ReactionRole, + // For debugging EventResolvedTypeRole, }; From 1de54e0ec1228e2450d6d6fc7d2de21a31e27cab Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 21 Jul 2019 20:53:22 +0800 Subject: [PATCH 03/16] Tweak reaction size. --- imports/Spectral/Component/Timeline/MessageDelegate.qml | 1 + imports/Spectral/Component/Timeline/ReactionDelegate.qml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index c6a77c8aa..6276f2c38 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -222,6 +222,7 @@ ColumnLayout { } ReactionDelegate { + Layout.topMargin: 0 Layout.bottomMargin: 8 Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/imports/Spectral/Component/Timeline/ReactionDelegate.qml b/imports/Spectral/Component/Timeline/ReactionDelegate.qml index 9865f5eb7..0ac17e324 100644 --- a/imports/Spectral/Component/Timeline/ReactionDelegate.qml +++ b/imports/Spectral/Component/Timeline/ReactionDelegate.qml @@ -10,7 +10,7 @@ RowLayout { model: reaction delegate: Control { - horizontalPadding: 4 + horizontalPadding: 6 verticalPadding: 0 background: Rectangle { @@ -20,7 +20,7 @@ RowLayout { contentItem: Label { text: modelData.reaction + " " + modelData.count - font.pixelSize: 10 + font.pixelSize: 14 } } } From cf9a542f7409640041c0cf000cb3c768e864118f Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 21 Jul 2019 21:08:37 +0800 Subject: [PATCH 04/16] Highlight reactions sent by local user. --- imports/Spectral/Component/Timeline/ReactionDelegate.qml | 2 +- src/messageeventmodel.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/imports/Spectral/Component/Timeline/ReactionDelegate.qml b/imports/Spectral/Component/Timeline/ReactionDelegate.qml index 0ac17e324..1a282d7c5 100644 --- a/imports/Spectral/Component/Timeline/ReactionDelegate.qml +++ b/imports/Spectral/Component/Timeline/ReactionDelegate.qml @@ -15,7 +15,7 @@ RowLayout { background: Rectangle { radius: height / 2 - color: MPalette.banner + color: modelData.hasLocalUser ? (MSettings.darkTheme ? Qt.darker(MPalette.accent, 1.55) : Qt.lighter(MPalette.accent, 1.55)) : MPalette.banner } contentItem: Label { diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index bffae7ca2..d836cc13e 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -500,9 +500,12 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { for (auto author : i.value()) { authors.append(QVariant::fromValue(author)); } + bool hasLocalUser = i.value().contains( + static_cast(m_currentRoom->localUser())); res.append(QVariantMap{{"reaction", i.key()}, {"count", i.value().count()}, - {"authors", authors}}); + {"authors", authors}, + {"hasLocalUser", hasLocalUser}}); ++i; } From 6f5c23b3c3a55002ab5279ef1a9fd7e587f858c0 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 21 Jul 2019 21:23:14 +0800 Subject: [PATCH 05/16] Hover to show reaction sender list. --- .../Component/Timeline/ReactionDelegate.qml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/imports/Spectral/Component/Timeline/ReactionDelegate.qml b/imports/Spectral/Component/Timeline/ReactionDelegate.qml index 1a282d7c5..e536003ba 100644 --- a/imports/Spectral/Component/Timeline/ReactionDelegate.qml +++ b/imports/Spectral/Component/Timeline/ReactionDelegate.qml @@ -16,6 +16,31 @@ RowLayout { background: Rectangle { radius: height / 2 color: modelData.hasLocalUser ? (MSettings.darkTheme ? Qt.darker(MPalette.accent, 1.55) : Qt.lighter(MPalette.accent, 1.55)) : MPalette.banner + + MouseArea { + anchors.fill: parent + + hoverEnabled: true + + ToolTip.visible: containsMouse + ToolTip.text: { + var text = ""; + + for (var i = 0; i < modelData.authors.length; i++) { + if (i === modelData.authors.length - 1 && i !== 0) { + text += " and " + } else if (i !== 0) { + text += ", " + } + + text += modelData.authors[i].displayName + } + + text += " reacted with " + modelData.reaction + + return text + } + } } contentItem: Label { From 18c42b6af666939a5280a5b5ecad59e9342b48fe Mon Sep 17 00:00:00 2001 From: Black Hat Date: Fri, 26 Jul 2019 15:35:45 +0800 Subject: [PATCH 06/16] Login via access token. --- imports/Spectral/Dialog/LoginDialog.qml | 19 +++++++++- src/controller.cpp | 49 +++++++++++++++++++++++-- src/controller.h | 1 + src/spectralroom.cpp | 5 ++- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/imports/Spectral/Dialog/LoginDialog.qml b/imports/Spectral/Dialog/LoginDialog.qml index b531ebc08..cdafed19c 100644 --- a/imports/Spectral/Dialog/LoginDialog.qml +++ b/imports/Spectral/Dialog/LoginDialog.qml @@ -44,6 +44,16 @@ Dialog { placeholderText: "Password" echoMode: TextInput.Password + onAccepted: accessTokenField.forceActiveFocus() + } + + AutoTextField { + Layout.fillWidth: true + + id: accessTokenField + + placeholderText: "Access Token (Optional)" + onAccepted: deviceNameField.forceActiveFocus() } @@ -52,14 +62,19 @@ Dialog { id: deviceNameField - placeholderText: "Device Name" + placeholderText: "Device Name (Optional)" onAccepted: root.accept() } } function doLogin() { - spectralController.loginWithCredentials(serverField.text, usernameField.text, passwordField.text, deviceNameField.text) + if (accessTokenField.text !== "") { + console.log("Login using access token.") + spectralController.loginWithAccessToken(serverField.text, usernameField.text, accessTokenField.text, deviceNameField.text) + } else { + spectralController.loginWithCredentials(serverField.text, usernameField.text, passwordField.text, deviceNameField.text) + } } onClosed: destroy() diff --git a/src/controller.cpp b/src/controller.cpp index 79312949d..2ef5ecfb1 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -103,6 +103,39 @@ void Controller::loginWithCredentials(QString serverAddr, } } +void Controller::loginWithAccessToken(QString serverAddr, + QString user, + QString token, + QString deviceName) { + if (!user.isEmpty() && !token.isEmpty()) { + QUrl serverUrl(serverAddr); + + Connection* conn = new Connection(this); + if (serverUrl.isValid()) { + conn->setHomeserver(serverUrl); + } + + connect(conn, &Connection::connected, [=] { + AccountSettings account(conn->userId()); + account.setKeepLoggedIn(true); + account.clearAccessToken(); // Drop the legacy - just in case + account.setHomeserver(conn->homeserver()); + account.setDeviceId(conn->deviceId()); + account.setDeviceName(deviceName); + if (!saveAccessTokenToKeyChain(account, conn->accessToken())) + qWarning() << "Couldn't save access token"; + account.sync(); + addConnection(conn); + setConnection(conn); + }); + connect(conn, &Connection::networkError, + [=](QString error, QString, int, int) { + emit errorOccured("Network Error", error); + }); + conn->connectWithToken(user, token, deviceName); + } +} + void Controller::logout(Connection* conn) { if (!conn) { qCritical() << "Attempt to logout null connection"; @@ -112,16 +145,24 @@ void Controller::logout(Connection* conn) { SettingsGroup("Accounts").remove(conn->userId()); QFile(accessTokenFileName(AccountSettings(conn->userId()))).remove(); - auto job = conn->callApi(); - connect(job, &LogoutJob::finished, conn, [=] { + QKeychain::DeletePasswordJob job(qAppName()); + job.setAutoDelete(true); + job.setKey(conn->userId()); + QEventLoop loop; + QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); + + auto logoutJob = conn->callApi(); + connect(logoutJob, &LogoutJob::finished, conn, [=] { conn->stopSync(); emit conn->stateChanged(); emit conn->loggedOut(); if (!m_connections.isEmpty()) setConnection(m_connections[0]); }); - connect(job, &LogoutJob::failure, this, [=] { - emit errorOccured("Server-side Logout Failed", job->errorString()); + connect(logoutJob, &LogoutJob::failure, this, [=] { + emit errorOccured("Server-side Logout Failed", logoutJob->errorString()); }); } diff --git a/src/controller.h b/src/controller.h index 49c92c5d3..5c9e1836f 100644 --- a/src/controller.h +++ b/src/controller.h @@ -32,6 +32,7 @@ class Controller : public QObject { ~Controller(); Q_INVOKABLE void loginWithCredentials(QString, QString, QString, QString); + Q_INVOKABLE void loginWithAccessToken(QString, QString, QString, QString); QVector connections() { return m_connections; } diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index 28953d1da..6b6b9dfcd 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -12,6 +12,7 @@ #include "events/accountdataevents.h" #include "events/roommessageevent.h" #include "events/typingevent.h" +#include "events/reactionevent.h" #include "jobs/downloadfilejob.h" #include @@ -119,13 +120,13 @@ QString SpectralRoom::lastEvent() { for (auto i = messageEvents().rbegin(); i < messageEvents().rend(); i++) { const RoomEvent* evt = i->get(); - if (is(*evt)) + if (is(*evt) || is(*evt)) continue; if (evt->isRedacted()) continue; if (evt->isStateEvent() && - static_cast(evt)->repeatsState()) + static_cast(*evt).repeatsState()) continue; if (connection()->isIgnored(user(evt->senderId()))) From 442b68558e46d654bb22219ff588f33290b004ff Mon Sep 17 00:00:00 2001 From: Black Hat Date: Fri, 26 Jul 2019 17:48:17 +0800 Subject: [PATCH 07/16] Add/remove reactions. --- .../Component/Timeline/MessageDelegate.qml | 2 ++ .../Component/Timeline/ReactionDelegate.qml | 6 +++- src/spectralroom.cpp | 36 +++++++++++++++++-- src/spectralroom.h | 1 + 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index 6276f2c38..18c39dd8e 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -222,6 +222,8 @@ ColumnLayout { } ReactionDelegate { + Layout.fillWidth: true + Layout.topMargin: 0 Layout.bottomMargin: 8 Layout.leftMargin: 16 diff --git a/imports/Spectral/Component/Timeline/ReactionDelegate.qml b/imports/Spectral/Component/Timeline/ReactionDelegate.qml index e536003ba..74509b005 100644 --- a/imports/Spectral/Component/Timeline/ReactionDelegate.qml +++ b/imports/Spectral/Component/Timeline/ReactionDelegate.qml @@ -3,9 +3,11 @@ import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import Spectral.Setting 0.1 -RowLayout { +Flow { visible: reaction || false + spacing: 8 + Repeater { model: reaction @@ -40,6 +42,8 @@ RowLayout { return text } + + onClicked: currentRoom.toggleReaction(eventId, modelData.reaction) } } diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index 6b6b9dfcd..389d3d7b6 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -10,9 +10,9 @@ #include "csapi/rooms.h" #include "csapi/typing.h" #include "events/accountdataevents.h" +#include "events/reactionevent.h" #include "events/roommessageevent.h" #include "events/typingevent.h" -#include "events/reactionevent.h" #include "jobs/downloadfilejob.h" #include @@ -306,7 +306,7 @@ QString SpectralRoom::markdownToHTML(const QString& markdown) { std::string html(tmp_buf); - free((char *)tmp_buf); + free((char*)tmp_buf); auto result = QString::fromStdString(html).trimmed(); @@ -420,3 +420,35 @@ void SpectralRoom::postHtmlMessage(const QString& text, Room::postHtmlMessage(text, html, type); } + +void SpectralRoom::toggleReaction(const QString& eventId, + const QString& reaction) { + const auto eventIt = findInTimeline(eventId); + if (eventIt == timelineEdge()) + return; + + const auto& evt = **eventIt; + + QString redactEventId = ""; + + const auto& annotations = relatedEvents(evt, EventRelation::Annotation()); + if (!annotations.isEmpty()) { + for (const auto& a : annotations) { + if (auto e = eventCast(a)) { + if (e->relation().key != reaction) + continue; + + if (e->senderId() == localUser()->id()) { + redactEventId = e->id(); + break; + } + } + } + } + + if (!redactEventId.isEmpty()) { + redactEvent(redactEventId); + } else { + postEvent(new ReactionEvent(EventRelation::annotate(eventId, reaction))); + } +} diff --git a/src/spectralroom.h b/src/spectralroom.h index e4885b8f5..9c84368fc 100644 --- a/src/spectralroom.h +++ b/src/spectralroom.h @@ -315,6 +315,7 @@ class SpectralRoom : public Room { void changeAvatar(QUrl localFile); void addLocalAlias(const QString& alias); void removeLocalAlias(const QString& alias); + void toggleReaction(const QString& eventId, const QString& reaction); }; #endif // SpectralRoom_H From b7bdfa75a1e4bc764953461fb77ef57416caf634 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Fri, 26 Jul 2019 18:03:47 +0800 Subject: [PATCH 08/16] Add reaction from menu. --- .../Timeline/MessageDelegateContextMenu.qml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/imports/Spectral/Menu/Timeline/MessageDelegateContextMenu.qml b/imports/Spectral/Menu/Timeline/MessageDelegateContextMenu.qml index 62ca02a33..b536fcb03 100644 --- a/imports/Spectral/Menu/Timeline/MessageDelegateContextMenu.qml +++ b/imports/Spectral/Menu/Timeline/MessageDelegateContextMenu.qml @@ -12,6 +12,31 @@ Menu { id: root + Flow { + width: parent.width + + spacing: 0 + + Repeater { + model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"] + + delegate: ItemDelegate { + width: 32 + height: 32 + + contentItem: Label { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + font.pixelSize: 16 + text: modelData + } + + onClicked: currentRoom.toggleReaction(eventId, modelData) + } + } + } + MenuItem { text: "View Source" From a9a59fad4104f1b6a344606f314f1284b77e6a11 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Fri, 26 Jul 2019 19:09:53 +0800 Subject: [PATCH 09/16] More debug logs and stricter input check. --- .../Component/Timeline/ReactionDelegate.qml | 2 +- src/spectralroom.cpp | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/imports/Spectral/Component/Timeline/ReactionDelegate.qml b/imports/Spectral/Component/Timeline/ReactionDelegate.qml index 74509b005..ef606ccc3 100644 --- a/imports/Spectral/Component/Timeline/ReactionDelegate.qml +++ b/imports/Spectral/Component/Timeline/ReactionDelegate.qml @@ -4,7 +4,7 @@ import QtQuick.Layouts 1.12 import Spectral.Setting 0.1 Flow { - visible: reaction || false + visible: (reaction && reaction.length > 0) || false spacing: 8 diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index 389d3d7b6..bba8a8f51 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -423,13 +423,16 @@ void SpectralRoom::postHtmlMessage(const QString& text, void SpectralRoom::toggleReaction(const QString& eventId, const QString& reaction) { + if (eventId.isEmpty() || reaction.isEmpty()) + return; + const auto eventIt = findInTimeline(eventId); if (eventIt == timelineEdge()) return; const auto& evt = **eventIt; - QString redactEventId = ""; + QStringList redactEventIds; // What if there are multiple reaction events? const auto& annotations = relatedEvents(evt, EventRelation::Annotation()); if (!annotations.isEmpty()) { @@ -439,16 +442,23 @@ void SpectralRoom::toggleReaction(const QString& eventId, continue; if (e->senderId() == localUser()->id()) { - redactEventId = e->id(); + redactEventIds.push_back(e->id()); break; } } } } - if (!redactEventId.isEmpty()) { - redactEvent(redactEventId); + if (!redactEventIds.isEmpty()) { + qDebug() << "Remove reaction event" << redactEventIds << "of event" + << eventId << ":" << reaction; + for (auto redactEventId : redactEventIds) { + redactEvent(redactEventId); + } } else { - postEvent(new ReactionEvent(EventRelation::annotate(eventId, reaction))); + qDebug() << "Add reaction event" << eventId << ":" << reaction; + postEvent(new ReactionEvent(EventRelation::annotate(eventId, reaction))); } + + qDebug() << "End of SpectralRoom::toggleReaction()"; } From f7da8eebadbd6b22a909d4d0d83eb0b0388fd997 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Tue, 30 Jul 2019 21:29:55 +0800 Subject: [PATCH 10/16] Reactions are refreshed correctly in the timeline. --- .../Timeline/MessageDelegateContextMenu.qml | 35 +++++++++++------- include/libQuotient | 2 +- src/messageeventmodel.cpp | 37 ++++++++++++++----- src/spectralroom.cpp | 9 +---- 4 files changed, 52 insertions(+), 31 deletions(-) diff --git a/imports/Spectral/Menu/Timeline/MessageDelegateContextMenu.qml b/imports/Spectral/Menu/Timeline/MessageDelegateContextMenu.qml index b536fcb03..f3a53c517 100644 --- a/imports/Spectral/Menu/Timeline/MessageDelegateContextMenu.qml +++ b/imports/Spectral/Menu/Timeline/MessageDelegateContextMenu.qml @@ -12,31 +12,38 @@ Menu { id: root - Flow { + Item { width: parent.width + height: 32 - spacing: 0 + Row { + anchors.centerIn: parent - Repeater { - model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"] + spacing: 0 - delegate: ItemDelegate { - width: 32 - height: 32 + Repeater { + model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"] - contentItem: Label { - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + delegate: ItemDelegate { + width: 32 + height: 32 - font.pixelSize: 16 - text: modelData + contentItem: Label { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + font.pixelSize: 16 + text: modelData + } + + onClicked: currentRoom.toggleReaction(eventId, modelData) } - - onClicked: currentRoom.toggleReaction(eventId, modelData) } } } + MenuSeparator {} + MenuItem { text: "View Source" diff --git a/include/libQuotient b/include/libQuotient index 2fb22758c..64bea52b8 160000 --- a/include/libQuotient +++ b/include/libQuotient @@ -1 +1 @@ -Subproject commit 2fb22758c1668d65e9400fe7d35857ddc54fcc6a +Subproject commit 64bea52b8672df45e5a0bd2ca54fe6a0fd97a7b2 diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index d836cc13e..94cca3d59 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -136,6 +136,10 @@ void MessageEventModel::setRoom(SpectralRoom* room) { refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex()); }); + connect(m_currentRoom, &Room::updatedEvent, this, + [this](const QString& eventId) { + refreshEventRoles(eventId, {ReactionRole}); + }); connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::fileTransferCompleted, this, @@ -178,15 +182,24 @@ void MessageEventModel::refreshEventRoles(int row, const QVector& roles) { emit dataChanged(idx, idx, roles); } -int MessageEventModel::refreshEventRoles(const QString& eventId, +int MessageEventModel::refreshEventRoles(const QString& id, const QVector& roles) { - const auto it = m_currentRoom->findInTimeline(eventId); - if (it == m_currentRoom->timelineEdge()) { - qWarning() << "Trying to refresh inexistent event:" << eventId; - return -1; + // On 64-bit platforms, difference_type for std containers is long long + // but Qt uses int throughout its interfaces; hence casting to int below. + int row = -1; + // First try pendingEvents because it is almost always very short. + const auto pendingIt = m_currentRoom->findPendingEvent(id); + if (pendingIt != m_currentRoom->pendingEvents().end()) + row = int(pendingIt - m_currentRoom->pendingEvents().begin()); + else { + const auto timelineIt = m_currentRoom->findInTimeline(id); + if (timelineIt == m_currentRoom->timelineEdge()) { + qWarning() << "Trying to refresh inexistent event:" << id; + return -1; + } + row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + + timelineBaseIndex(); } - const auto row = - it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex(); refreshEventRoles(row, roles); return row; } @@ -485,14 +498,20 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { m_currentRoom->relatedEvents(evt, EventRelation::Annotation()); if (annotations.isEmpty()) return {}; - QMap> reactions; + QMap> reactions = {}; for (const auto& a : annotations) { + if (a->isRedacted()) // Just in case? + continue; if (auto e = eventCast(a)) reactions[e->relation().key].append( static_cast(m_currentRoom->user(e->senderId()))); } - QVariantList res; + if (reactions.isEmpty()) { + return {}; + } + + QVariantList res = {}; QMap>::const_iterator i = reactions.constBegin(); while (i != reactions.constEnd()) { diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index bba8a8f51..f22471206 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -432,7 +432,7 @@ void SpectralRoom::toggleReaction(const QString& eventId, const auto& evt = **eventIt; - QStringList redactEventIds; // What if there are multiple reaction events? + QStringList redactEventIds; // What if there are multiple reaction events? const auto& annotations = relatedEvents(evt, EventRelation::Annotation()); if (!annotations.isEmpty()) { @@ -450,15 +450,10 @@ void SpectralRoom::toggleReaction(const QString& eventId, } if (!redactEventIds.isEmpty()) { - qDebug() << "Remove reaction event" << redactEventIds << "of event" - << eventId << ":" << reaction; for (auto redactEventId : redactEventIds) { - redactEvent(redactEventId); + redactEvent(redactEventId); } } else { - qDebug() << "Add reaction event" << eventId << ":" << reaction; postEvent(new ReactionEvent(EventRelation::annotate(eventId, reaction))); } - - qDebug() << "End of SpectralRoom::toggleReaction()"; } From 7aa95c9289c42df4ad2b86de90af516b7cee38b4 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Wed, 31 Jul 2019 19:07:35 +0800 Subject: [PATCH 11/16] Hide edit event. Also libQuotient refuses to compile. --- include/libQuotient | 2 +- src/messageeventmodel.cpp | 8 +++++++- src/spectralroom.cpp | 8 +++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/include/libQuotient b/include/libQuotient index 64bea52b8..7b938d45a 160000 --- a/include/libQuotient +++ b/include/libQuotient @@ -1 +1 @@ -Subproject commit 64bea52b8672df45e5a0bd2ca54fe6a0fd97a7b2 +Subproject commit 7b938d45a7d2c2fad421e2d39f98764ac7c494c3 diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 94cca3d59..1583dc588 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -138,7 +138,7 @@ void MessageEventModel::setRoom(SpectralRoom* room) { }); connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString& eventId) { - refreshEventRoles(eventId, {ReactionRole}); + refreshEventRoles(eventId, {ReactionRole, Qt::DisplayRole}); }); connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent); @@ -381,6 +381,12 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { static_cast(evt).repeatsState()) return EventStatus::Hidden; + if (auto e = eventCast(&evt)) { + if (!e->replacedEvent().isEmpty()) { + return EventStatus::Hidden; + } + } + if (m_currentRoom->connection()->isIgnored( m_currentRoom->user(evt.senderId()))) return EventStatus::Hidden; diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index f22471206..07eb772f7 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -129,6 +129,12 @@ QString SpectralRoom::lastEvent() { static_cast(*evt).repeatsState()) continue; + if (auto e = eventCast(evt)) { + if (!e->replacedEvent().isEmpty()) { + continue; + } + } + if (connection()->isIgnored(user(evt->senderId()))) continue; @@ -454,6 +460,6 @@ void SpectralRoom::toggleReaction(const QString& eventId, redactEvent(redactEventId); } } else { - postEvent(new ReactionEvent(EventRelation::annotate(eventId, reaction))); + postReaction(eventId, reaction); } } From 0ad56be335dec9ea2b19e492b001f09a3620fd3e Mon Sep 17 00:00:00 2001 From: Black Hat Date: Fri, 2 Aug 2019 14:45:05 +0800 Subject: [PATCH 12/16] Update libQuotient. --- include/libQuotient | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libQuotient b/include/libQuotient index 7b938d45a..5b236dfe8 160000 --- a/include/libQuotient +++ b/include/libQuotient @@ -1 +1 @@ -Subproject commit 7b938d45a7d2c2fad421e2d39f98764ac7c494c3 +Subproject commit 5b236dfe895c7766002559570aa29c9033009228 From 5d193b68a655b65f275eef44a22096b15fe1f047 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sat, 3 Aug 2019 11:21:34 +0800 Subject: [PATCH 13/16] Fix windows icon. --- CMakeLists.txt | 1 + spectral_win32.rc | 1 + 2 files changed, 2 insertions(+) create mode 100644 spectral_win32.rc diff --git a/CMakeLists.txt b/CMakeLists.txt index d665794f7..b2f61f946 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,6 +176,7 @@ QT5_ADD_RESOURCES(spectral_QRC_SRC ${spectral_QRC}) set_property(SOURCE qrc_resources.cpp PROPERTY SKIP_AUTOMOC ON) if(WIN32) + set(spectral_WINRC spectral_win32.rc) set_property(SOURCE spectral_win32.rc APPEND PROPERTY OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/icons/icon.ico ) diff --git a/spectral_win32.rc b/spectral_win32.rc new file mode 100644 index 000000000..339c9cdca --- /dev/null +++ b/spectral_win32.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "icons/icon.ico" From 19e49560722d083f02724022c4cebbdff4f7425c Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sat, 3 Aug 2019 21:41:04 +0800 Subject: [PATCH 14/16] Minor UI cleanup. --- imports/Spectral/Component/Timeline/MessageDelegate.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index 18c39dd8e..b793a3409 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -66,6 +66,7 @@ ColumnLayout { Control { Layout.maximumWidth: messageListView.width - (!sentByMe ? 36 + messageRow.spacing : 0) - 48 + Layout.minimumHeight: 36 padding: 0 From 31f13880a8b5a58673ee83d75976dabce7aa3d4e Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sat, 3 Aug 2019 21:44:05 +0800 Subject: [PATCH 15/16] Fix QML_IMPORT_PATH. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b2f61f946..181bcdace 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,7 @@ if (Qt5_POSITION_INDEPENDENT_CODE) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() -set(QML_IMPORT_PATH ${CMAKE_SOURCE_DIR}/qml ${CMAKE_BINARY_DIR}/imports CACHE string "" FORCE) +set(QML_IMPORT_PATH ${CMAKE_SOURCE_DIR}/qml ${CMAKE_SOURCE_DIR}/imports CACHE string "" FORCE) if(WIN32) enable_language(RC) From c4ae77dd6229bb06ed0af54524635df22e0379af Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 4 Aug 2019 18:47:09 +0800 Subject: [PATCH 16/16] Clean up. --- src/spectralroom.cpp | 7 +++++++ src/spectralroom.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index 07eb772f7..7936fec7d 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -171,6 +171,13 @@ void SpectralRoom::onAddHistoricalTimelineEvents(rev_iter_t from) { [this](const TimelineItem& ti) { checkForHighlights(ti); }); } +void SpectralRoom::onRedaction(const RoomEvent& prevEvent, + const RoomEvent& /*after*/) { + if (const auto& e = eventCast(&prevEvent)) { + emit updatedEvent(e->relation().eventId); + } +} + void SpectralRoom::countChanged() { if (displayed() && !hasUnreadMessages()) { resetNotificationCount(); diff --git a/src/spectralroom.h b/src/spectralroom.h index 9c84368fc..972a8f269 100644 --- a/src/spectralroom.h +++ b/src/spectralroom.h @@ -284,6 +284,7 @@ class SpectralRoom : public Room { void onAddNewTimelineEvents(timeline_iter_t from) override; void onAddHistoricalTimelineEvents(rev_iter_t from) override; + void onRedaction(const RoomEvent& prevEvent, const RoomEvent& after) override; static QString markdownToHTML(const QString& plaintext);