diff --git a/README.md b/README.md index be04670..cd5d76d 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ Currently, Hammerhead has not yet released an on-device app store for easy insta After installing this app on your Karoo, you can add a data field showing the relative wind direction or one of the auxiliary fields to your data pages. The relative wind direction will be shown as an arrow image, with an optional overlay of the wind speed in your chosen unit of measurement (default is kilometers per hour). -The app will automatically attempt to download weather data for your current approximate location from the [open-meteo.com](https://open-meteo.com) API once your device has acquired a GPS fix. The API service is free for non-commercial use. Your location is rounded to approximately two kilometers to maintain privacy. The data is updated when you ride more than two kilometers from the location where the weather data was downloaded or after one hour at the latest. If the app cannot connect to the weather service, it will retry the download every minute. Downloading weather data should work on Karoo 2 if you have a SIM card inserted or on Karoo 3 via the companion app. +The app will automatically attempt to download weather data for your current approximate location from the [open-meteo.com](https://open-meteo.com) API once your device has acquired a GPS fix. The API service is free for non-commercial use. Your location is rounded to approximately two kilometers to maintain privacy. The data is updated when you ride more than two kilometers from the location where the weather data was downloaded or after one hour at the latest. If the app cannot connect to the weather service, it will retry the download every minute. Downloading weather data should work on Karoo 2 if you have a SIM card inserted or on Karoo 3 via your phone's internet connection if you have the Karoo companion app installed. ## Credits -- Icons are from [boxicons.com](https://boxicons.com) (MIT-licensed). +- Icons are from [boxicons.com](https://boxicons.com) (MIT-licensed) - Made possible by the generous usage terms of [open-meteo.com](https://open-meteo.com) ## Links diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/Extensions.kt b/app/src/main/kotlin/de/timklge/karoowinddir/Extensions.kt index 25780ae..536d6b3 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/Extensions.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/Extensions.kt @@ -80,7 +80,7 @@ fun Context.streamCurrentWeatherData(): Flow { Log.e(KarooWinddirExtension.TAG, "Failed to read preferences", e) null } - }.filterNotNull().distinctUntilChanged().filter { it.current.time * 1000 >= System.currentTimeMillis() - (1000 * 60 * 60 * 3) } + }.filterNotNull().distinctUntilChanged().filter { it.current.time * 1000 >= System.currentTimeMillis() - (1000 * 60 * 60 * 12) } } fun Context.streamSettings(): Flow { @@ -112,8 +112,8 @@ fun Context.streamStats(): Flow { @OptIn(FlowPreview::class) suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoordinates, settings: WinddirSettings): HttpResponseState.Complete { return callbackFlow { - // https://open-meteo.com/en/docs#current=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=&daily=&location_mode=csv_coordinates&timeformat=unixtime&forecast_days=3 - val url = "https://api.open-meteo.com/v1/forecast?latitude=${gpsCoordinates.lat}&longitude=${gpsCoordinates.lon}¤t=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m,surface_pressure&timeformat=unixtime&wind_speed_unit=${settings.windUnit.id}&precipitation_unit=${settings.precipitationUnit.id}" + // https://open-meteo.com/en/docs#current=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=&daily=&location_mode=csv_coordinates&timeformat=unixtime&forecast_days=3 + val url = "https://api.open-meteo.com/v1/forecast?latitude=${gpsCoordinates.lat}&longitude=${gpsCoordinates.lon}¤t=weather_code,temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m,surface_pressure&timeformat=unixtime&wind_speed_unit=${settings.windUnit.id}&precipitation_unit=${settings.precipitationUnit.id}" Log.d(KarooWinddirExtension.TAG, "Http request to ${url}...") @@ -143,7 +143,7 @@ suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoord } fun KarooSystemService.getHeadingFlow(): Flow { - // return flowOf(2) + return flowOf(2) return streamDataFlow(DataType.Type.HEADING) .mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue } @@ -153,7 +153,7 @@ fun KarooSystemService.getHeadingFlow(): Flow { @OptIn(FlowPreview::class) fun KarooSystemService.getGpsCoordinateFlow(): Flow { - // return flowOf(GpsCoordinates(52.5164069,13.3784)) + return flowOf(GpsCoordinates(52.5164069,13.3784)) return streamDataFlow("TYPE_LOCATION_ID") .mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.values } diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/KarooWindroseExtension.kt b/app/src/main/kotlin/de/timklge/karoowinddir/KarooWinddirExtension.kt similarity index 95% rename from app/src/main/kotlin/de/timklge/karoowinddir/KarooWindroseExtension.kt rename to app/src/main/kotlin/de/timklge/karoowinddir/KarooWinddirExtension.kt index cd950c7..ee8e369 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/KarooWindroseExtension.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/KarooWinddirExtension.kt @@ -9,7 +9,8 @@ import de.timklge.karoowinddir.datatypes.SurfacePressureDataType import de.timklge.karoowinddir.datatypes.WindDirectionDataType import de.timklge.karoowinddir.datatypes.WindGustsDataType import de.timklge.karoowinddir.datatypes.WindSpeedDataType -import de.timklge.karoowinddir.datatypes.WinddirDataType +import de.timklge.karoowinddir.datatypes.RelativeWindDirectionDataType +import de.timklge.karoowinddir.datatypes.WeatherDataType import de.timklge.karoowinddir.screens.WinddirStats import io.hammerhead.karooext.KarooSystemService import io.hammerhead.karooext.extension.KarooExtension @@ -39,7 +40,8 @@ class KarooWinddirExtension : KarooExtension("karoo-winddir", "1.0.0-beta1") { override val types by lazy { listOf( - WinddirDataType(karooSystem, applicationContext), + RelativeWindDirectionDataType(karooSystem, applicationContext), + WeatherDataType(karooSystem, applicationContext), RelativeHumidityDataType(applicationContext), CloudCoverDataType(applicationContext), WindSpeedDataType(applicationContext), diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/OpenMeteoData.kt b/app/src/main/kotlin/de/timklge/karoowinddir/OpenMeteoData.kt index be01dff..ffbd148 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/OpenMeteoData.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/OpenMeteoData.kt @@ -13,9 +13,29 @@ data class OpenMeteoData( @SerialName("surface_pressure") val surfacePressure: Double, @SerialName("wind_speed_10m") val windSpeed: Double, @SerialName("wind_direction_10m") val windDirection: Double, - @SerialName("wind_gusts_10m") val windGusts: Double + @SerialName("wind_gusts_10m") val windGusts: Double, + @SerialName("weather_code") val weatherCode: Int, ) +enum class WeatherInterpretation { + CLEAR, CLOUDY, RAINY, SNOWY, DRIZZLE, THUNDERSTORM, UNKNOWN; + + companion object { + // WMO weather interpretation codes (WW) + fun fromWeatherCode(code: Int): WeatherInterpretation { + return when(code){ + 0 -> CLEAR + 1, 2, 3 -> CLOUDY + 45, 48, 61, 63, 65, 66, 67, 80, 81, 82 -> RAINY + 71, 73, 75, 77, 85, 86 -> SNOWY + 51, 53, 55, 56, 57 -> DRIZZLE + 95, 96, 99 -> THUNDERSTORM + else -> UNKNOWN + } + } + } +} + @Serializable data class OpenMeteoCurrentWeatherResponse( val current: OpenMeteoData, diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/BaseDataType.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/BaseDataType.kt new file mode 100644 index 0000000..2b67915 --- /dev/null +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/BaseDataType.kt @@ -0,0 +1,39 @@ +package de.timklge.karoowinddir.datatypes + +import android.content.Context +import android.util.Log +import de.timklge.karoowinddir.KarooWinddirExtension +import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse +import de.timklge.karoowinddir.streamCurrentWeatherData +import io.hammerhead.karooext.extension.DataTypeImpl +import io.hammerhead.karooext.internal.Emitter +import io.hammerhead.karooext.models.DataPoint +import io.hammerhead.karooext.models.DataType +import io.hammerhead.karooext.models.StreamState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +abstract class BaseDataType( + private val applicationContext: Context, + dataTypeId: String +) : DataTypeImpl("karoo-winddir", dataTypeId) { + abstract fun getValue(data: OpenMeteoCurrentWeatherResponse): Double + + override fun startStream(emitter: Emitter) { + Log.d(KarooWinddirExtension.TAG, "start $dataTypeId stream") + val job = CoroutineScope(Dispatchers.IO).launch { + val currentWeatherData = applicationContext.streamCurrentWeatherData() + + currentWeatherData.collect { data -> + val value = getValue(data) + Log.d(KarooWinddirExtension.TAG, "$dataTypeId: $value") + emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to value)))) + } + } + emitter.setCancellable { + Log.d(KarooWinddirExtension.TAG, "stop $dataTypeId stream") + job.cancel() + } + } +} diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/CloudCoverDataType.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/CloudCoverDataType.kt index 380efdb..9139a73 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/CloudCoverDataType.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/CloudCoverDataType.kt @@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes import android.content.Context import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse -class CloudCoverDataType(context: Context) : WeatherDataType(context, "cloudCover"){ +class CloudCoverDataType(context: Context) : BaseDataType(context, "cloudCover"){ override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double { return data.current.cloudCover.toDouble() } diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/PrecipitationDataType.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/PrecipitationDataType.kt index 5320913..af27566 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/PrecipitationDataType.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/PrecipitationDataType.kt @@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes import android.content.Context import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse -class PrecipitationDataType(context: Context) : WeatherDataType(context, "precipitation"){ +class PrecipitationDataType(context: Context) : BaseDataType(context, "precipitation"){ override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double { return data.current.precipitation } diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeHumidityDataType.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeHumidityDataType.kt index 5d55fd6..e6c7642 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeHumidityDataType.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeHumidityDataType.kt @@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes import android.content.Context import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse -class RelativeHumidityDataType(context: Context) : WeatherDataType(context, "relativeHumidity"){ +class RelativeHumidityDataType(context: Context) : BaseDataType(context, "relativeHumidity"){ override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double { return data.current.relativeHumidity.toDouble() } diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindroseDataType.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeWindDirectionDataType.kt similarity index 85% rename from app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindroseDataType.kt rename to app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeWindDirectionDataType.kt index 6cdbc4c..d8a7b95 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindroseDataType.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeWindDirectionDataType.kt @@ -1,7 +1,7 @@ package de.timklge.karoowinddir.datatypes -import android.util.Log import android.content.Context +import android.util.Log import androidx.compose.ui.unit.DpSize import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi import androidx.glance.appwidget.GlanceRemoteViews @@ -23,7 +23,6 @@ import io.hammerhead.karooext.models.UpdateGraphicConfig import io.hammerhead.karooext.models.ViewConfig import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ObsoleteCoroutinesApi import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter @@ -31,10 +30,11 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.launch import kotlin.math.abs +import kotlin.math.cos import kotlin.math.roundToInt @OptIn(ExperimentalGlanceRemoteViewsApi::class) -class WinddirDataType( +class RelativeWindDirectionDataType( private val karooSystem: KarooSystemService, private val applicationContext: Context ) : DataTypeImpl("karoo-winddir", "winddir") { @@ -59,7 +59,6 @@ class WinddirDataType( } override fun startStream(emitter: Emitter) { - Log.d(KarooWinddirExtension.TAG, "start winddir stream") val job = CoroutineScope(Dispatchers.IO).launch { val currentWeatherData = applicationContext.streamCurrentWeatherData() @@ -72,19 +71,17 @@ class WinddirDataType( val windBearing = data.current.windDirection + 180 val diff = (signedAngleDifference(bearing, windBearing) + 360) % 360 - Log.d(KarooWinddirExtension.TAG, "wind bearing: $bearing vs $windBearing => $diff") + Log.d(KarooWinddirExtension.TAG, "Wind bearing: $bearing vs $windBearing => $diff") emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to diff)))) } } emitter.setCancellable { - Log.d(KarooWinddirExtension.TAG, "stop winddir stream") job.cancel() } } - @OptIn(ObsoleteCoroutinesApi::class) override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) { - Log.d(KarooWinddirExtension.TAG, "Starting winddir view with $emitter") + Log.d(KarooWinddirExtension.TAG, "Starting relative wind direction view with $emitter") val configJob = CoroutineScope(Dispatchers.IO).launch { emitter.onNext(UpdateGraphicConfig(showHeader = false)) awaitCancellation() @@ -98,15 +95,19 @@ class WinddirDataType( .combine(context.streamCurrentWeatherData()) { value, data -> value to data } .combine(context.streamSettings()) { (value, data), settings -> StreamData(value, data, settings) } .onCompletion { + // Clear view on completion val result = glance.compose(context, DpSize.Unspecified) { } emitter.updateView(result.remoteViews) } .collect { streamData -> - Log.d(KarooWinddirExtension.TAG, "Updating view") - val windSpeedText = if(streamData.settings.showWindspeedOverlay) "${streamData.data.current.windSpeed.roundToInt()}" else null + Log.d(KarooWinddirExtension.TAG, "Updating relative wind direction view") + val windSpeed = streamData.data.current.windSpeed + val windDirection = streamData.value + val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed + val windSpeedText = if(streamData.settings.showWindspeedOverlay) "${headwindSpeed.roundToInt()}" else null val result = glance.compose(context, DpSize.Unspecified) { - RelativeWindDirection(streamData.value.roundToInt(), config.textSize, windSpeedText) + RelativeWindDirection(windDirection.roundToInt(), config.textSize, windSpeedText) } emitter.updateView(result.remoteViews) diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeWindDirection.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeWindDirectionView.kt similarity index 100% rename from app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeWindDirection.kt rename to app/src/main/kotlin/de/timklge/karoowinddir/datatypes/RelativeWindDirectionView.kt diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/SurfacePressureDataType.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/SurfacePressureDataType.kt index 4f8a932..e8c3a11 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/SurfacePressureDataType.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/SurfacePressureDataType.kt @@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes import android.content.Context import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse -class SurfacePressureDataType(context: Context) : WeatherDataType(context, "surfacePressure"){ +class SurfacePressureDataType(context: Context) : BaseDataType(context, "surfacePressure"){ override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double { return data.current.surfacePressure } diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WeatherDataType.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WeatherDataType.kt index b5b8f81..d0c3247 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WeatherDataType.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WeatherDataType.kt @@ -2,38 +2,89 @@ package de.timklge.karoowinddir.datatypes import android.content.Context import android.util.Log +import androidx.compose.ui.unit.DpSize +import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi +import androidx.glance.appwidget.GlanceRemoteViews import de.timklge.karoowinddir.KarooWinddirExtension import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse +import de.timklge.karoowinddir.WeatherInterpretation +import de.timklge.karoowinddir.getHeadingFlow +import de.timklge.karoowinddir.screens.WinddirSettings import de.timklge.karoowinddir.streamCurrentWeatherData +import de.timklge.karoowinddir.streamSettings +import io.hammerhead.karooext.KarooSystemService import io.hammerhead.karooext.extension.DataTypeImpl import io.hammerhead.karooext.internal.Emitter +import io.hammerhead.karooext.internal.ViewEmitter import io.hammerhead.karooext.models.DataPoint import io.hammerhead.karooext.models.DataType import io.hammerhead.karooext.models.StreamState +import io.hammerhead.karooext.models.UpdateGraphicConfig +import io.hammerhead.karooext.models.ViewConfig import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.launch +import kotlin.math.roundToInt -abstract class WeatherDataType( - private val applicationContext: Context, - dataTypeId: String -) : DataTypeImpl("karoo-winddir", dataTypeId) { - abstract fun getValue(data: OpenMeteoCurrentWeatherResponse): Double +@OptIn(ExperimentalGlanceRemoteViewsApi::class) +class WeatherDataType( + private val karooSystem: KarooSystemService, + private val applicationContext: Context +) : DataTypeImpl("karoo-winddir", "weather") { + private val glance = GlanceRemoteViews() + // FIXME: Remove. Currently, the data field will permanently show "no sensor" if no data stream is provided override fun startStream(emitter: Emitter) { - Log.d(KarooWinddirExtension.TAG, "start $dataTypeId stream") val job = CoroutineScope(Dispatchers.IO).launch { val currentWeatherData = applicationContext.streamCurrentWeatherData() - currentWeatherData.collect { data -> - val value = getValue(data) - Log.d(KarooWinddirExtension.TAG, "$dataTypeId: $value") - emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to value)))) - } + currentWeatherData + .collect { data -> + Log.d(KarooWinddirExtension.TAG, "Wind code: ${data.current.weatherCode}") + emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to data.current.weatherCode.toDouble())))) + } } emitter.setCancellable { - Log.d(KarooWinddirExtension.TAG, "stop $dataTypeId stream") job.cancel() } } -} + + override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) { + Log.d(KarooWinddirExtension.TAG, "Starting weather view with $emitter") + val configJob = CoroutineScope(Dispatchers.IO).launch { + emitter.onNext(UpdateGraphicConfig(showHeader = false)) + awaitCancellation() + } + + data class StreamData(val data: OpenMeteoCurrentWeatherResponse, val settings: WinddirSettings) + + val viewJob = CoroutineScope(Dispatchers.IO).launch { + context.streamCurrentWeatherData() + .combine(context.streamSettings()) { data, settings -> StreamData(data, settings) } + .onCompletion { + // Clear view on completion + val result = glance.compose(context, DpSize.Unspecified) { } + emitter.updateView(result.remoteViews) + } + .collect { (data, settings) -> + Log.d(KarooWinddirExtension.TAG, "Updating weather view") + val interpretation = WeatherInterpretation.fromWeatherCode(data.current.weatherCode) + + val result = glance.compose(context, DpSize.Unspecified) { + Weather(interpretation, data.current.windDirection.roundToInt(), data.current.windSpeed.roundToInt(), data.current.windGusts.roundToInt()) + } + + emitter.updateView(result.remoteViews) + } + } + emitter.setCancellable { + Log.d(KarooWinddirExtension.TAG, "Stopping winddir view with $emitter") + configJob.cancel() + viewJob.cancel() + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WeatherView.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WeatherView.kt new file mode 100644 index 0000000..fc94026 --- /dev/null +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WeatherView.kt @@ -0,0 +1,75 @@ +package de.timklge.karoowinddir.datatypes + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType +import androidx.compose.ui.unit.dp +import androidx.glance.ColorFilter +import androidx.glance.GlanceModifier +import androidx.glance.Image +import androidx.glance.ImageProvider +import androidx.glance.color.ColorProvider +import androidx.glance.layout.Alignment +import androidx.glance.layout.Column +import androidx.glance.layout.ContentScale +import androidx.glance.layout.Row +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.preview.ExperimentalGlancePreviewApi +import androidx.glance.preview.Preview +import androidx.glance.text.FontFamily +import androidx.glance.text.Text +import androidx.glance.text.TextStyle +import de.timklge.karoowinddir.R +import de.timklge.karoowinddir.WeatherInterpretation +import de.timklge.karoowinddir.screens.WinddirSettings + +fun getWeatherIcon(interpretation: WeatherInterpretation): Int { + return when (interpretation){ + WeatherInterpretation.CLEAR -> R.drawable.bx_clear + WeatherInterpretation.CLOUDY -> R.drawable.bx_cloud + WeatherInterpretation.RAINY -> R.drawable.bx_cloud_rain + WeatherInterpretation.SNOWY -> R.drawable.bx_cloud_snow + WeatherInterpretation.DRIZZLE -> R.drawable.bx_cloud_drizzle + WeatherInterpretation.THUNDERSTORM -> R.drawable.bx_cloud_lightning + WeatherInterpretation.UNKNOWN -> R.drawable.question_mark_regular_240 + } +} + +@OptIn(ExperimentalGlancePreviewApi::class) +@Preview(widthDp = 200, heightDp = 150) +@Composable +fun Weather(current: WeatherInterpretation, windBearing: Int, windSpeed: Int, windGusts: Int) { + Column(modifier = GlanceModifier.fillMaxSize(), horizontalAlignment = Alignment.End) { + Row(modifier = GlanceModifier.defaultWeight(), horizontalAlignment = Alignment.End) { + val imageW = 70 + val imageH = (imageW * (280.0 / 400)).toInt() + Image( + modifier = GlanceModifier.height(imageH.dp).width(imageW.dp), + provider = ImageProvider(getWeatherIcon(current)), + contentDescription = "Current weather information", + contentScale = ContentScale.FillBounds, + colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White)) + ) + } + + Row(horizontalAlignment = Alignment.CenterHorizontally, verticalAlignment = Alignment.CenterVertically) { + Image( + modifier = GlanceModifier.height(20.dp).width(12.dp), + provider = ImageProvider(getArrowResourceByBearing(windBearing)), + contentDescription = "Current wind direction", + contentScale = ContentScale.Fit, + colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White)) + ) + + Text( + text = "$windSpeed,$windGusts", + style = TextStyle(color = ColorProvider(Color.Black, Color.White), fontFamily = FontFamily.Monospace, fontSize = TextUnit(18f, TextUnitType.Sp)) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindDirectionDataType.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindDirectionDataType.kt index 2807712..cbe125e 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindDirectionDataType.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindDirectionDataType.kt @@ -3,22 +3,17 @@ package de.timklge.karoowinddir.datatypes import android.content.Context import android.util.Log import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.style.TextAlign 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.glance.GlanceModifier import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi import androidx.glance.appwidget.GlanceRemoteViews -import androidx.glance.background 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.FontFamily -import androidx.glance.text.FontStyle import androidx.glance.text.Text import androidx.glance.text.TextStyle import de.timklge.karoowinddir.KarooWinddirExtension @@ -36,7 +31,7 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.launch import kotlin.math.roundToInt -class WindDirectionDataType(val karooSystem: KarooSystemService, context: Context) : WeatherDataType(context, "windDirection"){ +class WindDirectionDataType(val karooSystem: KarooSystemService, context: Context) : BaseDataType(context, "windDirection"){ @OptIn(ExperimentalGlanceRemoteViewsApi::class) private val glance = GlanceRemoteViews() diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindGustsDataType.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindGustsDataType.kt index f42b60e..c61cf39 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindGustsDataType.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindGustsDataType.kt @@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes import android.content.Context import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse -class WindGustsDataType(context: Context) : WeatherDataType(context, "windGusts"){ +class WindGustsDataType(context: Context) : BaseDataType(context, "windGusts"){ override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double { return data.current.windGusts } diff --git a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindSpeedDataType.kt b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindSpeedDataType.kt index c100807..ce6f4c9 100644 --- a/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindSpeedDataType.kt +++ b/app/src/main/kotlin/de/timklge/karoowinddir/datatypes/WindSpeedDataType.kt @@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes import android.content.Context import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse -class WindSpeedDataType(context: Context) : WeatherDataType(context, "windSpeed"){ +class WindSpeedDataType(context: Context) : BaseDataType(context, "windSpeed"){ override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double { return data.current.windSpeed } diff --git a/app/src/main/res/drawable/bx_clear.png b/app/src/main/res/drawable/bx_clear.png new file mode 100644 index 0000000..b678517 Binary files /dev/null and b/app/src/main/res/drawable/bx_clear.png differ diff --git a/app/src/main/res/drawable/bx_cloud.png b/app/src/main/res/drawable/bx_cloud.png new file mode 100644 index 0000000..570dac0 Binary files /dev/null and b/app/src/main/res/drawable/bx_cloud.png differ diff --git a/app/src/main/res/drawable/bx_cloud_drizzle.png b/app/src/main/res/drawable/bx_cloud_drizzle.png new file mode 100644 index 0000000..85473c0 Binary files /dev/null and b/app/src/main/res/drawable/bx_cloud_drizzle.png differ diff --git a/app/src/main/res/drawable/bx_cloud_light_rain.png b/app/src/main/res/drawable/bx_cloud_light_rain.png new file mode 100644 index 0000000..6f267fe Binary files /dev/null and b/app/src/main/res/drawable/bx_cloud_light_rain.png differ diff --git a/app/src/main/res/drawable/bx_cloud_lightning.png b/app/src/main/res/drawable/bx_cloud_lightning.png new file mode 100644 index 0000000..c3aed00 Binary files /dev/null and b/app/src/main/res/drawable/bx_cloud_lightning.png differ diff --git a/app/src/main/res/drawable/bx_cloud_rain.png b/app/src/main/res/drawable/bx_cloud_rain.png new file mode 100644 index 0000000..37741b2 Binary files /dev/null and b/app/src/main/res/drawable/bx_cloud_rain.png differ diff --git a/app/src/main/res/drawable/bx_cloud_snow.png b/app/src/main/res/drawable/bx_cloud_snow.png new file mode 100644 index 0000000..947dab5 Binary files /dev/null and b/app/src/main/res/drawable/bx_cloud_snow.png differ diff --git a/app/src/main/res/drawable/question_mark_regular_240.png b/app/src/main/res/drawable/question_mark_regular_240.png new file mode 100644 index 0000000..91f9a34 Binary files /dev/null and b/app/src/main/res/drawable/question_mark_regular_240.png differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7ba91f0..78f8cb7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - Winddir + WindDir winddir Wind direction Current wind direction relative to the riding direction @@ -17,4 +17,6 @@ Current precipitation (rainfall / snowfall) Surface pressure Atmospheric pressure at surface in configured unit + Weather + Current weather conditions \ No newline at end of file diff --git a/app/src/main/res/xml/extension_info.xml b/app/src/main/res/xml/extension_info.xml index a587ff5..98dfb02 100644 --- a/app/src/main/res/xml/extension_info.xml +++ b/app/src/main/res/xml/extension_info.xml @@ -11,6 +11,13 @@ icon="@drawable/ic_launcher" typeId="winddir" /> + +