Browse Source

room-details: Handle all states of room members list

fractal-9
Kévin Commaille 1 year ago committed by Kévin Commaille
parent
commit
c3242b1080
  1. 7
      data/resources/style.css
  2. 2
      src/components/user_page.rs
  3. 34
      src/session/model/room/member.rs
  4. 37
      src/session/view/content/room_details/members_page/members_list_view/item_row.rs
  5. 34
      src/session/view/content/room_details/members_page/members_list_view/membership_subpage_row.rs
  6. 9
      src/session/view/content/room_details/members_page/members_list_view/membership_subpage_row.ui
  7. 315
      src/session/view/content/room_details/members_page/members_list_view/mod.rs
  8. 104
      src/session/view/content/room_details/members_page/members_list_view/mod.ui
  9. 9
      src/session/view/content/room_details/members_page/mod.rs
  10. 18
      src/session/view/content/room_details/membership_lists.rs

7
data/resources/style.css

@ -985,10 +985,13 @@ audio-history-viewer listview > row:last-child {
background: transparent;
}
members-list row {
margin-bottom: 6px;
members-list listview > row, members-list list > row {
padding: 8px 12px;
min-height: 32px;
}
members-list listview > row {
margin-bottom: 6px;
border-radius: 12px;
}

2
src/components/user_page.rs

@ -378,7 +378,7 @@ impl UserPage {
// Translators: As in, 'The room member knocked to request access to the room'.
.set_label(&pgettext("member", "Knocked"));
}
Membership::Custom => {
Membership::Unsupported => {
imp.membership_label
// Translators: As in, 'The room member has an unknown role'.
.set_label(&pgettext("member", "Unknown"));

34
src/session/model/room/member.rs

@ -15,18 +15,34 @@ use super::{
};
use crate::{components::PillSource, prelude::*, session::model::User, spawn, spawn_tokio};
/// The possible states of membership of a user in a room.
#[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum, glib::Variant)]
#[variant_enum(repr)]
#[repr(u32)]
#[enum_type(name = "Membership")]
pub enum Membership {
/// The user left the room, or was never in the room.
#[default]
Leave = 0,
Join = 1,
Invite = 2,
Ban = 3,
Knock = 4,
Custom = 5,
Leave,
/// The user is currently in the room.
Join,
/// The user was invited to the room.
Invite,
/// The user was baned from the room.
Ban,
/// The user knocked on the room.
Knock,
/// The user is in an unsupported membership state.
Unsupported,
}
impl Membership {
/// Get the icon name that represents this membership.
pub fn icon_name(&self) -> &'static str {
match self {
Self::Invite => "user-add-symbolic",
Self::Ban => "blocked-symbolic",
_ => "users-symbolic",
}
}
}
impl From<&MembershipState> for Membership {
@ -37,7 +53,7 @@ impl From<&MembershipState> for Membership {
MembershipState::Invite => Membership::Invite,
MembershipState::Ban => Membership::Ban,
MembershipState::Knock => Membership::Knock,
_ => Membership::Custom,
_ => Membership::Unsupported,
}
}
}

37
src/session/view/content/room_details/members_page/members_list_view/item_row.rs

@ -1,17 +1,14 @@
use adw::{prelude::BinExt, subclass::prelude::*};
use gtk::{glib, glib::prelude::*};
use adw::{prelude::*, subclass::prelude::*};
use gtk::glib;
use super::MembershipSubpageRow;
use crate::{
components::LoadingRow,
session::{
model::Member,
view::content::room_details::{MemberRow, MembershipSubpageItem},
},
use crate::session::{
model::Member,
view::content::room_details::{MemberRow, MembershipSubpageItem},
};
mod imp {
use std::cell::RefCell;
use std::cell::{Cell, RefCell};
use super::*;
@ -23,6 +20,9 @@ mod imp {
/// It can be a `Member` or a `MemberSubpageItem`.
#[property(get, set = Self::set_item, explicit_notify, nullable)]
item: RefCell<Option<glib::Object>>,
/// Whether this row can be activated.
#[property(get)]
activatable: Cell<bool>,
}
#[glib::object_subclass]
@ -58,19 +58,24 @@ mod imp {
child
};
child.set_member(Some(member.clone()));
self.set_activatable(true);
} else if let Some(item) = item.downcast_ref::<MembershipSubpageItem>() {
let child =
if let Some(child) = obj.child().and_downcast::<MembershipSubpageRow>() {
child
} else {
let child = MembershipSubpageRow::new();
child.set_activatable(false);
obj.set_child(Some(&child));
child
};
child.set_item(Some(item.clone()));
} else if let Some(child) = item.downcast_ref::<LoadingRow>() {
obj.set_child(Some(child))
self.set_activatable(true);
} else if let Some(child) = item.downcast_ref::<gtk::Widget>() {
obj.set_child(Some(child));
self.set_activatable(false);
} else {
unimplemented!("The object {item:?} doesn't have a widget implementation");
}
@ -79,6 +84,16 @@ mod imp {
self.item.replace(item);
obj.notify_item();
}
/// Set whether this row can be activated.
fn set_activatable(&self, activatable: bool) {
if self.activatable.get() == activatable {
return;
}
self.activatable.set(activatable);
self.obj().notify_activatable();
}
}
}

