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
@@ -535,6 +535,27 @@ InputBar::notice(const QString &msg, bool rainbowify)
}
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<mtx::crypto::EncryptedFile> &file,
const QString &url,
@@ -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<mtx::crypto::EncryptedFile> &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<mtx::events::msg::Confetti> &)
+ {
+ return qml_mtx_events::EventType::ConfettiMessage;
+ }
qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Emote> &)
{
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<StateEvent<state::space::Parent>>(e)) {
this->parentChecked = false;
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();
}
@@ -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<QString> &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<RoomSummary, DeleteLaterDeleter> parentSummary = nullptr;
bool parentChecked = false;
};
|