Compare commits

..

No commits in common. "1.4.2" and "master" have entirely different histories.

3 changed files with 47 additions and 75 deletions

View File

@ -25,8 +25,6 @@ to be displayed at the bottom or at the top of the screen:
- Average power over the last 10 seconds - Average power over the last 10 seconds
- Speed - Speed
- Cadence - Cadence
- Route Progress (shows currently ridden distance)
- Remaining Route (shows remaining distance to the end of the route)
Subsequently, the bar(s) will be shown when riding. Bars are filled and colored according Subsequently, the bar(s) will be shown when riding. Bars are filled and colored according
to your current power output / heart rate zone as setup in your Karoo settings. Optionally, the actual data value can be displayed on top of the bar. to your current power output / heart rate zone as setup in your Karoo settings. Optionally, the actual data value can be displayed on top of the bar.

View File

@ -34,6 +34,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -151,8 +152,7 @@ class Window(
SelectedSource.SPEED_3S -> streamSpeed(true) SelectedSource.SPEED_3S -> streamSpeed(true)
SelectedSource.CADENCE -> streamCadence(false) SelectedSource.CADENCE -> streamCadence(false)
SelectedSource.CADENCE_3S -> streamCadence(true) SelectedSource.CADENCE_3S -> streamCadence(true)
SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(::getRouteProgress) SelectedSource.ROUTE_PROGRESS -> streamRouteProgress()
SelectedSource.REMAINING_ROUTE -> streamRouteProgress(::getRemainingRouteProgress)
else -> {} else -> {}
} }
} }
@ -168,50 +168,19 @@ class Window(
} }
} }
data class BarProgress( private suspend fun streamRouteProgress() {
val progress: Double?,
val label: String,
)
private fun getRouteProgress(userProfile: UserProfile, riddenDistance: Double?, routeEndAt: Double?, distanceToDestination: Double?): BarProgress {
val routeProgress = if (routeEndAt != null && riddenDistance != null) remap(riddenDistance, 0.0, routeEndAt, 0.0, 1.0) else null
val routeProgressInUserUnit = when (userProfile.preferredUnit.distance) {
UserProfile.PreferredUnit.UnitType.IMPERIAL -> riddenDistance?.times(0.000621371)?.roundToInt() // Miles
else -> riddenDistance?.times(0.001)?.roundToInt() // Kilometers
}
return BarProgress(routeProgress, "$routeProgressInUserUnit")
}
private fun getRemainingRouteProgress(userProfile: UserProfile, riddenDistance: Double?, routeEndAt: Double?, distanceToDestination: Double?): BarProgress {
val routeProgress = if (routeEndAt != null && riddenDistance != null) remap(riddenDistance, 0.0, routeEndAt, 0.0, 1.0) else null
val distanceToDestinationInUserUnit = when (userProfile.preferredUnit.distance) {
UserProfile.PreferredUnit.UnitType.IMPERIAL -> distanceToDestination?.times(0.000621371)?.roundToInt() // Miles
else -> distanceToDestination?.times(0.001)?.roundToInt() // Kilometers
}
return BarProgress(routeProgress, "$distanceToDestinationInUserUnit")
}
private suspend fun streamRouteProgress(routeProgressProvider: (UserProfile, Double?, Double?, Double?) -> BarProgress) {
data class StreamData( data class StreamData(
val userProfile: UserProfile, val userProfile: UserProfile,
val distanceToDestination: Double?, val distanceToDestination: Double?,
val navigationState: OnNavigationState, val navigationState: OnNavigationState
val riddenDistance: Double?
) )
var lastKnownRoutePolyline: String? = null var lastKnownRoutePolyline: String? = null
var lastKnownRouteLength: Double? = null var lastKnownRouteLength: Double? = null
combine(karooSystem.streamUserProfile(), karooSystem.streamDataFlow(DataType.Type.DISTANCE_TO_DESTINATION), karooSystem.streamNavigationState(), karooSystem.streamDataFlow(DataType.Type.DISTANCE)) { userProfile, distanceToDestination, navigationState, riddenDistance -> combine(karooSystem.streamUserProfile(), karooSystem.streamDataFlow(DataType.Type.DISTANCE_TO_DESTINATION), karooSystem.streamNavigationState()) { userProfile, distanceToDestination, navigationState ->
StreamData( StreamData(userProfile, (distanceToDestination as? StreamState.Streaming)?.dataPoint?.values[DataType.Field.DISTANCE_TO_DESTINATION], navigationState)
userProfile, }.distinctUntilChanged().throttle(5_000).collect { (userProfile, distanceToDestination, navigationState) ->
(distanceToDestination as? StreamState.Streaming)?.dataPoint?.values?.get(DataType.Field.DISTANCE_TO_DESTINATION),
navigationState,
(riddenDistance as? StreamState.Streaming)?.dataPoint?.values?.get(DataType.Field.DISTANCE)
)
}.distinctUntilChanged().throttle(5_000).collect { (userProfile, distanceToDestination, navigationState, riddenDistance) ->
val state = navigationState.state val state = navigationState.state
val routePolyline = when (state) { val routePolyline = when (state) {
is OnNavigationState.NavigationState.NavigatingRoute -> state.routePolyline is OnNavigationState.NavigationState.NavigatingRoute -> state.routePolyline
@ -233,12 +202,17 @@ class Window(
} }
} }
val routeEndAt = lastKnownRouteLength?.plus((distanceToDestination ?: 0.0)) val routeLength = lastKnownRouteLength
val barProgress = routeProgressProvider(userProfile, riddenDistance, routeEndAt, distanceToDestination) val routeProgressMeters = routeLength?.let { routeLength - (distanceToDestination ?: 0.0) }?.coerceAtLeast(0.0)
val routeProgress = if (routeLength != null && routeProgressMeters != null) remap(routeProgressMeters, 0.0, routeLength, 0.0, 1.0) else null
val routeProgressInUserUnit = when (userProfile.preferredUnit.distance) {
UserProfile.PreferredUnit.UnitType.IMPERIAL -> routeProgressMeters?.times(0.000621371)?.roundToInt() // Miles
else -> routeProgressMeters?.times(0.001)?.roundToInt() // Kilometers
}
powerbar.progressColor = context.getColor(R.color.zone0) powerbar.progressColor = context.getColor(R.color.zone0)
powerbar.progress = barProgress.progress powerbar.progress = routeProgress
powerbar.label = barProgress.label powerbar.label = "$routeProgressInUserUnit"
powerbar.invalidate() powerbar.invalidate()
} }
} }
@ -310,9 +284,9 @@ class Window(
val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence
val progress = remap(value.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) ?: 0.0 val progress = remap(value.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) ?: 0.0
powerbar.minTarget = remap(streamData.cadenceTarget?.values?.get(FIELD_TARGET_MIN_ID)?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) powerbar.minTarget = remap(streamData.cadenceTarget?.values[FIELD_TARGET_MIN_ID]?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0)
powerbar.maxTarget = remap(streamData.cadenceTarget?.values?.get(FIELD_TARGET_MAX_ID)?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) powerbar.maxTarget = remap(streamData.cadenceTarget?.values[FIELD_TARGET_MAX_ID]?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0)
powerbar.target = remap(streamData.cadenceTarget?.values?.get(FIELD_TARGET_VALUE_ID)?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) powerbar.target = remap(streamData.cadenceTarget?.values[FIELD_TARGET_VALUE_ID]?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0)
@ColorRes val zoneColorRes = Zone.entries[(progress * Zone.entries.size).roundToInt().coerceIn(0..<Zone.entries.size)].colorResource @ColorRes val zoneColorRes = Zone.entries[(progress * Zone.entries.size).roundToInt().coerceIn(0..<Zone.entries.size)].colorResource
@ -360,9 +334,9 @@ class Window(
val maxHr = customMaxHr ?: streamData.userProfile.maxHr val maxHr = customMaxHr ?: streamData.userProfile.maxHr
val progress = remap(value.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) val progress = remap(value.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
powerbar.minTarget = remap(streamData.heartrateTarget?.values?.get(FIELD_TARGET_MIN_ID), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) powerbar.minTarget = remap(streamData.heartrateTarget?.values[FIELD_TARGET_MIN_ID]?.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
powerbar.maxTarget = remap(streamData.heartrateTarget?.values?.get(FIELD_TARGET_MAX_ID), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) powerbar.maxTarget = remap(streamData.heartrateTarget?.values[FIELD_TARGET_MAX_ID]?.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
powerbar.target = remap(streamData.heartrateTarget?.values?.get(FIELD_TARGET_VALUE_ID), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) powerbar.target = remap(streamData.heartrateTarget?.values[FIELD_TARGET_VALUE_ID]?.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) { powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
context.getColor(getZone(streamData.userProfile.heartRateZones, value)?.colorResource ?: R.color.zone7) context.getColor(getZone(streamData.userProfile.heartRateZones, value)?.colorResource ?: R.color.zone7)
@ -415,9 +389,9 @@ class Window(
val maxPower = customMaxPower ?: (streamData.userProfile.powerZones.last().min + 30) val maxPower = customMaxPower ?: (streamData.userProfile.powerZones.last().min + 30)
val progress = remap(value.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) val progress = remap(value.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
powerbar.minTarget = remap(streamData.powerTarget?.values?.get(FIELD_TARGET_MIN_ID), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) powerbar.minTarget = remap(streamData.powerTarget?.values[FIELD_TARGET_MIN_ID]?.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
powerbar.maxTarget = remap(streamData.powerTarget?.values?.get(FIELD_TARGET_MAX_ID), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) powerbar.maxTarget = remap(streamData.powerTarget?.values[FIELD_TARGET_MAX_ID]?.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
powerbar.target = remap(streamData.powerTarget?.values?.get(FIELD_TARGET_VALUE_ID), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) powerbar.target = remap(streamData.powerTarget?.values[FIELD_TARGET_VALUE_ID]?.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) { powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
context.getColor(getZone(streamData.userProfile.powerZones, value)?.colorResource ?: R.color.zone7) context.getColor(getZone(streamData.userProfile.powerZones, value)?.colorResource ?: R.color.zone7)

View File

@ -59,6 +59,7 @@ import androidx.datastore.preferences.core.edit
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import de.timklge.karoopowerbar.CustomProgressBarBarSize import de.timklge.karoopowerbar.CustomProgressBarBarSize
import de.timklge.karoopowerbar.CustomProgressBarFontSize import de.timklge.karoopowerbar.CustomProgressBarFontSize
import de.timklge.karoopowerbar.CustomProgressBarSize
import de.timklge.karoopowerbar.KarooPowerbarExtension import de.timklge.karoopowerbar.KarooPowerbarExtension
import de.timklge.karoopowerbar.PowerbarSettings import de.timklge.karoopowerbar.PowerbarSettings
import de.timklge.karoopowerbar.R import de.timklge.karoopowerbar.R
@ -87,8 +88,7 @@ enum class SelectedSource(val id: String, val label: String) {
SPEED_3S("speed_3s", "Speed (3 sec avg"), SPEED_3S("speed_3s", "Speed (3 sec avg"),
CADENCE("cadence", "Cadence"), CADENCE("cadence", "Cadence"),
CADENCE_3S("cadence_3s", "Cadence (3 sec avg)"), CADENCE_3S("cadence_3s", "Cadence (3 sec avg)"),
ROUTE_PROGRESS("route_progress", "Route Progress"), ROUTE_PROGRESS("route_progress", "Route Progress");
REMAINING_ROUTE("route_progress_remaining", "Route Remaining");
fun isPower() = this == POWER || this == POWER_3S || this == POWER_10S fun isPower() = this == POWER || this == POWER_3S || this == POWER_10S
} }
@ -276,25 +276,6 @@ fun MainScreen(onFinish: () -> Unit) {
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) { .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) {
FilledTonalButton(modifier = Modifier
.fillMaxWidth()
.height(60.dp),
onClick = {
topBarDialogVisible = true
}) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp))
Text("Top Bar: ${topSelectedSource.label}", modifier = Modifier.weight(1.0f))
}
if (topBarDialogVisible){
BarSelectDialog(topSelectedSource, onHide = { topBarDialogVisible = false }, onSelect = { selected ->
topSelectedSource = selected
coroutineScope.launch { updateSettings() }
topBarDialogVisible = false
})
}
FilledTonalButton(modifier = Modifier FilledTonalButton(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(60.dp), .height(60.dp),
@ -314,6 +295,25 @@ fun MainScreen(onFinish: () -> Unit) {
}) })
} }
FilledTonalButton(modifier = Modifier
.fillMaxWidth()
.height(60.dp),
onClick = {
topBarDialogVisible = true
}) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp))
Text("Top Bar: ${topSelectedSource.label}", modifier = Modifier.weight(1.0f))
}
if (topBarDialogVisible){
BarSelectDialog(topSelectedSource, onHide = { topBarDialogVisible = false }, onSelect = { selected ->
topSelectedSource = selected
coroutineScope.launch { updateSettings() }
topBarDialogVisible = false
})
}
apply { apply {
val dropdownOptions = CustomProgressBarBarSize.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) } val dropdownOptions = CustomProgressBarBarSize.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
val dropdownInitialSelection by remember(barBarSize) { val dropdownInitialSelection by remember(barBarSize) {
@ -494,7 +494,7 @@ fun MainScreen(onFinish: () -> Unit) {
coroutineScope.launch { updateSettings() } coroutineScope.launch { updateSettings() }
}) })
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
Text("Solid background") Text("Opaque background")
} }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {