Skip to content

Commit 4084625

Browse files
committed
chat-info-members: Put members list in a separate widget
also move MemberRow inside chat_info_window module
1 parent c8c4f28 commit 4084625

File tree

7 files changed

+304
-180
lines changed

7 files changed

+304
-180
lines changed

data/resources/resources.gresource.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
<file compressed="true" preprocess="xml-stripblanks">ui/add-account-row.ui</file>
1414
<file compressed="true" preprocess="xml-stripblanks">ui/avatar-with-selection.ui</file>
1515
<file compressed="true" preprocess="xml-stripblanks">ui/components-avatar.ui</file>
16-
<file compressed="true" preprocess="xml-stripblanks">ui/components-chat-member-row.ui</file>
1716
<file compressed="true" preprocess="xml-stripblanks">ui/components-message-entry.ui</file>
1817
<file compressed="true" preprocess="xml-stripblanks">ui/content.ui</file>
1918
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-action-bar.ui</file>
2019
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-history.ui</file>
20+
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-info-member-row.ui</file>
2121
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-info-window.ui</file>
2222
<file compressed="true" preprocess="xml-stripblanks">ui/content-event-row.ui</file>
2323
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-document.ui</file>

data/resources/ui/components-chat-member-row.ui renamed to data/resources/ui/content-chat-info-member-row.ui

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<interface>
3-
<template class="ComponentsChatMemberRow" parent="GtkBox">
4-
<property name="spacing">12</property>
3+
<template class="ContentChatInfoMemberRow" parent="GtkWidget">
4+
<property name="layout-manager">
5+
<object class="GtkBoxLayout">
6+
<property name="spacing">12</property>
7+
</object>
8+
</property>
59
<child>
610
<object class="ComponentsAvatar" id="avatar">
711
<property name="size">40</property>

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>

src/session/components/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
mod avatar;
2-
mod chat_member_row;
32
mod message_entry;
43
mod snow;
54

65
pub(crate) use self::avatar::Avatar;
7-
pub(crate) use self::chat_member_row::ChatMemberRow;
86
pub(crate) use self::message_entry::MessageEntry;
97
pub(crate) use self::snow::Snow;

src/session/components/chat_member_row.rs renamed to src/session/content/chat_info_window/member_row.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ use gtk::prelude::*;
44
use gtk::subclass::prelude::*;
55
use gtk::{glib, CompositeTemplate};
66

7-
use super::Avatar;
7+
use crate::session::components::Avatar;
88
use crate::{expressions, strings};
99
use tdlib::enums::{UserStatus, UserType};
1010

1111
mod imp {
1212
use super::*;
1313
#[derive(Debug, Default, CompositeTemplate)]
14-
#[template(resource = "/com/github/melix99/telegrand/ui/components-chat-member-row.ui")]
15-
pub(crate) struct ChatMemberRow {
14+
#[template(resource = "/com/github/melix99/telegrand/ui/content-chat-info-member-row.ui")]
15+
pub(crate) struct MemberRow {
1616
#[template_child]
1717
pub(super) avatar: TemplateChild<Avatar>,
1818
#[template_child]
@@ -24,10 +24,10 @@ mod imp {
2424
}
2525

2626
#[glib::object_subclass]
27-
impl ObjectSubclass for ChatMemberRow {
28-
const NAME: &'static str = "ComponentsChatMemberRow";
29-
type Type = super::ChatMemberRow;
30-
type ParentType = gtk::Box;
27+
impl ObjectSubclass for MemberRow {
28+
const NAME: &'static str = "ContentChatInfoMemberRow";
29+
type Type = super::MemberRow;
30+
type ParentType = gtk::Widget;
3131

3232
fn class_init(klass: &mut Self::Class) {
3333
Self::bind_template(klass);
@@ -39,19 +39,19 @@ mod imp {
3939
}
4040
}
4141

42-
impl ObjectImpl for ChatMemberRow {}
42+
impl ObjectImpl for MemberRow {}
4343

44-
impl WidgetImpl for ChatMemberRow {}
44+
impl WidgetImpl for MemberRow {}
4545

46-
impl BoxImpl for ChatMemberRow {}
46+
impl BoxImpl for MemberRow {}
4747
}
4848

4949
glib::wrapper! {
50-
pub(crate) struct ChatMemberRow(ObjectSubclass<imp::ChatMemberRow>)
50+
pub(crate) struct MemberRow(ObjectSubclass<imp::MemberRow>)
5151
@extends gtk::Widget;
5252
}
5353

54-
impl ChatMemberRow {
54+
impl MemberRow {
5555
pub fn new() -> Self {
5656
glib::Object::new(&[])
5757
}
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)