Skip to main content

fractal/session/
session_settings.rs

1use std::collections::BTreeSet;
2
3use gtk::{glib, prelude::*, subclass::prelude::*};
4use indexmap::IndexSet;
5use ruma::{OwnedServerName, events::media_preview_config::MediaPreviews};
6use serde::{Deserialize, Serialize};
7use tracing::info;
8
9use super::SidebarSectionName;
10use crate::{Application, session_list::SessionListSettings};
11
12/// The current version of the stored session settings.
13const CURRENT_VERSION: u8 = 1;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[allow(clippy::struct_excessive_bools)]
17pub(crate) struct StoredSessionSettings {
18    /// The version of the stored settings.
19    #[serde(default)]
20    pub(super) version: u8,
21
22    /// Custom servers to explore.
23    #[serde(default, skip_serializing_if = "IndexSet::is_empty")]
24    explore_custom_servers: IndexSet<OwnedServerName>,
25
26    /// Whether notifications are enabled for this session.
27    #[serde(
28        default = "ruma::serde::default_true",
29        skip_serializing_if = "ruma::serde::is_true"
30    )]
31    notifications_enabled: bool,
32
33    /// Whether public read receipts are enabled for this session.
34    #[serde(
35        default = "ruma::serde::default_true",
36        skip_serializing_if = "ruma::serde::is_true"
37    )]
38    public_read_receipts_enabled: bool,
39
40    /// Whether typing notifications are enabled for this session.
41    #[serde(
42        default = "ruma::serde::default_true",
43        skip_serializing_if = "ruma::serde::is_true"
44    )]
45    typing_enabled: bool,
46
47    /// The sections that are expanded.
48    #[serde(default)]
49    sections_expanded: SectionsExpanded,
50
51    /// Which rooms display media previews for this session.
52    ///
53    /// Legacy setting from version 0 of the stored settings.
54    #[serde(skip_serializing)]
55    pub(super) media_previews_enabled: Option<MediaPreviewsSetting>,
56
57    /// Whether to display avatars in invites.
58    ///
59    /// Legacy setting from version 0 of the stored settings.
60    #[serde(skip_serializing)]
61    pub(super) invite_avatars_enabled: Option<bool>,
62}
63
64impl Default for StoredSessionSettings {
65    fn default() -> Self {
66        Self {
67            version: CURRENT_VERSION,
68            explore_custom_servers: Default::default(),
69            notifications_enabled: true,
70            public_read_receipts_enabled: true,
71            typing_enabled: true,
72            sections_expanded: Default::default(),
73            media_previews_enabled: Default::default(),
74            invite_avatars_enabled: Default::default(),
75        }
76    }
77}
78
79mod imp {
80    use std::{
81        cell::{OnceCell, RefCell},
82        marker::PhantomData,
83    };
84
85    use super::*;
86
87    #[derive(Debug, Default, glib::Properties)]
88    #[properties(wrapper_type = super::SessionSettings)]
89    pub struct SessionSettings {
90        /// The ID of the session these settings are for.
91        #[property(get, construct_only)]
92        session_id: OnceCell<String>,
93        /// The stored settings.
94        pub(super) stored_settings: RefCell<StoredSessionSettings>,
95        /// Whether notifications are enabled for this session.
96        #[property(get = Self::notifications_enabled, set = Self::set_notifications_enabled, explicit_notify, default = true)]
97        notifications_enabled: PhantomData<bool>,
98        /// Whether public read receipts are enabled for this session.
99        #[property(get = Self::public_read_receipts_enabled, set = Self::set_public_read_receipts_enabled, explicit_notify, default = true)]
100        public_read_receipts_enabled: PhantomData<bool>,
101        /// Whether typing notifications are enabled for this session.
102        #[property(get = Self::typing_enabled, set = Self::set_typing_enabled, explicit_notify, default = true)]
103        typing_enabled: PhantomData<bool>,
104    }
105
106    #[glib::object_subclass]
107    impl ObjectSubclass for SessionSettings {
108        const NAME: &'static str = "SessionSettings";
109        type Type = super::SessionSettings;
110    }
111
112    #[glib::derived_properties]
113    impl ObjectImpl for SessionSettings {}
114
115    impl SessionSettings {
116        /// Whether notifications are enabled for this session.
117        fn notifications_enabled(&self) -> bool {
118            self.stored_settings.borrow().notifications_enabled
119        }
120
121        /// Set whether notifications are enabled for this session.
122        fn set_notifications_enabled(&self, enabled: bool) {
123            if self.notifications_enabled() == enabled {
124                return;
125            }
126
127            self.stored_settings.borrow_mut().notifications_enabled = enabled;
128            session_list_settings().save();
129            self.obj().notify_notifications_enabled();
130        }
131
132        /// Whether public read receipts are enabled for this session.
133        fn public_read_receipts_enabled(&self) -> bool {
134            self.stored_settings.borrow().public_read_receipts_enabled
135        }
136
137        /// Set whether public read receipts are enabled for this session.
138        fn set_public_read_receipts_enabled(&self, enabled: bool) {
139            if self.public_read_receipts_enabled() == enabled {
140                return;
141            }
142
143            self.stored_settings
144                .borrow_mut()
145                .public_read_receipts_enabled = enabled;
146            session_list_settings().save();
147            self.obj().notify_public_read_receipts_enabled();
148        }
149
150        /// Whether typing notifications are enabled for this session.
151        fn typing_enabled(&self) -> bool {
152            self.stored_settings.borrow().typing_enabled
153        }
154
155        /// Set whether typing notifications are enabled for this session.
156        fn set_typing_enabled(&self, enabled: bool) {
157            if self.typing_enabled() == enabled {
158                return;
159            }
160
161            self.stored_settings.borrow_mut().typing_enabled = enabled;
162            session_list_settings().save();
163            self.obj().notify_typing_enabled();
164        }
165
166        /// Apply the migration of the stored settings from version 0 to version
167        /// 1.
168        pub(crate) fn apply_version_1_migration(&self) {
169            {
170                let mut stored_settings = self.stored_settings.borrow_mut();
171
172                if stored_settings.version > 0 {
173                    return;
174                }
175
176                info!(
177                    session = self.obj().session_id(),
178                    "Migrating store session to version 1"
179                );
180
181                stored_settings.media_previews_enabled.take();
182                stored_settings.invite_avatars_enabled.take();
183                stored_settings.version = 1;
184            }
185
186            session_list_settings().save();
187        }
188    }
189}
190
191glib::wrapper! {
192    /// The settings of a [`Session`](super::Session).
193    pub struct SessionSettings(ObjectSubclass<imp::SessionSettings>);
194}
195
196impl SessionSettings {
197    /// Create a new `SessionSettings` for the given session ID.
198    pub(crate) fn new(session_id: &str) -> Self {
199        glib::Object::builder()
200            .property("session-id", session_id)
201            .build()
202    }
203
204    /// Restore existing `SessionSettings` with the given session ID and stored
205    /// settings.
206    pub(crate) fn restore(session_id: &str, stored_settings: StoredSessionSettings) -> Self {
207        let obj = Self::new(session_id);
208        *obj.imp().stored_settings.borrow_mut() = stored_settings;
209        obj
210    }
211
212    /// The stored settings.
213    pub(crate) fn stored_settings(&self) -> StoredSessionSettings {
214        self.imp().stored_settings.borrow().clone()
215    }
216
217    /// Apply the migration of the stored settings from version 0 to version 1.
218    pub(crate) fn apply_version_1_migration(&self) {
219        self.imp().apply_version_1_migration();
220    }
221
222    /// Delete the settings from the application settings.
223    pub(crate) fn delete(&self) {
224        session_list_settings().remove(&self.session_id());
225    }
226
227    /// Custom servers to explore.
228    pub(crate) fn explore_custom_servers(&self) -> IndexSet<OwnedServerName> {
229        self.imp()
230            .stored_settings
231            .borrow()
232            .explore_custom_servers
233            .clone()
234    }
235
236    /// Set the custom servers to explore.
237    pub(crate) fn set_explore_custom_servers(&self, servers: IndexSet<OwnedServerName>) {
238        if self.explore_custom_servers() == servers {
239            return;
240        }
241
242        self.imp()
243            .stored_settings
244            .borrow_mut()
245            .explore_custom_servers = servers;
246        session_list_settings().save();
247    }
248
249    /// Whether the section with the given name is expanded.
250    pub(crate) fn is_section_expanded(&self, section_name: SidebarSectionName) -> bool {
251        self.imp()
252            .stored_settings
253            .borrow()
254            .sections_expanded
255            .is_section_expanded(section_name)
256    }
257
258    /// Set whether the section with the given name is expanded.
259    pub(crate) fn set_section_expanded(&self, section_name: SidebarSectionName, expanded: bool) {
260        self.imp()
261            .stored_settings
262            .borrow_mut()
263            .sections_expanded
264            .set_section_expanded(section_name, expanded);
265        session_list_settings().save();
266    }
267}
268
269/// The sections that are expanded.
270#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
271pub(crate) struct SectionsExpanded(BTreeSet<SidebarSectionName>);
272
273impl SectionsExpanded {
274    /// Whether the section with the given name is expanded.
275    pub(crate) fn is_section_expanded(&self, section_name: SidebarSectionName) -> bool {
276        self.0.contains(&section_name)
277    }
278
279    /// Set whether the section with the given name is expanded.
280    pub(crate) fn set_section_expanded(
281        &mut self,
282        section_name: SidebarSectionName,
283        expanded: bool,
284    ) {
285        if expanded {
286            self.0.insert(section_name);
287        } else {
288            self.0.remove(&section_name);
289        }
290    }
291}
292
293impl Default for SectionsExpanded {
294    fn default() -> Self {
295        Self(BTreeSet::from([
296            SidebarSectionName::VerificationRequest,
297            SidebarSectionName::InviteRequest,
298            SidebarSectionName::Invited,
299            SidebarSectionName::Favorite,
300            SidebarSectionName::Normal,
301            SidebarSectionName::LowPriority,
302        ]))
303    }
304}
305
306/// Setting about which rooms display media previews.
307///
308/// Legacy setting from version 0 of the stored settings.
309#[derive(Debug, Clone, Default, Deserialize)]
310pub(super) struct MediaPreviewsSetting {
311    /// The default setting for all rooms.
312    #[serde(default)]
313    pub(super) global: MediaPreviewsGlobalSetting,
314}
315
316/// Possible values of the global setting about which rooms display media
317/// previews.
318///
319/// Legacy setting from version 0 of the stored settings.
320#[derive(Debug, Clone, Default, Deserialize)]
321#[serde(rename_all = "lowercase")]
322pub(super) enum MediaPreviewsGlobalSetting {
323    /// All rooms show media previews.
324    All,
325    /// Only private rooms show media previews.
326    #[default]
327    Private,
328    /// No rooms show media previews.
329    None,
330}
331
332impl From<MediaPreviewsGlobalSetting> for MediaPreviews {
333    fn from(value: MediaPreviewsGlobalSetting) -> Self {
334        match value {
335            MediaPreviewsGlobalSetting::All => Self::On,
336            MediaPreviewsGlobalSetting::Private => Self::Private,
337            MediaPreviewsGlobalSetting::None => Self::Off,
338        }
339    }
340}
341
342/// The session list settings of the application.
343fn session_list_settings() -> SessionListSettings {
344    Application::default().session_list().settings()
345}