[fractal/fractal-next] session: Use a single room list with GtkFilterListModels



commit f0f0c4cbb9c43a26b0f3b32810a56f885dc6e46f
Author: Kévin Commaille <46488-zecakeh1 users noreply gitlab gnome org>
Date:   Wed May 19 06:24:29 2021 +0000

    session: Use a single room list with GtkFilterListModels

 Cargo.lock                           |   1 +
 Cargo.toml                           |   1 +
 src/session/categories/categories.rs |  74 ++++----------------
 src/session/categories/category.rs   |  96 ++++++++++++-------------
 src/session/categories/mod.rs        |   2 +
 src/session/categories/room_list.rs  | 132 +++++++++++++++++++++++++++++++++++
 src/session/mod.rs                   |  25 +++----
 7 files changed, 201 insertions(+), 130 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index 5ed72023..45323381 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -678,6 +678,7 @@ dependencies = [
  "gtk-macros",
  "gtk4",
  "html2pango",
+ "indexmap",
  "libadwaita",
  "log",
  "matrix-sdk",
diff --git a/Cargo.toml b/Cargo.toml
index 8bbb11b0..614dd516 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,6 +21,7 @@ html2pango = "0.4"
 futures = "0.3"
 comrak = "0.10"
 rand = "0.8"
+indexmap = "1.6.2"
 
 [dependencies.sourceview]
 branch = "main"
diff --git a/src/session/categories/categories.rs b/src/session/categories/categories.rs
index 687d67e1..6ef7480f 100644
--- a/src/session/categories/categories.rs
+++ b/src/session/categories/categories.rs
@@ -1,19 +1,14 @@
-use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
-use std::collections::HashMap;
+use gtk::{gio, glib, prelude::*, subclass::prelude::*};
 
-use crate::session::{
-    categories::{Category, CategoryType},
-    room::Room,
-};
+use crate::session::categories::{Category, CategoryType, RoomList};
 
 mod imp {
     use super::*;
-    use std::cell::RefCell;
 
     #[derive(Debug)]
     pub struct Categories {
         pub list: [Category; 5],
-        pub room_map: RefCell<HashMap<Room, CategoryType>>,
+        pub room_list: RoomList,
     }
 
     #[glib::object_subclass]
@@ -24,15 +19,17 @@ mod imp {
         type Interfaces = (gio::ListModel,);
 
         fn new() -> Self {
+            let room_list = RoomList::new();
+
             Self {
                 list: [
-                    Category::new(CategoryType::Invited),
-                    Category::new(CategoryType::Favorite),
-                    Category::new(CategoryType::Normal),
-                    Category::new(CategoryType::LowPriority),
-                    Category::new(CategoryType::Left),
+                    Category::new(CategoryType::Invited, &room_list),
+                    Category::new(CategoryType::Favorite, &room_list),
+                    Category::new(CategoryType::Normal, &room_list),
+                    Category::new(CategoryType::LowPriority, &room_list),
+                    Category::new(CategoryType::Left, &room_list),
                 ],
-                room_map: Default::default(),
+                room_list,
             }
         }
     }
@@ -71,53 +68,8 @@ impl Categories {
         glib::Object::new(&[]).expect("Failed to create Categories")
     }
 
-    pub fn append(&self, rooms: Vec<Room>) {
+    pub fn room_list(&self) -> &RoomList {
         let priv_ = imp::Categories::from_instance(&self);
-
-        let rooms: Vec<Room> = {
-            let room_map = priv_.room_map.borrow();
-            rooms
-                .into_iter()
-                .filter(|room| !room_map.contains_key(&room))
-                .collect()
-        };
-
-        let rooms_by_category = rooms.into_iter().fold(HashMap::new(), |mut acc, room| {
-            acc.entry(room.category()).or_insert(vec![]).push(room);
-            acc
-        });
-        let mut room_map = priv_.room_map.borrow_mut();
-        for (category_type, rooms) in rooms_by_category {
-            for room in &rooms {
-                room_map.insert(room.clone(), category_type);
-                room.connect_notify_local(
-                    Some("category"),
-                    clone!(@weak self as obj => move |room, _| {
-                        obj.move_room(room);
-                    }),
-                );
-            }
-
-            self.find_category_by_type(category_type)
-                .append_batch(rooms);
-        }
-    }
-
-    fn find_category_by_type(&self, type_: CategoryType) -> &Category {
-        let priv_ = imp::Categories::from_instance(&self);
-        let position = priv_.list.iter().position(|item| item.type_() == type_);
-        priv_.list.get(position.unwrap()).unwrap()
-    }
-
-    fn move_room(&self, room: &Room) {
-        let priv_ = imp::Categories::from_instance(&self);
-        let mut room_map = priv_.room_map.borrow_mut();
-
-        if let Some(old_category_type) = room_map.remove(&room) {
-            self.find_category_by_type(old_category_type).remove(room);
-        }
-
-        room_map.insert(room.clone(), room.category());
-        self.find_category_by_type(room.category()).append(room);
+        &priv_.room_list
     }
 }
diff --git a/src/session/categories/category.rs b/src/session/categories/category.rs
index 08823535..369d4c10 100644
--- a/src/session/categories/category.rs
+++ b/src/session/categories/category.rs
@@ -1,14 +1,19 @@
-use gtk::{gio, glib, prelude::*, subclass::prelude::*};
+use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
 
-use crate::session::{categories::CategoryType, room::Room};
+use crate::session::{
+    categories::{CategoryType, RoomList},
+    room::Room,
+};
 
 mod imp {
+    use once_cell::unsync::OnceCell;
+    use std::cell::Cell;
+
     use super::*;
-    use std::cell::{Cell, RefCell};
 
     #[derive(Debug, Default)]
     pub struct Category {
-        pub list: RefCell<Vec<Room>>,
+        pub model: OnceCell<gtk::FilterListModel>,
         pub type_: Cell<CategoryType>,
     }
 
@@ -40,6 +45,13 @@ mod imp {
                         None,
                         glib::ParamFlags::READABLE,
                     ),
+                    glib::ParamSpec::new_object(
+                        "model",
+                        "Model",
+                        "The filter list model in that category",
+                        gio::ListModel::static_type(),
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                    ),
                 ]
             });
 
