use adw::{prelude::BinExt, subclass::prelude::BinImpl}; use gettextrs::gettext; use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate}; use super::PublicRoom; use crate::{ components::{Avatar, Spinner, SpinnerButton}, spawn, toast, Window, }; mod imp { use std::cell::RefCell; use glib::{signal::SignalHandlerId, subclass::InitializingObject}; use once_cell::sync::Lazy; use super::*; #[derive(Debug, Default, CompositeTemplate)] #[template(resource = "/org/gnome/Fractal/ui/session/view/content/explore/public_room_row.ui")] pub struct PublicRoomRow { pub public_room: RefCell>, #[template_child] pub avatar: TemplateChild, #[template_child] pub display_name: TemplateChild, #[template_child] pub description: TemplateChild, #[template_child] pub alias: TemplateChild, #[template_child] pub members_count: TemplateChild, #[template_child] pub button: TemplateChild, pub original_child: RefCell>, pub pending_handler: RefCell>, pub room_handler: RefCell>, } #[glib::object_subclass] impl ObjectSubclass for PublicRoomRow { const NAME: &'static str = "ContentPublicRoomRow"; type Type = super::PublicRoomRow; type ParentType = adw::Bin; fn class_init(klass: &mut Self::Class) { Self::bind_template(klass); } fn instance_init(obj: &InitializingObject) { obj.init_template(); } } impl ObjectImpl for PublicRoomRow { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![glib::ParamSpecObject::builder::("public-room") .explicit_notify() .build()] }); PROPERTIES.as_ref() } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { match pspec.name() { "public-room" => self.obj().set_public_room(value.get().unwrap()), _ => unimplemented!(), } } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { match pspec.name() { "public-room" => self.obj().public_room().to_value(), _ => unimplemented!(), } } fn constructed(&self) { self.parent_constructed(); self.button .connect_clicked(clone!(@weak self as imp => move |_| { imp.obj().join_or_view(); })); } fn dispose(&self) { if let Some(ref old_public_room) = self.obj().public_room() { if let Some(handler) = self.pending_handler.take() { old_public_room.disconnect(handler); } if let Some(handler_id) = self.room_handler.take() { old_public_room.disconnect(handler_id); } } } } impl WidgetImpl for PublicRoomRow {} impl BinImpl for PublicRoomRow {} } glib::wrapper! { pub struct PublicRoomRow(ObjectSubclass) @extends gtk::Widget, adw::Bin, @implements gtk::Accessible; } impl PublicRoomRow { pub fn new() -> Self { glib::Object::new() } /// The public room displayed by this row. pub fn public_room(&self) -> Option { self.imp().public_room.borrow().clone() } /// Set the public room displayed by this row. pub fn set_public_room(&self, public_room: Option) { let imp = self.imp(); let old_public_room = self.public_room(); if old_public_room == public_room { return; } if let Some(ref old_public_room) = old_public_room { if let Some(handler) = imp.room_handler.take() { old_public_room.disconnect(handler); } if let Some(handler) = imp.pending_handler.take() { old_public_room.disconnect(handler); } } if let Some(ref public_room) = public_room { if let Some(child) = imp.original_child.take() { self.set_child(Some(&child)); } if let Some(matrix_public_room) = public_room.matrix_public_room() { imp.avatar .set_data(Some(public_room.avatar_data().clone().upcast())); let display_name = matrix_public_room .name .as_deref() .map(AsRef::as_ref) // FIXME: display some other identification for this room .unwrap_or("Room without name"); imp.display_name.set_text(display_name); let has_topic = if let Some(ref topic) = matrix_public_room.topic { imp.description.set_text(topic); true } else { false }; imp.description.set_visible(has_topic); let has_alias = if let Some(ref alias) = matrix_public_room.canonical_alias { imp.alias.set_text(alias.as_str()); true } else { false }; imp.alias.set_visible(has_alias); imp.members_count .set_text(&matrix_public_room.num_joined_members.to_string()); let pending_handler = public_room.connect_notify_local( Some("pending"), clone!(@weak self as obj => move |public_room, _| { obj.update_button(public_room); }), ); imp.pending_handler.replace(Some(pending_handler)); let room_handler = public_room.connect_notify_local( Some("room"), clone!(@weak self as obj => move |public_room, _| { obj.update_button(public_room); }), ); imp.room_handler.replace(Some(room_handler)); self.update_button(public_room); } else if imp.original_child.borrow().is_none() { let spinner = Spinner::default(); spinner.set_margin_top(12); spinner.set_margin_bottom(12); imp.original_child.replace(self.child()); self.set_child(Some(&spinner)); } } imp.avatar .set_data(public_room.clone().map(|room| room.avatar_data().clone())); imp.public_room.replace(public_room); self.notify("public-room"); } fn update_button(&self, public_room: &PublicRoom) { let button = &self.imp().button; if public_room.room().is_some() { button.set_label(&gettext("View")); } else { button.set_label(&gettext("Join")); } button.set_loading(public_room.is_pending()); } /// Join or view the public room. pub fn join_or_view(&self) { let Some(public_room) = self.public_room() else { return; }; let room_list = public_room.room_list(); if let Some(room) = public_room.room() { if let Some(window) = self.root().and_downcast::() { let session = room_list.session(); window.show_room(session.session_id(), room.room_id()); } } else if let Some(matrix_public_room) = public_room.matrix_public_room() { let room_id = matrix_public_room.room_id.clone(); spawn!(clone!(@weak self as obj, @weak room_list => async move { if let Err(error) = room_list.join_by_id_or_alias(room_id.into(), vec![]).await { toast!(obj, error); } })); } } }