1use 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
16pub 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
72pub 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
119pub 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
186pub 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
253pub 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
291pub 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
329pub 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
367pub 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
422pub 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
464pub 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
547pub 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
656pub 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
709pub 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
762pub 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
809fn 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}