@@ -48,7 +60,7 @@ mod imp {
 
         fn set_property(
             &self,
-            _obj: &Self::Type,
+            obj: &Self::Type,
             _id: usize,
             value: &glib::Value,
             pspec: &glib::ParamSpec,
@@ -58,6 +70,10 @@ mod imp {
                     let type_ = value.get().unwrap();
                     self.type_.set(type_);
                 }
+                "model" => {
+                    let model = value.get().unwrap();
+                    obj.set_model(model);
+                }
                 _ => unimplemented!(),
             }
         }
@@ -66,6 +82,7 @@ mod imp {
             match pspec.name() {
                 "type" => obj.type_().to_value(),
                 "display-name" => obj.type_().to_string().to_value(),
+                "model" => self.model.get().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -76,13 +93,10 @@ mod imp {
             Room::static_type()
         }
         fn n_items(&self, _list_model: &Self::Type) -> u32 {
-            self.list.borrow().len() as u32
+            self.model.get().map(|l| l.n_items()).unwrap_or(0)
         }
         fn item(&self, _list_model: &Self::Type, position: u32) -> Option<glib::Object> {
-            self.list
-                .borrow()
-                .get(position as usize)
-                .map(|o| o.clone().upcast::<glib::Object>())
+            self.model.get().and_then(|l| l.item(position))
         }
     }
 }
