fix #18: Add option for headwind widget to use absolute wind direction

This commit is contained in:
Tim Kluge 2024-12-20 17:23:20 +01:00
parent 72daf42938
commit b7000428ae
8 changed files with 40 additions and 18 deletions

View File

@ -33,7 +33,7 @@ If you are using a Karoo 2, you can use manual sideloading:
After installing this app on your Karoo and opening it once from the main menu, you can add the following new data fields to your data pages: After installing this app on your Karoo and opening it once from the main menu, you can add the following new data fields to your data pages:
- Headwind (graphical, 1x1 field): Shows the headwind direction and speed as a circle with a triangular direction indicator. The speed is shown at the center in your set unit of measurement (default is kilometers per hour if you have set up metric units in your Karoo, otherwise miles per hour). Both direction and speed are relative to the current riding direction by default, i. e., riding directly into a wind of 20 km/h will show a headwind speed of 20 km/h, while riding in the same direction will show -20 km/h. You can change this behavior in the app settings to show the absolute wind speed instead. - Headwind (graphical, 1x1 field): Shows the headwind direction and speed as a circle with a triangular direction indicator. The speed is shown at the center in your set unit of measurement (default is kilometers per hour if you have set up metric units in your Karoo, otherwise miles per hour). Both direction and speed are relative to the current riding direction by default, i. e., riding directly into a wind of 20 km/h will show a headwind speed of 20 km/h, while riding in the same direction will show -20 km/h. You can change this behavior in the app settings to show the absolute wind direction and speed instead.
- Weather forecast (graphical, 2x1 field): Shows three columns indicating the current weather conditions (sunny, cloudy, ...), wind direction, precipitation and temperature forecasted for the next three hours. Tap on this widget to cycle through the 12 hour forecast. - Weather forecast (graphical, 2x1 field): Shows three columns indicating the current weather conditions (sunny, cloudy, ...), wind direction, precipitation and temperature forecasted for the next three hours. Tap on this widget to cycle through the 12 hour forecast.
- Additionally, data fields that only show the current data value for headwind speed, humidity, cloud cover, absolute wind speed, absolute wind gust speed, absolute wind direction, rainfall and surface pressure can be added if desired. - Additionally, data fields that only show the current data value for headwind speed, humidity, cloud cover, absolute wind speed, absolute wind gust speed, absolute wind direction, rainfall and surface pressure can be added if desired.

View File

@ -15,8 +15,8 @@ android {
applicationId = "de.timklge.karooheadwind" applicationId = "de.timklge.karooheadwind"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 5 versionCode = 6
versionName = "1.1.1" versionName = "1.1.2"
} }
signingConfigs { signingConfigs {

View File

@ -3,9 +3,9 @@
"packageName": "de.timklge.karooheadwind", "packageName": "de.timklge.karooheadwind",
"iconUrl": "https://github.com/timklge/karoo-headwind/releases/latest/download/karoo-headwind.png", "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", "latestApkUrl": "https://github.com/timklge/karoo-headwind/releases/latest/download/app-release.apk",
"latestVersion": "1.1.1", "latestVersion": "1.1.2",
"latestVersionCode": 5, "latestVersionCode": 6,
"developer": "timklge", "developer": "timklge",
"description": "Provides headwind direction, wind speed and other weather data fields", "description": "Provides headwind direction, wind speed and other weather data fields",
"releaseNotes": "Add hourly forecast and temperature datafields" "releaseNotes": "Add hourly forecast and temperature datafields. Add setting to use absolute wind direction on headwind datafield."
} }

View File

