diff --git a/README.md b/README.md index c7b8ab6..8be8278 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ to be displayed at the bottom or at the top of the screen: - Average power over the last 10 seconds - Speed - 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 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. diff --git a/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt b/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt index 33be186..1df4410 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt @@ -34,7 +34,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -152,7 +151,8 @@ class Window( SelectedSource.SPEED_3S -> streamSpeed(true) SelectedSource.CADENCE -> streamCadence(false) SelectedSource.CADENCE_3S -> streamCadence(true) - SelectedSource.ROUTE_PROGRESS -> streamRouteProgress() + SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(::getRouteProgress) + SelectedSource.REMAINING_ROUTE -> streamRouteProgress(::getRemainingRouteProgress) else -> {} } } @@ -168,19 +168,50 @@ class Window( } } - private suspend fun streamRouteProgress() { + data class BarProgress( + 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( val userProfile: UserProfile, val distanceToDestination: Double?, - val navigationState: OnNavigationState + val navigationState: OnNavigationState, + val riddenDistance: Double? ) var lastKnownRoutePolyline: String? = null var lastKnownRouteLength: Double? = null - combine(karooSystem.streamUserProfile(), karooSystem.streamDataFlow(DataType.Type.DISTANCE_TO_DESTINATION), karooSystem.streamNavigationState()) { userProfile, distanceToDestination, navigationState -> - StreamData(userProfile, (distanceToDestination as? StreamState.Streaming)?.dataPoint?.values[DataType.Field.DISTANCE_TO_DESTINATION], navigationState) - }.distinctUntilChanged().throttle(5_000).collect { (userProfile, distanceToDestination, navigationState) -> + combine(karooSystem.streamUserProfile(), karooSystem.streamDataFlow(DataType.Type.DISTANCE_TO_DESTINATION), karooSystem.streamNavigationState(), karooSystem.streamDataFlow(DataType.Type.DISTANCE)) { userProfile, distanceToDestination, navigationState, riddenDistance -> + StreamData( + userProfile, + (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 routePolyline = when (state) { is OnNavigationState.NavigationState.NavigatingRoute -> state.routePolyline @@ -202,17 +233,12 @@ class Window( } } - val routeLength = lastKnownRouteLength - 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 - } + val routeEndAt = lastKnownRouteLength?.plus((distanceToDestination ?: 0.0)) + val barProgress = routeProgressProvider(userProfile, riddenDistance, routeEndAt, distanceToDestination) powerbar.progressColor = context.getColor(R.color.zone0) - powerbar.progress = routeProgress - powerbar.label = "$routeProgressInUserUnit" + powerbar.progress = barProgress.progress + powerbar.label = barProgress.label powerbar.invalidate() } } @@ -284,9 +310,9 @@ class Window( val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence val progress = remap(value.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) ?: 0.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[FIELD_TARGET_MAX_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) + powerbar.minTarget = remap(streamData.cadenceTarget?.values?.get(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.target = remap(streamData.cadenceTarget?.values?.get(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..