* 🎉 (confetti) message support.  Thanks @LorenDB !
pull/1246/head
Loren Burkholder 2 years ago committed by GitHub
parent c2eb2f7ad1
commit fa0c14b846
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CMakeLists.txt
  2. 2
      io.github.NhekoReborn.Nheko.yaml
  3. BIN
      resources/confettiparticle.png
  4. 49
      resources/confettiparticle.svg
  5. 1
      resources/qml/MessageView.qml
  6. 64
      resources/qml/TimelineView.qml
  7. 14
      resources/qml/delegates/MessageDelegate.qml
  8. 1
      resources/res.qrc
  9. 12
      src/CommandCompleter.cpp
  10. 2
      src/CommandCompleter.h
  11. 30
      src/UserSettingsPage.cpp
  12. 7
      src/UserSettingsPage.h
  13. 25
      src/timeline/InputBar.cpp
  14. 1
      src/timeline/InputBar.h
  15. 37
      src/timeline/TimelineModel.cpp
  16. 8
      src/timeline/TimelineModel.h

@ -595,7 +595,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare( FetchContent_Declare(
MatrixClient MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
GIT_TAG 7155cbb8ed3289f2cc7269da7607b0dd012f471b GIT_TAG 13285437739413587a22272865d1e684e1959579
) )
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")

