Show preview in weather, weather forecast widgets (#26)

This commit is contained in:
timklge 2025-01-17 18:19:32 +01:00 committed by GitHub
parent a98fcb875a
commit 2968cc0eef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 87 additions and 20 deletions

View File

@ -45,6 +45,8 @@ enum class WeatherInterpretation {
else -> UNKNOWN
}
}
fun getKnownWeatherCodes(): Set<Int> = setOf(0, 1, 2, 3, 45, 48, 61, 63, 65, 66, 67, 80, 81, 82, 71, 73, 75, 77, 85, 86, 51, 53, 55, 56, 57, 95, 96, 99)
}
}
@ -56,5 +58,5 @@ data class OpenMeteoCurrentWeatherResponse(
val timezone: String,
val elevation: Double,
@SerialName("utc_offset_seconds") val utfOffsetSeconds: Int,
@SerialName("hourly") val forecastData: OpenMeteoForecastData
@SerialName("hourly") val forecastData: OpenMeteoForecastData?
)

View File

@ -13,6 +13,7 @@ import androidx.glance.layout.fillMaxSize
import de.timklge.karooheadwind.HeadingResponse
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.OpenMeteoData
import de.timklge.karooheadwind.WeatherInterpretation
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.screens.HeadwindSettings
@ -34,7 +35,10 @@ import io.hammerhead.karooext.models.ViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import java.time.Instant
import java.time.ZoneId
@ -69,6 +73,23 @@ class WeatherDataType(
}
}
data class StreamData(val data: OpenMeteoCurrentWeatherResponse?, val settings: HeadwindSettings,
val profile: UserProfile? = null, val headingResponse: HeadingResponse? = null)
private fun previewFlow(): Flow<StreamData> = flow {
while (true){
emit(StreamData(
OpenMeteoCurrentWeatherResponse(
OpenMeteoData(Instant.now().epochSecond, 0, 20.0, 50, 3.0, 0, 1013.25, 15.0, 30.0, 30.0, WeatherInterpretation.getKnownWeatherCodes().random()),
0.0, 0.0, "Europe/Berlin", 30.0, 0,
null
), HeadwindSettings()))
delay(5_000)
}
}
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
Log.d(KarooHeadwindExtension.TAG, "Starting weather view with $emitter")
val configJob = CoroutineScope(Dispatchers.IO).launch {
@ -81,14 +102,18 @@ class WeatherDataType(
de.timklge.karooheadwind.R.drawable.arrow_0
)
data class StreamData(val data: OpenMeteoCurrentWeatherResponse?, val settings: HeadwindSettings,
val profile: UserProfile? = null, val headingResponse: HeadingResponse? = null)
val viewJob = CoroutineScope(Dispatchers.IO).launch {
val dataFlow = if (config.preview){
previewFlow()
} else {
context.streamCurrentWeatherData()
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
.combine(karooSystem.getHeadingFlow(context)) { data, heading -> data.copy(headingResponse = heading) }
}
val viewJob = CoroutineScope(Dispatchers.IO).launch {
dataFlow
.collect { (data, settings, userProfile, headingResponse) ->
Log.d(KarooHeadwindExtension.TAG, "Updating weather view")

View File

@ -25,7 +25,10 @@ import androidx.glance.layout.width
import de.timklge.karooheadwind.HeadingResponse
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.OpenMeteoData
import de.timklge.karooheadwind.OpenMeteoForecastData
import de.timklge.karooheadwind.WeatherInterpretation
import de.timklge.karooheadwind.datatypes.WeatherDataType.StreamData
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.saveWidgetSettings
import de.timklge.karooheadwind.screens.HeadwindSettings
@ -49,12 +52,16 @@ import io.hammerhead.karooext.models.ViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.time.format.FormatStyle
import kotlin.math.roundToInt
@ -70,7 +77,7 @@ class CycleHoursAction : ActionCallback {
val data = context.streamCurrentWeatherData().first()
var hourOffset = currentSettings.currentForecastHourOffset + 3
if (data == null || hourOffset >= data.forecastData.weatherCode.size) {
if (data == null || hourOffset >= ((data.forecastData?.weatherCode?.size) ?: 0)) {
hourOffset = 0
}
@ -106,6 +113,37 @@ class WeatherForecastDataType(
}
}
data class StreamData(val data: OpenMeteoCurrentWeatherResponse?, val settings: HeadwindSettings,
val widgetSettings: HeadwindWidgetSettings? = null, val profile: UserProfile? = null, val headingResponse: HeadingResponse? = null)
private fun previewFlow(): Flow<StreamData> = flow {
while (true){
val timeAtFullHour = Instant.now().truncatedTo(ChronoUnit.HOURS).epochSecond
val forecastTimes = (0..<12).map { timeAtFullHour + it * 60 * 60 }
val forecastTemperatures = (0..<12).map { 20.0 + (-20..20).random() }
val forecastPrecipitationPropability = (0..<12).map { (0..100).random() }
val forecastPrecipitation = (0..<12).map { 0.0 + (0..10).random() }
val forecastWeatherCodes = (0..<12).map { WeatherInterpretation.getKnownWeatherCodes().random() }
val forecastWindSpeed = (0..<12).map { 0.0 + (0..10).random() }
val forecastWindDirection = (0..<12).map { 0.0 + (0..360).random() }
val forecastWindGusts = (0..<12).map { 0.0 + (0..10).random() }
emit(
StreamData(
OpenMeteoCurrentWeatherResponse(
OpenMeteoData(Instant.now().epochSecond, 0, 20.0, 50, 3.0, 0, 1013.25, 15.0, 30.0, 30.0, WeatherInterpretation.getKnownWeatherCodes().random()),
0.0, 0.0, "Europe/Berlin", 30.0, 0,
OpenMeteoForecastData(forecastTimes, forecastTemperatures, forecastPrecipitationPropability,
forecastPrecipitation, forecastWeatherCodes, forecastWindSpeed, forecastWindDirection,
forecastWindGusts)
), HeadwindSettings())
)
delay(5_000)
}
}
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
Log.d(KarooHeadwindExtension.TAG, "Starting weather forecast view with $emitter")
val configJob = CoroutineScope(Dispatchers.IO).launch {
@ -118,16 +156,18 @@ class WeatherForecastDataType(
de.timklge.karooheadwind.R.drawable.arrow_0
)
data class StreamData(val data: OpenMeteoCurrentWeatherResponse?, val settings: HeadwindSettings,
val widgetSettings: HeadwindWidgetSettings? = null, val profile: UserProfile? = null, val headingResponse: HeadingResponse? = null)
val viewJob = CoroutineScope(Dispatchers.IO).launch {
val dataFlow = if (config.preview){
previewFlow()
} else {
context.streamCurrentWeatherData()
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
.combine(context.streamWidgetSettings()) { data, widgetSettings -> data.copy(widgetSettings = widgetSettings) }
.combine(karooSystem.getHeadingFlow(context)) { data, headingResponse -> data.copy(headingResponse = headingResponse) }
.collect { (data, settings, widgetSettings, userProfile, headingResponse) ->
}
val viewJob = CoroutineScope(Dispatchers.IO).launch {
dataFlow.collect { (data, settings, widgetSettings, userProfile, headingResponse) ->
Log.d(KarooHeadwindExtension.TAG, "Updating weather forecast view")
if (data == null){
@ -141,14 +181,14 @@ class WeatherForecastDataType(
val hourOffset = widgetSettings?.currentForecastHourOffset ?: 0
var previousDate: String? = let {
val unixTime = data.forecastData.time.firstOrNull()
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) {
if (index >= (data.forecastData?.weatherCode?.size ?: 0)) {
break
}
@ -160,22 +200,22 @@ class WeatherForecastDataType(
)
}
val interpretation = WeatherInterpretation.fromWeatherCode(data.forecastData.weatherCode[index])
val unixTime = data.forecastData.time[index]
val interpretation = WeatherInterpretation.fromWeatherCode(data.forecastData?.weatherCode?.get(index) ?: 0)
val unixTime = data.forecastData?.time?.get(index) ?: 0
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,
windBearing = data.forecastData.windDirection[index].roundToInt(),
windSpeed = data.forecastData.windSpeed[index].roundToInt(),
windGusts = data.forecastData.windGusts[index].roundToInt(),
windBearing = data.forecastData?.windDirection?.get(index)?.roundToInt() ?: 0,
windSpeed = data.forecastData?.windSpeed?.get(index)?.roundToInt() ?: 0,
windGusts = data.forecastData?.windGusts?.get(index)?.roundToInt() ?: 0,
windSpeedUnit = settings.windUnit,
precipitation = data.forecastData.precipitation[index],
precipitationProbability = data.forecastData.precipitationProbability[index],
precipitation = data.forecastData?.precipitation?.get(index) ?: 0.0,
precipitationProbability = data.forecastData?.precipitationProbability?.get(index) ?: 0,
precipitationUnit = if (userProfile?.preferredUnit?.distance != UserProfile.PreferredUnit.UnitType.IMPERIAL) PrecipitationUnit.MILLIMETERS else PrecipitationUnit.INCH,
temperature = data.forecastData.temperature[index].roundToInt(),
temperature = data.forecastData?.temperature?.get(index)?.roundToInt() ?: 0,
temperatureUnit = if (userProfile?.preferredUnit?.temperature != UserProfile.PreferredUnit.UnitType.IMPERIAL) TemperatureUnit.CELSIUS else TemperatureUnit.FAHRENHEIT,
timeLabel = formattedTime,
dateLabel = if (hasNewDate) formattedDate else null