[fractal/fractal-next] sidebar: Order rooms by latest message



commit a2a7800c1bac6d71ef2269ce9b16b5d81c95f915
Author: Kévin Commaille <zecakeh tedomum fr>
Date:   Sun Mar 27 16:26:17 2022 +0200

    sidebar: Order rooms by latest message
    
    Only use latest messages or the user join state event.
    Bind to property so the order is updated when it changes.
    
    Fixes #858

 src/session/room/event.rs       | 43 +++++++++++++++++++++++++++++++++++++++--
 src/session/room/mod.rs         | 38 ++++++++++++++++++++----------------
 src/session/room/timeline.rs    | 41 +++++++++++++++++++++++++++++++++------
 src/session/sidebar/category.rs |  9 ++++-----
 4 files changed, 101 insertions(+), 30 deletions(-)
---
diff --git a/src/session/room/event.rs b/src/session/room/event.rs
index 1c1e6c3df..1b8b1ab3e 100644
--- a/src/session/room/event.rs
+++ b/src/session/room/event.rs
@@ -12,7 +12,10 @@ use matrix_sdk::{
     media::MediaEventContent,
     ruma::{
         events::{
-            room::message::{MessageType, Relation},
+            room::{
+                member::MembershipState,
+                message::{MessageType, Relation},
+            },
             AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent,
             Unsigned,
         },
@@ -25,7 +28,7 @@ use matrix_sdk::{
 use crate::{
     session::{
         room::{Member, ReactionList},
-        Room,
+        Room, UserExt,
     },
     spawn_tokio,
     utils::{filename_for_mime, media_type_uid},
@@ -268,6 +271,24 @@ impl Event {
             .and_then(|unsigned| unsigned.transaction_id)
     }
 
+    /// The original timestamp of this event.
+    pub fn matrix_origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
+        let priv_ = self.imp();
+        if let Some(event) = priv_.event.borrow().as_ref() {
+            event.origin_server_ts().to_owned()
+        } else {
+            priv_
+                .pure_event
+                .borrow()
+                .as_ref()
+                .unwrap()
+                .event
+                .get_field::<MilliSecondsSinceUnixEpoch>("origin_server_ts")
+                .unwrap()
+                .unwrap()
+        }
+    }
+
     /// The pretty-formatted JSON of this matrix event.
     pub fn original_source(&self) -> String {
         // We have to convert it to a Value, because a RawValue cannot be
@@ -721,4 +742,22 @@ impl Event {
             .await?;
         Ok(Some(event))
     }
+
+    /// Whether this `Event` can be used as the `latest_change` of a room.
+    ///
+    /// This means that the event is a message, or it is the state event of the
+    /// user joining the room, which should be the oldest possible change.
+    pub fn can_be_latest_change(&self) -> bool {
+        if let Some(event) = self.matrix_event() {
+            matches!(event, AnySyncRoomEvent::Message(_))
+                || matches!(event, AnySyncRoomEvent::State(AnySyncStateEvent::RoomMember(event))
+                    if event.state_key == self.room().session().user().unwrap().user_id().to_string()
+                    && event.content.membership == MembershipState::Join
+                    && event.prev_content.as_ref()
+                            .filter(|content| content.membership == MembershipState::Join).is_none()
+                )
+        } else {
+            false
+        }
+    }
 }
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index 3c8d0bb0d..3bd95d9ab 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -86,7 +86,7 @@ mod imp {
         pub inviter: RefCell<Option<Member>>,
         pub members_loaded: Cell<bool>,
         pub power_levels: RefCell<PowerLevels>,
-        pub latest_change: RefCell<Option<glib::DateTime>>,
+        pub latest_change: Cell<u64>,
         pub predecessor: OnceCell<Box<RoomId>>,
         pub successor: OnceCell<Box<RoomId>>,
     }
@@ -176,11 +176,13 @@ mod imp {
                         None,
                         glib::ParamFlags::READWRITE,
                     ),
-                    glib::ParamSpecBoxed::new(
+                    glib::ParamSpecUInt64::new(
                         "latest-change",
                         "Latest Change",
-                        "Latest origin_server_ts of all loaded invents",
-                        glib::DateTime::static_type(),
+                        "Timestamp of the latest message",
+                        u64::MIN,
+                        u64::MAX,
+                        u64::default(),
                         glib::ParamFlags::READABLE,
                     ),
                     glib::ParamSpecObject::new(
@@ -797,12 +799,9 @@ impl Room {
     /// Update the room state based on the new sync response
     /// FIXME: We should use the sdk's event handler to get updates
     pub fn update_for_events(&self, batch: Vec<SyncRoomEvent>) {
-        let priv_ = self.imp();
-
         // FIXME: notify only when the count has changed
         self.notify_notification_count();
 
-        let mut latest_change = self.latest_change();
         for event in batch.iter().flat_map(|e| e.event.deserialize().ok()) {
             match &event {
                 AnySyncRoomEvent::State(AnySyncStateEvent::RoomMember(event)) => {
@@ -826,22 +825,27 @@ impl Room {
                 }
                 _ => {}
             }
-            let event_is_join_or_leave = matches!(&event, 
AnySyncRoomEvent::State(AnySyncStateEvent::RoomMember(event))
-                if event.content.membership == MembershipState::Join || event.content.membership == 
MembershipState::Leave);
-            if !event_is_join_or_leave {
-                let event_ts = glib::DateTime::from_unix_millis_utc(event.origin_server_ts());
-                latest_change = latest_change.max(event_ts.ok());
-            }
         }
 
-        priv_.latest_change.replace(latest_change);
         self.notify("latest-change");
         self.emit_by_name::<()>("order-changed", &[]);
     }
 
-    /// Returns the point in time this room received its latest event.
-    pub fn latest_change(&self) -> Option<glib::DateTime> {
-        self.imp().latest_change.borrow().clone()
+    /// The timestamp of the room's latest message.
+    ///
+    /// If it is not known, it will return 0.
+    pub fn latest_change(&self) -> u64 {
+        self.imp().latest_change.get()
+    }
+
+    /// Set the timestamp of the room's latest message.
+    pub fn set_latest_change(&self, latest_change: u64) {
+        if latest_change == self.latest_change() {
+            return;
+        }
+
+        self.imp().latest_change.set(latest_change);
+        self.notify("latest-change");
     }
 
     pub fn load_members(&self) {
diff --git a/src/session/room/timeline.rs b/src/session/room/timeline.rs
index 0f4f322c1..afbdee032 100644
--- a/src/session/room/timeline.rs
+++ b/src/session/room/timeline.rs
@@ -576,6 +576,21 @@ impl Timeline {
                     self.set_state(TimelineState::Error);
                     return false;
                 }
+
+                // Update the latest change of the room.
+                let room = self.room();
+                let mut latest_change = room.latest_change();
+                // We receive the events in reverse chronological order so start from the
+                // beginning.
+                for event in events.iter() {
+                    if event.can_be_latest_change() {
+                        latest_change =
+                            latest_change.max(event.matrix_origin_server_ts().get().into());
+                        break;
+                    }
+                }
+                room.set_latest_change(latest_change);
+
                 self.set_state(TimelineState::Ready);
                 self.prepend(events);
                 true
@@ -944,12 +959,26 @@ async fn handle_forward_stream(
         let ctx = glib::MainContext::default();
         ctx.spawn(async move {
             let result = if let Some(timeline) = timeline.upgrade() {
-                timeline.append(
-                    events
-                        .into_iter()
-                        .map(|event| Event::new(event, &timeline.room()))
-                        .collect(),
-                );
+                let events: Vec<_> = events
+                    .into_iter()
+                    .map(|event| Event::new(event, &timeline.room()))
+                    .collect();
+
+                // Update the latest change of the room.
+                let room = timeline.room();
+                let mut latest_change = room.latest_change();
+                // We receive the events in chronological order so start from the end.
+                let mut iter = events.iter();
+                while let Some(event) = iter.next_back() {
+                    if event.can_be_latest_change() {
+                        latest_change =
+                            latest_change.max(event.matrix_origin_server_ts().get().into());
+                        break;
+                    }
+                }
+                room.set_latest_change(latest_change);
+
+                timeline.append(events);
 
                 true
             } else {
diff --git a/src/session/sidebar/category.rs b/src/session/sidebar/category.rs
index 9483c0ca8..ba8d25c49 100644
--- a/src/session/sidebar/category.rs
+++ b/src/session/sidebar/category.rs
@@ -137,11 +137,10 @@ impl Category {
             });
             let filter_model = gtk::FilterListModel::new(Some(&model), Some(&filter));
 
-            let sorter = gtk::CustomSorter::new(|a, b| {
-                let a = a.downcast_ref::<Room>().unwrap();
-                let b = b.downcast_ref::<Room>().unwrap();
-                b.latest_change().cmp(&a.latest_change()).into()
-            });
+            let sorter = gtk::NumericSorter::builder()
+                .expression(Room::this_expression("latest-change"))
+                .sort_order(gtk::SortType::Descending)
+                .build();
             let sort_model = gtk::SortListModel::new(Some(&filter_model), Some(&sorter));
             sort_model.upcast()
         } else {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]