Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1afeaae9e6 | ||
|
|
b8c3356674 | ||
|
|
807623d04a |
@ -25,6 +25,8 @@ 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.
|
||||||
|
|||||||
@ -34,7 +34,6 @@ 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
|
||||||
@ -152,7 +151,8 @@ 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()
|
SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(::getRouteProgress)
|
||||||
|
SelectedSource.REMAINING_ROUTE -> streamRouteProgress(::getRemainingRouteProgress)
|
||||||
else -> {}
|
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(
|
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()) { 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[DataType.Field.DISTANCE_TO_DESTINATION], navigationState)
|
StreamData(
|
||||||
}.distinctUntilChanged().throttle(5_000).collect { (userProfile, distanceToDestination, navigationState) ->
|
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 state = navigationState.state
|
||||||
val routePolyline = when (state) {
|
val routePolyline = when (state) {
|
||||||
is OnNavigationState.NavigationState.NavigatingRoute -> state.routePolyline
|
is OnNavigationState.NavigationState.NavigatingRoute -> state.routePolyline
|
||||||
@ -202,17 +233,12 @@ class Window(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val routeLength = lastKnownRouteLength
|
val routeEndAt = lastKnownRouteLength?.plus((distanceToDestination ?: 0.0))
|
||||||
val routeProgressMeters = routeLength?.let { routeLength - (distanceToDestination ?: 0.0) }?.coerceAtLeast(0.0)
|
val barProgress = routeProgressProvider(userProfile, riddenDistance, routeEndAt, distanceToDestination)
|
||||||
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 = routeProgress
|
powerbar.progress = barProgress.progress
|
||||||
powerbar.label = "$routeProgressInUserUnit"
|
powerbar.label = barProgress.label
|
||||||
powerbar.invalidate()
|
powerbar.invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,9 +310,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[FIELD_TARGET_MIN_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[FIELD_TARGET_MAX_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[FIELD_TARGET_VALUE_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..<Zone.entries.size)].colorResource
|
@ColorRes val zoneColorRes = Zone.entries[(progress * Zone.entries.size).roundToInt().coerceIn(0..<Zone.entries.size)].colorResource
|
||||||
|
|
||||||
@ -334,9 +360,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[FIELD_TARGET_MIN_ID]?.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.maxTarget = remap(streamData.heartrateTarget?.values[FIELD_TARGET_MAX_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.target = remap(streamData.heartrateTarget?.values[FIELD_TARGET_VALUE_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.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)
|
||||||
@ -389,9 +415,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[FIELD_TARGET_MIN_ID]?.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.maxTarget = remap(streamData.powerTarget?.values[FIELD_TARGET_MAX_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.target = remap(streamData.powerTarget?.values[FIELD_TARGET_VALUE_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.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)
|
||||||
|
|||||||
@ -59,7 +59,6 @@ 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
|
||||||
@ -88,7 +87,8 @@ 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 = {
|
|
||||||
bottomBarDialogVisible = true
|
|
||||||
}) {
|
|
||||||
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp))
|
|
||||||
Spacer(modifier = Modifier.width(5.dp))
|
|
||||||
Text("Bottom Bar: ${bottomSelectedSource.label}", modifier = Modifier.weight(1.0f))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bottomBarDialogVisible){
|
|
||||||
BarSelectDialog(bottomSelectedSource, onHide = { bottomBarDialogVisible = false }, onSelect = { selected ->
|
|
||||||
bottomSelectedSource = selected
|
|
||||||
coroutineScope.launch { updateSettings() }
|
|
||||||
bottomBarDialogVisible = 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 = {
|
||||||
|
bottomBarDialogVisible = true
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp))
|
||||||
|
Spacer(modifier = Modifier.width(5.dp))
|
||||||
|
Text("Bottom Bar: ${bottomSelectedSource.label}", modifier = Modifier.weight(1.0f))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bottomBarDialogVisible){
|
||||||
|
BarSelectDialog(bottomSelectedSource, onHide = { bottomBarDialogVisible = false }, onSelect = { selected ->
|
||||||
|
bottomSelectedSource = selected
|
||||||
|
coroutineScope.launch { updateSettings() }
|
||||||
|
bottomBarDialogVisible = 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("Opaque background")
|
Text("Solid background")
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user