diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index 0e211ded335393a65106b7c148ae453230cef875..44da16bfcf93fde8813468979dbc1899ea1c507c 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -284,11 +284,12 @@ Item { isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: qsTr("%1 changed the stickers and emotes in this room.").arg(d.userName) + formatted: d.relatedEventCacheBuster, room.formatImagePackEvent(d.eventId) } } + DelegateChoice { roleValue: MtxEvent.CanonicalAlias @@ -390,7 +391,6 @@ Item { } DelegateChoice { - // TODO: make a more complex formatter for the power levels. roleValue: MtxEvent.PowerLevels NoticeMessage { diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 8d7b7919ff55e182e8c26b3563744be657f66bdf..9c12b967ee2303b4333cdc9b1efab7e3957fc8e0 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -197,6 +197,12 @@ qml_mtx_events::toRoomEventType(mtx::events::EventType e) return qml_mtx_events::EventType::SpaceParent; case EventType::SpaceChild: return qml_mtx_events::EventType::SpaceChild; + case EventType::ImagePackInRoom: + return qml_mtx_events::ImagePackInRoom; + case EventType::ImagePackInAccountData: + return qml_mtx_events::ImagePackInAccountData; + case EventType::ImagePackRooms: + return qml_mtx_events::ImagePackRooms; case EventType::Unsupported: return qml_mtx_events::EventType::Unsupported; default: @@ -2201,6 +2207,69 @@ TimelineModel::formatPowerLevelEvent(const QString &id) } } +QString +TimelineModel::formatImagePackEvent(const QString &id) +{ + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return {}; + + auto event = std::get_if<mtx::events::StateEvent<mtx::events::msc2545::ImagePack>>(e); + if (!event) + return {}; + + mtx::events::StateEvent<mtx::events::msc2545::ImagePack> *prevEvent = nullptr; + if (!event->unsigned_data.replaces_state.empty()) { + auto tempPrevEvent = events.get(event->unsigned_data.replaces_state, event->event_id); + if (tempPrevEvent) { + prevEvent = + std::get_if<mtx::events::StateEvent<mtx::events::msc2545::ImagePack>>(tempPrevEvent); + } + } + + const auto &newImages = event->content.images; + const auto oldImages = prevEvent ? prevEvent->content.images : decltype(newImages){}; + + auto ascent = QFontMetrics(UserSettings::instance()->font()).ascent(); + + auto calcChange = [ascent](const std::map<std::string, mtx::events::msc2545::PackImage> &newI, + const std::map<std::string, mtx::events::msc2545::PackImage> &oldI) { + QStringList added; + for (const auto &[shortcode, img] : newI) { + if (!oldI.count(shortcode)) + added.push_back(QStringLiteral("<img data-mx-emoticon height=%1 src=\"%2\"> (~%3)") + .arg(ascent) + .arg(QString::fromStdString(img.url) + .replace("mxc://", "image://mxcImage/") + .toHtmlEscaped(), + QString::fromStdString(shortcode))); + } + return added; + }; + + auto added = calcChange(newImages, oldImages); + auto removed = calcChange(oldImages, newImages); + + auto sender = utils::replaceEmoji(displayName(QString::fromStdString(event->sender))); + + QString msg; + + if (!removed.isEmpty()) { + msg = tr("%1 removed the following images from the pack:<br>%2") + .arg(sender, removed.join(", ")); + } + if (!added.isEmpty()) { + if (!msg.isEmpty()) + msg += "<br>"; + msg += tr("%1 added the following images to the pack:<br>%2").arg(sender, added.join(", ")); + } + + if (msg.isEmpty()) + return tr("%1 changed the sticker and emotes in this room.").arg(sender); + else + return msg; +} + QVariantMap TimelineModel::formatRedactedEvent(const QString &id) { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 7e21a394ee300236794f324071ff288d7683126c..c52473b19b78a325fd102e33ee7704926e631e1b 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -262,6 +262,7 @@ public: Q_INVOKABLE QString formatHistoryVisibilityEvent(const QString &id); Q_INVOKABLE QString formatGuestAccessEvent(const QString &id); Q_INVOKABLE QString formatPowerLevelEvent(const QString &id); + Q_INVOKABLE QString formatImagePackEvent(const QString &id); Q_INVOKABLE QVariantMap formatRedactedEvent(const QString &id); Q_INVOKABLE void viewRawMessage(const QString &id);