@@ -93,8 +107,8 @@ glib::wrapper! {
 }
 
 impl Category {
-    pub fn new(type_: CategoryType) -> Self {
-        glib::Object::new(&[("type", &type_)]).expect("Failed to create Category")
+    pub fn new(type_: CategoryType, model: &RoomList) -> Self {
+        glib::Object::new(&[("type", &type_), ("model", model)]).expect("Failed to create Category")
     }
 
     pub fn type_(&self) -> CategoryType {
@@ -102,47 +116,23 @@ impl Category {
         priv_.type_.get()
     }
 
-    pub fn append(&self, room: &Room) {
-        let priv_ = imp::Category::from_instance(self);
-        let index = {
-            let mut list = priv_.list.borrow_mut();
-            let index = list.len();
-            list.push(room.clone());
-            index
-        };
-        self.items_changed(index as u32, 0, 1);
-    }
-
-    pub fn append_batch(&self, rooms: Vec<Room>) {
-        let priv_ = imp::Category::from_instance(self);
-        let added = rooms.len();
-        let index = {
-            let mut list = priv_.list.borrow_mut();
-            let index = list.len();
-            list.reserve(added);
-            for room in rooms {
-                list.push(room);
-            }
-            index
-        };
-        self.items_changed(index as u32, 0, added as u32);
-    }
-
-    pub fn remove(&self, room: &Room) {
+    fn set_model(&self, model: gio::ListModel) {
         let priv_ = imp::Category::from_instance(self);
-
-        let index = {
-            let mut list = priv_.list.borrow_mut();
-
-            let index = list.iter().position(|item| item == room);
-            if let Some(index) = index {
-                list.remove(index);
-            }
-            index
-        };
-
-        if let Some(index) = index {
-            self.items_changed(index as u32, 1, 0);
-        }
+        let type_ = self.type_();
+
+        let filter = gtk::CustomFilter::new(move |o| {
+            o.downcast_ref::<Room>()
+                .filter(|r| r.category() == type_)
+                .is_some()
+        });
+        let filter_model = gtk::FilterListModel::new(Some(&model), Some(&filter));
+
+        filter_model.connect_items_changed(
+            clone!(@weak self as obj => move |_, pos, added, removed| {
+                obj.items_changed(pos, added, removed);
+            }),
+        );
+
+        let _ = priv_.model.set(filter_model);
     }
 }
diff --git a/src/session/categories/mod.rs b/src/session/categories/mod.rs
index 9b644b47..f58f4f0a 100644
--- a/src/session/categories/mod.rs
+++ b/src/session/categories/mod.rs
@@ -1,7 +1,9 @@
 mod categories;
 mod category;
 mod category_type;
+mod room_list;
 
 pub use self::categories::Categories;
 pub use self::category::Category;
 pub use self::category_type::CategoryType;
+pub use self::room_list::RoomList;
diff --git a/src/session/categories/room_list.rs b/src/session/categories/room_list.rs
new file mode 100644
index 00000000..7b5056b5
--- /dev/null
+++ b/src/session/categories/room_list.rs
@@ -0,0 +1,132 @@
+use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
+use indexmap::map::IndexMap;
+use matrix_sdk::identifiers::RoomId;
+
+use crate::session::room::Room;
+
+mod imp {
+    use super::*;
+    use std::cell::RefCell;
+
+    #[derive(Debug)]
+    pub struct RoomList {
+        pub list: RefCell<IndexMap<RoomId, Room>>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for RoomList {
+        const NAME: &'static str = "RoomList";
+        type Type = super::RoomList;
+        type ParentType = glib::Object;
+        type Interfaces = (gio::ListModel,);
+
+        fn new() -> Self {
+            Self {
+                list: Default::default(),
+            }
+        }
+    }
+
+    impl ObjectImpl for RoomList {}
+
+    impl ListModelImpl for RoomList {
+        fn item_type(&self, _list_model: &Self::Type) -> glib::Type {
+            Room::static_type()
+        }
+        fn n_items(&self, _list_model: &Self::Type) -> u32 {
+            self.list.borrow().len() as u32
+        }
+        fn item(&self, _list_model: &Self::Type, position: u32) -> Option<glib::Object> {
+            self.list
+                .borrow()
+                .values()
+                .nth(position as usize)
+                .map(glib::object::Cast::upcast_ref::<glib::Object>)
+                .cloned()
+        }
+    }
+}
+
+glib::wrapper! {
+    pub struct RoomList(ObjectSubclass<imp::RoomList>)
+        @implements gio::ListModel;
+}
+
+impl Default for RoomList {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl RoomList {
+    pub fn new() -> Self {
+        glib::Object::new(&[]).expect("Failed to create RoomList")
+    }
+
+    pub fn get(&self, room_id: &RoomId) -> Option<Room> {
+        let priv_ = imp::RoomList::from_instance(&self);
+        priv_.list.borrow().get(room_id).cloned()
+    }
+
+    fn get_full(&self, room_id: &RoomId) -> Option<(usize, RoomId, Room)> {
+        let priv_ = imp::RoomList::from_instance(&self);
+        priv_
+            .list
+            .borrow()
+            .get_full(room_id)
+            .map(|(pos, room_id, room)| (pos, room_id.clone(), room.clone()))
+    }
+
+    pub fn contains_key(&self, room_id: &RoomId) -> bool {
+        let priv_ = imp::RoomList::from_instance(&self);
+        priv_.list.borrow().contains_key(room_id)
+    }
+
+    pub fn insert(&self, rooms: Vec<(RoomId, Room)>) {
+        let priv_ = imp::RoomList::from_instance(&self);
+
+        let rooms: Vec<(RoomId, Room)> = {
+            rooms
+                .into_iter()
+                .filter(|(room_id, _)| !priv_.list.borrow().contains_key(room_id))
+                .collect()
+        };
+
+        let added = rooms.len();
+
+        if added > 0 {
+            let position = priv_.list.borrow().len();
+
+            {
+                let mut list = priv_.list.borrow_mut();
+                for (room_id, room) in rooms {
+                    room.connect_notify_local(
+                        Some("category"),
+                        clone!(@weak self as obj => move |r, _| {
+                            if let Some((position, _, _)) = obj.get_full(r.matrix_room().room_id()) {
+                                obj.items_changed(position as u32, 1, 1);
+                            }
+                        }),
+                    );
+                    list.insert(room_id, room);
+                }
+            }
+
+            self.items_changed(position as u32, 0, added as u32);
+        }
+    }
+
+    pub fn remove(&self, room_id: &RoomId) {
+        let priv_ = imp::RoomList::from_instance(&self);
+
+        let removed = {
+            let mut list = priv_.list.borrow_mut();
+
+            list.shift_remove_full(room_id)
+        };
+
+        if let Some((position, _, _)) = removed {
+            self.items_changed(position as u32, 1, 0);
+        }
+    }
+}
diff --git a/src/session/mod.rs b/src/session/mod.rs
index d150dfd4..65aa6586 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -4,6 +4,7 @@ mod room;
 mod sidebar;
 mod user;
 
+use self::categories::Categories;
 use self::content::Content;
 use self::room::Room;
 use self::sidebar::Sidebar;
@@ -15,7 +16,6 @@ use crate::secret::StoredSession;
 use crate::utils::do_async;
 use crate::RUNTIME;
 
-use crate::session::categories::Categories;
 use adw;
 use adw::subclass::prelude::BinImpl;
 use gtk::subclass::prelude::*;
@@ -42,7 +42,6 @@ mod imp {
     use glib::subclass::{InitializingObject, Signal};
     use once_cell::sync::{Lazy, OnceCell};
     use std::cell::RefCell;
-    use std::collections::HashMap;
 
     #[derive(Debug, Default, CompositeTemplate)]
     #[template(resource = "/org/gnome/FractalNext/session.ui")]
@@ -54,7 +53,6 @@ mod imp {
         /// Contains the error if something went wrong
         pub error: RefCell<Option<matrix_sdk::Error>>,
         pub client: OnceCell<Client>,
-        pub rooms: RefCell<HashMap<RoomId, Room>>,
         pub categories: Categories,
         pub user: OnceCell<User>,
         pub selected_room: RefCell<Option<Room>>,
@@ -358,16 +356,15 @@ impl Session {
         let rooms = priv_.client.get().unwrap().rooms();
 
         let mut new_rooms = Vec::with_capacity(rooms.len());
-        let mut rooms_map = priv_.rooms.borrow_mut();
 
         for matrix_room in rooms {
-            let room_id = matrix_room.room_id().clone();
-            let room = Room::new(matrix_room, self.user());
-            rooms_map.insert(room_id, room.clone());
-            new_rooms.push(room.clone());
+            new_rooms.push((
+                matrix_room.room_id().clone(),
+                Room::new(matrix_room, self.user()),
+            ));
         }
 
-        priv_.categories.append(new_rooms);
+        priv_.categories.room_list().insert(new_rooms);
     }
 
     /// Returns and consumes the `error` that was generated when the session failed to login,
@@ -391,10 +388,9 @@ impl Session {
 
     fn handle_sync_response(&self, response: SyncResponse) {
         let priv_ = imp::Session::from_instance(self);
+        let rooms_map = priv_.categories.room_list();
 
         let new_rooms_id: Vec<RoomId> = {
-            let rooms_map = priv_.rooms.borrow();
-
             let new_left_rooms = response.rooms.leave.iter().filter_map(|(room_id, _)| {
                 if !rooms_map.contains_key(room_id) {
                     Some(room_id)
@@ -414,17 +410,14 @@ impl Session {
         };
 
         let mut new_rooms = Vec::new();
-        let mut rooms_map = priv_.rooms.borrow_mut();
 
         for room_id in new_rooms_id {
             if let Some(matrix_room) = priv_.client.get().unwrap().get_room(&room_id) {
-                let room = Room::new(matrix_room, self.user());
-                rooms_map.insert(room_id.clone(), room.clone());
-                new_rooms.push(room.clone());
+                new_rooms.push((room_id, Room::new(matrix_room, self.user())));
             }
         }
 
-        priv_.categories.append(new_rooms);
+        rooms_map.insert(new_rooms);
 
         for (room_id, matrix_room) in response.rooms.leave {
             if matrix_room.timeline.events.is_empty() {


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