build: S08 settings and S09 units
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -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 <T> Choice(label: String, options: List<T>, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user