From 1d8c3f8f5fae03acdb45cc723aec0917431ddb56 Mon Sep 17 00:00:00 2001 From: Louis Date: Sat, 17 Oct 2026 13:52:42 +0200 Subject: [PATCH] build: S04 day detail Co-Authored-By: Claude Opus 5.5 --- .../java/app/wetter/ui/screens/DayScreen.kt | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 wetter/app/src/main/java/app/wetter/ui/screens/DayScreen.kt diff --git a/wetter/app/src/main/java/app/wetter/ui/screens/DayScreen.kt b/wetter/app/src/main/java/app/wetter/ui/screens/DayScreen.kt new file mode 100644 index 0000000..95f1ead --- /dev/null +++ b/wetter/app/src/main/java/app/wetter/ui/screens/DayScreen.kt @@ -0,0 +1,112 @@ +package app.wetter.ui.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import app.wetter.R +import app.wetter.data.Fmt +import app.wetter.data.Graph +import app.wetter.data.Settings +import app.wetter.data.Units +import app.wetter.data.conditionOf +import app.wetter.ui.components.DailyCapsules +import app.wetter.ui.components.HourlyStrip +import app.wetter.ui.components.conditionLabel +import app.wetter.ui.scene.WeatherIcon +import app.wetter.ui.theme.WSpace +import app.wetter.ui.theme.WType +import java.time.LocalDate + +/** S04: one day, picked from the capsule row. */ +@Composable +fun DayScreen(locationId: Long, initialDate: LocalDate, settings: Settings, onBack: () -> Unit) { + val forecast by remember(locationId) { Graph.repo.forecast(locationId) }.collectAsState(initial = null) + val location by produceState(null, locationId) { value = Graph.repo.location(locationId) } + val f = forecast ?: return + DayContent(f, location?.name?.ifBlank { null }, initialDate, settings, onBack) +} + +@Composable +fun DayContent(f: app.wetter.data.Forecast, placeName: String?, initialDate: LocalDate, settings: Settings, onBack: () -> Unit) { + var date by rememberSaveable { mutableStateOf(initialDate.toEpochDay()) } + val units = remember(settings) { Units(settings) } + val day = f.daily.firstOrNull { it.date.toEpochDay() == date } ?: f.daily.first() + val cond = conditionOf(day.code) + val onBg = MaterialTheme.colorScheme.onBackground + + Box(Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) { + LazyColumn( + Modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = WSpace.s32), + verticalArrangement = Arrangement.spacedBy(WSpace.s16), + ) { + item { + Row(Modifier.statusBarsPadding().height(64.dp), verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Rounded.ArrowBack, stringResource(R.string.back), tint = onBg) } + Text(stringResource(R.string.daily), style = WType.headline, color = onBg) + } + } + item { DailyCapsules(f.daily, units, selected = day.date, asSection = false) { date = it.date.toEpochDay() } } + item { + Column(Modifier.padding(horizontal = WSpace.s24)) { + Text(Fmt.longDate(day.date), style = WType.bodySm, color = onBg) + Text(placeName ?: stringResource(R.string.current_location), style = WType.headline, color = onBg) + Row(verticalAlignment = Alignment.CenterVertically) { + Text("${units.temp(day.tMax)}/${units.temp(day.tMin)}", style = WType.dayRange, color = onBg) + Spacer(Modifier.width(WSpace.s8)) + WeatherIcon(cond, true, 56.dp) + } + Text(stringResource(conditionLabel(cond, true)), style = WType.body, color = onBg) + } + } + item { + val hours = if (day.date == f.now.toLocalDate()) f.upcomingHours(24).filter { it.time.toLocalDate() == day.date } else f.hoursOf(day.date) + HourlyStrip(hours, units, Modifier.padding(horizontal = WSpace.s16).widthIn(max = 720.dp), markNow = day.date == f.now.toLocalDate()) + } + item { + // the day's own values for the tiles that make sense per day + val dayHours = f.hoursOf(day.date) + val noon = dayHours.getOrNull(12) + val cur = if (day.date == f.now.toLocalDate() || noon == null) f.current else f.current.copy( + temp = noon.temp, code = noon.code, isDay = true, humidity = noon.humidity, windSpeed = noon.windSpeed, uv = day.uvMax, + ) + app.wetter.ui.components.DetailGrid( + cur, day, if (day.date == f.now.toLocalDate()) f.air else null, units, + if (day.date == f.now.toLocalDate()) f.now else day.date.atTime(12, 0), + Modifier.padding(horizontal = WSpace.s16).navigationBarsPadding().widthIn(max = 720.dp).fillMaxWidth(), + ) + } + } + } +}