You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
375 lines
13 KiB
375 lines
13 KiB
use adw::{prelude::*, subclass::prelude::*}; |
|
use gtk::{ |
|
glib, |
|
glib::{clone, closure_local}, |
|
}; |
|
use matrix_sdk::authentication::oauth::{ |
|
AccountManagementUrlBuilder, OAuthError, error::OAuthDiscoveryError, |
|
}; |
|
use tracing::{error, warn}; |
|
|
|
mod encryption_page; |
|
mod general_page; |
|
mod notifications_page; |
|
mod safety_page; |
|
mod user_session; |
|
|
|
use self::{ |
|
encryption_page::{EncryptionPage, ImportExportKeysSubpage, ImportExportKeysSubpageMode}, |
|
general_page::{ChangePasswordSubpage, DeactivateAccountSubpage, GeneralPage, LogOutSubpage}, |
|
notifications_page::NotificationsPage, |
|
safety_page::{IgnoredUsersSubpage, SafetyPage}, |
|
user_session::{UserSessionListSubpage, UserSessionSubpage}, |
|
}; |
|
use crate::{ |
|
components::crypto::{CryptoIdentitySetupView, CryptoRecoverySetupView}, |
|
session::Session, |
|
spawn, spawn_tokio, |
|
utils::BoundObjectWeakRef, |
|
}; |
|
|
|
/// A subpage of the account settings. |
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, glib::Variant)] |
|
pub(crate) enum AccountSettingsSubpage { |
|
/// A form to change the account's password. |
|
ChangePassword, |
|
/// A page to view the list of account's sessions. |
|
UserSessionList, |
|
/// A page to confirm the logout. |
|
LogOut, |
|
/// A page to confirm the deactivation of the password. |
|
DeactivateAccount, |
|
/// The list of ignored users. |
|
IgnoredUsers, |
|
/// A form to import encryption keys. |
|
ImportKeys, |
|
/// A form to export encryption keys. |
|
ExportKeys, |
|
/// The crypto identity setup view. |
|
CryptoIdentitySetup, |
|
/// The recovery setup view. |
|
RecoverySetup, |
|
} |
|
|
|
mod imp { |
|
use std::{cell::RefCell, sync::LazyLock}; |
|
|
|
use glib::subclass::{InitializingObject, Signal}; |
|
|
|
use super::*; |
|
|
|
#[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)] |
|
#[template(resource = "/org/gnome/Fractal/ui/account_settings/mod.ui")] |
|
#[properties(wrapper_type = super::AccountSettings)] |
|
pub struct AccountSettings { |
|
/// The current session. |
|
#[property(get, set = Self::set_session, nullable)] |
|
session: BoundObjectWeakRef<Session>, |
|
/// The builder for the account management URL of the OAuth 2.0 |
|
/// authorization server, if any. |
|
account_management_url_builder: RefCell<Option<AccountManagementUrlBuilder>>, |
|
} |
|
|
|
#[glib::object_subclass] |
|
impl ObjectSubclass for AccountSettings { |
|
const NAME: &'static str = "AccountSettings"; |
|
type Type = super::AccountSettings; |
|
type ParentType = adw::PreferencesDialog; |
|
|
|
fn class_init(klass: &mut Self::Class) { |
|
GeneralPage::ensure_type(); |
|
NotificationsPage::ensure_type(); |
|
SafetyPage::ensure_type(); |
|
EncryptionPage::ensure_type(); |
|
|
|
Self::bind_template(klass); |
|
|
|
klass.install_action( |
|
"account-settings.show-subpage", |
|
Some(&AccountSettingsSubpage::static_variant_type()), |
|
|obj, _, param| { |
|
let subpage = param |
|
.and_then(glib::Variant::get::<AccountSettingsSubpage>) |
|
.expect("The parameter should be a valid subpage name"); |
|
|
|
obj.show_subpage(subpage); |
|
}, |
|
); |
|
|
|
klass.install_action( |
|
"account-settings.show-session-subpage", |
|
Some(&String::static_variant_type()), |
|
|obj, _, param| { |
|
obj.show_session_subpage( |
|
¶m |
|
.and_then(glib::Variant::get::<String>) |
|
.expect("The parameter should be a string"), |
|
); |
|
}, |
|
); |
|
|
|
klass.install_action_async( |
|
"account-settings.reload-user-sessions", |
|
None, |
|
|obj, _, _| async move { |
|
obj.imp().reload_user_sessions().await; |
|
}, |
|
); |
|
|
|
klass.install_action("account-settings.close", None, |obj, _, _| { |
|
obj.close(); |
|
}); |
|
|
|
klass.install_action("account-settings.close-subpage", None, |obj, _, _| { |
|
obj.pop_subpage(); |
|
}); |
|
} |
|
|
|
fn instance_init(obj: &InitializingObject<Self>) { |
|
obj.init_template(); |
|
} |
|
} |
|
|
|
#[glib::derived_properties] |
|
impl ObjectImpl for AccountSettings { |
|
fn signals() -> &'static [Signal] { |
|
static SIGNALS: LazyLock<Vec<Signal>> = LazyLock::new(|| { |
|
vec![Signal::builder("account-management-url-builder-changed").build()] |
|
}); |
|
SIGNALS.as_ref() |
|
} |
|
} |
|
|
|
impl WidgetImpl for AccountSettings {} |
|
impl AdwDialogImpl for AccountSettings {} |
|
impl PreferencesDialogImpl for AccountSettings {} |
|
|
|
impl AccountSettings { |
|
/// Set the current session. |
|
fn set_session(&self, session: Option<Session>) { |
|
if self.session.obj() == session { |
|
return; |
|
} |
|
let obj = self.obj(); |
|
|
|
self.session.disconnect_signals(); |
|
self.set_account_management_url_builder(None); |
|
|
|
if let Some(session) = session { |
|
let logged_out_handler = session.connect_logged_out(clone!( |
|
#[weak] |
|
obj, |
|
move |_| { |
|
obj.close(); |
|
} |
|
)); |
|
self.session.set(&session, vec![logged_out_handler]); |
|
|
|
// Refresh the list of sessions. |
|
spawn!(clone!( |
|
#[weak(rename_to = imp)] |
|
self, |
|
async move { |
|
imp.reload_user_sessions().await; |
|
} |
|
)); |
|
|
|
// Load the account management URL. |
|
spawn!(clone!( |
|
#[weak(rename_to = imp)] |
|
self, |
|
async move { |
|
imp.load_account_management_url_builder().await; |
|
} |
|
)); |
|
} |
|
|
|
obj.notify_session(); |
|
} |
|
|
|
/// Load the builder for the account management URL of the OAuth 2.0 |
|
/// authorization server. |
|
async fn load_account_management_url_builder(&self) { |
|
let Some(session) = self.session.obj() else { |
|
return; |
|
}; |
|
|
|
let oauth = session.client().oauth(); |
|
let handle = spawn_tokio!(async move { oauth.account_management_url().await }); |
|
|
|
let url_builder = match handle.await.expect("task was not aborted") { |
|
Ok(url_builder) => url_builder, |
|
Err(error) => { |
|
// Ignore the error that says that OAuth 2.0 is not supported, it can happen. |
|
if !matches!( |
|
error, |
|
OAuthError::Discovery(OAuthDiscoveryError::NotSupported) |
|
) { |
|
warn!("Could not fetch OAuth 2.0 account management URL: {error}"); |
|
} |
|
None |
|
} |
|
}; |
|
self.set_account_management_url_builder(url_builder); |
|
} |
|
|
|
/// Set the builder for the account management URL of the OAuth 2.0 |
|
/// authorization server. |
|
fn set_account_management_url_builder( |
|
&self, |
|
url_builder: Option<AccountManagementUrlBuilder>, |
|
) { |
|
self.account_management_url_builder.replace(url_builder); |
|
self.obj() |
|
.emit_by_name::<()>("account-management-url-builder-changed", &[]); |
|
} |
|
|
|
/// The builder for the account management URL of the OAuth 2.0 |
|
/// authorization server, if any. |
|
pub(super) fn account_management_url_builder(&self) -> Option<AccountManagementUrlBuilder> { |
|
self.account_management_url_builder.borrow().clone() |
|
} |
|
|
|
/// Reload the sessions from the server. |
|
async fn reload_user_sessions(&self) { |
|
let Some(session) = self.session.obj() else { |
|
return; |
|
}; |
|
|
|
session.user_sessions().load().await; |
|
} |
|
} |
|
} |
|
|
|
glib::wrapper! { |
|
/// Preference window to display and update account settings. |
|
pub struct AccountSettings(ObjectSubclass<imp::AccountSettings>) |
|
@extends gtk::Widget, adw::Dialog, adw::PreferencesDialog, |
|
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::ShortcutManager; |
|
} |
|
|
|
impl AccountSettings { |
|
/// Construct new `AccountSettings` for the given session. |
|
pub fn new(session: &Session) -> Self { |
|
glib::Object::builder().property("session", session).build() |
|
} |
|
|
|
/// The builder for the account management URL of the OAuth 2.0 |
|
/// authorization server, if any. |
|
fn account_management_url_builder(&self) -> Option<AccountManagementUrlBuilder> { |
|
self.imp().account_management_url_builder() |
|
} |
|
|
|
/// Show the "Encryption" tab. |
|
pub(crate) fn show_encryption_tab(&self) { |
|
self.set_visible_page_name("encryption"); |
|
} |
|
|
|
/// Show the given subpage. |
|
pub(crate) fn show_subpage(&self, subpage: AccountSettingsSubpage) { |
|
let Some(session) = self.session() else { |
|
return; |
|
}; |
|
|
|
let page: adw::NavigationPage = match subpage { |
|
AccountSettingsSubpage::ChangePassword => ChangePasswordSubpage::new(&session).upcast(), |
|
AccountSettingsSubpage::UserSessionList => { |
|
UserSessionListSubpage::new(&session).upcast() |
|
} |
|
AccountSettingsSubpage::LogOut => LogOutSubpage::new(&session).upcast(), |
|
AccountSettingsSubpage::DeactivateAccount => { |
|
DeactivateAccountSubpage::new(&session, self).upcast() |
|
} |
|
AccountSettingsSubpage::IgnoredUsers => IgnoredUsersSubpage::new(&session).upcast(), |
|
AccountSettingsSubpage::ImportKeys => { |
|
ImportExportKeysSubpage::new(&session, ImportExportKeysSubpageMode::Import).upcast() |
|
} |
|
AccountSettingsSubpage::ExportKeys => { |
|
ImportExportKeysSubpage::new(&session, ImportExportKeysSubpageMode::Export).upcast() |
|
} |
|
AccountSettingsSubpage::CryptoIdentitySetup => { |
|
let view = CryptoIdentitySetupView::new(&session); |
|
view.connect_completed(clone!( |
|
#[weak(rename_to = obj)] |
|
self, |
|
move |_, _| { |
|
obj.pop_subpage(); |
|
} |
|
)); |
|
|
|
let page = adw::NavigationPage::builder() |
|
.tag("crypto-identity-setup") |
|
.child(&view) |
|
.build(); |
|
page.connect_shown(clone!( |
|
#[weak] |
|
view, |
|
move |_| { |
|
view.grab_focus(); |
|
} |
|
)); |
|
|
|
page |
|
} |
|
AccountSettingsSubpage::RecoverySetup => { |
|
let view = CryptoRecoverySetupView::new(&session); |
|
view.connect_completed(clone!( |
|
#[weak(rename_to = obj)] |
|
self, |
|
move |_| { |
|
obj.pop_subpage(); |
|
} |
|
)); |
|
|
|
let page = adw::NavigationPage::builder() |
|
.tag("crypto-recovery-setup") |
|
.child(&view) |
|
.build(); |
|
page.connect_shown(clone!( |
|
#[weak] |
|
view, |
|
move |_| { |
|
view.grab_focus(); |
|
} |
|
)); |
|
|
|
page |
|
} |
|
}; |
|
|
|
self.push_subpage(&page); |
|
} |
|
|
|
/// Show a subpage with the session details of the given session ID. |
|
pub(crate) fn show_session_subpage(&self, device_id: &str) { |
|
let Some(session) = self.session() else { |
|
return; |
|
}; |
|
|
|
let user_session = session.user_sessions().get(&device_id.into()); |
|
|
|
let Some(user_session) = user_session else { |
|
error!("ID {device_id} is not associated to any device"); |
|
return; |
|
}; |
|
|
|
let page = UserSessionSubpage::new(&user_session, self); |
|
|
|
self.push_subpage(&page); |
|
} |
|
|
|
/// Connect to the signal emitted when the builder for the OAuth 2.0 account |
|
/// management URL changed. |
|
pub fn connect_account_management_url_builder_changed<F: Fn(&Self) + 'static>( |
|
&self, |
|
f: F, |
|
) -> glib::SignalHandlerId { |
|
self.connect_closure( |
|
"account-management-url-builder-changed", |
|
true, |
|
closure_local!(move |obj: Self| { |
|
f(&obj); |
|
}), |
|
) |
|
} |
|
}
|
|
|