Browse Source

room-details: Display the members list loading state

merge-requests/1461/head
Kévin Commaille 2 years ago
parent
commit
2e62541bef
No known key found for this signature in database
GPG Key ID: 29A48C1F03620416
  1. 1
      po/POTFILES.in
  2. 15
      src/components/loading_row.rs
  3. 2
      src/components/mod.rs
  4. 17
      src/session/model/room/member_list.rs
  5. 133
      src/session/view/content/room_details/member_page/members_list_view/extra_lists.rs
  6. 4
      src/session/view/content/room_details/member_page/members_list_view/item_row.rs
  7. 10
      src/session/view/content/room_details/member_page/mod.rs

1
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

15
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;

2
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,

17
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};

133
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<MemberList>,
pub state: RefCell<Option<LoadingRow>>,
pub invited: OnceCell<MembershipSubpageItem>,
pub banned: OnceCell<MembershipSubpageItem>,
pub invited_is_empty: Cell<bool>,
@ -28,6 +36,9 @@ mod imp {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<MemberList>("members")
.construct_only()
.build(),
glib::ParamSpecObject::builder::<MembershipSubpageItem>("invited")
.construct_only()
.build(),
@ -44,6 +55,7 @@ mod imp {
let obj = self.obj();
match pspec.name() {
"members" => obj.set_members(value.get::<Option<MemberList>>().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<glib::Object> {
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<MemberList> {
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);
}

4
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::<LoadingRow>() {
self.set_child(Some(child))
} else {
unimplemented!("The object {item:?} doesn't have a widget implementation");
}

10
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),
);

Loading…
Cancel
Save