Page MenuHomePhorge

D368.1792309673.diff
No OneTemporary

Size
7 KB
Referenced Files
None
Subscribers
None

D368.1792309673.diff

diff --git a/src/client/room/room-model.hpp b/src/client/room/room-model.hpp
--- a/src/client/room/room-model.hpp
+++ b/src/client/room/room-model.hpp
@@ -404,6 +404,17 @@
*/
bool isReadBy(const std::string &eventId, const std::string &userId) const;
+ /**
+ * Check if the room has events unread by the current user.
+ *
+ * An event is considered read if its order in timeline is before
+ * the local read marker or the read receipt of the current user.
+ *
+ * @param selfUserId The user id of the current user.
+ * @return true iff the room has events unread by the current user.
+ */
+ bool hasSelfUnreadEvents(const std::string &selfUserId) const;
+
using Action = std::variant<
AddStateEventsAction,
MaybeAddStateEventsAction,
diff --git a/src/client/room/room-model.cpp b/src/client/room/room-model.cpp
--- a/src/client/room/room-model.cpp
+++ b/src/client/room/room-model.cpp
@@ -815,4 +815,33 @@
auto event = messages[eventId];
return sortKeyForTimelineEvent(readReceiptAt) >= sortKeyForTimelineEvent(event);
}
+
+ bool RoomModel::hasSelfUnreadEvents(const std::string &selfUserId) const
+ {
+ auto k = [this](const std::string &eventId) {
+ return sortKeyForTimelineEvent(messages[eventId]);
+ };
+
+ auto readReceiptForCurrentUser = getMaxInTimeline(
+ readReceipts[selfUserId].eventId,
+ localReadMarker,
+ k
+ );
+
+ // The timeline is sorted from oldest to latest, and self-sent events
+ // are sparse. Scan from the end to find the latest non-self-sent event;
+ // the room has unread events iff that event is not yet read.
+ for (auto it = timeline.rbegin(); it != timeline.rend(); ++it) {
+ const auto &eventId = *it;
+ if (messages[eventId].sender() == selfUserId) {
+ continue;
+ }
+ if (!readReceiptForCurrentUser.empty()
+ && k(readReceiptForCurrentUser) >= k(eventId)) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
}
diff --git a/src/client/room/room.hpp b/src/client/room/room.hpp
--- a/src/client/room/room.hpp
+++ b/src/client/room/room.hpp
@@ -876,6 +876,19 @@
*/
bool isReadBy(const std::string &eventId, const std::string &userId) const;
+ /**
+ * Get whether the room has events unread by the current user.
+ *
+ * An event is considered read if its order in timeline is before
+ * the local read marker or the read receipt of the current user.
+ *
+ * The returned cursor takes O(1) time to be updated.
+ *
+ * @return A lager::reader of a boolean, which is true iff the room
+ * has events unread by the current user.
+ */
+ auto hasSelfUnreadEvents() const -> lager::reader<bool>;
+
private:
const lager::reader<SdkModel> &sdkCursor() const;
const lager::reader<RoomModel> &roomCursor() const;
diff --git a/src/client/room/room.cpp b/src/client/room/room.cpp
--- a/src/client/room/room.cpp
+++ b/src/client/room/room.cpp
@@ -983,4 +983,14 @@
{
return m_roomCursor.get().isReadBy(eventId, userId);
}
+
+ auto Room::hasSelfUnreadEvents() const -> lager::reader<bool>
+ {
+ return lager::with(
+ sdkCursor().map(&SdkModel::c)[&ClientModel::userId],
+ roomCursor()
+ ).map([](const std::string &selfUserId, const RoomModel &r) {
+ return r.hasSelfUnreadEvents(selfUserId);
+ });
+ }
}
diff --git a/src/tests/client/room/read-receipt-test.cpp b/src/tests/client/room/read-receipt-test.cpp
--- a/src/tests/client/room/read-receipt-test.cpp
+++ b/src/tests/client/room/read-receipt-test.cpp
@@ -436,3 +436,82 @@
REQUIRE(room.isReadBy("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Z2", "@rikj:jki.re") == true);
REQUIRE(room.isReadBy("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Z1", "@rikj:jki.re") == false);
}
+
+TEST_CASE("Room::hasSelfUnreadEvents()", "[client][room][receipt]")
+{
+ auto selfUserId = "@self:example.com"s;
+
+ auto e0 = makeEvent(withEventTs(100) | withEventId("$e0"));
+ auto e1 = makeEvent(withEventTs(200) | withEventId("$e1"));
+ auto e2 = makeEvent(withEventTs(300) | withEventId("$e2"));
+ auto selfE = makeEvent(withEventTs(400) | withEventId("$e4") | withEventSenderId(selfUserId));
+
+ auto checkHasSelfUnread = [selfUserId](RoomModel room, bool expected) {
+ boost::asio::io_context io;
+ AsioPromiseHandler ph{io.get_executor()};
+
+ auto model = makeClient(withRoom(room));
+ model.userId = selfUserId;
+ auto store = createTestClientStoreFrom(model, ph);
+ auto client = Client(store.reader().map([](auto c) { return SdkModel{c}; }),
+ store,
+ std::nullopt);
+ auto r = client.room(room.roomId);
+ REQUIRE(r.hasSelfUnreadEvents().get() == expected);
+ };
+
+ SECTION("no events") {
+ checkHasSelfUnread(makeRoom(), false);
+ }
+
+ SECTION("no read receipt or local read marker") {
+ checkHasSelfUnread(makeRoom(withRoomTimeline(EventList{e0, e1, e2})), true);
+ }
+
+ SECTION("read receipt at last event") {
+ auto room = makeRoom(withRoomTimeline(EventList{e0, e1, e2}));
+ room.readReceipts = {{selfUserId, ReadReceipt{e2.id(), 0}}};
+ checkHasSelfUnread(room, false);
+ }
+
+ SECTION("read receipt at middle event") {
+ auto room = makeRoom(withRoomTimeline(EventList{e0, e1, e2}));
+ room.readReceipts = {{selfUserId, ReadReceipt{e1.id(), 0}}};
+ checkHasSelfUnread(room, true);
+ }
+
+ SECTION("local read marker at last event") {
+ auto room = makeRoom(withRoomTimeline(EventList{e0, e1, e2}));
+ room.localReadMarker = e2.id();
+ checkHasSelfUnread(room, false);
+ }
+
+ SECTION("local read marker at middle event") {
+ auto room = makeRoom(withRoomTimeline(EventList{e0, e1, e2}));
+ room.localReadMarker = e1.id();
+ checkHasSelfUnread(room, true);
+ }
+
+ SECTION("read receipt and local read marker, the later one wins") {
+ auto room = makeRoom(withRoomTimeline(EventList{e0, e1, e2}));
+ room.readReceipts = {{selfUserId, ReadReceipt{e0.id(), 0}}};
+ room.localReadMarker = e2.id();
+ checkHasSelfUnread(room, false);
+ }
+
+ SECTION("only self-sent events") {
+ checkHasSelfUnread(makeRoom(withRoomTimeline(EventList{selfE})), false);
+ }
+
+ SECTION("self-sent event after read position") {
+ auto room = makeRoom(withRoomTimeline(EventList{e0, selfE}));
+ room.localReadMarker = e0.id();
+ checkHasSelfUnread(room, false);
+ }
+
+ SECTION("non-self event after read position") {
+ auto room = makeRoom(withRoomTimeline(EventList{e0, e2, selfE}));
+ room.readReceipts = {{selfUserId, ReadReceipt{e0.id(), 0}}};
+ checkHasSelfUnread(room, true);
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Sun, Oct 18, 12:47 AM (23 h, 10 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1789005
Default Alt Text
D368.1792309673.diff (7 KB)

Event Timeline