diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml index 5b3bbc2..c109175 100644 --- a/resources/qml/Reactions.qml +++ b/resources/qml/Reactions.qml @@ -34,7 +34,7 @@ Flow { onClicked: { 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) + timelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key) } diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index e87590f..8186db8 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -90,7 +90,6 @@ MouseArea { ToolTip.visible: hovered ToolTip.text: qsTr("React") emojiPicker: emojiPopup - room_id: model.roomId event_id: model.id } ImageButton { diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index fd185bd..1d7b4a4 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -40,19 +40,20 @@ Page { id: messageContextMenu modal: true - function show(eventId_, eventType_, isEncrypted_, showAt) { + function show(eventId_, eventType_, isEncrypted_, showAt_) { eventId = eventId_ eventType = eventType_ isEncrypted = isEncrypted_ - popup(showAt) + popup(showAt_) } property string eventId property int eventType property bool isEncrypted + MenuItem { text: qsTr("React") - onClicked: chat.model.reactAction(messageContextMenu.eventId) + onClicked: emojiPopup.show(messageContextMenu.parent, messageContextMenu.eventId) } MenuItem { text: qsTr("Reply") diff --git a/resources/qml/emoji/EmojiButton.qml b/resources/qml/emoji/EmojiButton.qml index f8f75e3..c5eee4e 100644 --- a/resources/qml/emoji/EmojiButton.qml +++ b/resources/qml/emoji/EmojiButton.qml @@ -8,11 +8,10 @@ import "../" ImageButton { property var colors: currentActivePalette property var emojiPicker - property string room_id property string event_id image: ":/icons/icons/ui/smile.png" id: emojiButton - onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, room_id, event_id) + onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, event_id) } diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml index ac67af2..f75221d 100644 --- a/resources/qml/emoji/EmojiPicker.qml +++ b/resources/qml/emoji/EmojiPicker.qml @@ -10,17 +10,17 @@ import "../" Popup { - function show(showAt, room_id, event_id) { - console.debug("Showing emojiPicker for " + event_id + "in room " + room_id) - parent = showAt - x = Math.round((showAt.width - width) / 2) - y = showAt.height - emojiPopup.room_id = room_id - emojiPopup.event_id = event_id - open() - } + function show(showAt, event_id) { + console.debug("Showing emojiPicker for " + event_id) + if (showAt){ + parent = showAt + x = Math.round((showAt.width - width) / 2) + y = showAt.height + } + emojiPopup.event_id = event_id + open() + } - property string room_id property string event_id property var colors property alias model: gridView.model @@ -102,9 +102,9 @@ Popup { } // TODO: maybe add favorites at some point? onClicked: { - console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id + " in room " + emojiPopup.room_id) + console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id) emojiPopup.close() - timelineManager.queueReactionMessage(emojiPopup.room_id, emojiPopup.event_id, model.unicode) + timelineManager.queueReactionMessage(emojiPopup.event_id, model.unicode) } } diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp index 7071819..0618206 100644 --- a/src/EventAccessors.cpp +++ b/src/EventAccessors.cpp @@ -223,6 +223,20 @@ struct EventInReplyTo } }; +struct EventRelatesTo +{ + template + using related_ev_id_t = decltype(Content::relates_to.event_id); + template + std::string operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) { + return e.content.relates_to.event_id; + } + return ""; + } +}; + struct EventTransactionId { template @@ -378,6 +392,11 @@ mtx::accessors::in_reply_to_event(const mtx::events::collections::TimelineEvents { return std::visit(EventInReplyTo{}, event); } +std::string +mtx::accessors::relates_to_event_id(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(EventRelatesTo{}, event); +} std::string mtx::accessors::transaction_id(const mtx::events::collections::TimelineEvents &event) diff --git a/src/EventAccessors.h b/src/EventAccessors.h index a7577d8..8f08ef1 100644 --- a/src/EventAccessors.h +++ b/src/EventAccessors.h @@ -53,6 +53,8 @@ mimetype(const mtx::events::collections::TimelineEvents &event); std::string in_reply_to_event(const mtx::events::collections::TimelineEvents &event); std::string +relates_to_event_id(const mtx::events::collections::TimelineEvents &event); +std::string transaction_id(const mtx::events::collections::TimelineEvents &event); int64_t diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 0bd7a97..eb1162c 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -202,6 +202,20 @@ EventStore::handleSync(const mtx::responses::Timeline &events) if (auto redaction = std::get_if>( &event)) { + // fixup reactions + auto redacted = events_by_id_.object({room_id_, redaction->redacts}); + if (redacted) { + auto id = mtx::accessors::relates_to_event_id(*redacted); + if (!id.empty()) { + auto idx = idToIndex(id); + if (idx) { + events_by_id_.remove( + {room_id_, redaction->redacts}); + emit dataChanged(*idx, *idx); + } + } + } + relates_to = redaction->redacts; } else if (auto reaction = std::get_if>( diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 85d2eb4..8631eb8 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1069,8 +1069,9 @@ struct SendMessageVisitor // reactions need to have the relation outside of ciphertext, or synapse / the homeserver // cannot handle it correctly. See the MSC for more details: // https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption - void operator()(const mtx::events::RoomEvent &msg) + void operator()(mtx::events::RoomEvent msg) { + msg.type = mtx::events::EventType::Reaction; emit model_->addPendingMessageToStore(msg); } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index cbe88fd..f8a84f1 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -197,6 +197,15 @@ public: Q_INVOKABLE void cacheMedia(QString eventId); Q_INVOKABLE bool saveMedia(QString eventId) const; + std::vector<::Reaction> reactions(const std::string &event_id) + { + auto list = events.reactions(event_id); + std::vector<::Reaction> vec; + for (const auto &r : list) + vec.push_back(r.value()); + return vec; + } + void updateLastMessage(); void addEvents(const mtx::responses::Timeline &events); template diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 64af8af..8cb72ed 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -314,35 +314,38 @@ TimelineViewManager::queueEmoteMessage(const QString &msg) } void -TimelineViewManager::reactToMessage(const QString &roomId, - const QString &reactedEvent, - const QString &reactionKey, - const QString &selfReactedEvent) +TimelineViewManager::queueReactionMessage(const QString &reactedEvent, const QString &reactionKey) { + if (!timeline_) + return; + + auto reactions = timeline_->reactions(reactedEvent.toStdString()); + + QString selfReactedEvent; + for (const auto &reaction : reactions) { + if (reactionKey == reaction.key_) { + selfReactedEvent = reaction.selfReactedEvent_; + break; + } + } + + if (selfReactedEvent.startsWith("m")) + return; + // If selfReactedEvent is empty, that means we haven't previously reacted if (selfReactedEvent.isEmpty()) { - queueReactionMessage(roomId, reactedEvent, reactionKey); + mtx::events::msg::Reaction reaction; + reaction.relates_to.rel_type = mtx::common::RelationType::Annotation; + reaction.relates_to.event_id = reactedEvent.toStdString(); + reaction.relates_to.key = reactionKey.toStdString(); + + timeline_->sendMessage(reaction); // Otherwise, we have previously reacted and the reaction should be redacted } else { - auto model = models.value(roomId); - model->redactEvent(selfReactedEvent); + timeline_->redactEvent(selfReactedEvent); } } -void -TimelineViewManager::queueReactionMessage(const QString &roomId, - const QString &reactedEvent, - const QString &reactionKey) -{ - mtx::events::msg::Reaction reaction; - reaction.relates_to.rel_type = mtx::common::RelationType::Annotation; - reaction.relates_to.event_id = reactedEvent.toStdString(); - reaction.relates_to.key = reactionKey.toStdString(); - - auto model = models.value(roomId); - model->sendMessage(reaction); -} - void TimelineViewManager::queueImageMessage(const QString &roomid, const QString &filename, diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index ed09505..6310691 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -61,13 +61,7 @@ public slots: void setHistoryView(const QString &room_id); void updateColorPalette(); - void queueReactionMessage(const QString &roomId, - const QString &reactedEvent, - const QString &reaction); - void reactToMessage(const QString &roomId, - const QString &reactedEvent, - const QString &reactionKey, - const QString &selfReactedEvent); + void queueReactionMessage(const QString &reactedEvent, const QString &reactionKey); void queueTextMessage(const QString &msg); void queueEmoteMessage(const QString &msg); void queueImageMessage(const QString &roomid,