@ -182,7 +182,7 @@ modules:
buildsystem: cmake-ninja buildsystem: cmake-ninja
name: mtxclient name: mtxclient
sources: sources:
- commit: 7155cbb8ed3289f2cc7269da7607b0dd012f471b - commit: 13285437739413587a22272865d1e684e1959579
#tag: v0.8.2 #tag: v0.8.2
type: git type: git
url: https://github.com/Nheko-Reborn/mtxclient.git url: https://github.com/Nheko-Reborn/mtxclient.git

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
width="32.000000pt"
height="10.000000pt"
viewBox="0 0 32.000000 10.000000"
preserveAspectRatio="xMidYMid meet"
id="svg4"
sodipodi:docname="confettiparticle.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="pt"
showgrid="false"
inkscape:zoom="17.7"
inkscape:cx="7.1751412"
inkscape:cy="0.64971751"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<g
transform="translate(0.000000,10.000000) scale(0.100000,-0.100000)"
fill="#000000"
stroke="none"
id="g2" />
<rect
style="fill:#ffffff;stroke-width:0.75"
id="rect307"
width="32.033897"
height="10"
x="0"
y="0" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -59,7 +59,6 @@ Item {
onCountChanged: { onCountChanged: {
// Mark timeline as read // Mark timeline as read
if (atYEnd && room) model.currentIndex = 0; if (atYEnd && room) model.currentIndex = 0;
} }
ScrollBar.vertical: scrollbar ScrollBar.vertical: scrollbar

@ -13,6 +13,7 @@ import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.5 import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtQuick.Particles 2.15
import QtQuick.Window 2.13 import QtQuick.Window 2.13
import im.nheko 1.0 import im.nheko 1.0
import im.nheko.EmojiModel 1.0 import im.nheko.EmojiModel 1.0
@ -25,6 +26,8 @@ Item {
property bool showBackButton: false property bool showBackButton: false
clip: true clip: true
onRoomChanged: if (room != null) room.triggerSpecialEffects()
Shortcut { Shortcut {
sequence: StandardKey.Close sequence: StandardKey.Close
onActivated: Rooms.resetCurrentRoom() onActivated: Rooms.resetCurrentRoom()
@ -298,6 +301,58 @@ Item {
onClicked: Rooms.resetCurrentRoom() 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 { NhekoDropArea {
anchors.fill: parent anchors.fill: parent
roomid: room ? room.roomId : "" roomid: room ? room.roomId : ""
@ -321,6 +376,15 @@ Item {
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
} }
function onConfetti()
{
if (!Settings.fancyEffects)
return
confettiEmitter.pulse(parent.height * 2)
room.markSpecialEffectsDone()
}
target: room target: room
} }

@ -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 { DelegateChoice {
roleValue: MtxEvent.NoticeMessage roleValue: MtxEvent.NoticeMessage

@ -193,6 +193,7 @@
<file>qml/voip/PlaceCall.qml</file> <file>qml/voip/PlaceCall.qml</file>
<file>qml/voip/ScreenShare.qml</file> <file>qml/voip/ScreenShare.qml</file>
<file>qml/voip/VideoCall.qml</file> <file>qml/voip/VideoCall.qml</file>
<file>confettiparticle.svg</file>
</qresource> </qresource>
<qresource prefix="/media"> <qresource prefix="/media">
<file>media/ring.ogg</file> <file>media/ring.ogg</file>

@ -82,6 +82,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const
return QString("/notice "); return QString("/notice ");
case RainbowNotice: case RainbowNotice:
return QString("/rainbownotice "); return QString("/rainbownotice ");
case Confetti:
return QString("/confetti ");
case RainbowConfetti:
return QString("/rainbowconfetti ");
case Goto: case Goto:
return QString("/goto "); return QString("/goto ");
case ConvertToDm: case ConvertToDm:
@ -145,6 +149,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const
return tr("/notice [message]"); return tr("/notice [message]");
case RainbowNotice: case RainbowNotice:
return tr("/rainbownotice [message]"); return tr("/rainbownotice [message]");
case Confetti:
return tr("/confetti [message]");
case RainbowConfetti:
return tr("/rainbowconfetti [message]");
case Goto: case Goto:
return tr("/goto ($eventid|message index|matrix:r/room/e/event)"); return tr("/goto ($eventid|message index|matrix:r/room/e/event)");
case ConvertToDm: case ConvertToDm:
@ -207,6 +215,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const
return tr("Send a bot message."); return tr("Send a bot message.");
case RainbowNotice: case RainbowNotice:
return tr("Send a bot message in rainbow colors."); 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: case Goto:
return tr("Go to this event or link."); return tr("Go to this event or link.");
case ConvertToDm: case ConvertToDm:

@ -44,6 +44,8 @@ public:
RainbowMe, RainbowMe,
Notice, Notice,
RainbowNotice, RainbowNotice,
Confetti,
RainbowConfetti,
Goto, Goto,
ConvertToDm, ConvertToDm,
ConvertToRoom, ConvertToRoom,

@ -92,6 +92,7 @@ UserSettings::load(std::optional<QString> profile)
decryptNotifications_ = decryptNotifications_ =
settings.value(QStringLiteral("user/decrypt_notifications"), true).toBool(); settings.value(QStringLiteral("user/decrypt_notifications"), true).toBool();
spaceNotifications_ = settings.value(QStringLiteral("user/space_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(); privacyScreen_ = settings.value(QStringLiteral("user/privacy_screen"), false).toBool();
privacyScreenTimeout_ = privacyScreenTimeout_ =
settings.value(QStringLiteral("user/privacy_screen_timeout"), 0).toInt(); settings.value(QStringLiteral("user/privacy_screen_timeout"), 0).toInt();
@ -459,6 +460,16 @@ UserSettings::setSpaceNotifications(bool state)
save(); save();
} }
void
UserSettings::setFancyEffects(bool state)
{
if (state == fancyEffects_)
return;
fancyEffects_ = state;
emit fancyEffectsChanged(state);
save();
}
void void
UserSettings::setPrivacyScreen(bool state) UserSettings::setPrivacyScreen(bool state)
{ {
@ -822,6 +833,7 @@ UserSettings::save()
settings.setValue(QStringLiteral("decrypt_sidebar"), decryptSidebar_); settings.setValue(QStringLiteral("decrypt_sidebar"), decryptSidebar_);
settings.setValue(QStringLiteral("decrypt_notificatons"), decryptNotifications_); settings.setValue(QStringLiteral("decrypt_notificatons"), decryptNotifications_);
settings.setValue(QStringLiteral("space_notifications"), spaceNotifications_); settings.setValue(QStringLiteral("space_notifications"), spaceNotifications_);
settings.setValue(QStringLiteral("fancy_effects"), fancyEffects_);
settings.setValue(QStringLiteral("privacy_screen"), privacyScreen_); settings.setValue(QStringLiteral("privacy_screen"), privacyScreen_);
settings.setValue(QStringLiteral("privacy_screen_timeout"), privacyScreenTimeout_); settings.setValue(QStringLiteral("privacy_screen_timeout"), privacyScreenTimeout_);
settings.setValue(QStringLiteral("mobile_mode"), mobileMode_); settings.setValue(QStringLiteral("mobile_mode"), mobileMode_);
@ -976,6 +988,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return tr("Decrypt notifications"); return tr("Decrypt notifications");
case SpaceNotifications: case SpaceNotifications:
return tr("Show message counts for communities and tags"); return tr("Show message counts for communities and tags");
case FancyEffects:
return tr("Display fancy effects such as confetti");
case PrivacyScreen: case PrivacyScreen:
return tr("Privacy Screen"); return tr("Privacy Screen");
case PrivacyScreenTimeout: case PrivacyScreenTimeout:
@ -1112,6 +1126,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return i->decryptNotifications(); return i->decryptNotifications();
case SpaceNotifications: case SpaceNotifications:
return i->spaceNotifications(); return i->spaceNotifications();
case FancyEffects:
return i->fancyEffects();
case PrivacyScreen: case PrivacyScreen:
return i->privacyScreen(); return i->privacyScreen();
case PrivacyScreenTimeout: case PrivacyScreenTimeout:
@ -1276,6 +1292,9 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
case SpaceNotifications: case SpaceNotifications:
return tr("Choose where to show the total number of notifications contained within a " return tr("Choose where to show the total number of notifications contained within a "
"community or tag."); "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: case PrivacyScreen:
return tr("When the window loses focus, the timeline will\nbe blurred."); return tr("When the window loses focus, the timeline will\nbe blurred.");
case MobileMode: case MobileMode:
@ -1388,6 +1407,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
case UseOnlineKeyBackup: case UseOnlineKeyBackup:
case ExposeDBusApi: case ExposeDBusApi:
case SpaceNotifications: case SpaceNotifications:
case FancyEffects:
return Toggle; return Toggle;
case Profile: case Profile:
case UserId: case UserId:
@ -1716,6 +1736,13 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
} else } else
return false; return false;
} }
case FancyEffects: {
if (value.userType() == QMetaType::Bool) {
i->setFancyEffects(value.toBool());
return true;
} else
return false;
}
case PrivacyScreen: { case PrivacyScreen: {
if (value.userType() == QMetaType::Bool) { if (value.userType() == QMetaType::Bool) {
i->setPrivacyScreen(value.toBool()); i->setPrivacyScreen(value.toBool());
@ -2037,6 +2064,9 @@ UserSettingsModel::UserSettingsModel(QObject *p)
connect(s.get(), &UserSettings::spaceNotificationsChanged, this, [this]() { connect(s.get(), &UserSettings::spaceNotificationsChanged, this, [this]() {
emit dataChanged(index(SpaceNotifications), index(SpaceNotifications), {Value}); 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]() { connect(s.get(), &UserSettings::trayChanged, this, [this]() {
emit dataChanged(index(Tray), index(Tray), {Value}); emit dataChanged(index(Tray), index(Tray), {Value});
emit dataChanged(index(StartInTray), index(StartInTray), {Enabled}); emit dataChanged(index(StartInTray), index(StartInTray), {Enabled});

@ -64,6 +64,7 @@ class UserSettings final : public QObject
NOTIFY decryptNotificationsChanged) NOTIFY decryptNotificationsChanged)
Q_PROPERTY(bool spaceNotifications READ spaceNotifications WRITE setSpaceNotifications NOTIFY Q_PROPERTY(bool spaceNotifications READ spaceNotifications WRITE setSpaceNotifications NOTIFY
spaceNotificationsChanged) spaceNotificationsChanged)
Q_PROPERTY(bool fancyEffects READ fancyEffects WRITE setFancyEffects NOTIFY fancyEffectsChanged)
Q_PROPERTY( Q_PROPERTY(
bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged) bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged)
Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout
@ -171,6 +172,7 @@ public:
void setDecryptSidebar(bool state); void setDecryptSidebar(bool state);
void setDecryptNotifications(bool state); void setDecryptNotifications(bool state);
void setSpaceNotifications(bool state); void setSpaceNotifications(bool state);
void setFancyEffects(bool state);
void setPrivacyScreen(bool state); void setPrivacyScreen(bool state);
void setPrivacyScreenTimeout(int state); void setPrivacyScreenTimeout(int state);
void setPresence(Presence state); void setPresence(Presence state);
@ -214,6 +216,7 @@ public:
bool decryptSidebar() const { return decryptSidebar_; } bool decryptSidebar() const { return decryptSidebar_; }
bool decryptNotifications() const { return decryptNotifications_; } bool decryptNotifications() const { return decryptNotifications_; }
bool spaceNotifications() const { return spaceNotifications_; } bool spaceNotifications() const { return spaceNotifications_; }
bool fancyEffects() const { return fancyEffects_; }
bool privacyScreen() const { return privacyScreen_; } bool privacyScreen() const { return privacyScreen_; }
int privacyScreenTimeout() const { return privacyScreenTimeout_; } int privacyScreenTimeout() const { return privacyScreenTimeout_; }
bool markdown() const { return markdown_; } bool markdown() const { return markdown_; }
@ -295,6 +298,7 @@ signals:
void decryptSidebarChanged(bool state); void decryptSidebarChanged(bool state);
void decryptNotificationsChanged(bool state); void decryptNotificationsChanged(bool state);
void spaceNotificationsChanged(bool state); void spaceNotificationsChanged(bool state);
void fancyEffectsChanged(bool state);
void privacyScreenChanged(bool state); void privacyScreenChanged(bool state);
void privacyScreenTimeoutChanged(int state); void privacyScreenTimeoutChanged(int state);
void timelineMaxWidthChanged(int state); void timelineMaxWidthChanged(int state);
@ -360,6 +364,7 @@ private:
bool decryptSidebar_; bool decryptSidebar_;
bool decryptNotifications_; bool decryptNotifications_;
bool spaceNotifications_; bool spaceNotifications_;
bool fancyEffects_;
bool privacyScreen_; bool privacyScreen_;
int privacyScreenTimeout_; int privacyScreenTimeout_;
bool shareKeysWithTrustedUsers_; bool shareKeysWithTrustedUsers_;
@ -442,6 +447,8 @@ class UserSettingsModel final : public QAbstractListModel
InvertEnterKey, InvertEnterKey,
Bubbles, Bubbles,
SmallAvatars, SmallAvatars,
FancyEffects,
SidebarSection, SidebarSection,
GroupView, GroupView,
SortByImportance, SortByImportance,

@ -534,6 +534,27 @@ InputBar::notice(const QString &msg, bool rainbowify)
room->sendMessageEvent(notice, mtx::events::EventType::RoomMessage); 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 void
InputBar::image(const QString &filename, InputBar::image(const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
@ -777,6 +798,10 @@ InputBar::command(const QString &command, QString args)
notice(args, false); notice(args, false);
} else if (command == QLatin1String("rainbownotice")) { } else if (command == QLatin1String("rainbownotice")) {
notice(args, true); notice(args, true);
} else if (command == QLatin1String("confetti")) {
confetti(args, false);
} else if (command == QLatin1String("rainbowconfetti")) {
confetti(args, true);
} else if (command == QLatin1String("goto")) { } else if (command == QLatin1String("goto")) {
// Goto has three different modes: // Goto has three different modes:
// 1 - Going directly to a given event ID // 1 - Going directly to a given event ID

@ -230,6 +230,7 @@ signals:
private: private:
void emote(const QString &body, bool rainbowify); void emote(const QString &body, bool rainbowify);
void notice(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 command(const QString &name, QString args);
void image(const QString &filename, void image(const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,

@ -53,6 +53,10 @@ struct RoomEventType
{ {
return qml_mtx_events::EventType::AudioMessage; return qml_mtx_events::EventType::AudioMessage;
} }
qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Confetti> &)
{
return qml_mtx_events::EventType::ConfettiMessage;
}
qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Emote> &) qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Emote> &)
{ {
return qml_mtx_events::EventType::EmoteMessage; return qml_mtx_events::EventType::EmoteMessage;
@ -346,6 +350,7 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t)
return mtx::events::EventType::SpaceChild; return mtx::events::EventType::SpaceChild;
/// m.room.message /// m.room.message
case qml_mtx_events::AudioMessage: case qml_mtx_events::AudioMessage:
case qml_mtx_events::ConfettiMessage:
case qml_mtx_events::EmoteMessage: case qml_mtx_events::EmoteMessage:
case qml_mtx_events::FileMessage: case qml_mtx_events::FileMessage:
case qml_mtx_events::ImageMessage: case qml_mtx_events::ImageMessage:
@ -1025,8 +1030,17 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
} else if (std::holds_alternative<StateEvent<state::space::Parent>>(e)) { } else if (std::holds_alternative<StateEvent<state::space::Parent>>(e)) {
this->parentChecked = false; this->parentChecked = false;
emit parentSpaceChanged(); emit parentSpaceChanged();
} 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("🎊"))
needsSpecialEffects_ = true;
} else if (std::holds_alternative<RoomEvent<mtx::events::msg::Confetti>>(e))
needsSpecialEffects_ = true;
} }
}
if (needsSpecialEffects_)
emit confetti();
updateLastMessage(); updateLastMessage();
} }
@ -1957,6 +1971,22 @@ TimelineModel::copyLinkToEvent(const QString &eventId) const
QGuiApplication::clipboard()->setText(link); 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 QString
TimelineModel::formatTypingUsers(const std::vector<QString> &users, const QColor &bg) TimelineModel::formatTypingUsers(const std::vector<QString> &users, const QColor &bg)
{ {
@ -2790,7 +2820,8 @@ TimelineModel::setEdit(const QString &newEdit)
auto msgType = mtx::accessors::msg_type(e); auto msgType = mtx::accessors::msg_type(e);
if (msgType == mtx::events::MessageType::Text || if (msgType == mtx::events::MessageType::Text ||
msgType == mtx::events::MessageType::Notice || 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 relInfo = relatedInfo(newEdit);
auto editText = relInfo.quoted_body; auto editText = relInfo.quoted_body;
@ -2811,6 +2842,8 @@ TimelineModel::setEdit(const QString &newEdit)
if (msgType == mtx::events::MessageType::Emote) if (msgType == mtx::events::MessageType::Emote)
input()->setText("/me " + editText); input()->setText("/me " + editText);
else if (msgType == mtx::events::MessageType::Confetti)
input()->setText("/confetti" + editText);
else else
input()->setText(editText); input()->setText(editText);
} else { } else {

@ -100,6 +100,7 @@ enum EventType
Widget, Widget,
/// m.room.message /// m.room.message
AudioMessage, AudioMessage,
ConfettiMessage,
EmoteMessage, EmoteMessage,
FileMessage, FileMessage,
ImageMessage, ImageMessage,
@ -419,6 +420,9 @@ public slots:
QString scrollTarget() const; QString scrollTarget() const;
void triggerSpecialEffects();
void markSpecialEffectsDone();
private slots: private slots:
void addPendingMessage(mtx::events::collections::TimelineEvents event); void addPendingMessage(mtx::events::collections::TimelineEvents event);
void scrollTimerEvent(); void scrollTimerEvent();
@ -438,6 +442,7 @@ signals:
void paginationInProgressChanged(const bool); void paginationInProgressChanged(const bool);
void newCallEvent(const mtx::events::collections::TimelineEvents &event); void newCallEvent(const mtx::events::collections::TimelineEvents &event);
void scrollToIndex(int index); void scrollToIndex(int index);
void confetti();
void lastMessageChanged(); void lastMessageChanged();
void notificationsChanged(); void notificationsChanged();
@ -509,6 +514,9 @@ private:
std::string last_event_id; std::string last_event_id;
std::string fullyReadEventId_; std::string fullyReadEventId_;
// TODO (Loren): This should hopefully handle more than just confetti in the future
bool needsSpecialEffects_ = false;
std::unique_ptr<RoomSummary, DeleteLaterDeleter> parentSummary = nullptr; std::unique_ptr<RoomSummary, DeleteLaterDeleter> parentSummary = nullptr;
bool parentChecked = false; bool parentChecked = false;
}; };

Loading…
Cancel
Save