34
src/session/view/content/room_details/members_page/members_list_view/membership_subpage_row.rs

@ -1,6 +1,5 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::npgettext;
use gtk::{glib, glib::clone, CompositeTemplate};
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
use crate::session::{
model::Membership, view::content::room_details::membership_subpage_item::MembershipSubpageItem,
@ -23,9 +22,9 @@ mod imp {
#[property(get, set = Self::set_item, explicit_notify, nullable)]
item: RefCell<Option<MembershipSubpageItem>>,
items_changed_handler: RefCell<Option<glib::SignalHandlerId>>,
/// The icon of this row.
#[property(get = Self::icon)]
icon: PhantomData<Option<String>>,
/// The name of the icon of this row.
#[property(get = Self::icon_name)]
icon_name: PhantomData<Option<String>>,
/// The label of this row.
#[property(get = Self::label)]
label: PhantomData<Option<String>>,
@ -37,7 +36,7 @@ mod imp {
impl ObjectSubclass for MembershipSubpageRow {
const NAME: &'static str = "MembersPageMembershipSubpageRow";
type Type = super::MembershipSubpageRow;
type ParentType = adw::Bin;
type ParentType = gtk::ListBoxRow;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
@ -60,7 +59,7 @@ mod imp {
}
impl WidgetImpl for MembershipSubpageRow {}
impl BinImpl for MembershipSubpageRow {}
impl ListBoxRowImpl for MembershipSubpageRow {}
impl MembershipSubpageRow {
/// Set the item presented by this row.
@ -96,17 +95,20 @@ mod imp {
self.item.replace(item);
obj.notify_item();
obj.notify_icon();
obj.notify_icon_name();
obj.notify_label();
}
/// The icon of this row.
fn icon(&self) -> Option<String> {
match self.item.borrow().as_ref()?.membership() {
Membership::Invite => Some("user-add-symbolic".to_owned()),
Membership::Ban => Some("blocked-symbolic".to_owned()),
_ => None,
}
/// The name of the icon of this row.
fn icon_name(&self) -> Option<String> {
Some(
self.item
.borrow()
.as_ref()?
.membership()
.icon_name()
.to_owned(),
)
}
/// The label of this row.
@ -132,7 +134,7 @@ mod imp {
glib::wrapper! {
/// A row presenting a `MembershipSubpageItem`.
pub struct MembershipSubpageRow(ObjectSubclass<imp::MembershipSubpageRow>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
@extends gtk::Widget, gtk::ListBoxRow, @implements gtk::Accessible;
}
impl MembershipSubpageRow {

9
src/session/view/content/room_details/members_page/members_list_view/membership_subpage_row.ui

@ -1,17 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="MembersPageMembershipSubpageRow" parent="AdwBin">
<template class="MembersPageMembershipSubpageRow" parent="GtkListBoxRow">
<property name="selectable">False</property>
<accessibility>
<relation name="labelled-by">title</relation>
</accessibility>
<child>
<property name="child">
<object class="GtkBox">
<property name="spacing">6</property>
<child>
<object class="GtkImage">
<property name="valign">center</property>
<property name="accessible-role">presentation</property>
<property name="icon-name" bind-source="MembersPageMembershipSubpageRow" bind-property="icon" bind-flags="sync-create"/>
<property name="icon-name" bind-source="MembersPageMembershipSubpageRow" bind-property="icon-name" bind-flags="sync-create"/>
<style>
<class name="icon"/>
</style>
@ -39,6 +40,6 @@
</object>
</child>
</object>
</child>
</property>
</template>
</interface>

315
src/session/view/content/room_details/members_page/members_list_view/mod.rs

@ -1,5 +1,5 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::ngettext;
use gettextrs::{gettext, ngettext};
use gtk::{
gio, glib,
glib::{clone, closure},
@ -10,20 +10,18 @@ mod item_row;
mod membership_subpage_row;
use self::{item_row::ItemRow, membership_subpage_row::MembershipSubpageRow};
use super::membership_as_tag;
use crate::{
prelude::*,
session::{
model::{Member, Membership, Room},
view::content::room_details::MembershipSubpageItem,
view::content::room_details::{MembershipLists, MembershipSubpageItem},
},
utils::expression,
utils::{expression, LoadingState},
};
mod imp {
use std::{
cell::{Cell, RefCell},
marker::PhantomData,
};
use std::cell::{Cell, RefCell};
use glib::subclass::InitializingObject;
@ -35,27 +33,41 @@ mod imp {
)]
#[properties(wrapper_type = super::MembersListView)]
pub struct MembersListView {
#[template_child]
search_button: TemplateChild<gtk::ToggleButton>,
#[template_child]
search_bar: TemplateChild<gtk::SearchBar>,
#[template_child]
search_entry: TemplateChild<gtk::SearchEntry>,
#[template_child]
stack: TemplateChild<gtk::Stack>,
#[template_child]
empty_stack_page: TemplateChild<gtk::StackPage>,
#[template_child]
empty_page: TemplateChild<adw::StatusPage>,
#[template_child]
empty_listbox: TemplateChild<gtk::ListBox>,
#[template_child]
members_stack_page: TemplateChild<gtk::StackPage>,
#[template_child]
list_view: TemplateChild<gtk::ListView>,
/// The room containing the members to present.
#[property(get, set = Self::set_room, construct_only)]
room: glib::WeakRef<Room>,
/// The model used for this view.
#[property(get = Self::model, set = Self::set_model, construct_only)]
model: PhantomData<Option<gio::ListModel>>,
/// The lists of members filtered by membership for the room.
#[property(get, set = Self::set_membership_lists, construct_only)]
membership_lists: glib::WeakRef<MembershipLists>,
/// The model with the search filter.
filtered_model: gtk::FilterListModel,
/// The membership used to filter the model.
#[property(get, construct_only, builder(Membership::default()))]
#[property(get, set = Self::set_membership, construct_only, builder(Membership::default()))]
membership: Cell<Membership>,
/// Whether our own user can send an invite in the current room.
#[property(get, set = Self::set_can_invite, explicit_notify)]
can_invite: Cell<bool>,
members_state_handler: RefCell<Option<glib::SignalHandlerId>>,
items_changed_handler: RefCell<Option<glib::SignalHandlerId>>,
extra_items_changed_handler: RefCell<Option<glib::SignalHandlerId>>,
}
#[glib::object_subclass]
@ -68,6 +80,7 @@ mod imp {
ItemRow::ensure_type();
Self::bind_template(klass);
Self::bind_template_callbacks(klass);
klass.set_css_name("members-list");
}
@ -81,7 +94,6 @@ mod imp {
impl ObjectImpl for MembersListView {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
// Needed because the GtkSearchEntry is not the direct child of the
// GtkSearchBear.
@ -116,41 +128,26 @@ mod imp {
);
self.filtered_model.set_filter(Some(&search_filter));
self.list_view.set_model(Some(&gtk::NoSelection::new(Some(
self.filtered_model.clone(),
))));
self.list_view.connect_activate(clone!(
#[weak]
obj,
move |_, pos| {
let Some(item) = obj.imp().filtered_model.item(pos) else {
return;
};
if let Some(member) = item.downcast_ref::<Member>() {
obj.activate_action(
"details.show-member",
Some(&member.user_id().as_str().to_variant()),
)
.unwrap();
} else if let Some(item) = item.downcast_ref::<MembershipSubpageItem>() {
obj.activate_action(
"members.show-membership-list",
Some(&item.membership().to_variant()),
)
.unwrap();
}
}
));
self.update_title();
self.init_members_list();
}
fn dispose(&self) {
if let Some(model) = self.model() {
if let Some(membership_lists) = self.membership_lists.upgrade() {
if let Some(handler) = self.members_state_handler.take() {
membership_lists.members().disconnect(handler);
}
if let Some(handler) = self.items_changed_handler.take() {
model.disconnect(handler);
self.members_only_model(&membership_lists)
.disconnect(handler);
}
if let Some(handler) = self.extra_items_changed_handler.take() {
if let Some(model) = self.extra_items_model(&membership_lists) {
model.disconnect(handler);
}
}
}
}
@ -159,6 +156,7 @@ mod imp {
impl WidgetImpl for MembersListView {}
impl NavigationPageImpl for MembersListView {}
#[gtk::template_callbacks]
impl MembersListView {
/// Set the room containing the members to present.
fn set_room(&self, room: &Room) {
@ -174,26 +172,26 @@ mod imp {
);
}
/// The model used for this view.
fn model(&self) -> Option<gio::ListModel> {
self.filtered_model.model()
}
/// Set the model used for this view.
fn set_model(&self, model: &gio::ListModel) {
self.filtered_model.set_model(Some(model));
/// Set the room containing the members to present.
fn set_membership_lists(&self, membership_lists: &MembershipLists) {
self.membership_lists.set(Some(membership_lists));
let items_changed_handler = model.connect_items_changed(clone!(
let members_state_handler = membership_lists.members().connect_state_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_, _, _, _| {
imp.update_title();
move |_| {
imp.update_view();
}
));
self.items_changed_handler
.replace(Some(items_changed_handler));
self.members_state_handler
.replace(Some(members_state_handler));
}
self.obj().notify_model();
/// Set the membership used to filter the model.
fn set_membership(&self, membership: Membership) {
self.membership.set(membership);
self.obj().set_tag(Some(membership_as_tag(membership)));
self.update_empty_page();
}
/// Set whether our own user can send an invite in the current room.
@ -206,14 +204,84 @@ mod imp {
self.obj().notify_can_invite();
}
/// Update the page title for the current state.
fn update_title(&self) {
let Some(model) = self.model() else {
/// The full list model from the given lists of members used by the list
/// view.
fn full_model(&self, membership_lists: &MembershipLists) -> gio::ListModel {
match self.membership.get() {
Membership::Invite => membership_lists.invited(),
Membership::Ban => membership_lists.banned(),
_ => membership_lists.joined_full(),
}
}
/// The list model from the given lists of members containing only
/// members.
fn members_only_model(&self, membership_lists: &MembershipLists) -> gio::ListModel {
match self.membership.get() {
Membership::Invite => membership_lists.invited(),
Membership::Ban => membership_lists.banned(),
_ => membership_lists.joined(),
}
}
/// The list model from the given lists of members containing extra
/// items.
fn extra_items_model(&self, membership_lists: &MembershipLists) -> Option<gio::ListModel> {
match self.membership.get() {
Membership::Invite | Membership::Ban => None,
_ => Some(membership_lists.extra_joined_items().upcast()),
}
}
/// Initialize the members list used for this view.
fn init_members_list(&self) {
let Some(membership_lists) = self.membership_lists.upgrade() else {
return;
};
let full_model = self.full_model(&membership_lists);
self.filtered_model.set_model(Some(&full_model));
let members_only_model = self.members_only_model(&membership_lists);
let items_changed_handler = members_only_model.connect_items_changed(clone!(
#[weak(rename_to = imp)]
self,
move |_, _, _, _| {
imp.update_view();
}
));
self.items_changed_handler
.replace(Some(items_changed_handler));
if let Some(extra_items_model) = self.extra_items_model(&membership_lists) {
let extra_items_changed_handler = extra_items_model.connect_items_changed(clone!(
#[weak(rename_to = imp)]
self,
move |_, _, _, _| {
imp.update_empty_listbox();
}
));
self.extra_items_changed_handler
.replace(Some(extra_items_changed_handler));
}
self.update_view();
self.update_empty_listbox();
}
/// Update the view for the current state.
fn update_view(&self) {
let Some(membership_lists) = self.membership_lists.upgrade() else {
self.stack.set_visible_child_name("no-members");
return;
};
let model = self.members_only_model(&membership_lists);
let count = model.n_items();
let title = match self.membership.get() {
let is_empty = count == 0;
let membership = self.membership.get();
let title = match membership {
Membership::Invite => {
ngettext("Invited Room Member", "Invited Room Members", count)
}
@ -222,6 +290,125 @@ mod imp {
};
self.obj().set_title(&title);
self.members_stack_page.set_title(&title);
let (visible_page, extra_items_model) = if is_empty {
match membership_lists.members().state() {
LoadingState::Initial | LoadingState::Loading => ("loading", None),
LoadingState::Error => ("error", None),
LoadingState::Ready => {
let extra_items_model = self.extra_items_model(&membership_lists);
("empty", extra_items_model)
}
}
} else {
("members", None)
};
self.empty_listbox
.bind_model(extra_items_model.as_ref(), |item| {
let row = MembershipSubpageRow::new();
row.set_item(item.downcast_ref::<MembershipSubpageItem>().cloned());
row.upcast()
});
// Hide the search button and bar if the list is empty, since there is no search
// possible.
self.search_button.set_visible(!is_empty);
self.search_bar.set_visible(!is_empty);
self.stack.set_visible_child_name(visible_page);
}
/// Update the "empty" page for the current state.
fn update_empty_page(&self) {
let membership = self.membership.get();
let (title, description) = match membership {
Membership::Invite => {
let title = gettext("No Invited Room Members");
let description = gettext("There are no invited members in this room");
(title, description)
}
Membership::Ban => {
let title = gettext("No Banned Room Members");
let description = gettext("There are no banned members in this room");
(title, description)
}
_ => {
let title = gettext("No Room Members");
let description = gettext("There are no members in this room");
(title, description)
}
};
self.empty_stack_page.set_title(&title);
self.empty_page.set_title(&title);
self.empty_page.set_description(Some(&description));
self.empty_page.set_icon_name(Some(membership.icon_name()));
}
/// Update the `GtkListBox` of the "empty" page for the current state.
fn update_empty_listbox(&self) {
let has_extra_items = self
.membership_lists
.upgrade()
.and_then(|membership_lists| self.extra_items_model(&membership_lists))
.is_some_and(|model| model.n_items() > 0);
self.empty_listbox.set_visible(has_extra_items);
}
/// Activate the row of the members `GtkListView` at the given position.
#[template_callback]
fn activate_listview_row(&self, pos: u32) {
let Some(item) = self.filtered_model.item(pos) else {
return;
};
let obj = self.obj();
if let Some(member) = item.downcast_ref::<Member>() {
obj.activate_action(
"details.show-member",
Some(&member.user_id().as_str().to_variant()),
)
.expect("action exists");
} else if let Some(item) = item.downcast_ref::<MembershipSubpageItem>() {
obj.activate_action(
"members.show-membership-list",
Some(&item.membership().to_variant()),
)
.expect("action exists");
}
}
/// Activate the given row from the `GtkListBox`.
#[template_callback]
fn activate_listbox_row(&self, row: &gtk::ListBoxRow) {
let row = row
.downcast_ref::<MembershipSubpageRow>()
.expect("list box contains only membership subpage rows");
let Some(item) = row.item() else {
return;
};
self.obj()
.activate_action(
"members.show-membership-list",
Some(&item.membership().to_variant()),
)
.expect("action exists");
}
/// Reload the list of members of the room.
#[template_callback]
fn reload_members(&self) {
let Some(membership_lists) = self.membership_lists.upgrade() else {
return;
};
membership_lists.members().reload();
}
}
}
@ -233,19 +420,13 @@ glib::wrapper! {
}
impl MembersListView {
/// Construct a new `MembersListView` with the given room, list and
/// membership.
pub fn new(
room: &Room,
model: &impl IsA<gio::ListModel>,
membership: Membership,
tag: &str,
) -> Self {
/// Construct a new `MembersListView` with the given room, membership lists
/// and membership.
pub fn new(room: &Room, membership_lists: &MembershipLists, membership: Membership) -> Self {
glib::Object::builder()
.property("room", room)
.property("model", model)
.property("membership-lists", membership_lists)
.property("membership", membership)
.property("tag", tag)
.build()
}
}

104
src/session/view/content/room_details/members_page/members_list_view/mod.ui

@ -39,28 +39,92 @@
<property name="content">
<object class="GtkOverlay">
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="child">
<object class="AdwClampScrollable">
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<object class="GtkStack" id="stack">
<property name="transition-type">crossfade</property>
<child>
<object class="GtkStackPage">
<property name="name">loading</property>
<property name="child">
<object class="AdwSpinner" />
</property>
</object>
</child>
<child>
<object class="GtkStackPage" id="empty_stack_page">
<property name="name">empty</property>
<property name="child">
<object class="AdwStatusPage" id="empty_page">
<property name="vexpand">true</property>
<property name="child">
<object class="AdwClamp">
<property name="child">
<object class="GtkListBox" id="empty_listbox">
<style>
<class name="boxed-list" />
</style>
<signal name="row-activated" handler="activate_listbox_row" swapped="true"/>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">error</property>
<property name="title" translatable="yes">Error</property>
<property name="child">
<object class="GtkListView" id="list_view">
<property name="single-click-activate">True</property>
<property name="tab-behavior">item</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<object class="AdwStatusPage" id="error_page">
<property name="icon-name">error-symbolic</property>
<property name="title" translatable="yes">Error</property>
<property name="description" translatable="yes">Could not load the full list of room members</property>
<property name="child">
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Try Again</property>
<property name="halign">center</property>
<signal name="clicked" handler="reload_members" swapped="true"/>
<style>
<class name="pill"/>
</style>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage" id="members_stack_page">
<property name="name">members</property>
<property name="child">
<object class="GtkScrolledWindow">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="child">
<object class="AdwClampScrollable">
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="child">
<object class="GtkListView" id="list_view">
<property name="single-click-activate">True</property>
<property name="tab-behavior">item</property>
<signal name="activate" handler="activate_listview_row" swapped="true"/>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="selectable">False</property>
<binding name="activatable">
<lookup name="activatable">row</lookup>
</binding>
<property name="child">
<object class="ContentMemberItemRow">
<object class="ContentMemberItemRow" id="row">
<binding name="item">
<lookup name="item">GtkListItem</lookup>
</binding>
@ -68,13 +132,17 @@
</property>
</template>
</interface>
]]></property>
]]></property>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</property>
</child>
</object>
</child>
<child type="overlay">

9
src/session/view/content/room_details/members_page/mod.rs

@ -86,14 +86,7 @@ mod imp {
return;
};
let model = match membership {
Membership::Join => membership_lists.joined_full(),
Membership::Invite => membership_lists.invited(),
Membership::Ban => membership_lists.banned(),
_ => return,
};
let subpage = MembersListView::new(&room, &model, membership, tag);
let subpage = MembersListView::new(&room, &membership_lists, membership);
self.navigation_view.push(&subpage);
}
}

18
src/session/view/content/room_details/membership_lists.rs

@ -170,6 +170,14 @@ mod imp {
loading_row
} else {
let loading_row = LoadingRow::new();
loading_row.connect_retry(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
imp.members.obj().reload();
}
));
self.extra_joined_items.insert(0, &loading_row);
loading_row
};
@ -206,9 +214,10 @@ mod imp {
self.invited_is_empty.set(is_empty);
let position = self.has_loading_row() as u32;
if is_empty && self.has_membership_item_at(Membership::Invite, position) {
let has_invite_row = self.has_membership_item_at(Membership::Invite, position);
if is_empty && has_invite_row {
self.extra_joined_items.remove(position);
} else if !is_empty && !self.has_membership_item_at(Membership::Invite, position) {
} else if !is_empty && !has_invite_row {
let invite_item = MembershipSubpageItem::new(
Membership::Invite,
self.invited.get().expect("invited members are initialized"),
@ -239,9 +248,10 @@ mod imp {
let mut position = self.has_loading_row() as u32;
position += self.has_membership_item_at(Membership::Invite, position) as u32;
if is_empty && self.has_membership_item_at(Membership::Ban, position) {
let has_ban_row = self.has_membership_item_at(Membership::Ban, position);
if is_empty && has_ban_row {
self.extra_joined_items.remove(position);
} else if !is_empty && !self.has_membership_item_at(Membership::Ban, position) {
} else if !is_empty && !has_ban_row {
let invite_item = MembershipSubpageItem::new(
Membership::Ban,
self.banned.get().expect("banned members are initialized"),

Loading…
Cancel
Save