* 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:
parent
4bbf79ecb0
commit
c7686e43df
@ -4,7 +4,9 @@
|
|||||||
[](https://github.com/timklge/karoo-powerbar/releases)
|
[](https://github.com/timklge/karoo-powerbar/releases)
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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" }
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user