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 minSpeed: Float = defaultMinSpeedMs, val maxSpeed: Float = defaultMaxSpeedMs, // 50 km/h in m/s
|
||||||
val minPower: Int? = null, val maxPower: Int? = null,
|
val minPower: Int? = null, val maxPower: Int? = null,
|
||||||
val minHr: Int? = null, val maxHr: 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 {
|
companion object {
|
||||||
val defaultSettings = Json.encodeToString(PowerbarSettings())
|
val defaultSettings = Json.encodeToString(PowerbarSettings())
|
||||||
@ -37,6 +41,8 @@ data class PowerbarSettings(
|
|||||||
const val defaultMaxSpeedMs = 13.89f
|
const val defaultMaxSpeedMs = 13.89f
|
||||||
const val defaultMinCadence = 50
|
const val defaultMinCadence = 50
|
||||||
const val defaultMaxCadence = 120
|
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.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.util.Locale
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
fun remap(value: Double?, fromMin: Double, fromMax: Double, toMin: Double, toMax: Double): Double? {
|
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.CADENCE_3S -> streamCadence(true)
|
||||||
SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(::getRouteProgress)
|
SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(::getRouteProgress)
|
||||||
SelectedSource.REMAINING_ROUTE -> streamRouteProgress(::getRemainingRouteProgress)
|
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) {
|
private suspend fun streamCadence(smoothed: Boolean) {
|
||||||
val cadenceFlow = karooSystem.streamDataFlow(if(smoothed) DataType.Type.SMOOTHED_3S_AVERAGE_CADENCE else DataType.Type.CADENCE)
|
val cadenceFlow = karooSystem.streamDataFlow(if(smoothed) DataType.Type.SMOOTHED_3S_AVERAGE_CADENCE else DataType.Type.CADENCE)
|
||||||
.map { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
.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"),
|
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)"),
|
||||||
|
GRADE("grade", "Grade"),
|
||||||
ROUTE_PROGRESS("route_progress", "Route Progress"),
|
ROUTE_PROGRESS("route_progress", "Route Progress"),
|
||||||
REMAINING_ROUTE("route_progress_remaining", "Route Remaining");
|
REMAINING_ROUTE("route_progress_remaining", "Route Remaining");
|
||||||
|
|
||||||
@ -160,6 +161,8 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
var customMaxPower by remember { mutableStateOf("") }
|
var customMaxPower by remember { mutableStateOf("") }
|
||||||
var customMinHr by remember { mutableStateOf("") }
|
var customMinHr by remember { mutableStateOf("") }
|
||||||
var customMaxHr 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 useCustomPowerRange by remember { mutableStateOf(false) }
|
||||||
var useCustomHrRange by remember { mutableStateOf(false) }
|
var useCustomHrRange by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@ -188,6 +191,8 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
maxPower = customMaxPower.toIntOrNull(),
|
maxPower = customMaxPower.toIntOrNull(),
|
||||||
minHr = customMinHr.toIntOrNull(),
|
minHr = customMinHr.toIntOrNull(),
|
||||||
maxHr = customMaxHr.toIntOrNull(),
|
maxHr = customMaxHr.toIntOrNull(),
|
||||||
|
minGradient = minGrade.toIntOrNull() ?: PowerbarSettings.defaultMinGradient,
|
||||||
|
maxGradient = maxGrade.toIntOrNull() ?: PowerbarSettings.defaultMaxGradient,
|
||||||
barBarSize = barBarSize,
|
barBarSize = barBarSize,
|
||||||
barFontSize = barFontSize,
|
barFontSize = barFontSize,
|
||||||
useCustomPowerRange = useCustomPowerRange,
|
useCustomPowerRange = useCustomPowerRange,
|
||||||
@ -243,6 +248,8 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
customMaxPower = settings.maxPower?.toString() ?: ""
|
customMaxPower = settings.maxPower?.toString() ?: ""
|
||||||
customMinHr = settings.minHr?.toString() ?: ""
|
customMinHr = settings.minHr?.toString() ?: ""
|
||||||
customMaxHr = settings.maxHr?.toString() ?: ""
|
customMaxHr = settings.maxHr?.toString() ?: ""
|
||||||
|
minGrade = settings.minGradient?.toString() ?: ""
|
||||||
|
maxGrade = settings.maxGradient?.toString() ?: ""
|
||||||
useCustomPowerRange = settings.useCustomPowerRange
|
useCustomPowerRange = settings.useCustomPowerRange
|
||||||
useCustomHrRange = settings.useCustomHrRange
|
useCustomHrRange = settings.useCustomHrRange
|
||||||
}
|
}
|
||||||
@ -344,7 +351,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.absolutePadding(right = 2.dp)
|
.absolutePadding(right = 2.dp)
|
||||||
.onFocusEvent(::updateFocus),
|
.onFocusEvent(::updateFocus),
|
||||||
onValueChange = { minSpeed = it },
|
onValueChange = { minSpeed = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("Min Speed") },
|
label = { Text("Min Speed") },
|
||||||
suffix = { Text(if (isImperial) "mph" else "kph") },
|
suffix = { Text(if (isImperial) "mph" else "kph") },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
@ -355,7 +362,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.absolutePadding(left = 2.dp)
|
.absolutePadding(left = 2.dp)
|
||||||
.onFocusEvent(::updateFocus),
|
.onFocusEvent(::updateFocus),
|
||||||
onValueChange = { maxSpeed = it },
|
onValueChange = { maxSpeed = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("Max Speed") },
|
label = { Text("Max Speed") },
|
||||||
suffix = { Text(if (isImperial) "mph" else "kph") },
|
suffix = { Text(if (isImperial) "mph" else "kph") },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
@ -380,7 +387,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.absolutePadding(right = 2.dp)
|
.absolutePadding(right = 2.dp)
|
||||||
.onFocusEvent(::updateFocus),
|
.onFocusEvent(::updateFocus),
|
||||||
onValueChange = { customMinPower = it },
|
onValueChange = { customMinPower = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("Min Power", fontSize = 12.sp) },
|
label = { Text("Min Power", fontSize = 12.sp) },
|
||||||
suffix = { Text("W") },
|
suffix = { Text("W") },
|
||||||
placeholder = { Text("$profileMinPower") },
|
placeholder = { Text("$profileMinPower") },
|
||||||
@ -392,7 +399,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.absolutePadding(left = 2.dp)
|
.absolutePadding(left = 2.dp)
|
||||||
.onFocusEvent(::updateFocus),
|
.onFocusEvent(::updateFocus),
|
||||||
onValueChange = { customMaxPower = it },
|
onValueChange = { customMaxPower = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("Max Power", fontSize = 12.sp) },
|
label = { Text("Max Power", fontSize = 12.sp) },
|
||||||
suffix = { Text("W") },
|
suffix = { Text("W") },
|
||||||
placeholder = { Text("$profileMaxPower") },
|
placeholder = { Text("$profileMaxPower") },
|
||||||
@ -419,7 +426,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.absolutePadding(right = 2.dp)
|
.absolutePadding(right = 2.dp)
|
||||||
.onFocusEvent(::updateFocus),
|
.onFocusEvent(::updateFocus),
|
||||||
onValueChange = { customMinHr = it },
|
onValueChange = { customMinHr = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("Min Hr") },
|
label = { Text("Min Hr") },
|
||||||
suffix = { Text("bpm") },
|
suffix = { Text("bpm") },
|
||||||
placeholder = { if(profileRestHr > 0) Text("$profileRestHr") else Unit },
|
placeholder = { if(profileRestHr > 0) Text("$profileRestHr") else Unit },
|
||||||
@ -431,7 +438,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.absolutePadding(left = 2.dp)
|
.absolutePadding(left = 2.dp)
|
||||||
.onFocusEvent(::updateFocus),
|
.onFocusEvent(::updateFocus),
|
||||||
onValueChange = { customMaxHr = it },
|
onValueChange = { customMaxHr = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("Max Hr") },
|
label = { Text("Max Hr") },
|
||||||
suffix = { Text("bpm") },
|
suffix = { Text("bpm") },
|
||||||
placeholder = { if(profileMaxHr > 0) Text("$profileMaxHr") else Unit },
|
placeholder = { if(profileMaxHr > 0) Text("$profileMaxHr") else Unit },
|
||||||
@ -450,7 +457,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.absolutePadding(right = 2.dp)
|
.absolutePadding(right = 2.dp)
|
||||||
.onFocusEvent(::updateFocus),
|
.onFocusEvent(::updateFocus),
|
||||||
onValueChange = { minCadence = it },
|
onValueChange = { minCadence = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("Min Cadence") },
|
label = { Text("Min Cadence") },
|
||||||
suffix = { Text("rpm") },
|
suffix = { Text("rpm") },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
@ -461,7 +468,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.absolutePadding(left = 2.dp)
|
.absolutePadding(left = 2.dp)
|
||||||
.onFocusEvent(::updateFocus),
|
.onFocusEvent(::updateFocus),
|
||||||
onValueChange = { maxCadence = it },
|
onValueChange = { maxCadence = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("Min Cadence") },
|
label = { Text("Min Cadence") },
|
||||||
suffix = { Text("rpm") },
|
suffix = { Text("rpm") },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
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) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Switch(checked = colorBasedOnZones, onCheckedChange = {
|
Switch(checked = colorBasedOnZones, onCheckedChange = {
|
||||||
colorBasedOnZones = it
|
colorBasedOnZones = it
|
||||||
|
|||||||
@ -12,4 +12,16 @@
|
|||||||
<color name="zone6">#FE581F</color>
|
<color name="zone6">#FE581F</color>
|
||||||
<color name="zone7">#D60404</color>
|
<color name="zone7">#D60404</color>
|
||||||
<color name="zone8">#B700A2</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>
|
</resources>
|
||||||
Loading…
x
Reference in New Issue
Block a user