Show preview in weather, weather forecast widgets (#26)
This commit is contained in:
parent
a98fcb875a
commit
2968cc0eef
@ -45,6 +45,8 @@ enum class WeatherInterpretation {
|
|||||||
else -> UNKNOWN
|
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 timezone: String,
|
||||||
val elevation: Double,
|
val elevation: Double,
|
||||||
@SerialName("utc_offset_seconds") val utfOffsetSeconds: Int,
|
@SerialName("utc_offset_seconds") val utfOffsetSeconds: Int,
|
||||||
@SerialName("hourly") val forecastData: OpenMeteoForecastData
|
@SerialName("hourly") val forecastData: OpenMeteoForecastData?
|
||||||
)
|
)
|
||||||
@ -13,6 +13,7 @@ import androidx.glance.layout.fillMaxSize
|
|||||||
import de.timklge.karooheadwind.HeadingResponse
|
import de.timklge.karooheadwind.HeadingResponse
|
||||||
import de.timklge.karooheadwind.KarooHeadwindExtension
|
import de.timklge.karooheadwind.KarooHeadwindExtension
|
||||||
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
|
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
|
||||||
|
import de.timklge.karooheadwind.OpenMeteoData
|
||||||
import de.timklge.karooheadwind.WeatherInterpretation
|
import de.timklge.karooheadwind.WeatherInterpretation
|
||||||
import de.timklge.karooheadwind.getHeadingFlow
|
import de.timklge.karooheadwind.getHeadingFlow
|
||||||
import de.timklge.karooheadwind.screens.HeadwindSettings
|
import de.timklge.karooheadwind.screens.HeadwindSettings
|
||||||
@ -34,7 +35,10 @@ import io.hammerhead.karooext.models.ViewConfig
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.awaitCancellation
|
import kotlinx.coroutines.awaitCancellation
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
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) {
|
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
|
||||||
Log.d(KarooHeadwindExtension.TAG, "Starting weather view with $emitter")
|
Log.d(KarooHeadwindExtension.TAG, "Starting weather view with $emitter")
|
||||||
val configJob = CoroutineScope(Dispatchers.IO).launch {
|
val configJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
@ -81,14 +102,18 @@ class WeatherDataType(
|
|||||||
de.timklge.karooheadwind.R.drawable.arrow_0
|
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()
|
context.streamCurrentWeatherData()
|
||||||
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
|
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
|
||||||
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
|
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
|
||||||
.combine(karooSystem.getHeadingFlow(context)) { data, heading -> data.copy(headingResponse = heading) }
|
.combine(karooSystem.getHeadingFlow(context)) { data, heading -> data.copy(headingResponse = heading) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val viewJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
dataFlow
|
||||||
.collect { (data, settings, userProfile, headingResponse) ->
|
.collect { (data, settings, userProfile, headingResponse) ->
|
||||||
Log.d(KarooHeadwindExtension.TAG, "Updating weather view")
|
Log.d(KarooHeadwindExtension.TAG, "Updating weather view")
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,10 @@ import androidx.glance.layout.width
|
|||||||
import de.timklge.karooheadwind.HeadingResponse
|
import de.timklge.karooheadwind.HeadingResponse
|
||||||
import de.timklge.karooheadwind.KarooHeadwindExtension
|
import de.timklge.karooheadwind.KarooHeadwindExtension
|
||||||
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
|
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
|
||||||
|
import de.timklge.karooheadwind.OpenMeteoData
|
||||||
|
import de.timklge.karooheadwind.OpenMeteoForecastData
|
||||||
import de.timklge.karooheadwind.WeatherInterpretation
|
import de.timklge.karooheadwind.WeatherInterpretation
|
||||||
|
import de.timklge.karooheadwind.datatypes.WeatherDataType.StreamData
|
||||||
import de.timklge.karooheadwind.getHeadingFlow
|
import de.timklge.karooheadwind.getHeadingFlow
|
||||||
import de.timklge.karooheadwind.saveWidgetSettings
|
import de.timklge.karooheadwind.saveWidgetSettings
|
||||||
import de.timklge.karooheadwind.screens.HeadwindSettings
|
import de.timklge.karooheadwind.screens.HeadwindSettings
|
||||||
@ -49,12 +52,16 @@ import io.hammerhead.karooext.models.ViewConfig
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.awaitCancellation
|
import kotlinx.coroutines.awaitCancellation
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import java.time.format.FormatStyle
|
import java.time.format.FormatStyle
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@ -70,7 +77,7 @@ class CycleHoursAction : ActionCallback {
|
|||||||
val data = context.streamCurrentWeatherData().first()
|
val data = context.streamCurrentWeatherData().first()
|
||||||
|
|
||||||
var hourOffset = currentSettings.currentForecastHourOffset + 3
|
var hourOffset = currentSettings.currentForecastHourOffset + 3
|
||||||
if (data == null || hourOffset >= data.forecastData.weatherCode.size) {
|
if (data == null || hourOffset >= ((data.forecastData?.weatherCode?.size) ?: 0)) {
|
||||||
hourOffset = 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) {
|
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
|
||||||
Log.d(KarooHeadwindExtension.TAG, "Starting weather forecast view with $emitter")
|
Log.d(KarooHeadwindExtension.TAG, "Starting weather forecast view with $emitter")
|
||||||
val configJob = CoroutineScope(Dispatchers.IO).launch {
|
val configJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
@ -118,16 +156,18 @@ class WeatherForecastDataType(
|
|||||||
de.timklge.karooheadwind.R.drawable.arrow_0
|
de.timklge.karooheadwind.R.drawable.arrow_0
|
||||||
)
|
)
|
||||||
|
|
||||||
data class StreamData(val data: OpenMeteoCurrentWeatherResponse?, val settings: HeadwindSettings,
|
val dataFlow = if (config.preview){
|
||||||
val widgetSettings: HeadwindWidgetSettings? = null, val profile: UserProfile? = null, val headingResponse: HeadingResponse? = null)
|
previewFlow()
|
||||||
|
} else {
|
||||||
val viewJob = CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
context.streamCurrentWeatherData()
|
context.streamCurrentWeatherData()
|
||||||
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
|
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
|
||||||
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
|
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
|
||||||
.combine(context.streamWidgetSettings()) { data, widgetSettings -> data.copy(widgetSettings = widgetSettings) }
|
.combine(context.streamWidgetSettings()) { data, widgetSettings -> data.copy(widgetSettings = widgetSettings) }
|
||||||
.combine(karooSystem.getHeadingFlow(context)) { data, headingResponse -> data.copy(headingResponse = headingResponse) }
|
.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")
|
Log.d(KarooHeadwindExtension.TAG, "Updating weather forecast view")
|
||||||
|
|
||||||
if (data == null){
|
if (data == null){
|
||||||
@ -141,14 +181,14 @@ class WeatherForecastDataType(
|
|||||||
val hourOffset = widgetSettings?.currentForecastHourOffset ?: 0
|
val hourOffset = widgetSettings?.currentForecastHourOffset ?: 0
|
||||||
|
|
||||||
var previousDate: String? = let {
|
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() }
|
val formattedDate = unixTime?.let { Instant.ofEpochSecond(it).atZone(ZoneId.systemDefault()).toLocalDate().toString() }
|
||||||
|
|
||||||
formattedDate
|
formattedDate
|
||||||
}
|
}
|
||||||
|
|
||||||
for (index in hourOffset..hourOffset + 2){
|
for (index in hourOffset..hourOffset + 2){
|
||||||
if (index >= data.forecastData.weatherCode.size) {
|
if (index >= (data.forecastData?.weatherCode?.size ?: 0)) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,22 +200,22 @@ class WeatherForecastDataType(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val interpretation = WeatherInterpretation.fromWeatherCode(data.forecastData.weatherCode[index])
|
val interpretation = WeatherInterpretation.fromWeatherCode(data.forecastData?.weatherCode?.get(index) ?: 0)
|
||||||
val unixTime = data.forecastData.time[index]
|
val unixTime = data.forecastData?.time?.get(index) ?: 0
|
||||||
val formattedTime = timeFormatter.format(Instant.ofEpochSecond(unixTime))
|
val formattedTime = timeFormatter.format(Instant.ofEpochSecond(unixTime))
|
||||||
val formattedDate = Instant.ofEpochSecond(unixTime).atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT))
|
val formattedDate = Instant.ofEpochSecond(unixTime).atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT))
|
||||||
val hasNewDate = formattedDate != previousDate || index == 0
|
val hasNewDate = formattedDate != previousDate || index == 0
|
||||||
|
|
||||||
Weather(baseBitmap,
|
Weather(baseBitmap,
|
||||||
current = interpretation,
|
current = interpretation,
|
||||||
windBearing = data.forecastData.windDirection[index].roundToInt(),
|
windBearing = data.forecastData?.windDirection?.get(index)?.roundToInt() ?: 0,
|
||||||
windSpeed = data.forecastData.windSpeed[index].roundToInt(),
|
windSpeed = data.forecastData?.windSpeed?.get(index)?.roundToInt() ?: 0,
|
||||||
windGusts = data.forecastData.windGusts[index].roundToInt(),
|
windGusts = data.forecastData?.windGusts?.get(index)?.roundToInt() ?: 0,
|
||||||
windSpeedUnit = settings.windUnit,
|
windSpeedUnit = settings.windUnit,
|
||||||
precipitation = data.forecastData.precipitation[index],
|
precipitation = data.forecastData?.precipitation?.get(index) ?: 0.0,
|
||||||
precipitationProbability = data.forecastData.precipitationProbability[index],
|
precipitationProbability = data.forecastData?.precipitationProbability?.get(index) ?: 0,
|
||||||
precipitationUnit = if (userProfile?.preferredUnit?.distance != UserProfile.PreferredUnit.UnitType.IMPERIAL) PrecipitationUnit.MILLIMETERS else PrecipitationUnit.INCH,
|
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,
|
temperatureUnit = if (userProfile?.preferredUnit?.temperature != UserProfile.PreferredUnit.UnitType.IMPERIAL) TemperatureUnit.CELSIUS else TemperatureUnit.FAHRENHEIT,
|
||||||
timeLabel = formattedTime,
|
timeLabel = formattedTime,
|
||||||
dateLabel = if (hasNewDate) formattedDate else null
|
dateLabel = if (hasNewDate) formattedDate else null
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user