diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index 0e211ded..44da16bf 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 8d7b7919..9c12b967 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>(e); + if (!event) + return {}; + + mtx::events::StateEvent *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>(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 &newI, + const std::map &oldI) { + QStringList added; + for (const auto &[shortcode, img] : newI) { + if (!oldI.count(shortcode)) + added.push_back(QStringLiteral(" (~%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:
%2") + .arg(sender, removed.join(", ")); + } + if (!added.isEmpty()) { + if (!msg.isEmpty()) + msg += "
"; + msg += tr("%1 added the following images to the pack:
%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 7e21a394..c52473b1 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);