From 2e62541bef31a7be635309f4fe077fbef2f96f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 26 Oct 2023 13:35:56 +0200 Subject: [PATCH] room-details: Display the members list loading state --- po/POTFILES.in | 1 + src/components/loading_row.rs | 15 ++ src/components/mod.rs | 2 +- src/session/model/room/member_list.rs | 17 +-- .../members_list_view/extra_lists.rs | 133 ++++++++++++++---- .../member_page/members_list_view/item_row.rs | 4 +- .../content/room_details/member_page/mod.rs | 10 +- 7 files changed, 134 insertions(+), 48 deletions(-) diff --git a/po/POTFILES.in b/po/POTFILES.in index 5129d915..d6082123 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -66,6 +66,7 @@ src/session/view/content/room_details/history_viewer/media.ui src/session/view/content/room_details/invite_subpage/invitee_list.rs src/session/view/content/room_details/invite_subpage/mod.rs src/session/view/content/room_details/invite_subpage/mod.ui +src/session/view/content/room_details/member_page/members_list_view/extra_lists.rs src/session/view/content/room_details/member_page/member_menu.ui src/session/view/content/room_details/member_page/mod.rs src/session/view/content/room_details/member_page/mod.ui diff --git a/src/components/loading_row.rs b/src/components/loading_row.rs index b2908e0b..d44e7569 100644 --- a/src/components/loading_row.rs +++ b/src/components/loading_row.rs @@ -3,6 +3,21 @@ use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate use super::Spinner; +/// The state of a resource that can be loaded. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, glib::Enum)] +#[enum_type(name = "LoadingState")] +pub enum LoadingState { + /// It hasn't been loaded yet. + #[default] + Initial, + /// It is currently loading. + Loading, + /// It has been fully loaded. + Ready, + /// An error occurred while loading it. + Error, +} + mod imp { use std::cell::Cell; diff --git a/src/components/mod.rs b/src/components/mod.rs index 06c4f0be..b865679a 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -37,7 +37,7 @@ pub use self::{ editable_avatar::{EditableAvatar, EditableAvatarState}, image_paintable::ImagePaintable, label_with_widgets::{LabelWithWidgets, DEFAULT_PLACEHOLDER}, - loading_row::LoadingRow, + loading_row::{LoadingRow, LoadingState}, location_viewer::LocationViewer, media_content_viewer::{ContentType, MediaContentViewer}, overlapping_box::OverlappingBox, diff --git a/src/session/model/room/member_list.rs b/src/session/model/room/member_list.rs index 47daa987..97f472cb 100644 --- a/src/session/model/room/member_list.rs +++ b/src/session/model/room/member_list.rs @@ -15,22 +15,7 @@ use matrix_sdk::{ use tracing::error; use super::{Event, Member, Membership, Room}; -use crate::{spawn, spawn_tokio}; - -/// The state of a resource that can be loaded. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, glib::Enum)] -#[enum_type(name = "LoadingState")] -pub enum LoadingState { - /// It hasn't been loaded yet. - #[default] - Initial, - /// It is currently loading. - Loading, - /// It has been fully loaded. - Ready, - /// An error occurred while loading it. - Error, -} +use crate::{components::LoadingState, spawn, spawn_tokio}; mod imp { use std::cell::{Cell, RefCell}; diff --git a/src/session/view/content/room_details/member_page/members_list_view/extra_lists.rs b/src/session/view/content/room_details/member_page/members_list_view/extra_lists.rs index 9f122110..8996d1eb 100644 --- a/src/session/view/content/room_details/member_page/members_list_view/extra_lists.rs +++ b/src/session/view/content/room_details/member_page/members_list_view/extra_lists.rs @@ -1,9 +1,15 @@ +use gettextrs::gettext; use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*}; use super::MembershipSubpageItem; +use crate::{ + components::{LoadingRow, LoadingState}, + session::model::MemberList, + utils::BoundObjectWeakRef, +}; mod imp { - use std::cell::Cell; + use std::cell::{Cell, RefCell}; use once_cell::{sync::Lazy, unsync::OnceCell}; @@ -11,6 +17,8 @@ mod imp { #[derive(Debug, Default)] pub struct ExtraLists { + pub members: BoundObjectWeakRef, + pub state: RefCell>, pub invited: OnceCell, pub banned: OnceCell, pub invited_is_empty: Cell, @@ -28,6 +36,9 @@ mod imp { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ + glib::ParamSpecObject::builder::("members") + .construct_only() + .build(), glib::ParamSpecObject::builder::("invited") .construct_only() .build(), @@ -44,6 +55,7 @@ mod imp { let obj = self.obj(); match pspec.name() { + "members" => obj.set_members(value.get::>().unwrap().as_ref()), "invited" => obj.set_invited(value.get().unwrap()), "banned" => obj.set_banned(value.get().unwrap()), _ => unimplemented!(), @@ -54,6 +66,7 @@ mod imp { let obj = self.obj(); match pspec.name() { + "members" => obj.members().to_value(), "invited" => obj.invited().to_value(), "banned" => obj.banned().to_value(), _ => unimplemented!(), @@ -78,6 +91,10 @@ mod imp { self.invited_is_empty.set(invited_members.n_items() == 0); self.banned_is_empty.set(banned_members.n_items() == 0); } + + fn dispose(&self) { + self.members.disconnect_signals(); + } } impl ListModelImpl for ExtraLists { @@ -86,30 +103,32 @@ mod imp { } fn n_items(&self) -> u32 { - let mut len = 0; - - if !self.invited_is_empty.get() { - len += 1; - } - if !self.banned_is_empty.get() { - len += 1; - } - - len + self.state.borrow().is_some() as u32 + + (!self.invited_is_empty.get()) as u32 + + (!self.banned_is_empty.get()) as u32 } fn item(&self, position: u32) -> Option { - let has_invited = !self.invited_is_empty.get(); - let has_banned = !self.banned_is_empty.get(); + let mut position = position; + + if let Some(state_row) = &*self.state.borrow() { + if position == 0 { + return Some(state_row.clone().upcast()); + } - if position == 0 && has_invited { - let invited = self.invited.get().unwrap(); - return Some(invited.clone().upcast()); + position -= 1; } - if has_banned && ((position == 0 && !has_invited) || (position == 1 && has_invited)) { - let banned = self.banned.get().unwrap(); - return Some(banned.clone().upcast()); + if !self.invited_is_empty.get() { + if position == 0 { + return self.invited.get().cloned().and_upcast(); + } + + position -= 1; + } + + if !self.banned_is_empty.get() && position == 0 { + return self.banned.get().cloned().and_upcast(); } None @@ -123,13 +142,74 @@ glib::wrapper! { } impl ExtraLists { - pub fn new(invited: &MembershipSubpageItem, banned: &MembershipSubpageItem) -> Self { + pub fn new( + members: &MemberList, + invited: &MembershipSubpageItem, + banned: &MembershipSubpageItem, + ) -> Self { glib::Object::builder() + .property("members", members) .property("invited", invited) .property("banned", banned) .build() } + pub fn members(&self) -> Option { + self.imp().members.obj() + } + + fn set_members(&self, members: Option<&MemberList>) { + let Some(members) = members else { + // Ignore if there is no list. + return; + }; + + let imp = self.imp(); + imp.members.disconnect_signals(); + + let signal_handler_ids = vec![members.connect_notify_local( + Some("state"), + clone!(@weak self as obj => move |members, _| { + obj.update_loading_state(members.state()); + }), + )]; + self.update_loading_state(members.state()); + + imp.members.set(members, signal_handler_ids); + self.notify("members"); + } + + /// Update this list for the given loading state. + fn update_loading_state(&self, state: LoadingState) { + let imp = self.imp(); + + if state == LoadingState::Ready { + if imp.state.take().is_some() { + self.items_changed(0, 1, 0); + } + + return; + } + + let mut added = false; + { + let mut state_row_borrow = imp.state.borrow_mut(); + let state_row = state_row_borrow.get_or_insert_with(|| { + added = true; + LoadingRow::new() + }); + + let error = (state == LoadingState::Error) + .then(|| gettext("Could not load the full list of room members")); + + state_row.set_error(error.as_deref()); + } + + if added { + self.items_changed(0, 0, 1); + } + } + /// The subpage item for invited members. pub fn invited(&self) -> &MembershipSubpageItem { self.imp().invited.get().unwrap() @@ -162,12 +242,13 @@ impl ExtraLists { } imp.invited_is_empty.set(is_empty); + let position = imp.state.borrow().is_some() as u32; - let added = if was_empty { 1 } else { 0 }; // If it is not added, it is removed. - let removed = 1 - added; + let added = was_empty as u32; + let removed = (!was_empty) as u32; - self.items_changed(0, removed, added); + self.items_changed(position, removed, added); } fn update_banned(&self) { @@ -183,11 +264,11 @@ impl ExtraLists { imp.banned_is_empty.set(is_empty); - let position = if imp.invited_is_empty.get() { 0 } else { 1 }; + let position = imp.state.borrow().is_some() as u32 + (!imp.invited_is_empty.get()) as u32; - let added = if was_empty { 1 } else { 0 }; // If it is not added, it is removed. - let removed = 1 - added; + let added = was_empty as u32; + let removed = (!was_empty) as u32; self.items_changed(position, removed, added); } diff --git a/src/session/view/content/room_details/member_page/members_list_view/item_row.rs b/src/session/view/content/room_details/member_page/members_list_view/item_row.rs index 1b130fbc..34afd1dd 100644 --- a/src/session/view/content/room_details/member_page/members_list_view/item_row.rs +++ b/src/session/view/content/room_details/member_page/members_list_view/item_row.rs @@ -2,7 +2,7 @@ use adw::{prelude::BinExt, subclass::prelude::*}; use gtk::{glib, glib::prelude::*}; use super::{MemberRow, MembershipSubpageItem, MembershipSubpageRow}; -use crate::session::model::Member; +use crate::{components::LoadingRow, session::model::Member}; mod imp { use std::cell::RefCell; @@ -98,6 +98,8 @@ impl ItemRow { }; child.set_item(Some(item.clone())); + } else if let Some(child) = item.downcast_ref::() { + self.set_child(Some(child)) } else { unimplemented!("The object {item:?} doesn't have a widget implementation"); } diff --git a/src/session/view/content/room_details/member_page/mod.rs b/src/session/view/content/room_details/member_page/mod.rs index 372c7fe0..7dc88d97 100644 --- a/src/session/view/content/room_details/member_page/mod.rs +++ b/src/session/view/content/room_details/member_page/mod.rs @@ -191,13 +191,15 @@ impl MemberPage { // We should have a strong reference to the list in the main page so we can use // `get_or_create_members()`. - let members = gtk::SortListModel::new(Some(room.get_or_create_members()), Some(sorter)); + let members = room.get_or_create_members(); + let sorted_members = gtk::SortListModel::new(Some(members.clone()), Some(sorter)); - let joined_members = self.build_filtered_list(members.clone(), Membership::Join); - let invited_members = self.build_filtered_list(members.clone(), Membership::Invite); - let banned_members = self.build_filtered_list(members, Membership::Ban); + let joined_members = self.build_filtered_list(sorted_members.clone(), Membership::Join); + let invited_members = self.build_filtered_list(sorted_members.clone(), Membership::Invite); + let banned_members = self.build_filtered_list(sorted_members, Membership::Ban); let extra_list = ExtraLists::new( + &members, &MembershipSubpageItem::new(Membership::Invite, &invited_members), &MembershipSubpageItem::new(Membership::Ban, &banned_members), );