Skip to main content

cadmus_core/view/settings_editor/kinds/
general.rs

1//! Setting kinds for the General category.
2
3use super::{
4    InputSettingKind, SettingData, SettingIdentity, SettingKind, ToggleSettings, WidgetKind,
5};
6use crate::device::CURRENT_DEVICE;
7use crate::fl;
8use crate::frontlight::LightLevel;
9use crate::geolocation::Coordinates;
10use crate::i18n::I18nDisplay;
11use crate::settings::{Settings, StartupMode};
12use crate::view::{Bus, EntryId, EntryKind, Event, ToggleEvent, ViewId};
13use anyhow::Error;
14use std::fs;
15
16/// Language and locale selection setting
17pub struct Locale;
18
19impl SettingKind for Locale {
20    fn identity(&self) -> SettingIdentity {
21        SettingIdentity::Locale
22    }
23
24    fn label(&self, _settings: &Settings) -> String {
25        fl!("settings-general-language")
26    }
27
28    fn fetch(&self, settings: &Settings) -> SettingData {
29        let current = settings.locale.as_ref().map(|l| l.to_string());
30        let display = current
31            .as_deref()
32            .unwrap_or(crate::i18n::DEFAULT_LOCALE)
33            .to_string();
34
35        let entries = crate::i18n::AVAILABLE_LOCALES
36            .iter()
37            .map(|&tag| {
38                let lang_id: Option<unic_langid::LanguageIdentifier> = tag.parse().ok();
39                EntryKind::RadioButton(
40                    tag.to_string(),
41                    EntryId::SetLocale(lang_id),
42                    current.as_deref() == Some(tag),
43                )
44            })
45            .collect::<Vec<_>>();
46
47        SettingData {
48            value: display,
49            widget: WidgetKind::SubMenu(entries),
50        }
51    }
52
53    fn handle(
54        &self,
55        evt: &Event,
56        settings: &mut Settings,
57        _bus: &mut Bus,
58    ) -> (Option<String>, bool) {
59        if let Event::Select(EntryId::SetLocale(locale)) = evt {
60            settings.locale = locale.clone();
61            crate::i18n::init(locale.as_ref());
62            let display = locale
63                .as_ref()
64                .map(|l| l.to_string())
65                .unwrap_or_else(|| crate::i18n::DEFAULT_LOCALE.to_string());
66            return (Some(display), true);
67        }
68        (None, false)
69    }
70}
71
72/// Keyboard layout selection setting
73pub struct KeyboardLayout;
74
75impl SettingKind for KeyboardLayout {
76    fn identity(&self) -> SettingIdentity {
77        SettingIdentity::KeyboardLayout
78    }
79
80    fn label(&self, _settings: &Settings) -> String {
81        fl!("settings-general-keyboard-layout")
82    }
83
84    fn fetch(&self, settings: &Settings) -> SettingData {
85        let current_layout = settings.keyboard_layout.clone();
86        let available_layouts = get_available_layouts().unwrap_or_default();
87
88        let entries = available_layouts
89            .iter()
90            .map(|layout| {
91                EntryKind::RadioButton(
92                    layout.clone(),
93                    EntryId::SetKeyboardLayout(layout.clone()),
94                    current_layout == *layout,
95                )
96            })
97            .collect::<Vec<_>>();
98
99        SettingData {
100            value: current_layout,
101            widget: WidgetKind::SubMenu(entries),
102        }
103    }
104
105    fn handle(
106        &self,
107        evt: &Event,
108        settings: &mut Settings,
109        _bus: &mut Bus,
110    ) -> (Option<String>, bool) {
111        if let Event::Select(EntryId::SetKeyboardLayout(layout)) = evt {
112            settings.keyboard_layout = layout.clone();
113            return (Some(layout.clone()), true);
114        }
115        (None, false)
116    }
117}
118
119/// Auto suspend timeout setting
120pub struct AutoSuspend;
121
122impl SettingKind for AutoSuspend {
123    fn identity(&self) -> SettingIdentity {
124        SettingIdentity::AutoSuspend
125    }
126
127    fn label(&self, _settings: &Settings) -> String {
128        fl!("settings-general-auto-suspend")
129    }
130
131    fn fetch(&self, settings: &Settings) -> SettingData {
132        let value = if settings.auto_suspend == 0.0 {
133            fl!("settings-general-never")
134        } else {
135            format!("{:.1}", settings.auto_suspend)
136        };
137
138        SettingData {
139            value,
140            widget: WidgetKind::ActionLabel(Event::Select(EntryId::EditAutoSuspend)),
141        }
142    }
143
144    fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
145        Some(self)
146    }
147}
148
149impl InputSettingKind for AutoSuspend {
150    fn submit_view_id(&self) -> ViewId {
151        ViewId::AutoSuspendInput
152    }
153
154    fn open_entry_id(&self) -> EntryId {
155        EntryId::EditAutoSuspend
156    }
157
158    fn input_label(&self) -> String {
159        fl!("settings-general-auto-suspend-input")
160    }
161
162    fn input_max_chars(&self) -> usize {
163        10
164    }
165
166    fn current_text(&self, settings: &Settings) -> String {
167        if settings.auto_suspend == 0.0 {
168            "0".to_string()
169        } else {
170            format!("{:.1}", settings.auto_suspend)
171        }
172    }
173
174    fn apply_text(&self, text: &str, settings: &mut Settings) -> String {
175        if let Ok(value) = text.parse::<f32>() {
176            settings.auto_suspend = value;
177        }
178        if settings.auto_suspend == 0.0 {
179            fl!("settings-general-never")
180        } else {
181            format!("{:.1}", settings.auto_suspend)
182        }
183    }
184}
185
186/// Auto power off timeout setting
187pub struct AutoPowerOff;
188
189impl SettingKind for AutoPowerOff {
190    fn identity(&self) -> SettingIdentity {
191        SettingIdentity::AutoPowerOff
192    }
193
194    fn label(&self, _settings: &Settings) -> String {
195        fl!("settings-general-auto-power-off")
196    }
197
198    fn fetch(&self, settings: &Settings) -> SettingData {
199        let value = if settings.auto_power_off == 0.0 {
200            fl!("settings-general-never")
201        } else {
202            format!("{:.1}", settings.auto_power_off)
203        };
204
205        SettingData {
206            value,
207            widget: WidgetKind::ActionLabel(Event::Select(EntryId::EditAutoPowerOff)),
208        }
209    }
210
211    fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
212        Some(self)
213    }
214}
215
216impl InputSettingKind for AutoPowerOff {
217    fn submit_view_id(&self) -> ViewId {
218        ViewId::AutoPowerOffInput
219    }
220
221    fn open_entry_id(&self) -> EntryId {
222        EntryId::EditAutoPowerOff
223    }
224
225    fn input_label(&self) -> String {
226        fl!("settings-general-auto-power-off-input")
227    }
228
229    fn input_max_chars(&self) -> usize {
230        10
231    }
232
233    fn current_text(&self, settings: &Settings) -> String {
234        if settings.auto_power_off == 0.0 {
235            "0".to_string()
236        } else {
237            format!("{:.1}", settings.auto_power_off)
238        }
239    }
240
241    fn apply_text(&self, text: &str, settings: &mut Settings) -> String {
242        if let Ok(value) = text.parse::<f32>() {
243            settings.auto_power_off = value;
244        }
245        if settings.auto_power_off == 0.0 {
246            fl!("settings-general-never")
247        } else {
248            format!("{:.1}", settings.auto_power_off)
249        }
250    }
251}
252
253/// Sleep cover enable/disable toggle setting
254pub struct SleepCover;
255
256impl SettingKind for SleepCover {
257    fn identity(&self) -> SettingIdentity {
258        SettingIdentity::SleepCover
259    }
260
261    fn label(&self, _settings: &Settings) -> String {
262        fl!("settings-general-enable-sleep-cover")
263    }
264
265    fn fetch(&self, settings: &Settings) -> SettingData {
266        SettingData {
267            value: settings.sleep_cover.to_string(),
268            widget: WidgetKind::Toggle {
269                left_label: fl!("settings-general-toggle-on"),
270                right_label: fl!("settings-general-toggle-off"),
271                enabled: settings.sleep_cover,
272                tap_event: Event::Toggle(ToggleEvent::Setting(ToggleSettings::SleepCover)),
273            },
274        }
275    }
276
277    fn handle(
278        &self,
279        evt: &Event,
280        settings: &mut Settings,
281        _bus: &mut Bus,
282    ) -> (Option<String>, bool) {
283        if let Event::Toggle(ToggleEvent::Setting(ToggleSettings::SleepCover)) = evt {
284            settings.sleep_cover = !settings.sleep_cover;
285            return (Some(settings.sleep_cover.to_string()), true);
286        }
287        (None, false)
288    }
289}
290
291/// Auto share enable/disable toggle setting
292pub struct AutoShare;
293
294impl SettingKind for AutoShare {
295    fn identity(&self) -> SettingIdentity {
296        SettingIdentity::AutoShare
297    }
298
299    fn label(&self, _settings: &Settings) -> String {
300        fl!("settings-general-enable-auto-share")
301    }
302
303    fn fetch(&self, settings: &Settings) -> SettingData {
304        SettingData {
305            value: settings.auto_share.to_string(),
306            widget: WidgetKind::Toggle {
307                left_label: fl!("settings-general-toggle-on"),
308                right_label: fl!("settings-general-toggle-off"),
309                enabled: settings.auto_share,
310                tap_event: Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoShare)),
311            },
312        }
313    }
314
315    fn handle(
316        &self,
317        evt: &Event,
318        settings: &mut Settings,
319        _bus: &mut Bus,
320    ) -> (Option<String>, bool) {
321        if let Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoShare)) = evt {
322            settings.auto_share = !settings.auto_share;
323            return (Some(settings.auto_share.to_string()), true);
324        }
325        (None, false)
326    }
327}
328
329/// Auto time sync enable/disable toggle setting
330pub struct AutoTime;
331
332impl SettingKind for AutoTime {
333    fn identity(&self) -> SettingIdentity {
334        SettingIdentity::AutoTime
335    }
336
337    fn label(&self, _settings: &Settings) -> String {
338        fl!("settings-general-auto-time")
339    }
340
341    fn fetch(&self, settings: &Settings) -> SettingData {
342        SettingData {
343            value: settings.auto_time.to_string(),
344            widget: WidgetKind::Toggle {
345                left_label: fl!("settings-general-toggle-on"),
346                right_label: fl!("settings-general-toggle-off"),
347                enabled: settings.auto_time,
348                tap_event: Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoTime)),
349            },
350        }
351    }
352
353    fn handle(
354        &self,
355        evt: &Event,
356        settings: &mut Settings,
357        _bus: &mut Bus,
358    ) -> (Option<String>, bool) {
359        if let Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoTime)) = evt {
360            settings.auto_time = !settings.auto_time;
361            return (Some(settings.auto_time.to_string()), true);
362        }
363        (None, false)
364    }
365}
366
367/// Button scheme (natural/inverted) toggle setting
368pub struct ButtonScheme;
369
370impl SettingKind for ButtonScheme {
371    fn identity(&self) -> SettingIdentity {
372        SettingIdentity::ButtonScheme
373    }
374
375    fn label(&self, _settings: &Settings) -> String {
376        fl!("settings-general-button-scheme")
377    }
378
379    fn fetch(&self, settings: &Settings) -> SettingData {
380        let enabled = settings.button_scheme == crate::settings::ButtonScheme::Natural;
381        SettingData {
382            value: settings.button_scheme.to_i18n_string(),
383            widget: WidgetKind::Toggle {
384                left_label: crate::settings::ButtonScheme::Natural.to_i18n_string(),
385                right_label: crate::settings::ButtonScheme::Inverted.to_i18n_string(),
386                enabled,
387                tap_event: Event::Toggle(ToggleEvent::Setting(ToggleSettings::ButtonScheme)),
388            },
389        }
390    }
391
392    fn handle(
393        &self,
394        evt: &Event,
395        settings: &mut Settings,
396        bus: &mut Bus,
397    ) -> (Option<String>, bool) {
398        let new_scheme = match evt {
399            Event::Toggle(ToggleEvent::Setting(ToggleSettings::ButtonScheme)) => {
400                match settings.button_scheme {
401                    crate::settings::ButtonScheme::Natural => {
402                        Some(crate::settings::ButtonScheme::Inverted)
403                    }
404                    crate::settings::ButtonScheme::Inverted => {
405                        Some(crate::settings::ButtonScheme::Natural)
406                    }
407                }
408            }
409            Event::Select(EntryId::SetButtonScheme(scheme)) => Some(*scheme),
410            _ => None,
411        };
412
413        if let Some(scheme) = new_scheme {
414            settings.button_scheme = scheme;
415            bus.push_back(Event::Select(EntryId::SetButtonScheme(scheme)));
416            return (Some(settings.button_scheme.to_i18n_string()), true);
417        }
418        (None, false)
419    }
420}
421
422/// Setting kind for toggling automatic frontlight adjustment.
423///
424/// Changing this setting triggers a re-evaluation of the active frontlight
425/// configuration so the device can immediately react to the new mode.
426pub struct AutoFrontlight;
427
428impl SettingKind for AutoFrontlight {
429    fn identity(&self) -> SettingIdentity {
430        SettingIdentity::AutoFrontlight
431    }
432
433    fn label(&self, _settings: &Settings) -> String {
434        fl!("settings-general-auto-frontlight")
435    }
436
437    fn fetch(&self, settings: &Settings) -> SettingData {
438        SettingData {
439            value: settings.auto_frontlight.to_string(),
440            widget: WidgetKind::Toggle {
441                left_label: fl!("settings-general-toggle-on"),
442                right_label: fl!("settings-general-toggle-off"),
443                enabled: settings.auto_frontlight,
444                tap_event: Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoFrontlight)),
445            },
446        }
447    }
448
449    fn handle(
450        &self,
451        evt: &Event,
452        settings: &mut Settings,
453        bus: &mut Bus,
454    ) -> (Option<String>, bool) {
455        if let Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoFrontlight)) = evt {
456            settings.auto_frontlight = !settings.auto_frontlight;
457            bus.push_back(Event::AutoFrontlightConfigChanged);
458            return (Some(settings.auto_frontlight.to_string()), true);
459        }
460        (None, false)
461    }
462}
463
464/// Setting kind for configuring the brightness used while the sun is down.
465///
466/// This value is applied by automatic frontlight mode after sunset and before
467/// sunrise.
468pub struct AutoFrontlightBrightness;
469
470impl SettingKind for AutoFrontlightBrightness {
471    fn identity(&self) -> SettingIdentity {
472        SettingIdentity::AutoFrontlightBrightness
473    }
474
475    fn label(&self, _settings: &Settings) -> String {
476        fl!("settings-general-auto-frontlight-brightness")
477    }
478
479    fn fetch(&self, settings: &Settings) -> SettingData {
480        SettingData {
481            value: self.display_value(settings),
482            widget: WidgetKind::ActionLabel(Event::Select(EntryId::EditAutoFrontlightBrightness)),
483        }
484    }
485
486    fn handle(
487        &self,
488        evt: &Event,
489        settings: &mut Settings,
490        bus: &mut Bus,
491    ) -> (Option<String>, bool) {
492        if let Event::Submit(ViewId::AutoFrontlightBrightnessInput, text) = evt {
493            let display = self.apply_text(text, settings);
494            bus.push_back(Event::AutoFrontlightConfigChanged);
495            return (Some(display), true);
496        }
497
498        (None, false)
499    }
500
501    fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
502        Some(self)
503    }
504}
505
506impl AutoFrontlightBrightness {
507    fn display_value(&self, settings: &Settings) -> String {
508        settings
509            .auto_frontlight_night_brightness
510            .map(|brightness| brightness.to_string())
511            .unwrap_or_else(|| LightLevel::default().to_string())
512    }
513}
514
515impl InputSettingKind for AutoFrontlightBrightness {
516    fn submit_view_id(&self) -> ViewId {
517        ViewId::AutoFrontlightBrightnessInput
518    }
519
520    fn open_entry_id(&self) -> EntryId {
521        EntryId::EditAutoFrontlightBrightness
522    }
523
524    fn input_label(&self) -> String {
525        fl!("settings-general-auto-frontlight-brightness-input")
526    }
527
528    fn input_max_chars(&self) -> usize {
529        3
530    }
531
532    fn current_text(&self, settings: &Settings) -> String {
533        settings
534            .auto_frontlight_night_brightness
535            .map(|b| b.into())
536            .unwrap_or_else(|| LightLevel::default().into())
537    }
538
539    fn apply_text(&self, text: &str, settings: &mut Settings) -> String {
540        if let Ok(value) = text.trim().parse::<f32>() {
541            settings.auto_frontlight_night_brightness = Some(value.into());
542        }
543        self.display_value(settings)
544    }
545}
546
547/// Setting kind for overriding automatic frontlight coordinates manually.
548///
549/// Users can enter a `latitude, longitude` pair to control which sunrise and
550/// sunset times automatic frontlight should follow.
551pub struct AutoFrontlightManualCoordinates;
552
553impl SettingKind for AutoFrontlightManualCoordinates {
554    fn identity(&self) -> SettingIdentity {
555        SettingIdentity::AutoFrontlightManualCoordinates
556    }
557
558    fn label(&self, _settings: &Settings) -> String {
559        fl!("settings-general-auto-frontlight-manual-coordinates")
560    }
561
562    fn fetch(&self, settings: &Settings) -> SettingData {
563        SettingData {
564            value: self.display_value(settings),
565            widget: WidgetKind::ActionLabel(Event::Select(
566                EntryId::EditAutoFrontlightManualCoordinates,
567            )),
568        }
569    }
570
571    fn handle(
572        &self,
573        evt: &Event,
574        settings: &mut Settings,
575        bus: &mut Bus,
576    ) -> (Option<String>, bool) {
577        if let Event::Submit(ViewId::AutoFrontlightManualCoordinatesInput, text) = evt {
578            let display = self.apply_text(text, settings);
579            bus.push_back(Event::AutoFrontlightConfigChanged);
580            return (Some(display), true);
581        }
582
583        (None, false)
584    }
585
586    fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
587        Some(self)
588    }
589}
590
591impl AutoFrontlightManualCoordinates {
592    fn display_value(&self, settings: &Settings) -> String {
593        settings
594            .auto_frontlight_manual_coordinates
595            .map(|coordinates| {
596                format!(
597                    "{:.4}, {:.4}",
598                    coordinates.latitude(),
599                    coordinates.longitude()
600                )
601            })
602            .unwrap_or_else(|| fl!("settings-general-not-set"))
603    }
604}
605
606impl InputSettingKind for AutoFrontlightManualCoordinates {
607    fn submit_view_id(&self) -> ViewId {
608        ViewId::AutoFrontlightManualCoordinatesInput
609    }
610
611    fn open_entry_id(&self) -> EntryId {
612        EntryId::EditAutoFrontlightManualCoordinates
613    }
614
615    fn input_label(&self) -> String {
616        fl!("settings-general-auto-frontlight-manual-coordinates-input")
617    }
618
619    fn input_max_chars(&self) -> usize {
620        32
621    }
622
623    fn current_text(&self, settings: &Settings) -> String {
624        settings
625            .auto_frontlight_manual_coordinates
626            .map(|coordinates| coordinates.to_string())
627            .unwrap_or_default()
628    }
629
630    fn apply_text(&self, text: &str, settings: &mut Settings) -> String {
631        let trimmed = text.trim();
632        if trimmed.is_empty() {
633            settings.auto_frontlight_manual_coordinates = None;
634            return self.display_value(settings);
635        }
636
637        let mut parts = trimmed.split(',').map(str::trim);
638        let parsed_coordinates =
639            parts
640                .next()
641                .zip(parts.next())
642                .and_then(|(latitude, longitude)| {
643                    let latitude = latitude.parse::<f64>().ok()?;
644                    let longitude = longitude.parse::<f64>().ok()?;
645                    Coordinates::new(latitude, longitude).ok()
646                });
647
648        if parsed_coordinates.is_some() && parts.next().is_none() {
649            settings.auto_frontlight_manual_coordinates = parsed_coordinates;
650        }
651
652        self.display_value(settings)
653    }
654}
655
656/// Settings retention count setting
657pub struct SettingsRetention;
658
659impl SettingKind for SettingsRetention {
660    fn identity(&self) -> SettingIdentity {
661        SettingIdentity::SettingsRetention
662    }
663
664    fn label(&self, _settings: &Settings) -> String {
665        fl!("settings-general-settings-retention")
666    }
667
668    fn fetch(&self, settings: &Settings) -> SettingData {
669        SettingData {
670            value: settings.settings_retention.to_string(),
671            widget: WidgetKind::ActionLabel(Event::Select(EntryId::EditSettingsRetention)),
672        }
673    }
674
675    fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
676        Some(self)
677    }
678}
679
680impl InputSettingKind for SettingsRetention {
681    fn submit_view_id(&self) -> ViewId {
682        ViewId::SettingsRetentionInput
683    }
684
685    fn open_entry_id(&self) -> EntryId {
686        EntryId::EditSettingsRetention
687    }
688
689    fn input_label(&self) -> String {
690        fl!("settings-general-settings-retention")
691    }
692
693    fn input_max_chars(&self) -> usize {
694        3
695    }
696
697    fn current_text(&self, settings: &Settings) -> String {
698        settings.settings_retention.to_string()
699    }
700
701    fn apply_text(&self, text: &str, settings: &mut Settings) -> String {
702        if let Ok(value) = text.parse::<usize>() {
703            settings.settings_retention = value;
704        }
705        settings.settings_retention.to_string()
706    }
707}
708
709/// Database backup retention count setting
710pub struct DbBackupRetentionSetting;
711
712impl SettingKind for DbBackupRetentionSetting {
713    fn identity(&self) -> SettingIdentity {
714        SettingIdentity::DbBackupRetention
715    }
716
717    fn label(&self, _settings: &Settings) -> String {
718        fl!("settings-general-db-backup-retention")
719    }
720
721    fn fetch(&self, settings: &Settings) -> SettingData {
722        SettingData {
723            value: settings.db_backup_retention.to_string(),
724            widget: WidgetKind::ActionLabel(Event::Select(EntryId::EditDbBackupRetention)),
725        }
726    }
727
728    fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
729        Some(self)
730    }
731}
732
733impl InputSettingKind for DbBackupRetentionSetting {
734    fn submit_view_id(&self) -> ViewId {
735        ViewId::DbBackupRetentionInput
736    }
737
738    fn open_entry_id(&self) -> EntryId {
739        EntryId::EditDbBackupRetention
740    }
741
742    fn input_label(&self) -> String {
743        fl!("settings-general-db-backup-retention-input")
744    }
745
746    fn input_max_chars(&self) -> usize {
747        3
748    }
749
750    fn current_text(&self, settings: &Settings) -> String {
751        settings.db_backup_retention.to_string()
752    }
753
754    fn apply_text(&self, text: &str, settings: &mut Settings) -> String {
755        if let Ok(value) = text.parse::<usize>() {
756            settings.db_backup_retention = value;
757        }
758        settings.db_backup_retention.to_string()
759    }
760}
761
762/// Startup mode selection setting (Home or Last File)
763pub struct StartupModeSetting;
764
765impl SettingKind for StartupModeSetting {
766    fn identity(&self) -> SettingIdentity {
767        SettingIdentity::StartupMode
768    }
769
770    fn label(&self, _settings: &Settings) -> String {
771        fl!("settings-general-startup-mode")
772    }
773
774    fn fetch(&self, settings: &Settings) -> SettingData {
775        let current = settings.startup_mode;
776        let entries = vec![
777            EntryKind::RadioButton(
778                StartupMode::Home.to_i18n_string(),
779                EntryId::SetStartupMode(StartupMode::Home),
780                current == StartupMode::Home,
781            ),
782            EntryKind::RadioButton(
783                StartupMode::LastFile.to_i18n_string(),
784                EntryId::SetStartupMode(StartupMode::LastFile),
785                current == StartupMode::LastFile,
786            ),
787        ];
788
789        SettingData {
790            value: current.to_i18n_string(),
791            widget: WidgetKind::SubMenu(entries),
792        }
793    }
794
795    fn handle(
796        &self,
797        evt: &Event,
798        settings: &mut Settings,
799        _bus: &mut Bus,
800    ) -> (Option<String>, bool) {
801        if let Event::Select(EntryId::SetStartupMode(mode)) = evt {
802            settings.startup_mode = *mode;
803            return (Some(mode.to_i18n_string()), true);
804        }
805        (None, false)
806    }
807}
808
809/// Scans the keyboard-layouts directory for available keyboard layouts
810fn get_available_layouts() -> Result<Vec<String>, Error> {
811    let layouts_dir = CURRENT_DEVICE.install_path("keyboard-layouts");
812    let mut layouts = Vec::new();
813
814    if layouts_dir.exists() {
815        for entry in fs::read_dir(&layouts_dir)? {
816            let entry = entry?;
817            let path = entry.path();
818
819            if path.extension().and_then(|s| s.to_str()) == Some("json") {
820                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
821                    let layout_name = stem
822                        .chars()
823                        .enumerate()
824                        .map(|(i, c)| {
825                            if i == 0 {
826                                c.to_uppercase().collect::<String>()
827                            } else {
828                                c.to_string()
829                            }
830                        })
831                        .collect::<String>();
832                    layouts.push(layout_name);
833                }
834            }
835        }
836    }
837
838    layouts.sort();
839    Ok(layouts)
840}
841
842#[cfg(test)]
843mod tests {
844    use super::*;
845    use crate::settings::Settings;
846    use crate::view::settings_editor::kinds::{InputSettingKind, ToggleSettings};
847    use crate::view::{Bus, EntryId, Event, ToggleEvent};
848    use std::collections::VecDeque;
849
850    mod locale {
851        use super::*;
852
853        #[test]
854        fn handle_set_locale_updates_settings() {
855            let setting = Locale;
856            let mut settings = Settings::default();
857            let mut bus: Bus = VecDeque::new();
858            let locale: Option<unic_langid::LanguageIdentifier> = Some("de-DE".parse().unwrap());
859            let event = Event::Select(EntryId::SetLocale(locale.clone()));
860
861            let result = setting.handle(&event, &mut settings, &mut bus);
862
863            assert!(result.0.is_some());
864            assert_eq!(result.0.unwrap(), "de-DE");
865            assert_eq!(settings.locale, locale);
866        }
867
868        #[test]
869        fn handle_returns_none_for_wrong_event() {
870            let setting = Locale;
871            let mut settings = Settings::default();
872            let mut bus: Bus = VecDeque::new();
873
874            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
875
876            assert!(result.0.is_none());
877        }
878    }
879
880    mod keyboard_layout {
881        use super::*;
882
883        #[test]
884        fn handle_set_layout_updates_settings() {
885            let setting = KeyboardLayout;
886            let mut settings = Settings::default();
887            let mut bus: Bus = VecDeque::new();
888            let event = Event::Select(EntryId::SetKeyboardLayout("German".to_string()));
889
890            let result = setting.handle(&event, &mut settings, &mut bus);
891
892            assert!(result.0.is_some());
893            assert_eq!(result.0.unwrap(), "German");
894            assert_eq!(settings.keyboard_layout, "German");
895        }
896
897        #[test]
898        fn handle_returns_none_for_wrong_event() {
899            let setting = KeyboardLayout;
900            let mut settings = Settings::default();
901            let mut bus: Bus = VecDeque::new();
902
903            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
904
905            assert!(result.0.is_none());
906        }
907    }
908
909    mod auto_suspend {
910        use super::*;
911
912        #[test]
913        fn apply_text_parses_and_updates() {
914            let setting = AutoSuspend;
915            let mut settings = Settings::default();
916
917            let display = setting.apply_text("60.0", &mut settings);
918
919            assert_eq!(display, "60.0");
920            assert_eq!(settings.auto_suspend, 60.0);
921        }
922
923        #[test]
924        fn apply_text_returns_never_for_zero() {
925            let setting = AutoSuspend;
926            let mut settings = Settings::default();
927
928            let display = setting.apply_text("0", &mut settings);
929
930            assert_eq!(display, "Never");
931            assert_eq!(settings.auto_suspend, 0.0);
932        }
933
934        #[test]
935        fn apply_text_ignores_invalid_input() {
936            let setting = AutoSuspend;
937            let mut settings = Settings {
938                auto_suspend: 30.0,
939                ..Default::default()
940            };
941
942            let display = setting.apply_text("invalid", &mut settings);
943
944            assert_eq!(settings.auto_suspend, 30.0);
945            assert_eq!(display, "30.0");
946        }
947    }
948
949    mod auto_power_off {
950        use super::*;
951
952        #[test]
953        fn apply_text_parses_and_updates() {
954            let setting = AutoPowerOff;
955            let mut settings = Settings::default();
956
957            let display = setting.apply_text("14.0", &mut settings);
958
959            assert_eq!(display, "14.0");
960            assert_eq!(settings.auto_power_off, 14.0);
961        }
962
963        #[test]
964        fn apply_text_returns_never_for_zero() {
965            let setting = AutoPowerOff;
966            let mut settings = Settings::default();
967
968            let display = setting.apply_text("0", &mut settings);
969
970            assert_eq!(display, "Never");
971            assert_eq!(settings.auto_power_off, 0.0);
972        }
973
974        #[test]
975        fn apply_text_ignores_invalid_input() {
976            let setting = AutoPowerOff;
977            let mut settings = Settings {
978                auto_power_off: 7.0,
979                ..Default::default()
980            };
981
982            let display = setting.apply_text("invalid", &mut settings);
983
984            assert_eq!(settings.auto_power_off, 7.0);
985            assert_eq!(display, "7.0");
986        }
987    }
988
989    mod sleep_cover {
990        use super::*;
991
992        #[test]
993        fn handle_toggle_event_toggles_value() {
994            let setting = SleepCover;
995            let mut settings = Settings {
996                sleep_cover: true,
997                ..Default::default()
998            };
999            let mut bus: Bus = VecDeque::new();
1000            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::SleepCover));
1001
1002            let result = setting.handle(&event, &mut settings, &mut bus);
1003
1004            assert!(result.0.is_some());
1005            assert_eq!(result.0.unwrap(), "false");
1006            assert!(!settings.sleep_cover);
1007        }
1008
1009        #[test]
1010        fn handle_returns_none_for_wrong_event() {
1011            let setting = SleepCover;
1012            let mut settings = Settings::default();
1013            let mut bus: Bus = VecDeque::new();
1014
1015            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
1016
1017            assert!(result.0.is_none());
1018        }
1019    }
1020
1021    mod auto_share {
1022        use super::*;
1023
1024        #[test]
1025        fn handle_toggle_event_toggles_value() {
1026            let setting = AutoShare;
1027            let mut settings = Settings {
1028                auto_share: false,
1029                ..Default::default()
1030            };
1031            let mut bus: Bus = VecDeque::new();
1032            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoShare));
1033
1034            let result = setting.handle(&event, &mut settings, &mut bus);
1035
1036            assert!(result.0.is_some());
1037            assert_eq!(result.0.unwrap(), "true");
1038            assert!(settings.auto_share);
1039        }
1040
1041        #[test]
1042        fn handle_returns_none_for_wrong_event() {
1043            let setting = AutoShare;
1044            let mut settings = Settings::default();
1045            let mut bus: Bus = VecDeque::new();
1046
1047            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
1048
1049            assert!(result.0.is_none());
1050        }
1051    }
1052
1053    mod auto_frontlight {
1054        use super::*;
1055
1056        #[test]
1057        fn brightness_apply_text_parses_and_updates() {
1058            let setting = AutoFrontlightBrightness;
1059            let mut settings = Settings::default();
1060            let mut bus: Bus = VecDeque::new();
1061
1062            let display = setting.apply_text("25", &mut settings);
1063            let result = setting.handle(
1064                &Event::Submit(ViewId::AutoFrontlightBrightnessInput, "25".to_string()),
1065                &mut settings,
1066                &mut bus,
1067            );
1068
1069            assert_eq!(display, "25%");
1070            assert_eq!(settings.auto_frontlight_night_brightness, Some(25.0.into()));
1071            assert_eq!(result, (Some("25%".to_string()), true));
1072            assert!(matches!(
1073                bus.pop_front(),
1074                Some(Event::AutoFrontlightConfigChanged)
1075            ));
1076        }
1077
1078        #[test]
1079        fn brightness_apply_text_ignores_invalid_input() {
1080            let setting = AutoFrontlightBrightness;
1081            let mut settings = Settings {
1082                auto_frontlight_night_brightness: Some(10.0.into()),
1083                ..Default::default()
1084            };
1085            let mut bus: Bus = VecDeque::new();
1086
1087            let display = setting.apply_text("invalid", &mut settings);
1088            let result = setting.handle(
1089                &Event::Submit(ViewId::AutoFrontlightBrightnessInput, "invalid".to_string()),
1090                &mut settings,
1091                &mut bus,
1092            );
1093
1094            assert_eq!(display, "10%");
1095            assert_eq!(settings.auto_frontlight_night_brightness, Some(10.0.into()));
1096            assert_eq!(result, (Some("10%".to_string()), true));
1097            assert!(matches!(
1098                bus.pop_front(),
1099                Some(Event::AutoFrontlightConfigChanged)
1100            ));
1101        }
1102
1103        #[test]
1104        fn manual_coordinates_apply_text_parses_and_updates() {
1105            let setting = AutoFrontlightManualCoordinates;
1106            let mut settings = Settings::default();
1107            let mut bus: Bus = VecDeque::new();
1108
1109            let display = setting.apply_text("51.5074, -0.1278", &mut settings);
1110            let result = setting.handle(
1111                &Event::Submit(
1112                    ViewId::AutoFrontlightManualCoordinatesInput,
1113                    "51.5074, -0.1278".to_string(),
1114                ),
1115                &mut settings,
1116                &mut bus,
1117            );
1118
1119            assert_eq!(display, "51.5074, -0.1278");
1120            assert_eq!(
1121                settings.auto_frontlight_manual_coordinates,
1122                Some(Coordinates::new(51.5074, -0.1278).unwrap())
1123            );
1124            assert_eq!(result, (Some("51.5074, -0.1278".to_string()), true));
1125            assert!(matches!(
1126                bus.pop_front(),
1127                Some(Event::AutoFrontlightConfigChanged)
1128            ));
1129        }
1130
1131        #[test]
1132        fn manual_coordinates_apply_text_clears_on_empty_input() {
1133            let setting = AutoFrontlightManualCoordinates;
1134            let mut settings = Settings {
1135                auto_frontlight_manual_coordinates: Some(
1136                    Coordinates::new(51.5074, -0.1278).unwrap(),
1137                ),
1138                ..Default::default()
1139            };
1140            let mut bus: Bus = VecDeque::new();
1141
1142            let display = setting.apply_text("", &mut settings);
1143            let result = setting.handle(
1144                &Event::Submit(ViewId::AutoFrontlightManualCoordinatesInput, "".to_string()),
1145                &mut settings,
1146                &mut bus,
1147            );
1148
1149            assert_eq!(display, "Not set");
1150            assert_eq!(settings.auto_frontlight_manual_coordinates, None);
1151            assert_eq!(result, (Some("Not set".to_string()), true));
1152            assert!(matches!(
1153                bus.pop_front(),
1154                Some(Event::AutoFrontlightConfigChanged)
1155            ));
1156        }
1157
1158        #[test]
1159        fn manual_coordinates_apply_text_ignores_invalid_input() {
1160            let setting = AutoFrontlightManualCoordinates;
1161            let mut settings = Settings {
1162                auto_frontlight_manual_coordinates: Some(
1163                    Coordinates::new(51.5074, -0.1278).unwrap(),
1164                ),
1165                ..Default::default()
1166            };
1167            let mut bus: Bus = VecDeque::new();
1168
1169            let display = setting.apply_text("invalid", &mut settings);
1170            let result = setting.handle(
1171                &Event::Submit(
1172                    ViewId::AutoFrontlightManualCoordinatesInput,
1173                    "invalid".to_string(),
1174                ),
1175                &mut settings,
1176                &mut bus,
1177            );
1178
1179            assert_eq!(display, "51.5074, -0.1278");
1180            assert_eq!(
1181                settings.auto_frontlight_manual_coordinates,
1182                Some(Coordinates::new(51.5074, -0.1278).unwrap())
1183            );
1184            assert_eq!(result, (Some("51.5074, -0.1278".to_string()), true));
1185            assert!(matches!(
1186                bus.pop_front(),
1187                Some(Event::AutoFrontlightConfigChanged)
1188            ));
1189        }
1190    }
1191
1192    mod button_scheme {
1193        use super::*;
1194        use crate::settings::ButtonScheme;
1195
1196        #[test]
1197        fn handle_toggle_event_switches_natural_to_inverted() {
1198            let setting = ButtonScheme;
1199            let mut settings = Settings {
1200                button_scheme: ButtonScheme::Natural,
1201                ..Default::default()
1202            };
1203            let mut bus: Bus = VecDeque::new();
1204            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::ButtonScheme));
1205
1206            let result = setting.handle(&event, &mut settings, &mut bus);
1207
1208            assert_eq!(settings.button_scheme, ButtonScheme::Inverted);
1209            assert_eq!(bus.len(), 1);
1210            assert!(result.0.is_some());
1211        }
1212
1213        #[test]
1214        fn handle_toggle_event_switches_inverted_to_natural() {
1215            let setting = ButtonScheme;
1216            let mut settings = Settings {
1217                button_scheme: ButtonScheme::Inverted,
1218                ..Default::default()
1219            };
1220            let mut bus: Bus = VecDeque::new();
1221            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::ButtonScheme));
1222
1223            let result = setting.handle(&event, &mut settings, &mut bus);
1224
1225            assert_eq!(settings.button_scheme, ButtonScheme::Natural);
1226            assert_eq!(bus.len(), 1);
1227            assert!(result.0.is_some());
1228        }
1229
1230        #[test]
1231        fn handle_set_scheme_event_applies_directly() {
1232            let setting = ButtonScheme;
1233            let mut settings = Settings {
1234                button_scheme: ButtonScheme::Natural,
1235                ..Default::default()
1236            };
1237            let mut bus: Bus = VecDeque::new();
1238            let event = Event::Select(EntryId::SetButtonScheme(ButtonScheme::Inverted));
1239
1240            let result = setting.handle(&event, &mut settings, &mut bus);
1241
1242            assert_eq!(settings.button_scheme, ButtonScheme::Inverted);
1243            assert!(result.0.is_some());
1244        }
1245
1246        #[test]
1247        fn handle_returns_none_for_wrong_event() {
1248            let setting = ButtonScheme;
1249            let mut settings = Settings::default();
1250            let mut bus: Bus = VecDeque::new();
1251
1252            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
1253
1254            assert!(result.0.is_none());
1255        }
1256    }
1257
1258    mod settings_retention {
1259        use super::*;
1260
1261        #[test]
1262        fn apply_text_parses_and_updates() {
1263            let setting = SettingsRetention;
1264            let mut settings = Settings::default();
1265
1266            let display = setting.apply_text("10", &mut settings);
1267
1268            assert_eq!(display, "10");
1269            assert_eq!(settings.settings_retention, 10);
1270        }
1271
1272        #[test]
1273        fn apply_text_ignores_invalid_input() {
1274            let setting = SettingsRetention;
1275            let mut settings = Settings {
1276                settings_retention: 3,
1277                ..Default::default()
1278            };
1279
1280            let display = setting.apply_text("invalid", &mut settings);
1281
1282            assert_eq!(settings.settings_retention, 3);
1283            assert_eq!(display, "3");
1284        }
1285    }
1286}