fractal/session/view/content/explore/
public_room_row.rs1use adw::{prelude::*, subclass::prelude::*};
2use gettextrs::gettext;
3use gtk::{glib, glib::clone, CompositeTemplate};
4
5use crate::{
6 components::{Avatar, LoadingButton},
7 gettext_f, ngettext_f,
8 prelude::*,
9 session::model::RemoteRoom,
10 toast,
11 utils::{matrix::MatrixIdUri, string::linkify},
12 Window,
13};
14
15mod imp {
16 use std::cell::RefCell;
17
18 use glib::subclass::InitializingObject;
19
20 use super::*;
21
22 #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
23 #[template(resource = "/org/gnome/Fractal/ui/session/view/content/explore/public_room_row.ui")]
24 #[properties(wrapper_type = super::PublicRoomRow)]
25 pub struct PublicRoomRow {
26 #[template_child]
27 avatar: TemplateChild<Avatar>,
28 #[template_child]
29 display_name: TemplateChild<gtk::Label>,
30 #[template_child]
31 description: TemplateChild<gtk::Label>,
32 #[template_child]
33 alias: TemplateChild<gtk::Label>,
34 #[template_child]
35 members_count: TemplateChild<gtk::Label>,
36 #[template_child]
37 members_count_box: TemplateChild<gtk::Box>,
38 #[template_child]
39 button: TemplateChild<LoadingButton>,
40 #[property(get, set= Self::set_room, explicit_notify)]
42 room: RefCell<Option<RemoteRoom>>,
43 room_list_info_handlers: RefCell<Vec<glib::SignalHandlerId>>,
44 }
45
46 #[glib::object_subclass]
47 impl ObjectSubclass for PublicRoomRow {
48 const NAME: &'static str = "PublicRoomRow";
49 type Type = super::PublicRoomRow;
50 type ParentType = adw::Bin;
51
52 fn class_init(klass: &mut Self::Class) {
53 Self::bind_template(klass);
54 Self::bind_template_callbacks(klass);
55 }
56
57 fn instance_init(obj: &InitializingObject<Self>) {
58 obj.init_template();
59 }
60 }
61
62 #[glib::derived_properties]
63 impl ObjectImpl for PublicRoomRow {
64 fn constructed(&self) {
65 self.parent_constructed();
66 let obj = self.obj();
67
68 self.description.connect_activate_link(clone!(
69 #[weak]
70 obj,
71 #[upgrade_or]
72 glib::Propagation::Proceed,
73 move |_, uri| {
74 if MatrixIdUri::parse(uri).is_ok() {
75 let _ =
76 obj.activate_action("session.show-matrix-uri", Some(&uri.to_variant()));
77 glib::Propagation::Stop
78 } else {
79 glib::Propagation::Proceed
80 }
81 }
82 ));
83 }
84
85 fn dispose(&self) {
86 self.disconnect_signals();
87 }
88 }
89
90 impl WidgetImpl for PublicRoomRow {}
91 impl BinImpl for PublicRoomRow {}
92
93 #[gtk::template_callbacks]
94 impl PublicRoomRow {
95 fn set_room(&self, room: RemoteRoom) {
97 if self.room.borrow().as_ref().is_some_and(|r| *r == room) {
98 return;
99 }
100
101 self.disconnect_signals();
102
103 let room_list_info = room.room_list_info();
104 let is_joining_handler = room_list_info.connect_is_joining_notify(clone!(
105 #[weak(rename_to = imp)]
106 self,
107 move |_| {
108 imp.update_button();
109 }
110 ));
111 let local_room_handler = room_list_info.connect_local_room_notify(clone!(
112 #[weak(rename_to = imp)]
113 self,
114 move |_| {
115 imp.update_button();
116 }
117 ));
118
119 self.room_list_info_handlers
120 .replace(vec![is_joining_handler, local_room_handler]);
121
122 self.room.replace(Some(room));
123
124 self.update_button();
125 self.update_row();
126 self.obj().notify_room();
127 }
128
129 fn update_row(&self) {
131 let Some(room) = self.room.borrow().clone() else {
132 return;
133 };
134
135 self.avatar.set_data(Some(room.avatar_data()));
136 self.display_name.set_text(&room.display_name());
137
138 if let Some(topic) = room.topic() {
139 let mut t = linkify(&topic);
141 t.truncate_end_whitespaces();
143
144 self.description.set_label(&t);
145 self.description.set_visible(!t.is_empty());
146 } else {
147 self.description.set_visible(false);
148 }
149
150 let canonical_alias = room.canonical_alias();
151 if let Some(alias) = &canonical_alias {
152 self.alias.set_text(alias.as_str());
153 }
154 self.alias.set_visible(canonical_alias.is_some());
155
156 let members_count = room.joined_members_count();
157 self.members_count.set_text(&members_count.to_string());
158 let members_count_tooltip = ngettext_f(
159 "1 member",
162 "{n} members",
163 members_count,
164 &[("n", &members_count.to_string())],
165 );
166 self.members_count_box
167 .set_tooltip_text(Some(&members_count_tooltip));
168 }
169
170 fn update_button(&self) {
172 let Some(room) = self.room.borrow().clone() else {
173 return;
174 };
175
176 let room_list_info = room.room_list_info();
177 let room_name = room.display_name();
178
179 let (label, accessible_desc) = if room_list_info.local_room().is_some() {
180 (
181 gettext("View"),
183 gettext_f("View {room_name}", &[("room_name", &room_name)]),
184 )
185 } else {
186 (
187 gettext("Join"),
188 gettext_f("Join {room_name}", &[("room_name", &room_name)]),
189 )
190 };
191
192 self.button.set_content_label(label);
193 self.button
194 .update_property(&[gtk::accessible::Property::Description(&accessible_desc)]);
195
196 self.button.set_is_loading(room_list_info.is_joining());
197 }
198
199 #[template_callback]
201 async fn join_or_view(&self) {
202 let Some(room) = self.room.borrow().clone() else {
203 return;
204 };
205
206 let obj = self.obj();
207
208 if let Some(local_room) = room.room_list_info().local_room() {
209 if let Some(window) = obj.root().and_downcast::<Window>() {
210 window.session_view().select_room(local_room);
211 }
212 } else {
213 let Some(session) = room.session() else {
214 return;
215 };
216
217 let uri = room.uri();
218
219 if let Err(error) = session
220 .room_list()
221 .join_by_id_or_alias(uri.id.clone(), uri.via.clone())
222 .await
223 {
224 toast!(obj, error);
225 }
226 }
227 }
228
229 fn disconnect_signals(&self) {
231 if let Some(room) = self.room.borrow().as_ref() {
232 let room_list_info = room.room_list_info();
233 for handler in self.room_list_info_handlers.take() {
234 room_list_info.disconnect(handler);
235 }
236 }
237 }
238 }
239}
240
241glib::wrapper! {
242 pub struct PublicRoomRow(ObjectSubclass<imp::PublicRoomRow>)
244 @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
245}
246
247impl PublicRoomRow {
248 pub fn new() -> Self {
249 glib::Object::new()
250 }
251}
252
253impl Default for PublicRoomRow {
254 fn default() -> Self {
255 Self::new()
256 }
257}