diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e7506d2..2fe63f2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,8 +15,8 @@ android { applicationId = "de.timklge.karooheadwind" minSdk = 26 targetSdk = 35 - versionCode = 6 - versionName = "1.1.2" + versionCode = 7 + versionName = "1.1.3" } signingConfigs { diff --git a/app/manifest.json b/app/manifest.json index 2def8fe..a31a952 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -3,9 +3,9 @@ "packageName": "de.timklge.karooheadwind", "iconUrl": "https://github.com/timklge/karoo-headwind/releases/latest/download/karoo-headwind.png", "latestApkUrl": "https://github.com/timklge/karoo-headwind/releases/latest/download/app-release.apk", - "latestVersion": "1.1.2", - "latestVersionCode": 6, + "latestVersion": "1.1.3", + "latestVersionCode": 7, "developer": "timklge", "description": "Provides headwind direction, wind speed and other weather data fields", - "releaseNotes": "Add hourly forecast and temperature datafields. Add setting to use absolute wind direction on headwind datafield." + "releaseNotes": "Add hourly forecast and temperature datafields. Show error message in fields if no weather data or gps is available." } \ No newline at end of file diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt b/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt index 0f438ce..a3d5f80 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt @@ -2,8 +2,25 @@ package de.timklge.karooheadwind import android.content.Context import android.util.Log +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType +import androidx.compose.ui.unit.dp import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.glance.GlanceModifier +import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi +import androidx.glance.appwidget.GlanceRemoteViews +import androidx.glance.appwidget.RemoteViewsCompositionResult +import androidx.glance.color.ColorProvider +import androidx.glance.layout.Alignment +import androidx.glance.layout.Box +import androidx.glance.layout.fillMaxSize +import androidx.glance.layout.padding +import androidx.glance.text.Text +import androidx.glance.text.TextAlign +import androidx.glance.text.TextStyle import de.timklge.karooheadwind.datatypes.GpsCoordinates import de.timklge.karooheadwind.screens.HeadwindSettings import de.timklge.karooheadwind.screens.HeadwindStats @@ -28,6 +45,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf @@ -86,7 +104,30 @@ fun KarooSystemService.streamDataFlow(dataTypeId: String): Flow { } } -fun Context.streamCurrentWeatherData(): Flow { +@OptIn(ExperimentalGlanceRemoteViewsApi::class) +suspend fun getErrorWidget(glance: GlanceRemoteViews, context: Context, settings: HeadwindSettings?, headingResponse: HeadingResponse?): RemoteViewsCompositionResult { + return glance.compose(context, DpSize.Unspecified) { + Box(modifier = GlanceModifier.fillMaxSize().padding(5.dp), contentAlignment = Alignment.Center) { + val errorMessage = if (settings?.welcomeDialogAccepted == false) { + "Headwind app not set up" + } else if (headingResponse is HeadingResponse.NoGps){ + "No GPS signal" + } else if (headingResponse is HeadingResponse.NoWeatherData) { + "No weather data" + } else { + "Unknown error" + } + + Log.d(KarooHeadwindExtension.TAG, "Error widget: $errorMessage") + + Text(text = errorMessage, style = TextStyle(fontSize = TextUnit(16f, TextUnitType.Sp), + textAlign = TextAlign.Center, + color = ColorProvider(Color.Black, Color.White))) + } + } +} + +fun Context.streamCurrentWeatherData(): Flow { return dataStore.data.map { settingsJson -> try { val data = settingsJson[currentDataKey] @@ -95,7 +136,13 @@ fun Context.streamCurrentWeatherData(): Flow { Log.e(KarooHeadwindExtension.TAG, "Failed to read weather data", e) null } - }.filterNotNull().distinctUntilChanged().filter { it.current.time * 1000 >= System.currentTimeMillis() - (1000 * 60 * 60 * 12) } + }.distinctUntilChanged().map { response -> + if (response != null && response.current.time * 1000 >= System.currentTimeMillis() - (1000 * 60 * 60 * 12)){ + response + } else { + null + } + } } fun Context.streamWidgetSettings(): Flow { @@ -212,63 +259,95 @@ fun signedAngleDifference(angle1: Double, angle2: Double): Double { return sign * diff } -fun KarooSystemService.getRelativeHeadingFlow(context: Context): Flow { +sealed class HeadingResponse { + data object NoGps: HeadingResponse() + data object NoWeatherData: HeadingResponse() + data class Value(val diff: Double): HeadingResponse() +} + +fun KarooSystemService.getRelativeHeadingFlow(context: Context): Flow { val currentWeatherData = context.streamCurrentWeatherData() return getHeadingFlow() - .filter { it >= 0 } .combine(currentWeatherData) { bearing, data -> bearing to data } .map { (bearing, data) -> - val windBearing = data.current.windDirection + 180 - val diff = signedAngleDifference(bearing, windBearing) - Log.d(KarooHeadwindExtension.TAG, "Wind bearing: $bearing vs $windBearing => $diff") + when { + bearing is HeadingResponse.Value && data != null -> { + val windBearing = data.current.windDirection + 180 + val diff = signedAngleDifference(bearing.diff, windBearing) - diff + Log.d(KarooHeadwindExtension.TAG, "Wind bearing: $bearing vs $windBearing => $diff") + + HeadingResponse.Value(diff) + } + bearing is HeadingResponse.NoGps -> HeadingResponse.NoGps + bearing is HeadingResponse.NoWeatherData || data == null -> HeadingResponse.NoWeatherData + else -> bearing + } } } -fun KarooSystemService.getHeadingFlow(): Flow { - // return flowOf(20.0) +fun KarooSystemService.getHeadingFlow(): Flow { + // return flowOf(HeadingResponse.Value(20.0)) return streamDataFlow(DataType.Type.LOCATION) - .mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.values } + .map { (it as? StreamState.Streaming)?.dataPoint?.values } .map { values -> - val heading = values[DataType.Field.LOC_BEARING] + val heading = values?.get(DataType.Field.LOC_BEARING) Log.d(KarooHeadwindExtension.TAG, "Updated gps bearing: $heading") - heading ?: 0.0 + val headingValue = heading?.let { HeadingResponse.Value(it) } + + headingValue ?: HeadingResponse.NoGps } .distinctUntilChanged() - .scan(emptyList()) { acc, value -> /* Average over 3 values */ + .scan(emptyList()) { acc, value -> /* Average over 3 values */ + if (value !is HeadingResponse.Value) return@scan listOf(value) + val newAcc = acc + value if (newAcc.size > 3) newAcc.drop(1) else newAcc } - .map { it.average() } + .map { data -> + if (data.isEmpty()) return@map HeadingResponse.NoGps + + if (data.all { it is HeadingResponse.Value }) { + val avg = data.mapNotNull { (it as? HeadingResponse.Value)?.diff }.average() + HeadingResponse.Value(avg) + } else { + data.first() + } + } } @OptIn(FlowPreview::class) -fun KarooSystemService.getGpsCoordinateFlow(context: Context): Flow { +fun KarooSystemService.getGpsCoordinateFlow(context: Context): Flow { // return flowOf(GpsCoordinates(52.5164069,13.3784)) return streamDataFlow(DataType.Type.LOCATION) - .mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.values } - .mapNotNull { values -> - val lat = values[DataType.Field.LOC_LATITUDE] - val lon = values[DataType.Field.LOC_LONGITUDE] + .map { (it as? StreamState.Streaming)?.dataPoint?.values } + .map { values -> + val lat = values?.get(DataType.Field.LOC_LATITUDE) + val lon = values?.get(DataType.Field.LOC_LONGITUDE) if (lat != null && lon != null){ - Log.d(KarooHeadwindExtension.TAG, "Updated gps coords: $lat $lon") + Log.d(KarooHeadwindExtension.TAG, "Updated gps coordinates: $lat $lon") GpsCoordinates(lat, lon) } else { - Log.e(KarooHeadwindExtension.TAG, "Missing gps values: $values") + Log.w(KarooHeadwindExtension.TAG, "Gps unavailable: $values") null } } .combine(context.streamSettings(this)) { gps, settings -> gps to settings } .map { (gps, settings) -> - val rounded = gps.round(settings.roundLocationTo.km.toDouble()) - Log.d(KarooHeadwindExtension.TAG, "Round location to ${settings.roundLocationTo.km} - $rounded") + val rounded = gps?.round(settings.roundLocationTo.km.toDouble()) + if (rounded != null) Log.d(KarooHeadwindExtension.TAG, "Round location to ${settings.roundLocationTo.km} - $rounded") rounded } - .distinctUntilChanged { old, new -> old.distanceTo(new).absoluteValue < 0.001 } + .distinctUntilChanged { old, new -> + if (old != null && new != null) { + old.distanceTo(new).absoluteValue < 0.001 + } else { + old == new + } + } .debounce(Duration.ofSeconds(10)) } \ No newline at end of file diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt b/app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt index b37a53e..dc029cd 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt @@ -35,7 +35,7 @@ import kotlinx.coroutines.launch import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes -class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.2") { +class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.3") { companion object { const val TAG = "karoo-headwind" } @@ -62,7 +62,7 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.2") { ) } - data class StreamData(val settings: HeadwindSettings, val gps: GpsCoordinates, + data class StreamData(val settings: HeadwindSettings, val gps: GpsCoordinates?, val profile: UserProfile? = null) @OptIn(ExperimentalCoroutinesApi::class) @@ -80,7 +80,7 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.2") { val gpsFlow = karooSystem .getGpsCoordinateFlow(this@KarooHeadwindExtension) - .transformLatest { value: GpsCoordinates -> + .transformLatest { value: GpsCoordinates? -> while(true){ emit(value) delay(1.hours) @@ -101,6 +101,10 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.2") { HeadwindStats() } + if (gps == null){ + error("No GPS coordinates available") + } + val response = karooSystem.makeOpenMeteoHttpRequest(gps, settings, profile) if (response.error != null){ try { diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/BaseDataType.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/BaseDataType.kt index e59a2aa..ce93f2a 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/BaseDataType.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/BaseDataType.kt @@ -12,6 +12,7 @@ import io.hammerhead.karooext.models.DataType import io.hammerhead.karooext.models.StreamState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.launch abstract class BaseDataType( @@ -25,10 +26,12 @@ abstract class BaseDataType( val job = CoroutineScope(Dispatchers.IO).launch { val currentWeatherData = applicationContext.streamCurrentWeatherData() - currentWeatherData.collect { data -> - val value = getValue(data) - Log.d(KarooHeadwindExtension.TAG, "$dataTypeId: $value") - emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to value)))) + currentWeatherData + .filterNotNull() + .collect { data -> + val value = getValue(data) + Log.d(KarooHeadwindExtension.TAG, "$dataTypeId: $value") + emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to value)))) } } emitter.setCancellable { diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindDirectionDataType.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindDirectionDataType.kt index 832d154..ee14750 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindDirectionDataType.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindDirectionDataType.kt @@ -6,7 +6,9 @@ import android.util.Log import androidx.compose.ui.unit.DpSize import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi import androidx.glance.appwidget.GlanceRemoteViews +import de.timklge.karooheadwind.HeadingResponse import de.timklge.karooheadwind.KarooHeadwindExtension +import de.timklge.karooheadwind.getErrorWidget import de.timklge.karooheadwind.getRelativeHeadingFlow import de.timklge.karooheadwind.screens.HeadwindSettings import de.timklge.karooheadwind.screens.WindDirectionIndicatorSetting @@ -47,7 +49,8 @@ class HeadwindDirectionDataType( val job = CoroutineScope(Dispatchers.IO).launch { karooSystem.getRelativeHeadingFlow(applicationContext) .collect { diff -> - emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to diff)))) + val value = (diff as? HeadingResponse.Value)?.diff ?: 0.0 + emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to value)))) } } emitter.setCancellable { @@ -55,7 +58,7 @@ class HeadwindDirectionDataType( } } - data class StreamData(val value: Double, val absoluteWindDirection: Double, val windSpeed: Double, val settings: HeadwindSettings) + data class StreamData(val headingResponse: HeadingResponse?, val absoluteWindDirection: Double?, val windSpeed: Double?, val settings: HeadwindSettings? = null) private fun previewFlow(): Flow { return flow { @@ -63,7 +66,7 @@ class HeadwindDirectionDataType( val bearing = (0..360).random().toDouble() val windSpeed = (-20..20).random() - emit(StreamData(bearing, bearing, windSpeed.toDouble(), HeadwindSettings())) + emit(StreamData(HeadingResponse.Value(bearing), bearing, windSpeed.toDouble(), HeadwindSettings())) delay(2_000) } } @@ -86,36 +89,47 @@ class HeadwindDirectionDataType( val flow = if (config.preview) { previewFlow() } else { - karooSystem.streamDataFlow(dataTypeId) - .mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue } - .combine(context.streamCurrentWeatherData()) { value, data -> value to data } - .combine(context.streamSettings(karooSystem)) { (value, data), settings -> - StreamData(value, data.current.windDirection, data.current.windSpeed, settings) - } + karooSystem.getRelativeHeadingFlow(context) + .combine(context.streamCurrentWeatherData()) { headingResponse, data -> StreamData(headingResponse, data?.current?.windDirection, data?.current?.windSpeed) } + .combine(context.streamSettings(karooSystem)) { data, settings -> data.copy(settings = settings) } } val viewJob = CoroutineScope(Dispatchers.IO).launch { flow.collect { streamData -> - Log.d(KarooHeadwindExtension.TAG, "Updating headwind direction view") - val windSpeed = streamData.windSpeed - val windDirection = when (streamData.settings.windDirectionIndicatorSetting){ - WindDirectionIndicatorSetting.HEADWIND_DIRECTION -> streamData.value - WindDirectionIndicatorSetting.WIND_DIRECTION -> streamData.absoluteWindDirection + 180 - } - val text = when (streamData.settings.windDirectionIndicatorTextSetting) { - WindDirectionIndicatorTextSetting.HEADWIND_SPEED -> { - val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed - headwindSpeed.roundToInt().toString() - } - WindDirectionIndicatorTextSetting.WIND_SPEED -> windSpeed.roundToInt().toString() - WindDirectionIndicatorTextSetting.NONE -> "" + Log.d(KarooHeadwindExtension.TAG, "Updating headwind direction view") + + val value = (streamData.headingResponse as? HeadingResponse.Value)?.diff + if (value == null || streamData.absoluteWindDirection == null || streamData.settings == null || streamData.windSpeed == null){ + var headingResponse = streamData.headingResponse + + if (headingResponse is HeadingResponse.Value && (streamData.absoluteWindDirection == null || streamData.windSpeed == null)){ + headingResponse = HeadingResponse.NoWeatherData } - val result = glance.compose(context, DpSize.Unspecified) { - HeadwindDirection(baseBitmap, windDirection.roundToInt(), config.textSize, text) - } + emitter.updateView(getErrorWidget(glance, context, streamData.settings, headingResponse).remoteViews) - emitter.updateView(result.remoteViews) + return@collect + } + + val windSpeed = streamData.windSpeed + val windDirection = when (streamData.settings.windDirectionIndicatorSetting){ + WindDirectionIndicatorSetting.HEADWIND_DIRECTION -> value + WindDirectionIndicatorSetting.WIND_DIRECTION -> streamData.absoluteWindDirection + 180 + } + val text = when (streamData.settings.windDirectionIndicatorTextSetting) { + WindDirectionIndicatorTextSetting.HEADWIND_SPEED -> { + val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed + headwindSpeed.roundToInt().toString() + } + WindDirectionIndicatorTextSetting.WIND_SPEED -> windSpeed.roundToInt().toString() + WindDirectionIndicatorTextSetting.NONE -> "" + } + + val result = glance.compose(context, DpSize.Unspecified) { + HeadwindDirection(baseBitmap, windDirection.roundToInt(), config.textSize, text) + } + + emitter.updateView(result.remoteViews) } } emitter.setCancellable { diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindSpeedDataType.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindSpeedDataType.kt index 2af4a6e..f34a370 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindSpeedDataType.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindSpeedDataType.kt @@ -1,6 +1,7 @@ package de.timklge.karooheadwind.datatypes import android.content.Context +import de.timklge.karooheadwind.HeadingResponse import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse import de.timklge.karooheadwind.getRelativeHeadingFlow import de.timklge.karooheadwind.screens.HeadwindSettings @@ -15,6 +16,7 @@ import io.hammerhead.karooext.models.StreamState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import kotlin.math.cos @@ -22,16 +24,17 @@ class HeadwindSpeedDataType( private val karooSystem: KarooSystemService, private val context: Context) : DataTypeImpl("karoo-headwind", "headwindSpeed"){ - data class StreamData(val value: Double, val data: OpenMeteoCurrentWeatherResponse, val settings: HeadwindSettings) + data class StreamData(val headingResponse: HeadingResponse, val weatherResponse: OpenMeteoCurrentWeatherResponse?, val settings: HeadwindSettings) override fun startStream(emitter: Emitter) { val job = CoroutineScope(Dispatchers.IO).launch { karooSystem.getRelativeHeadingFlow(context) .combine(context.streamCurrentWeatherData()) { value, data -> value to data } .combine(context.streamSettings(karooSystem)) { (value, data), settings -> StreamData(value, data, settings) } + .filter { it.weatherResponse != null } .collect { streamData -> - val windSpeed = streamData.data.current.windSpeed - val windDirection = streamData.value + val windSpeed = streamData.weatherResponse?.current?.windSpeed ?: 0.0 + val windDirection = (streamData.headingResponse as? HeadingResponse.Value)?.diff ?: 0.0 val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to headwindSpeed)))) 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 7b927b0..5d735be 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherDataType.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherDataType.kt @@ -4,15 +4,22 @@ import android.content.Context import android.graphics.BitmapFactory import android.util.Log import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType import androidx.glance.GlanceModifier import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi import androidx.glance.appwidget.GlanceRemoteViews import androidx.glance.layout.Alignment import androidx.glance.layout.Box import androidx.glance.layout.fillMaxSize +import androidx.glance.text.Text +import androidx.glance.text.TextStyle +import de.timklge.karooheadwind.HeadingResponse import de.timklge.karooheadwind.KarooHeadwindExtension import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse import de.timklge.karooheadwind.WeatherInterpretation +import de.timklge.karooheadwind.getErrorWidget +import de.timklge.karooheadwind.getHeadingFlow import de.timklge.karooheadwind.screens.HeadwindSettings import de.timklge.karooheadwind.screens.PrecipitationUnit import de.timklge.karooheadwind.screens.TemperatureUnit @@ -33,6 +40,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.launch import java.time.Instant @@ -57,6 +66,7 @@ class WeatherDataType( val currentWeatherData = applicationContext.streamCurrentWeatherData() currentWeatherData + .filterNotNull() .collect { data -> Log.d(KarooHeadwindExtension.TAG, "Wind code: ${data.current.weatherCode}") emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to data.current.weatherCode.toDouble())))) @@ -79,15 +89,23 @@ class WeatherDataType( de.timklge.karooheadwind.R.drawable.arrow_0 ) - data class StreamData(val data: OpenMeteoCurrentWeatherResponse, val settings: HeadwindSettings, - val profile: UserProfile? = null) + data class StreamData(val data: OpenMeteoCurrentWeatherResponse?, val settings: HeadwindSettings, + val profile: UserProfile? = null, val headingResponse: HeadingResponse? = null) val viewJob = CoroutineScope(Dispatchers.IO).launch { context.streamCurrentWeatherData() .combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) } .combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) } - .collect { (data, settings, userProfile) -> + .combine(karooSystem.getHeadingFlow()) { data, heading -> data.copy(headingResponse = heading) } + .collect { (data, settings, userProfile, headingResponse) -> Log.d(KarooHeadwindExtension.TAG, "Updating weather view") + + if (data == null){ + emitter.updateView(getErrorWidget(glance, context, settings, headingResponse).remoteViews) + + return@collect + } + val interpretation = WeatherInterpretation.fromWeatherCode(data.current.weatherCode) val formattedTime = timeFormatter.format(Instant.ofEpochSecond(data.current.time)) 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 f30416b..e14668a 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherForecastDataType.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherForecastDataType.kt @@ -3,10 +3,6 @@ package de.timklge.karooheadwind.datatypes import android.content.Context import android.graphics.BitmapFactory import android.util.Log -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp @@ -25,12 +21,13 @@ import androidx.glance.layout.Row import androidx.glance.layout.Spacer import androidx.glance.layout.fillMaxHeight import androidx.glance.layout.fillMaxSize -import androidx.glance.layout.padding import androidx.glance.layout.width +import de.timklge.karooheadwind.HeadingResponse import de.timklge.karooheadwind.KarooHeadwindExtension import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse import de.timklge.karooheadwind.WeatherInterpretation -import de.timklge.karooheadwind.saveSettings +import de.timklge.karooheadwind.getErrorWidget +import de.timklge.karooheadwind.getHeadingFlow import de.timklge.karooheadwind.saveWidgetSettings import de.timklge.karooheadwind.screens.HeadwindSettings import de.timklge.karooheadwind.screens.HeadwindWidgetSettings @@ -55,7 +52,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.launch import java.time.Instant import java.time.ZoneId @@ -74,7 +70,7 @@ class CycleHoursAction : ActionCallback { val data = context.streamCurrentWeatherData().first() var hourOffset = currentSettings.currentForecastHourOffset + 3 - if (hourOffset >= data.forecastData.weatherCode.size) { + if (data == null || hourOffset >= data.forecastData.weatherCode.size) { hourOffset = 0 } @@ -101,8 +97,8 @@ class WeatherForecastDataType( currentWeatherData .collect { data -> - Log.d(KarooHeadwindExtension.TAG, "Wind code: ${data.current.weatherCode}") - emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to data.current.weatherCode.toDouble())))) + Log.d(KarooHeadwindExtension.TAG, "Wind code: ${data?.current?.weatherCode}") + emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to (data?.current?.weatherCode?.toDouble() ?: 0.0))))) } } emitter.setCancellable { @@ -122,16 +118,23 @@ 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) + 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 { 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) } - .collect { (data, settings, widgetSettings, userProfile) -> - Log.d(KarooHeadwindExtension.TAG, "Updating weather view") + .combine(karooSystem.getHeadingFlow()) { data, headingResponse -> data.copy(headingResponse = headingResponse) } + .collect { (data, settings, widgetSettings, userProfile, headingResponse) -> + Log.d(KarooHeadwindExtension.TAG, "Updating weather forecast view") + + if (data == null){ + emitter.updateView(getErrorWidget(glance, context, settings, headingResponse).remoteViews) + + return@collect + } val result = glance.compose(context, DpSize.Unspecified) { Row(modifier = GlanceModifier.fillMaxSize().clickable(onClick = actionRunCallback()), horizontalAlignment = Alignment.Horizontal.CenterHorizontally) {