From 88fada89eaaf64f413e17ba5733c06b11fc1c211 Mon Sep 17 00:00:00 2001 From: James Graham Date: Fri, 12 May 2023 15:54:15 +0000 Subject: [PATCH] Limit the maximum number of avatars shown Limit the maximum number of avatars shown for other user read markers and collapsed state events For state events \ ![image](/uploads/0d3ec7c3da02a8832dfdb18dc265db92/image.png) For read markers \ ![image](/uploads/5694f14927e5c10b2159e58445c8a0a3/image.png) --- src/models/collapsestateproxymodel.cpp | 37 +++++++++++++++++++ src/models/collapsestateproxymodel.h | 10 ++++- src/models/messageeventmodel.cpp | 28 ++++++++++++-- src/models/messageeventmodel.h | 3 +- src/qml/Component/Timeline/AvatarFlow.qml | 29 ++++++++++++++- src/qml/Component/Timeline/StateDelegate.qml | 30 ++++++++++++++- .../Component/Timeline/TimelineContainer.qml | 1 + 7 files changed, 130 insertions(+), 8 deletions(-) diff --git a/src/models/collapsestateproxymodel.cpp b/src/models/collapsestateproxymodel.cpp index 2d674e4a9..51bfda6c5 100644 --- a/src/models/collapsestateproxymodel.cpp +++ b/src/models/collapsestateproxymodel.cpp @@ -24,6 +24,8 @@ QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const return stateEventsList(mapToSource(index).row()); } else if (role == AuthorListRole) { return authorList(mapToSource(index).row()); + } else if (role == ExcessAuthorsRole) { + return excessAuthors(mapToSource(index).row()); } return sourceModel()->data(mapToSource(index), role); } @@ -34,6 +36,7 @@ QHash CollapseStateProxyModel::roleNames() const roles[AggregateDisplayRole] = "aggregateDisplay"; roles[StateEventsRole] = "stateEvents"; roles[AuthorListRole] = "authorList"; + roles[ExcessAuthorsRole] = "excessAuthors"; return roles; } @@ -130,5 +133,39 @@ QVariantList CollapseStateProxyModel::authorList(int sourceRow) const break; } } + + if (uniqueAuthors.count() > 5) { + uniqueAuthors = uniqueAuthors.mid(0, 5); + } return uniqueAuthors; } + +QString CollapseStateProxyModel::excessAuthors(int row) const +{ + QVariantList uniqueAuthors; + for (int i = row; i >= 0; i--) { + QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole); + if (!uniqueAuthors.contains(nextAvatar)) { + uniqueAuthors.append(nextAvatar); + } + if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) + != MessageEventModel::DelegateType::State // If it's not a state event + || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible + ) { + break; + } + } + + int excessAuthors; + if (uniqueAuthors.count() > 5) { + excessAuthors = uniqueAuthors.count() - 5; + } else { + excessAuthors = 0; + } + QString excessAuthorsString; + if (excessAuthors == 0) { + return QString(); + } else { + return QStringLiteral("+ %1").arg(excessAuthors); + } +} diff --git a/src/models/collapsestateproxymodel.h b/src/models/collapsestateproxymodel.h index 3b868bcd9..75d5fe870 100644 --- a/src/models/collapsestateproxymodel.h +++ b/src/models/collapsestateproxymodel.h @@ -25,7 +25,8 @@ public: enum Roles { AggregateDisplayRole = MessageEventModel::LastRole + 1, /**< Single line aggregation of all the state events. */ StateEventsRole, /**< List of state events in the aggregated state. */ - AuthorListRole, /**< List of unique authors of the aggregated state event. */ + AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */ + ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */ }; /** @@ -67,7 +68,12 @@ private: [[nodiscard]] QVariantList stateEventsList(int row) const; /** - * @brief List of unique authors for the aggregate state events starting at row. + * @brief List of the first 5 unique authors for the aggregate state events starting at row. */ [[nodiscard]] QVariantList authorList(int row) const; + + /** + * @brief The number of unique authors beyond the first 5 for the aggregate state events starting at row. + */ + [[nodiscard]] QString excessAuthors(int row) const; }; diff --git a/src/models/messageeventmodel.cpp b/src/models/messageeventmodel.cpp index ce69dab79..13caa3381 100644 --- a/src/models/messageeventmodel.cpp +++ b/src/models/messageeventmodel.cpp @@ -57,6 +57,7 @@ QHash MessageEventModel::roleNames() const roles[ShowAuthorRole] = "showAuthor"; roles[ShowSectionRole] = "showSection"; roles[ReadMarkersRole] = "readMarkers"; + roles[ExcessReadMarkersRole] = "excessReadMarkers"; roles[ReadMarkersStringRole] = "readMarkersString"; roles[ShowReadMarkersRole] = "showReadMarkers"; roles[ReactionRole] = "reaction"; @@ -248,7 +249,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room) connect(m_currentRoom, &Room::changed, this, [this]() { for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) { auto event = it->event(); - refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole}); + refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole, ExcessReadMarkersRole}); } }); connect(m_currentRoom, &Room::newFileTransfer, this, &MessageEventModel::refreshEvent); @@ -852,8 +853,13 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const if (role == ReadMarkersRole) { #ifdef QUOTIENT_07 - auto userIds = room()->userIdsAtEvent(evt.id()); - userIds.remove(m_currentRoom->localUser()->id()); + auto userIds_temp = room()->userIdsAtEvent(evt.id()); + userIds_temp.remove(m_currentRoom->localUser()->id()); + + auto userIds = userIds_temp.values(); + if (userIds.count() > 5) { + userIds = userIds.mid(0, 5); + } #else auto userIds = room()->usersAtEventId(evt.id()); userIds.removeAll(m_currentRoom->localUser()); @@ -873,6 +879,22 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const return users; } + if (role == ExcessReadMarkersRole) { +#ifdef QUOTIENT_07 + auto userIds = room()->userIdsAtEvent(evt.id()); + userIds.remove(m_currentRoom->localUser()->id()); +#else + auto userIds = room()->usersAtEventId(evt.id()); + userIds.removeAll(m_currentRoom->localUser()); +#endif + + if (userIds.count() > 5) { + return QStringLiteral("+ ") + QString::number(userIds.count() - 5); + } else { + return QString(); + } + } + if (role == ReadMarkersStringRole) { #ifdef QUOTIENT_07 auto userIds = room()->userIdsAtEvent(evt.id()); diff --git a/src/models/messageeventmodel.h b/src/models/messageeventmodel.h index 294688ea3..20d249e19 100644 --- a/src/models/messageeventmodel.h +++ b/src/models/messageeventmodel.h @@ -86,7 +86,8 @@ public: ShowAuthorRole, /**< Whether the author's name should be shown. */ ShowSectionRole, /**< Whether the section header should be shown. */ - ReadMarkersRole, /**< Other users at the event for read marker tracking. */ + ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */ + ExcessReadMarkersRole, /**< The number of other users at the event after the first 5. */ ReadMarkersStringRole, /**< String with the display name and mxID of the users at the event. */ ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */ ReactionRole, /**< List of reactions to this event. */ diff --git a/src/qml/Component/Timeline/AvatarFlow.qml b/src/qml/Component/Timeline/AvatarFlow.qml index 8774054fe..df63da4cb 100644 --- a/src/qml/Component/Timeline/AvatarFlow.qml +++ b/src/qml/Component/Timeline/AvatarFlow.qml @@ -12,19 +12,46 @@ Flow { property var avatarSize: Kirigami.Units.iconSizes.small property alias model: avatarFlowRepeater.model property string toolTipText + property alias excessAvatars: excessAvatarsLabel.text spacing: -avatarSize / 2 Repeater { id: avatarFlowRepeater delegate: Kirigami.Avatar { + topInset: Kirigami.Units.smallSpacing / 2 + topPadding: Kirigami.Units.smallSpacing / 2 implicitWidth: avatarSize - implicitHeight: avatarSize + implicitHeight: avatarSize + Kirigami.Units.smallSpacing / 2 name: modelData.displayName source: modelData.avatarSource color: modelData.color } } + QQC2.Label { + id: excessAvatarsLabel + visible: text !== "" + color: Kirigami.Theme.textColor + horizontalAlignment: Text.AlignHCenter + background: Kirigami.ShadowedRectangle { + color: Kirigami.Theme.backgroundColor + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.View + radius: height / 2 + shadow.size: Kirigami.Units.smallSpacing + shadow.color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10) + border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15) + border.width: 1 + } + + height: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing + width: Math.max(excessAvatarsTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height) + + TextMetrics { + id: excessAvatarsTextMetrics + text: excessAvatarsLabel.text + } + } QQC2.ToolTip.text: toolTipText QQC2.ToolTip.visible: hoverHandler.hovered diff --git a/src/qml/Component/Timeline/StateDelegate.qml b/src/qml/Component/Timeline/StateDelegate.qml index 4ed626ff8..2f4fb08be 100644 --- a/src/qml/Component/Timeline/StateDelegate.qml +++ b/src/qml/Component/Timeline/StateDelegate.qml @@ -78,14 +78,41 @@ QQC2.Control { Repeater { model: authorList delegate: Kirigami.Avatar { + topInset: Kirigami.Units.smallSpacing / 2 + topPadding: Kirigami.Units.smallSpacing / 2 implicitWidth: Kirigami.Units.iconSizes.small - implicitHeight: Kirigami.Units.iconSizes.small + implicitHeight: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing / 2 name: modelData.displayName source: modelData.avatarSource color: modelData.color } } + QQC2.Label { + id: excessAuthorsLabel + text: model.excessAuthors + visible: model.excessAuthors !== "" + color: Kirigami.Theme.textColor + horizontalAlignment: Text.AlignHCenter + background: Kirigami.ShadowedRectangle { + color: Kirigami.Theme.backgroundColor + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.View + radius: height / 2 + shadow.size: Kirigami.Units.smallSpacing + shadow.color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10) + border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15) + border.width: 1 + } + + height: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing + width: Math.max(excessAuthorsTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height) + + TextMetrics { + id: excessAuthorsTextMetrics + text: excessAuthorsLabel.text + } + } } QQC2.Label { Layout.fillWidth: true @@ -140,6 +167,7 @@ QQC2.Control { visible: showReadMarkers model: readMarkers toolTipText: readMarkersString + excessAvatars: excessReadMarkers } } } diff --git a/src/qml/Component/Timeline/TimelineContainer.qml b/src/qml/Component/Timeline/TimelineContainer.qml index f937458c3..288d2152c 100644 --- a/src/qml/Component/Timeline/TimelineContainer.qml +++ b/src/qml/Component/Timeline/TimelineContainer.qml @@ -342,6 +342,7 @@ ColumnLayout { visible: showReadMarkers model: readMarkers toolTipText: readMarkersString + excessAvatars: excessReadMarkers } function isVisibleInTimeline() {