264 lines
7.8 KiB
Rust
264 lines
7.8 KiB
Rust
use adw::{prelude::*, subclass::prelude::*};
|
|
use gettextrs::gettext;
|
|
use gtk::{glib, glib::clone, CompositeTemplate};
|
|
|
|
use crate::{
|
|
components::{Avatar, SpinnerButton},
|
|
prelude::*,
|
|
session::model::User,
|
|
spawn, toast,
|
|
utils::BoundObject,
|
|
Window,
|
|
};
|
|
|
|
mod imp {
|
|
use std::cell::RefCell;
|
|
|
|
use glib::subclass::InitializingObject;
|
|
|
|
use super::*;
|
|
|
|
#[derive(Debug, Default, CompositeTemplate)]
|
|
#[template(resource = "/org/gnome/Fractal/ui/session/view/user_page.ui")]
|
|
pub struct UserPage {
|
|
#[template_child]
|
|
pub avatar: TemplateChild<Avatar>,
|
|
#[template_child]
|
|
pub direct_chat_button: TemplateChild<SpinnerButton>,
|
|
#[template_child]
|
|
pub verified_row: TemplateChild<adw::ActionRow>,
|
|
#[template_child]
|
|
pub verified_stack: TemplateChild<gtk::Stack>,
|
|
#[template_child]
|
|
pub verify_button: TemplateChild<SpinnerButton>,
|
|
/// The current user.
|
|
pub user: BoundObject<User>,
|
|
pub bindings: RefCell<Vec<glib::Binding>>,
|
|
}
|
|
|
|
#[glib::object_subclass]
|
|
impl ObjectSubclass for UserPage {
|
|
const NAME: &'static str = "UserPage";
|
|
type Type = super::UserPage;
|
|
type ParentType = adw::NavigationPage;
|
|
|
|
fn class_init(klass: &mut Self::Class) {
|
|
Self::bind_template(klass);
|
|
|
|
klass.install_action_async(
|
|
"user-page.open-direct-chat",
|
|
None,
|
|
|widget, _, _| async move {
|
|
widget.open_direct_chat().await;
|
|
},
|
|
);
|
|
|
|
klass.install_action_async("user-page.verify-user", None, |widget, _, _| async move {
|
|
widget.verify_user().await;
|
|
});
|
|
}
|
|
|
|
fn instance_init(obj: &InitializingObject<Self>) {
|
|
obj.init_template();
|
|
}
|
|
}
|
|
|
|
impl ObjectImpl for UserPage {
|
|
fn properties() -> &'static [glib::ParamSpec] {
|
|
use once_cell::sync::Lazy;
|
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
|
vec![glib::ParamSpecObject::builder::<User>("user")
|
|
.construct_only()
|
|
.build()]
|
|
});
|
|
|
|
PROPERTIES.as_ref()
|
|
}
|
|
|
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
|
match pspec.name() {
|
|
"user" => self.obj().set_user(value.get().unwrap()),
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
|
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
|
match pspec.name() {
|
|
"user" => self.obj().user().to_value(),
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
|
|
fn dispose(&self) {
|
|
for binding in self.bindings.take() {
|
|
binding.unbind();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WidgetImpl for UserPage {}
|
|
impl NavigationPageImpl for UserPage {}
|
|
}
|
|
|
|
glib::wrapper! {
|
|
/// Page to view details about a user.
|
|
pub struct UserPage(ObjectSubclass<imp::UserPage>)
|
|
@extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible;
|
|
}
|
|
|
|
impl UserPage {
|
|
/// Construct a new `UserPage` for the given user.
|
|
pub fn new(user: &impl IsA<User>) -> Self {
|
|
glib::Object::builder().property("user", user).build()
|
|
}
|
|
|
|
/// The current user.
|
|
pub fn user(&self) -> Option<User> {
|
|
self.imp().user.obj()
|
|
}
|
|
|
|
/// Set the current user.
|
|
fn set_user(&self, user: Option<User>) {
|
|
let Some(user) = user else {
|
|
// Ignore missing user.
|
|
return;
|
|
};
|
|
let imp = self.imp();
|
|
|
|
let title_binding = user
|
|
.bind_property("display-name", self, "title")
|
|
.sync_create()
|
|
.build();
|
|
let avatar_binding = user
|
|
.bind_property("avatar-data", &*imp.avatar, "data")
|
|
.sync_create()
|
|
.build();
|
|
imp.bindings.replace(vec![title_binding, avatar_binding]);
|
|
|
|
let is_verified_handler = user.connect_notify_local(
|
|
Some("is-verified"),
|
|
clone!(@weak self as obj => move |_, _| {
|
|
obj.update_verified();
|
|
}),
|
|
);
|
|
|
|
// We don't need to listen to changes of the property, it never changes after
|
|
// construction.
|
|
imp.direct_chat_button.set_visible(!user.is_own_user());
|
|
|
|
imp.user.set(user, vec![is_verified_handler]);
|
|
|
|
spawn!(clone!(@weak self as obj => async move {
|
|
obj.load_direct_chat().await;
|
|
}));
|
|
self.update_verified();
|
|
}
|
|
|
|
/// Load whether the current user has a direct chat or not.
|
|
async fn load_direct_chat(&self) {
|
|
self.set_direct_chat_loading(true);
|
|
|
|
let Some(user) = self.user() else {
|
|
return;
|
|
};
|
|
|
|
let direct_chat = user.direct_chat().await;
|
|
|
|
let label = if direct_chat.is_some() {
|
|
gettext("Open Direct Chat")
|
|
} else {
|
|
gettext("Create Direct Chat")
|
|
};
|
|
self.imp().direct_chat_button.set_label(label);
|
|
|
|
self.set_direct_chat_loading(false);
|
|
}
|
|
|
|
/// Set whether the direct chat button is loading.
|
|
fn set_direct_chat_loading(&self, loading: bool) {
|
|
self.action_set_enabled("user-page.open-direct-chat", !loading);
|
|
self.imp().direct_chat_button.set_loading(loading);
|
|
}
|
|
|
|
/// Open a direct chat with the current user.
|
|
///
|
|
/// If one doesn't exist already, it is created.
|
|
async fn open_direct_chat(&self) {
|
|
let Some(user) = self.user() else {
|
|
return;
|
|
};
|
|
|
|
self.set_direct_chat_loading(true);
|
|
|
|
let Ok(room) = user.get_or_create_direct_chat().await else {
|
|
toast!(self, &gettext("Failed to create a new Direct Chat"));
|
|
self.set_direct_chat_loading(false);
|
|
|
|
return;
|
|
};
|
|
|
|
let Some(parent_window) = self.root().and_downcast::<gtk::Window>() else {
|
|
return;
|
|
};
|
|
|
|
if let Some(main_window) = parent_window.transient_for().and_downcast::<Window>() {
|
|
main_window.show_room(user.session().session_id(), room.room_id());
|
|
}
|
|
|
|
parent_window.close();
|
|
}
|
|
|
|
/// Update the verified row.
|
|
fn update_verified(&self) {
|
|
let Some(user) = self.user() else {
|
|
return;
|
|
};
|
|
let imp = self.imp();
|
|
|
|
if user.verified() {
|
|
imp.verified_row.set_title(&gettext("Identity verified"));
|
|
imp.verified_stack.set_visible_child_name("icon");
|
|
self.action_set_enabled("user-page.verify-user", false);
|
|
} else {
|
|
self.action_set_enabled("user-page.verify-user", true);
|
|
imp.verified_stack.set_visible_child_name("button");
|
|
imp.verified_row
|
|
.set_title(&gettext("Identity not verified"));
|
|
}
|
|
}
|
|
|
|
/// Launch the verification for the current user.
|
|
async fn verify_user(&self) {
|
|
let Some(user) = self.user() else {
|
|
return;
|
|
};
|
|
let imp = self.imp();
|
|
|
|
self.action_set_enabled("user-page.verify-user", false);
|
|
imp.verify_button.set_loading(true);
|
|
let verification = user.verify_identity().await;
|
|
|
|
let Some(flow_id) = verification.flow_id() else {
|
|
toast!(self, gettext("Failed to start user verification"));
|
|
self.action_set_enabled("user-page.verify-user", true);
|
|
imp.verify_button.set_loading(false);
|
|
|
|
return;
|
|
};
|
|
|
|
let Some(parent_window) = self.root().and_downcast::<gtk::Window>() else {
|
|
return;
|
|
};
|
|
|
|
if let Some(main_window) = parent_window.transient_for().and_downcast::<Window>() {
|
|
main_window.show_verification(
|
|
user.session().session_id(),
|
|
&UserExt::user_id(&user),
|
|
flow_id,
|
|
);
|
|
}
|
|
|
|
parent_window.close();
|
|
}
|
|
}
|