Rename project

This commit is contained in:
Tim Kluge 2024-12-08 11:54:18 +01:00
parent 9d57bfde6f
commit 0830674d62
27 changed files with 148 additions and 150 deletions

View File

@ -1,9 +1,10 @@
# Karoo Winddir Extension
# Karoo Headwind Extension
> [!WARNING]
> This app is currently in prototype stage and its main features might not work at all. If you want to test it anyway and encounter issues, please report them in the [issue tracker](https://github.com/timklge/karoo-winddir/issues), ideally with adb logs attached.
> This app is currently in prototype stage and its main features might not work at all. If you want to test it anyway and encounter issues, please report them in the [issue tracker](https://github.com/timklge/karoo-headwind/issues), ideally with adb logs attached.
This extension for Karoo devices adds a graphical data field that shows the current wind direction relative to the rider as an arrow. It also provides data fields for relative humidity, cloud coverage, wind speed, wind gust speed, surface pressure, and rainfall at the current location.
This extension for Karoo devices adds a graphical data field that shows the current headwind direction as an arrow.
It also provides data fields for relative humidity, cloud coverage, wind speed, wind gust speed, surface pressure, and rainfall at the current location.
Compatible with Karoo 2 and Karoo 3 devices running Karoo OS version 1.524.2003 and later.
@ -16,13 +17,13 @@ Compatible with Karoo 2 and Karoo 3 devices running Karoo OS version 1.524.2003
Currently, Hammerhead has not yet released an on-device app store for easy installation of extensions like this. Until then, you can sideload the app.
1. Download the APK from the [releases page](https://github.com/timklge/karoo-winddir/releases) (or build it from source).
1. Download the APK from the [releases page](https://github.com/timklge/karoo-headwind/releases) (or build it from source).
2. Prepare your Karoo for sideloading by following the [step-by-step guide](https://www.dcrainmaker.com/2021/02/how-to-sideload-android-apps-on-your-hammerhead-karoo-1-karoo-2.html) by DC Rainmaker.
3. Install the app using the command `adb install app-release.apk`.
## Usage
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).
After installing this app on your Karoo, you can add a data field showing the headwind direction or one of the auxiliary fields to your data pages. The headwind 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 your phone's internet connection if you have the Karoo companion app installed.

View File

@ -6,15 +6,15 @@ plugins {
}
android {
namespace = "de.timklge.karoowinddir"
namespace = "de.timklge.karooheadwind"
compileSdk = 34
defaultConfig {
applicationId = "de.timklge.karoowinddir"
applicationId = "de.timklge.karooheadwind"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0.0-beta1"
versionCode = 2
versionName = "1.0.0-beta2"
}
buildTypes {

View File

@ -1,11 +1,11 @@
{
"label": "karoo-winddir",
"packageName": "de.timklge.karoowinddir",
"iconUrl": "https://github.com/timklge/karoo-winddir/releases/latest/download/karoo-winddir.png",
"latestApkUrl": "https://github.com/timklge/karoo-winddir/releases/latest/download/app-release.apk",
"latestVersion": "1.0.0-beta1",
"latestVersionCode": 1,
"label": "karoo-headwind",
"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.0.0-beta2",
"latestVersionCode": 2,
"developer": "timklge",
"description": "Provides relative wind direction, wind speed and other weather data fields",
"description": "Provides headwind direction, wind speed and other weather data fields",
"releaseNotes": "Initial release"
}

View File

@ -10,7 +10,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat">
<activity
android:name="de.timklge.karoowinddir.MainActivity"
android:name="de.timklge.karooheadwind.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -19,7 +19,7 @@
</activity>
<service
android:name="de.timklge.karoowinddir.KarooWinddirExtension"
android:name="de.timklge.karooheadwind.KarooHeadwindExtension"
android:exported="true"
tools:ignore="ExportedService">
<intent-filter>
@ -32,6 +32,6 @@
<meta-data
android:name="io.hammerhead.karooext.MANIFEST_URL"
android:value="https://github.com/timklge/karoo-winddir/releases/latest/download/manifest.json" />
android:value="https://github.com/timklge/karoo-headwind/releases/latest/download/manifest.json" />
</application>
</manifest>

View File

@ -1,12 +1,12 @@
package de.timklge.karoowinddir
package de.timklge.karooheadwind
import android.content.Context
import android.util.Log
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import de.timklge.karoowinddir.datatypes.GpsCoordinates
import de.timklge.karoowinddir.screens.WinddirSettings
import de.timklge.karoowinddir.screens.WinddirStats
import de.timklge.karooheadwind.datatypes.GpsCoordinates
import de.timklge.karooheadwind.screens.HeadwindSettings
import de.timklge.karooheadwind.screens.HeadwindStats
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.DataType
import io.hammerhead.karooext.models.HttpResponseState
@ -42,13 +42,13 @@ val settingsKey = stringPreferencesKey("settings")
val currentDataKey = stringPreferencesKey("current")
val statsKey = stringPreferencesKey("stats")
suspend fun saveSettings(context: Context, settings: WinddirSettings) {
suspend fun saveSettings(context: Context, settings: HeadwindSettings) {
context.dataStore.edit { t ->
t[settingsKey] = Json.encodeToString(settings)
}
}
suspend fun saveStats(context: Context, stats: WinddirStats) {
suspend fun saveStats(context: Context, stats: HeadwindStats) {
context.dataStore.edit { t ->
t[statsKey] = Json.encodeToString(stats)
}
@ -77,45 +77,45 @@ fun Context.streamCurrentWeatherData(): Flow<OpenMeteoCurrentWeatherResponse> {
val data = settingsJson[currentDataKey]
data?.let { d -> jsonWithUnknownKeys.decodeFromString<OpenMeteoCurrentWeatherResponse>(d) }
} catch (e: Throwable) {
Log.e(KarooWinddirExtension.TAG, "Failed to read preferences", e)
Log.e(KarooHeadwindExtension.TAG, "Failed to read preferences", e)
null
}
}.filterNotNull().distinctUntilChanged().filter { it.current.time * 1000 >= System.currentTimeMillis() - (1000 * 60 * 60 * 12) }
}
fun Context.streamSettings(): Flow<WinddirSettings> {
fun Context.streamSettings(): Flow<HeadwindSettings> {
return dataStore.data.map { settingsJson ->
try {
jsonWithUnknownKeys.decodeFromString<WinddirSettings>(
settingsJson[settingsKey] ?: WinddirSettings.defaultSettings
jsonWithUnknownKeys.decodeFromString<HeadwindSettings>(
settingsJson[settingsKey] ?: HeadwindSettings.defaultSettings
)
} catch(e: Throwable){
Log.e(KarooWinddirExtension.TAG, "Failed to read preferences", e)
jsonWithUnknownKeys.decodeFromString<WinddirSettings>(WinddirSettings.defaultSettings)
Log.e(KarooHeadwindExtension.TAG, "Failed to read preferences", e)
jsonWithUnknownKeys.decodeFromString<HeadwindSettings>(HeadwindSettings.defaultSettings)
}
}.distinctUntilChanged()
}
fun Context.streamStats(): Flow<WinddirStats> {
fun Context.streamStats(): Flow<HeadwindStats> {
return dataStore.data.map { statsJson ->
try {
jsonWithUnknownKeys.decodeFromString<WinddirStats>(
statsJson[statsKey] ?: WinddirStats.defaultStats
jsonWithUnknownKeys.decodeFromString<HeadwindStats>(
statsJson[statsKey] ?: HeadwindStats.defaultStats
)
} catch(e: Throwable){
Log.e(KarooWinddirExtension.TAG, "Failed to read stats", e)
jsonWithUnknownKeys.decodeFromString<WinddirStats>(WinddirStats.defaultStats)
Log.e(KarooHeadwindExtension.TAG, "Failed to read stats", e)
jsonWithUnknownKeys.decodeFromString<HeadwindStats>(HeadwindStats.defaultStats)
}
}.distinctUntilChanged()
}
@OptIn(FlowPreview::class)
suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoordinates, settings: WinddirSettings): HttpResponseState.Complete {
suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoordinates, settings: HeadwindSettings): HttpResponseState.Complete {
return callbackFlow {
// 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}&current=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}...")
Log.d(KarooHeadwindExtension.TAG, "Http request to ${url}...")
val listenerId = addConsumer(
OnHttpResponse.MakeHttpRequest(
@ -124,7 +124,7 @@ suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoord
waitForConnection = false,
),
) { event: OnHttpResponse ->
Log.d(KarooWinddirExtension.TAG, "Http response event $event")
Log.d(KarooHeadwindExtension.TAG, "Http response event $event")
if (event.state is HttpResponseState.Complete){
trySend(event.state as HttpResponseState.Complete)
close()
@ -162,10 +162,10 @@ fun KarooSystemService.getGpsCoordinateFlow(): Flow<GpsCoordinates> {
val lon = values[DataType.Field.LOC_LONGITUDE]
if (lat != null && lon != null){
Log.d(KarooWinddirExtension.TAG, "Updated gps coords: $lat $lon")
Log.d(KarooHeadwindExtension.TAG, "Updated gps coords: $lat $lon")
GpsCoordinates(lat, lon)
} else {
Log.e(KarooWinddirExtension.TAG, "Missing gps values: $values")
Log.e(KarooHeadwindExtension.TAG, "Missing gps values: $values")
null
}
}

View File

@ -1,17 +1,17 @@
package de.timklge.karoowinddir
package de.timklge.karooheadwind
import android.util.Log
import de.timklge.karoowinddir.datatypes.CloudCoverDataType
import de.timklge.karoowinddir.datatypes.GpsCoordinates
import de.timklge.karoowinddir.datatypes.PrecipitationDataType
import de.timklge.karoowinddir.datatypes.RelativeHumidityDataType
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.RelativeWindDirectionDataType
import de.timklge.karoowinddir.datatypes.WeatherDataType
import de.timklge.karoowinddir.screens.WinddirStats
import de.timklge.karooheadwind.datatypes.CloudCoverDataType
import de.timklge.karooheadwind.datatypes.GpsCoordinates
import de.timklge.karooheadwind.datatypes.PrecipitationDataType
import de.timklge.karooheadwind.datatypes.RelativeHumidityDataType
import de.timklge.karooheadwind.datatypes.SurfacePressureDataType
import de.timklge.karooheadwind.datatypes.WindDirectionDataType
import de.timklge.karooheadwind.datatypes.WindGustsDataType
import de.timklge.karooheadwind.datatypes.WindSpeedDataType
import de.timklge.karooheadwind.datatypes.RelativeWindDirectionDataType
import de.timklge.karooheadwind.datatypes.WeatherDataType
import de.timklge.karooheadwind.screens.HeadwindStats
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.KarooExtension
import kotlinx.coroutines.CoroutineScope
@ -29,9 +29,9 @@ import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
class KarooWinddirExtension : KarooExtension("karoo-winddir", "1.0.0-beta1") {
class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.0.0-beta2") {
companion object {
const val TAG = "karoo-winddir"
const val TAG = "karoo-headwind"
}
lateinit var karooSystem: KarooSystemService
@ -84,14 +84,14 @@ class KarooWinddirExtension : KarooExtension("karoo-winddir", "1.0.0-beta1") {
streamStats().first()
} catch(e: Exception){
Log.e(TAG, "Failed to read stats", e)
WinddirStats()
HeadwindStats()
}
val response = karooSystem.makeOpenMeteoHttpRequest(gps, settings)
if (response.error != null){
try {
val stats = lastKnownStats.copy(failedWeatherRequest = System.currentTimeMillis())
launch { saveStats(this@KarooWinddirExtension, stats) }
launch { saveStats(this@KarooHeadwindExtension, stats) }
} catch(e: Exception){
Log.e(TAG, "Failed to write stats", e)
}
@ -102,7 +102,7 @@ class KarooWinddirExtension : KarooExtension("karoo-winddir", "1.0.0-beta1") {
lastSuccessfulWeatherRequest = System.currentTimeMillis(),
lastSuccessfulWeatherPosition = gps
)
launch { saveStats(this@KarooWinddirExtension, stats) }
launch { saveStats(this@KarooHeadwindExtension, stats) }
} catch(e: Exception){
Log.e(TAG, "Failed to write stats", e)
}

View File

@ -1,4 +1,4 @@
package de.timklge.karoowinddir
package de.timklge.karooheadwind
import android.content.Context
import android.os.Bundle
@ -8,8 +8,8 @@ import androidx.compose.material3.Text
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import de.timklge.karoowinddir.screens.MainScreen
import de.timklge.karoowinddir.theme.AppTheme
import de.timklge.karooheadwind.screens.MainScreen
import de.timklge.karooheadwind.theme.AppTheme
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

View File

@ -1,4 +1,4 @@
package de.timklge.karoowinddir
package de.timklge.karooheadwind
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@ -1,10 +1,10 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.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 de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.streamCurrentWeatherData
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.Emitter
import io.hammerhead.karooext.models.DataPoint
@ -17,22 +17,22 @@ import kotlinx.coroutines.launch
abstract class BaseDataType(
private val applicationContext: Context,
dataTypeId: String
) : DataTypeImpl("karoo-winddir", dataTypeId) {
) : DataTypeImpl("karoo-headwind", dataTypeId) {
abstract fun getValue(data: OpenMeteoCurrentWeatherResponse): Double
override fun startStream(emitter: Emitter<StreamState>) {
Log.d(KarooWinddirExtension.TAG, "start $dataTypeId stream")
Log.d(KarooHeadwindExtension.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")
Log.d(KarooHeadwindExtension.TAG, "$dataTypeId: $value")
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to value))))
}
}
emitter.setCancellable {
Log.d(KarooWinddirExtension.TAG, "stop $dataTypeId stream")
Log.d(KarooHeadwindExtension.TAG, "stop $dataTypeId stream")
job.cancel()
}
}

View File

@ -1,7 +1,7 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
class CloudCoverDataType(context: Context) : BaseDataType(context, "cloudCover"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {

View File

@ -1,4 +1,4 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.datatypes
import kotlinx.serialization.Serializable
import kotlin.math.atan2

View File

@ -1,7 +1,7 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
class PrecipitationDataType(context: Context) : BaseDataType(context, "precipitation"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {

View File

@ -1,7 +1,7 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
class RelativeHumidityDataType(context: Context) : BaseDataType(context, "relativeHumidity"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {

View File

@ -1,17 +1,17 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.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.getHeadingFlow
import de.timklge.karoowinddir.screens.WinddirSettings
import de.timklge.karoowinddir.streamCurrentWeatherData
import de.timklge.karoowinddir.streamDataFlow
import de.timklge.karoowinddir.streamSettings
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.screens.HeadwindSettings
import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamDataFlow
import de.timklge.karooheadwind.streamSettings
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.Emitter
@ -37,7 +37,7 @@ import kotlin.math.roundToInt
class RelativeWindDirectionDataType(
private val karooSystem: KarooSystemService,
private val applicationContext: Context
) : DataTypeImpl("karoo-winddir", "winddir") {
) : DataTypeImpl("karoo-headwind", "headwind") {
private val glance = GlanceRemoteViews()
private fun signedAngleDifference(angle1: Double, angle2: Double): Double {
@ -71,7 +71,7 @@ class RelativeWindDirectionDataType(
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(KarooHeadwindExtension.TAG, "Wind bearing: $bearing vs $windBearing => $diff")
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to diff))))
}
}
@ -81,13 +81,13 @@ class RelativeWindDirectionDataType(
}
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
Log.d(KarooWinddirExtension.TAG, "Starting relative wind direction view with $emitter")
Log.d(KarooHeadwindExtension.TAG, "Starting headwind direction view with $emitter")
val configJob = CoroutineScope(Dispatchers.IO).launch {
emitter.onNext(UpdateGraphicConfig(showHeader = false))
awaitCancellation()
}
data class StreamData(val value: Double, val data: OpenMeteoCurrentWeatherResponse, val settings: WinddirSettings)
data class StreamData(val value: Double, val data: OpenMeteoCurrentWeatherResponse, val settings: HeadwindSettings)
val viewJob = CoroutineScope(Dispatchers.IO).launch {
karooSystem.streamDataFlow(dataTypeId)
@ -100,7 +100,7 @@ class RelativeWindDirectionDataType(
emitter.updateView(result.remoteViews)
}
.collect { streamData ->
Log.d(KarooWinddirExtension.TAG, "Updating relative wind direction view")
Log.d(KarooHeadwindExtension.TAG, "Updating headwind direction view")
val windSpeed = streamData.data.current.windSpeed
val windDirection = streamData.value
val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed
@ -114,7 +114,7 @@ class RelativeWindDirectionDataType(
}
}
emitter.setCancellable {
Log.d(KarooWinddirExtension.TAG, "Stopping winddir view with $emitter")
Log.d(KarooHeadwindExtension.TAG, "Stopping headwind view with $emitter")
configJob.cancel()
viewJob.cancel()
}

View File

@ -1,4 +1,4 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.datatypes
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
@ -20,7 +20,7 @@ import androidx.glance.preview.ExperimentalGlancePreviewApi
import androidx.glance.preview.Preview
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import de.timklge.karoowinddir.R
import de.timklge.karooheadwind.R
import kotlin.math.roundToInt
fun getArrowResourceByBearing(bearing: Int): Int {

View File

@ -1,7 +1,7 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
class SurfacePressureDataType(context: Context) : BaseDataType(context, "surfacePressure"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {

View File

@ -1,17 +1,17 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.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 de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.WeatherInterpretation
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.screens.HeadwindSettings
import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamSettings
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.Emitter
@ -34,7 +34,7 @@ import kotlin.math.roundToInt
class WeatherDataType(
private val karooSystem: KarooSystemService,
private val applicationContext: Context
) : DataTypeImpl("karoo-winddir", "weather") {
) : DataTypeImpl("karoo-headwind", "weather") {
private val glance = GlanceRemoteViews()
// FIXME: Remove. Currently, the data field will permanently show "no sensor" if no data stream is provided
@ -44,7 +44,7 @@ class WeatherDataType(
currentWeatherData
.collect { data ->
Log.d(KarooWinddirExtension.TAG, "Wind code: ${data.current.weatherCode}")
Log.d(KarooHeadwindExtension.TAG, "Wind code: ${data.current.weatherCode}")
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to data.current.weatherCode.toDouble()))))
}
}
@ -54,13 +54,13 @@ class WeatherDataType(
}
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
Log.d(KarooWinddirExtension.TAG, "Starting weather view with $emitter")
Log.d(KarooHeadwindExtension.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)
data class StreamData(val data: OpenMeteoCurrentWeatherResponse, val settings: HeadwindSettings)
val viewJob = CoroutineScope(Dispatchers.IO).launch {
context.streamCurrentWeatherData()
@ -71,7 +71,7 @@ class WeatherDataType(
emitter.updateView(result.remoteViews)
}
.collect { (data, settings) ->
Log.d(KarooWinddirExtension.TAG, "Updating weather view")
Log.d(KarooHeadwindExtension.TAG, "Updating weather view")
val interpretation = WeatherInterpretation.fromWeatherCode(data.current.weatherCode)
val result = glance.compose(context, DpSize.Unspecified) {
@ -82,7 +82,7 @@ class WeatherDataType(
}
}
emitter.setCancellable {
Log.d(KarooWinddirExtension.TAG, "Stopping winddir view with $emitter")
Log.d(KarooHeadwindExtension.TAG, "Stopping headwind view with $emitter")
configJob.cancel()
viewJob.cancel()
}

View File

@ -1,4 +1,4 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.datatypes
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
@ -15,18 +15,15 @@ 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
import de.timklge.karooheadwind.R
import de.timklge.karooheadwind.WeatherInterpretation
fun getWeatherIcon(interpretation: WeatherInterpretation): Int {
return when (interpretation){

View File

@ -1,4 +1,4 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.datatypes
import android.content.Context
import android.util.Log
@ -16,9 +16,9 @@ import androidx.glance.layout.fillMaxSize
import androidx.glance.text.FontFamily
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import de.timklge.karoowinddir.KarooWinddirExtension
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
import de.timklge.karoowinddir.streamDataFlow
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.streamDataFlow
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.internal.ViewEmitter
import io.hammerhead.karooext.models.StreamState
@ -66,7 +66,7 @@ class WindDirectionDataType(val karooSystem: KarooSystemService, context: Contex
7 -> "NW"
else -> "N/A"
}
Log.d( KarooWinddirExtension.TAG,"Updating wind direction view")
Log.d( KarooHeadwindExtension.TAG,"Updating wind direction view")
val result = glance.compose(context, DpSize.Unspecified) {
Box(modifier = GlanceModifier.fillMaxSize(),
contentAlignment = Alignment(

View File

@ -1,7 +1,7 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
class WindGustsDataType(context: Context) : BaseDataType(context, "windGusts"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {

View File

@ -1,7 +1,7 @@
package de.timklge.karoowinddir.datatypes
package de.timklge.karooheadwind.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
class WindSpeedDataType(context: Context) : BaseDataType(context, "windSpeed"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {

View File

@ -1,4 +1,4 @@
package de.timklge.karoowinddir.screens
package de.timklge.karooheadwind.screens
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.DropdownMenuItem

View File

@ -1,4 +1,4 @@
package de.timklge.karoowinddir.screens
package de.timklge.karooheadwind.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@ -35,11 +35,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import de.timklge.karoowinddir.datatypes.GpsCoordinates
import de.timklge.karoowinddir.getGpsCoordinateFlow
import de.timklge.karoowinddir.saveSettings
import de.timklge.karoowinddir.streamSettings
import de.timklge.karoowinddir.streamStats
import de.timklge.karooheadwind.datatypes.GpsCoordinates
import de.timklge.karooheadwind.getGpsCoordinateFlow
import de.timklge.karooheadwind.saveSettings
import de.timklge.karooheadwind.streamSettings
import de.timklge.karooheadwind.streamStats
import io.hammerhead.karooext.KarooSystemService
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
@ -64,25 +64,25 @@ enum class PrecipitationUnit(val id: String, val label: String){
}
@Serializable
data class WinddirSettings(
data class HeadwindSettings(
val windUnit: WindUnit = WindUnit.KILOMETERS_PER_HOUR,
val precipitationUnit: PrecipitationUnit = PrecipitationUnit.MILLIMETERS,
val welcomeDialogAccepted: Boolean = false,
val showWindspeedOverlay: Boolean = true
){
companion object {
val defaultSettings = Json.encodeToString(WinddirSettings())
val defaultSettings = Json.encodeToString(HeadwindSettings())
}
}
@Serializable
data class WinddirStats(
data class HeadwindStats(
val lastSuccessfulWeatherRequest: Long? = null,
val lastSuccessfulWeatherPosition: GpsCoordinates? = null,
val failedWeatherRequest: Long? = null,
){
companion object {
val defaultStats = Json.encodeToString(WinddirStats())
val defaultStats = Json.encodeToString(HeadwindStats())
}
}
@ -99,7 +99,7 @@ fun MainScreen() {
var welcomeDialogVisible by remember { mutableStateOf(false) }
var showWindspeedOverlay by remember { mutableStateOf(false) }
val stats by ctx.streamStats().collectAsState(WinddirStats())
val stats by ctx.streamStats().collectAsState(HeadwindStats())
val location by karooSystem.getGpsCoordinateFlow().collectAsState(initial = null)
var savedDialogVisible by remember { mutableStateOf(false) }
@ -143,13 +143,13 @@ fun MainScreen() {
Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = showWindspeedOverlay, onCheckedChange = { showWindspeedOverlay = it})
Spacer(modifier = Modifier.width(10.dp))
Text("Show wind speed on direction field")
Text("Show headwind speed on arrow")
}
FilledTonalButton(modifier = Modifier
.fillMaxWidth()
.height(50.dp), onClick = {
val newSettings = WinddirSettings(windUnit = selectedWindUnit, precipitationUnit = selectedPrecipitationUnit, welcomeDialogAccepted = true, showWindspeedOverlay = showWindspeedOverlay)
val newSettings = HeadwindSettings(windUnit = selectedWindUnit, precipitationUnit = selectedPrecipitationUnit, welcomeDialogAccepted = true, showWindspeedOverlay = showWindspeedOverlay)
coroutineScope.launch {
saveSettings(ctx, newSettings)
@ -198,16 +198,16 @@ fun MainScreen() {
AlertDialog(onDismissRequest = { },
confirmButton = { Button(onClick = {
coroutineScope.launch {
saveSettings(ctx, WinddirSettings(windUnit = selectedWindUnit, precipitationUnit = selectedPrecipitationUnit, welcomeDialogAccepted = true))
saveSettings(ctx, HeadwindSettings(windUnit = selectedWindUnit, precipitationUnit = selectedPrecipitationUnit, welcomeDialogAccepted = true))
}
}) { Text("OK") } },
text = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text("Welcome to karoo-winddir!")
Text("Welcome to karoo-headwind!")
Spacer(Modifier.padding(10.dp))
Text("You can add relative wind direction and other fields to your data pages in your profile settings.")
Text("You can add headwind direction and other fields to your data pages in your profile settings.")
Spacer(Modifier.padding(10.dp))

View File

@ -1,4 +1,4 @@
package de.timklge.karoowinddir.theme
package de.timklge.karooheadwind.theme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable

View File

@ -1,8 +1,8 @@
<resources>
<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>
<string name="extension_name">headwind</string>
<string name="headwind">Wind direction</string>
<string name="headwind_description">Current wind direction relative to the riding direction</string>
<string name="relativeHumidity">Humidity</string>
<string name="relativeHumidity_description">Relative humidity in percent</string>
<string name="cloudCover">Cloud cover</string>

View File

@ -2,14 +2,14 @@
<ExtensionInfo
displayName="@string/extension_name"
icon="@drawable/ic_launcher"
id="karoo-winddir"
id="karoo-headwind"
scansDevices="false">
<DataType
description="@string/winddir_description"
displayName="@string/winddir"
description="@string/headwind_description"
displayName="@string/headwind"
graphical="true"
icon="@drawable/ic_launcher"
typeId="winddir" />
typeId="headwind" />
<DataType
description="@string/weather_description"

View File

@ -34,5 +34,5 @@ dependencyResolutionManagement {
}
}
rootProject.name = "Karoo Winddir"
rootProject.name = "Karoo Headwind"
include("app")