[network/ruqola] src/core: Update read receipts live when the room is read
Laurent Montel <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit b8152574fc721d37b3ca46907aac7f428224f3b4 by Laurent Montel, on behalf of Till Adam.
Committed on 27/07/2026 at 11:39.
Pushed by mlaurent into branch 'master'.
Update read receipts live when the room is read
The read-receipt checkmark is driven by Message::unread(), which was only
ever set while parsing a message on load or from the local database. When
the other participant read a message there was no live update, so the
single→double-check transition only appeared after a reload.
Rocket.Chat broadcasts a "messagesRead" event on stream-notify-room once
everyone in the room has read up to a given timestamp (the oldest
last-seen across participants), but Ruqola subscribed to it and then
dropped it in the "unknown event" branch. Handle it: parse the "until"
timestamp and clear the unread flag on every message up to that point,
repainting the affected rows so the indicator updates in real time.
Known limitation: the cleared flag is not written back to the local
message-cache database, so a room reopened from cache before the next
server sync can briefly show the old state; it self-heals on sync. Also,
per-thread reads (payload with tmid) are not handled yet.
M +28 -0 src/core/model/messagesmodel.cpp
M +9 -0 src/core/model/messagesmodel.h
M +19 -0 src/core/rocketchatbackend.cpp
https://invent.kde.org/network/ruqola/-/commit/b8152574fc721d37b3ca46907aac7f428224f3b4
diff --git a/src/core/model/messagesmodel.cpp b/src/core/model/messagesmodel.cpp
index 9242c1d4dc..b88ac40a38 100644
--- a/src/core/model/messagesmodel.cpp
+++ b/src/core/model/messagesmodel.cpp
@@ -789,6 +789,34 @@ void MessagesModel::updateTextToSpeech(const QByteArray &messageId, bool inProgr
setData(index, inProgress, MessagesModel::TextToSpeechInProgress);
}
+void MessagesModel::markMessagesReadUntil(qint64 until)
+{
+ if (until <= 0) {
+ return;
+ }
+ // mAllMessages is sorted by ascending timestamp, so the messages read (ts <= until) form a
+ // contiguous prefix. Clear their unread flag and repaint that range in one go.
+ int firstRow = -1;
+ int lastRow = -1;
+ for (int row = 0, total = mAllMessages.count(); row < total; ++row) {
+ Message &message = mAllMessages[row];
+ if (message.timeStamp() > until) {
+ break;
+ }
+ if (message.unread()) {
+ message.setUnread(false);
+ if (firstRow == -1) {
+ firstRow = row;
+ }
+ lastRow = row;
+ }
+ }
+ if (firstRow != -1) {
+ qCDebug(RUQOLA_MESSAGEMODELS_LOG) << "markMessagesReadUntil until=" << until << "cleared unread on rows" << firstRow << "-" << lastRow;
+ Q_EMIT dataChanged(createIndex(firstRow, 0), createIndex(lastRow, 0));
+ }
+}
+
RuqolaQuickSearchMessageSettings *MessagesModel::quickSearchMessageSettings() const
{
return mQuickSearchMessageSettings;
diff --git a/src/core/model/messagesmodel.h b/src/core/model/messagesmodel.h
index ee7ee2d98f..ca39ddf853 100644
--- a/src/core/model/messagesmodel.h
+++ b/src/core/model/messagesmodel.h
@@ -170,6 +170,15 @@ public:
void updateTextToSpeech(const QByteArray &messageId, bool inProgress);
+ /**
+ * @brief Clears the read-receipt "unread" flag on every message sent no later than @p until.
+ *
+ * Called in response to the server's @c messagesRead notification, whose @c until is the
+ * oldest last-seen timestamp across all room participants — i.e. the point up to which
+ * everyone has read. Repaints the affected rows so the read-receipt indicator updates live.
+ */
+ void markMessagesReadUntil(qint64 until);
+
void generateText(const Message &message, const QString &searchText, int hightLightStringIndex);
private:
diff --git a/src/core/rocketchatbackend.cpp b/src/core/rocketchatbackend.cpp
index 0b6d048695..d8a6223477 100644
--- a/src/core/rocketchatbackend.cpp
+++ b/src/core/rocketchatbackend.cpp
@@ -611,6 +611,25 @@ void RocketChatBackend::slotChanged(const QJsonObject &object)
roomId.remove(u"/deleteMessageBulk"_s);
qCDebug(RUQOLA_BACKEND_LOG) << "UNIMPLEMENT!!!!!! deleteMessageBulk " << collection << " object " << object;
// QJsonObject({"collection":"stream-notify-room","fields":{"args":[{"excludePinned":false,"ignoreDiscussion":true,"rid":"QgCf8GcnXYW5QXiHN","ts":{"$gt":{"$date":946681200000},"$lt":{"$date":1599602400000}},"users":[]}],"eventName":"QgCf8GcnXYW5QXiHN/deleteMessageBulk"},"id":"id","msg":"changed"})
+ } else if (eventname.endsWith("/messagesRead"_L1)) {
+ if (mRocketChatAccount->ruqolaLogger()) {
+ QJsonDocument d;
+ d.setObject(object);
+ mRocketChatAccount->ruqolaLogger()->dataReceived("stream-notify-room: messagesRead:"_ba + d.toJson());
+ }
+ // Rocket.Chat emits this (only when Message_Read_Receipt_Enabled) once everyone in the
+ // room has read up to "until" — the oldest last-seen timestamp across participants. Clear
+ // the read-receipt flag on messages up to that point so the indicator updates live.
+ // Args: [{ until: Date, tmid?: string }]; a per-thread read (tmid set) is not handled here.
+ QString roomId = eventname;
+ roomId.remove(u"/messagesRead"_s);
+ const QJsonObject readInfo = contents.at(0).toObject();
+ const qint64 until = Utils::parseDate(u"until"_s, readInfo);
+ if (until > 0 && readInfo.value("tmid"_L1).toString().isEmpty()) {
+ if (MessagesModel *messageModel = mRocketChatAccount->messageModelForRoom(roomId.toLatin1())) {
+ messageModel->markMessagesReadUntil(until);
+ }
+ }
} else {
if (mRocketChatAccount->ruqolaLogger()) {
QJsonDocument d;