diff options
author | DeepBlueV7.X <nicolas.werner@hotmail.de> | 2023-04-10 23:19:44 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-10 23:19:44 +0000 |
commit | 7973fbce8c0db3eb82eafb4cc0f776ba10ec8a79 (patch) | |
tree | c5cb9c8eccb061747919ac9c4574f207c62a8b42 | |
parent | Translated using Weblate (German) (diff) | |
parent | Update mtxclient commit hash (diff) | |
download | nheko-7973fbce8c0db3eb82eafb4cc0f776ba10ec8a79.tar.xz |
Merge pull request #1407 from Nheko-Reborn/ducktyping
Implement unknown msgtype functionality
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | io.github.NhekoReborn.Nheko.yaml | 2 | ||||
-rw-r--r-- | resources/qml/TimelineView.qml | 78 | ||||
-rw-r--r-- | resources/qml/delegates/MessageDelegate.qml | 18 | ||||
-rw-r--r-- | resources/qml/ui/TimelineEffects.qml | 112 | ||||
-rw-r--r-- | resources/res.qrc | 1 | ||||
-rw-r--r-- | src/CommandCompleter.cpp | 12 | ||||
-rw-r--r-- | src/CommandCompleter.h | 2 | ||||
-rw-r--r-- | src/Utils.cpp | 33 | ||||
-rw-r--r-- | src/Utils.h | 38 | ||||
-rw-r--r-- | src/timeline/InputBar.cpp | 59 | ||||
-rw-r--r-- | src/timeline/InputBar.h | 2 | ||||
-rw-r--r-- | src/timeline/TimelineModel.cpp | 80 | ||||
-rw-r--r-- | src/timeline/TimelineModel.h | 18 |
14 files changed, 345 insertions, 112 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 06af4bbe..20fcb029 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -602,7 +602,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG c8849cd033bb59bee39f3fb2eaca953853731eb2 + GIT_TAG dd2bdbd104ae8f70f82da9ff7b4b60007fc105c3 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index 3bcfdf52..97d0b770 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -213,7 +213,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: c8849cd033bb59bee39f3fb2eaca953853731eb2 + - commit: dd2bdbd104ae8f70f82da9ff7b4b60007fc105c3 #tag: v0.9.2 type: git url: https://github.com/Nheko-Reborn/mtxclient.git diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index a146a991..30ad9292 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -364,60 +364,10 @@ Item { onClicked: Rooms.resetCurrentRoom() } - ParticleSystem { id: confettiParticleSystem - Component.onCompleted: pause(); - paused: !shouldEffectsRun - } - - Emitter { - id: confettiEmitter - - width: parent.width * 3/4 - enabled: false - anchors.horizontalCenter: parent.horizontalCenter - y: parent.height - emitRate: Math.min(400 * Math.sqrt(parent.width * parent.height) / 870, 1000) - lifeSpan: 15000 - system: confettiParticleSystem - maximumEmitted: 500 - velocityFromMovement: 8 - size: 16 - sizeVariation: 4 - velocity: PointDirection { - x: 0 - y: -Math.min(450 * parent.height / 700, 1000) - xVariation: Math.min(4 * parent.width / 7, 450) - yVariation: 250 - } - } + TimelineEffects { + id: timelineEffects - ImageParticle { - system: confettiParticleSystem - source: "qrc:/confettiparticle.svg" - rotationVelocity: 0 - rotationVelocityVariation: 360 - colorVariation: 1 - color: "white" - entryEffect: ImageParticle.None - xVector: PointDirection { - x: 1 - y: 0 - xVariation: 0.2 - yVariation: 0.2 - } - yVector: PointDirection { - x: 0 - y: 0.5 - xVariation: 0.2 - yVariation: 0.2 - } - } - - Gravity { - system: confettiParticleSystem anchors.fill: parent - magnitude: 350 - angle: 90 } NhekoDropArea { @@ -428,7 +378,7 @@ Item { Timer { id: effectsTimer onTriggered: shouldEffectsRun = false; - interval: confettiEmitter.lifeSpan + interval: timelineEffects.maxLifespan repeat: false running: false } @@ -462,7 +412,7 @@ Item { return shouldEffectsRun = true; - confettiEmitter.pulse(parent.height * 2) + timelineEffects.pulseConfetti() room.markSpecialEffectsDone() } @@ -471,7 +421,25 @@ Item { if (!Settings.fancyEffects) return - effectsTimer.start(); + effectsTimer.restart(); + } + + function onRainfall() + { + if (!Settings.fancyEffects) + return + + shouldEffectsRun = true; + timelineEffects.pulseRainfall() + room.markSpecialEffectsDone() + } + + function onRainfallDone() + { + if (!Settings.fancyEffects) + return + + effectsTimer.restart(); } target: room diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index d298fa4e..c0bcec0d 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -51,7 +51,7 @@ Item { width: parent.width? parent.width: 0 // this should get rid of "cannot read property 'width' of null" DelegateChoice { - roleValue: MtxEvent.UnknownMessage + roleValue: MtxEvent.UnknownEvent Placeholder { typeString: d.typeString @@ -102,7 +102,21 @@ Item { } DelegateChoice { - roleValue: MtxEvent.ConfettiMessage + roleValue: MtxEvent.UnknownMessage + + TextMessage { + formatted: d.formattedBody + body: d.body + isOnlyEmoji: d.isOnlyEmoji + isReply: d.isReply + keepFullText: d.keepFullText + metadataWidth: d.metadataWidth + } + + } + + DelegateChoice { + roleValue: MtxEvent.ElementEffectMessage TextMessage { formatted: d.formattedBody diff --git a/resources/qml/ui/TimelineEffects.qml b/resources/qml/ui/TimelineEffects.qml new file mode 100644 index 00000000..aaff04a0 --- /dev/null +++ b/resources/qml/ui/TimelineEffects.qml @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Particles 2.15 + +Item { + readonly property int maxLifespan: Math.max(confettiEmitter.lifeSpan, rainfallEmitter.lifeSpan) + + function pulseConfetti() + { + confettiEmitter.pulse(parent.height * 2) + } + + function pulseRainfall() + { + rainfallEmitter.pulse(parent.height * 3.3) + } + + ParticleSystem { + id: particleSystem + + Component.onCompleted: pause(); + paused: !shouldEffectsRun + } + + Emitter { + id: confettiEmitter + + group: "confetti" + width: parent.width * 3/4 + enabled: false + anchors.horizontalCenter: parent.horizontalCenter + y: parent.height + emitRate: Math.min(400 * Math.sqrt(parent.width * parent.height) / 870, 1000) + lifeSpan: 15000 + system: particleSystem + maximumEmitted: 500 + velocityFromMovement: 8 + size: 16 + sizeVariation: 4 + velocity: PointDirection { + x: 0 + y: -Math.min(450 * parent.height / 700, 1000) + xVariation: Math.min(4 * parent.width / 7, 450) + yVariation: 250 + } + } + + ImageParticle { + system: particleSystem + groups: ["confetti"] + source: "qrc:/confettiparticle.svg" + rotationVelocity: 0 + rotationVelocityVariation: 360 + colorVariation: 1 + color: "white" + entryEffect: ImageParticle.None + xVector: PointDirection { + x: 1 + y: 0 + xVariation: 0.2 + yVariation: 0.2 + } + yVector: PointDirection { + x: 0 + y: 0.5 + xVariation: 0.2 + yVariation: 0.2 + } + } + + Gravity { + system: particleSystem + groups: ["confetti"] + anchors.fill: parent + magnitude: 350 + angle: 90 + } + + Emitter { + id: rainfallEmitter + + group: "rain" + width: parent.width + enabled: false + anchors.horizontalCenter: parent.horizontalCenter + y: -60 + emitRate: parent.width / 50 + lifeSpan: 10000 + system: particleSystem + velocity: PointDirection { + x: 0 + y: 300 + xVariation: 0 + yVariation: 75 + } + + ItemParticle { + system: particleSystem + groups: ["rain"] + fade: false + delegate: Rectangle { + width: 2 + height: 30 + 30 * Math.random() + radius: 2 + color: "#0099ff" + } + } + } +} diff --git a/resources/res.qrc b/resources/res.qrc index faa90495..3f1b2b65 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -197,6 +197,7 @@ <file>qml/voip/VideoCall.qml</file> <file>confettiparticle.svg</file> <file>qml/delegates/EncryptionEnabled.qml</file> + <file>qml/ui/TimelineEffects.qml</file> </qresource> <qresource prefix="/media"> <file>media/ring.ogg</file> diff --git a/src/CommandCompleter.cpp b/src/CommandCompleter.cpp index 2ec427d6..8123b8e6 100644 --- a/src/CommandCompleter.cpp +++ b/src/CommandCompleter.cpp @@ -87,6 +87,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const return QStringLiteral("/confetti "); case RainbowConfetti: return QStringLiteral("/rainbowconfetti "); + case Rainfall: + return QStringLiteral("/rainfall "); + case Msgtype: + return QStringLiteral("/msgtype "); case Goto: return QStringLiteral("/goto "); case ConvertToDm: @@ -156,6 +160,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const return tr("/confetti [message]"); case RainbowConfetti: return tr("/rainbowconfetti [message]"); + case Rainfall: + return tr("/rainfall [message]"); + case Msgtype: + return tr("/msgtype <msgtype> [message]"); case Goto: return tr("/goto <message reference>"); case ConvertToDm: @@ -225,6 +233,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const return tr("Send a message with confetti."); case RainbowConfetti: return tr("Send a message in rainbow colors with confetti."); + case Rainfall: + return tr("Send a message with rain."); + case Msgtype: + return tr("Send a message with a custom message type."); case Goto: return tr("Go to a specific message using an event id, index or matrix: link"); case ConvertToDm: diff --git a/src/CommandCompleter.h b/src/CommandCompleter.h index fcbbe3e5..4f27fe29 100644 --- a/src/CommandCompleter.h +++ b/src/CommandCompleter.h @@ -46,6 +46,8 @@ public: RainbowNotice, Confetti, RainbowConfetti, + Rainfall, + Msgtype, Goto, ConvertToDm, ConvertToRoom, diff --git a/src/Utils.cpp b/src/Utils.cpp index c5b2abd1..2bf8eb3b 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -215,19 +215,20 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUser, const QString &displayName) { - using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; - using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; - using File = mtx::events::RoomEvent<mtx::events::msg::File>; - using Image = mtx::events::RoomEvent<mtx::events::msg::Image>; - using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>; - using Text = mtx::events::RoomEvent<mtx::events::msg::Text>; - using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; - using Confetti = mtx::events::RoomEvent<mtx::events::msg::Confetti>; - using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>; - using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>; - using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>; - using CallReject = mtx::events::RoomEvent<mtx::events::voip::CallReject>; - using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; + using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; + using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; + using File = mtx::events::RoomEvent<mtx::events::msg::File>; + using Image = mtx::events::RoomEvent<mtx::events::msg::Image>; + using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>; + using Text = mtx::events::RoomEvent<mtx::events::msg::Text>; + using Unknown = mtx::events::RoomEvent<mtx::events::msg::Unknown>; + using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; + using ElementEffect = mtx::events::RoomEvent<mtx::events::msg::ElementEffect>; + using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>; + using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>; + using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>; + using CallReject = mtx::events::RoomEvent<mtx::events::voip::CallReject>; + using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; if (std::holds_alternative<Audio>(event)) { return createDescriptionInfo<Audio>(event, localUser, displayName); @@ -241,10 +242,12 @@ utils::getMessageDescription(const TimelineEvent &event, return createDescriptionInfo<Notice>(event, localUser, displayName); } else if (std::holds_alternative<Text>(event)) { return createDescriptionInfo<Text>(event, localUser, displayName); + } else if (std::holds_alternative<Unknown>(event)) { + return createDescriptionInfo<Unknown>(event, localUser, displayName); } else if (std::holds_alternative<Video>(event)) { return createDescriptionInfo<Video>(event, localUser, displayName); - } else if (std::holds_alternative<Confetti>(event)) { - return createDescriptionInfo<Confetti>(event, localUser, displayName); + } else if (std::holds_alternative<ElementEffect>(event)) { + return createDescriptionInfo<ElementEffect>(event, localUser, displayName); } else if (std::holds_alternative<CallInvite>(event)) { return createDescriptionInfo<CallInvite>(event, localUser, displayName); } else if (std::holds_alternative<CallAnswer>(event)) { diff --git a/src/Utils.h b/src/Utils.h index 2bf01f84..2c8988e5 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -95,20 +95,21 @@ messageDescription(const QString &username = QString(), const QString &body = QString(), const bool isLocal = false) { - using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; - using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; - using File = mtx::events::RoomEvent<mtx::events::msg::File>; - using Image = mtx::events::RoomEvent<mtx::events::msg::Image>; - using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>; - using Sticker = mtx::events::Sticker; - using Text = mtx::events::RoomEvent<mtx::events::msg::Text>; - using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; - using Confetti = mtx::events::RoomEvent<mtx::events::msg::Confetti>; - using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>; - using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>; - using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>; - using CallReject = mtx::events::RoomEvent<mtx::events::voip::CallReject>; - using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; + using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; + using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; + using File = mtx::events::RoomEvent<mtx::events::msg::File>; + using Image = mtx::events::RoomEvent<mtx::events::msg::Image>; + using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>; + using Sticker = mtx::events::Sticker; + using Text = mtx::events::RoomEvent<mtx::events::msg::Text>; + using Unknown = mtx::events::RoomEvent<mtx::events::msg::Unknown>; + using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; + using ElementEffect = mtx::events::RoomEvent<mtx::events::msg::ElementEffect>; + using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>; + using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>; + using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>; + using CallReject = mtx::events::RoomEvent<mtx::events::voip::CallReject>; + using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; if (std::is_same<T, Audio>::value) { if (isLocal) @@ -149,20 +150,21 @@ messageDescription(const QString &username = QString(), return QCoreApplication::translate("message-description sent:", "%1 sent a notification") .arg(username); - } else if (std::is_same<T, Text>::value) { + } else if (std::is_same<T, Text>::value || std::is_same<T, Unknown>::value) { if (isLocal) return QCoreApplication::translate("message-description sent:", "You: %1").arg(body); else return QCoreApplication::translate("message-description sent:", "%1: %2") .arg(username, body); - } else if (std::is_same<T, Confetti>::value) { + } else if (std::is_same<T, ElementEffect>::value) { if (body.isEmpty()) { + // TODO: what is the best way to handle this? if (isLocal) return QCoreApplication::translate("message-description sent:", - "You sent some confetti"); + "You sent a chat effect"); else return QCoreApplication::translate("message-description sent:", - "%1 sent some confetti") + "%1 sent a chat effect") .arg(username); } else { if (isLocal) diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index b27128e0..fe8b8e48 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -281,6 +281,8 @@ InputBar::updateTextContentProperties(const QString &t) QStringLiteral("rainbownotice"), QStringLiteral("confetti"), QStringLiteral("rainbowconfetti"), + QStringLiteral("rainfall"), + QStringLiteral("msgtype"), QStringLiteral("goto"), QStringLiteral("converttodm"), QStringLiteral("converttoroom")}; @@ -607,8 +609,9 @@ InputBar::confetti(const QString &body, bool rainbowify) { auto html = utils::markdownToHtml(body, rainbowify); - mtx::events::msg::Confetti confetti; - confetti.body = body.trimmed().toStdString(); + mtx::events::msg::ElementEffect confetti; + confetti.msgtype = "nic.custom.confetti"; + confetti.body = body.trimmed().toStdString(); if (html != body.trimmed().toHtmlEscaped() && ChatPage::instance()->userSettings()->markdown()) { @@ -624,6 +627,54 @@ InputBar::confetti(const QString &body, bool rainbowify) } void +InputBar::rainfall(const QString &body) +{ + auto html = utils::markdownToHtml(body); + + mtx::events::msg::Unknown rain; + rain.msgtype = "io.element.effect.rainfall"; + rain.body = body.trimmed().toStdString(); + + if (html != body.trimmed().toHtmlEscaped() && + ChatPage::instance()->userSettings()->markdown()) { + nlohmann::json j; + j["formatted_body"] = html.toStdString(); + j["format"] = "org.matrix.custom.html"; + rain.content = j.dump(); + // Remove markdown links by completer + rain.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString(); + } + + rain.relations = generateRelations(); + + room->sendMessageEvent(rain, mtx::events::EventType::RoomMessage); +} + +void +InputBar::customMsgtype(const QString &msgtype, const QString &body) +{ + auto html = utils::markdownToHtml(body); + + mtx::events::msg::Unknown msg; + msg.msgtype = msgtype.toStdString(); + msg.body = body.trimmed().toStdString(); + + if (html != body.trimmed().toHtmlEscaped() && + ChatPage::instance()->userSettings()->markdown()) { + nlohmann::json j; + j["formatted_body"] = html.toStdString(); + j["format"] = "org.matrix.custom.html"; + msg.content = j.dump(); + // Remove markdown links by completer + msg.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString(); + } + + msg.relations = generateRelations(); + + room->sendMessageEvent(msg, mtx::events::EventType::RoomMessage); +} + +void InputBar::image(const QString &filename, const std::optional<mtx::crypto::EncryptedFile> &file, const QString &url, @@ -890,6 +941,10 @@ InputBar::command(const QString &command, QString args) confetti(args, false); } else if (command == QLatin1String("rainbowconfetti")) { confetti(args, true); + } else if (command == QLatin1String("rainfall")) { + rainfall(args); + } else if (command == QLatin1String("msgtype")) { + customMsgtype(args.section(' ', 0, 0), args.section(' ', 1, -1)); } else if (command == QLatin1String("goto")) { // Goto has three different modes: // 1 - Going directly to a given event ID diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index acafd964..b2db377f 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -242,6 +242,8 @@ private: void emote(const QString &body, bool rainbowify); void notice(const QString &body, bool rainbowify); void confetti(const QString &body, bool rainbowify); + void rainfall(const QString &body); + void customMsgtype(const QString &msgtype, const QString &body); bool command(const QString &name, QString args); void image(const QString &filename, const std::optional<mtx::crypto::EncryptedFile> &file, diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index cb9fb7fa..918d1c0b 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -54,9 +54,9 @@ struct RoomEventType return qml_mtx_events::EventType::AudioMessage; } constexpr qml_mtx_events::EventType - operator()(const mtx::events::Event<mtx::events::msg::Confetti> &) + operator()(const mtx::events::Event<mtx::events::msg::ElementEffect> &) { - return qml_mtx_events::EventType::ConfettiMessage; + return qml_mtx_events::EventType::ElementEffectMessage; } constexpr qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Emote> &) @@ -84,6 +84,11 @@ struct RoomEventType return qml_mtx_events::EventType::TextMessage; } constexpr qml_mtx_events::EventType + operator()(const mtx::events::Event<mtx::events::msg::Unknown> &) + { + return qml_mtx_events::EventType::UnknownMessage; + } + constexpr qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Video> &) { return qml_mtx_events::EventType::VideoMessage; @@ -203,7 +208,7 @@ qml_mtx_events::toRoomEventType(mtx::events::EventType e) case EventType::RoomMember: return qml_mtx_events::EventType::Member; case EventType::RoomMessage: - return qml_mtx_events::EventType::UnknownMessage; + return qml_mtx_events::EventType::UnknownEvent; case EventType::RoomName: return qml_mtx_events::EventType::Name; case EventType::RoomPowerLevels: @@ -239,7 +244,7 @@ qml_mtx_events::toRoomEventType(mtx::events::EventType e) case EventType::Unsupported: return qml_mtx_events::EventType::Unsupported; default: - return qml_mtx_events::EventType::UnknownMessage; + return qml_mtx_events::EventType::UnknownEvent; } } @@ -362,16 +367,17 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t) return mtx::events::EventType::SpaceChild; /// m.room.message case qml_mtx_events::AudioMessage: - case qml_mtx_events::ConfettiMessage: + case qml_mtx_events::ElementEffectMessage: case qml_mtx_events::EmoteMessage: case qml_mtx_events::FileMessage: case qml_mtx_events::ImageMessage: case qml_mtx_events::LocationMessage: case qml_mtx_events::NoticeMessage: case qml_mtx_events::TextMessage: + case qml_mtx_events::UnknownMessage: case qml_mtx_events::VideoMessage: case qml_mtx_events::Redacted: - case qml_mtx_events::UnknownMessage: + case qml_mtx_events::UnknownEvent: case qml_mtx_events::KeyVerificationRequest: case qml_mtx_events::KeyVerificationStart: case qml_mtx_events::KeyVerificationMac: @@ -1075,14 +1081,32 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline) } else if (std::holds_alternative<RoomEvent<mtx::events::msg::Text>>(e)) { if (auto msg = QString::fromStdString( std::get<RoomEvent<mtx::events::msg::Text>>(e).content.body); - msg.contains("🎉") || msg.contains("🎊")) + msg.contains("🎉") || msg.contains("🎊")) { + needsSpecialEffects_ = true; + specialEffects_.setFlag(Confetti); + } + } else if (std::holds_alternative<RoomEvent<mtx::events::msg::Unknown>>(e)) { + if (auto msg = QString::fromStdString( + std::get<RoomEvent<mtx::events::msg::Unknown>>(e).content.body); + msg.contains("🎉") || msg.contains("🎊")) { + needsSpecialEffects_ = true; + specialEffects_.setFlag(Confetti); + } + } else if (std::holds_alternative<RoomEvent<mtx::events::msg::ElementEffect>>(e)) { + if (auto msgtype = + std::get<RoomEvent<mtx::events::msg::ElementEffect>>(e).content.msgtype; + msgtype == "nic.custom.confetti") { needsSpecialEffects_ = true; - } else if (std::holds_alternative<RoomEvent<mtx::events::msg::Confetti>>(e)) - needsSpecialEffects_ = true; + specialEffects_.setFlag(Confetti); + } else if (msgtype == "io.element.effect.rainfall") { + needsSpecialEffects_ = true; + specialEffects_.setFlag(Rainfall); + } + } } if (needsSpecialEffects_) - emit confetti(); + triggerSpecialEffects(); if (avatarChanged) emit roomAvatarUrlChanged(); @@ -2045,7 +2069,14 @@ TimelineModel::triggerSpecialEffects() { if (needsSpecialEffects_) { // Note (Loren): Without the timer, this apparently emits before QML is ready - QTimer::singleShot(1, this, [this] { emit confetti(); }); + if (specialEffects_.testFlag(Confetti)) { + QTimer::singleShot(1, this, [this] { emit confetti(); }); + specialEffects_.setFlag(Confetti, false); + } + if (specialEffects_.testFlag(Rainfall)) { + QTimer::singleShot(1, this, [this] { emit rainfall(); }); + specialEffects_.setFlag(Rainfall, false); + } needsSpecialEffects_ = false; } } @@ -2055,6 +2086,10 @@ TimelineModel::markSpecialEffectsDone() { needsSpecialEffects_ = false; emit confettiDone(); + emit rainfallDone(); + + specialEffects_.setFlag(Confetti, false); + specialEffects_.setFlag(Rainfall, false); } QString @@ -2917,7 +2952,8 @@ TimelineModel::setEdit(const QString &newEdit) if (msgType == mtx::events::MessageType::Text || msgType == mtx::events::MessageType::Notice || msgType == mtx::events::MessageType::Emote || - msgType == mtx::events::MessageType::Confetti) { + msgType == mtx::events::MessageType::ElementEffect || + msgType == mtx::events::MessageType::Unknown) { auto relInfo = relatedInfo(newEdit); auto editText = relInfo.quoted_body; @@ -2938,9 +2974,23 @@ TimelineModel::setEdit(const QString &newEdit) if (msgType == mtx::events::MessageType::Emote) input()->setText("/me " + editText); - else if (msgType == mtx::events::MessageType::Confetti) - input()->setText("/confetti" + editText); - else + else if (msgType == mtx::events::MessageType::ElementEffect) { + auto u = + std::get_if<mtx::events::RoomEvent<mtx::events::msg::ElementEffect>>(&e); + auto msgtypeString = u ? u->content.msgtype : ""; + if (msgtypeString == "io.element.effect.rainfall") + input()->setText("/rainfall " + editText); + else if (msgtypeString == "nic.custom.confetti") + input()->setText("/confetti " + editText); + else + input()->setText("/msgtype " + QString::fromStdString(msgtypeString) + " " + + editText); + } else if (msgType == mtx::events::MessageType::Unknown) { + auto u = std::get_if<mtx::events::RoomEvent<mtx::events::msg::Unknown>>(&e); + input()->setText("/msgtype " + + (u ? QString::fromStdString(u->content.msgtype) : "") + " " + + editText); + } else input()->setText(editText); } else { input()->setText(QLatin1String("")); diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 03cb9ecb..ef845bb5 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -99,16 +99,17 @@ enum EventType Widget, /// m.room.message AudioMessage, - ConfettiMessage, + ElementEffectMessage, EmoteMessage, FileMessage, ImageMessage, LocationMessage, NoticeMessage, TextMessage, + UnknownMessage, VideoMessage, Redacted, - UnknownMessage, + UnknownEvent, KeyVerificationRequest, KeyVerificationStart, KeyVerificationMac, @@ -266,6 +267,13 @@ public: }; Q_ENUM(Roles); + enum SpecialEffect + { + Confetti, + Rainfall, + }; + Q_DECLARE_FLAGS(SpecialEffects, SpecialEffect) + 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; @@ -450,6 +458,8 @@ signals: void scrollToIndex(int index); void confetti(); void confettiDone(); + void rainfall(); + void rainfallDone(); void lastMessageChanged(); void notificationsChanged(); @@ -521,8 +531,8 @@ private: std::string last_event_id; std::string fullyReadEventId_; - // TODO (Loren): This should hopefully handle more than just confetti in the future bool needsSpecialEffects_ = false; + QFlags<SpecialEffect> specialEffects_; std::unique_ptr<RoomSummary, DeleteLaterDeleter> parentSummary = nullptr; bool parentChecked = false; @@ -530,6 +540,8 @@ private: friend void EventStore::refetchOnlineKeyBackupKeys(TimelineModel *room); }; +Q_DECLARE_OPERATORS_FOR_FLAGS(TimelineModel::SpecialEffects) + template<class T> void TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType) |