mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +03:00
Merge pull request #1407 from Nheko-Reborn/ducktyping
Implement unknown msgtype functionality
This commit is contained in:
commit
7973fbce8c
14 changed files with 345 additions and 112 deletions
|
@ -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 "")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
112
resources/qml/ui/TimelineEffects.qml
Normal file
112
resources/qml/ui/TimelineEffects.qml
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -46,6 +46,8 @@ public:
|
||||||
RainbowNotice,
|
RainbowNotice,
|
||||||
Confetti,
|
Confetti,
|
||||||
RainbowConfetti,
|
RainbowConfetti,
|
||||||
|
Rainfall,
|
||||||
|
Msgtype,
|
||||||
Goto,
|
Goto,
|
||||||
ConvertToDm,
|
ConvertToDm,
|
||||||
ConvertToRoom,
|
ConvertToRoom,
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
38
src/Utils.h
38
src/Utils.h
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(""));
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue