Compare commits
No commits in common. "1.4.2" and "master" have entirely different histories.
@ -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.
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user