fix #7: Set units of measurement to karoo profile preferences on first load

This commit is contained in:
Tim Kluge 2024-12-13 17:56:20 +01:00
parent 8d30e62015
commit a4347511a5
7 changed files with 45 additions and 19 deletions

View File

@ -7,12 +7,15 @@ import androidx.datastore.preferences.core.stringPreferencesKey
import de.timklge.karooheadwind.datatypes.GpsCoordinates import de.timklge.karooheadwind.datatypes.GpsCoordinates
import de.timklge.karooheadwind.screens.HeadwindSettings import de.timklge.karooheadwind.screens.HeadwindSettings
import de.timklge.karooheadwind.screens.HeadwindStats import de.timklge.karooheadwind.screens.HeadwindStats
import de.timklge.karooheadwind.screens.PrecipitationUnit
import de.timklge.karooheadwind.screens.WindUnit
import io.hammerhead.karooext.KarooSystemService import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.DataType import io.hammerhead.karooext.models.DataType
import io.hammerhead.karooext.models.HttpResponseState import io.hammerhead.karooext.models.HttpResponseState
import io.hammerhead.karooext.models.OnHttpResponse import io.hammerhead.karooext.models.OnHttpResponse
import io.hammerhead.karooext.models.OnStreamState import io.hammerhead.karooext.models.OnStreamState
import io.hammerhead.karooext.models.StreamState import io.hammerhead.karooext.models.StreamState
import io.hammerhead.karooext.models.UserProfile
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
@ -24,6 +27,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
@ -86,12 +90,22 @@ fun Context.streamCurrentWeatherData(): Flow<OpenMeteoCurrentWeatherResponse> {
}.filterNotNull().distinctUntilChanged().filter { it.current.time * 1000 >= System.currentTimeMillis() - (1000 * 60 * 60 * 12) } }.filterNotNull().distinctUntilChanged().filter { it.current.time * 1000 >= System.currentTimeMillis() - (1000 * 60 * 60 * 12) }
} }
fun Context.streamSettings(): Flow<HeadwindSettings> { fun Context.streamSettings(karooSystemService: KarooSystemService): Flow<HeadwindSettings> {
return dataStore.data.map { settingsJson -> return dataStore.data.map { settingsJson ->
try { try {
jsonWithUnknownKeys.decodeFromString<HeadwindSettings>( if (settingsJson.contains(settingsKey)){
settingsJson[settingsKey] ?: HeadwindSettings.defaultSettings jsonWithUnknownKeys.decodeFromString<HeadwindSettings>(settingsJson[settingsKey]!!)
} else {
val defaultSettings = jsonWithUnknownKeys.decodeFromString<HeadwindSettings>(HeadwindSettings.defaultSettings)
val preferredUnits = karooSystemService.streamUserProfile().first().preferredUnit
val preferredMetric = preferredUnits.distance == UserProfile.PreferredUnit.UnitType.METRIC
defaultSettings.copy(
windUnit = if (preferredMetric) WindUnit.KILOMETERS_PER_HOUR else WindUnit.MILES_PER_HOUR,
precipitationUnit = if (preferredMetric) PrecipitationUnit.MILLIMETERS else PrecipitationUnit.INCH
) )
}
} catch(e: Throwable){ } catch(e: Throwable){
Log.e(KarooHeadwindExtension.TAG, "Failed to read preferences", e) Log.e(KarooHeadwindExtension.TAG, "Failed to read preferences", e)
jsonWithUnknownKeys.decodeFromString<HeadwindSettings>(HeadwindSettings.defaultSettings) jsonWithUnknownKeys.decodeFromString<HeadwindSettings>(HeadwindSettings.defaultSettings)
@ -112,6 +126,17 @@ fun Context.streamStats(): Flow<HeadwindStats> {
}.distinctUntilChanged() }.distinctUntilChanged()
} }
fun KarooSystemService.streamUserProfile(): Flow<UserProfile> {
return callbackFlow {
val listenerId = addConsumer { userProfile: UserProfile ->
trySendBlocking(userProfile)
}
awaitClose {
removeConsumer(listenerId)
}
}
}
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoordinates, settings: HeadwindSettings): HttpResponseState.Complete { suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoordinates, settings: HeadwindSettings): HttpResponseState.Complete {
return callbackFlow { return callbackFlow {

View File

@ -75,7 +75,7 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.0.0-beta2") {
} }
} }
streamSettings() streamSettings(karooSystem)
.filter { it.welcomeDialogAccepted } .filter { it.welcomeDialogAccepted }
.combine(gpsFlow) { settings, gps -> settings to gps } .combine(gpsFlow) { settings, gps -> settings to gps }
.map { (settings, gps) -> .map { (settings, gps) ->

View File

@ -72,7 +72,7 @@ class HeadwindDirectionDataType(
karooSystem.streamDataFlow(dataTypeId) karooSystem.streamDataFlow(dataTypeId)
.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()) { (value, data), settings -> StreamData(value, data, settings) } .combine(context.streamSettings(karooSystem)) { (value, data), settings -> StreamData(value, data, settings) }
.onCompletion { .onCompletion {
// Clear view on completion // Clear view on completion
val result = glance.compose(context, DpSize.Unspecified) { } val result = glance.compose(context, DpSize.Unspecified) { }
@ -83,7 +83,7 @@ class HeadwindDirectionDataType(
val windSpeed = streamData.data.current.windSpeed val windSpeed = streamData.data.current.windSpeed
val windDirection = streamData.value val windDirection = streamData.value
val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed
val windSpeedText = "${headwindSpeed.roundToInt()}" val windSpeedText = headwindSpeed.roundToInt().toString()
val result = glance.compose(context, DpSize.Unspecified) { val result = glance.compose(context, DpSize.Unspecified) {
HeadwindDirection(baseBitmap, windDirection.roundToInt(), config.textSize, windSpeedText) HeadwindDirection(baseBitmap, windDirection.roundToInt(), config.textSize, windSpeedText)

View File

@ -28,7 +28,7 @@ class HeadwindSpeedDataType(
val job = CoroutineScope(Dispatchers.IO).launch { val job = CoroutineScope(Dispatchers.IO).launch {
karooSystem.getRelativeHeadingFlow(context) karooSystem.getRelativeHeadingFlow(context)
.combine(context.streamCurrentWeatherData()) { value, data -> value to data } .combine(context.streamCurrentWeatherData()) { value, data -> value to data }
.combine(context.streamSettings()) { (value, data), settings -> StreamData(value, data, settings) } .combine(context.streamSettings(karooSystem)) { (value, data), settings -> StreamData(value, data, settings) }
.collect { streamData -> .collect { streamData ->
val windSpeed = streamData.data.current.windSpeed val windSpeed = streamData.data.current.windSpeed
val windDirection = streamData.value val windDirection = streamData.value

View File

@ -68,7 +68,7 @@ class WeatherDataType(
val viewJob = CoroutineScope(Dispatchers.IO).launch { val viewJob = CoroutineScope(Dispatchers.IO).launch {
context.streamCurrentWeatherData() context.streamCurrentWeatherData()
.combine(context.streamSettings()) { data, settings -> StreamData(data, settings) } .combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
.onCompletion { .onCompletion {
// Clear view on completion // Clear view on completion
val result = glance.compose(context, DpSize.Unspecified) { } val result = glance.compose(context, DpSize.Unspecified) { }

View File

@ -17,14 +17,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
data class DropdownOption(val id: String, val name: String) data class DropdownOption(val id: String, val name: String)
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Dropdown(label: String, options: List<DropdownOption>, initialSelection: DropdownOption, onSelect: (selectedOption: DropdownOption) -> Unit) { fun Dropdown(label: String, options: List<DropdownOption>, selected: DropdownOption, onSelect: (selectedOption: DropdownOption) -> Unit) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
var selected by remember { mutableStateOf(initialSelection) }
ExposedDropdownMenuBox( ExposedDropdownMenuBox(
expanded = expanded, expanded = expanded,
@ -51,9 +49,8 @@ fun Dropdown(label: String, options: List<DropdownOption>, initialSelection: Dro
DropdownMenuItem( DropdownMenuItem(
text = { Text(option.name, style = MaterialTheme.typography.bodyLarge) }, text = { Text(option.name, style = MaterialTheme.typography.bodyLarge) },
onClick = { onClick = {
selected = option
expanded = false expanded = false
onSelect(selected) onSelect(option)
}, },
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
) )

View File

@ -100,7 +100,7 @@ fun MainScreen() {
var savedDialogVisible by remember { mutableStateOf(false) } var savedDialogVisible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
ctx.streamSettings().collect { settings -> ctx.streamSettings(karooSystem).collect { settings ->
selectedWindUnit = settings.windUnit selectedWindUnit = settings.windUnit
selectedPrecipitationUnit = settings.precipitationUnit selectedPrecipitationUnit = settings.precipitationUnit
welcomeDialogVisible = !settings.welcomeDialogAccepted welcomeDialogVisible = !settings.welcomeDialogAccepted
@ -123,14 +123,18 @@ fun MainScreen() {
.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) { .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) {
val windSpeedUnitDropdownOptions = WindUnit.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) } val windSpeedUnitDropdownOptions = WindUnit.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
val windSpeedUnitInitialSelection = windSpeedUnitDropdownOptions.find { option -> option.id == selectedWindUnit.id }!! val windSpeedUnitInitialSelection by remember(selectedWindUnit) {
Dropdown(label = "Wind Speed Unit", options = windSpeedUnitDropdownOptions, initialSelection = windSpeedUnitInitialSelection) { selectedOption -> mutableStateOf(windSpeedUnitDropdownOptions.find { option -> option.id == selectedWindUnit.id }!!)
}
Dropdown(label = "Wind Speed Unit", options = windSpeedUnitDropdownOptions, selected = windSpeedUnitInitialSelection) { selectedOption ->
selectedWindUnit = WindUnit.entries.find { unit -> unit.id == selectedOption.id }!! selectedWindUnit = WindUnit.entries.find { unit -> unit.id == selectedOption.id }!!
} }
val precipitationUnitDropdownOptions = PrecipitationUnit.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) } val precipitationUnitDropdownOptions = PrecipitationUnit.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
val precipitationUnitInitialSelection = precipitationUnitDropdownOptions.find { option -> option.id == selectedPrecipitationUnit.id }!! val precipitationUnitInitialSelection by remember(selectedPrecipitationUnit) {
Dropdown(label = "Precipitation Unit", options = precipitationUnitDropdownOptions, initialSelection = precipitationUnitInitialSelection) { selectedOption -> mutableStateOf(precipitationUnitDropdownOptions.find { option -> option.id == selectedPrecipitationUnit.id }!!)
}
Dropdown(label = "Precipitation Unit", options = precipitationUnitDropdownOptions, selected = precipitationUnitInitialSelection) { selectedOption ->
selectedPrecipitationUnit = PrecipitationUnit.entries.find { unit -> unit.id == selectedOption.id }!! selectedPrecipitationUnit = PrecipitationUnit.entries.find { unit -> unit.id == selectedOption.id }!!
} }