Merge pull request #1407 from Nheko-Reborn/ducktyping

Implement unknown msgtype functionality
This commit is contained in:
DeepBlueV7.X 2023-04-10 23:19:44 +00:00 committed by GitHub
commit 7973fbce8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 345 additions and 112 deletions

View file

@ -602,7 +602,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 c8849cd033bb59bee39f3fb2eaca953853731eb2 GIT_TAG dd2bdbd104ae8f70f82da9ff7b4b60007fc105c3
) )
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 "")

View file

@ -213,7 +213,7 @@ modules:
buildsystem: cmake-ninja buildsystem: cmake-ninja
name: mtxclient name: mtxclient
sources: sources:
- commit: c8849cd033bb59bee39f3fb2eaca953853731eb2 - commit: dd2bdbd104ae8f70f82da9ff7b4b60007fc105c3
#tag: v0.9.2 #tag: v0.9.2
type: git type: git
url: https://github.com/Nheko-Reborn/mtxclient.git url: https://github.com/Nheko-Reborn/mtxclient.git

View file

@ -364,60 +364,10 @@ Item {
onClicked: Rooms.resetCurrentRoom() onClicked: Rooms.resetCurrentRoom()
} }
ParticleSystem { id: confettiParticleSystem TimelineEffects {
Component.onCompleted: pause(); id: timelineEffects
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
}
}
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 anchors.fill: parent
magnitude: 350
angle: 90
} }
NhekoDropArea { NhekoDropArea {
@ -428,7 +378,7 @@ Item {
Timer { Timer {
id: effectsTimer id: effectsTimer
onTriggered: shouldEffectsRun = false; onTriggered: shouldEffectsRun = false;
interval: confettiEmitter.lifeSpan interval: timelineEffects.maxLifespan
repeat: false repeat: false
running: false running: false
} }
@ -462,7 +412,7 @@ Item {
return return
shouldEffectsRun = true; shouldEffectsRun = true;
confettiEmitter.pulse(parent.height * 2) timelineEffects.pulseConfetti()
room.markSpecialEffectsDone() room.markSpecialEffectsDone()
} }
@ -471,7 +421,25 @@ Item {
if (!Settings.fancyEffects) if (!Settings.fancyEffects)
return 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 target: room

View file

@ -51,7 +51,7 @@ Item {
width: parent.width? parent.width: 0 // this should get rid of "cannot read property 'width' of null" width: parent.width? parent.width: 0 // this should get rid of "cannot read property 'width' of null"
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.UnknownMessage roleValue: MtxEvent.UnknownEvent
Placeholder { Placeholder {
typeString: d.typeString typeString: d.typeString
@ -102,7 +102,21 @@ Item {
} }
DelegateChoice { 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 { TextMessage {
formatted: d.formattedBody formatted: d.formattedBody

View file

@ -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"
}
}
}
}

View file

@ -197,6 +197,7 @@
<file>qml/voip/VideoCall.qml</file> <file>qml/voip/VideoCall.qml</file>
<file>confettiparticle.svg</file> <file>confettiparticle.svg</file>
<file>qml/delegates/EncryptionEnabled.qml</file> <file>qml/delegates/EncryptionEnabled.qml</file>
<file>qml/ui/TimelineEffects.qml</file>
</qresource> </qresource>
<qresource prefix="/media"> <qresource prefix="/media">
<file>media/ring.ogg</file> <file>media/ring.ogg</file>

View file

@ -87,6 +87,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const
return QStringLiteral("/confetti "); return QStringLiteral("/confetti ");
case RainbowConfetti: case RainbowConfetti:
return QStringLiteral("/rainbowconfetti "); return QStringLiteral("/rainbowconfetti ");
case Rainfall:
return QStringLiteral("/rainfall ");
case Msgtype:
return QStringLiteral("/msgtype ");
case Goto: case Goto:
return QStringLiteral("/goto "); return QStringLiteral("/goto ");
case ConvertToDm: case ConvertToDm:
@ -156,6 +160,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const
return tr("/confetti [message]"); return tr("/confetti [message]");
case RainbowConfetti: case RainbowConfetti:
return tr("/rainbowconfetti [message]"); return tr("/rainbowconfetti [message]");
case Rainfall:
return tr("/rainfall [message]");
case Msgtype:
return tr("/msgtype <msgtype> [message]");
case Goto: case Goto:
return tr("/goto <message reference>"); return tr("/goto <message reference>");
case ConvertToDm: case ConvertToDm:
@ -225,6 +233,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const
return tr("Send a message with confetti."); return tr("Send a message with confetti.");
case RainbowConfetti: case RainbowConfetti:
return tr("Send a message in rainbow colors with confetti."); 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: case Goto:
return tr("Go to a specific message using an event id, index or matrix: link"); return tr("Go to a specific message using an event id, index or matrix: link");
case ConvertToDm: case ConvertToDm:

View file

@ -46,6 +46,8 @@ public:
RainbowNotice, RainbowNotice,
Confetti, Confetti,
RainbowConfetti, RainbowConfetti,
Rainfall,
Msgtype,
Goto, Goto,
ConvertToDm, ConvertToDm,
ConvertToRoom, ConvertToRoom,

View file

@ -215,19 +215,20 @@ utils::getMessageDescription(const TimelineEvent &event,
const QString &localUser, const QString &localUser,
const QString &displayName) const QString &displayName)
{ {
using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>;
using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
using File = mtx::events::RoomEvent<mtx::events::msg::File>; using File = mtx::events::RoomEvent<mtx::events::msg::File>;
using Image = mtx::events::RoomEvent<mtx::events::msg::Image>; using Image = mtx::events::RoomEvent<mtx::events::msg::Image>;
using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>; using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
using Text = mtx::events::RoomEvent<mtx::events::msg::Text>; using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; using Unknown = mtx::events::RoomEvent<mtx::events::msg::Unknown>;
using Confetti = mtx::events::RoomEvent<mtx::events::msg::Confetti>; using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>; using ElementEffect = mtx::events::RoomEvent<mtx::events::msg::ElementEffect>;
using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>; using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>;
using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>; using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>;
using CallReject = mtx::events::RoomEvent<mtx::events::voip::CallReject>; using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>;
using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; using CallReject = mtx::events::RoomEvent<mtx::events::voip::CallReject>;
using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
if (std::holds_alternative<Audio>(event)) { if (std::holds_alternative<Audio>(event)) {
return createDescriptionInfo<Audio>(event, localUser, displayName); return createDescriptionInfo<Audio>(event, localUser, displayName);
@ -241,10 +242,12 @@ utils::getMessageDescription(const TimelineEvent &event,
return createDescriptionInfo<Notice>(event, localUser, displayName); return createDescriptionInfo<Notice>(event, localUser, displayName);
} else if (std::holds_alternative<Text>(event)) { } else if (std::holds_alternative<Text>(event)) {
return createDescriptionInfo<Text>(event, localUser, displayName); 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)) { } else if (std::holds_alternative<Video>(event)) {
return createDescriptionInfo<Video>(event, localUser, displayName); return createDescriptionInfo<Video>(event, localUser, displayName);
} else if (std::holds_alternative<Confetti>(event)) { } else if (std::holds_alternative<ElementEffect>(event)) {
return createDescriptionInfo<Confetti>(event, localUser, displayName); return createDescriptionInfo<ElementEffect>(event, localUser, displayName);
} else if (std::holds_alternative<CallInvite>(event)) { } else if (std::holds_alternative<CallInvite>(event)) {
return createDescriptionInfo<CallInvite>(event, localUser, displayName); return createDescriptionInfo<CallInvite>(event, localUser, displayName);
} else if (std::holds_alternative<CallAnswer>(event)) { } else if (std::holds_alternative<CallAnswer>(event)) {

View file

@ -95,20 +95,21 @@ messageDescription(const QString &username = QString(),
const QString &body = QString(), const QString &body = QString(),
const bool isLocal = false) const bool isLocal = false)
{ {
using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>;
using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
using File = mtx::events::RoomEvent<mtx::events::msg::File>; using File = mtx::events::RoomEvent<mtx::events::msg::File>;
using Image = mtx::events::RoomEvent<mtx::events::msg::Image>; using Image = mtx::events::RoomEvent<mtx::events::msg::Image>;
using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>; using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
using Sticker = mtx::events::Sticker; using Sticker = mtx::events::Sticker;
using Text = mtx::events::RoomEvent<mtx::events::msg::Text>; using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; using Unknown = mtx::events::RoomEvent<mtx::events::msg::Unknown>;
using Confetti = mtx::events::RoomEvent<mtx::events::msg::Confetti>; using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>; using ElementEffect = mtx::events::RoomEvent<mtx::events::msg::ElementEffect>;
using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>; using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>;
using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>; using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>;
using CallReject = mtx::events::RoomEvent<mtx::events::voip::CallReject>; using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>;
using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; 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 (std::is_same<T, Audio>::value) {
if (isLocal) if (isLocal)
@ -149,20 +150,21 @@ messageDescription(const QString &username = QString(),
return QCoreApplication::translate("message-description sent:", return QCoreApplication::translate("message-description sent:",
"%1 sent a notification") "%1 sent a notification")
.arg(username); .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) if (isLocal)
return QCoreApplication::translate("message-description sent:", "You: %1").arg(body); return QCoreApplication::translate("message-description sent:", "You: %1").arg(body);
else else
return QCoreApplication::translate("message-description sent:", "%1: %2") return QCoreApplication::translate("message-description sent:", "%1: %2")
.arg(username, body); .arg(username, body);
} else if (std::is_same<T, Confetti>::value) { } else if (std::is_same<T, ElementEffect>::value) {
if (body.isEmpty()) { if (body.isEmpty()) {
// TODO: what is the best way to handle this?
if (isLocal) if (isLocal)
return QCoreApplication::translate("message-description sent:", return QCoreApplication::translate("message-description sent:",
"You sent some confetti"); "You sent a chat effect");
else else
return QCoreApplication::translate("message-description sent:", return QCoreApplication::translate("message-description sent:",
"%1 sent some confetti") "%1 sent a chat effect")
.arg(username); .arg(username);
} else { } else {
if (isLocal) if (isLocal)

View file

@ -281,6 +281,8 @@ InputBar::updateTextContentProperties(const QString &t)
QStringLiteral("rainbownotice"), QStringLiteral("rainbownotice"),
QStringLiteral("confetti"), QStringLiteral("confetti"),
QStringLiteral("rainbowconfetti"), QStringLiteral("rainbowconfetti"),
QStringLiteral("rainfall"),
QStringLiteral("msgtype"),
QStringLiteral("goto"), QStringLiteral("goto"),
QStringLiteral("converttodm"), QStringLiteral("converttodm"),
QStringLiteral("converttoroom")}; QStringLiteral("converttoroom")};
@ -607,8 +609,9 @@ InputBar::confetti(const QString &body, bool rainbowify)
{ {
auto html = utils::markdownToHtml(body, rainbowify); auto html = utils::markdownToHtml(body, rainbowify);
mtx::events::msg::Confetti confetti; mtx::events::msg::ElementEffect confetti;
confetti.body = body.trimmed().toStdString(); confetti.msgtype = "nic.custom.confetti";
confetti.body = body.trimmed().toStdString();
if (html != body.trimmed().toHtmlEscaped() && if (html != body.trimmed().toHtmlEscaped() &&
ChatPage::instance()->userSettings()->markdown()) { ChatPage::instance()->userSettings()->markdown()) {
@ -623,6 +626,54 @@ InputBar::confetti(const QString &body, bool rainbowify)
room->sendMessageEvent(confetti, mtx::events::EventType::RoomMessage); room->sendMessageEvent(confetti, mtx::events::EventType::RoomMessage);
} }
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 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,
@ -890,6 +941,10 @@ InputBar::command(const QString &command, QString args)
confetti(args, false); confetti(args, false);
} else if (command == QLatin1String("rainbowconfetti")) { } else if (command == QLatin1String("rainbowconfetti")) {
confetti(args, true); 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")) { } 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

View file

@ -242,6 +242,8 @@ 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 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); bool 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,

View file

@ -54,9 +54,9 @@ struct RoomEventType
return qml_mtx_events::EventType::AudioMessage; return qml_mtx_events::EventType::AudioMessage;
} }
constexpr qml_mtx_events::EventType 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 constexpr qml_mtx_events::EventType
operator()(const mtx::events::Event<mtx::events::msg::Emote> &) operator()(const mtx::events::Event<mtx::events::msg::Emote> &)
@ -84,6 +84,11 @@ struct RoomEventType
return qml_mtx_events::EventType::TextMessage; return qml_mtx_events::EventType::TextMessage;
} }
constexpr qml_mtx_events::EventType 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> &) operator()(const mtx::events::Event<mtx::events::msg::Video> &)
{ {
return qml_mtx_events::EventType::VideoMessage; return qml_mtx_events::EventType::VideoMessage;
@ -203,7 +208,7 @@ qml_mtx_events::toRoomEventType(mtx::events::EventType e)
case EventType::RoomMember: case EventType::RoomMember:
return qml_mtx_events::EventType::Member; return qml_mtx_events::EventType::Member;
case EventType::RoomMessage: case EventType::RoomMessage:
return qml_mtx_events::EventType::UnknownMessage; return qml_mtx_events::EventType::UnknownEvent;
case EventType::RoomName: case EventType::RoomName:
return qml_mtx_events::EventType::Name; return qml_mtx_events::EventType::Name;
case EventType::RoomPowerLevels: case EventType::RoomPowerLevels:
@ -239,7 +244,7 @@ qml_mtx_events::toRoomEventType(mtx::events::EventType e)
case EventType::Unsupported: case EventType::Unsupported:
return qml_mtx_events::EventType::Unsupported; return qml_mtx_events::EventType::Unsupported;
default: 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; 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::ElementEffectMessage:
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:
case qml_mtx_events::LocationMessage: case qml_mtx_events::LocationMessage:
case qml_mtx_events::NoticeMessage: case qml_mtx_events::NoticeMessage:
case qml_mtx_events::TextMessage: case qml_mtx_events::TextMessage:
case qml_mtx_events::UnknownMessage:
case qml_mtx_events::VideoMessage: case qml_mtx_events::VideoMessage:
case qml_mtx_events::Redacted: case qml_mtx_events::Redacted:
case qml_mtx_events::UnknownMessage: case qml_mtx_events::UnknownEvent:
case qml_mtx_events::KeyVerificationRequest: case qml_mtx_events::KeyVerificationRequest:
case qml_mtx_events::KeyVerificationStart: case qml_mtx_events::KeyVerificationStart:
case qml_mtx_events::KeyVerificationMac: 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)) { } else if (std::holds_alternative<RoomEvent<mtx::events::msg::Text>>(e)) {
if (auto msg = QString::fromStdString( if (auto msg = QString::fromStdString(
std::get<RoomEvent<mtx::events::msg::Text>>(e).content.body); std::get<RoomEvent<mtx::events::msg::Text>>(e).content.body);
msg.contains("🎉") || msg.contains("🎊")) msg.contains("🎉") || msg.contains("🎊")) {
needsSpecialEffects_ = true; needsSpecialEffects_ = true;
} else if (std::holds_alternative<RoomEvent<mtx::events::msg::Confetti>>(e)) specialEffects_.setFlag(Confetti);
needsSpecialEffects_ = true; }
} 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;
specialEffects_.setFlag(Confetti);
} else if (msgtype == "io.element.effect.rainfall") {
needsSpecialEffects_ = true;
specialEffects_.setFlag(Rainfall);
}
}
} }
if (needsSpecialEffects_) if (needsSpecialEffects_)
emit confetti(); triggerSpecialEffects();
if (avatarChanged) if (avatarChanged)
emit roomAvatarUrlChanged(); emit roomAvatarUrlChanged();
@ -2045,7 +2069,14 @@ TimelineModel::triggerSpecialEffects()
{ {
if (needsSpecialEffects_) { if (needsSpecialEffects_) {
// Note (Loren): Without the timer, this apparently emits before QML is ready // 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; needsSpecialEffects_ = false;
} }
} }
@ -2055,6 +2086,10 @@ TimelineModel::markSpecialEffectsDone()
{ {
needsSpecialEffects_ = false; needsSpecialEffects_ = false;
emit confettiDone(); emit confettiDone();
emit rainfallDone();
specialEffects_.setFlag(Confetti, false);
specialEffects_.setFlag(Rainfall, false);
} }
QString QString
@ -2917,7 +2952,8 @@ TimelineModel::setEdit(const QString &newEdit)
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) { msgType == mtx::events::MessageType::ElementEffect ||
msgType == mtx::events::MessageType::Unknown) {
auto relInfo = relatedInfo(newEdit); auto relInfo = relatedInfo(newEdit);
auto editText = relInfo.quoted_body; auto editText = relInfo.quoted_body;
@ -2938,9 +2974,23 @@ 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) else if (msgType == mtx::events::MessageType::ElementEffect) {
input()->setText("/confetti" + editText); auto u =
else 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); input()->setText(editText);
} else { } else {
input()->setText(QLatin1String("")); input()->setText(QLatin1String(""));

View file

@ -99,16 +99,17 @@ enum EventType
Widget, Widget,
/// m.room.message /// m.room.message
AudioMessage, AudioMessage,
ConfettiMessage, ElementEffectMessage,
EmoteMessage, EmoteMessage,
FileMessage, FileMessage,
ImageMessage, ImageMessage,
LocationMessage, LocationMessage,
NoticeMessage, NoticeMessage,
TextMessage, TextMessage,
UnknownMessage,
VideoMessage, VideoMessage,
Redacted, Redacted,
UnknownMessage, UnknownEvent,
KeyVerificationRequest, KeyVerificationRequest,
KeyVerificationStart, KeyVerificationStart,
KeyVerificationMac, KeyVerificationMac,
@ -266,6 +267,13 @@ public:
}; };
Q_ENUM(Roles); Q_ENUM(Roles);
enum SpecialEffect
{
Confetti,
Rainfall,
};
Q_DECLARE_FLAGS(SpecialEffects, SpecialEffect)
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
@ -450,6 +458,8 @@ signals:
void scrollToIndex(int index); void scrollToIndex(int index);
void confetti(); void confetti();
void confettiDone(); void confettiDone();
void rainfall();
void rainfallDone();
void lastMessageChanged(); void lastMessageChanged();
void notificationsChanged(); void notificationsChanged();
@ -521,8 +531,8 @@ 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; bool needsSpecialEffects_ = false;
QFlags<SpecialEffect> specialEffects_;
std::unique_ptr<RoomSummary, DeleteLaterDeleter> parentSummary = nullptr; std::unique_ptr<RoomSummary, DeleteLaterDeleter> parentSummary = nullptr;
bool parentChecked = false; bool parentChecked = false;
@ -530,6 +540,8 @@ private:
friend void EventStore::refetchOnlineKeyBackupKeys(TimelineModel *room); friend void EventStore::refetchOnlineKeyBackupKeys(TimelineModel *room);
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(TimelineModel::SpecialEffects)
template<class T> template<class T>
void void
TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType) TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType)