fix #17: Adds optional custom range inputs for power, heart rate (#18)

* fix #17: Adds optional custom range inputs for power, heart rate data sources

* Update karoo-ext

* Fix display of overlay value if out of range

* Fix custom speed range
This commit is contained in:
timklge 2025-01-24 18:56:09 +01:00 committed by GitHub
parent 4bbf79ecb0
commit c7686e43df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 157 additions and 49 deletions

View File

@ -4,7 +4,9 @@
[![GitHub Downloads (specific asset, all releases)](https://img.shields.io/github/downloads/timklge/karoo-powerbar/app-release.apk)](https://github.com/timklge/karoo-powerbar/releases) [![GitHub Downloads (specific asset, all releases)](https://img.shields.io/github/downloads/timklge/karoo-powerbar/app-release.apk)](https://github.com/timklge/karoo-powerbar/releases)
![GitHub License](https://img.shields.io/github/license/timklge/karoo-powerbar) ![GitHub License](https://img.shields.io/github/license/timklge/karoo-powerbar)
Simple karoo extension that shows an overlay power bar at the edge of the screen. For Karoo 2 and Karoo 3 devices. Simple karoo extension that shows an overlay power bar at the edge of the screen, comparable to the
dedicated LEDs featured on Wahoo devices.
For Karoo 2 and Karoo 3 devices.
![Powerbar](powerbar0.png) ![Powerbar](powerbar0.png)
![Settings](powerbar1.png) ![Settings](powerbar1.png)

View File

@ -15,8 +15,8 @@ android {
applicationId = "de.timklge.karoopowerbar" applicationId = "de.timklge.karoopowerbar"
minSdk = 26 minSdk = 26
targetSdk = 33 targetSdk = 33
versionCode = 11 versionCode = 12
versionName = "1.3.2" versionName = "1.3.3"
} }
signingConfigs { signingConfigs {

View File

@ -3,9 +3,9 @@
"packageName": "de.timklge.karoopowerbar", "packageName": "de.timklge.karoopowerbar",
"iconUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/karoo-powerbar.png", "iconUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/karoo-powerbar.png",
"latestApkUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/app-release.apk", "latestApkUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/app-release.apk",
"latestVersion": "1.3.2", "latestVersion": "1.3.3",
"latestVersionCode": 11, "latestVersionCode": 12,
"developer": "timklge", "developer": "timklge",
"description": "Adds a colored power bar to the bottom of the screen", "description": "Adds a colored power bar to the bottom of the screen",
"releaseNotes": "Add size setting, cadence and speed data sources with custom ranges" "releaseNotes": "Adds option to set a custom range for power and heart rate bar"
} }

View File

@ -23,8 +23,7 @@ enum class CustomProgressBarSize(val id: String, val label: String, val fontSize
class CustomProgressBar @JvmOverloads constructor( class CustomProgressBar @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null context: Context, attrs: AttributeSet? = null
) : View(context, attrs) { ) : View(context, attrs) {
var progress: Double = 0.5 var progress: Double? = 0.5
var showValueIfNull: Boolean = false
var location: PowerbarLocation = PowerbarLocation.BOTTOM var location: PowerbarLocation = PowerbarLocation.BOTTOM
var label: String = "" var label: String = ""
var showLabel: Boolean = true var showLabel: Boolean = true
@ -99,13 +98,13 @@ class CustomProgressBar @JvmOverloads constructor(
val rect = RectF( val rect = RectF(
1f, 1f,
15f, 15f,
((canvas.width.toDouble() - 1f) * progress.coerceIn(0.0, 1.0)).toFloat(), ((canvas.width.toDouble() - 1f) * (progress ?: 0.0).coerceIn(0.0, 1.0)).toFloat(),
15f + size.barHeight 15f + size.barHeight
) )
canvas.drawRect(0f, 15f, canvas.width.toFloat(), 15f + size.barHeight, backgroundPaint) canvas.drawRect(0f, 15f, canvas.width.toFloat(), 15f + size.barHeight, backgroundPaint)
if (progress > 0.0 || showValueIfNull) { if (progress != null) {
canvas.drawRoundRect(rect, 2f, 2f, blurPaint) canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
canvas.drawRoundRect(rect, 2f, 2f, linePaint) canvas.drawRoundRect(rect, 2f, 2f, linePaint)
@ -135,13 +134,13 @@ class CustomProgressBar @JvmOverloads constructor(
val rect = RectF( val rect = RectF(
1f, 1f,
canvas.height.toFloat() - 1f - size.barHeight, canvas.height.toFloat() - 1f - size.barHeight,
((canvas.width.toDouble() - 1f) * progress.coerceIn(0.0, 1.0)).toFloat(), ((canvas.width.toDouble() - 1f) * (progress ?: 0.0).coerceIn(0.0, 1.0)).toFloat(),
canvas.height.toFloat() canvas.height.toFloat()
) )
canvas.drawRect(0f, canvas.height.toFloat() - size.barHeight, canvas.width.toFloat(), canvas.height.toFloat(), backgroundPaint) canvas.drawRect(0f, canvas.height.toFloat() - size.barHeight, canvas.width.toFloat(), canvas.height.toFloat(), backgroundPaint)
if (progress > 0.0 || showValueIfNull) { if (progress != null) {
canvas.drawRoundRect(rect, 2f, 2f, blurPaint) canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
canvas.drawRoundRect(rect, 2f, 2f, linePaint) canvas.drawRoundRect(rect, 2f, 2f, linePaint)

View File

@ -10,7 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class KarooPowerbarExtension : KarooExtension("karoo-powerbar", "1.3.2") { class KarooPowerbarExtension : KarooExtension("karoo-powerbar", "1.3.3") {
companion object { companion object {
const val TAG = "karoo-powerbar" const val TAG = "karoo-powerbar"

View File

@ -25,13 +25,16 @@ data class PowerbarSettings(
val minCadence: Int = defaultMinCadence, val maxCadence: Int = defaultMaxCadence, val minCadence: Int = defaultMinCadence, val maxCadence: Int = defaultMaxCadence,
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 minHr: Int? = null, val maxHr: Int? = null,
val useCustomHrRange: Boolean = false, val useCustomPowerRange: Boolean = false
){ ){
companion object { companion object {
val defaultSettings = Json.encodeToString(PowerbarSettings()) val defaultSettings = Json.encodeToString(PowerbarSettings())
val defaultMinSpeedMs = 0f const val defaultMinSpeedMs = 0f
val defaultMaxSpeedMs = 13.89f const val defaultMaxSpeedMs = 13.89f
val defaultMinCadence = 50 const val defaultMinCadence = 50
val defaultMaxCadence = 120 const val defaultMaxCadence = 120
} }
} }

View File

@ -69,7 +69,7 @@ class Window(
layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
rootView = layoutInflater.inflate(R.layout.popup_window, null) rootView = layoutInflater.inflate(R.layout.popup_window, null)
powerbar = rootView.findViewById(R.id.progressBar) powerbar = rootView.findViewById(R.id.progressBar)
powerbar.progress = 0.0 powerbar.progress = null
windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager
val displayMetrics = DisplayMetrics() val displayMetrics = DisplayMetrics()
@ -119,7 +119,7 @@ class Window(
} }
powerbar.progressColor = context.resources.getColor(R.color.zone7) powerbar.progressColor = context.resources.getColor(R.color.zone7)
powerbar.progress = 0.0 powerbar.progress = null
powerbar.location = powerbarLocation powerbar.location = powerbarLocation
powerbar.showLabel = showLabel powerbar.showLabel = showLabel
powerbar.size = powerbarSize powerbar.size = powerbarSize
@ -175,7 +175,6 @@ class Window(
val maxSpeed = streamData.settings?.maxSpeed ?: PowerbarSettings.defaultMaxSpeedMs val maxSpeed = streamData.settings?.maxSpeed ?: PowerbarSettings.defaultMaxSpeedMs
val progress = val progress =
remap(valueMetersPerSecond, minSpeed.toDouble(), maxSpeed.toDouble(), 0.0, 1.0) remap(valueMetersPerSecond, minSpeed.toDouble(), maxSpeed.toDouble(), 0.0, 1.0)
powerbar.showValueIfNull = valueMetersPerSecond != 0.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
@ -184,14 +183,13 @@ class Window(
} else { } else {
context.getColor(R.color.zone0) context.getColor(R.color.zone0)
} }
powerbar.progress = progress powerbar.progress = if (value > 0) progress else null
powerbar.label = "$value" powerbar.label = "$value"
Log.d(TAG, "Speed: $value min: $minSpeed max: $maxSpeed") Log.d(TAG, "Speed: $value min: $minSpeed max: $maxSpeed")
} else { } else {
powerbar.progressColor = context.getColor(R.color.zone0) powerbar.progressColor = context.getColor(R.color.zone0)
powerbar.progress = 0.0 powerbar.progress = null
powerbar.showValueIfNull = false
powerbar.label = "?" powerbar.label = "?"
Log.d(TAG, "Speed: Unavailable") Log.d(TAG, "Speed: Unavailable")
@ -223,20 +221,18 @@ class Window(
@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
powerbar.showValueIfNull = value != 0
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) { powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
context.getColor(zoneColorRes) context.getColor(zoneColorRes)
} else { } else {
context.getColor(R.color.zone0) context.getColor(R.color.zone0)
} }
powerbar.progress = progress powerbar.progress = if (value > 0) progress else null
powerbar.label = "$value" powerbar.label = "$value"
Log.d(TAG, "Cadence: $value min: $minCadence max: $maxCadence") Log.d(TAG, "Cadence: $value min: $minCadence max: $maxCadence")
} else { } else {
powerbar.progressColor = context.getColor(R.color.zone0) powerbar.progressColor = context.getColor(R.color.zone0)
powerbar.progress = 0.0 powerbar.progress = null
powerbar.showValueIfNull = false
powerbar.label = "?" powerbar.label = "?"
Log.d(TAG, "Cadence: Unavailable") Log.d(TAG, "Cadence: Unavailable")
@ -261,23 +257,24 @@ class Window(
val value = streamData.value?.roundToInt() val value = streamData.value?.roundToInt()
if (value != null) { if (value != null) {
val minHr = streamData.userProfile.restingHr val customMinHr = if (streamData.settings?.useCustomHrRange == true) streamData.settings.minHr else null
val maxHr = streamData.userProfile.maxHr val customMaxHr = if (streamData.settings?.useCustomHrRange == true) streamData.settings.maxHr else null
val progress = val minHr = customMinHr ?: streamData.userProfile.restingHr
remap(value.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) val maxHr = customMaxHr ?: streamData.userProfile.maxHr
val progress = remap(value.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)
} else { } else {
context.getColor(R.color.zone0) context.getColor(R.color.zone0)
} }
powerbar.progress = progress powerbar.progress = if (value > 0) progress else null
powerbar.label = "$value" powerbar.label = "$value"
Log.d(TAG, "Hr: $value min: $minHr max: $maxHr") Log.d(TAG, "Hr: $value min: $minHr max: $maxHr")
} else { } else {
powerbar.progressColor = context.getColor(R.color.zone0) powerbar.progressColor = context.getColor(R.color.zone0)
powerbar.progress = 0.0 powerbar.progress = null
powerbar.label = "?" powerbar.label = "?"
Log.d(TAG, "Hr: Unavailable") Log.d(TAG, "Hr: Unavailable")
@ -308,23 +305,24 @@ class Window(
val value = streamData.value?.roundToInt() val value = streamData.value?.roundToInt()
if (value != null) { if (value != null) {
val minPower = streamData.userProfile.powerZones.first().min val customMinPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.minPower else null
val maxPower = streamData.userProfile.powerZones.last().min + 50 val customMaxPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.maxPower else null
val progress = val minPower = customMinPower ?: streamData.userProfile.powerZones.first().min
remap(value.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) val maxPower = customMaxPower ?: (streamData.userProfile.powerZones.last().min + 50)
val progress = remap(value.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)
} else { } else {
context.getColor(R.color.zone0) context.getColor(R.color.zone0)
} }
powerbar.progress = progress powerbar.progress = if (value > 0) progress else null
powerbar.label = "${value}W" powerbar.label = "${value}W"
Log.d(TAG, "Power: $value min: $minPower max: $maxPower") Log.d(TAG, "Power: $value min: $minPower max: $maxPower")
} else { } else {
powerbar.progressColor = context.getColor(R.color.zone0) powerbar.progressColor = context.getColor(R.color.zone0)
powerbar.progress = 0.0 powerbar.progress = null
powerbar.label = "?" powerbar.label = "?"
Log.d(TAG, "Power: Unavailable") Log.d(TAG, "Power: Unavailable")

View File

@ -32,6 +32,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -41,6 +42,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import de.timklge.karoopowerbar.CustomProgressBarSize import de.timklge.karoopowerbar.CustomProgressBarSize
@ -65,7 +67,9 @@ enum class SelectedSource(val id: String, val label: String) {
SPEED("speed", "Speed"), SPEED("speed", "Speed"),
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)");
fun isPower() = this == POWER || this == POWER_3S || this == POWER_10S
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -93,10 +97,25 @@ fun MainScreen() {
var minSpeed by remember { mutableStateOf("0") } var minSpeed by remember { mutableStateOf("0") }
var maxSpeed by remember { mutableStateOf("0") } var maxSpeed by remember { mutableStateOf("0") }
var isImperial by remember { mutableStateOf(false) } var isImperial by remember { mutableStateOf(false) }
var customMinPower by remember { mutableStateOf("") }
var customMaxPower by remember { mutableStateOf("") }
var customMinHr by remember { mutableStateOf("") }
var customMaxHr by remember { mutableStateOf("") }
var useCustomPowerRange by remember { mutableStateOf(false) }
var useCustomHrRange by remember { mutableStateOf(false) }
var profileMaxHr by remember { mutableIntStateOf(0) }
var profileRestHr by remember { mutableIntStateOf(0) }
var profileMinPower by remember { mutableIntStateOf(0) }
var profileMaxPower by remember { mutableIntStateOf(0) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
karooSystem.streamUserProfile().distinctUntilChanged().collect { karooSystem.streamUserProfile().distinctUntilChanged().collect { profileData ->
isImperial = it.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL isImperial = profileData.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
profileMaxHr = profileData.maxHr
profileRestHr = profileData.restingHr
profileMinPower = profileData.powerZones.first().min
profileMaxPower = profileData.powerZones.last().min + 50
} }
} }
@ -118,6 +137,12 @@ fun MainScreen() {
isImperial = profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL isImperial = profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
minSpeed = (if(isImperial) settings.minSpeed * 2.23694f else settings.minSpeed * 3.6f).roundToInt().toString() minSpeed = (if(isImperial) settings.minSpeed * 2.23694f else settings.minSpeed * 3.6f).roundToInt().toString()
maxSpeed = (if(isImperial) settings.maxSpeed * 2.23694f else settings.maxSpeed * 3.6f).roundToInt().toString() maxSpeed = (if(isImperial) settings.maxSpeed * 2.23694f else settings.maxSpeed * 3.6f).roundToInt().toString()
customMinPower = settings.minPower?.toString() ?: ""
customMaxPower = settings.maxPower?.toString() ?: ""
customMinHr = settings.minHr?.toString() ?: ""
customMaxHr = settings.maxHr?.toString() ?: ""
useCustomPowerRange = settings.useCustomPowerRange
useCustomHrRange = settings.useCustomHrRange
} }
} }
@ -182,7 +207,9 @@ fun MainScreen() {
bottomSelectedSource == SelectedSource.SPEED || bottomSelectedSource == SelectedSource.SPEED_3S){ bottomSelectedSource == SelectedSource.SPEED || bottomSelectedSource == SelectedSource.SPEED_3S){
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
OutlinedTextField(value = minSpeed, modifier = Modifier.weight(1f).absolutePadding(right = 2.dp), OutlinedTextField(value = minSpeed, modifier = Modifier
.weight(1f)
.absolutePadding(right = 2.dp),
onValueChange = { minSpeed = it }, onValueChange = { minSpeed = it },
label = { Text("Min Speed") }, label = { Text("Min Speed") },
suffix = { Text(if (isImperial) "mph" else "kph") }, suffix = { Text(if (isImperial) "mph" else "kph") },
@ -190,7 +217,9 @@ fun MainScreen() {
singleLine = true singleLine = true
) )
OutlinedTextField(value = maxSpeed, modifier = Modifier.weight(1f).absolutePadding(left = 2.dp), OutlinedTextField(value = maxSpeed, modifier = Modifier
.weight(1f)
.absolutePadding(left = 2.dp),
onValueChange = { maxSpeed = it }, onValueChange = { maxSpeed = it },
label = { Text("Max Speed") }, label = { Text("Max Speed") },
suffix = { Text(if (isImperial) "mph" else "kph") }, suffix = { Text(if (isImperial) "mph" else "kph") },
@ -200,11 +229,81 @@ fun MainScreen() {
} }
} }
if (topSelectedSource.isPower() || bottomSelectedSource.isPower()){
Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = useCustomPowerRange, onCheckedChange = { useCustomPowerRange = it})
Spacer(modifier = Modifier.width(10.dp))
Text("Use custom power range")
}
if(useCustomPowerRange){
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
OutlinedTextField(value = customMinPower, modifier = Modifier
.weight(1f)
.absolutePadding(right = 2.dp),
onValueChange = { customMinPower = it },
label = { Text("Min Power", fontSize = 12.sp) },
suffix = { Text("W") },
placeholder = { Text("$profileMinPower") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true
)
OutlinedTextField(value = customMaxPower, modifier = Modifier
.weight(1f)
.absolutePadding(left = 2.dp),
onValueChange = { customMaxPower = it },
label = { Text("Max Power", fontSize = 12.sp) },
suffix = { Text("W") },
placeholder = { Text("$profileMaxPower") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true
)
}
}
}
if (topSelectedSource == SelectedSource.HEART_RATE || bottomSelectedSource == SelectedSource.HEART_RATE){
Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = useCustomHrRange, onCheckedChange = { useCustomHrRange = it})
Spacer(modifier = Modifier.width(10.dp))
Text("Use custom HR range")
}
if (useCustomHrRange){
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
OutlinedTextField(value = customMinHr, modifier = Modifier
.weight(1f)
.absolutePadding(right = 2.dp),
onValueChange = { customMinHr = it },
label = { Text("Min Hr") },
suffix = { Text("bpm") },
placeholder = { if(profileRestHr > 0) Text("$profileRestHr") else Unit },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true
)
OutlinedTextField(value = customMaxHr, modifier = Modifier
.weight(1f)
.absolutePadding(left = 2.dp),
onValueChange = { customMaxHr = it },
label = { Text("Max Hr") },
suffix = { Text("bpm") },
placeholder = { if(profileMaxHr > 0) Text("$profileMaxHr") else Unit },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true
)
}
}
}
if (bottomSelectedSource == SelectedSource.CADENCE || topSelectedSource == SelectedSource.CADENCE || if (bottomSelectedSource == SelectedSource.CADENCE || topSelectedSource == SelectedSource.CADENCE ||
bottomSelectedSource == SelectedSource.CADENCE_3S || topSelectedSource == SelectedSource.CADENCE_3S){ bottomSelectedSource == SelectedSource.CADENCE_3S || topSelectedSource == SelectedSource.CADENCE_3S){
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
OutlinedTextField(value = minCadence, modifier = Modifier.weight(1f).absolutePadding(right = 2.dp), OutlinedTextField(value = minCadence, modifier = Modifier
.weight(1f)
.absolutePadding(right = 2.dp),
onValueChange = { minCadence = it }, onValueChange = { minCadence = it },
label = { Text("Min Cadence") }, label = { Text("Min Cadence") },
suffix = { Text("rpm") }, suffix = { Text("rpm") },
@ -212,7 +311,9 @@ fun MainScreen() {
singleLine = true singleLine = true
) )
OutlinedTextField(value = maxCadence, modifier = Modifier.weight(1f).absolutePadding(left = 2.dp), OutlinedTextField(value = maxCadence, modifier = Modifier
.weight(1f)
.absolutePadding(left = 2.dp),
onValueChange = { maxCadence = it }, onValueChange = { maxCadence = it },
label = { Text("Min Cadence") }, label = { Text("Min Cadence") },
suffix = { Text("rpm") }, suffix = { Text("rpm") },
@ -253,7 +354,13 @@ fun MainScreen() {
minCadence = minCadence.toIntOrNull() ?: PowerbarSettings.defaultMinCadence, minCadence = minCadence.toIntOrNull() ?: PowerbarSettings.defaultMinCadence,
maxCadence = maxCadence.toIntOrNull() ?: PowerbarSettings.defaultMaxCadence, maxCadence = maxCadence.toIntOrNull() ?: PowerbarSettings.defaultMaxCadence,
minSpeed = minSpeedSetting, maxSpeed = maxSpeedSetting, minSpeed = minSpeedSetting, maxSpeed = maxSpeedSetting,
barSize = barSize minPower = customMinPower.toIntOrNull(),
maxPower = customMaxPower.toIntOrNull(),
minHr = customMinHr.toIntOrNull(),
maxHr = customMaxHr.toIntOrNull(),
barSize = barSize,
useCustomPowerRange = useCustomPowerRange,
useCustomHrRange = useCustomHrRange,
) )
coroutineScope.launch { coroutineScope.launch {

View File

@ -9,7 +9,6 @@ androidxLifecycle = "2.8.6"
androidxActivity = "1.9.3" androidxActivity = "1.9.3"
androidxComposeUi = "1.7.4" androidxComposeUi = "1.7.4"
androidxComposeMaterial = "1.3.0" androidxComposeMaterial = "1.3.0"
glance = "1.1.1"
kotlinxSerializationJson = "1.7.3" kotlinxSerializationJson = "1.7.3"
lifecycleRuntimeKtx = "2.8.7" lifecycleRuntimeKtx = "2.8.7"
navigationRuntimeKtx = "2.8.4" navigationRuntimeKtx = "2.8.4"
@ -25,7 +24,7 @@ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
color = { module = "com.maxkeppeler.sheets-compose-dialogs:color", version.ref = "color" } color = { module = "com.maxkeppeler.sheets-compose-dialogs:color", version.ref = "color" }
hammerhead-karoo-ext = { group = "io.hammerhead", name = "karoo-ext", version = "1.1.1" } hammerhead-karoo-ext = { group = "io.hammerhead", name = "karoo-ext", version = "1.1.2" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }