[fractal/fractal-next] members-page: Only show active members



commit 34f71ce741340cb83aa1240ef7dc35b2c162b42c
Author: Kévin Commaille <zecakeh tedomum fr>
Date:   Tue Feb 1 09:17:26 2022 +0100

    members-page: Only show active members
    
    Separate joined and invited in two lists

 data/resources/ui/content-member-page.ui           |  27 ++++-
 .../content/room_details/member_page/mod.rs        | 110 ++++++++++++++++++---
 src/session/room/member.rs                         |  88 +++++++++++++++--
 src/session/room/mod.rs                            |   2 +-
 4 files changed, 200 insertions(+), 27 deletions(-)
---
diff --git a/data/resources/ui/content-member-page.ui b/data/resources/ui/content-member-page.ui
index cf9f4dcc..7f43a4b9 100644
--- a/data/resources/ui/content-member-page.ui
+++ b/data/resources/ui/content-member-page.ui
@@ -34,8 +34,9 @@
           </object>
         </child>
         <child>
-          <object class="GtkScrolledWindow">
+          <object class="GtkScrolledWindow" id="members_scroll">
             <property name="propagate-natural-height">True</property>
+            <property name="max-content-height">300</property>
             <child>
               <object class="GtkListView" id="members_list_view">
                 <property name="show-separators">True</property>
@@ -53,6 +54,28 @@
         </child>
       </object>
     </child>
+    <child>
+      <object class="AdwPreferencesGroup" id="invited_section">
+        <child>
+          <object class="GtkScrolledWindow" id="invited_scroll">
+            <property name="propagate-natural-height">True</property>
+            <property name="max-content-height">300</property>
+            <child>
+              <object class="GtkListView" id="invited_list_view">
+                <property name="show-separators">True</property>
+                <property name="factory">
+                  <object class="GtkBuilderListItemFactory">
+                    <property name="resource">/org/gnome/FractalNext/content-member-item.ui</property>
+                  </object>
+                </property>
+                <style>
+                  <class name="content"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
   </template>
 </interface>
-
diff --git a/src/session/content/room_details/member_page/mod.rs 
b/src/session/content/room_details/member_page/mod.rs
index de6b601c..fbd20ea0 100644
--- a/src/session/content/room_details/member_page/mod.rs
+++ b/src/session/content/room_details/member_page/mod.rs
@@ -5,10 +5,10 @@ use gtk::{
     subclass::prelude::*,
     CompositeTemplate,
 };
+use log::warn;
 
 mod member_menu;
 mod member_row;
-use log::warn;
 
 use self::{member_menu::MemberMenu, member_row::MemberRow};
 use crate::{
@@ -16,12 +16,14 @@ use crate::{
     prelude::*,
     session::{
         content::RoomDetails,
-        room::{Member, RoomAction},
+        room::{Member, Membership, RoomAction},
         Room, User, UserActions,
     },
     spawn,
 };
 
+const MAX_LIST_HEIGHT: i32 = 300;
+
 mod imp {
     use glib::subclass::InitializingObject;
     use once_cell::{sync::Lazy, unsync::OnceCell};
@@ -40,7 +42,15 @@ mod imp {
         pub members_search_entry: TemplateChild<gtk::SearchEntry>,
         #[template_child]
         pub members_list_view: TemplateChild<gtk::ListView>,
+        #[template_child]
+        pub members_scroll: TemplateChild<gtk::ScrolledWindow>,
         pub member_menu: OnceCell<MemberMenu>,
+        #[template_child]
+        pub invited_section: TemplateChild<adw::PreferencesGroup>,
+        #[template_child]
+        pub invited_list_view: TemplateChild<gtk::ListView>,
+        #[template_child]
+        pub invited_scroll: TemplateChild<gtk::ScrolledWindow>,
     }
 
     #[glib::object_subclass]
@@ -117,8 +127,8 @@ mod imp {
         fn constructed(&self, obj: &Self::Type) {
             self.parent_constructed(obj);
 
-            obj.init_member_search();
-            obj.init_member_count();
+            obj.init_members_list();
+            obj.init_invited_list();
             obj.init_invite_button();
         }
     }
@@ -144,10 +154,28 @@ impl MemberPage {
         self.imp().room.set(room).expect("Room already initialized");
     }
 
-    fn init_member_search(&self) {
+    fn init_members_list(&self) {
         let priv_ = self.imp();
         let members = self.room().members();
 
+        // Only keep the members that are in the join membership state
+        let joined_expression = gtk::PropertyExpression::new(
+            Member::static_type(),
+            gtk::Expression::NONE,
+            "membership",
+        )
+        .chain_closure::<bool>(closure!(
+            |_: Option<glib::Object>, membership: Membership| { membership == Membership::Join }
+        ));
+        let joined_filter = gtk::BoolFilter::new(Some(joined_expression));
+        let joined_members = gtk::FilterListModel::new(Some(members), Some(&joined_filter));
+
+        // Set up the members count.
+        self.member_count_changed(joined_members.n_items());
+        joined_members.connect_items_changed(clone!(@weak self as obj => move |members, _, _, _| {
+            obj.member_count_changed(members.n_items());
+        }));
+
         // Sort the members list by power level, then display name.
         let sorter = gtk::MultiSorter::new();
         sorter.append(
@@ -167,7 +195,7 @@ impl MemberPage {
                 "display-name",
             ),
         )));
-        let sorted_members = gtk::SortListModel::new(Some(members), Some(&sorter));
+        let sorted_members = gtk::SortListModel::new(Some(&joined_members), Some(&sorter));
 
         fn search_string(member: Member) -> String {
             format!(
@@ -199,17 +227,71 @@ impl MemberPage {
         priv_.members_list_view.set_model(Some(&model));
     }
 
-    fn init_member_count(&self) {
+    fn member_count_changed(&self, n: u32) {
+        let priv_ = self.imp();
+        priv_
+            .member_count
+            .set_text(&ngettext!("{} Member", "{} Members", n, n));
+        // FIXME: This won't be needed when we can request the natural height
+        // on AdwPreferencesPage
+        // See: https://gitlab.gnome.org/GNOME/libadwaita/-/issues/77
+        if n > 5 {
+            priv_.members_scroll.set_min_content_height(MAX_LIST_HEIGHT);
+        } else {
+            priv_.members_scroll.set_min_content_height(-1);
+        }
+    }
+
+    fn init_invited_list(&self) {
+        let priv_ = self.imp();
         let members = self.room().members();
 
-        let member_count = self.imp().member_count.get();
-        fn set_member_count(member_count: &gtk::Label, n: u32) {
-            member_count.set_text(&ngettext!("{} Member", "{} Members", n, n));
+        // Only keep the members that are in the join membership state
+        let invited_expression = gtk::PropertyExpression::new(
+            Member::static_type(),
+            gtk::Expression::NONE,
+            "membership",
+        )
+        .chain_closure::<bool>(closure!(
+            |_: Option<glib::Object>, membership: Membership| { membership == Membership::Invite }
+        ));
+        let invited_filter = gtk::BoolFilter::new(Some(invited_expression));
+        let invited_members = gtk::FilterListModel::new(Some(members), Some(&invited_filter));
+
+        // Set up the invited section visibility and the invited count.
+        self.invited_count_changed(invited_members.n_items());
+        invited_members.connect_items_changed(
+            clone!(@weak self as obj => move |members, _, _, _| {
+                obj.invited_count_changed(members.n_items());
+            }),
+        );
+
+        // Sort the invited list by display name.
+        let sorter = gtk::StringSorter::new(Some(&gtk::PropertyExpression::new(
+            Member::static_type(),
+            gtk::Expression::NONE,
+            "display-name",
+        )));
+        let sorted_invited = gtk::SortListModel::new(Some(&invited_members), Some(&sorter));
+
+        let model = gtk::NoSelection::new(Some(&sorted_invited));
+        priv_.invited_list_view.set_model(Some(&model));
+    }
+
+    fn invited_count_changed(&self, n: u32) {
+        let priv_ = self.imp();
+        priv_.invited_section.set_visible(n > 0);
+        priv_
+            .invited_section
+            .set_title(&ngettext!("{} Invited", "{} Invited", n, n));
+        // FIXME: This won't be needed when we can request the natural height
+        // on AdwPreferencesPage
+        // See: https://gitlab.gnome.org/GNOME/libadwaita/-/issues/77
+        if n > 5 {
+            priv_.invited_scroll.set_min_content_height(MAX_LIST_HEIGHT);
+        } else {
+            priv_.invited_scroll.set_min_content_height(-1);
         }
-        set_member_count(&member_count, members.n_items());
-        members.connect_items_changed(clone!(@weak member_count => move |members, _, _, _| {
-            set_member_count(&member_count, members.n_items());
-        }));
     }
 
     fn init_invite_button(&self) {
diff --git a/src/session/room/member.rs b/src/session/room/member.rs
index 90ea62b2..6e9f3394 100644
--- a/src/session/room/member.rs
+++ b/src/session/room/member.rs
@@ -1,7 +1,10 @@
 use gtk::{glib, prelude::*, subclass::prelude::*};
 use matrix_sdk::{
     ruma::{
-        events::{room::member::RoomMemberEventContent, StrippedStateEvent, SyncStateEvent},
+        events::{
+            room::member::{MembershipState, RoomMemberEventContent},
+            StrippedStateEvent, SyncStateEvent,
+        },
         identifiers::{MxcUri, UserId},
     },
     RoomMember,
@@ -18,6 +21,43 @@ use crate::{
     },
 };
 
+#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
+#[repr(u32)]
+#[enum_type(name = "Membership")]
+pub enum Membership {
+    Leave = 0,
+    Join = 1,
+    Invite = 2,
+    Ban = 3,
+    Knock = 4,
+    Custom = 5,
+}
+
+impl Default for Membership {
+    fn default() -> Self {
+        Membership::Leave
+    }
+}
+
+impl From<&MembershipState> for Membership {
+    fn from(state: &MembershipState) -> Self {
+        match state {
+            MembershipState::Leave => Membership::Leave,
+            MembershipState::Join => Membership::Join,
+            MembershipState::Invite => Membership::Invite,
+            MembershipState::Ban => Membership::Ban,
+            MembershipState::Knock => Membership::Knock,
+            _ => Membership::Custom,
+        }
+    }
+}
+
+impl From<MembershipState> for Membership {
+    fn from(state: MembershipState) -> Self {
+        Membership::from(&state)
+    }
+}
+
 mod imp {
     use std::cell::Cell;
 
@@ -28,6 +68,7 @@ mod imp {
     #[derive(Debug, Default)]
     pub struct Member {
         pub power_level: Cell<PowerLevel>,
+        pub membership: Cell<Membership>,
     }
 
     #[glib::object_subclass]
@@ -40,15 +81,25 @@ mod imp {
     impl ObjectImpl for Member {
         fn properties() -> &'static [glib::ParamSpec] {
             static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
-                vec![glib::ParamSpecInt64::new(
-                    "power-level",
-                    "Power level",
-                    "Power level of the member in its room.",
-                    POWER_LEVEL_MIN,
-                    POWER_LEVEL_MAX,
-                    0,
-                    glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
-                )]
+                vec![
+                    glib::ParamSpecInt64::new(
+                        "power-level",
+                        "Power level",
+                        "Power level of the member in its room.",
+                        POWER_LEVEL_MIN,
+                        POWER_LEVEL_MAX,
+                        0,
+                        glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                    ),
+                    glib::ParamSpecEnum::new(
+                        "membership",
+                        "Membership",
+                        "This member's membership state.",
+                        Membership::static_type(),
+                        Membership::default() as i32,
+                        glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                    ),
+                ]
             });
 
             PROPERTIES.as_ref()
@@ -57,6 +108,7 @@ mod imp {
         fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
             match pspec.name() {
                 "power-level" => obj.power_level().to_value(),
+                "membership" => obj.membership().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -103,6 +155,20 @@ impl Member {
         self.role().is_peasant()
     }
 
+    pub fn membership(&self) -> Membership {
+        let priv_ = imp::Member::from_instance(self);
+        priv_.membership.get()
+    }
+
+    fn set_membership(&self, membership: Membership) {
+        if self.membership() == membership {
+            return;
+        }
+        let priv_ = imp::Member::from_instance(self);
+        priv_.membership.replace(membership);
+        self.notify("membership");
+    }
+
     /// Update the user based on the the room member state event
     pub fn update_from_room_member(&self, member: &RoomMember) {
         if member.user_id() != &*self.user_id() {
@@ -114,6 +180,7 @@ impl Member {
         self.avatar()
             .set_url(member.avatar_url().map(std::borrow::ToOwned::to_owned));
         self.set_power_level(member.power_level());
+        self.set_membership(member.membership().into());
     }
 
     /// Update the user based on the the room member state event
@@ -125,6 +192,7 @@ impl Member {
 
         self.set_display_name(event.display_name());
         self.avatar().set_url(event.avatar_url());
+        self.set_membership((&event.content().membership).into());
     }
 }
 
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index c3733b85..6e447364 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -48,7 +48,7 @@ pub use self::{
     event_actions::EventActions,
     highlight_flags::HighlightFlags,
     item::{Item, ItemType},
-    member::Member,
+    member::{Member, Membership},
     member_role::MemberRole,
     power_levels::{PowerLevel, PowerLevels, RoomAction, POWER_LEVEL_MAX, POWER_LEVEL_MIN},
     reaction_group::ReactionGroup,


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