From fa0c14b84681b94ff2136d6b7480c8b7283ad96a Mon Sep 17 00:00:00 2001 From: Loren Burkholder <55629213+LorenDB@users.noreply.github.com> Date: Sat, 10 Dec 2022 10:17:15 -0500 Subject: [PATCH] confetti (#1243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🎉 (confetti) message support. Thanks @LorenDB ! --- CMakeLists.txt | 4 +- io.github.NhekoReborn.Nheko.yaml | 2 +- resources/confettiparticle.png | Bin 0 -> 4732 bytes resources/confettiparticle.svg | 49 +++++++++++++++ resources/qml/MessageView.qml | 1 - resources/qml/TimelineView.qml | 64 ++++++++++++++++++++ resources/qml/delegates/MessageDelegate.qml | 14 +++++ resources/res.qrc | 1 + src/CommandCompleter.cpp | 12 ++++ src/CommandCompleter.h | 2 + src/UserSettingsPage.cpp | 30 +++++++++ src/UserSettingsPage.h | 7 +++ src/timeline/InputBar.cpp | 25 ++++++++ src/timeline/InputBar.h | 1 + src/timeline/TimelineModel.cpp | 37 ++++++++++- src/timeline/TimelineModel.h | 8 +++ 16 files changed, 251 insertions(+), 6 deletions(-) create mode 100644 resources/confettiparticle.png create mode 100644 resources/confettiparticle.svg diff --git a/CMakeLists.txt b/CMakeLists.txt index 60f16d9a..d7219318 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -594,8 +594,8 @@ if(USE_BUNDLED_MTXCLIENT) include(FetchContent) FetchContent_Declare( MatrixClient - GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 7155cbb8ed3289f2cc7269da7607b0dd012f471b + GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git + GIT_TAG 13285437739413587a22272865d1e684e1959579 ) 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 f41f71b4..c82513e4 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -182,7 +182,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 7155cbb8ed3289f2cc7269da7607b0dd012f471b + - commit: 13285437739413587a22272865d1e684e1959579 #tag: v0.8.2 type: git url: https://github.com/Nheko-Reborn/mtxclient.git diff --git a/resources/confettiparticle.png b/resources/confettiparticle.png new file mode 100644 index 0000000000000000000000000000000000000000..808429d82e78c276c0776b178d69aea714cbcfea GIT binary patch literal 4732 zcmeHKX;f3!77n5hM8Oe6hH?!mW0ISBSOkPHg%V^EnXF!t8@NaYl8^*Ms-Rd7I|+!mUjMk(>p!yAy}9?CZ-0C5@9eYJ zxjDf>ex@c?CO90A8$)@LKhE%jEdRUct?3ZuP zY`;wyaeOeZY;R{<)& zRo}VQbN_R)$n|kCtN3YVjWRG>`=+xaA+`X2Y08l6Z~f;SuR6GIUi`%Q%%%K6;R(xm z6|U@8)v=3I+}krugRe}}M*mP^ZIU_Y&XAFZj;((hImfqwz4=kdx`tV)ubbT4w>Pv_ zrIx1tj_=E*KZ}3Y$>}Wb$bfdN&yfAvcCfc=Xwj`a+oPKEjUDbSCCRzPl(=2}CEfNF z3)}M+)>pe6jcGF2k`gE;b>w}ET|e=Kwe!om+Sys}4|^YQHs!S5FTGrU&B5CDnMGvF z5yQ~O|N44*(Z--GL9QzKm`@Blf0Nzngd)PtSobvFC%T>H1vqM=gDQ)Lca zF$P{!@0!H>FN4Ijc1~CAg$(@EUkpmGq!e5;GT!N|Vf~xG*wC!Z@Q2FAgq$;d)!WXM z9fO>sW+vr^*9Gu*c@)^N!&lHwBu0d_9-)7Ea8Kdcr-Nw~OA7)C`-+TQB38N8q^Xtt zPpai}YJaRg9JIACG;#AClVjb^13T?))7r0^c?7T$c|%qAs-B9QW3XyvVcwLU znwb8t(zu=8&Ida0Q4gtyqJHM=Oyv1mar#cf>G>gciPl}S-ZM+@9BEAr)Z<4!xXt8eKs#{x%iILl7sC_sp&g{&2Pv<9rg$9wlqGzJJN{zf>E=dTyMfoGCYlRukZ?hAd~Py%_bT(OQ^sqI>x|c74@! zx+ti5-jtT52CA_ajQp-e(VM6x)PsFlb;Ps8EdJhXB|xE57GacDdy<>GrT{)C4x5f{Ib zE`SAc9-4sprzy~|w4iWd+FBu7g!gbaano`Dfdo|{kXDi;RdTdkyq=c>o^`_{Jfw%H z)^hQYf?$XzQ=kxyNF%}oA1$V);N49iH-$*d3HA0JqX2JQe1b|P=a5JmjfSY95@m{b z5}D0rlVA#oLLmSIL76I5AzFe|X|JOg>8gQP{|Br*{uNhG9+7D|&J6Q=0G<9l=QI{zGzOo)j%`bR3vW>e^K6oQaKXA@{_8k2yC zU>ZRz6pJY^Sp+l5lnGRPsZxbVg{Y1SkP|V$Bc{^H6skCmK&8-V1R71uCa_^v93f83 zCNn8alq#l8q*$)NKvg116Qj~mi2zj`T}T%(V1z(}5pbiisRTBa%p$-jlgfn2Y=)SD z>Z1_}IleN51Ody5NsxGyB$vkPCv<{yJcIdMJcS5<90^WBRAOMj#Rp>26z#`}a7==R zsSur-WCooKe9(aq44A=Yegs9J3MHsS9VZzkQb&1p%fbQ40AUebodN>A1;~ZNQ=o`S zrU;kGlDK$XNRZA_U*3@0=%V;zN`Oe!Rs2}ZhoQ-%Z=+iv3DZwOkiKj=h;Y=25>cZf zy(3^ZIwed%r12=&-(v;!VI2FL#X@O98kwbAA~q8y&=5M6z(SZP0TnZZ6c!9KSoG1w z8fRC^#3~JBPTN4=CzpT?p|Ky^C7U~hveqAC*wc(8(WKi&;y{Jbn;pHPRcbY z*Jmm4S>Va)nw0Ca6!n;QB zcOGgmXr$r`e2kt=wZ=iy_Wkr{AQ)Px@`+UOWD?ye8K*zIp(03wsS+UF;cewcvjqTT z!}s + + + + + + diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 8e6ad8d2..e3e02ee9 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -59,7 +59,6 @@ Item { onCountChanged: { // Mark timeline as read if (atYEnd && room) model.currentIndex = 0; - } ScrollBar.vertical: scrollbar diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index ab1bbc28..dff23700 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -13,6 +13,7 @@ import Qt.labs.platform 1.1 as Platform import QtQuick 2.15 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 +import QtQuick.Particles 2.15 import QtQuick.Window 2.13 import im.nheko 1.0 import im.nheko.EmojiModel 1.0 @@ -25,6 +26,8 @@ Item { property bool showBackButton: false clip: true + onRoomChanged: if (room != null) room.triggerSpecialEffects() + Shortcut { sequence: StandardKey.Close onActivated: Rooms.resetCurrentRoom() @@ -298,6 +301,58 @@ Item { onClicked: Rooms.resetCurrentRoom() } + ParticleSystem { id: confettiParticleSystem } + + 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 + 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: 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 { anchors.fill: parent roomid: room ? room.roomId : "" @@ -321,6 +376,15 @@ Item { timelineRoot.destroyOnClose(dialog); } + function onConfetti() + { + if (!Settings.fancyEffects) + return + + confettiEmitter.pulse(parent.height * 2) + room.markSpecialEffectsDone() + } + target: room } diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index a2a44cb2..3725be05 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -75,6 +75,20 @@ Item { } + DelegateChoice { + roleValue: MtxEvent.ConfettiMessage + + TextMessage { + formatted: d.formattedBody + body: d.body + isOnlyEmoji: d.isOnlyEmoji + isReply: d.isReply + keepFullText: d.keepFullText + metadataWidth: d.metadataWidth + } + + } + DelegateChoice { roleValue: MtxEvent.NoticeMessage diff --git a/resources/res.qrc b/resources/res.qrc index 595dd5a7..e9320a1b 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -193,6 +193,7 @@ qml/voip/PlaceCall.qml qml/voip/ScreenShare.qml qml/voip/VideoCall.qml + confettiparticle.svg media/ring.ogg diff --git a/src/CommandCompleter.cpp b/src/CommandCompleter.cpp index 96dfeace..307defa4 100644 --- a/src/CommandCompleter.cpp +++ b/src/CommandCompleter.cpp @@ -82,6 +82,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const return QString("/notice "); case RainbowNotice: return QString("/rainbownotice "); + case Confetti: + return QString("/confetti "); + case RainbowConfetti: + return QString("/rainbowconfetti "); case Goto: return QString("/goto "); case ConvertToDm: @@ -145,6 +149,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const return tr("/notice [message]"); case RainbowNotice: return tr("/rainbownotice [message]"); + case Confetti: + return tr("/confetti [message]"); + case RainbowConfetti: + return tr("/rainbowconfetti [message]"); case Goto: return tr("/goto ($eventid|message index|matrix:r/room/e/event)"); case ConvertToDm: @@ -207,6 +215,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const return tr("Send a bot message."); case RainbowNotice: return tr("Send a bot message in rainbow colors."); + case Confetti: + return tr("Send a message with confetti."); + case RainbowConfetti: + return tr("Send a message in rainbow colors with confetti."); case Goto: return tr("Go to this event or link."); case ConvertToDm: diff --git a/src/CommandCompleter.h b/src/CommandCompleter.h index 360bff73..08272a19 100644 --- a/src/CommandCompleter.h +++ b/src/CommandCompleter.h @@ -44,6 +44,8 @@ public: RainbowMe, Notice, RainbowNotice, + Confetti, + RainbowConfetti, Goto, ConvertToDm, ConvertToRoom, diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 7082d4e2..145b3b8f 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -92,6 +92,7 @@ UserSettings::load(std::optional profile) decryptNotifications_ = settings.value(QStringLiteral("user/decrypt_notifications"), true).toBool(); spaceNotifications_ = settings.value(QStringLiteral("user/space_notifications"), true).toBool(); + fancyEffects_ = settings.value(QStringLiteral("user/fancy_effects"), true).toBool(); privacyScreen_ = settings.value(QStringLiteral("user/privacy_screen"), false).toBool(); privacyScreenTimeout_ = settings.value(QStringLiteral("user/privacy_screen_timeout"), 0).toInt(); @@ -459,6 +460,16 @@ UserSettings::setSpaceNotifications(bool state) save(); } +void +UserSettings::setFancyEffects(bool state) +{ + if (state == fancyEffects_) + return; + fancyEffects_ = state; + emit fancyEffectsChanged(state); + save(); +} + void UserSettings::setPrivacyScreen(bool state) { @@ -822,6 +833,7 @@ UserSettings::save() settings.setValue(QStringLiteral("decrypt_sidebar"), decryptSidebar_); settings.setValue(QStringLiteral("decrypt_notificatons"), decryptNotifications_); settings.setValue(QStringLiteral("space_notifications"), spaceNotifications_); + settings.setValue(QStringLiteral("fancy_effects"), fancyEffects_); settings.setValue(QStringLiteral("privacy_screen"), privacyScreen_); settings.setValue(QStringLiteral("privacy_screen_timeout"), privacyScreenTimeout_); settings.setValue(QStringLiteral("mobile_mode"), mobileMode_); @@ -976,6 +988,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const return tr("Decrypt notifications"); case SpaceNotifications: return tr("Show message counts for communities and tags"); + case FancyEffects: + return tr("Display fancy effects such as confetti"); case PrivacyScreen: return tr("Privacy Screen"); case PrivacyScreenTimeout: @@ -1112,6 +1126,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const return i->decryptNotifications(); case SpaceNotifications: return i->spaceNotifications(); + case FancyEffects: + return i->fancyEffects(); case PrivacyScreen: return i->privacyScreen(); case PrivacyScreenTimeout: @@ -1276,6 +1292,9 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case SpaceNotifications: return tr("Choose where to show the total number of notifications contained within a " "community or tag."); + case FancyEffects: + return tr("Some messages can be sent with fancy effects. For example, messages sent " + "with '/confetti' will show confetti on screen."); case PrivacyScreen: return tr("When the window loses focus, the timeline will\nbe blurred."); case MobileMode: @@ -1388,6 +1407,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case UseOnlineKeyBackup: case ExposeDBusApi: case SpaceNotifications: + case FancyEffects: return Toggle; case Profile: case UserId: @@ -1716,6 +1736,13 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int } else return false; } + case FancyEffects: { + if (value.userType() == QMetaType::Bool) { + i->setFancyEffects(value.toBool()); + return true; + } else + return false; + } case PrivacyScreen: { if (value.userType() == QMetaType::Bool) { i->setPrivacyScreen(value.toBool()); @@ -2037,6 +2064,9 @@ UserSettingsModel::UserSettingsModel(QObject *p) connect(s.get(), &UserSettings::spaceNotificationsChanged, this, [this]() { emit dataChanged(index(SpaceNotifications), index(SpaceNotifications), {Value}); }); + connect(s.get(), &UserSettings::fancyEffectsChanged, this, [this]() { + emit dataChanged(index(FancyEffects), index(FancyEffects), {Value}); + }); connect(s.get(), &UserSettings::trayChanged, this, [this]() { emit dataChanged(index(Tray), index(Tray), {Value}); emit dataChanged(index(StartInTray), index(StartInTray), {Enabled}); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 3bd0f833..4d805bb7 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -64,6 +64,7 @@ class UserSettings final : public QObject NOTIFY decryptNotificationsChanged) Q_PROPERTY(bool spaceNotifications READ spaceNotifications WRITE setSpaceNotifications NOTIFY spaceNotificationsChanged) + Q_PROPERTY(bool fancyEffects READ fancyEffects WRITE setFancyEffects NOTIFY fancyEffectsChanged) Q_PROPERTY( bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged) Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout @@ -171,6 +172,7 @@ public: void setDecryptSidebar(bool state); void setDecryptNotifications(bool state); void setSpaceNotifications(bool state); + void setFancyEffects(bool state); void setPrivacyScreen(bool state); void setPrivacyScreenTimeout(int state); void setPresence(Presence state); @@ -214,6 +216,7 @@ public: bool decryptSidebar() const { return decryptSidebar_; } bool decryptNotifications() const { return decryptNotifications_; } bool spaceNotifications() const { return spaceNotifications_; } + bool fancyEffects() const { return fancyEffects_; } bool privacyScreen() const { return privacyScreen_; } int privacyScreenTimeout() const { return privacyScreenTimeout_; } bool markdown() const { return markdown_; } @@ -295,6 +298,7 @@ signals: void decryptSidebarChanged(bool state); void decryptNotificationsChanged(bool state); void spaceNotificationsChanged(bool state); + void fancyEffectsChanged(bool state); void privacyScreenChanged(bool state); void privacyScreenTimeoutChanged(int state); void timelineMaxWidthChanged(int state); @@ -360,6 +364,7 @@ private: bool decryptSidebar_; bool decryptNotifications_; bool spaceNotifications_; + bool fancyEffects_; bool privacyScreen_; int privacyScreenTimeout_; bool shareKeysWithTrustedUsers_; @@ -442,6 +447,8 @@ class UserSettingsModel final : public QAbstractListModel InvertEnterKey, Bubbles, SmallAvatars, + FancyEffects, + SidebarSection, GroupView, SortByImportance, diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index a9afb01c..94955152 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -534,6 +534,27 @@ InputBar::notice(const QString &msg, bool rainbowify) room->sendMessageEvent(notice, mtx::events::EventType::RoomMessage); } +void +InputBar::confetti(const QString &body, bool rainbowify) +{ + auto html = utils::markdownToHtml(body, rainbowify); + + mtx::events::msg::Confetti confetti; + confetti.body = body.trimmed().toStdString(); + + if (html != body.trimmed().toHtmlEscaped() && + ChatPage::instance()->userSettings()->markdown()) { + confetti.formatted_body = html.toStdString(); + confetti.format = "org.matrix.custom.html"; + // Remove markdown links by completer + confetti.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString(); + } + + confetti.relations = generateRelations(); + + room->sendMessageEvent(confetti, mtx::events::EventType::RoomMessage); +} + void InputBar::image(const QString &filename, const std::optional &file, @@ -777,6 +798,10 @@ InputBar::command(const QString &command, QString args) notice(args, false); } else if (command == QLatin1String("rainbownotice")) { notice(args, true); + } else if (command == QLatin1String("confetti")) { + confetti(args, false); + } else if (command == QLatin1String("rainbowconfetti")) { + confetti(args, true); } 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 eced7cb8..125591d4 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -230,6 +230,7 @@ signals: private: void emote(const QString &body, bool rainbowify); void notice(const QString &body, bool rainbowify); + void confetti(const QString &body, bool rainbowify); void command(const QString &name, QString args); void image(const QString &filename, const std::optional &file, diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index f61214fd..fe8a78ef 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -53,6 +53,10 @@ struct RoomEventType { return qml_mtx_events::EventType::AudioMessage; } + qml_mtx_events::EventType operator()(const mtx::events::Event &) + { + return qml_mtx_events::EventType::ConfettiMessage; + } qml_mtx_events::EventType operator()(const mtx::events::Event &) { return qml_mtx_events::EventType::EmoteMessage; @@ -346,6 +350,7 @@ 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::EmoteMessage: case qml_mtx_events::FileMessage: case qml_mtx_events::ImageMessage: @@ -1025,9 +1030,18 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline) } else if (std::holds_alternative>(e)) { this->parentChecked = false; emit parentSpaceChanged(); - } + } else if (std::holds_alternative>(e)) { + if (auto msg = QString::fromStdString( + std::get>(e).content.body); + msg.contains("🎉") || msg.contains("🎊")) + needsSpecialEffects_ = true; + } else if (std::holds_alternative>(e)) + needsSpecialEffects_ = true; } + if (needsSpecialEffects_) + emit confetti(); + updateLastMessage(); } @@ -1957,6 +1971,22 @@ TimelineModel::copyLinkToEvent(const QString &eventId) const QGuiApplication::clipboard()->setText(link); } +void +TimelineModel::triggerSpecialEffects() +{ + if (needsSpecialEffects_) { + // Note (Loren): Without the timer, this apparently emits before QML is ready + QTimer::singleShot(1, this, [this] { emit confetti(); }); + needsSpecialEffects_ = false; + } +} + +void +TimelineModel::markSpecialEffectsDone() +{ + needsSpecialEffects_ = false; +} + QString TimelineModel::formatTypingUsers(const std::vector &users, const QColor &bg) { @@ -2790,7 +2820,8 @@ TimelineModel::setEdit(const QString &newEdit) auto msgType = mtx::accessors::msg_type(e); if (msgType == mtx::events::MessageType::Text || msgType == mtx::events::MessageType::Notice || - msgType == mtx::events::MessageType::Emote) { + msgType == mtx::events::MessageType::Emote || + msgType == mtx::events::MessageType::Confetti) { auto relInfo = relatedInfo(newEdit); auto editText = relInfo.quoted_body; @@ -2811,6 +2842,8 @@ 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 input()->setText(editText); } else { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 9daeeb3a..2352be1f 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -100,6 +100,7 @@ enum EventType Widget, /// m.room.message AudioMessage, + ConfettiMessage, EmoteMessage, FileMessage, ImageMessage, @@ -419,6 +420,9 @@ public slots: QString scrollTarget() const; + void triggerSpecialEffects(); + void markSpecialEffectsDone(); + private slots: void addPendingMessage(mtx::events::collections::TimelineEvents event); void scrollTimerEvent(); @@ -438,6 +442,7 @@ signals: void paginationInProgressChanged(const bool); void newCallEvent(const mtx::events::collections::TimelineEvents &event); void scrollToIndex(int index); + void confetti(); void lastMessageChanged(); void notificationsChanged(); @@ -509,6 +514,9 @@ 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; + std::unique_ptr parentSummary = nullptr; bool parentChecked = false; };