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;
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/// Scans the keyboard-layouts directory for available keyboard layouts
710fn get_available_layouts() -> Result<Vec<String>, Error> {
711    let layouts_dir = CURRENT_DEVICE.install_path("keyboard-layouts");
712    let mut layouts = Vec::new();
713
714    if layouts_dir.exists() {
715        for entry in fs::read_dir(&layouts_dir)? {
716            let entry = entry?;
717            let path = entry.path();
718
719            if path.extension().and_then(|s| s.to_str()) == Some("json") {
720                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
721                    let layout_name = stem
722                        .chars()
723                        .enumerate()
724                        .map(|(i, c)| {
725                            if i == 0 {
726                                c.to_uppercase().collect::<String>()
727                            } else {
728                                c.to_string()
729                            }
730                        })
731                        .collect::<String>();
732                    layouts.push(layout_name);
733                }
734            }
735        }
736    }
737
738    layouts.sort();
739    Ok(layouts)
740}
741
742#[cfg(test)]
743mod tests {
744    use super::*;
745    use crate::settings::Settings;
746    use crate::view::settings_editor::kinds::{InputSettingKind, ToggleSettings};
747    use crate::view::{Bus, EntryId, Event, ToggleEvent};
748    use std::collections::VecDeque;
749
750    mod locale {
751        use super::*;
752
753        #[test]
754        fn handle_set_locale_updates_settings() {
755            let setting = Locale;
756            let mut settings = Settings::default();
757            let mut bus: Bus = VecDeque::new();
758            let locale: Option<unic_langid::LanguageIdentifier> = Some("de-DE".parse().unwrap());
759            let event = Event::Select(EntryId::SetLocale(locale.clone()));
760
761            let result = setting.handle(&event, &mut settings, &mut bus);
762
763            assert!(result.0.is_some());
764            assert_eq!(result.0.unwrap(), "de-DE");
765            assert_eq!(settings.locale, locale);
766        }
767
768        #[test]
769        fn handle_returns_none_for_wrong_event() {
770            let setting = Locale;
771            let mut settings = Settings::default();
772            let mut bus: Bus = VecDeque::new();
773
774            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
775
776            assert!(result.0.is_none());
777        }
778    }
779
780    mod keyboard_layout {
781        use super::*;
782
783        #[test]
784        fn handle_set_layout_updates_settings() {
785            let setting = KeyboardLayout;
786            let mut settings = Settings::default();
787            let mut bus: Bus = VecDeque::new();
788            let event = Event::Select(EntryId::SetKeyboardLayout("German".to_string()));
789
790            let result = setting.handle(&event, &mut settings, &mut bus);
791
792            assert!(result.0.is_some());
793            assert_eq!(result.0.unwrap(), "German");
794            assert_eq!(settings.keyboard_layout, "German");
795        }
796
797        #[test]
798        fn handle_returns_none_for_wrong_event() {
799            let setting = KeyboardLayout;
800            let mut settings = Settings::default();
801            let mut bus: Bus = VecDeque::new();
802
803            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
804
805            assert!(result.0.is_none());
806        }
807    }
808
809    mod auto_suspend {
810        use super::*;
811
812        #[test]
813        fn apply_text_parses_and_updates() {
814            let setting = AutoSuspend;
815            let mut settings = Settings::default();
816
817            let display = setting.apply_text("60.0", &mut settings);
818
819            assert_eq!(display, "60.0");
820            assert_eq!(settings.auto_suspend, 60.0);
821        }
822
823        #[test]
824        fn apply_text_returns_never_for_zero() {
825            let setting = AutoSuspend;
826            let mut settings = Settings::default();
827
828            let display = setting.apply_text("0", &mut settings);
829
830            assert_eq!(display, "Never");
831            assert_eq!(settings.auto_suspend, 0.0);
832        }
833
834        #[test]
835        fn apply_text_ignores_invalid_input() {
836            let setting = AutoSuspend;
837            let mut settings = Settings {
838                auto_suspend: 30.0,
839                ..Default::default()
840            };
841
842            let display = setting.apply_text("invalid", &mut settings);
843
844            assert_eq!(settings.auto_suspend, 30.0);
845            assert_eq!(display, "30.0");
846        }
847    }
848
849    mod auto_power_off {
850        use super::*;
851
852        #[test]
853        fn apply_text_parses_and_updates() {
854            let setting = AutoPowerOff;
855            let mut settings = Settings::default();
856
857            let display = setting.apply_text("14.0", &mut settings);
858
859            assert_eq!(display, "14.0");
860            assert_eq!(settings.auto_power_off, 14.0);
861        }
862
863        #[test]
864        fn apply_text_returns_never_for_zero() {
865            let setting = AutoPowerOff;
866            let mut settings = Settings::default();
867
868            let display = setting.apply_text("0", &mut settings);
869
870            assert_eq!(display, "Never");
871            assert_eq!(settings.auto_power_off, 0.0);
872        }
873
874        #[test]
875        fn apply_text_ignores_invalid_input() {
876            let setting = AutoPowerOff;
877            let mut settings = Settings {
878                auto_power_off: 7.0,
879                ..Default::default()
880            };
881
882            let display = setting.apply_text("invalid", &mut settings);
883
884            assert_eq!(settings.auto_power_off, 7.0);
885            assert_eq!(display, "7.0");
886        }
887    }
888
889    mod sleep_cover {
890        use super::*;
891
892        #[test]
893        fn handle_toggle_event_toggles_value() {
894            let setting = SleepCover;
895            let mut settings = Settings {
896                sleep_cover: true,
897                ..Default::default()
898            };
899            let mut bus: Bus = VecDeque::new();
900            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::SleepCover));
901
902            let result = setting.handle(&event, &mut settings, &mut bus);
903
904            assert!(result.0.is_some());
905            assert_eq!(result.0.unwrap(), "false");
906            assert!(!settings.sleep_cover);
907        }
908
909        #[test]
910        fn handle_returns_none_for_wrong_event() {
911            let setting = SleepCover;
912            let mut settings = Settings::default();
913            let mut bus: Bus = VecDeque::new();
914
915            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
916
917            assert!(result.0.is_none());
918        }
919    }
920
921    mod auto_share {
922        use super::*;
923
924        #[test]
925        fn handle_toggle_event_toggles_value() {
926            let setting = AutoShare;
927            let mut settings = Settings {
928                auto_share: false,
929                ..Default::default()
930            };
931            let mut bus: Bus = VecDeque::new();
932            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoShare));
933
934            let result = setting.handle(&event, &mut settings, &mut bus);
935
936            assert!(result.0.is_some());
937            assert_eq!(result.0.unwrap(), "true");
938            assert!(settings.auto_share);
939        }
940
941        #[test]
942        fn handle_returns_none_for_wrong_event() {
943            let setting = AutoShare;
944            let mut settings = Settings::default();
945            let mut bus: Bus = VecDeque::new();
946
947            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
948
949            assert!(result.0.is_none());
950        }
951    }
952
953    mod auto_frontlight {
954        use super::*;
955
956        #[test]
957        fn brightness_apply_text_parses_and_updates() {
958            let setting = AutoFrontlightBrightness;
959            let mut settings = Settings::default();
960            let mut bus: Bus = VecDeque::new();
961
962            let display = setting.apply_text("25", &mut settings);
963            let result = setting.handle(
964                &Event::Submit(ViewId::AutoFrontlightBrightnessInput, "25".to_string()),
965                &mut settings,
966                &mut bus,
967            );
968
969            assert_eq!(display, "25%");
970            assert_eq!(settings.auto_frontlight_night_brightness, Some(25.0.into()));
971            assert_eq!(result, (Some("25%".to_string()), true));
972            assert!(matches!(
973                bus.pop_front(),
974                Some(Event::AutoFrontlightConfigChanged)
975            ));
976        }
977
978        #[test]
979        fn brightness_apply_text_ignores_invalid_input() {
980            let setting = AutoFrontlightBrightness;
981            let mut settings = Settings {
982                auto_frontlight_night_brightness: Some(10.0.into()),
983                ..Default::default()
984            };
985            let mut bus: Bus = VecDeque::new();
986
987            let display = setting.apply_text("invalid", &mut settings);
988            let result = setting.handle(
989                &Event::Submit(ViewId::AutoFrontlightBrightnessInput, "invalid".to_string()),
990                &mut settings,
991                &mut bus,
992            );
993
994            assert_eq!(display, "10%");
995            assert_eq!(settings.auto_frontlight_night_brightness, Some(10.0.into()));
996            assert_eq!(result, (Some("10%".to_string()), true));
997            assert!(matches!(
998                bus.pop_front(),
999                Some(Event::AutoFrontlightConfigChanged)
1000            ));
1001        }
1002
1003        #[test]
1004        fn manual_coordinates_apply_text_parses_and_updates() {
1005            let setting = AutoFrontlightManualCoordinates;
1006            let mut settings = Settings::default();
1007            let mut bus: Bus = VecDeque::new();
1008
1009            let display = setting.apply_text("51.5074, -0.1278", &mut settings);
1010            let result = setting.handle(
1011                &Event::Submit(
1012                    ViewId::AutoFrontlightManualCoordinatesInput,
1013                    "51.5074, -0.1278".to_string(),
1014                ),
1015                &mut settings,
1016                &mut bus,
1017            );
1018
1019            assert_eq!(display, "51.5074, -0.1278");
1020            assert_eq!(
1021                settings.auto_frontlight_manual_coordinates,
1022                Some(Coordinates::new(51.5074, -0.1278).unwrap())
1023            );
1024            assert_eq!(result, (Some("51.5074, -0.1278".to_string()), true));
1025            assert!(matches!(
1026                bus.pop_front(),
1027                Some(Event::AutoFrontlightConfigChanged)
1028            ));
1029        }
1030
1031        #[test]
1032        fn manual_coordinates_apply_text_clears_on_empty_input() {
1033            let setting = AutoFrontlightManualCoordinates;
1034            let mut settings = Settings {
1035                auto_frontlight_manual_coordinates: Some(
1036                    Coordinates::new(51.5074, -0.1278).unwrap(),
1037                ),
1038                ..Default::default()
1039            };
1040            let mut bus: Bus = VecDeque::new();
1041
1042            let display = setting.apply_text("", &mut settings);
1043            let result = setting.handle(
1044                &Event::Submit(ViewId::AutoFrontlightManualCoordinatesInput, "".to_string()),
1045                &mut settings,
1046                &mut bus,
1047            );
1048
1049            assert_eq!(display, "Not set");
1050            assert_eq!(settings.auto_frontlight_manual_coordinates, None);
1051            assert_eq!(result, (Some("Not set".to_string()), true));
1052            assert!(matches!(
1053                bus.pop_front(),
1054                Some(Event::AutoFrontlightConfigChanged)
1055            ));
1056        }
1057
1058        #[test]
1059        fn manual_coordinates_apply_text_ignores_invalid_input() {
1060            let setting = AutoFrontlightManualCoordinates;
1061            let mut settings = Settings {
1062                auto_frontlight_manual_coordinates: Some(
1063                    Coordinates::new(51.5074, -0.1278).unwrap(),
1064                ),
1065                ..Default::default()
1066            };
1067            let mut bus: Bus = VecDeque::new();
1068
1069            let display = setting.apply_text("invalid", &mut settings);
1070            let result = setting.handle(
1071                &Event::Submit(
1072                    ViewId::AutoFrontlightManualCoordinatesInput,
1073                    "invalid".to_string(),
1074                ),
1075                &mut settings,
1076                &mut bus,
1077            );
1078
1079            assert_eq!(display, "51.5074, -0.1278");
1080            assert_eq!(
1081                settings.auto_frontlight_manual_coordinates,
1082                Some(Coordinates::new(51.5074, -0.1278).unwrap())
1083            );
1084            assert_eq!(result, (Some("51.5074, -0.1278".to_string()), true));
1085            assert!(matches!(
1086                bus.pop_front(),
1087                Some(Event::AutoFrontlightConfigChanged)
1088            ));
1089        }
1090    }
1091
1092    mod button_scheme {
1093        use super::*;
1094        use crate::settings::ButtonScheme;
1095
1096        #[test]
1097        fn handle_toggle_event_switches_natural_to_inverted() {
1098            let setting = ButtonScheme;
1099            let mut settings = Settings {
1100                button_scheme: ButtonScheme::Natural,
1101                ..Default::default()
1102            };
1103            let mut bus: Bus = VecDeque::new();
1104            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::ButtonScheme));
1105
1106            let result = setting.handle(&event, &mut settings, &mut bus);
1107
1108            assert_eq!(settings.button_scheme, ButtonScheme::Inverted);
1109            assert_eq!(bus.len(), 1);
1110            assert!(result.0.is_some());
1111        }
1112
1113        #[test]
1114        fn handle_toggle_event_switches_inverted_to_natural() {
1115            let setting = ButtonScheme;
1116            let mut settings = Settings {
1117                button_scheme: ButtonScheme::Inverted,
1118                ..Default::default()
1119            };
1120            let mut bus: Bus = VecDeque::new();
1121            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::ButtonScheme));
1122
1123            let result = setting.handle(&event, &mut settings, &mut bus);
1124
1125            assert_eq!(settings.button_scheme, ButtonScheme::Natural);
1126            assert_eq!(bus.len(), 1);
1127            assert!(result.0.is_some());
1128        }
1129
1130        #[test]
1131        fn handle_set_scheme_event_applies_directly() {
1132            let setting = ButtonScheme;
1133            let mut settings = Settings {
1134                button_scheme: ButtonScheme::Natural,
1135                ..Default::default()
1136            };
1137            let mut bus: Bus = VecDeque::new();
1138            let event = Event::Select(EntryId::SetButtonScheme(ButtonScheme::Inverted));
1139
1140            let result = setting.handle(&event, &mut settings, &mut bus);
1141
1142            assert_eq!(settings.button_scheme, ButtonScheme::Inverted);
1143            assert!(result.0.is_some());
1144        }
1145
1146        #[test]
1147        fn handle_returns_none_for_wrong_event() {
1148            let setting = ButtonScheme;
1149            let mut settings = Settings::default();
1150            let mut bus: Bus = VecDeque::new();
1151
1152            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
1153
1154            assert!(result.0.is_none());
1155        }
1156    }
1157
1158    mod settings_retention {
1159        use super::*;
1160
1161        #[test]
1162        fn apply_text_parses_and_updates() {
1163            let setting = SettingsRetention;
1164            let mut settings = Settings::default();
1165
1166            let display = setting.apply_text("10", &mut settings);
1167
1168            assert_eq!(display, "10");
1169            assert_eq!(settings.settings_retention, 10);
1170        }
1171
1172        #[test]
1173        fn apply_text_ignores_invalid_input() {
1174            let setting = SettingsRetention;
1175            let mut settings = Settings {
1176                settings_retention: 3,
1177                ..Default::default()
1178            };
1179
1180            let display = setting.apply_text("invalid", &mut settings);
1181
1182            assert_eq!(settings.settings_retention, 3);
1183            assert_eq!(display, "3");
1184        }
1185    }
1186}