fractal/src/session/view/content/invite.rs

254 lines
8.6 KiB
Rust

use adw::subclass::prelude::*;
use gettextrs::gettext;
use gtk::{glib, glib::clone, prelude::*, CompositeTemplate};
use crate::{
components::{Avatar, LabelWithWidgets, Pill, SpinnerButton},
gettext_f,
session::model::{MemberList, Room, RoomType},
toast,
utils::message_dialog,
};
mod imp {
use std::{cell::RefCell, collections::HashSet};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/content/invite.ui")]
#[properties(wrapper_type = super::Invite)]
pub struct Invite {
/// The room currently displayed.
#[property(get, set = Self::set_room, explicit_notify, nullable)]
pub room: RefCell<Option<Room>>,
pub room_members: RefCell<Option<MemberList>>,
pub accept_requests: RefCell<HashSet<Room>>,
pub decline_requests: RefCell<HashSet<Room>>,
pub category_handler: RefCell<Option<glib::SignalHandlerId>>,
#[template_child]
pub room_topic: TemplateChild<gtk::Label>,
#[template_child]
pub inviter: TemplateChild<LabelWithWidgets>,
#[template_child]
pub accept_button: TemplateChild<SpinnerButton>,
#[template_child]
pub decline_button: TemplateChild<SpinnerButton>,
}
#[glib::object_subclass]
impl ObjectSubclass for Invite {
const NAME: &'static str = "ContentInvite";
type Type = super::Invite;
type ParentType = adw::Bin;
fn class_init(klass: &mut Self::Class) {
Pill::static_type();
Avatar::static_type();
Self::bind_template(klass);
klass.set_accessible_role(gtk::AccessibleRole::Group);
klass.install_action_async("invite.decline", None, move |widget, _, _| async move {
widget.decline().await;
});
klass.install_action_async("invite.accept", None, move |widget, _, _| async move {
widget.accept().await;
});
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
impl ObjectImpl for Invite {
fn constructed(&self) {
self.parent_constructed();
self.room_topic
.connect_notify_local(Some("label"), |room_topic, _| {
room_topic.set_visible(!room_topic.label().is_empty());
});
self.room_topic
.set_visible(!self.room_topic.label().is_empty());
// Translators: Do NOT translate the content between '{' and '}', this is a
// variable name.
self.inviter.set_label(Some(gettext_f(
"{user} invited you",
&[("user", "<widget>")],
)));
}
fn dispose(&self) {
if let Some(room) = self.room.take() {
if let Some(handler) = self.category_handler.take() {
room.disconnect(handler);
}
}
}
}
impl WidgetImpl for Invite {}
impl BinImpl for Invite {}
impl Invite {
/// Set the room currently displayed.
fn set_room(&self, room: Option<Room>) {
if *self.room.borrow() == room {
return;
}
let obj = self.obj();
match &room {
Some(room) if self.accept_requests.borrow().contains(room) => {
obj.action_set_enabled("invite.accept", false);
obj.action_set_enabled("invite.decline", false);
self.accept_button.set_loading(true);
}
Some(room) if self.decline_requests.borrow().contains(room) => {
obj.action_set_enabled("invite.accept", false);
obj.action_set_enabled("invite.decline", false);
self.decline_button.set_loading(true);
}
_ => obj.reset(),
}
if let Some(room) = self.room.take() {
if let Some(handler) = self.category_handler.take() {
room.disconnect(handler);
}
}
if let Some(room) = &room {
let category_handler = room.connect_category_notify(
clone!(@weak obj => move |room| {
let category = room.category();
if category == RoomType::Left {
// We declined the invite or the invite was retracted, we should close the room
// if it is opened.
let Some(session) = room.session() else {
return;
};
let selection = session.sidebar_list_model().selection_model();
if let Some(selected_room) = selection.selected_item().and_downcast::<Room>() {
if selected_room == *room {
selection.set_selected_item(None::<glib::Object>);
}
}
}
if category != RoomType::Invited {
let imp = obj.imp();
imp.decline_requests.borrow_mut().remove(room);
imp.accept_requests.borrow_mut().remove(room);
obj.reset();
if let Some(category_handler) = imp.category_handler.take() {
room.disconnect(category_handler);
}
}
}),
);
self.category_handler.replace(Some(category_handler));
}
// Keep a strong reference to the members list.
self.room_members
.replace(room.as_ref().map(|r| r.get_or_create_members()));
self.room.replace(room);
obj.notify_room();
}
}
}
glib::wrapper! {
/// A view presenting an invitation to a room.
pub struct Invite(ObjectSubclass<imp::Invite>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
}
impl Invite {
pub fn new() -> Self {
glib::Object::new()
}
fn reset(&self) {
let imp = self.imp();
imp.accept_button.set_loading(false);
imp.decline_button.set_loading(false);
self.action_set_enabled("invite.accept", true);
self.action_set_enabled("invite.decline", true);
}
/// Accept the invite.
async fn accept(&self) {
let Some(room) = self.room() else {
return;
};
let imp = self.imp();
self.action_set_enabled("invite.accept", false);
self.action_set_enabled("invite.decline", false);
imp.accept_button.set_loading(true);
imp.accept_requests.borrow_mut().insert(room.clone());
let result = room.accept_invite().await;
if result.is_err() {
toast!(
self,
gettext(
// Translators: Do NOT translate the content between '{' and '}', this
// is a variable name.
"Failed to accept invitation for {room}. Try again later.",
),
@room,
);
imp.accept_requests.borrow_mut().remove(&room);
self.reset();
}
}
/// Decline the invite.
async fn decline(&self) {
let Some(window) = self.root().and_downcast::<gtk::Window>() else {
return;
};
let Some(room) = self.room() else {
return;
};
let imp = self.imp();
if !message_dialog::confirm_leave_room(&room, &window).await {
return;
}
self.action_set_enabled("invite.accept", false);
self.action_set_enabled("invite.decline", false);
imp.decline_button.set_loading(true);
imp.decline_requests.borrow_mut().insert(room.clone());
let result = room.decline_invite().await;
if result.is_err() {
toast!(
self,
gettext(
// Translators: Do NOT translate the content between '{' and '}', this
// is a variable name.
"Failed to decline invitation for {room}. Try again later.",
),
@room,
);
imp.decline_requests.borrow_mut().remove(&room);
self.reset();
}
}
}