Skip to content

Commit 4ed695a

Browse files
committed
chat-info-members: Put members list in a separate widget
1 parent 2783c97 commit 4ed695a

3 files changed

Lines changed: 282 additions & 163 deletions

File tree

data/resources/ui/content-chat-info-window.ui

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,25 +85,15 @@
8585
<property name="name">members</property>
8686
<property name="title" translatable="yes">Members</property>
8787
<property name="icon-name">avatar-default-symbolic</property>
88-
<property name="visible">false</property>
88+
<property name="visible">False</property>
89+
<binding name="visible">
90+
<lookup name="visible">members_list</lookup>
91+
</binding>
8992
<property name="child">
90-
<object class="GtkScrolledWindow">
91-
<property name="child">
92-
<object class="AdwClampScrollable">
93-
<property name="maximum-size">440</property>
94-
<property name="tightening-threshold">200</property>
95-
<property name="child">
96-
<object class="GtkListView" id="members_list">
97-
<property name="model">
98-
<object class="GtkNoSelection" id="selection"/>
99-
</property>
100-
<style>
101-
<class name="navigation-sidebar"/>
102-
</style>
103-
</object>
104-
</property>
105-
</object>
106-
</property>
93+
<object class="ContentChatInfoMembers" id="members_list">
94+
<binding name="chat">
95+
<lookup name="chat">ContentChatInfoWindow</lookup>
96+
</binding>
10797
</object>
10898
</property>
10999
</object>
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
use super::member_row::MemberRow;
2+
3+
use adw::prelude::*;
4+
use adw::subclass::prelude::*;
5+
use glib::clone;
6+
use gtk::{glib, CompositeTemplate};
7+
use tdlib::enums::BasicGroupFullInfo::BasicGroupFullInfo as TdBasicGroupFullInfo;
8+
use tdlib::enums::ChatMembers::ChatMembers as TdChatMembers;
9+
use tdlib::enums::MessageSender;
10+
use tdlib::enums::SupergroupFullInfo::SupergroupFullInfo as TdSupergroupFullInfo;
11+
use tdlib::enums::User::User as TdUser;
12+
use tdlib::functions;
13+
use tdlib::types::{ChatMember as TdChatMember, ChatMembers};
14+
15+
use crate::tdlib::{BasicGroup, Chat, ChatMember, ChatType, Supergroup, User};
16+
use crate::utils::spawn;
17+
18+
mod imp {
19+
use super::*;
20+
use once_cell::sync::{Lazy, OnceCell};
21+
use std::cell::Cell;
22+
23+
#[derive(Debug, Default, CompositeTemplate)]
24+
#[template(string = r#"
25+
<interface>
26+
<template class="ContentChatInfoMembers" parent="AdwBin">
27+
<property name="child">
28+
<object class="GtkScrolledWindow">
29+
<property name="child">
30+
<object class="AdwClampScrollable">
31+
<property name="maximum-size">440</property>
32+
<property name="tightening-threshold">200</property>
33+
<property name="child">
34+
<object class="GtkListView" id="members_list">
35+
<property name="model">
36+
<object class="GtkNoSelection" id="selection"/>
37+
</property>
38+
<style>
39+
<class name="navigation-sidebar"/>
40+
</style>
41+
</object>
42+
</property>
43+
</object>
44+
</property>
45+
</object>
46+
</property>
47+
</template>
48+
</interface>
49+
"#)]
50+
pub(crate) struct ChatInfoMembers {
51+
pub(super) loading: Cell<bool>,
52+
pub(super) chat: OnceCell<Chat>,
53+
#[template_child]
54+
pub(super) members_list: TemplateChild<gtk::ListView>,
55+
}
56+
57+
#[glib::object_subclass]
58+
impl ObjectSubclass for ChatInfoMembers {
59+
const NAME: &'static str = "ContentChatInfoMembers";
60+
type Type = super::ChatInfoMembers;
61+
type ParentType = adw::Bin;
62+
63+
fn class_init(klass: &mut Self::Class) {
64+
klass.bind_template();
65+
}
66+
67+
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
68+
obj.init_template();
69+
}
70+
}
71+
72+
impl ObjectImpl for ChatInfoMembers {
73+
fn properties() -> &'static [glib::ParamSpec] {
74+
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
75+
vec![glib::ParamSpecObject::builder::<Chat>("chat")
76+
// .construct_only()
77+
.build()]
78+
});
79+
PROPERTIES.as_ref()
80+
}
81+
82+
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
83+
match pspec.name() {
84+
"chat" => {
85+
if let Some(chat) = value.get().unwrap() {
86+
self.chat.set(chat).unwrap();
87+
self.obj().setup_page();
88+
}
89+
}
90+
_ => unimplemented!(),
91+
}
92+
}
93+
94+
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
95+
let obj = self.obj();
96+
97+
match pspec.name() {
98+
"chat" => obj.chat().to_value(),
99+
_ => unimplemented!(),
100+
}
101+
}
102+
}
103+
104+
impl WidgetImpl for ChatInfoMembers {}
105+
impl BinImpl for ChatInfoMembers {}
106+
}
107+
108+
glib::wrapper! {
109+
pub(crate) struct ChatInfoMembers(ObjectSubclass<imp::ChatInfoMembers>)
110+
@extends gtk::Widget;
111+
}
112+
113+
impl ChatInfoMembers {
114+
fn setup_page(&self) {
115+
match self.chat().unwrap().type_() {
116+
ChatType::BasicGroup(basic_group) => {
117+
self.setup_basic_group_info(basic_group);
118+
}
119+
ChatType::Supergroup(supergroup) => {
120+
self.setup_supergroup_info(supergroup);
121+
}
122+
_ => {
123+
self.set_visible(false);
124+
}
125+
}
126+
}
127+
128+
fn setup_basic_group_info(&self, basic_group: &BasicGroup) {
129+
let client_id = self.chat().unwrap().session().client_id();
130+
let basic_group_id = basic_group.id();
131+
132+
spawn(clone!(@weak self as obj => async move {
133+
let result = functions::get_basic_group_full_info(basic_group_id, client_id).await;
134+
if let Ok(TdBasicGroupFullInfo(full_info)) = result {
135+
obj.append_members(full_info.members).await;
136+
}
137+
}));
138+
}
139+
140+
fn setup_supergroup_info(&self, supergroup: &Supergroup) {
141+
let client_id = self.chat().unwrap().session().client_id();
142+
let supergroup_id = supergroup.id();
143+
144+
spawn(clone!(@weak self as obj => async move {
145+
let imp = obj.imp();
146+
let result = functions::get_supergroup_full_info(supergroup_id, client_id).await;
147+
if let Ok(TdSupergroupFullInfo(full_info)) = result {
148+
if full_info.can_get_members {
149+
imp.loading.set(true);
150+
let result = functions::get_supergroup_members(
151+
supergroup_id,
152+
None,
153+
0,
154+
200,
155+
client_id,
156+
).await;
157+
if let Ok(TdChatMembers(ChatMembers {members, total_count})) = result {
158+
obj.append_members(members).await;
159+
160+
if total_count > 200 {
161+
obj.imp().members_list.vadjustment().unwrap()
162+
.connect_changed(clone!(@weak obj => move |adj| {
163+
obj.load_more_members(adj, supergroup_id);
164+
}));
165+
}
166+
}
167+
imp.loading.set(false);
168+
}
169+
}
170+
}));
171+
}
172+
173+
fn load_more_members(&self, adj: &gtk::Adjustment, supergroup_id: i64) {
174+
let imp = self.imp();
175+
if imp.loading.get() {
176+
return;
177+
}
178+
imp.loading.set(true);
179+
180+
if adj.value() > adj.page_size() * 2.0 || adj.upper() >= adj.page_size() * 2.0 {
181+
let offset = imp.members_list.model().unwrap().n_items() as i32;
182+
let limit = 200;
183+
let client_id = self.chat().unwrap().session().client_id();
184+
185+
spawn(clone!(@weak self as obj => async move {
186+
let result = functions::get_supergroup_members(
187+
supergroup_id,
188+
None,
189+
offset,
190+
limit,
191+
client_id,
192+
).await;
193+
if let Ok(TdChatMembers(ChatMembers {members, ..})) = result {
194+
obj.append_members(members).await;
195+
obj.imp().loading.set(false);
196+
} else {
197+
log::error!("can't load members {result:?}");
198+
}
199+
}));
200+
}
201+
}
202+
203+
async fn append_members(&self, members: Vec<TdChatMember>) {
204+
let members: Vec<_> = {
205+
let mut users: Vec<User> = vec![];
206+
207+
let session = self.chat().unwrap().session();
208+
let client_id = session.client_id();
209+
210+
for member in &members {
211+
let user = match member.member_id {
212+
MessageSender::User(ref user) => {
213+
let TdUser(user) =
214+
functions::get_user(user.user_id, client_id).await.unwrap();
215+
User::from_td_object(user, &session)
216+
}
217+
MessageSender::Chat(_) => unreachable!(),
218+
};
219+
users.push(user);
220+
}
221+
222+
members
223+
.into_iter()
224+
.zip(users.into_iter())
225+
.map(|(member, user)| ChatMember::new(member, user))
226+
.collect()
227+
};
228+
229+
let members_list = &self.imp().members_list;
230+
231+
let selection_model: gtk::NoSelection = members_list.model().unwrap().downcast().unwrap();
232+
233+
let model: gtk::gio::ListStore = if let Some(model) = selection_model.model() {
234+
model.downcast().unwrap()
235+
} else {
236+
let model = gtk::gio::ListStore::new(ChatMember::static_type());
237+
selection_model.set_model(Some(&model));
238+
model
239+
};
240+
241+
model.extend_from_slice(&members);
242+
243+
if members_list.factory().is_none() {
244+
let factory = gtk::SignalListItemFactory::new();
245+
246+
factory.connect_setup(move |_, list_item| {
247+
list_item.set_property("child", MemberRow::new());
248+
});
249+
250+
factory.connect_bind(move |_, list_item| {
251+
let list_item: &gtk::ListItem = list_item.downcast_ref().unwrap();
252+
253+
let user_row: MemberRow = list_item.child().unwrap().downcast().unwrap();
254+
let member: ChatMember = list_item.item().unwrap().downcast().unwrap();
255+
256+
user_row.bind_member(member);
257+
});
258+
259+
members_list.set_factory(Some(&factory));
260+
}
261+
}
262+
263+
pub(crate) fn chat(&self) -> Option<&Chat> {
264+
self.imp().chat.get()
265+
}
266+
}

0 commit comments

Comments
 (0)