[fractal] sidebar: Style rooms with unread messages
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] sidebar: Style rooms with unread messages
- Date: Thu, 31 Mar 2022 13:59:48 +0000 (UTC)
commit 0491c75ec7e58e822a735ec7843faba83a6f3a15
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Thu Mar 31 14:38:11 2022 +0200
sidebar: Style rooms with unread messages
Use the read receipt or the last message sent by a user to find the
latest read message.
Cache the compute steps to only re-compute them when they changed
and for future use.
src/session/room/mod.rs | 249 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 238 insertions(+), 11 deletions(-)
---
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index dbafe4305..d17f2f36c 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -24,6 +24,7 @@ use matrix_sdk::{
api::client::sync::sync_events::v3::InvitedRoom,
events::{
reaction::{ReactionEventContent, Relation},
+ receipt::ReceiptEventContent,
room::{
member::MembershipState,
name::RoomNameEventContent,
@@ -35,10 +36,12 @@ use matrix_sdk::{
AnySyncStateEvent, EventContent, MessageLikeEventType, MessageLikeUnsigned,
StateEventType, SyncMessageLikeEvent,
},
+ receipt::ReceiptType,
serde::Raw,
EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId,
},
};
+use ruma::events::SyncEphemeralRoomEvent;
pub use self::{
event::Event,
@@ -86,7 +89,14 @@ mod imp {
pub inviter: RefCell<Option<Member>>,
pub members_loaded: Cell<bool>,
pub power_levels: RefCell<PowerLevels>,
+ /// The timestamp of the latest message in the room.
pub latest_change: Cell<u64>,
+ /// The event of the user's read receipt for this room.
+ pub read_receipt: RefCell<Option<Event>>,
+ /// The latest read event in the room's timeline.
+ pub latest_read: RefCell<Option<Event>>,
+ /// The highlight state of the room,
+ pub highlight: Cell<HighlightFlags>,
pub predecessor: OnceCell<Box<RoomId>>,
pub successor: OnceCell<Box<RoomId>>,
}
@@ -184,6 +194,13 @@ mod imp {
u64::default(),
glib::ParamFlags::READABLE,
),
+ glib::ParamSpecObject::new(
+ "latest-read",
+ "Latest Read",
+ "The latest read event in the room's timeline",
+ Event::static_type(),
+ glib::ParamFlags::READABLE,
+ ),
glib::ParamSpecObject::new(
"members",
"Members",
@@ -257,6 +274,7 @@ mod imp {
"members" => obj.members().to_value(),
"notification-count" => obj.notification_count().to_value(),
"latest-change" => obj.latest_change().to_value(),
+ "latest-read" => obj.latest_read().to_value(),
"predecessor" => obj.predecessor().map_or_else(
|| {
let none: Option<&str> = None;
@@ -366,6 +384,7 @@ impl Room {
self.load_predecessor();
self.load_successor();
self.load_category();
+ self.setup_receipts();
}
/// Forget a room that is left.
@@ -601,6 +620,165 @@ impl Room {
};
}
+ fn setup_receipts(&self) {
+ spawn!(
+ glib::PRIORITY_DEFAULT_IDLE,
+ clone!(@weak self as obj => async move {
+ let user_id = obj.session().user().unwrap().user_id();
+ let matrix_room = obj.matrix_room();
+
+ let handle = spawn_tokio!(async move { matrix_room.user_read_receipt(&user_id).await });
+
+ match handle.await.unwrap() {
+ Ok(Some((event_id, _))) => {
+ obj.update_read_receipt(&event_id).await;
+ },
+ Err(error) => {
+ error!(
+ "Couldn’t get the user's read receipt for room {}: {}",
+ obj.room_id(),
+ error
+ );
+ }
+ _ => {}
+ }
+
+ // Listen to changes in the read receipts.
+ let room_weak = glib::SendWeakRef::from(obj.downgrade());
+ obj.session().client().register_event_handler(
+ move |event: SyncEphemeralRoomEvent<ReceiptEventContent>, matrix_room: MatrixRoom| {
+ let room_weak = room_weak.clone();
+ async move {
+ let ctx = glib::MainContext::default();
+ ctx.spawn(async move {
+ spawn!(async move {
+ if let Some(obj) = room_weak.upgrade() {
+ if matrix_room.room_id() == obj.room_id() {
+ obj.handle_receipt_event(event.content).await
+ }
+ }
+ });
+ });
+ }
+ },
+ )
+ .await;
+ })
+ );
+ }
+
+ async fn handle_receipt_event(&self, content: ReceiptEventContent) {
+ let user_id = self.session().user().unwrap().user_id();
+
+ for (event_id, receipts) in content.iter() {
+ if let Some(users) = receipts.get(&ReceiptType::Read) {
+ for user in users.keys() {
+ if user == user_id.as_ref() {
+ self.update_read_receipt(event_id.as_ref()).await;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /// Update the user's read receipt event for this room with the given event
+ /// ID.
+ async fn update_read_receipt(&self, event_id: &EventId) {
+ if Some(event_id)
+ == self
+ .read_receipt()
+ .map(|event| event.matrix_event_id())
+ .as_deref()
+ {
+ return;
+ }
+
+ match self.timeline().fetch_event_by_id(event_id).await {
+ Ok(read_receipt) => {
+ self.set_read_receipt(Some(read_receipt));
+ }
+ Err(error) => {
+ error!(
+ "Couldn’t get the event of the user's read receipt for room {}: {}",
+ self.room_id(),
+ error
+ );
+ }
+ }
+ }
+
+ /// The user's read receipt event for this room.
+ pub fn read_receipt(&self) -> Option<Event> {
+ self.imp().read_receipt.borrow().clone()
+ }
+
+ /// Set the user's read receipt event for this room.
+ fn set_read_receipt(&self, read_receipt: Option<Event>) {
+ if read_receipt == self.read_receipt() {
+ return;
+ }
+
+ self.imp().read_receipt.replace(read_receipt);
+ self.update_latest_read()
+ }
+
+ fn update_latest_read(&self) {
+ let read_receipt = self.read_receipt();
+ let user_id = self.session().user().unwrap().user_id();
+ let timeline = self.timeline();
+
+ let latest_read = read_receipt.and_then(|read_receipt| {
+ (0..timeline.n_items()).rev().find_map(|i| {
+ timeline
+ .item(i)
+ .as_ref()
+ .and_then(|obj| obj.downcast_ref::<Item>())
+ .and_then(|item| item.event())
+ .and_then(|event| {
+ // The user sent the event so it's the latest read event.
+ // Necessary because we don't get read receipts for the user's own events.
+ if event.sender().user_id() == user_id {
+ return Some(event.to_owned());
+ }
+
+ // This is the event corresponding to the read receipt.
+ if event == &read_receipt {
+ return Some(event.to_owned());
+ }
+
+ // The event is older than the read receipt so it has been read.
+ if event.can_be_latest_change()
+ && event.matrix_origin_server_ts()
+ <= read_receipt.matrix_origin_server_ts()
+ {
+ return Some(event.to_owned());
+ }
+
+ None
+ })
+ })
+ });
+
+ self.set_latest_read(latest_read);
+ }
+
+ /// The latest read event in the room's timeline.
+ pub fn latest_read(&self) -> Option<Event> {
+ self.imp().latest_read.borrow().clone()
+ }
+
+ /// Set the latest read event.
+ fn set_latest_read(&self, latest_read: Option<Event>) {
+ if latest_read == self.latest_read() {
+ return;
+ }
+
+ self.imp().latest_read.replace(latest_read);
+ self.notify("latest-read");
+ self.update_highlight();
+ }
+
pub fn timeline(&self) -> &Timeline {
self.imp().timeline.get().unwrap()
}
@@ -610,26 +788,73 @@ impl Room {
}
fn notify_notification_count(&self) {
- self.notify("highlight");
self.notify("notification-count");
}
- pub fn highlight(&self) -> HighlightFlags {
- let count = self
+ fn update_highlight(&self) {
+ let mut highlight = HighlightFlags::empty();
+
+ let counts = self
.imp()
.matrix_room
.borrow()
.as_ref()
.unwrap()
- .unread_notification_counts()
- .highlight_count;
+ .unread_notification_counts();
- // TODO: how do we know when to set the row to be bold
- if count > 0 {
- HighlightFlags::HIGHLIGHT
- } else {
- HighlightFlags::empty()
+ if counts.highlight_count > 0 {
+ highlight = HighlightFlags::all();
}
+
+ if counts.notification_count > 0 || self.has_unread_messages() {
+ highlight = HighlightFlags::BOLD;
+ }
+
+ self.set_highlight(highlight);
+ }
+
+ pub fn highlight(&self) -> HighlightFlags {
+ self.imp().highlight.get()
+ }
+
+ fn set_highlight(&self, highlight: HighlightFlags) {
+ if self.highlight() == highlight {
+ return;
+ }
+
+ self.imp().highlight.set(highlight);
+ self.notify("highlight");
+ }
+
+ /// Whether this room has unread messages.
+ fn has_unread_messages(&self) -> bool {
+ self.latest_read()
+ .filter(|latest_read| {
+ let timeline = self.timeline();
+
+ for i in (0..timeline.n_items()).rev() {
+ if let Some(event) = timeline
+ .item(i)
+ .as_ref()
+ .and_then(|obj| obj.downcast_ref::<Item>())
+ .and_then(|item| item.event())
+ {
+ // This is the event corresponding to the read receipt so there's no unread
+ // messages.
+ if event == latest_read {
+ return true;
+ }
+
+ // The user hasn't read the latest message.
+ if event.can_be_latest_change() {
+ return false;
+ }
+ }
+ }
+
+ false
+ })
+ .is_none()
}
pub fn display_name(&self) -> String {
@@ -824,7 +1049,6 @@ impl Room {
}
}
- self.notify("latest-change");
self.emit_by_name::<()>("order-changed", &[]);
}
@@ -843,6 +1067,9 @@ impl Room {
self.imp().latest_change.set(latest_change);
self.notify("latest-change");
+ self.update_highlight();
+ // Necessary because we don't get read receipts for the user's own events.
+ self.update_latest_read();
}
pub fn load_members(&self) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]