From 52d07320ef2854a235369f9f716bdb763368a4df Mon Sep 17 00:00:00 2001 From: James Graham Date: Sat, 29 Jun 2024 12:39:25 +0100 Subject: [PATCH] Make the author line in the bubble and reply be part of the content model --- src/enums/messagecomponenttype.h | 1 + src/models/mediamessagefiltermodel.cpp | 9 +++ src/models/messagecontentmodel.cpp | 49 +++++++++++- src/models/messagecontentmodel.h | 17 +++- src/models/messageeventmodel.cpp | 10 +-- src/models/messageeventmodel.h | 1 - src/models/messagefiltermodel.cpp | 41 ++++++---- src/models/messagefiltermodel.h | 3 +- src/models/searchmodel.cpp | 9 --- src/models/searchmodel.h | 3 - src/qml/RoomMedia.qml | 2 - src/timeline/AuthorComponent.qml | 79 +++++++++++++++++++ src/timeline/Bubble.qml | 48 ----------- src/timeline/CMakeLists.txt | 2 + src/timeline/CodeComponent.qml | 2 +- src/timeline/MessageComponentChooser.qml | 13 +-- src/timeline/MessageDelegate.qml | 34 +------- src/timeline/ReplyAuthorComponent.qml | 61 ++++++++++++++ src/timeline/ReplyComponent.qml | 25 ------ src/timeline/ReplyMessageComponentChooser.qml | 7 ++ 20 files changed, 260 insertions(+), 156 deletions(-) create mode 100644 src/timeline/AuthorComponent.qml create mode 100644 src/timeline/ReplyAuthorComponent.qml diff --git a/src/enums/messagecomponenttype.h b/src/enums/messagecomponenttype.h index d7aca7185..d4a406504 100644 --- a/src/enums/messagecomponenttype.h +++ b/src/enums/messagecomponenttype.h @@ -33,6 +33,7 @@ public: * a room message. */ enum Type { + Author, /**< The message sender and time. */ Text, /**< A text message. */ Image, /**< A message that is an image. */ Audio, /**< A message that is an audio recording. */ diff --git a/src/models/mediamessagefiltermodel.cpp b/src/models/mediamessagefiltermodel.cpp index 64025978b..4c1749ba4 100644 --- a/src/models/mediamessagefiltermodel.cpp +++ b/src/models/mediamessagefiltermodel.cpp @@ -6,6 +6,7 @@ #include #include +#include "messagecontentmodel.h" #include "messageeventmodel.h" #include "messagefiltermodel.h" @@ -70,6 +71,14 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const const auto previousEventDay = mapToSource(this->index(index.row() + 1, 0)).data(MessageEventModel::TimeRole).toDateTime().toLocalTime().date(); return day != previousEventDay; } + // Catch and force the author to be shown for all rows + if (role == MessageEventModel::ContentModelRole) { + const auto model = qvariant_cast(mapToSource(index).data(MessageEventModel::ContentModelRole)); + if (model != nullptr) { + model->setShowAuthor(true); + } + return QVariant::fromValue(model); + } return sourceModel()->data(mapToSource(index), role); } diff --git a/src/models/messagecontentmodel.cpp b/src/models/messagecontentmodel.cpp index 068472d2d..b48215027 100644 --- a/src/models/messagecontentmodel.cpp +++ b/src/models/messagecontentmodel.cpp @@ -6,12 +6,14 @@ #include +#include #include #include #include #include #include +#include #ifndef Q_OS_ANDROID #include @@ -30,20 +32,22 @@ using namespace Quotient; -MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply) +MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply, bool isPending) : QAbstractListModel(nullptr) , m_room(room) , m_eventId(event != nullptr ? event->id() : QString()) , m_event(event) + , m_isPending(isPending) , m_isReply(isReply) { initializeModel(); } -MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply) +MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply, bool isPending) : QAbstractListModel(nullptr) , m_room(room) , m_eventId(eventId) + , m_isPending(isPending) , m_isReply(isReply) { initializeModel(); @@ -77,6 +81,7 @@ void MessageContentModel::initializeModel() if (m_room != nullptr && m_event != nullptr) { if (m_event->id() == serverEvent->id()) { beginResetModel(); + m_isPending = false; m_event = serverEvent; Q_EMIT eventUpdated(); endResetModel(); @@ -165,6 +170,22 @@ void MessageContentModel::initializeModel() updateComponents(); } +bool MessageContentModel::showAuthor() const +{ + return m_showAuthor; +} + +void MessageContentModel::setShowAuthor(bool showAuthor) +{ + if (showAuthor == m_showAuthor) { + return; + } + + m_showAuthor = showAuthor; + Q_EMIT showAuthorChanged(); + updateComponents(); +} + static LinkPreviewer *emptyLinkPreview = new LinkPreviewer; QVariant MessageContentModel::data(const QModelIndex &index, int role) const @@ -207,8 +228,24 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const if (role == EventIdRole) { return eventHandler.getId(); } + if (role == TimeRole) { + const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) { + return m_event->transactionId() == pendingEvent->transactionId(); + }); + + auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated(); + return eventHandler.getTime(m_isPending, lastUpdated); + } + if (role == TimeStringRole) { + const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) { + return m_event->transactionId() == pendingEvent->transactionId(); + }); + + auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated(); + return eventHandler.getTimeString(false, QLocale::ShortFormat, m_isPending, lastUpdated); + } if (role == AuthorRole) { - return QVariant::fromValue(eventHandler.getAuthor(false)); + return QVariant::fromValue(eventHandler.getAuthor(m_isPending)); } if (role == MediaInfoRole) { return eventHandler.getMediaInfo(); @@ -268,6 +305,8 @@ QHash MessageContentModel::roleNames() const roles[ComponentTypeRole] = "componentType"; roles[ComponentAttributesRole] = "componentAttributes"; roles[EventIdRole] = "eventId"; + roles[TimeRole] = "time"; + roles[TimeStringRole] = "timeString"; roles[AuthorRole] = "author"; roles[MediaInfoRole] = "mediaInfo"; roles[FileTransferInfoRole] = "fileTransferInfo"; @@ -295,6 +334,10 @@ void MessageContentModel::updateComponents(bool isEditing) return; } + if (m_showAuthor) { + m_components += MessageComponent{MessageComponentType::Author, QString(), {}}; + } + if (eventCast(m_event) && eventCast(m_event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) { m_components += MessageComponent{MessageComponentType::Verification, QString(), {}}; diff --git a/src/models/messagecontentmodel.h b/src/models/messagecontentmodel.h index 575c50656..71244b0f2 100644 --- a/src/models/messagecontentmodel.h +++ b/src/models/messagecontentmodel.h @@ -39,6 +39,11 @@ class MessageContentModel : public QAbstractListModel QML_ELEMENT QML_UNCREATABLE("") + /** + * @brief Whether the author component is being shown. + */ + Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged) + public: /** * @brief Defines the model roles. @@ -48,6 +53,8 @@ public: ComponentTypeRole, /**< The type of component to visualise the message. */ ComponentAttributesRole, /**< The attributes of the component. */ EventIdRole, /**< The matrix event ID of the event. */ + TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */ + TimeStringRole, /**< The timestamp for when the event was sent as a string (in QLocale::ShortFormat). */ AuthorRole, /**< The author of the event. */ MediaInfoRole, /**< The media info for the event. */ FileTransferInfoRole, /**< FileTransferInfo for any downloading files. */ @@ -66,8 +73,11 @@ public: }; Q_ENUM(Roles) - explicit MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply = false); - MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply = false); + explicit MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply = false, bool isPending = false); + MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply = false, bool isPending = false); + + bool showAuthor() const; + void setShowAuthor(bool showAuthor); /** * @brief Get the given role value at the given index. @@ -98,6 +108,7 @@ public: Q_INVOKABLE void closeLinkPreview(int row); Q_SIGNALS: + void showAuthorChanged(); void eventUpdated(); private: @@ -105,6 +116,8 @@ private: QString m_eventId; const Quotient::RoomEvent *m_event = nullptr; + bool m_isPending; + bool m_showAuthor = true; bool m_isReply; void initializeModel(); diff --git a/src/models/messageeventmodel.cpp b/src/models/messageeventmodel.cpp index 997c2ddd7..d7625f6cb 100644 --- a/src/models/messageeventmodel.cpp +++ b/src/models/messageeventmodel.cpp @@ -36,7 +36,6 @@ QHash MessageEventModel::roleNames() const roles[DelegateTypeRole] = "delegateType"; roles[EventIdRole] = "eventId"; roles[TimeRole] = "time"; - roles[TimeStringRole] = "timeString"; roles[SectionRole] = "section"; roles[AuthorRole] = "author"; roles[HighlightRole] = "isHighlighted"; @@ -153,7 +152,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room) } if (biggest < m_currentRoom->maxTimelineIndex()) { auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1; - refreshEventRoles(rowBelowInserted, {MessageFilterModel::ShowAuthorRole}); + refreshEventRoles(rowBelowInserted, {ContentModelRole}); } for (auto i = m_currentRoom->maxTimelineIndex() - biggest; i <= m_currentRoom->maxTimelineIndex() - lowest; ++i) { refreshLastUserEvents(i); @@ -183,7 +182,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room) refreshRow(timelineBaseIndex()); // Refresh the looks refreshLastUserEvents(0); if (timelineBaseIndex() > 0) { // Refresh below, see #312 - refreshEventRoles(timelineBaseIndex() - 1, {MessageFilterModel::ShowAuthorRole}); + refreshEventRoles(timelineBaseIndex() - 1, {ContentModelRole}); } }); connect(m_currentRoom, &Room::pendingEventChanged, this, &MessageEventModel::refreshRow); @@ -525,11 +524,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const return eventHandler.getTime(isPending, lastUpdated); } - if (role == TimeStringRole) { - auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime(); - return eventHandler.getTimeString(false, QLocale::ShortFormat, isPending, lastUpdated); - } - if (role == SectionRole) { auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime(); return eventHandler.getTimeString(true, QLocale::ShortFormat, isPending, lastUpdated); diff --git a/src/models/messageeventmodel.h b/src/models/messageeventmodel.h index 8ae8891f0..e86ad1349 100644 --- a/src/models/messageeventmodel.h +++ b/src/models/messageeventmodel.h @@ -42,7 +42,6 @@ public: DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */ EventIdRole, /**< The matrix event ID of the event. */ TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */ - TimeStringRole, /**< The timestamp for when the event was sent as a string (in QLocale::ShortFormat). */ SectionRole, /**< The date of the event as a string. */ AuthorRole, /**< The author of the event. */ HighlightRole, /**< Whether the event should be highlighted. */ diff --git a/src/models/messagefiltermodel.cpp b/src/models/messagefiltermodel.cpp index caeb56dcd..e5f025abc 100644 --- a/src/models/messagefiltermodel.cpp +++ b/src/models/messagefiltermodel.cpp @@ -6,6 +6,7 @@ #include #include "enums/delegatetype.h" +#include "messagecontentmodel.h" #include "messageeventmodel.h" #include "neochatconfig.h" #include "timelinemodel.h" @@ -91,22 +92,12 @@ QVariant MessageFilterModel::data(const QModelIndex &index, int role) const return authorList(mapToSource(index).row()); } else if (role == ExcessAuthorsRole) { return excessAuthors(mapToSource(index).row()); - } else if (role == ShowAuthorRole) { - for (auto r = index.row() + 1; r < rowCount(); ++r) { - auto i = this->index(r, 0); - // Note !itemData(i).empty() is a check for instances where rows have been removed, e.g. when the read marker is moved. - // While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index. - // See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows - if (data(i, MessageEventModel::SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) { - return data(i, MessageEventModel::AuthorRole) != data(index, MessageEventModel::AuthorRole) - || data(i, MessageEventModel::DelegateTypeRole) == DelegateType::State - || data(i, MessageEventModel::TimeRole).toDateTime().msecsTo(data(index, MessageEventModel::TimeRole).toDateTime()) > 600000 - || data(i, MessageEventModel::TimeRole).toDateTime().toLocalTime().date().day() - != data(index, MessageEventModel::TimeRole).toDateTime().toLocalTime().date().day(); - } + } else if (role == MessageEventModel::ContentModelRole) { + const auto model = qvariant_cast(mapToSource(index).data(MessageEventModel::ContentModelRole)); + if (model != nullptr && !showAuthor(index)) { + model->setShowAuthor(false); } - - return true; + return QVariant::fromValue(model); } return QSortFilterProxyModel::data(index, role); } @@ -118,10 +109,28 @@ QHash MessageFilterModel::roleNames() const roles[StateEventsRole] = "stateEvents"; roles[AuthorListRole] = "authorList"; roles[ExcessAuthorsRole] = "excessAuthors"; - roles[ShowAuthorRole] = "showAuthor"; return roles; } +bool MessageFilterModel::showAuthor(QModelIndex index) const +{ + for (auto r = index.row() + 1; r < rowCount(); ++r) { + auto i = this->index(r, 0); + // Note !itemData(i).empty() is a check for instances where rows have been removed, e.g. when the read marker is moved. + // While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index. + // See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows + if (data(i, MessageEventModel::SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) { + return data(i, MessageEventModel::AuthorRole) != data(index, MessageEventModel::AuthorRole) + || data(i, MessageEventModel::DelegateTypeRole) == DelegateType::State + || data(i, MessageEventModel::TimeRole).toDateTime().msecsTo(data(index, MessageEventModel::TimeRole).toDateTime()) > 600000 + || data(i, MessageEventModel::TimeRole).toDateTime().toLocalTime().date().day() + != data(index, MessageEventModel::TimeRole).toDateTime().toLocalTime().date().day(); + } + } + + return true; +} + QString MessageFilterModel::aggregateEventToString(int sourceRow) const { QStringList parts; diff --git a/src/models/messagefiltermodel.h b/src/models/messagefiltermodel.h index 3b4896eff..1791af578 100644 --- a/src/models/messagefiltermodel.h +++ b/src/models/messagefiltermodel.h @@ -34,7 +34,6 @@ public: StateEventsRole, /**< List of state events in the aggregated state. */ AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */ ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */ - ShowAuthorRole, /**< Whether the author (name and avatar) should be shown at this message. */ LastRole, // Keep this last }; @@ -62,6 +61,8 @@ public: private: bool eventIsVisible(int sourceRow, const QModelIndex &sourceParent) const; + bool showAuthor(QModelIndex index) const; + /** * @brief Aggregation of the text of consecutive state events starting at row. * diff --git a/src/models/searchmodel.cpp b/src/models/searchmodel.cpp index 5f6e68518..4a8dc2031 100644 --- a/src/models/searchmodel.cpp +++ b/src/models/searchmodel.cpp @@ -82,8 +82,6 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const EventHandler eventHandler(m_room, &event); switch (role) { - case ShowAuthorRole: - return true; case AuthorRole: return QVariant::fromValue(eventHandler.getAuthor()); case ShowSectionRole: @@ -93,10 +91,6 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const return event.originTimestamp().date() != m_result->results[row - 1].result->originTimestamp().date(); case SectionRole: return eventHandler.getTimeString(true); - case TimeRole: - return eventHandler.getTime(); - case TimeStringRole: - return eventHandler.getTimeString(false); case ShowReactionsRole: return false; case ShowReadMarkersRole: @@ -145,9 +139,6 @@ QHash SearchModel::roleNames() const {AuthorRole, "author"}, {ShowSectionRole, "showSection"}, {SectionRole, "section"}, - {TimeRole, "time"}, - {TimeStringRole, "timeString"}, - {ShowAuthorRole, "showAuthor"}, {EventIdRole, "eventId"}, {ExcessReadMarkersRole, "excessReadMarkers"}, {HighlightRole, "isHighlighted"}, diff --git a/src/models/searchmodel.h b/src/models/searchmodel.h index 7eec953dd..79477741c 100644 --- a/src/models/searchmodel.h +++ b/src/models/searchmodel.h @@ -52,12 +52,9 @@ public: */ enum Roles { DelegateTypeRole = Qt::DisplayRole + 1, - ShowAuthorRole, AuthorRole, ShowSectionRole, SectionRole, - TimeRole, - TimeStringRole, EventIdRole, ExcessReadMarkersRole, HighlightRole, diff --git a/src/qml/RoomMedia.qml b/src/qml/RoomMedia.qml index 8fd62969b..f12ea6742 100644 --- a/src/qml/RoomMedia.qml +++ b/src/qml/RoomMedia.qml @@ -50,7 +50,6 @@ QQC2.ScrollView { DelegateChoice { roleValue: MediaMessageFilterModel.Image delegate: MessageDelegate { - alwaysShowAuthor: true alwaysFillWidth: true cardBackground: false room: root.currentRoom @@ -60,7 +59,6 @@ QQC2.ScrollView { DelegateChoice { roleValue: MediaMessageFilterModel.Video delegate: MessageDelegate { - alwaysShowAuthor: true alwaysFillWidth: true cardBackground: false room: root.currentRoom diff --git a/src/timeline/AuthorComponent.qml b/src/timeline/AuthorComponent.qml new file mode 100644 index 000000000..bd19ef0ab --- /dev/null +++ b/src/timeline/AuthorComponent.qml @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami + +import org.kde.neochat + +RowLayout { + id: root + + /** + * @brief The message author. + * + * This should consist of the following: + * - id - The matrix ID of the author. + * - isLocalUser - Whether the author is the local user. + * - avatarSource - The mxc URL for the author's avatar in the current room. + * - avatarMediaId - The media ID of the author's avatar. + * - avatarUrl - The mxc URL for the author's avatar. + * - displayName - The display name of the author. + * - display - The name of the author. + * - color - The color for the author. + * - object - The Quotient::User object for the author. + * + * @sa Quotient::User + */ + required property var author + + /** + * @brief The timestamp of the message. + */ + required property var time + + /** + * @brief The timestamp of the message as a string. + */ + required property string timeString + + /** + * @brief The maximum width that the bubble's content can be. + */ + property real maxContentWidth: -1 + + Layout.fillWidth: true + Layout.maximumWidth: root.maxContentWidth + + implicitHeight: Math.max(nameButton.implicitHeight, timeLabel.implicitHeight) + + QQC2.AbstractButton { + id: nameButton + Layout.fillWidth: true + contentItem: QQC2.Label { + text: root.author.disambiguatedName + color: root.author.color + textFormat: Text.PlainText + font.weight: Font.Bold + elide: Text.ElideRight + } + Accessible.name: contentItem.text + onClicked: RoomManager.resolveResource(root.author.uri) + } + QQC2.Label { + id: timeLabel + text: root.timeString + horizontalAlignment: Text.AlignRight + color: Kirigami.Theme.disabledTextColor + QQC2.ToolTip.visible: timeHoverHandler.hovered + QQC2.ToolTip.text: root.time.toLocaleString(Qt.locale(), Locale.LongFormat) + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + + HoverHandler { + id: timeHoverHandler + } + } +} diff --git a/src/timeline/Bubble.qml b/src/timeline/Bubble.qml index bbb96cf8b..c99ac2a00 100644 --- a/src/timeline/Bubble.qml +++ b/src/timeline/Bubble.qml @@ -41,21 +41,6 @@ QQC2.Control { */ property var author - /** - * @brief Whether the author should be shown. - */ - required property bool showAuthor - - /** - * @brief The timestamp of the message. - */ - property var time - - /** - * @brief The timestamp of the message as a string. - */ - property string timeString - /** * @brief Whether the message should be highlighted. */ @@ -107,45 +92,12 @@ QQC2.Control { contentItem: ColumnLayout { id: contentColumn spacing: Kirigami.Units.smallSpacing - RowLayout { - id: headerRow - Layout.maximumWidth: root.maxContentWidth - implicitHeight: Math.max(nameButton.implicitHeight, timeLabel.implicitHeight) - visible: root.showAuthor - QQC2.AbstractButton { - id: nameButton - Layout.fillWidth: true - contentItem: QQC2.Label { - text: root.author.disambiguatedName - color: root.author.color - textFormat: Text.PlainText - font.weight: Font.Bold - elide: Text.ElideRight - } - Accessible.name: contentItem.text - onClicked: RoomManager.resolveResource(root.author.uri) - } - QQC2.Label { - id: timeLabel - text: root.timeString - horizontalAlignment: Text.AlignRight - color: Kirigami.Theme.disabledTextColor - QQC2.ToolTip.visible: timeHoverHandler.hovered - QQC2.ToolTip.text: root.time.toLocaleString(Qt.locale(), Locale.LongFormat) - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - - HoverHandler { - id: timeHoverHandler - } - } - } Repeater { id: contentRepeater model: root.contentModel delegate: MessageComponentChooser { room: root.room index: root.index - time: root.time actionsHandler: root.actionsHandler timeline: root.timeline maxContentWidth: root.maxContentWidth diff --git a/src/timeline/CMakeLists.txt b/src/timeline/CMakeLists.txt index 610ac6bcb..30e194a91 100644 --- a/src/timeline/CMakeLists.txt +++ b/src/timeline/CMakeLists.txt @@ -19,6 +19,7 @@ qt_add_qml_module(timeline SectionDelegate.qml MessageComponentChooser.qml ReplyMessageComponentChooser.qml + AuthorComponent.qml AudioComponent.qml CodeComponent.qml EncryptedComponent.qml @@ -42,6 +43,7 @@ qt_add_qml_module(timeline PdfPreviewComponent.qml PollComponent.qml QuoteComponent.qml + ReplyAuthorComponent.qml ReplyComponent.qml StateComponent.qml TextComponent.qml diff --git a/src/timeline/CodeComponent.qml b/src/timeline/CodeComponent.qml index 83f2be110..be55e88de 100644 --- a/src/timeline/CodeComponent.qml +++ b/src/timeline/CodeComponent.qml @@ -25,7 +25,7 @@ QQC2.Control { /** * @brief The timestamp of the message. */ - property date time + required property var time /** * @brief The display text of the message. diff --git a/src/timeline/MessageComponentChooser.qml b/src/timeline/MessageComponentChooser.qml index 392b5cf82..6d777e591 100644 --- a/src/timeline/MessageComponentChooser.qml +++ b/src/timeline/MessageComponentChooser.qml @@ -22,11 +22,6 @@ DelegateChooser { */ required property var index - /** - * @brief The timestamp of the message. - */ - required property var time - /** * @brief The ActionsHandler object to use. * @@ -64,6 +59,13 @@ DelegateChooser { role: "componentType" + DelegateChoice { + roleValue: MessageComponentType.Author + delegate: AuthorComponent { + maxContentWidth: root.maxContentWidth + } + } + DelegateChoice { roleValue: MessageComponentType.Text delegate: TextComponent { @@ -96,7 +98,6 @@ DelegateChooser { DelegateChoice { roleValue: MessageComponentType.Code delegate: CodeComponent { - time: root.time maxContentWidth: root.maxContentWidth onSelectedTextChanged: selectedText => { root.selectedTextChanged(selectedText); diff --git a/src/timeline/MessageDelegate.qml b/src/timeline/MessageDelegate.qml index 24b1b94f8..685041987 100644 --- a/src/timeline/MessageDelegate.qml +++ b/src/timeline/MessageDelegate.qml @@ -43,16 +43,6 @@ TimelineDelegate { */ required property string eventId - /** - * @brief The timestamp of the message. - */ - required property var time - - /** - * @brief The timestamp of the message as a string. - */ - required property string timeString - /** * @brief The message author. * @@ -62,21 +52,6 @@ TimelineDelegate { */ required property var author - /** - * @brief Whether the author should be shown. - */ - required property bool showAuthor - - /** - * @brief Whether the author should always be shown. - * - * This is primarily used when these delegates are used in a filtered list of - * events rather than a sequential timeline, e.g. the media model view. - * - * @note This setting still respects the avatar configuration settings. - */ - property bool alwaysShowAuthor: false - /** * @brief The model to visualise the content of the message. */ @@ -246,11 +221,11 @@ TimelineDelegate { id: mainContainer Layout.fillWidth: true - Layout.topMargin: root.showAuthor || root.alwaysShowAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing) + Layout.topMargin: root.contentModel.showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing) Layout.leftMargin: Kirigami.Units.smallSpacing Layout.rightMargin: Kirigami.Units.smallSpacing - implicitHeight: Math.max(root.showAuthor || root.alwaysShowAuthor ? avatar.implicitHeight : 0, bubble.height) + implicitHeight: Math.max(root.contentModel.showAuthor ? avatar.implicitHeight : 0, bubble.height) // show hover actions onHoveredChanged: { @@ -270,7 +245,7 @@ TimelineDelegate { topMargin: Kirigami.Units.smallSpacing } - visible: (root.showAuthor || root.alwaysShowAuthor) && Config.showAvatarInTimeline && (Config.compactLayout || !_private.showUserMessageOnRight) + visible: root.contentModel.showAuthor && Config.showAvatarInTimeline && (Config.compactLayout || !_private.showUserMessageOnRight) name: root.author.displayName source: root.author.avatarUrl color: root.author.color @@ -316,9 +291,6 @@ TimelineDelegate { index: root.index author: root.author - showAuthor: root.showAuthor || root.alwaysShowAuthor - time: root.time - timeString: root.timeString contentModel: root.contentModel actionsHandler: root.ListView.view?.actionsHandler ?? null diff --git a/src/timeline/ReplyAuthorComponent.qml b/src/timeline/ReplyAuthorComponent.qml new file mode 100644 index 000000000..756261b70 --- /dev/null +++ b/src/timeline/ReplyAuthorComponent.qml @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami +import org.kde.kirigamiaddons.labs.components as KirigamiComponents + +RowLayout { + id: root + + /** + * @brief The message author. + * + * This should consist of the following: + * - id - The matrix ID of the author. + * - isLocalUser - Whether the author is the local user. + * - avatarSource - The mxc URL for the author's avatar in the current room. + * - avatarMediaId - The media ID of the author's avatar. + * - avatarUrl - The mxc URL for the author's avatar. + * - displayName - The display name of the author. + * - display - The name of the author. + * - color - The color for the author. + * - object - The Quotient::User object for the author. + * + * @sa Quotient::User + */ + required property var author + + /** + * @brief The maximum width that the bubble's content can be. + */ + property real maxContentWidth: -1 + + Layout.fillWidth: true + Layout.maximumWidth: root.maxContentWidth + + implicitHeight: Math.max(replyAvatar.implicitHeight, replyName.implicitHeight) + spacing: Kirigami.Units.largeSpacing + + KirigamiComponents.Avatar { + id: replyAvatar + + implicitWidth: Kirigami.Units.iconSizes.small + implicitHeight: Kirigami.Units.iconSizes.small + + source: root.author.avatarUrl + name: root.author.displayName + color: root.author.color + } + QQC2.Label { + id: replyName + Layout.fillWidth: true + + color: root.author.color + text: root.author.disambiguatedName + elide: Text.ElideRight + } +} diff --git a/src/timeline/ReplyComponent.qml b/src/timeline/ReplyComponent.qml index 5932be367..73172b064 100644 --- a/src/timeline/ReplyComponent.qml +++ b/src/timeline/ReplyComponent.qml @@ -66,31 +66,6 @@ RowLayout { id: contentColumn spacing: Kirigami.Units.smallSpacing - RowLayout { - id: headerRow - implicitHeight: Math.max(replyAvatar.implicitHeight, replyName.implicitHeight) - Layout.maximumWidth: root.maxContentWidth - spacing: Kirigami.Units.largeSpacing - - KirigamiComponents.Avatar { - id: replyAvatar - - implicitWidth: Kirigami.Units.iconSizes.small - implicitHeight: Kirigami.Units.iconSizes.small - - source: root.replyAuthor.avatarUrl - name: root.replyAuthor.displayName - color: root.replyAuthor.color - } - QQC2.Label { - id: replyName - Layout.fillWidth: true - - color: root.replyAuthor.color - text: root.replyAuthor.displayName - elide: Text.ElideRight - } - } Repeater { id: contentRepeater model: root.replyContentModel diff --git a/src/timeline/ReplyMessageComponentChooser.qml b/src/timeline/ReplyMessageComponentChooser.qml index d1f6af510..4dd92baf7 100644 --- a/src/timeline/ReplyMessageComponentChooser.qml +++ b/src/timeline/ReplyMessageComponentChooser.qml @@ -25,6 +25,13 @@ DelegateChooser { role: "componentType" + DelegateChoice { + roleValue: MessageComponentType.Author + delegate: ReplyAuthorComponent { + maxContentWidth: root.maxContentWidth + } + } + DelegateChoice { roleValue: MessageComponentType.Text delegate: TextComponent {