Add weather data field (shows icon indicating weather conditions)
@ -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
|
||||
|
||||
@ -80,7 +80,7 @@ fun Context.streamCurrentWeatherData(): Flow<OpenMeteoCurrentWeatherResponse> {
|
||||
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<WinddirSettings> {
|
||||
@ -112,8 +112,8 @@ fun Context.streamStats(): Flow<WinddirStats> {
|
||||
@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<Int> {
|
||||
// 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<Int> {
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
fun KarooSystemService.getGpsCoordinateFlow(): Flow<GpsCoordinates> {
|
||||
// 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 }
|
||||
|
||||
@ -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),
|
||||
@ -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,
|
||||
|
||||
@ -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<StreamState>) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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<StreamState>) {
|
||||
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)
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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<StreamState>) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
BIN
app/src/main/res/drawable/bx_clear.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable/bx_cloud.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable/bx_cloud_drizzle.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable/bx_cloud_light_rain.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable/bx_cloud_lightning.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/drawable/bx_cloud_rain.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable/bx_cloud_snow.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/drawable/question_mark_regular_240.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
@ -1,5 +1,5 @@
|
||||
<resources>
|
||||
<string name="app_name">Winddir</string>
|
||||
<string name="app_name">WindDir</string>
|
||||
<string name="extension_name">winddir</string>
|
||||
<string name="winddir">Wind direction</string>
|
||||
<string name="winddir_description">Current wind direction relative to the riding direction</string>
|
||||
@ -17,4 +17,6 @@
|
||||
<string name="precipitation_description">Current precipitation (rainfall / snowfall)</string>
|
||||
<string name="surfacePressure">Surface pressure</string>
|
||||
<string name="surfacePressure_description">Atmospheric pressure at surface in configured unit</string>
|
||||
<string name="weather">Weather</string>
|
||||
<string name="weather_description">Current weather conditions</string>
|
||||
</resources>
|
||||
@ -11,6 +11,13 @@
|
||||
icon="@drawable/ic_launcher"
|
||||
typeId="winddir" />
|
||||
|
||||
<DataType
|
||||
description="@string/weather_description"
|
||||
displayName="@string/weather"
|
||||
graphical="true"
|
||||
icon="@drawable/ic_launcher"
|
||||
typeId="weather" />
|
||||
|
||||
<DataType
|
||||
description="@string/relativeHumidity_description"
|
||||
displayName="@string/relativeHumidity"
|
||||
|
||||