cranpose_liquid/widgets/
language_picker.rs1use cranpose_core::rememberMutableStateOf;
2use cranpose_macros::composable;
3use cranpose_ui::{
4 LanguagePreference, LinearArrangement, LocalizationController, Modifier,
5 text::TextStyle,
6 widgets::{Column, ColumnSpec, FlowRow, FlowRowSpec, Text},
7};
8
9use super::{
10 button::{GlassButton, GlassButtonLabel, GlassButtonSize, GlassButtonSpec},
11 card::LiquidCard,
12};
13use crate::theme::{liquid_colors, liquid_typography};
14
15#[composable]
22pub fn LiquidLanguagePicker(controller: LocalizationController) {
23 let expanded = rememberMutableStateOf(|| false);
24 let preference = controller.preference();
25 let current_name = preference_name(&preference);
26 let colors = liquid_colors();
27 let typography = liquid_typography();
28 let title = cranpose_ui::tr!("App language", id = "app-language");
29 let change = cranpose_ui::tr!("Change app language", id = "change-language");
30 let system_name = cranpose_ui::tr!("System default", id = "system-language");
31
32 LiquidCard(Modifier::empty().fill_max_width(), move || {
33 let preference = preference.clone();
34 let current_name = current_name.clone();
35 let title = title.clone();
36 let change = change.clone();
37 let system_name = system_name.clone();
38 let controller = controller.clone();
39 let expanded = expanded;
40 let typography = typography.clone();
41 let colors = colors;
42 Column(
43 Modifier::empty().padding(16.0),
44 ColumnSpec::default().vertical_arrangement(LinearArrangement::spaced_by(8.0)),
45 move || {
46 let mut heading_style = typography.headline.clone();
47 heading_style.span_style.color = Some(colors.label);
48 Text(title.clone(), Modifier::empty(), heading_style);
49 Text(
50 current_name.clone(),
51 Modifier::empty(),
52 typography.body.clone(),
53 );
54
55 let state_name = current_name.as_str().to_owned();
56 let toggle_modifier =
57 Modifier::empty()
58 .fill_max_width()
59 .semantics(move |semantics| {
60 semantics.state_description = Some(state_name.clone());
61 });
62 let toggle = expanded;
63 let change_label = change.clone();
64 let change_spec = GlassButtonSpec::glass();
65 GlassButton(
66 toggle_modifier,
67 change_spec.clone(),
68 move || toggle.set(!toggle.get()),
69 move || GlassButtonLabel(change_label.clone(), change_spec.clone()),
70 );
71
72 if expanded.get() {
73 let preference = preference.clone();
74 let controller = controller.clone();
75 let system_name = system_name.clone();
76 FlowRow(
77 Modifier::empty().fill_max_width(),
78 FlowRowSpec::new()
79 .main_axis_spacing(8.0)
80 .cross_axis_spacing(8.0),
81 move || {
82 let system_selected = preference == LanguagePreference::System;
83 let system_controller = controller.clone();
84 let system_expanded = expanded;
85 add_language_choice(system_name.clone(), system_selected, move || {
86 system_controller.select(LanguagePreference::System);
87 system_expanded.set(false);
88 });
89
90 for language in controller.catalog().languages() {
91 let selected =
92 preference == LanguagePreference::Selected(language.clone());
93 let choice = language.clone();
94 let label = language.name().to_owned().into();
95 let choice_controller = controller.clone();
96 let choice_expanded = expanded;
97 add_language_choice(label, selected, move || {
98 choice_controller
99 .select(LanguagePreference::Selected(choice.clone()));
100 choice_expanded.set(false);
101 });
102 }
103 },
104 );
105 }
106
107 if let Some(error) = controller.error() {
108 let message = cranpose_ui::tr!(
109 "Could not save language: {error}",
110 error = error,
111 id = "m-bae8fe9c44aa9004"
112 );
113 let mut error_style: TextStyle = typography.body.clone();
114 error_style.span_style.color = Some(colors.destructive);
115 Text(message, Modifier::empty(), error_style);
116 }
117 },
118 );
119 });
120}
121
122fn preference_name(preference: &LanguagePreference) -> cranpose_ui::SharedText {
123 match preference {
124 LanguagePreference::System => cranpose_ui::tr!("System default", id = "system-language"),
125 LanguagePreference::Selected(language) => language.name().to_owned().into(),
126 }
127}
128
129fn add_language_choice(
130 label: cranpose_ui::SharedText,
131 selected: bool,
132 on_click: impl Fn() + 'static,
133) {
134 let modifier =
135 Modifier::empty().semantics(move |semantics| semantics.selected = Some(selected));
136 let spec = GlassButtonSpec::plain().with_size(GlassButtonSize::Small);
137 GlassButton(modifier, spec.clone(), on_click, move || {
138 GlassButtonLabel(label.clone(), spec.clone());
139 });
140}