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
12const CURRENT_VERSION: u8 = 1;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[allow(clippy::struct_excessive_bools)]
17pub(crate) struct StoredSessionSettings {
18 #[serde(default)]
20 pub(super) version: u8,
21
22 #[serde(default, skip_serializing_if = "IndexSet::is_empty")]
24 explore_custom_servers: IndexSet<OwnedServerName>,
25
26 #[serde(
28 default = "ruma::serde::default_true",
29 skip_serializing_if = "ruma::serde::is_true"
30 )]
31 notifications_enabled: bool,
32
33 #[serde(
35 default = "ruma::serde::default_true",
36 skip_serializing_if = "ruma::serde::is_true"
37 )]
38 public_read_receipts_enabled: bool,
39
40 #[serde(
42 default = "ruma::serde::default_true",
43 skip_serializing_if = "ruma::serde::is_true"
44 )]
45 typing_enabled: bool,
46
47 #[serde(default)]
49 sections_expanded: SectionsExpanded,
50
51 #[serde(skip_serializing)]
55 pub(super) media_previews_enabled: Option<MediaPreviewsSetting>,
56
57 #[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 #[property(get, construct_only)]
92 session_id: OnceCell<String>,
93 pub(super) stored_settings: RefCell<StoredSessionSettings>,
95 #[property(get = Self::notifications_enabled, set = Self::set_notifications_enabled, explicit_notify, default = true)]
97 notifications_enabled: PhantomData<bool>,
98 #[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 #[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 fn notifications_enabled(&self) -> bool {
118 self.stored_settings.borrow().notifications_enabled
119 }
120
121 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 fn public_read_receipts_enabled(&self) -> bool {
134 self.stored_settings.borrow().public_read_receipts_enabled
135 }
136
137 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 fn typing_enabled(&self) -> bool {
152 self.stored_settings.borrow().typing_enabled
153 }
154
155 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 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 pub struct SessionSettings(ObjectSubclass<imp::SessionSettings>);
194}
195
196impl SessionSettings {
197 pub(crate) fn new(session_id: &str) -> Self {
199 glib::Object::builder()
200 .property("session-id", session_id)
201 .build()
202 }
203
204 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 pub(crate) fn stored_settings(&self) -> StoredSessionSettings {
214 self.imp().stored_settings.borrow().clone()
215 }
216
217 pub(crate) fn apply_version_1_migration(&self) {
219 self.imp().apply_version_1_migration();
220 }
221
222 pub(crate) fn delete(&self) {
224 session_list_settings().remove(&self.session_id());
225 }
226
227 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
271pub(crate) struct SectionsExpanded(BTreeSet<SidebarSectionName>);
272
273impl SectionsExpanded {
274 pub(crate) fn is_section_expanded(&self, section_name: SidebarSectionName) -> bool {
276 self.0.contains(§ion_name)
277 }
278
279 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(§ion_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#[derive(Debug, Clone, Default, Deserialize)]
310pub(super) struct MediaPreviewsSetting {
311 #[serde(default)]
313 pub(super) global: MediaPreviewsGlobalSetting,
314}
315
316#[derive(Debug, Clone, Default, Deserialize)]
321#[serde(rename_all = "lowercase")]
322pub(super) enum MediaPreviewsGlobalSetting {
323 All,
325 #[default]
327 Private,
328 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
342fn session_list_settings() -> SessionListSettings {
344 Application::default().session_list().settings()
345}