diff --git a/src/models/messageeventmodel.cpp b/src/models/messageeventmodel.cpp index 25edc9c93..d0f6a459b 100644 --- a/src/models/messageeventmodel.cpp +++ b/src/models/messageeventmodel.cpp @@ -54,6 +54,9 @@ QHash MessageEventModel::roleNames() const roles[UserMarkerRole] = "userMarker"; roles[ShowAuthorRole] = "showAuthor"; roles[ShowSectionRole] = "showSection"; + roles[ReadMarkersRole] = "readMarkers"; + roles[ReadMarkersStringRole] = "readMarkersString"; + roles[ShowReadMarkersRole] = "showReadMarkers"; roles[ReactionRole] = "reaction"; roles[IsEditedRole] = "isEdited"; roles[SourceRole] = "source"; @@ -216,6 +219,12 @@ void MessageEventModel::setRoom(NeoChatRoom *room) } refreshEventRoles(eventId, {ReactionRole, Qt::DisplayRole}); }); + 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}); + } + }); connect(m_currentRoom, &Room::newFileTransfer, this, &MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent); @@ -791,6 +800,65 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const return false; } + if (role == ReadMarkersRole) { +#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 + + QVariantList users; + users.reserve(userIds.size()); + for (const auto &userId : userIds) { +#ifdef QUOTIENT_07 + auto user = static_cast(m_currentRoom->user(userId)); +#else + auto user = static_cast(userId); +#endif + users += userAtEvent(user, m_currentRoom, evt); + } + + return users; + } + + if (role == ReadMarkersStringRole) { +#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 + /** + * The string ends up in the form + * "x users: user1DisplayName, user2DisplayName, etc." + */ + QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size()); + for (const auto &userId : userIds) { +#ifdef QUOTIENT_07 + auto user = static_cast(m_currentRoom->user(userId)); +#else + auto user = static_cast(userId); +#endif + readMarkersString += user->displayname(m_currentRoom) + i18nc("list separator", ", "); + } + readMarkersString.chop(2); + return readMarkersString; + } + + if (role == ShowReadMarkersRole) { +#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 + return userIds.size() > 0; + } + if (role == ReactionRole) { const auto &annotations = m_currentRoom->relatedEvents(evt, EventRelation::Annotation()); if (annotations.isEmpty()) { diff --git a/src/models/messageeventmodel.h b/src/models/messageeventmodel.h index 71614dcf7..d00b9f871 100644 --- a/src/models/messageeventmodel.h +++ b/src/models/messageeventmodel.h @@ -57,6 +57,9 @@ public: ShowAuthorRole, ShowSectionRole, + ReadMarkersRole, /**< QVariantList of users at the event for read marker tracking. */ + ReadMarkersStringRole, /**< QString with the display name and mxID of the users at the event. */ + ShowReadMarkersRole, /**< bool with whether there are any other user read markers to be shown. */ ReactionRole, IsEditedRole, diff --git a/src/qml/Component/Timeline/AvatarFlow.qml b/src/qml/Component/Timeline/AvatarFlow.qml new file mode 100644 index 000000000..c64c0dbaf --- /dev/null +++ b/src/qml/Component/Timeline/AvatarFlow.qml @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2022 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 + +import org.kde.kirigami 2.15 as Kirigami + +Flow { + id: root + + property var avatarSize: Kirigami.Units.iconSizes.small + property alias model: avatarFlowRepeater.model + property string toolTipText + + spacing: -avatarSize / 2 + Repeater { + id: avatarFlowRepeater + delegate: Kirigami.Avatar { + implicitWidth: avatarSize + implicitHeight: avatarSize + + name: modelData.displayName + source: modelData.avatarMediaId ? ("image://mxc/" + modelData.avatarMediaId) : "" + color: modelData.color + } + } + + QQC2.ToolTip.text: toolTipText + QQC2.ToolTip.visible: hoverHandler.hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + + HoverHandler { + id: hoverHandler + margin: Kirigami.Units.smallSpacing + } +} diff --git a/src/qml/Component/Timeline/StateDelegate.qml b/src/qml/Component/Timeline/StateDelegate.qml index 71a1767e8..9a2056e7f 100644 --- a/src/qml/Component/Timeline/StateDelegate.qml +++ b/src/qml/Component/Timeline/StateDelegate.qml @@ -134,5 +134,12 @@ QQC2.Control { folded = !folded foldedChanged() } + AvatarFlow { + Layout.alignment: Qt.AlignRight + Layout.rightMargin: Kirigami.Units.largeSpacing + visible: showReadMarkers + model: readMarkers + toolTipText: readMarkersString + } } } diff --git a/src/qml/Component/Timeline/TimelineContainer.qml b/src/qml/Component/Timeline/TimelineContainer.qml index e2707d811..37c3820bf 100644 --- a/src/qml/Component/Timeline/TimelineContainer.qml +++ b/src/qml/Component/Timeline/TimelineContainer.qml @@ -338,6 +338,13 @@ ColumnLayout { visible: eventType !== MessageEventModel.State && eventType !== MessageEventModel.Notice && reaction != undefined && reaction.length > 0 } + AvatarFlow { + Layout.alignment: Qt.AlignRight + Layout.rightMargin: Kirigami.Units.largeSpacing + visible: showReadMarkers + model: readMarkers + toolTipText: readMarkersString + } function isVisibleInTimeline() { let yoff = Math.round(y - ListView.view.contentY); diff --git a/src/res.qrc b/src/res.qrc index 77a604af3..fc26d0bd9 100644 --- a/src/res.qrc +++ b/src/res.qrc @@ -49,6 +49,7 @@ qml/Component/Timeline/MimeComponent.qml qml/Component/Timeline/StateComponent.qml qml/Component/Timeline/MessageEditComponent.qml + qml/Component/Timeline/AvatarFlow.qml qml/Component/Login/LoginStep.qml qml/Component/Login/Login.qml qml/Component/Login/Password.qml