@ -35,7 +35,7 @@ import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.1") { class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.2") {
companion object { companion object {
const val TAG = "karoo-headwind" const val TAG = "karoo-headwind"
} }

View File

@ -9,6 +9,7 @@ import androidx.glance.appwidget.GlanceRemoteViews
import de.timklge.karooheadwind.KarooHeadwindExtension import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.getRelativeHeadingFlow import de.timklge.karooheadwind.getRelativeHeadingFlow
import de.timklge.karooheadwind.screens.HeadwindSettings import de.timklge.karooheadwind.screens.HeadwindSettings
import de.timklge.karooheadwind.screens.WindDirectionIndicatorSetting
import de.timklge.karooheadwind.screens.WindDirectionIndicatorTextSetting import de.timklge.karooheadwind.screens.WindDirectionIndicatorTextSetting
import de.timklge.karooheadwind.streamCurrentWeatherData import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamDataFlow import de.timklge.karooheadwind.streamDataFlow
@ -54,7 +55,7 @@ class HeadwindDirectionDataType(
} }
} }
data class StreamData(val value: Double, val windSpeed: Double, val settings: HeadwindSettings) data class StreamData(val value: Double, val absoluteWindDirection: Double, val windSpeed: Double, val settings: HeadwindSettings)
private fun previewFlow(): Flow<StreamData> { private fun previewFlow(): Flow<StreamData> {
return flow { return flow {
@ -62,7 +63,7 @@ class HeadwindDirectionDataType(
val bearing = (0..360).random().toDouble() val bearing = (0..360).random().toDouble()
val windSpeed = (-20..20).random() val windSpeed = (-20..20).random()
emit(StreamData(bearing, windSpeed.toDouble(), HeadwindSettings())) emit(StreamData(bearing, bearing, windSpeed.toDouble(), HeadwindSettings()))
delay(2_000) delay(2_000)
} }
} }
@ -89,7 +90,7 @@ class HeadwindDirectionDataType(
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue } .mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
.combine(context.streamCurrentWeatherData()) { value, data -> value to data } .combine(context.streamCurrentWeatherData()) { value, data -> value to data }
.combine(context.streamSettings(karooSystem)) { (value, data), settings -> .combine(context.streamSettings(karooSystem)) { (value, data), settings ->
StreamData(value, data.current.windSpeed, settings) StreamData(value, data.current.windDirection, data.current.windSpeed, settings)
} }
} }
@ -102,7 +103,10 @@ class HeadwindDirectionDataType(
.collect { streamData -> .collect { streamData ->
Log.d(KarooHeadwindExtension.TAG, "Updating headwind direction view") Log.d(KarooHeadwindExtension.TAG, "Updating headwind direction view")
val windSpeed = streamData.windSpeed val windSpeed = streamData.windSpeed
val windDirection = streamData.value val windDirection = when (streamData.settings.windDirectionIndicatorSetting){
WindDirectionIndicatorSetting.HEADWIND_DIRECTION -> streamData.value
WindDirectionIndicatorSetting.WIND_DIRECTION -> streamData.absoluteWindDirection + 180
}
val text = when (streamData.settings.windDirectionIndicatorTextSetting) { val text = when (streamData.settings.windDirectionIndicatorTextSetting) {
WindDirectionIndicatorTextSetting.HEADWIND_SPEED -> { WindDirectionIndicatorTextSetting.HEADWIND_SPEED -> {
val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed

View File

@ -66,6 +66,11 @@ enum class WindDirectionIndicatorTextSetting(val id: String, val label: String){
NONE("none", "None") NONE("none", "None")
} }
enum class WindDirectionIndicatorSetting(val id: String, val label: String){
HEADWIND_DIRECTION("headwind-direction", "Headwind"),
WIND_DIRECTION("wind-direction", "Absolute wind direction"),
}
enum class TemperatureUnit(val id: String, val label: String, val unitDisplay: String){ enum class TemperatureUnit(val id: String, val label: String, val unitDisplay: String){
CELSIUS("celsius", "Celsius (°C)", "°C"), CELSIUS("celsius", "Celsius (°C)", "°C"),
FAHRENHEIT("fahrenheit", "Fahrenheit (°F)", "°F") FAHRENHEIT("fahrenheit", "Fahrenheit (°F)", "°F")
@ -82,6 +87,7 @@ data class HeadwindSettings(
val windUnit: WindUnit = WindUnit.KILOMETERS_PER_HOUR, val windUnit: WindUnit = WindUnit.KILOMETERS_PER_HOUR,
val welcomeDialogAccepted: Boolean = false, val welcomeDialogAccepted: Boolean = false,
val windDirectionIndicatorTextSetting: WindDirectionIndicatorTextSetting = WindDirectionIndicatorTextSetting.HEADWIND_SPEED, val windDirectionIndicatorTextSetting: WindDirectionIndicatorTextSetting = WindDirectionIndicatorTextSetting.HEADWIND_SPEED,
val windDirectionIndicatorSetting: WindDirectionIndicatorSetting = WindDirectionIndicatorSetting.HEADWIND_DIRECTION,
val roundLocationTo: RoundLocationSetting = RoundLocationSetting.KM_2 val roundLocationTo: RoundLocationSetting = RoundLocationSetting.KM_2
){ ){
companion object { companion object {
@ -120,6 +126,7 @@ fun MainScreen() {
var selectedWindUnit by remember { mutableStateOf(WindUnit.KILOMETERS_PER_HOUR) } var selectedWindUnit by remember { mutableStateOf(WindUnit.KILOMETERS_PER_HOUR) }
var welcomeDialogVisible by remember { mutableStateOf(false) } var welcomeDialogVisible by remember { mutableStateOf(false) }
var selectedWindDirectionIndicatorTextSetting by remember { mutableStateOf(WindDirectionIndicatorTextSetting.HEADWIND_SPEED) } var selectedWindDirectionIndicatorTextSetting by remember { mutableStateOf(WindDirectionIndicatorTextSetting.HEADWIND_SPEED) }
var selectedWindDirectionIndicatorSetting by remember { mutableStateOf(WindDirectionIndicatorSetting.HEADWIND_DIRECTION) }
var selectedRoundLocationSetting by remember { mutableStateOf(RoundLocationSetting.KM_2) } var selectedRoundLocationSetting by remember { mutableStateOf(RoundLocationSetting.KM_2) }
val stats by ctx.streamStats().collectAsState(HeadwindStats()) val stats by ctx.streamStats().collectAsState(HeadwindStats())
@ -132,6 +139,7 @@ fun MainScreen() {
selectedWindUnit = settings.windUnit selectedWindUnit = settings.windUnit
welcomeDialogVisible = !settings.welcomeDialogAccepted welcomeDialogVisible = !settings.welcomeDialogAccepted
selectedWindDirectionIndicatorTextSetting = settings.windDirectionIndicatorTextSetting selectedWindDirectionIndicatorTextSetting = settings.windDirectionIndicatorTextSetting
selectedWindDirectionIndicatorSetting = settings.windDirectionIndicatorSetting
selectedRoundLocationSetting = settings.roundLocationTo selectedRoundLocationSetting = settings.roundLocationTo
} }
} }
@ -151,6 +159,14 @@ fun MainScreen() {
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) { .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) {
val windDirectionIndicatorSettingDropdownOptions = WindDirectionIndicatorSetting.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
val windDirectionIndicatorSettingSelection by remember(selectedWindDirectionIndicatorSetting) {
mutableStateOf(windDirectionIndicatorSettingDropdownOptions.find { option -> option.id == selectedWindDirectionIndicatorSetting.id }!!)
}
Dropdown(label = "Wind direction indicator", options = windDirectionIndicatorSettingDropdownOptions, selected = windDirectionIndicatorSettingSelection) { selectedOption ->
selectedWindDirectionIndicatorSetting = WindDirectionIndicatorSetting.entries.find { unit -> unit.id == selectedOption.id }!!
}
val windDirectionIndicatorTextSettingDropdownOptions = WindDirectionIndicatorTextSetting.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) } val windDirectionIndicatorTextSettingDropdownOptions = WindDirectionIndicatorTextSetting.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
val windDirectionIndicatorTextSettingSelection by remember(selectedWindDirectionIndicatorTextSetting) { val windDirectionIndicatorTextSettingSelection by remember(selectedWindDirectionIndicatorTextSetting) {
mutableStateOf(windDirectionIndicatorTextSettingDropdownOptions.find { option -> option.id == selectedWindDirectionIndicatorTextSetting.id }!!) mutableStateOf(windDirectionIndicatorTextSettingDropdownOptions.find { option -> option.id == selectedWindDirectionIndicatorTextSetting.id }!!)
@ -180,7 +196,9 @@ fun MainScreen() {
.fillMaxWidth() .fillMaxWidth()
.height(50.dp), onClick = { .height(50.dp), onClick = {
val newSettings = HeadwindSettings(windUnit = selectedWindUnit, val newSettings = HeadwindSettings(windUnit = selectedWindUnit,
welcomeDialogAccepted = true, windDirectionIndicatorTextSetting = selectedWindDirectionIndicatorTextSetting, welcomeDialogAccepted = true,
windDirectionIndicatorSetting = selectedWindDirectionIndicatorSetting,
windDirectionIndicatorTextSetting = selectedWindDirectionIndicatorTextSetting,
roundLocationTo = selectedRoundLocationSetting) roundLocationTo = selectedRoundLocationSetting)
coroutineScope.launch { coroutineScope.launch {

View File

@ -2,7 +2,7 @@
<string name="app_name">Headwind</string> <string name="app_name">Headwind</string>
<string name="extension_name">headwind</string> <string name="extension_name">headwind</string>
<string name="headwind">Headwind</string> <string name="headwind">Headwind</string>
<string name="headwind_description">Current headwind direction</string> <string name="headwind_description">Current headwind direction and speed</string>
<string name="relativeHumidity">Humidity</string> <string name="relativeHumidity">Humidity</string>
<string name="relativeHumidity_description">Relative humidity in percent</string> <string name="relativeHumidity_description">Relative humidity in percent</string>
<string name="cloudCover">Cloud cover</string> <string name="cloudCover">Cloud cover</string>