diff --git a/wetter/app/src/main/java/app/wetter/ui/screens/SettingsScreen.kt b/wetter/app/src/main/java/app/wetter/ui/screens/SettingsScreen.kt new file mode 100644 index 0000000..e9f3aa7 --- /dev/null +++ b/wetter/app/src/main/java/app/wetter/ui/screens/SettingsScreen.kt @@ -0,0 +1,120 @@ +package app.wetter.ui.screens + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.foundation.selection.toggleable +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SegmentedButton +import androidx.compose.material3.SegmentedButtonDefaults +import androidx.compose.material3.SingleChoiceSegmentedButtonRow +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.heading +import androidx.compose.ui.semantics.semantics +import app.wetter.R +import app.wetter.data.DistanceUnit +import app.wetter.data.Graph +import app.wetter.data.PrecipUnit +import app.wetter.data.PressureUnit +import app.wetter.data.Settings +import app.wetter.data.TempUnit +import app.wetter.data.ThemeMode +import app.wetter.data.WindUnit +import app.wetter.ui.theme.WSpace +import app.wetter.ui.theme.WType +import kotlinx.coroutines.launch + +/** S08 + S09: units, look, data sources. */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScreen(settings: Settings, onBack: () -> Unit) { + val scope = rememberCoroutineScope() + val set: ((Settings) -> Settings) -> Unit = { t -> scope.launch { Graph.settings.update(t) } } + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.settings)) }, + navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Rounded.ArrowBack, stringResource(R.string.back)) } }, + ) + }, + ) { pad -> + Column(Modifier.padding(pad).verticalScroll(rememberScrollState()).padding(horizontal = WSpace.s16)) { + Header(stringResource(R.string.set_units)) + Choice(stringResource(R.string.set_temp), TempUnit.entries, settings.temp, { if (it == TempUnit.C) "°C" else "°F" }) { v -> set { it.copy(temp = v) } } + Choice(stringResource(R.string.set_wind), WindUnit.entries, settings.wind, { + when (it) { WindUnit.KMH -> "km/h"; WindUnit.MS -> "m/s"; WindUnit.MPH -> "mph"; WindUnit.KN -> "kn" } + }) { v -> set { it.copy(wind = v) } } + Choice(stringResource(R.string.set_precip), PrecipUnit.entries, settings.precip, { if (it == PrecipUnit.MM) "mm" else "in" }) { v -> set { it.copy(precip = v) } } + Choice(stringResource(R.string.set_pressure), PressureUnit.entries, settings.pressure, { if (it == PressureUnit.HPA) "hPa" else "inHg" }) { v -> set { it.copy(pressure = v) } } + Choice(stringResource(R.string.set_distance), DistanceUnit.entries, settings.distance, { if (it == DistanceUnit.KM) "km" else "mi" }) { v -> set { it.copy(distance = v) } } + + Header(stringResource(R.string.set_look)) + val themeLabels = mapOf( + ThemeMode.SYSTEM to stringResource(R.string.theme_system), + ThemeMode.LIGHT to stringResource(R.string.theme_light), + ThemeMode.DARK to stringResource(R.string.theme_dark), + ) + Choice(stringResource(R.string.set_theme), ThemeMode.entries, settings.theme, { themeLabels.getValue(it) }) { v -> set { it.copy(theme = v) } } + Toggle(stringResource(R.string.set_animations), stringResource(R.string.set_animations_sub), settings.animations) { v -> set { it.copy(animations = v) } } + Toggle(stringResource(R.string.set_brief), null, settings.brief) { v -> set { it.copy(brief = v) } } + + Header(stringResource(R.string.set_about)) + Text(stringResource(R.string.attribution), style = WType.bodySm, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(bottom = WSpace.s32)) + } + } +} + +@Composable +private fun Header(text: String) = Text( + text, style = WType.title, color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(top = WSpace.s24, bottom = WSpace.s8).semantics { heading() }, +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun Choice(label: String, options: List, selected: T, name: (T) -> String, onPick: (T) -> Unit) { + Column(Modifier.padding(vertical = WSpace.s8)) { + Text(label, style = WType.body) + SingleChoiceSegmentedButtonRow(Modifier.fillMaxWidth().padding(top = WSpace.s8).selectableGroup()) { + options.forEachIndexed { i, o -> + SegmentedButton( + selected = o == selected, + onClick = { onPick(o) }, + shape = SegmentedButtonDefaults.itemShape(i, options.size), + ) { Text(name(o)) } + } + } + } +} + +@Composable +private fun Toggle(label: String, sub: String?, checked: Boolean, onChange: (Boolean) -> Unit) { + Row( + Modifier.fillMaxWidth().toggleable(checked, role = Role.Switch, onValueChange = onChange).padding(vertical = WSpace.s12), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(Modifier.weight(1f)) { + Text(label, style = WType.body) + if (sub != null) Text(sub, style = WType.bodySm, color = MaterialTheme.colorScheme.onSurfaceVariant) + } + Switch(checked, onCheckedChange = null) + } +}