From 99b4111cc0d2f65fbadc640ef9ef5cce307ddc2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sat, 28 Feb 2026 13:46:31 +0100 Subject: [PATCH] Remove dependency on strum While it is somewhat convenient, let's stop being lazy and implement more explicit methods manually. --- Cargo.lock | 22 ------ Cargo.toml | 1 - src/account_settings/mod.rs | 6 +- src/account_settings/notifications_page.rs | 8 +-- src/components/crypto/identity_setup_view.rs | 57 +++++++++++---- src/components/crypto/recovery_setup_view.rs | 72 ++++++++++++++----- src/error_page.rs | 17 +++-- src/login/greeter.rs | 3 + src/login/homeserver_page.rs | 3 + src/login/in_browser_page.rs | 3 + src/login/method_page.rs | 3 + src/login/mod.rs | 55 ++++++++++---- src/login/session_setup_view.rs | 47 +++++++++--- .../notifications/notifications_settings.rs | 31 ++++++-- src/session/room/member_list.rs | 17 +++-- src/session/session_settings.rs | 14 +--- src/session_view/content.rs | 46 +++++++++--- .../create_direct_chat_dialog/mod.rs | 18 ++++- .../members_page/members_list_view/mod.rs | 2 +- .../room_details/members_page/mod.rs | 2 +- .../room_history/message_toolbar/mod.rs | 30 ++++++-- src/window.rs | 44 +++++++++--- 22 files changed, 363 insertions(+), 138 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a706613..6d8d414b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1167,7 +1167,6 @@ dependencies = [ "serde_bytes", "serde_json", "sourceview5", - "strum", "tempfile", "thiserror 2.0.18", "tld", @@ -4661,27 +4660,6 @@ dependencies = [ "quote", ] -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index 054de021..68298d7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,6 @@ secular = { version = "1", features = ["bmp", "normalization"] } serde = "1" serde_bytes = "0.11" serde_json = "1" -strum = { version = "0.27.1", features = ["derive"] } tempfile = "3" thiserror = "2" tld = "2" diff --git a/src/account_settings/mod.rs b/src/account_settings/mod.rs index ff79f277..3a46dd14 100644 --- a/src/account_settings/mod.rs +++ b/src/account_settings/mod.rs @@ -29,7 +29,7 @@ use crate::{ }; /// A subpage of the account settings. -#[derive(Debug, Clone, Copy, Eq, PartialEq, glib::Variant, strum::AsRefStr)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, glib::Variant)] pub(crate) enum AccountSettingsSubpage { /// A form to change the account's password. ChangePassword, @@ -298,7 +298,7 @@ impl AccountSettings { )); let page = adw::NavigationPage::builder() - .tag(AccountSettingsSubpage::CryptoIdentitySetup.as_ref()) + .tag("crypto-identity-setup") .child(&view) .build(); page.connect_shown(clone!( @@ -322,7 +322,7 @@ impl AccountSettings { )); let page = adw::NavigationPage::builder() - .tag(AccountSettingsSubpage::RecoverySetup.as_ref()) + .tag("crypto-recovery-setup") .child(&view) .build(); page.connect_shown(clone!( diff --git a/src/account_settings/notifications_page.rs b/src/account_settings/notifications_page.rs index fd3c40c7..53f8f117 100644 --- a/src/account_settings/notifications_page.rs +++ b/src/account_settings/notifications_page.rs @@ -1,7 +1,6 @@ use adw::{prelude::*, subclass::prelude::*}; use gettextrs::gettext; use gtk::{gio, glib, glib::clone}; -use tracing::error; use crate::{ components::{CheckLoadingRow, EntryAddRow, RemovableRow, SwitchLoadingRow}, @@ -234,7 +233,7 @@ mod imp { return String::new(); }; - settings.global_setting().to_string() + settings.global_setting().as_str().to_owned() } /// Update the global section. @@ -254,10 +253,7 @@ mod imp { /// Set the global setting, as a string. fn set_global_setting(&self, default: &str) { - let Ok(default) = default.parse::() else { - error!("Invalid value to set global default notifications setting: {default}"); - return; - }; + let default = NotificationsGlobalSetting::from_str(default); spawn!(clone!( #[weak(rename_to = imp)] diff --git a/src/components/crypto/identity_setup_view.rs b/src/components/crypto/identity_setup_view.rs index 6d765150..6451f7a6 100644 --- a/src/components/crypto/identity_setup_view.rs +++ b/src/components/crypto/identity_setup_view.rs @@ -18,8 +18,7 @@ use crate::{ }; /// A page of the crypto identity setup navigation stack. -#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumString, strum::AsRefStr, glib::Variant)] -#[strum(serialize_all = "kebab-case")] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum CryptoIdentitySetupPage { /// Choose a verification method. ChooseMethod, @@ -33,6 +32,33 @@ enum CryptoIdentitySetupPage { Recovery, } +impl CryptoIdentitySetupPage { + /// Get the tag for this page. + const fn tag(self) -> &'static str { + match self { + Self::ChooseMethod => "choose-method", + Self::Verify => "verify", + Self::Bootstrap => "bootstrap", + Self::Reset => "reset", + Self::Recovery => "recovery", + } + } + + /// Get page matching the given tag. + /// + /// Panics if the tag does not match any of the variants. + fn from_tag(tag: &str) -> Self { + match tag { + "choose-method" => Self::ChooseMethod, + "verify" => Self::Verify, + "bootstrap" => Self::Bootstrap, + "reset" => Self::Reset, + "recovery" => Self::Recovery, + _ => panic!("Unknown CryptoIdentitySetupPage: {tag}"), + } + } +} + /// The result of the crypto identity setup. #[derive(Debug, Clone, Copy, PartialEq, Eq, glib::Enum)] #[enum_type(name = "CryptoIdentitySetupNextStep")] @@ -151,11 +177,16 @@ mod imp { impl CryptoIdentitySetupView { /// The visible page of the view. fn visible_page(&self) -> CryptoIdentitySetupPage { - self.navigation - .visible_page() - .and_then(|p| p.tag()) - .and_then(|t| t.as_str().try_into().ok()) - .unwrap() + CryptoIdentitySetupPage::from_tag( + &self + .navigation + .visible_page() + .expect( + "CryptoIdentitySetupView navigation view should always have a visible page", + ) + .tag() + .expect("CryptoIdentitySetupView navigation page should always have a tag"), + ) } /// The recovery view. @@ -216,7 +247,7 @@ mod imp { let verification_state = security.verification_state(); if verification_state == SessionVerificationState::Verified { self.navigation - .replace_with_tags(&[CryptoIdentitySetupPage::Reset.as_ref()]); + .replace_with_tags(&[CryptoIdentitySetupPage::Reset.tag()]); return; } @@ -226,7 +257,7 @@ mod imp { // If there is no crypto identity, we need to bootstrap it. if crypto_identity_state == CryptoIdentityState::Missing { self.navigation - .replace_with_tags(&[CryptoIdentitySetupPage::Bootstrap.as_ref()]); + .replace_with_tags(&[CryptoIdentitySetupPage::Bootstrap.tag()]); return; } @@ -331,10 +362,10 @@ mod imp { .navigation .visible_page() .and_then(|p| p.tag()) - .is_none_or(|t| t != CryptoIdentitySetupPage::Verify.as_ref()) + .is_none_or(|t| t != CryptoIdentitySetupPage::Verify.tag()) { self.navigation - .push_by_tag(CryptoIdentitySetupPage::Verify.as_ref()); + .push_by_tag(CryptoIdentitySetupPage::Verify.tag()); } self.obj().notify_verification(); @@ -349,7 +380,7 @@ mod imp { recovery_view.set_initial_page(initial_page); let page = adw::NavigationPage::builder() - .tag(CryptoIdentitySetupPage::Recovery.as_ref()) + .tag(CryptoIdentitySetupPage::Recovery.tag()) .child(recovery_view) .build(); page.connect_shown(clone!( @@ -404,7 +435,7 @@ mod imp { self.navigation.push(&recovery_view); } else { self.navigation - .push_by_tag(CryptoIdentitySetupPage::Bootstrap.as_ref()); + .push_by_tag(CryptoIdentitySetupPage::Bootstrap.tag()); } } diff --git a/src/components/crypto/recovery_setup_view.rs b/src/components/crypto/recovery_setup_view.rs index 9698b83c..2a0cd18b 100644 --- a/src/components/crypto/recovery_setup_view.rs +++ b/src/components/crypto/recovery_setup_view.rs @@ -14,8 +14,7 @@ use crate::{ }; /// A page of the [`CryptoRecoverySetupView`] navigation stack. -#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumString, strum::AsRefStr)] -#[strum(serialize_all = "kebab-case")] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum CryptoRecoverySetupPage { /// Use account recovery. Recover, @@ -29,10 +28,35 @@ enum CryptoRecoverySetupPage { Incomplete, } +impl CryptoRecoverySetupPage { + /// Get the tag for this page. + const fn tag(self) -> &'static str { + match self { + Self::Recover => "recover", + Self::Reset => "reset", + Self::Enable => "enable", + Self::Success => "success", + Self::Incomplete => "incomplete", + } + } + + /// Get the page matching the given tag. + /// + /// Panics if the tag does not match any variant. + fn from_tag(tag: &str) -> Self { + match tag { + "recover" => Self::Recover, + "reset" => Self::Reset, + "enable" => Self::Enable, + "success" => Self::Success, + "incomplete" => Self::Incomplete, + _ => panic!("Unknown CryptoRecoverySetupPage: {tag}"), + } + } +} + /// The initial page of the [`CryptoRecoverySetupView`]. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, glib::Enum, strum::AsRefStr)] -#[enum_type(name = "CryptoRecoverySetupInitialPage")] -#[strum(serialize_all = "kebab-case")] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum CryptoRecoverySetupInitialPage { /// Use account recovery. #[default] @@ -43,6 +67,16 @@ pub enum CryptoRecoverySetupInitialPage { Enable, } +impl From for CryptoRecoverySetupPage { + fn from(value: CryptoRecoverySetupInitialPage) -> Self { + match value { + CryptoRecoverySetupInitialPage::Recover => Self::Recover, + CryptoRecoverySetupInitialPage::Reset => Self::Reset, + CryptoRecoverySetupInitialPage::Enable => Self::Enable, + } + } +} + mod imp { use std::sync::LazyLock; @@ -140,11 +174,16 @@ mod imp { impl CryptoRecoverySetupView { /// The visible page of the view. fn visible_page(&self) -> CryptoRecoverySetupPage { - self.navigation - .visible_page() - .and_then(|p| p.tag()) - .and_then(|t| t.as_str().try_into().ok()) - .unwrap() + CryptoRecoverySetupPage::from_tag( + &self + .navigation + .visible_page() + .expect( + "CryptoRecoverySetupView navigation view should always have a visible page", + ) + .tag() + .expect("CryptoRecoverySetupView navigation page should always have a tag"), + ) } /// Set the current session. @@ -213,7 +252,8 @@ mod imp { /// Set the initial page of this view. pub(super) fn set_initial_page(&self, initial_page: CryptoRecoverySetupInitialPage) { - self.navigation.replace_with_tags(&[initial_page.as_ref()]); + self.navigation + .replace_with_tags(&[CryptoRecoverySetupPage::from(initial_page).tag()]); } /// Update the success page for the given recovery key. @@ -276,7 +316,7 @@ mod imp { // sure of the SDK's recovery state at this point, not the Session's. if encryption.recovery().state() == SdkRecoveryState::Incomplete { self.navigation - .push_by_tag(CryptoRecoverySetupPage::Incomplete.as_ref()); + .push_by_tag(CryptoRecoverySetupPage::Incomplete.tag()); } else { self.emit_completed(); } @@ -424,7 +464,7 @@ mod imp { self.update_success(key); self.navigation - .push_by_tag(CryptoRecoverySetupPage::Success.as_ref()); + .push_by_tag(CryptoRecoverySetupPage::Success.tag()); } Err(error) => { error!("Could not re-enable account recovery: {error}"); @@ -458,7 +498,7 @@ mod imp { self.update_success(key); self.navigation - .push_by_tag(CryptoRecoverySetupPage::Success.as_ref()); + .push_by_tag(CryptoRecoverySetupPage::Success.tag()); } Err(error) => { error!("Could not reset account recovery key: {error}"); @@ -496,7 +536,7 @@ mod imp { self.update_success(key); self.navigation - .push_by_tag(CryptoRecoverySetupPage::Success.as_ref()); + .push_by_tag(CryptoRecoverySetupPage::Success.tag()); } Err(error) => { error!("Could not enable account recovery: {error}"); @@ -531,7 +571,7 @@ mod imp { fn show_reset(&self) { self.update_reset(); self.navigation - .push_by_tag(CryptoRecoverySetupPage::Reset.as_ref()); + .push_by_tag(CryptoRecoverySetupPage::Reset.tag()); } } } diff --git a/src/error_page.rs b/src/error_page.rs index d54caa85..a9ba0014 100644 --- a/src/error_page.rs +++ b/src/error_page.rs @@ -5,8 +5,7 @@ use gtk::glib; use crate::{APP_ID, toast}; /// The possible error subpages. -#[derive(Debug, Clone, Copy, strum::AsRefStr)] -#[strum(serialize_all = "kebab-case")] +#[derive(Debug, Clone, Copy)] pub enum ErrorSubpage { /// The page to present when there was an error with the secret API. Secret, @@ -14,6 +13,16 @@ pub enum ErrorSubpage { Session, } +impl ErrorSubpage { + /// The name of this page. + const fn name(self) -> &'static str { + match self { + Self::Secret => "secret", + Self::Session => "name", + } + } +} + mod imp { use glib::subclass::InitializingObject; @@ -74,14 +83,14 @@ mod imp { self.secret_error_page.set_description(Some(message)); self.stack - .set_visible_child_name(ErrorSubpage::Secret.as_ref()); + .set_visible_child_name(ErrorSubpage::Secret.name()); } /// Display the given session error. pub(super) fn display_session_error(&self, message: &str) { self.session_error_page.set_description(Some(message)); self.stack - .set_visible_child_name(ErrorSubpage::Session.as_ref()); + .set_visible_child_name(ErrorSubpage::Session.name()); } /// Copy the secret service override command to the clipboard. diff --git a/src/login/greeter.rs b/src/login/greeter.rs index 9bfa56f6..c8019c12 100644 --- a/src/login/greeter.rs +++ b/src/login/greeter.rs @@ -51,6 +51,9 @@ glib::wrapper! { } impl Greeter { + /// The tag for this page. + pub(super) const TAG: &str = "greeter"; + pub fn new() -> Self { glib::Object::new() } diff --git a/src/login/homeserver_page.rs b/src/login/homeserver_page.rs index 9e66537b..25cb1d70 100644 --- a/src/login/homeserver_page.rs +++ b/src/login/homeserver_page.rs @@ -282,6 +282,9 @@ glib::wrapper! { } impl LoginHomeserverPage { + /// The tag for this page. + pub(super) const TAG: &str = "homeserver"; + pub fn new() -> Self { glib::Object::new() } diff --git a/src/login/in_browser_page.rs b/src/login/in_browser_page.rs index e546f096..382c5f06 100644 --- a/src/login/in_browser_page.rs +++ b/src/login/in_browser_page.rs @@ -247,6 +247,9 @@ glib::wrapper! { } impl LoginInBrowserPage { + /// The tag for this page. + pub(super) const TAG: &str = "in-browser"; + pub fn new() -> Self { glib::Object::new() } diff --git a/src/login/method_page.rs b/src/login/method_page.rs index 8722a984..4dea9210 100644 --- a/src/login/method_page.rs +++ b/src/login/method_page.rs @@ -180,6 +180,9 @@ glib::wrapper! { } impl LoginMethodPage { + /// The tag for this page. + pub(super) const TAG: &str = "method"; + pub fn new() -> Self { glib::Object::new() } diff --git a/src/login/mod.rs b/src/login/mod.rs index a0869a6d..17850246 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -40,8 +40,7 @@ use crate::{ }; /// A page of the login stack. -#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumString, strum::AsRefStr)] -#[strum(serialize_all = "kebab-case")] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum LoginPage { /// The greeter page. Greeter, @@ -57,6 +56,35 @@ enum LoginPage { Completed, } +impl LoginPage { + /// Get the tag for this page. + const fn tag(self) -> &'static str { + match self { + Self::Greeter => Greeter::TAG, + Self::Homeserver => LoginHomeserverPage::TAG, + Self::Method => LoginMethodPage::TAG, + Self::InBrowser => LoginInBrowserPage::TAG, + Self::SessionSetup => SessionSetupView::TAG, + Self::Completed => "completed", + } + } + + /// Get the page matching the given tag. + /// + /// Panics if the tag does not match any of the variants. + fn from_tag(tag: &str) -> Self { + match tag { + Greeter::TAG => Self::Greeter, + LoginHomeserverPage::TAG => Self::Homeserver, + LoginMethodPage::TAG => Self::Method, + LoginInBrowserPage::TAG => Self::InBrowser, + SessionSetupView::TAG => Self::SessionSetup, + "completed" => Self::Completed, + _ => panic!("Unknown LoginPage: {tag}"), + } + } +} + mod imp { use std::cell::{Cell, RefCell}; @@ -175,11 +203,14 @@ mod imp { impl Login { /// The visible page of the view. pub(super) fn visible_page(&self) -> LoginPage { - self.navigation - .visible_page() - .and_then(|p| p.tag()) - .and_then(|s| s.as_str().try_into().ok()) - .unwrap() + LoginPage::from_tag( + &self + .navigation + .visible_page() + .expect("Login navigation view should always have a visible page") + .tag() + .expect("Login navigation page should always have a tag"), + ) } /// Set whether auto-discovery is enabled. @@ -195,7 +226,7 @@ mod imp { /// Get the session setup view, if any. pub(super) fn session_setup(&self) -> Option { self.navigation - .find_page(LoginPage::SessionSetup.as_ref()) + .find_page(LoginPage::SessionSetup.tag()) .and_downcast() } @@ -377,7 +408,7 @@ mod imp { ) { self.method_page .update(homeserver, server_name, supports_sso); - self.navigation.push_by_tag(LoginPage::Method.as_ref()); + self.navigation.push_by_tag(LoginPage::Method.tag()); } /// Show the page to log in with the browser with the given data. @@ -387,7 +418,7 @@ mod imp { data: LoginInBrowserData, ) { self.in_browser_page.set_up(local_server_handle, data); - self.navigation.push_by_tag(LoginPage::InBrowser.as_ref()); + self.navigation.push_by_tag(LoginPage::InBrowser.tag()); } /// Create the session after a successful login. @@ -414,7 +445,7 @@ mod imp { #[weak(rename_to = imp)] self, move |_| { - imp.navigation.push_by_tag(LoginPage::Completed.as_ref()); + imp.navigation.push_by_tag(LoginPage::Completed.tag()); } )); self.navigation.push(&setup_view); @@ -465,7 +496,7 @@ mod imp { self.drop_session(); // Reinitialize UI. - self.navigation.pop_to_tag(LoginPage::Greeter.as_ref()); + self.navigation.pop_to_tag(LoginPage::Greeter.tag()); self.unfreeze(); } diff --git a/src/login/session_setup_view.rs b/src/login/session_setup_view.rs index 45508983..dc75aa4e 100644 --- a/src/login/session_setup_view.rs +++ b/src/login/session_setup_view.rs @@ -13,8 +13,7 @@ use crate::{ }; /// A page of the session setup stack. -#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumString, strum::AsRefStr)] -#[strum(serialize_all = "kebab-case")] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum SessionSetupPage { /// The loading page. Loading, @@ -24,6 +23,29 @@ enum SessionSetupPage { Recovery, } +impl SessionSetupPage { + /// Get the name of this page. + const fn name(self) -> &'static str { + match self { + Self::Loading => "loading", + Self::CryptoIdentity => "crypto-identity", + Self::Recovery => "recovery", + } + } + + /// Get the page matching the given name. + /// + /// Panics if the name does not match any of the variants. + fn from_name(name: &str) -> Self { + match name { + "loading" => Self::Loading, + "crypto-identity" => Self::CryptoIdentity, + "recovery" => Self::Recovery, + _ => panic!("Unknown SessionSetupPage: {name}"), + } + } +} + mod imp { use std::{ cell::{OnceCell, RefCell}, @@ -113,10 +135,12 @@ mod imp { impl SessionSetupView { /// The visible page of the stack. fn visible_stack_page(&self) -> SessionSetupPage { - self.stack - .visible_child_name() - .and_then(|n| n.as_str().try_into().ok()) - .unwrap() + SessionSetupPage::from_name( + &self + .stack + .visible_child_name() + .expect("SessionSetupView stack should always have a visible child name"), + ) } /// The crypto identity view. @@ -280,10 +304,10 @@ mod imp { self.stack.add_named( crypto_identity_view, - Some(SessionSetupPage::CryptoIdentity.as_ref()), + Some(SessionSetupPage::CryptoIdentity.name()), ); self.stack - .set_visible_child_name(SessionSetupPage::CryptoIdentity.as_ref()); + .set_visible_child_name(SessionSetupPage::CryptoIdentity.name()); } else { self.switch_to_recovery(); } @@ -313,9 +337,9 @@ mod imp { let recovery_view = self.recovery_view(); self.stack - .add_named(recovery_view, Some(SessionSetupPage::Recovery.as_ref())); + .add_named(recovery_view, Some(SessionSetupPage::Recovery.name())); self.stack - .set_visible_child_name(SessionSetupPage::Recovery.as_ref()); + .set_visible_child_name(SessionSetupPage::Recovery.name()); } /// Focus the proper widget for the current page. @@ -343,6 +367,9 @@ glib::wrapper! { } impl SessionSetupView { + /// The tag for this page. + pub(super) const TAG: &str = "session-setup"; + pub fn new(session: &Session) -> Self { glib::Object::builder().property("session", session).build() } diff --git a/src/session/notifications/notifications_settings.rs b/src/session/notifications/notifications_settings.rs index 70bf0457..322cd6e7 100644 --- a/src/session/notifications/notifications_settings.rs +++ b/src/session/notifications/notifications_settings.rs @@ -22,11 +22,8 @@ use crate::{ }; /// The possible values for the global notifications setting. -#[derive( - Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum, strum::Display, strum::EnumString, -)] +#[derive(Debug, Default, Eq, PartialEq, Clone, Copy, glib::Enum)] #[enum_type(name = "NotificationsGlobalSetting")] -#[strum(serialize_all = "kebab-case")] pub enum NotificationsGlobalSetting { /// Every message in every room. #[default] @@ -37,10 +34,32 @@ pub enum NotificationsGlobalSetting { MentionsOnly, } +impl NotificationsGlobalSetting { + /// Get the string representation of this value. + pub(crate) fn as_str(self) -> &'static str { + match self { + Self::All => "all", + Self::DirectAndMentions => "direct-and-mentions", + Self::MentionsOnly => "mentions-only", + } + } + + /// Construct a `NotificationsGlobalSetting` from its string representation. + /// + /// Panics if the string does not match a variant of this enum. + pub(crate) fn from_str(s: &str) -> Self { + match s { + "all" => Self::All, + "direct-and-mentions" => Self::DirectAndMentions, + "mentions-only" => Self::MentionsOnly, + _ => panic!("Unknown NotificationsGlobalSetting: {s}"), + } + } +} + /// The possible values for a room notifications setting. -#[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum, strum::EnumString)] +#[derive(Debug, Default, Eq, PartialEq, Clone, Copy, glib::Enum)] #[enum_type(name = "NotificationsRoomSetting")] -#[strum(serialize_all = "kebab-case")] pub enum NotificationsRoomSetting { /// Use the global setting. #[default] diff --git a/src/session/room/member_list.rs b/src/session/room/member_list.rs index 04ab63ec..344cc4ad 100644 --- a/src/session/room/member_list.rs +++ b/src/session/room/member_list.rs @@ -322,11 +322,8 @@ impl MemberList { /// The kind of membership used to filter a list of room members. /// /// This is a subset of [`Membership`]. -#[derive( - Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum, glib::Variant, strum::AsRefStr, -)] +#[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum, glib::Variant)] #[enum_type(name = "MembershipListKind")] -#[strum(serialize_all = "lowercase")] pub enum MembershipListKind { /// The user is currently in the room. #[default] @@ -358,8 +355,18 @@ impl MembershipListKind { .upcast() } + /// The tag to use for pages that present this kind. + pub(crate) const fn tag(self) -> &'static str { + match self { + Self::Join => "join", + Self::Invite => "invite", + Self::Ban => "ban", + Self::Knock => "knock", + } + } + /// The name of the icon that represents this kind. - pub(crate) fn icon_name(self) -> &'static str { + pub(crate) const fn icon_name(self) -> &'static str { match self { Self::Join | Self::Knock => "users-symbolic", Self::Invite => "user-add-symbolic", diff --git a/src/session/session_settings.rs b/src/session/session_settings.rs index 8122577b..4d82edc4 100644 --- a/src/session/session_settings.rs +++ b/src/session/session_settings.rs @@ -317,8 +317,8 @@ pub(super) struct MediaPreviewsSetting { /// previews. /// /// Legacy setting from version 0 of the stored settings. -#[derive(Debug, Clone, Default, strum::EnumString)] -#[strum(serialize_all = "lowercase")] +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(rename_all = "lowercase")] pub(super) enum MediaPreviewsGlobalSetting { /// All rooms show media previews. All, @@ -329,16 +329,6 @@ pub(super) enum MediaPreviewsGlobalSetting { None, } -impl<'de> Deserialize<'de> for MediaPreviewsGlobalSetting { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let cow = ruma::serde::deserialize_cow_str(deserializer)?; - cow.parse().map_err(serde::de::Error::custom) - } -} - impl From for MediaPreviews { fn from(value: MediaPreviewsGlobalSetting) -> Self { match value { diff --git a/src/session_view/content.rs b/src/session_view/content.rs index 7d3756ae..ddea587b 100644 --- a/src/session_view/content.rs +++ b/src/session_view/content.rs @@ -11,8 +11,7 @@ use crate::{ }; /// A page of the content stack. -#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumString, strum::AsRefStr)] -#[strum(serialize_all = "kebab-case")] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ContentPage { /// The placeholder page when no content is presented. Empty, @@ -28,6 +27,35 @@ enum ContentPage { Verification, } +impl ContentPage { + /// The name of this page. + const fn name(self) -> &'static str { + match self { + Self::Empty => "empty", + Self::RoomHistory => "room-history", + Self::InviteRequest => "invite-request", + Self::Invite => "invite", + Self::Explore => "explore", + Self::Verification => "verification", + } + } + + /// Get the page matching the given name. + /// + /// Panics if the name does not match any of the variants. + fn from_name(name: &str) -> Self { + match name { + "empty" => Self::Empty, + "room-history" => Self::RoomHistory, + "invite-request" => Self::InviteRequest, + "invite" => Self::Invite, + "explore" => Self::Explore, + "verification" => Self::Verification, + _ => panic!("Unknown ContentPage: {name}"), + } + } +} + mod imp { use std::cell::{Cell, RefCell}; @@ -123,12 +151,12 @@ mod imp { impl Content { /// The visible page of the content. pub(super) fn visible_page(&self) -> ContentPage { - self.stack - .visible_child_name() - .expect("stack should always have a visible child name") - .as_str() - .try_into() - .expect("stack child name should be convertible to a ContentPage") + ContentPage::from_name( + &self + .stack + .visible_child_name() + .expect("Content stack should always have a visible child name"), + ) } /// Set the visible page of the content. @@ -137,7 +165,7 @@ mod imp { return; } - self.stack.set_visible_child_name(page.as_ref()); + self.stack.set_visible_child_name(page.name()); } /// Set the current session. diff --git a/src/session_view/create_direct_chat_dialog/mod.rs b/src/session_view/create_direct_chat_dialog/mod.rs index 6308b97e..39f2c1b3 100644 --- a/src/session_view/create_direct_chat_dialog/mod.rs +++ b/src/session_view/create_direct_chat_dialog/mod.rs @@ -13,8 +13,7 @@ use crate::{ }; /// A page of the [`CreateDirectChatDialog`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::AsRefStr)] -#[strum(serialize_all = "kebab-case")] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum CreateDirectChatDialogPage { /// The page when there is no search term. NoSearchTerm, @@ -28,6 +27,19 @@ enum CreateDirectChatDialogPage { Error, } +impl CreateDirectChatDialogPage { + /// Get the name of this page. + const fn name(self) -> &'static str { + match self { + Self::NoSearchTerm => "no-search-term", + Self::Loading => "loading", + Self::Results => "results", + Self::Empty => "empty", + Self::Error => "error", + } + } +} + mod imp { use glib::subclass::InitializingObject; @@ -129,7 +141,7 @@ mod imp { /// Set the visible page of the dialog. fn set_visible_page(&self, page: CreateDirectChatDialogPage) { - self.stack.set_visible_child_name(page.as_ref()); + self.stack.set_visible_child_name(page.name()); } /// Update the view for the current state of the user list. diff --git a/src/session_view/room_details/members_page/members_list_view/mod.rs b/src/session_view/room_details/members_page/members_list_view/mod.rs index 8440153f..64e9059e 100644 --- a/src/session_view/room_details/members_page/members_list_view/mod.rs +++ b/src/session_view/room_details/members_page/members_list_view/mod.rs @@ -177,7 +177,7 @@ mod imp { /// Set the kind of the membership list. fn set_kind(&self, kind: MembershipListKind) { self.kind.set(kind); - self.obj().set_tag(Some(kind.as_ref())); + self.obj().set_tag(Some(kind.tag())); self.update_empty_page(); } diff --git a/src/session_view/room_details/members_page/mod.rs b/src/session_view/room_details/members_page/mod.rs index ae61fe8c..b3ee6e27 100644 --- a/src/session_view/room_details/members_page/mod.rs +++ b/src/session_view/room_details/members_page/mod.rs @@ -69,7 +69,7 @@ mod imp { impl MembersPage { /// Show the subpage for the list with the given membership. pub(super) fn show_membership_list(&self, kind: MembershipListKind) { - let tag = kind.as_ref(); + let tag = kind.tag(); if self.navigation_view.find_page(tag).is_some() { self.navigation_view.push_by_tag(tag); diff --git a/src/session_view/room_history/message_toolbar/mod.rs b/src/session_view/room_history/message_toolbar/mod.rs index 60434a36..05e7a5fa 100644 --- a/src/session_view/room_history/message_toolbar/mod.rs +++ b/src/session_view/room_history/message_toolbar/mod.rs @@ -54,8 +54,7 @@ use crate::{ type ComposerStatesMap = HashMap, HashMap, ComposerState>>; /// The available stack pages of the [`MessageToolbar`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::AsRefStr, strum::EnumString)] -#[strum(serialize_all = "kebab-case")] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum MessageToolbarPage { /// The composer and other buttons to send messages. Composer, @@ -65,6 +64,29 @@ enum MessageToolbarPage { Tombstoned, } +impl MessageToolbarPage { + /// The name of this page. + const fn name(self) -> &'static str { + match self { + Self::Composer => "composer", + Self::NoPermission => "no-permission", + Self::Tombstoned => "tombstoned", + } + } + + /// Get the page matching the given name. + /// + /// Panics if the name does not match any variant. + fn from_name(name: &str) -> Self { + match name { + "composer" => Self::Composer, + "no-permission" => Self::NoPermission, + "tombstoned" => Self::Tombstoned, + _ => panic!("Unknown MessageToolbarPage: {name}"), + } + } +} + mod imp { use std::{ cell::{Cell, RefCell}, @@ -195,7 +217,7 @@ mod imp { let Some(visible_page) = self .main_stack .visible_child_name() - .and_then(|name| MessageToolbarPage::try_from(name.as_str()).ok()) + .map(|name| MessageToolbarPage::from_name(&name)) else { return false; }; @@ -315,7 +337,7 @@ mod imp { /// Update the visible stack page. fn update_visible_page(&self) { self.main_stack - .set_visible_child_name(self.visible_page().as_ref()); + .set_visible_child_name(self.visible_page().name()); } /// Update the identifier to watch for the successor of the current diff --git a/src/window.rs b/src/window.rs index 2e115480..3340b8e3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -23,8 +23,7 @@ use crate::{ }; /// A page of the main window stack. -#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumString, strum::AsRefStr)] -#[strum(serialize_all = "kebab-case")] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum WindowPage { /// The loading page. Loading, @@ -36,6 +35,31 @@ enum WindowPage { Error, } +impl WindowPage { + /// Get the name of this page. + const fn name(self) -> &'static str { + match self { + Self::Loading => "loading", + Self::Login => "login", + Self::Session => "session", + Self::Error => "error", + } + } + + /// Get the page matching the given name. + /// + /// Panics if the name does not match any of the variants. + fn from_name(name: &str) -> Self { + match name { + "loading" => Self::Loading, + "login" => Self::Login, + "session" => Self::Session, + "error" => Self::Error, + _ => panic!("Unknown WindowPage: {name}"), + } + } +} + mod imp { use std::{cell::RefCell, rc::Rc}; @@ -339,12 +363,12 @@ mod imp { /// The visible page of the window. pub(super) fn visible_page(&self) -> WindowPage { - self.main_stack - .visible_child_name() - .expect("stack should always have a visible child name") - .as_str() - .try_into() - .expect("stack child name should be convertible to a WindowPage") + WindowPage::from_name( + &self + .main_stack + .visible_child_name() + .expect("stack should always have a visible child name"), + ) } /// The ID of the currently visible session, if any. @@ -447,8 +471,8 @@ mod imp { } /// Set the visible page of the window. - fn set_visible_page(&self, name: WindowPage) { - self.main_stack.set_visible_child_name(name.as_ref()); + fn set_visible_page(&self, page: WindowPage) { + self.main_stack.set_visible_child_name(page.name()); } /// Open the error page and display the given secret error message.