account-settings: Port to glib::Properties macro

This commit is contained in:
Kévin Commaille 2023-12-19 21:48:30 +01:00
parent 118f4ca1b0
commit ca4ec3100d
No known key found for this signature in database
GPG Key ID: 29A48C1F03620416
13 changed files with 503 additions and 845 deletions

View File

@ -3,7 +3,7 @@ use matrix_sdk::{
encryption::identities::Device as CryptoDevice,
ruma::{
api::client::device::{delete_device, Device as MatrixDevice},
assign, DeviceId,
assign,
},
};
@ -13,16 +13,35 @@ use crate::{
};
mod imp {
use glib::object::WeakRef;
use once_cell::{sync::Lazy, unsync::OnceCell};
use std::{cell::OnceCell, marker::PhantomData};
use super::*;
#[derive(Debug, Default)]
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::Device)]
pub struct Device {
/// The device data.
pub device: OnceCell<MatrixDevice>,
/// The encryption API of the device.
pub crypto_device: OnceCell<CryptoDevice>,
pub session: WeakRef<Session>,
/// The current session.
#[property(get, construct_only)]
pub session: glib::WeakRef<Session>,
/// The ID of the device.
#[property(get = Self::device_id)]
device_id: PhantomData<String>,
/// The display name of the device.
#[property(get = Self::display_name)]
display_name: PhantomData<String>,
/// The last IP address the device used.
#[property(get = Self::last_seen_ip)]
last_seen_ip: PhantomData<Option<String>>,
/// The last time the device was used.
#[property(get = Self::last_seen_ts)]
last_seen_ts: PhantomData<Option<glib::DateTime>>,
/// Whether this device is verified.
#[property(get = Self::verified)]
verified: PhantomData<bool>,
}
#[glib::object_subclass]
@ -31,53 +50,50 @@ mod imp {
type Type = super::Device;
}
impl ObjectImpl for Device {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<Session>("session")
.construct_only()
.build(),
glib::ParamSpecString::builder("device-id")
.read_only()
.build(),
glib::ParamSpecString::builder("display-name")
.read_only()
.build(),
glib::ParamSpecString::builder("last-seen-ip")
.read_only()
.build(),
glib::ParamSpecBoxed::builder::<glib::DateTime>("last-seen-ts")
.read_only()
.build(),
glib::ParamSpecBoolean::builder("verified")
.read_only()
.build(),
]
});
#[glib::derived_properties]
impl ObjectImpl for Device {}
PROPERTIES.as_ref()
impl Device {
/// The device data.
fn device(&self) -> &MatrixDevice {
self.device.get().unwrap()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.session.set(value.get().ok().as_ref()),
_ => unimplemented!(),
/// The ID of this device.
fn device_id(&self) -> String {
self.device().device_id.to_string()
}
/// The display name of the device.
fn display_name(&self) -> String {
if let Some(display_name) = self.device().display_name.clone() {
display_name
} else {
self.device_id()
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
let obj = self.obj();
/// The last IP address the device used.
fn last_seen_ip(&self) -> Option<String> {
// TODO: Would be nice to also show the location
// See: https://gitlab.gnome.org/GNOME/fractal/-/issues/700
self.device().last_seen_ip.clone()
}
match pspec.name() {
"session" => obj.session().to_value(),
"display-name" => obj.display_name().to_value(),
"device-id" => obj.device_id().as_str().to_value(),
"last-seen-ip" => obj.last_seen_ip().to_value(),
"last-seen-ts" => obj.last_seen_ts().to_value(),
"verified" => obj.is_verified().to_value(),
_ => unimplemented!(),
}
/// The last time the device was used.
fn last_seen_ts(&self) -> Option<glib::DateTime> {
self.device().last_seen_ts.map(|last_seen_ts| {
glib::DateTime::from_unix_utc(last_seen_ts.as_secs().into())
.and_then(|t| t.to_local())
.unwrap()
})
}
/// Whether this device is verified.
fn verified(&self) -> bool {
self.crypto_device
.get()
.is_some_and(|device| device.is_verified())
}
}
}
@ -100,11 +116,6 @@ impl Device {
obj
}
/// The current session.
pub fn session(&self) -> Session {
self.imp().session.upgrade().unwrap()
}
/// Set the Matrix device of this `Device`.
fn set_matrix_device(&self, device: MatrixDevice, crypto_device: Option<CryptoDevice>) {
let imp = self.imp();
@ -114,48 +125,15 @@ impl Device {
}
}
/// The ID of this device.
pub fn device_id(&self) -> &DeviceId {
&self.imp().device.get().unwrap().device_id
}
/// The display name of the device.
pub fn display_name(&self) -> &str {
if let Some(ref display_name) = self.imp().device.get().unwrap().display_name {
display_name
} else {
self.device_id().as_str()
}
}
/// The last IP address the device used.
pub fn last_seen_ip(&self) -> Option<&str> {
// TODO: Would be nice to also show the location
// See: https://gitlab.gnome.org/GNOME/fractal/-/issues/700
self.imp().device.get().unwrap().last_seen_ip.as_deref()
}
/// The last time the device was used.
pub fn last_seen_ts(&self) -> Option<glib::DateTime> {
self.imp()
.device
.get()
.unwrap()
.last_seen_ts
.map(|last_seen_ts| {
glib::DateTime::from_unix_utc(last_seen_ts.as_secs().into())
.and_then(|t| t.to_local())
.unwrap()
})
}
/// Deletes the `Device`.
pub async fn delete(
&self,
transient_for: Option<&impl IsA<gtk::Window>>,
) -> Result<(), AuthError> {
let session = self.session();
let device_id = self.device_id().to_owned();
let Some(session) = self.session() else {
return Err(AuthError::NoSession);
};
let device_id = self.imp().device.get().unwrap().device_id.clone();
let dialog = AuthDialog::new(transient_for, &session);
@ -170,12 +148,4 @@ impl Device {
.await?;
Ok(())
}
/// Whether this device is verified.
pub fn is_verified(&self) -> bool {
self.imp()
.crypto_device
.get()
.map_or(false, |device| device.is_verified())
}
}

View File

@ -3,86 +3,61 @@ use gtk::{glib, prelude::*, subclass::prelude::*};
use super::Device;
/// This enum contains all possible types the device list can hold.
#[derive(Debug, Clone)]
pub enum ItemType {
#[derive(Debug, Clone, glib::Boxed)]
#[boxed_type(name = "DeviceListItemType")]
pub enum DeviceListItemType {
Device(Device),
Error(String),
LoadingSpinner,
}
#[derive(Clone, Debug, glib::Boxed)]
#[boxed_type(name = "BoxedDeviceItemType")]
pub struct BoxedItemType(ItemType);
impl From<ItemType> for BoxedItemType {
fn from(type_: ItemType) -> Self {
BoxedItemType(type_)
}
}
mod imp {
use once_cell::{sync::Lazy, unsync::OnceCell};
use std::cell::OnceCell;
use super::*;
#[derive(Debug, Default)]
pub struct Item {
pub type_: OnceCell<ItemType>,
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::DeviceListItem)]
pub struct DeviceListItem {
/// The type of this item.
#[property(get, construct_only)]
pub item_type: OnceCell<DeviceListItemType>,
}
#[glib::object_subclass]
impl ObjectSubclass for Item {
const NAME: &'static str = "DeviceItem";
type Type = super::Item;
impl ObjectSubclass for DeviceListItem {
const NAME: &'static str = "DeviceListItem";
type Type = super::DeviceListItem;
}
impl ObjectImpl for Item {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecBoxed::builder::<BoxedItemType>("type")
.write_only()
.construct_only()
.build()]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"type" => {
let type_ = value.get::<BoxedItemType>().unwrap();
self.type_.set(type_.0).unwrap();
}
_ => unimplemented!(),
}
}
}
#[glib::derived_properties]
impl ObjectImpl for DeviceListItem {}
}
glib::wrapper! {
pub struct Item(ObjectSubclass<imp::Item>);
/// An item in the device list.
pub struct DeviceListItem(ObjectSubclass<imp::DeviceListItem>);
}
impl Item {
impl DeviceListItem {
pub fn for_device(device: Device) -> Self {
let type_ = BoxedItemType(ItemType::Device(device));
glib::Object::builder().property("type", &type_).build()
let item_type = DeviceListItemType::Device(device);
glib::Object::builder()
.property("item-type", &item_type)
.build()
}
pub fn for_error(error: String) -> Self {
let type_ = BoxedItemType(ItemType::Error(error));
glib::Object::builder().property("type", &type_).build()
let item_type = DeviceListItemType::Error(error);
glib::Object::builder()
.property("item-type", &item_type)
.build()
}
pub fn for_loading_spinner() -> Self {
let type_ = BoxedItemType(ItemType::LoadingSpinner);
glib::Object::builder().property("type", &type_).build()
}
/// The type of this item.
pub fn type_(&self) -> &ItemType {
self.imp().type_.get().unwrap()
let item_type = DeviceListItemType::LoadingSpinner;
glib::Object::builder()
.property("item-type", &item_type)
.build()
}
}

View File

@ -6,22 +6,31 @@ use matrix_sdk::{
};
use tracing::error;
use super::{Device, DeviceItem};
use super::{Device, DeviceListItem};
use crate::{session::model::Session, spawn, spawn_tokio};
mod imp {
use std::cell::{Cell, RefCell};
use glib::object::WeakRef;
use once_cell::sync::Lazy;
use std::{
cell::{Cell, RefCell},
marker::PhantomData,
};
use super::*;
#[derive(Debug, Default)]
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::DeviceList)]
pub struct DeviceList {
pub list: RefCell<Vec<DeviceItem>>,
pub session: WeakRef<Session>,
pub current_device: RefCell<Option<DeviceItem>>,
/// The list of device list items.
pub list: RefCell<Vec<DeviceListItem>>,
/// The current session.
#[property(get, construct_only)]
pub session: glib::WeakRef<Session>,
/// The device of this session.
pub current_device_inner: RefCell<Option<DeviceListItem>>,
/// The device of this session, or a replacement list item if it is not
/// found.
#[property(get = Self::current_device)]
current_device: PhantomData<DeviceListItem>,
pub loading: Cell<bool>,
}
@ -32,39 +41,8 @@ mod imp {
type Interfaces = (gio::ListModel,);
}
#[glib::derived_properties]
impl ObjectImpl for DeviceList {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<Session>("session")
.construct_only()
.build(),
glib::ParamSpecObject::builder::<DeviceItem>("current-device")
.read_only()
.build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.session.set(value.get().ok().as_ref()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
let obj = self.obj();
match pspec.name() {
"session" => obj.session().to_value(),
"current-device" => obj.current_device().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
self.obj().load_devices();
@ -73,11 +51,13 @@ mod imp {
impl ListModelImpl for DeviceList {
fn item_type(&self) -> glib::Type {
DeviceItem::static_type()
DeviceListItem::static_type()
}
fn n_items(&self) -> u32 {
self.list.borrow().len() as u32
}
fn item(&self, position: u32) -> Option<glib::Object> {
self.list
.borrow()
@ -86,6 +66,22 @@ mod imp {
.cloned()
}
}
impl DeviceList {
/// The device of this session.
fn current_device(&self) -> DeviceListItem {
self.current_device_inner
.borrow()
.clone()
.unwrap_or_else(|| {
if self.loading.get() {
DeviceListItem::for_loading_spinner()
} else {
DeviceListItem::for_error(gettext("Failed to load connected device."))
}
})
}
}
}
glib::wrapper! {
@ -99,11 +95,6 @@ impl DeviceList {
glib::Object::builder().property("session", session).build()
}
/// The current session.
pub fn session(&self) -> Session {
self.imp().session.upgrade().unwrap()
}
fn set_loading(&self, loading: bool) {
let imp = self.imp();
@ -111,39 +102,20 @@ impl DeviceList {
return;
}
if loading {
self.update_list(vec![DeviceItem::for_loading_spinner()]);
self.update_list(vec![DeviceListItem::for_loading_spinner()]);
}
imp.loading.set(loading);
self.notify("current-device");
}
fn loading(&self) -> bool {
self.imp().loading.get()
}
/// The device of this session.
pub fn current_device(&self) -> DeviceItem {
self.imp()
.current_device
.borrow()
.clone()
.unwrap_or_else(|| {
if self.loading() {
DeviceItem::for_loading_spinner()
} else {
DeviceItem::for_error(gettext("Failed to load connected device."))
}
})
self.notify_current_device();
}
/// Set the device of this session.
fn set_current_device(&self, device: Option<DeviceItem>) {
self.imp().current_device.replace(device);
fn set_current_device(&self, device: Option<DeviceListItem>) {
self.imp().current_device_inner.replace(device);
self.notify("current-device");
self.notify_current_device();
}
fn update_list(&self, devices: Vec<DeviceItem>) {
fn update_list(&self, devices: Vec<DeviceListItem>) {
let added = devices.len();
let prev_devices = self.imp().list.replace(devices);
@ -155,7 +127,9 @@ impl DeviceList {
&self,
response: Result<(Option<MatrixDevice>, Vec<MatrixDevice>, CryptoDevices), Error>,
) {
let session = self.session();
let Some(session) = self.session() else {
return;
};
match response {
Ok((current_device, devices, crypto_devices)) => {
@ -163,7 +137,7 @@ impl DeviceList {
.into_iter()
.map(|device| {
let crypto_device = crypto_devices.get(&device.device_id);
DeviceItem::for_device(Device::new(&session, device, crypto_device))
DeviceListItem::for_device(Device::new(&session, device, crypto_device))
})
.collect();
@ -171,12 +145,12 @@ impl DeviceList {
self.set_current_device(current_device.map(|device| {
let crypto_device = crypto_devices.get(&device.device_id);
DeviceItem::for_device(Device::new(&session, device, crypto_device))
DeviceListItem::for_device(Device::new(&session, device, crypto_device))
}));
}
Err(error) => {
error!("Couldnt load device list: {error}");
self.update_list(vec![DeviceItem::for_error(gettext(
self.update_list(vec![DeviceListItem::for_error(gettext(
"Failed to load the list of connected devices.",
))]);
}
@ -185,7 +159,10 @@ impl DeviceList {
}
pub fn load_devices(&self) {
let client = self.session().client();
let Some(session) = self.session() else {
return;
};
let client = session.client();
self.set_loading(true);

View File

@ -15,14 +15,14 @@ mod imp {
use std::cell::{Cell, RefCell};
use glib::subclass::InitializingObject;
use once_cell::sync::Lazy;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(
resource = "/org/gnome/Fractal/ui/session/view/account_settings/devices_page/device_row.ui"
)]
#[properties(wrapper_type = super::DeviceRow)]
pub struct DeviceRow {
#[template_child]
pub display_name: TemplateChild<gtk::Label>,
@ -36,7 +36,11 @@ mod imp {
pub delete_logout_button: TemplateChild<SpinnerButton>,
#[template_child]
pub verify_button: TemplateChild<SpinnerButton>,
/// The device displayed by this row.
#[property(get, set = Self::set_device, construct_only)]
pub device: RefCell<Option<Device>>,
/// Whether this is the device of the current session.
#[property(get, construct_only)]
pub is_current_device: Cell<bool>,
pub system_settings_handler: RefCell<Option<glib::SignalHandlerId>>,
}
@ -56,46 +60,8 @@ mod imp {
}
}
#[glib::derived_properties]
impl ObjectImpl for DeviceRow {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<Device>("device")
.construct_only()
.build(),
glib::ParamSpecBoolean::builder("is-current-device")
.construct_only()
.build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
let obj = self.obj();
match pspec.name() {
"device" => {
obj.set_device(value.get().unwrap());
}
"is-current-device" => {
obj.set_current_device(value.get().unwrap());
}
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
let obj = self.obj();
match pspec.name() {
"device" => obj.device().to_value(),
"is-current-device" => obj.is_current_device().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
@ -144,9 +110,38 @@ mod imp {
impl WidgetImpl for DeviceRow {}
impl ListBoxRowImpl for DeviceRow {}
impl DeviceRow {
/// Set the device displayed by this row.
fn set_device(&self, device: Device) {
let obj = self.obj();
self.display_name.set_label(&device.display_name());
obj.set_tooltip_text(Some(device.device_id().as_str()));
self.verified_icon.set_visible(device.verified());
// TODO: Implement verification
// imp.verify_button.set_visible(!device.is_verified());
let last_seen_ip = device.last_seen_ip();
if let Some(last_seen_ip) = &last_seen_ip {
self.last_seen_ip.set_label(last_seen_ip);
}
self.last_seen_ip.set_visible(last_seen_ip.is_some());
self.last_seen_ts
.set_visible(device.last_seen_ts().is_some());
self.device.replace(Some(device));
obj.notify_device();
obj.update_last_seen_ts();
}
}
}
glib::wrapper! {
/// A row presenting a device.
pub struct DeviceRow(ObjectSubclass<imp::DeviceRow>)
@extends gtk::Widget, gtk::ListBoxRow, @implements gtk::Accessible;
}
@ -159,60 +154,6 @@ impl DeviceRow {
.build()
}
/// The device displayed by this row.
pub fn device(&self) -> Option<Device> {
self.imp().device.borrow().clone()
}
/// Set the device displayed by this row.
pub fn set_device(&self, device: Option<Device>) {
let imp = self.imp();
if self.device() == device {
return;
}
if let Some(ref device) = device {
imp.display_name.set_label(device.display_name());
self.set_tooltip_text(Some(device.device_id().as_str()));
imp.verified_icon.set_visible(device.is_verified());
// TODO: Implement verification
// imp.verify_button.set_visible(!device.is_verified());
let last_seen_ip_visible = if let Some(last_seen_ip) = device.last_seen_ip() {
imp.last_seen_ip.set_label(last_seen_ip);
true
} else {
false
};
imp.last_seen_ip.set_visible(last_seen_ip_visible);
imp.last_seen_ts
.set_visible(device.last_seen_ts().is_some());
}
imp.device.replace(device);
self.notify("device");
self.update_last_seen_ts();
}
/// Set whether this is the device of the current session.
fn set_current_device(&self, input_bool: bool) {
let imp = self.imp();
if imp.is_current_device.get() == input_bool {
return;
}
imp.is_current_device.replace(input_bool);
self.notify("is-current-device");
}
/// Whether this is the device of the current session.
pub fn is_current_device(&self) -> bool {
self.imp().is_current_device.get()
}
fn delete(&self) {
self.imp().delete_logout_button.set_loading(true);
@ -229,7 +170,7 @@ impl DeviceRow {
error!("Failed to disconnect device {}: {error:?}", device.device_id());
let device_name = device.display_name();
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
let error_message = gettext_f("Failed to disconnect device “{device_name}”", &[("device_name", device_name)]);
let error_message = gettext_f("Failed to disconnect device “{device_name}”", &[("device_name", &device_name)]);
toast!(obj, error_message);
},
}

View File

@ -2,13 +2,16 @@ use adw::subclass::prelude::*;
use gtk::{glib, glib::clone, prelude::*, CompositeTemplate};
mod device;
use self::device::Device;
mod device_row;
use self::device_row::DeviceRow;
mod device_item;
use self::device_item::Item as DeviceItem;
mod device_list;
use self::device_list::DeviceList;
mod device_row;
use self::{
device::Device,
device_item::{DeviceListItem, DeviceListItemType},
device_list::DeviceList,
device_row::DeviceRow,
};
use crate::{components::LoadingRow, session::model::User};
mod imp {
@ -18,11 +21,14 @@ mod imp {
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(
resource = "/org/gnome/Fractal/ui/session/view/account_settings/devices_page/mod.ui"
)]
#[properties(wrapper_type = super::DevicesPage)]
pub struct DevicesPage {
/// The logged-in user.
#[property(get, set = Self::set_user, explicit_notify)]
pub user: RefCell<Option<User>>,
#[template_child]
pub other_sessions_group: TemplateChild<adw::PreferencesGroup>,
@ -47,32 +53,67 @@ mod imp {
}
}
impl ObjectImpl for DevicesPage {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> =
Lazy::new(|| vec![glib::ParamSpecObject::builder::<User>("user").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!(),
}
}
}
#[glib::derived_properties]
impl ObjectImpl for DevicesPage {}
impl WidgetImpl for DevicesPage {}
impl PreferencesPageImpl for DevicesPage {}
impl DevicesPage {
/// Set the logged-in user.
fn set_user(&self, user: Option<User>) {
if *self.user.borrow() == user {
return;
}
let obj = self.obj();
if let Some(user) = &user {
let device_list = DeviceList::new(&user.session());
self.other_sessions.bind_model(
Some(&device_list),
clone!(@weak device_list => @default-panic, move |item| {
match item.downcast_ref::<DeviceListItem>().unwrap().item_type() {
DeviceListItemType::Device(device) => DeviceRow::new(&device, false).upcast(),
DeviceListItemType::Error(error) => {
let row = LoadingRow::new();
row.set_error(Some(error.clone()));
row.connect_retry(clone!(@weak device_list => move|_| {
device_list.load_devices()
}));
row.upcast()
}
DeviceListItemType::LoadingSpinner => {
LoadingRow::new().upcast()
}
}
}),
);
device_list.connect_items_changed(
clone!(@weak obj => move |device_list, _, _, _| {
obj.set_other_sessions_visibility(device_list.n_items() > 0)
}),
);
obj.set_other_sessions_visibility(device_list.n_items() > 0);
device_list.connect_current_device_notify(clone!(@weak obj => move |device_list| {
obj.set_current_device(device_list);
}));
obj.set_current_device(&device_list);
} else {
self.other_sessions.unbind_model();
if let Some(child) = self.current_session.first_child() {
self.current_session.remove(&child);
}
}
self.user.replace(user);
obj.notify_user();
}
}
}
glib::wrapper! {
@ -86,69 +127,6 @@ impl DevicesPage {
glib::Object::builder().property("user", user).build()
}
/// The logged-in user.
pub fn user(&self) -> Option<User> {
self.imp().user.borrow().clone()
}
/// Set the logged-in user.
fn set_user(&self, user: Option<User>) {
let imp = self.imp();
if self.user() == user {
return;
}
if let Some(ref user) = user {
let device_list = DeviceList::new(&user.session());
imp.other_sessions.bind_model(
Some(&device_list),
clone!(@weak device_list => @default-panic, move |item| {
match item.downcast_ref::<DeviceItem>().unwrap().type_() {
device_item::ItemType::Device(device) => DeviceRow::new(device, false).upcast(),
device_item::ItemType::Error(error) => {
let row = LoadingRow::new();
row.set_error(Some(error.clone()));
row.connect_retry(clone!(@weak device_list => move|_| {
device_list.load_devices()
}));
row.upcast()
}
device_item::ItemType::LoadingSpinner => {
LoadingRow::new().upcast()
}
}
}),
);
device_list.connect_items_changed(
clone!(@weak self as obj => move |device_list, _, _, _| {
obj.set_other_sessions_visibility(device_list.n_items() > 0)
}),
);
self.set_other_sessions_visibility(device_list.n_items() > 0);
device_list.connect_notify_local(
Some("current-device"),
clone!(@weak self as obj => move |device_list, _| {
obj.set_current_device(device_list);
}),
);
self.set_current_device(&device_list);
} else {
imp.other_sessions.unbind_model();
if let Some(child) = imp.current_session.first_child() {
imp.current_session.remove(&child);
}
}
imp.user.replace(user);
self.notify("user");
}
fn set_other_sessions_visibility(&self, visible: bool) {
self.imp().other_sessions_group.set_visible(visible);
}
@ -158,9 +136,9 @@ impl DevicesPage {
if let Some(child) = imp.current_session.first_child() {
imp.current_session.remove(&child);
}
let row: gtk::Widget = match device_list.current_device().type_() {
device_item::ItemType::Device(device) => DeviceRow::new(device, true).upcast(),
device_item::ItemType::Error(error) => {
let row: gtk::Widget = match device_list.current_device().item_type() {
DeviceListItemType::Device(device) => DeviceRow::new(&device, true).upcast(),
DeviceListItemType::Error(error) => {
let row = LoadingRow::new();
row.set_error(Some(error.clone()));
row.connect_retry(clone!(@weak device_list => move|_| {
@ -168,7 +146,7 @@ impl DevicesPage {
}));
row.upcast()
}
device_item::ItemType::LoadingSpinner => LoadingRow::new().upcast(),
DeviceListItemType::LoadingSpinner => LoadingRow::new().upcast(),
};
imp.current_session.append(&row);
}

View File

@ -18,16 +18,19 @@ use crate::{
};
mod imp {
use glib::{subclass::InitializingObject, WeakRef};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(
resource = "/org/gnome/Fractal/ui/session/view/account_settings/general_page/change_password_subpage.ui"
)]
#[properties(wrapper_type = super::ChangePasswordSubpage)]
pub struct ChangePasswordSubpage {
pub session: WeakRef<Session>,
/// The current session.
#[property(get, set, nullable)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub password: TemplateChild<adw::PasswordEntryRow>,
#[template_child]
@ -62,29 +65,8 @@ mod imp {
}
}
#[glib::derived_properties]
impl ObjectImpl for ChangePasswordSubpage {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> =
Lazy::new(|| vec![glib::ParamSpecObject::builder::<Session>("session").build()]);
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.obj().set_session(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"session" => self.obj().session().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
@ -127,16 +109,6 @@ impl ChangePasswordSubpage {
glib::Object::builder().property("session", session).build()
}
/// The current session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
self.imp().session.set(session.as_ref());
}
fn validate_password(&self) {
let imp = self.imp();
let entry = &imp.password;
@ -239,6 +211,10 @@ impl ChangePasswordSubpage {
}
async fn change_password(&self) {
let Some(session) = self.session() else {
return;
};
if !self.can_change_password() {
return;
}
@ -250,7 +226,6 @@ impl ChangePasswordSubpage {
imp.password.set_sensitive(false);
imp.confirm_password.set_sensitive(false);
let session = self.session().unwrap();
let dialog = AuthDialog::new(self.root().and_downcast_ref::<gtk::Window>(), &session);
let result = dialog

View File

@ -15,16 +15,19 @@ use crate::{
};
mod imp {
use glib::{subclass::InitializingObject, WeakRef};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(
resource = "/org/gnome/Fractal/ui/session/view/account_settings/general_page/deactivate_account_subpage.ui"
)]
#[properties(wrapper_type = super::DeactivateAccountSubpage)]
pub struct DeactivateAccountSubpage {
pub session: WeakRef<Session>,
/// The current session.
#[property(get, set = Self::set_session, nullable)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub confirmation: TemplateChild<adw::EntryRow>,
#[template_child]
@ -46,29 +49,8 @@ mod imp {
}
}
#[glib::derived_properties]
impl ObjectImpl for DeactivateAccountSubpage {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> =
Lazy::new(|| vec![glib::ParamSpecObject::builder::<Session>("session").build()]);
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.obj().set_session(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"session" => self.obj().session().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
@ -98,6 +80,16 @@ mod imp {
impl WidgetImpl for DeactivateAccountSubpage {}
impl NavigationPageImpl for DeactivateAccountSubpage {}
impl DeactivateAccountSubpage {
/// Set the current session.
fn set_session(&self, session: Option<Session>) {
if let Some(session) = session {
self.session.set(Some(&session));
self.confirmation.set_title(session.user_id().as_str());
}
}
}
}
glib::wrapper! {
@ -111,27 +103,6 @@ impl DeactivateAccountSubpage {
glib::Object::builder().property("session", session).build()
}
/// The current session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
if let Some(session) = session {
let imp = self.imp();
imp.session.set(Some(&session));
imp.confirmation.set_title(&self.user_id());
}
}
fn user_id(&self) -> String {
self.session()
.as_ref()
.map(|session| session.user_id().to_string())
.unwrap()
}
fn update_button(&self) {
self.imp()
.button
@ -139,11 +110,15 @@ impl DeactivateAccountSubpage {
}
fn can_deactivate_account(&self) -> bool {
let confirmation = self.imp().confirmation.text();
confirmation == self.user_id()
let confirmation = &self.imp().confirmation;
confirmation.text() == confirmation.title()
}
async fn deactivate_account(&self) {
let Some(session) = self.session() else {
return;
};
if !self.can_deactivate_account() {
return;
}
@ -152,7 +127,6 @@ impl DeactivateAccountSubpage {
imp.button.set_loading(true);
imp.confirmation.set_sensitive(false);
let session = self.session().unwrap();
let dialog = AuthDialog::new(self.root().and_downcast_ref::<gtk::Window>(), &session);
let result = dialog

View File

@ -7,16 +7,19 @@ use gtk::{
use crate::{components::SpinnerButton, session::model::Session, spawn, toast};
mod imp {
use glib::{subclass::InitializingObject, WeakRef};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(
resource = "/org/gnome/Fractal/ui/session/view/account_settings/general_page/log_out_subpage.ui"
)]
#[properties(wrapper_type = super::LogOutSubpage)]
pub struct LogOutSubpage {
pub session: WeakRef<Session>,
/// The current session.
#[property(get, set, nullable)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub logout_button: TemplateChild<SpinnerButton>,
#[template_child]
@ -39,29 +42,8 @@ mod imp {
}
}
impl ObjectImpl for LogOutSubpage {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> =
Lazy::new(|| vec![glib::ParamSpecObject::builder::<Session>("session").build()]);
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.obj().set_session(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"session" => self.obj().session().to_value(),
_ => unimplemented!(),
}
}
}
#[glib::derived_properties]
impl ObjectImpl for LogOutSubpage {}
impl WidgetImpl for LogOutSubpage {}
impl NavigationPageImpl for LogOutSubpage {}
@ -79,37 +61,24 @@ impl LogOutSubpage {
glib::Object::builder().property("session", session).build()
}
/// The current session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
if let Some(session) = session {
self.imp().session.set(Some(&session));
}
}
#[template_callback]
fn logout_button_clicked_cb(&self) {
let Some(session) = self.session() else {
return;
};
let imp = self.imp();
let logout_button = imp.logout_button.get();
let make_backup_button = imp.make_backup_button.get();
let session = self.session().unwrap();
imp.logout_button.set_loading(true);
imp.make_backup_button.set_sensitive(false);
logout_button.set_loading(true);
make_backup_button.set_sensitive(false);
spawn!(clone!(@weak self as obj, @weak session => async move {
if let Err(error) = session.logout().await {
toast!(obj, error);
}
spawn!(
clone!(@weak self as obj, @weak logout_button, @weak make_backup_button, @weak session => async move {
if let Err(error) = session.logout().await {
toast!(obj, error);
}
logout_button.set_loading(false);
make_backup_button.set_sensitive(true);
})
);
let imp = obj.imp();
imp.logout_button.set_loading(false);
imp.make_backup_button.set_sensitive(true);
}));
}
}

View File

@ -19,7 +19,7 @@ use log_out_subpage::LogOutSubpage;
use crate::{
components::{ActionButton, ActionState, ButtonRow, EditableAvatar},
prelude::*,
session::model::{Session, User},
session::model::Session,
spawn, spawn_tokio, toast,
utils::{media::load_file, template_callbacks::TemplateCallbacks, OngoingAsyncAction},
};
@ -27,16 +27,19 @@ use crate::{
mod imp {
use std::cell::RefCell;
use glib::{subclass::InitializingObject, WeakRef};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(
resource = "/org/gnome/Fractal/ui/session/view/account_settings/general_page/mod.ui"
)]
#[properties(wrapper_type = super::GeneralPage)]
pub struct GeneralPage {
pub session: WeakRef<Session>,
/// The current session.
#[property(get, set = Self::set_session, nullable)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub avatar: TemplateChild<EditableAvatar>,
#[template_child]
@ -59,6 +62,8 @@ mod imp {
pub log_out_subpage: TemplateChild<LogOutSubpage>,
pub changing_avatar: RefCell<Option<OngoingAsyncAction<String>>>,
pub changing_display_name: RefCell<Option<OngoingAsyncAction<String>>>,
pub avatar_uri_handler: RefCell<Option<glib::SignalHandlerId>>,
pub display_name_handler: RefCell<Option<glib::SignalHandlerId>>,
}
#[glib::object_subclass]
@ -98,32 +103,8 @@ mod imp {
}
}
#[glib::derived_properties]
impl ObjectImpl for GeneralPage {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Session>("session")
.explicit_notify()
.build()]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.obj().set_session(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"session" => self.obj().session().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
@ -136,6 +117,54 @@ mod imp {
impl WidgetImpl for GeneralPage {}
impl PreferencesPageImpl for GeneralPage {}
impl GeneralPage {
/// Set the current session.
fn set_session(&self, session: Option<Session>) {
let prev_session = self.session.upgrade();
if prev_session == session {
return;
}
let obj = self.obj();
if let Some(session) = prev_session {
let user = session.user();
if let Some(handler) = self.avatar_uri_handler.take() {
user.avatar_data().image().unwrap().disconnect(handler)
}
if let Some(handler) = self.display_name_handler.take() {
user.disconnect(handler);
}
}
self.session.set(session.as_ref());
obj.notify_session();
let Some(session) = session else {
return;
};
self.user_id.set_subtitle(session.user_id().as_str());
self.homeserver.set_subtitle(session.homeserver().as_str());
self.session_id.set_subtitle(session.device_id().as_str());
let user = session.user();
let avatar_uri_handler = user.avatar_data().image().unwrap().connect_uri_notify(
clone!(@weak obj => move |avatar_image| {
obj.avatar_changed(avatar_image.uri());
}),
);
self.avatar_uri_handler.replace(Some(avatar_uri_handler));
let display_name_handler =
user.connect_display_name_notify(clone!(@weak obj => move |user| {
obj.display_name_changed(user.display_name());
}));
self.display_name_handler
.replace(Some(display_name_handler));
}
}
}
glib::wrapper! {
@ -150,58 +179,6 @@ impl GeneralPage {
glib::Object::builder().property("session", session).build()
}
/// The current session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
if self.session() == session {
return;
}
self.imp().session.set(session.as_ref());
self.notify("session");
self.user()
.avatar_data()
.image()
.unwrap()
.connect_uri_notify(clone!(@weak self as obj => move |avatar_image| {
obj.avatar_changed(avatar_image.uri());
}));
self.user().connect_notify_local(
Some("display-name"),
clone!(@weak self as obj => move |user, _| {
obj.display_name_changed(user.display_name());
}),
);
spawn!(
glib::Priority::LOW,
clone!(@weak self as obj => async move {
let imp = obj.imp();
let client = obj.session().unwrap().client();
let homeserver = client.homeserver();
imp.homeserver.set_subtitle(homeserver.as_ref());
let user_id = client.user_id().unwrap();
imp.user_id.set_subtitle(user_id.as_ref());
let session_id = client.device_id().unwrap();
imp.session_id.set_subtitle(session_id.as_ref());
})
);
}
fn user(&self) -> User {
self.session()
.as_ref()
.map(|session| session.user())
.unwrap()
}
fn init_avatar(&self) {
let avatar = &self.imp().avatar;
avatar.connect_edit_avatar(clone!(@weak self as obj => move |_, file| {
@ -245,6 +222,10 @@ impl GeneralPage {
}
async fn change_avatar(&self, file: gio::File) {
let Some(session) = self.session() else {
return;
};
let imp = self.imp();
let avatar = &imp.avatar;
avatar.edit_in_progress();
@ -259,7 +240,7 @@ impl GeneralPage {
}
};
let client = self.session().unwrap().client();
let client = session.client();
let client_clone = client.clone();
let handle =
spawn_tokio!(async move { client_clone.media().upload(&info.mime, data).await });
@ -288,7 +269,7 @@ impl GeneralPage {
// Because this action can finish in avatar_changed, we must only act if this is
// still the current action.
if weak_action.is_ongoing() {
self.user().set_avatar_url(Some(uri))
session.user().set_avatar_url(Some(uri))
}
}
Err(error) => {
@ -305,6 +286,9 @@ impl GeneralPage {
}
async fn remove_avatar(&self) {
let Some(session) = self.session() else {
return;
};
let Some(window) = self.root().and_downcast::<gtk::Window>() else {
return;
};
@ -333,7 +317,7 @@ impl GeneralPage {
let (action, weak_action) = OngoingAsyncAction::remove();
imp.changing_avatar.replace(Some(action));
let client = self.session().unwrap().client();
let client = session.client();
let handle = spawn_tokio!(async move { client.account().set_avatar_url(None).await });
match handle.await.unwrap() {
@ -343,7 +327,7 @@ impl GeneralPage {
// Because this action can finish in avatar_changed, we must only act if this is
// still the current action.
if weak_action.is_ongoing() {
self.user().set_avatar_url(None)
session.user().set_avatar_url(None)
}
}
Err(error) => {
@ -362,8 +346,12 @@ impl GeneralPage {
fn init_display_name(&self) {
let imp = self.imp();
let entry = &imp.display_name;
entry.connect_changed(clone!(@weak self as obj => move|entry| {
obj.imp().display_name_button.set_visible(entry.text() != obj.user().display_name());
entry.connect_changed(clone!(@weak self as obj => move |entry| {
let Some(session) = obj.session() else {
return;
};
obj.imp().display_name_button.set_visible(entry.text() != session.user().display_name());
}));
}
@ -395,6 +383,10 @@ impl GeneralPage {
}
async fn change_display_name(&self) {
let Some(session) = self.session() else {
return;
};
let imp = self.imp();
let entry = &imp.display_name;
let button = &imp.display_name_button;
@ -407,7 +399,7 @@ impl GeneralPage {
let (action, weak_action) = OngoingAsyncAction::set(display_name.clone());
imp.changing_display_name.replace(Some(action));
let client = self.session().unwrap().client();
let client = session.client();
let display_name_clone = display_name.clone();
let handle = spawn_tokio!(async move {
client
@ -423,7 +415,7 @@ impl GeneralPage {
// Because this action can finish in display_name_changed, we must only act if
// this is still the current action.
if weak_action.is_ongoing() {
self.user().set_display_name(Some(display_name));
session.user().set_display_name(Some(display_name));
}
}
Err(error) => {
@ -442,11 +434,14 @@ impl GeneralPage {
}
fn init_change_password(&self) {
let Some(session) = self.session() else {
return;
};
let client = session.client();
spawn!(
glib::Priority::LOW,
clone!(@weak self as obj => async move {
let client = obj.session().unwrap().client();
// Check whether the user can change their password.
let handle = spawn_tokio!(async move {
client.send(get_capabilities::v3::Request::new(), None).await

View File

@ -14,19 +14,22 @@ use self::{
devices_page::DevicesPage, general_page::GeneralPage, notifications_page::NotificationsPage,
security_page::SecurityPage,
};
use crate::session::model::Session;
use crate::{session::model::Session, utils::BoundObjectWeakRef};
mod imp {
use std::cell::RefCell;
use glib::{subclass::InitializingObject, WeakRef};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/account_settings/mod.ui")]
#[properties(wrapper_type = super::AccountSettings)]
pub struct AccountSettings {
pub session: WeakRef<Session>,
/// The current session.
#[property(get, set = Self::set_session, nullable)]
pub session: BoundObjectWeakRef<Session>,
pub session_handler: RefCell<Option<glib::SignalHandlerId>>,
#[template_child]
pub general_page: TemplateChild<GeneralPage>,
@ -45,6 +48,7 @@ mod imp {
GeneralPage::static_type();
NotificationsPage::static_type();
SecurityPage::static_type();
Self::bind_template(klass);
klass.install_action("account-settings.close", None, |obj, _, _| {
@ -76,45 +80,34 @@ mod imp {
}
}
impl ObjectImpl for AccountSettings {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Session>("session")
.explicit_notify()
.build()]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.obj().set_session(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"session" => self.obj().session().to_value(),
_ => unimplemented!(),
}
}
fn dispose(&self) {
if let Some(session) = self.session.upgrade() {
if let Some(handler) = self.session_handler.take() {
session.disconnect(handler);
}
}
}
}
#[glib::derived_properties]
impl ObjectImpl for AccountSettings {}
impl WidgetImpl for AccountSettings {}
impl WindowImpl for AccountSettings {}
impl AdwWindowImpl for AccountSettings {}
impl PreferencesWindowImpl 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();
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]);
}
obj.notify_session();
}
}
}
glib::wrapper! {
@ -130,35 +123,4 @@ impl AccountSettings {
.property("session", session)
.build()
}
/// The current session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
let prev_session = self.session();
if prev_session == session {
return;
}
let imp = self.imp();
if let Some(session) = prev_session {
if let Some(handler) = imp.session_handler.take() {
session.disconnect(handler);
}
}
if let Some(session) = &session {
imp.session_handler.replace(Some(session.connect_logged_out(
clone!(@weak self as obj => move |_| {
obj.close();
}),
)));
}
self.imp().session.set(session.as_ref());
self.notify("session");
}
}

View File

@ -22,18 +22,24 @@ pub enum KeysSubpageMode {
}
mod imp {
use std::cell::{Cell, RefCell};
use std::{
cell::{Cell, RefCell},
marker::PhantomData,
};
use glib::{subclass::InitializingObject, WeakRef};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(
resource = "/org/gnome/Fractal/ui/session/view/account_settings/security_page/import_export_keys_subpage.ui"
)]
#[properties(wrapper_type = super::ImportExportKeysSubpage)]
pub struct ImportExportKeysSubpage {
pub session: WeakRef<Session>,
/// The current session.
#[property(get, set, nullable)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub description: TemplateChild<gtk::Label>,
#[template_child]
@ -54,7 +60,14 @@ mod imp {
pub file_button: TemplateChild<gtk::Button>,
#[template_child]
pub proceed_button: TemplateChild<SpinnerButton>,
/// The path to export the keys to.
#[property(get)]
pub file_path: RefCell<Option<gio::File>>,
/// The path to export the keys to, as a string.
#[property(get = Self::file_path_string)]
pub file_path_string: PhantomData<Option<String>>,
/// The export/import mode of the subpage.
#[property(get, set = Self::set_mode, explicit_notify, builder(KeysSubpageMode::default()))]
pub mode: Cell<KeysSubpageMode>,
}
@ -74,49 +87,8 @@ mod imp {
}
}
#[glib::derived_properties]
impl ObjectImpl for ImportExportKeysSubpage {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<Session>("session").build(),
glib::ParamSpecString::builder("file-path")
.read_only()
.build(),
glib::ParamSpecEnum::builder::<KeysSubpageMode>("mode")
.explicit_notify()
.build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
let obj = self.obj();
match pspec.name() {
"session" => obj.set_session(value.get().unwrap()),
"mode" => obj.set_mode(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
let obj = self.obj();
match pspec.name() {
"session" => obj.session().to_value(),
"file-path" => obj
.file_path()
.and_then(|file| file.path())
.map(|path| path.to_string_lossy().to_string())
.to_value(),
"mode" => obj.mode().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
@ -137,6 +109,30 @@ mod imp {
impl WidgetImpl for ImportExportKeysSubpage {}
impl NavigationPageImpl for ImportExportKeysSubpage {}
impl ImportExportKeysSubpage {
/// Set the export/import mode of the subpage.
fn set_mode(&self, mode: KeysSubpageMode) {
if self.mode.get() == mode {
return;
}
let obj = self.obj();
self.mode.set(mode);
obj.update_for_mode();
obj.clear();
obj.notify_mode();
}
/// The path to export the keys to, as a string.
fn file_path_string(&self) -> Option<String> {
self.file_path
.borrow()
.as_ref()
.and_then(|file| file.path())
.map(|path| path.to_string_lossy().to_string())
}
}
}
glib::wrapper! {
@ -151,21 +147,6 @@ impl ImportExportKeysSubpage {
glib::Object::builder().property("session", session).build()
}
/// The current session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
self.imp().session.set(session.as_ref());
}
/// The path to export the keys to.
pub fn file_path(&self) -> Option<gio::File> {
self.imp().file_path.borrow().clone()
}
/// Set the path to export the keys to.
fn set_file_path(&self, path: Option<gio::File>) {
let imp = self.imp();
@ -175,24 +156,8 @@ impl ImportExportKeysSubpage {
imp.file_path.replace(path);
self.update_button();
self.notify("file-path");
}
/// The export/import mode of the subpage.
pub fn mode(&self) -> KeysSubpageMode {
self.imp().mode.get()
}
/// Set the export/import mode of the subpage.
pub fn set_mode(&self, mode: KeysSubpageMode) {
if self.mode() == mode {
return;
}
self.imp().mode.set(mode);
self.update_for_mode();
self.clear();
self.notify("mode");
self.notify_file_path();
self.notify_file_path_string();
}
fn clear(&self) {

View File

@ -104,7 +104,7 @@
<child>
<object class="AdwActionRow" id="file_row">
<property name="title" translatable="yes">File</property>
<property name="subtitle" bind-source="ImportExportKeysSubpage" bind-property="file-path" bind-flags="sync-create"/>
<property name="subtitle" bind-source="ImportExportKeysSubpage" bind-property="file-path-string" bind-flags="sync-create"/>
<child>
<object class="GtkButton" id="file_button">
<property name="label" translatable="yes">Choose…</property>

View File

@ -8,16 +8,19 @@ mod import_export_keys_subpage;
use import_export_keys_subpage::{ImportExportKeysSubpage, KeysSubpageMode};
mod imp {
use glib::{subclass::InitializingObject, WeakRef};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(
resource = "/org/gnome/Fractal/ui/session/view/account_settings/security_page/mod.ui"
)]
#[properties(wrapper_type = super::SecurityPage)]
pub struct SecurityPage {
pub session: WeakRef<Session>,
/// The current session.
#[property(get, set = Self::set_session, nullable)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub import_export_keys_subpage: TemplateChild<ImportExportKeysSubpage>,
#[template_child]
@ -45,35 +48,28 @@ mod imp {
}
}
impl ObjectImpl for SecurityPage {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Session>("session")
.explicit_notify()
.build()]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.obj().set_session(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"session" => self.obj().session().to_value(),
_ => unimplemented!(),
}
}
}
#[glib::derived_properties]
impl ObjectImpl for SecurityPage {}
impl WidgetImpl for SecurityPage {}
impl PreferencesPageImpl for SecurityPage {}
impl SecurityPage {
/// Set the current session.
fn set_session(&self, session: Option<Session>) {
if self.session.upgrade() == session {
return;
}
let obj = self.obj();
self.session.set(session.as_ref());
obj.notify_session();
spawn!(clone!(@weak obj => async move {
obj.load_cross_signing_status().await;
}));
}
}
}
glib::wrapper! {
@ -88,25 +84,6 @@ impl SecurityPage {
glib::Object::builder().property("session", session).build()
}
/// The current session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
if self.session() == session {
return;
}
self.imp().session.set(session.as_ref());
self.notify("session");
spawn!(clone!(@weak self as obj => async move {
obj.load_cross_signing_status().await;
}));
}
#[template_callback]
pub fn show_export_keys_page(&self) {
let subpage = &*self.imp().import_export_keys_subpage;