parent
1afeaae9e6
commit
c202f20320
@ -29,7 +29,11 @@ data class PowerbarSettings(
|
||||
val minSpeed: Float = defaultMinSpeedMs, val maxSpeed: Float = defaultMaxSpeedMs, // 50 km/h in m/s
|
||||
val minPower: Int? = null, val maxPower: Int? = null,
|
||||
val minHr: Int? = null, val maxHr: Int? = null,
|
||||
val useCustomHrRange: Boolean = false, val useCustomPowerRange: Boolean = false
|
||||
val minGradient: Int? = defaultMinGradient, val maxGradient: Int? = defaultMaxGradient,
|
||||
|
||||
val useCustomGradientRange: Boolean = false,
|
||||
val useCustomHrRange: Boolean = false,
|
||||
val useCustomPowerRange: Boolean = false
|
||||
){
|
||||
companion object {
|
||||
val defaultSettings = Json.encodeToString(PowerbarSettings())
|
||||
@ -37,6 +41,8 @@ data class PowerbarSettings(
|
||||
const val defaultMaxSpeedMs = 13.89f
|
||||
const val defaultMinCadence = 50
|
||||
const val defaultMaxCadence = 120
|
||||
const val defaultMinGradient = 0
|
||||
const val defaultMaxGradient = 20
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Locale
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun remap(value: Double?, fromMin: Double, fromMax: Double, toMin: Double, toMax: Double): Double? {
|
||||
@ -153,7 +154,8 @@ class Window(
|
||||
SelectedSource.CADENCE_3S -> streamCadence(true)
|
||||
SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(::getRouteProgress)
|
||||
SelectedSource.REMAINING_ROUTE -> streamRouteProgress(::getRemainingRouteProgress)
|
||||
else -> {}
|
||||
SelectedSource.GRADE -> streamGrade()
|
||||
SelectedSource.NONE -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,6 +290,57 @@ class Window(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun streamGrade() {
|
||||
@ColorRes
|
||||
fun getInclineIndicatorColor(percent: Float): Int? {
|
||||
return when(percent) {
|
||||
in -Float.MAX_VALUE..<-7.5f -> R.color.eleDarkBlue // Dark blue
|
||||
in -7.5f..<-4.6f -> R.color.eleLightBlue // Light blue
|
||||
in -4.6f..<-2f -> R.color.eleWhite // White
|
||||
in 2f..<4.6f -> R.color.eleDarkGreen // Dark green
|
||||
in 4.6f..<7.5f -> R.color.eleLightGreen // Light green
|
||||
in 7.5f..<12.5f -> R.color.eleYellow // Yellow
|
||||
in 12.5f..<15.5f -> R.color.eleLightOrange // Light Orange
|
||||
in 15.5f..<19.5f -> R.color.eleDarkOrange // Dark Orange
|
||||
in 19.5f..<23.5f -> R.color.eleRed // Red
|
||||
in 23.5f..Float.MAX_VALUE -> R.color.elePurple // Purple
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val gradeFlow = karooSystem.streamDataFlow(DataType.Type.ELEVATION_GRADE)
|
||||
.map { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||
.distinctUntilChanged()
|
||||
|
||||
data class StreamData(val userProfile: UserProfile, val value: Double?, val settings: PowerbarSettings? = null)
|
||||
|
||||
val settingsFlow = context.streamSettings()
|
||||
|
||||
combine(karooSystem.streamUserProfile(), gradeFlow, settingsFlow) { userProfile, grade, settings ->
|
||||
StreamData(userProfile, grade, settings)
|
||||
}.distinctUntilChanged().throttle(1_000).collect { streamData ->
|
||||
val value = streamData.value
|
||||
|
||||
if (value != null) {
|
||||
val minGradient = streamData.settings?.minGradient ?: PowerbarSettings.defaultMinGradient
|
||||
val maxGradient = streamData.settings?.maxGradient ?: PowerbarSettings.defaultMaxGradient
|
||||
|
||||
powerbar.progressColor = getInclineIndicatorColor(value.toFloat()) ?: context.getColor(R.color.zone0)
|
||||
powerbar.progress = remap(value.toDouble(), minGradient.toDouble(), maxGradient.toDouble(), 0.0, 1.0)
|
||||
powerbar.label = "${String.format(Locale.getDefault(), "%.1f", value)}%"
|
||||
|
||||
Log.d(TAG, "Grade: $value")
|
||||
} else {
|
||||
powerbar.progressColor = context.getColor(R.color.zone0)
|
||||
powerbar.progress = null
|
||||
powerbar.label = "?"
|
||||
|
||||
Log.d(TAG, "Grade: Unavailable")
|
||||
}
|
||||
powerbar.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun streamCadence(smoothed: Boolean) {
|
||||
val cadenceFlow = karooSystem.streamDataFlow(if(smoothed) DataType.Type.SMOOTHED_3S_AVERAGE_CADENCE else DataType.Type.CADENCE)
|
||||
.map { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||
|
||||
@ -87,6 +87,7 @@ enum class SelectedSource(val id: String, val label: String) {
|
||||
SPEED_3S("speed_3s", "Speed (3 sec avg"),
|
||||
CADENCE("cadence", "Cadence"),
|
||||
CADENCE_3S("cadence_3s", "Cadence (3 sec avg)"),
|
||||
GRADE("grade", "Grade"),
|
||||
ROUTE_PROGRESS("route_progress", "Route Progress"),
|
||||
REMAINING_ROUTE("route_progress_remaining", "Route Remaining");
|
||||
|
||||
@ -160,6 +161,8 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
var customMaxPower by remember { mutableStateOf("") }
|
||||
var customMinHr by remember { mutableStateOf("") }
|
||||
var customMaxHr by remember { mutableStateOf("") }
|
||||
var minGrade by remember { mutableStateOf("0") }
|
||||
var maxGrade by remember { mutableStateOf("0") }
|
||||
var useCustomPowerRange by remember { mutableStateOf(false) }
|
||||
var useCustomHrRange by remember { mutableStateOf(false) }
|
||||
|
||||
@ -188,6 +191,8 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
maxPower = customMaxPower.toIntOrNull(),
|
||||
minHr = customMinHr.toIntOrNull(),
|
||||
maxHr = customMaxHr.toIntOrNull(),
|
||||
minGradient = minGrade.toIntOrNull() ?: PowerbarSettings.defaultMinGradient,
|
||||
maxGradient = maxGrade.toIntOrNull() ?: PowerbarSettings.defaultMaxGradient,
|
||||
barBarSize = barBarSize,
|
||||
barFontSize = barFontSize,
|
||||
useCustomPowerRange = useCustomPowerRange,
|
||||
@ -243,6 +248,8 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
customMaxPower = settings.maxPower?.toString() ?: ""
|
||||
customMinHr = settings.minHr?.toString() ?: ""
|
||||
customMaxHr = settings.maxHr?.toString() ?: ""
|
||||
minGrade = settings.minGradient?.toString() ?: ""
|
||||
maxGrade = settings.maxGradient?.toString() ?: ""
|
||||
useCustomPowerRange = settings.useCustomPowerRange
|
||||
useCustomHrRange = settings.useCustomHrRange
|
||||
}
|
||||
@ -344,7 +351,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
.weight(1f)
|
||||
.absolutePadding(right = 2.dp)
|
||||
.onFocusEvent(::updateFocus),
|
||||
onValueChange = { minSpeed = it },
|
||||
onValueChange = { minSpeed = it.filter { c -> c.isDigit() } },
|
||||
label = { Text("Min Speed") },
|
||||
suffix = { Text(if (isImperial) "mph" else "kph") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
@ -355,7 +362,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
.weight(1f)
|
||||
.absolutePadding(left = 2.dp)
|
||||
.onFocusEvent(::updateFocus),
|
||||
onValueChange = { maxSpeed = it },
|
||||
onValueChange = { maxSpeed = it.filter { c -> c.isDigit() } },
|
||||
label = { Text("Max Speed") },
|
||||
suffix = { Text(if (isImperial) "mph" else "kph") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
@ -380,7 +387,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
.weight(1f)
|
||||
.absolutePadding(right = 2.dp)
|
||||
.onFocusEvent(::updateFocus),
|
||||
onValueChange = { customMinPower = it },
|
||||
onValueChange = { customMinPower = it.filter { c -> c.isDigit() } },
|
||||
label = { Text("Min Power", fontSize = 12.sp) },
|
||||
suffix = { Text("W") },
|
||||
placeholder = { Text("$profileMinPower") },
|
||||
@ -392,7 +399,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
.weight(1f)
|
||||
.absolutePadding(left = 2.dp)
|
||||
.onFocusEvent(::updateFocus),
|
||||
onValueChange = { customMaxPower = it },
|
||||
onValueChange = { customMaxPower = it.filter { c -> c.isDigit() } },
|
||||
label = { Text("Max Power", fontSize = 12.sp) },
|
||||
suffix = { Text("W") },
|
||||
placeholder = { Text("$profileMaxPower") },
|
||||
@ -419,7 +426,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
.weight(1f)
|
||||
.absolutePadding(right = 2.dp)
|
||||
.onFocusEvent(::updateFocus),
|
||||
onValueChange = { customMinHr = it },
|
||||
onValueChange = { customMinHr = it.filter { c -> c.isDigit() } },
|
||||
label = { Text("Min Hr") },
|
||||
suffix = { Text("bpm") },
|
||||
placeholder = { if(profileRestHr > 0) Text("$profileRestHr") else Unit },
|
||||
@ -431,7 +438,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
.weight(1f)
|
||||
.absolutePadding(left = 2.dp)
|
||||
.onFocusEvent(::updateFocus),
|
||||
onValueChange = { customMaxHr = it },
|
||||
onValueChange = { customMaxHr = it.filter { c -> c.isDigit() } },
|
||||
label = { Text("Max Hr") },
|
||||
suffix = { Text("bpm") },
|
||||
placeholder = { if(profileMaxHr > 0) Text("$profileMaxHr") else Unit },
|
||||
@ -450,7 +457,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
.weight(1f)
|
||||
.absolutePadding(right = 2.dp)
|
||||
.onFocusEvent(::updateFocus),
|
||||
onValueChange = { minCadence = it },
|
||||
onValueChange = { minCadence = it.filter { c -> c.isDigit() } },
|
||||
label = { Text("Min Cadence") },
|
||||
suffix = { Text("rpm") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
@ -461,7 +468,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
.weight(1f)
|
||||
.absolutePadding(left = 2.dp)
|
||||
.onFocusEvent(::updateFocus),
|
||||
onValueChange = { maxCadence = it },
|
||||
onValueChange = { maxCadence = it.filter { c -> c.isDigit() } },
|
||||
label = { Text("Min Cadence") },
|
||||
suffix = { Text("rpm") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
@ -470,6 +477,32 @@ fun MainScreen(onFinish: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
if (topSelectedSource == SelectedSource.GRADE || bottomSelectedSource == SelectedSource.GRADE){
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
OutlinedTextField(value = minGrade, modifier = Modifier
|
||||
.weight(1f)
|
||||
.absolutePadding(right = 2.dp)
|
||||
.onFocusEvent(::updateFocus),
|
||||
onValueChange = { minGrade = it.filterIndexed { index, c -> c.isDigit() || (c == '-' && index == 0) } },
|
||||
label = { Text("Min Grade") },
|
||||
suffix = { Text("%") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
OutlinedTextField(value = maxGrade, modifier = Modifier
|
||||
.weight(1f)
|
||||
.absolutePadding(left = 2.dp)
|
||||
.onFocusEvent(::updateFocus),
|
||||
onValueChange = { maxGrade = it.filterIndexed { index, c -> c.isDigit() || (c == '-' && index == 0) } },
|
||||
label = { Text("Max Grade") },
|
||||
suffix = { Text("%") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Switch(checked = colorBasedOnZones, onCheckedChange = {
|
||||
colorBasedOnZones = it
|
||||
|
||||
@ -12,4 +12,16 @@
|
||||
<color name="zone6">#FE581F</color>
|
||||
<color name="zone7">#D60404</color>
|
||||
<color name="zone8">#B700A2</color>
|
||||
|
||||
<color name="eleDarkGreen">#079d78</color>
|
||||
<color name="eleLightGreen">#58c597</color>
|
||||
<color name="eleYellow">#e7e021</color>
|
||||
<color name="eleLightOrange">#e59174</color>
|
||||
<color name="eleDarkOrange">#e7693a</color>
|
||||
<color name="eleRed">#c82425</color>
|
||||
<color name="eleLightRed">#f05a28</color>
|
||||
<color name="elePurple">#b222a3</color>
|
||||
<color name="eleWhite">#ffffff</color>
|
||||
<color name="eleLightBlue">#4fc3f7</color>
|
||||
<color name="eleDarkBlue">#2D58AF</color>
|
||||
</resources>
|
||||
Loading…
x
Reference in New Issue
Block a user