Add dialog to see who has read this message

You were previously relegated to looking at any avatars or a buggy
tooltip, but suffer no longer! If you tap on a message's read marker, a
dialog will pop up listing the users who have read it.

Uou can also view their profiles from here, etc.
This commit is contained in:
Joshua Goins
2026-01-09 18:25:11 -05:00
parent 71c84be4b4
commit e8da02be7d
6 changed files with 118 additions and 27 deletions

View File

@@ -106,6 +106,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/NewPollDialog.qml
qml/UserMenu.qml
qml/MeetingDialog.qml
qml/SeenByDialog.qml
DEPENDENCIES
QtCore
QtQuick

View File

@@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: 2026 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat
Kirigami.Dialog {
id: root
property var model
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
maximumHeight: Kirigami.Units.gridUnit * 24
title: i18nc("@title:menu Seen by/read marker dialog", "Seen By")
contentItem: ColumnLayout {
spacing: 0
QQC2.ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
ListView {
id: listView
model: root.model
spacing: Kirigami.Units.smallSpacing
onCountChanged: {
if (listView.count === 0) {
root.close();
}
}
delegate: Delegates.RoundedItemDelegate {
id: userDelegate
required property string displayName
required property url avatarUrl
required property color memberColor
required property string userId
implicitHeight: Kirigami.Units.gridUnit * 2
text: displayName
highlighted: false
onClicked: {
root.close();
RoomManager.resolveResource(userDelegate.userId);
}
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
KirigamiComponents.Avatar {
implicitWidth: height
sourceSize {
height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
}
source: userDelegate.avatarUrl
name: userDelegate.displayName
color: userDelegate.memberColor
Layout.fillHeight: true
}
QQC2.Label {
text: userDelegate.displayName
textFormat: Text.PlainText
elide: Text.ElideRight
clip: true // Intentional to limit insane Unicode in display names
Layout.fillWidth: true
}
}
}
}
}
}
}

View File

@@ -8,16 +8,24 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
RowLayout {
id: root
property var avatarSize: Kirigami.Units.iconSizes.small
property alias model: avatarFlowRepeater.model
property alias model: root.limiterModel.sourceModel
property string toolTipText
property LimiterModel limiterModel: LimiterModel {
maximumCount: 5
}
spacing: -avatarSize / 2
Repeater {
id: avatarFlowRepeater
model: root.limiterModel
delegate: KirigamiComponents.Avatar {
required property string displayName
required property url avatarUrl
@@ -39,11 +47,11 @@ RowLayout {
Layout.preferredHeight: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing
Layout.fillHeight: true
visible: text !== ""
visible: root.limiterModel.extraCount > 0
color: Kirigami.Theme.textColor
horizontalAlignment: Text.AlignHCenter
text: root.model?.excessReadMarkersString ?? ""
text: "+ " + root.limiterModel.extraCount
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor

View File

@@ -208,6 +208,15 @@ MessageDelegateBase {
readMarkerComponent: AvatarFlow {
model: root.readMarkers
TapHandler {
onTapped: {
const dialog = Qt.createComponent("org.kde.neochat", "SeenByDialog").createObject(root, {
model: root.readMarkers
}) as SeenByDialog;
dialog.open();
}
}
}
compactBackgroundComponent: Rectangle {

View File

@@ -7,8 +7,6 @@
#include <Quotient/roommember.h>
#define MAXMARKERS 5
using namespace Qt::StringLiterals;
ReadMarkerModel::ReadMarkerModel(const QString &eventId, NeoChatRoom *room)
@@ -85,13 +83,17 @@ QVariant ReadMarkerModel::data(const QModelIndex &index, int role) const
return member.color();
}
if (role == UserIdRole) {
return member.id();
}
return {};
}
int ReadMarkerModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return std::min(int(m_markerIds.size()), MAXMARKERS);
return m_markerIds.size();
}
QHash<int, QByteArray> ReadMarkerModel::roleNames() const
@@ -100,6 +102,7 @@ QHash<int, QByteArray> ReadMarkerModel::roleNames() const
{DisplayNameRole, "displayName"},
{AvatarUrlRole, "avatarUrl"},
{ColorRole, "memberColor"},
{UserIdRole, "userId"},
};
}
@@ -122,17 +125,4 @@ QString ReadMarkerModel::readMarkersString()
return readMarkersString;
}
QString ReadMarkerModel::excessReadMarkersString()
{
if (m_room == nullptr) {
return {};
}
if (m_markerIds.size() > MAXMARKERS) {
return u"+ "_s + QString::number(m_markerIds.size() - MAXMARKERS);
} else {
return QString();
}
}
#include "moc_readmarkermodel.cpp"

View File

@@ -26,13 +26,6 @@ class ReadMarkerModel : public QAbstractListModel
*/
Q_PROPERTY(QString readMarkersString READ readMarkersString NOTIFY reactionUpdated)
/**
* @brief Returns the number of excess user read markers for the event.
*
* This returns a string in the form "+ x" ready for use in the UI.
*/
Q_PROPERTY(QString excessReadMarkersString READ excessReadMarkersString NOTIFY reactionUpdated)
public:
/**
* @brief Defines the model roles.
@@ -41,12 +34,12 @@ public:
DisplayNameRole = Qt::DisplayRole, /**< The display name of the member in the room. */
AvatarUrlRole, /**< The avatar for the member in the room. */
ColorRole, /**< The color for the member. */
UserIdRole, /** The user ID for the member. */
};
explicit ReadMarkerModel(const QString &eventId, NeoChatRoom *room);
QString readMarkersString();
QString excessReadMarkersString();
/**
* @brief Get the given role value at the given index.