From 69626dce67b60bb152c0afe107e7aba033b12f84 Mon Sep 17 00:00:00 2001 From: timklge <2026103+timklge@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:01:02 +0100 Subject: [PATCH] fix #23: Show date of weather data in weather forecast, main menu (#24) * fix #23: Show date of weather data in weather forecast, main menu * Use localized date format --- .../datatypes/WeatherDataType.kt | 7 ++- .../datatypes/WeatherForecastDataType.kt | 15 +++++- .../karooheadwind/datatypes/WeatherView.kt | 47 +++++++++++++------ .../karooheadwind/screens/MainScreen.kt | 9 ++-- 4 files changed, 59 insertions(+), 19 deletions(-) diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherDataType.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherDataType.kt index eab2beb..91ca584 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherDataType.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherDataType.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.launch import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle import kotlin.math.roundToInt @OptIn(ExperimentalGlanceRemoteViewsApi::class) @@ -99,6 +100,8 @@ class WeatherDataType( val interpretation = WeatherInterpretation.fromWeatherCode(data.current.weatherCode) val formattedTime = timeFormatter.format(Instant.ofEpochSecond(data.current.time)) + val formattedDate = Instant.ofEpochSecond(data.current.time).atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ofLocalizedDate( + FormatStyle.SHORT)) val result = glance.compose(context, DpSize.Unspecified) { Box(modifier = GlanceModifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { @@ -114,11 +117,13 @@ class WeatherDataType( temperature = data.current.temperature.roundToInt(), temperatureUnit = if (userProfile?.preferredUnit?.temperature != UserProfile.PreferredUnit.UnitType.IMPERIAL) TemperatureUnit.CELSIUS else TemperatureUnit.FAHRENHEIT, timeLabel = formattedTime, + dateLabel = formattedDate, rowAlignment = when (config.alignment){ ViewConfig.Alignment.LEFT -> Alignment.Horizontal.Start ViewConfig.Alignment.CENTER -> Alignment.Horizontal.CenterHorizontally ViewConfig.Alignment.RIGHT -> Alignment.Horizontal.End - } + }, + singleDisplay = true ) } } diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherForecastDataType.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherForecastDataType.kt index 7e9110b..c85271f 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherForecastDataType.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherForecastDataType.kt @@ -55,6 +55,7 @@ import kotlinx.coroutines.launch import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle import kotlin.math.roundToInt class CycleHoursAction : ActionCallback { @@ -139,6 +140,13 @@ class WeatherForecastDataType( Row(modifier = GlanceModifier.fillMaxSize().clickable(onClick = actionRunCallback()), horizontalAlignment = Alignment.Horizontal.CenterHorizontally) { val hourOffset = widgetSettings?.currentForecastHourOffset ?: 0 + var previousDate: String? = let { + val unixTime = data.forecastData.time.firstOrNull() + val formattedDate = unixTime?.let { Instant.ofEpochSecond(it).atZone(ZoneId.systemDefault()).toLocalDate().toString() } + + formattedDate + } + for (index in hourOffset..hourOffset + 2){ if (index >= data.forecastData.weatherCode.size) { break @@ -155,6 +163,8 @@ class WeatherForecastDataType( val interpretation = WeatherInterpretation.fromWeatherCode(data.forecastData.weatherCode[index]) val unixTime = data.forecastData.time[index] val formattedTime = timeFormatter.format(Instant.ofEpochSecond(unixTime)) + val formattedDate = Instant.ofEpochSecond(unixTime).atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)) + val hasNewDate = formattedDate != previousDate || index == 0 Weather(baseBitmap, current = interpretation, @@ -167,8 +177,11 @@ class WeatherForecastDataType( precipitationUnit = if (userProfile?.preferredUnit?.distance != UserProfile.PreferredUnit.UnitType.IMPERIAL) PrecipitationUnit.MILLIMETERS else PrecipitationUnit.INCH, temperature = data.forecastData.temperature[index].roundToInt(), temperatureUnit = if (userProfile?.preferredUnit?.temperature != UserProfile.PreferredUnit.UnitType.IMPERIAL) TemperatureUnit.CELSIUS else TemperatureUnit.FAHRENHEIT, - timeLabel = formattedTime + timeLabel = formattedTime, + dateLabel = if (hasNewDate) formattedDate else null ) + + previousDate = formattedDate } } } diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherView.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherView.kt index 07712c1..b35d6bd 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherView.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherView.kt @@ -17,9 +17,12 @@ import androidx.glance.layout.ContentScale import androidx.glance.layout.Row import androidx.glance.layout.Spacer import androidx.glance.layout.fillMaxHeight +import androidx.glance.layout.fillMaxSize +import androidx.glance.layout.fillMaxWidth import androidx.glance.layout.height import androidx.glance.layout.padding import androidx.glance.layout.width +import androidx.glance.layout.wrapContentWidth import androidx.glance.preview.ExperimentalGlancePreviewApi import androidx.glance.preview.Preview import androidx.glance.text.FontFamily @@ -51,14 +54,15 @@ fun getWeatherIcon(interpretation: WeatherInterpretation): Int { @Composable fun Weather(baseBitmap: Bitmap, current: WeatherInterpretation, windBearing: Int, windSpeed: Int, windGusts: Int, windSpeedUnit: WindUnit, precipitation: Double, precipitationProbability: Int?, precipitationUnit: PrecipitationUnit, - temperature: Int, temperatureUnit: TemperatureUnit, timeLabel: String? = null, rowAlignment: Alignment.Horizontal = Alignment.Horizontal.CenterHorizontally) { + temperature: Int, temperatureUnit: TemperatureUnit, timeLabel: String? = null, rowAlignment: Alignment.Horizontal = Alignment.Horizontal.CenterHorizontally, + dateLabel: String? = null, singleDisplay: Boolean = false) { val fontSize = 14f - Column(modifier = GlanceModifier.fillMaxHeight().padding(2.dp).width(85.dp), horizontalAlignment = rowAlignment) { - Row(modifier = GlanceModifier.defaultWeight(), horizontalAlignment = rowAlignment, verticalAlignment = Alignment.CenterVertically) { + Column(modifier = if (singleDisplay) GlanceModifier.fillMaxSize().padding(1.dp) else GlanceModifier.fillMaxHeight().padding(1.dp).width(86.dp), horizontalAlignment = rowAlignment) { + Row(modifier = GlanceModifier.defaultWeight().wrapContentWidth(), horizontalAlignment = rowAlignment, verticalAlignment = Alignment.CenterVertically) { Image( - modifier = GlanceModifier.defaultWeight(), + modifier = GlanceModifier.defaultWeight().wrapContentWidth().padding(1.dp), provider = ImageProvider(getWeatherIcon(current)), contentDescription = "Current weather information", contentScale = ContentScale.Fit, @@ -66,6 +70,19 @@ fun Weather(baseBitmap: Bitmap, current: WeatherInterpretation, windBearing: Int ) } + if (dateLabel != null && !singleDisplay){ + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = dateLabel, + style = TextStyle( + color = ColorProvider(Color.Black, Color.White), + fontFamily = FontFamily.Monospace, + fontSize = TextUnit(fontSize, TextUnitType.Sp) + ) + ) + } + } + Row(verticalAlignment = Alignment.CenterVertically, horizontalAlignment = rowAlignment) { if (timeLabel != null){ Text( @@ -78,7 +95,7 @@ fun Weather(baseBitmap: Bitmap, current: WeatherInterpretation, windBearing: Int } Image( - modifier = GlanceModifier.height(20.dp).width(12.dp), + modifier = GlanceModifier.height(16.dp).width(12.dp).padding(1.dp), provider = ImageProvider(R.drawable.thermometer), contentDescription = "Temperature", contentScale = ContentScale.Fit, @@ -91,14 +108,16 @@ fun Weather(baseBitmap: Bitmap, current: WeatherInterpretation, windBearing: Int ) } - Row(verticalAlignment = Alignment.CenterVertically, horizontalAlignment = rowAlignment) { - /* Image( - modifier = GlanceModifier.height(20.dp).width(12.dp), - provider = ImageProvider(R.drawable.water_regular), - contentDescription = "Rain", - contentScale = ContentScale.Fit, - colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White)) - ) */ + Row(verticalAlignment = Alignment.CenterVertically, horizontalAlignment = rowAlignment, modifier = GlanceModifier.fillMaxWidth()) { + if (dateLabel != null && singleDisplay){ + Text( + text = "$dateLabel", + style = TextStyle(color = ColorProvider(Color.Black, Color.White), + fontFamily = FontFamily.Monospace, fontSize = TextUnit(fontSize, TextUnitType.Sp)) + ) + + Spacer(modifier = GlanceModifier.width(5.dp)) + } val precipitationProbabilityLabel = if (precipitationProbability != null) "${precipitationProbability}%," else "" Text( @@ -109,7 +128,7 @@ fun Weather(baseBitmap: Bitmap, current: WeatherInterpretation, windBearing: Int Spacer(modifier = GlanceModifier.width(5.dp)) Image( - modifier = GlanceModifier.height(20.dp).width(12.dp), + modifier = GlanceModifier.height(16.dp).width(12.dp).padding(1.dp), provider = ImageProvider(getArrowBitmapByBearing(baseBitmap, windBearing + 180)), contentDescription = "Current wind direction", contentScale = ContentScale.Fit, diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/screens/MainScreen.kt b/app/src/main/kotlin/de/timklge/karooheadwind/screens/MainScreen.kt index ea1f024..cfcb451 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/screens/MainScreen.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/screens/MainScreen.kt @@ -222,16 +222,19 @@ fun MainScreen() { if (stats.failedWeatherRequest != null && (stats.lastSuccessfulWeatherRequest == null || stats.failedWeatherRequest!! > stats.lastSuccessfulWeatherRequest!!)){ val successfulTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(stats.lastSuccessfulWeatherRequest ?: 0), ZoneOffset.systemDefault()).toLocalTime().truncatedTo( ChronoUnit.SECONDS) + val successfulDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(stats.lastSuccessfulWeatherRequest ?: 0), ZoneOffset.systemDefault()).toLocalDate() val lastTryTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(stats.failedWeatherRequest ?: 0), ZoneOffset.systemDefault()).toLocalTime().truncatedTo( ChronoUnit.SECONDS) + val lastTryDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(stats.failedWeatherRequest ?: 0), ZoneOffset.systemDefault()).toLocalDate() - val successStr = if(lastPosition != null) " Last data received at ${successfulTime}${lastPositionDistanceStr}." else "" - Text(modifier = Modifier.padding(5.dp), text = "Failed to update weather data; last try at ${lastTryTime}.${successStr}") + val successStr = if(lastPosition != null) " Last data received at $successfulDate ${successfulTime}${lastPositionDistanceStr}." else "" + Text(modifier = Modifier.padding(5.dp), text = "Failed to update weather data; last try at $lastTryDate ${lastTryTime}.${successStr}") } else if(stats.lastSuccessfulWeatherRequest != null){ + val localDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(stats.lastSuccessfulWeatherRequest ?: 0), ZoneOffset.systemDefault()).toLocalDate() val localTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(stats.lastSuccessfulWeatherRequest ?: 0), ZoneOffset.systemDefault()).toLocalTime().truncatedTo( ChronoUnit.SECONDS) - Text(modifier = Modifier.padding(5.dp), text = "Last weather data received at ${localTime}${lastPositionDistanceStr}") + Text(modifier = Modifier.padding(5.dp), text = "Last weather data received at $localDate ${localTime}${lastPositionDistanceStr}") } else { Text(modifier = Modifier.padding(5.dp), text = "No weather data received yet, waiting for GPS fix...") }