fix #44: Add option to split powerbars (#47)

This commit is contained in:
timklge 2025-07-13 17:02:35 +02:00 committed by GitHub
parent c202f20320
commit 31418b834d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 467 additions and 206 deletions

View File

@ -8,15 +8,33 @@ import android.graphics.Paint
import android.graphics.RectF import android.graphics.RectF
import android.graphics.Typeface import android.graphics.Typeface
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.View import android.view.View
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import de.timklge.karoopowerbar.screens.SelectedSource
class CustomProgressBar @JvmOverloads constructor( class CustomView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null context: Context, attrs: AttributeSet? = null
) : View(context, attrs) { ) : View(context, attrs) {
var progressBars: Map<HorizontalPowerbarLocation, CustomProgressBar>? = null
override fun onDrawForeground(canvas: Canvas) {
super.onDrawForeground(canvas)
// Draw all progress bars
progressBars?.values?.forEach { progressBar ->
Log.d(KarooPowerbarExtension.TAG, "Drawing progress bar for source: ${progressBar.source} - location: ${progressBar.location} - horizontalLocation: ${progressBar.horizontalLocation}")
progressBar.onDrawForeground(canvas)
}
}
}
class CustomProgressBar(private val view: CustomView,
val source: SelectedSource,
val location: PowerbarLocation,
val horizontalLocation: HorizontalPowerbarLocation) {
var progress: Double? = 0.5 var progress: Double? = 0.5
var location: PowerbarLocation = PowerbarLocation.BOTTOM
var label: String = "" var label: String = ""
var minTarget: Double? = null var minTarget: Double? = null
var maxTarget: Double? = null var maxTarget: Double? = null
@ -29,7 +47,7 @@ class CustomProgressBar @JvmOverloads constructor(
set(value) { set(value) {
field = value field = value
textPaint.textSize = value.fontSize textPaint.textSize = value.fontSize
invalidate() // Redraw to apply new font size view.invalidate() // Redraw to apply new font size
} }
var barSize = CustomProgressBarBarSize.MEDIUM var barSize = CustomProgressBarBarSize.MEDIUM
@ -45,7 +63,7 @@ class CustomProgressBar @JvmOverloads constructor(
CustomProgressBarBarSize.MEDIUM -> 8f CustomProgressBarBarSize.MEDIUM -> 8f
CustomProgressBarBarSize.LARGE -> 10f CustomProgressBarBarSize.LARGE -> 10f
} }
invalidate() // Redraw to apply new bar size view.invalidate() // Redraw to apply new bar size
} }
private val targetColor = 0xFF9933FF.toInt() private val targetColor = 0xFF9933FF.toInt()
@ -120,9 +138,7 @@ class CustomProgressBar @JvmOverloads constructor(
style = Paint.Style.STROKE style = Paint.Style.STROKE
} }
override fun onDrawForeground(canvas: Canvas) { fun onDrawForeground(canvas: Canvas) {
super.onDrawForeground(canvas)
// Determine if the current progress is within the target range // Determine if the current progress is within the target range
val isTargetMet = val isTargetMet =
progress != null && minTarget != null && maxTarget != null && progress!! >= minTarget!! && progress!! <= maxTarget!! progress != null && minTarget != null && maxTarget != null && progress!! >= minTarget!! && progress!! <= maxTarget!!
@ -132,25 +148,65 @@ class CustomProgressBar @JvmOverloads constructor(
blurPaint.color = progressColor blurPaint.color = progressColor
blurPaintHighlight.color = ColorUtils.blendARGB(progressColor, 0xFFFFFF, 0.5f) blurPaintHighlight.color = ColorUtils.blendARGB(progressColor, 0xFFFFFF, 0.5f)
val p = (progress ?: 0.0).coerceIn(0.0, 1.0)
val fullWidth = canvas.width.toFloat()
val halfWidth = fullWidth / 2f
val barLeft = when (horizontalLocation) {
HorizontalPowerbarLocation.LEFT -> 0f
HorizontalPowerbarLocation.RIGHT -> fullWidth - (halfWidth * p).toFloat()
HorizontalPowerbarLocation.FULL -> 0f
}
val barRight = when (horizontalLocation) {
HorizontalPowerbarLocation.LEFT -> (halfWidth * p).toFloat()
HorizontalPowerbarLocation.RIGHT -> fullWidth
HorizontalPowerbarLocation.FULL -> (fullWidth * p).toFloat()
}
val minTargetX = when (horizontalLocation) {
HorizontalPowerbarLocation.LEFT -> if (minTarget != null) (halfWidth * minTarget!!).toFloat() else 0f
HorizontalPowerbarLocation.RIGHT -> if (minTarget != null) halfWidth + (halfWidth * minTarget!!).toFloat() else 0f
HorizontalPowerbarLocation.FULL -> if (minTarget != null) (fullWidth * minTarget!!).toFloat() else 0f
}
val maxTargetX = when (horizontalLocation) {
HorizontalPowerbarLocation.LEFT -> if (maxTarget != null) (halfWidth * maxTarget!!).toFloat() else 0f
HorizontalPowerbarLocation.RIGHT -> if (maxTarget != null) halfWidth + (halfWidth * maxTarget!!).toFloat() else 0f
HorizontalPowerbarLocation.FULL -> if (maxTarget != null) (fullWidth * maxTarget!!).toFloat() else 0f
}
val targetX = when (horizontalLocation) {
HorizontalPowerbarLocation.LEFT -> if (target != null) (halfWidth * target!!).toFloat() else 0f
HorizontalPowerbarLocation.RIGHT -> if (target != null) halfWidth + (halfWidth * target!!).toFloat() else 0f
HorizontalPowerbarLocation.FULL -> if (target != null) (fullWidth * target!!).toFloat() else 0f
}
val backgroundLeft = when (horizontalLocation) {
HorizontalPowerbarLocation.LEFT -> 0f
HorizontalPowerbarLocation.RIGHT -> halfWidth
HorizontalPowerbarLocation.FULL -> 0f
}
val backgroundRight = when (horizontalLocation) {
HorizontalPowerbarLocation.LEFT -> halfWidth
HorizontalPowerbarLocation.RIGHT -> fullWidth
HorizontalPowerbarLocation.FULL -> fullWidth
}
when (location) { when (location) {
PowerbarLocation.TOP -> { PowerbarLocation.TOP -> {
val rect = RectF( val rect = RectF(
1f, barLeft,
15f, 15f,
((canvas.width.toDouble() - 1f) * (progress ?: 0.0).coerceIn(0.0, 1.0)).toFloat(), barRight,
15f + barSize.barHeight // barSize.barHeight will be 0f if NONE 15f + barSize.barHeight // barSize.barHeight will be 0f if NONE
) )
// Draw bar components only if barSize is not NONE // Draw bar components only if barSize is not NONE
if (barSize != CustomProgressBarBarSize.NONE) { if (barSize != CustomProgressBarBarSize.NONE) {
if (barBackground){ if (barBackground){
canvas.drawRect(0f, 15f, canvas.width.toFloat(), 15f + barSize.barHeight, backgroundPaint) canvas.drawRect(backgroundLeft, 15f, backgroundRight, 15f + barSize.barHeight, backgroundPaint)
} }
// Draw target zone fill behind the progress bar // Draw target zone fill behind the progress bar
if (minTarget != null && maxTarget != null) { if (minTarget != null && maxTarget != null) {
val minTargetX = (canvas.width * minTarget!!).toFloat()
val maxTargetX = (canvas.width * maxTarget!!).toFloat()
canvas.drawRoundRect( canvas.drawRoundRect(
minTargetX, minTargetX,
15f, 15f,
@ -172,8 +228,6 @@ class CustomProgressBar @JvmOverloads constructor(
if (progress != null) { if (progress != null) {
// Draw target zone stroke after progress bar, before label // Draw target zone stroke after progress bar, before label
if (minTarget != null && maxTarget != null) { if (minTarget != null && maxTarget != null) {
val minTargetX = (canvas.width * minTarget!!).toFloat()
val maxTargetX = (canvas.width * maxTarget!!).toFloat()
// Draw stroked rounded rectangle for the target zone // Draw stroked rounded rectangle for the target zone
canvas.drawRoundRect( canvas.drawRoundRect(
minTargetX, minTargetX,
@ -188,7 +242,6 @@ class CustomProgressBar @JvmOverloads constructor(
// Draw vertical target indicator line if target is present // Draw vertical target indicator line if target is present
if (target != null) { if (target != null) {
val targetX = (canvas.width * target!!).toFloat()
targetIndicatorPaint.color = if (isTargetMet) Color.GREEN else Color.RED targetIndicatorPaint.color = if (isTargetMet) Color.GREEN else Color.RED
canvas.drawLine(targetX, 15f, targetX, 15f + barSize.barHeight, targetIndicatorPaint) canvas.drawLine(targetX, 15f, targetX, 15f + barSize.barHeight, targetIndicatorPaint)
} }
@ -203,7 +256,7 @@ class CustomProgressBar @JvmOverloads constructor(
val textBounds = textPaint.measureText(label) val textBounds = textPaint.measureText(label)
val xOffset = (textBounds + 20).coerceAtLeast(10f) / 2f val xOffset = (textBounds + 20).coerceAtLeast(10f) / 2f
val x = (rect.right - xOffset).coerceIn(0f..canvas.width-xOffset*2f) val x = (if (horizontalLocation != HorizontalPowerbarLocation.RIGHT) rect.right - xOffset else rect.left - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
val r = x + xOffset * 2 val r = x + xOffset * 2
val fm = textPaint.fontMetrics val fm = textPaint.fontMetrics
@ -226,9 +279,9 @@ class CustomProgressBar @JvmOverloads constructor(
PowerbarLocation.BOTTOM -> { PowerbarLocation.BOTTOM -> {
val rect = RectF( val rect = RectF(
1f, barLeft,
canvas.height.toFloat() - 1f - barSize.barHeight, // barSize.barHeight will be 0f if NONE canvas.height.toFloat() - 1f - barSize.barHeight, // barSize.barHeight will be 0f if NONE
((canvas.width.toDouble() - 1f) * (progress ?: 0.0).coerceIn(0.0, 1.0)).toFloat(), barRight,
canvas.height.toFloat() canvas.height.toFloat()
) )
@ -236,13 +289,11 @@ class CustomProgressBar @JvmOverloads constructor(
if (barSize != CustomProgressBarBarSize.NONE) { if (barSize != CustomProgressBarBarSize.NONE) {
if (barBackground){ if (barBackground){
// Use barSize.barHeight for background top calculation // Use barSize.barHeight for background top calculation
canvas.drawRect(0f, canvas.height.toFloat() - barSize.barHeight, canvas.width.toFloat(), canvas.height.toFloat(), backgroundPaint) canvas.drawRect(backgroundLeft, canvas.height.toFloat() - barSize.barHeight, backgroundRight, canvas.height.toFloat(), backgroundPaint)
} }
// Draw target zone fill behind the progress bar // Draw target zone fill behind the progress bar
if (minTarget != null && maxTarget != null) { if (minTarget != null && maxTarget != null) {
val minTargetX = (canvas.width * minTarget!!).toFloat()
val maxTargetX = (canvas.width * maxTarget!!).toFloat()
canvas.drawRoundRect( canvas.drawRoundRect(
minTargetX, minTargetX,
canvas.height.toFloat() - barSize.barHeight, canvas.height.toFloat() - barSize.barHeight,
@ -265,8 +316,6 @@ class CustomProgressBar @JvmOverloads constructor(
if (progress != null) { if (progress != null) {
// Draw target zone stroke after progress bar, before label // Draw target zone stroke after progress bar, before label
if (minTarget != null && maxTarget != null) { if (minTarget != null && maxTarget != null) {
val minTargetX = (canvas.width * minTarget!!).toFloat()
val maxTargetX = (canvas.width * maxTarget!!).toFloat()
// Draw stroked rounded rectangle for the target zone // Draw stroked rounded rectangle for the target zone
canvas.drawRoundRect( canvas.drawRoundRect(
minTargetX, minTargetX,
@ -281,7 +330,6 @@ class CustomProgressBar @JvmOverloads constructor(
// Draw vertical target indicator line if target is present // Draw vertical target indicator line if target is present
if (target != null) { if (target != null) {
val targetX = (canvas.width * target!!).toFloat()
targetIndicatorPaint.color = if (isTargetMet) Color.GREEN else Color.RED targetIndicatorPaint.color = if (isTargetMet) Color.GREEN else Color.RED
canvas.drawLine(targetX, canvas.height.toFloat() - barSize.barHeight, targetX, canvas.height.toFloat(), targetIndicatorPaint) canvas.drawLine(targetX, canvas.height.toFloat() - barSize.barHeight, targetX, canvas.height.toFloat(), targetIndicatorPaint)
} }
@ -296,7 +344,7 @@ class CustomProgressBar @JvmOverloads constructor(
val textBounds = textPaint.measureText(label) val textBounds = textPaint.measureText(label)
val xOffset = (textBounds + 20).coerceAtLeast(10f) / 2f val xOffset = (textBounds + 20).coerceAtLeast(10f) / 2f
val x = (rect.right - xOffset).coerceIn(0f..canvas.width-xOffset*2f) val x = (if (horizontalLocation != HorizontalPowerbarLocation.RIGHT) rect.right - xOffset else rect.left - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
val r = x + xOffset * 2 val r = x + xOffset * 2
// textDrawBaselineY calculation uses rect.top and barSize.barHeight. // textDrawBaselineY calculation uses rect.top and barSize.barHeight.
@ -315,4 +363,9 @@ class CustomProgressBar @JvmOverloads constructor(
} }
} }
} }
fun invalidate() {
// Invalidate the view to trigger a redraw
view.invalidate()
}
} }

View File

@ -52,17 +52,18 @@ class ForegroundService : Service() {
windows.forEach { it.close() } windows.forEach { it.close() }
windows.clear() windows.clear()
if (settings.source != SelectedSource.NONE && showBars) { if (showBars){
Window(this@ForegroundService, PowerbarLocation.BOTTOM, settings.showLabelOnBars, settings.barBackground, settings.barBarSize, settings.barFontSize).apply { if (settings.bottomBarSource != SelectedSource.NONE || settings.bottomBarLeftSource != SelectedSource.NONE || settings.bottomBarRightSource != SelectedSource.NONE) {
selectedSource = settings.source Window(this@ForegroundService, PowerbarLocation.BOTTOM, settings.showLabelOnBars, settings.barBackground, settings.barBarSize, settings.barFontSize,
settings.splitBottomBar, settings.bottomBarSource, settings.bottomBarLeftSource, settings.bottomBarRightSource).apply {
windows.add(this) windows.add(this)
open() open()
} }
} }
if (settings.topBarSource != SelectedSource.NONE && showBars){ if (settings.topBarSource != SelectedSource.NONE || settings.topBarLeftSource != SelectedSource.NONE || settings.topBarRightSource != SelectedSource.NONE) {
Window(this@ForegroundService, PowerbarLocation.TOP, settings.showLabelOnBars, settings.barBackground, settings.barBarSize, settings.barFontSize).apply { Window(this@ForegroundService, PowerbarLocation.TOP, settings.showLabelOnBars, settings.barBackground, settings.barBarSize, settings.barFontSize,
selectedSource = settings.topBarSource settings.splitTopBar, settings.topBarSource, settings.topBarLeftSource, settings.topBarRightSource).apply {
open() open()
windows.add(this) windows.add(this)
} }
@ -70,6 +71,7 @@ class ForegroundService : Service() {
} }
} }
} }
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)

View File

@ -7,6 +7,7 @@ import de.timklge.karoopowerbar.screens.SelectedSource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -15,8 +16,16 @@ val settingsKey = stringPreferencesKey("settings")
@Serializable @Serializable
data class PowerbarSettings( data class PowerbarSettings(
val source: SelectedSource = SelectedSource.POWER, @SerialName("source") val bottomBarSource: SelectedSource = SelectedSource.POWER,
val topBarSource: SelectedSource = SelectedSource.NONE, val topBarSource: SelectedSource = SelectedSource.NONE,
val splitTopBar: Boolean = false,
val splitBottomBar: Boolean = false,
val topBarLeftSource: SelectedSource = SelectedSource.NONE,
val topBarRightSource: SelectedSource = SelectedSource.NONE,
val bottomBarLeftSource: SelectedSource = SelectedSource.POWER,
val bottomBarRightSource: SelectedSource = SelectedSource.NONE,
val onlyShowWhileRiding: Boolean = true, val onlyShowWhileRiding: Boolean = true,
val showLabelOnBars: Boolean = true, val showLabelOnBars: Boolean = true,
val useZoneColors: Boolean = true, val useZoneColors: Boolean = true,

View File

@ -50,6 +50,10 @@ enum class PowerbarLocation {
TOP, BOTTOM TOP, BOTTOM
} }
enum class HorizontalPowerbarLocation {
FULL, LEFT, RIGHT
}
class Window( class Window(
private val context: Context, private val context: Context,
val powerbarLocation: PowerbarLocation = PowerbarLocation.BOTTOM, val powerbarLocation: PowerbarLocation = PowerbarLocation.BOTTOM,
@ -57,6 +61,10 @@ class Window(
val barBackground: Boolean, val barBackground: Boolean,
val powerbarBarSize: CustomProgressBarBarSize, val powerbarBarSize: CustomProgressBarBarSize,
val powerbarFontSize: CustomProgressBarFontSize, val powerbarFontSize: CustomProgressBarFontSize,
val splitBars: Boolean,
val selectedSource: SelectedSource = SelectedSource.NONE,
val selectedLeftSource: SelectedSource = SelectedSource.NONE,
val selectedRightSource: SelectedSource = SelectedSource.NONE
) { ) {
companion object { companion object {
val FIELD_TARGET_VALUE_ID = "FIELD_WORKOUT_TARGET_VALUE_ID"; val FIELD_TARGET_VALUE_ID = "FIELD_WORKOUT_TARGET_VALUE_ID";
@ -69,9 +77,8 @@ class Window(
private val windowManager: WindowManager private val windowManager: WindowManager
private val layoutInflater: LayoutInflater private val layoutInflater: LayoutInflater
private val powerbar: CustomProgressBar private val powerbars: MutableMap<HorizontalPowerbarLocation, CustomProgressBar> = mutableMapOf()
private val view: CustomView
var selectedSource: SelectedSource = SelectedSource.POWER
init { init {
layoutParams = WindowManager.LayoutParams( layoutParams = WindowManager.LayoutParams(
@ -84,8 +91,8 @@ 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) view = rootView.findViewById(R.id.customView)
powerbar.progress = null view.progressBars = powerbars
windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager
val displayMetrics = DisplayMetrics() val displayMetrics = DisplayMetrics()
@ -116,11 +123,10 @@ class Window(
private val karooSystem: KarooSystemService = KarooSystemService(context) private val karooSystem: KarooSystemService = KarooSystemService(context)
private var serviceJob: Job? = null private var serviceJobs: MutableSet<Job> = mutableSetOf()
@SuppressLint("UnspecifiedRegisterReceiverFlag") @SuppressLint("UnspecifiedRegisterReceiverFlag")
suspend fun open() { suspend fun open() {
serviceJob = CoroutineScope(Dispatchers.Default).launch {
val filter = IntentFilter("de.timklge.HIDE_POWERBAR") val filter = IntentFilter("de.timklge.HIDE_POWERBAR")
if (Build.VERSION.SDK_INT >= 33) { if (Build.VERSION.SDK_INT >= 33) {
context.registerReceiver(hideReceiver, filter, Context.RECEIVER_EXPORTED) context.registerReceiver(hideReceiver, filter, Context.RECEIVER_EXPORTED)
@ -132,31 +138,53 @@ class Window(
Log.i(TAG, "Karoo system service connected: $connected") Log.i(TAG, "Karoo system service connected: $connected")
} }
powerbars.clear()
if (!splitBars) {
if (selectedSource != SelectedSource.NONE){
powerbars[HorizontalPowerbarLocation.FULL] = CustomProgressBar(view, selectedSource, powerbarLocation, HorizontalPowerbarLocation.FULL)
}
} else {
if (selectedLeftSource != SelectedSource.NONE) {
powerbars[HorizontalPowerbarLocation.LEFT] = CustomProgressBar(view, selectedLeftSource, powerbarLocation, HorizontalPowerbarLocation.LEFT)
}
if (selectedRightSource != SelectedSource.NONE) {
powerbars[HorizontalPowerbarLocation.RIGHT] = CustomProgressBar(view, selectedRightSource, powerbarLocation, HorizontalPowerbarLocation.RIGHT)
}
}
powerbars.values.forEach { powerbar ->
powerbar.progressColor = context.resources.getColor(R.color.zone7) powerbar.progressColor = context.resources.getColor(R.color.zone7)
powerbar.progress = null powerbar.progress = null
powerbar.location = powerbarLocation
powerbar.showLabel = showLabel powerbar.showLabel = showLabel
powerbar.barBackground = barBackground powerbar.barBackground = barBackground
powerbar.fontSize = powerbarFontSize powerbar.fontSize = powerbarFontSize
powerbar.barSize = powerbarBarSize powerbar.barSize = powerbarBarSize
powerbar.invalidate() powerbar.invalidate()
}
Log.i(TAG, "Streaming $selectedSource") Log.i(TAG, "Streaming $selectedSource")
val selectedSources = powerbars.values.map { it.source }.toSet()
selectedSources.forEach { selectedSource ->
serviceJobs.add( CoroutineScope(Dispatchers.IO).launch {
Log.i(TAG, "Starting stream for $selectedSource")
when (selectedSource){ when (selectedSource){
SelectedSource.POWER -> streamPower(PowerStreamSmoothing.RAW) SelectedSource.POWER -> streamPower(SelectedSource.POWER, PowerStreamSmoothing.RAW)
SelectedSource.POWER_3S -> streamPower(PowerStreamSmoothing.SMOOTHED_3S) SelectedSource.POWER_3S -> streamPower(SelectedSource.POWER_3S, PowerStreamSmoothing.SMOOTHED_3S)
SelectedSource.POWER_10S -> streamPower(PowerStreamSmoothing.SMOOTHED_10S) SelectedSource.POWER_10S -> streamPower(SelectedSource.POWER_10S, PowerStreamSmoothing.SMOOTHED_10S)
SelectedSource.HEART_RATE -> streamHeartrate() SelectedSource.HEART_RATE -> streamHeartrate()
SelectedSource.SPEED -> streamSpeed(false) SelectedSource.SPEED -> streamSpeed(SelectedSource.SPEED, false)
SelectedSource.SPEED_3S -> streamSpeed(true) SelectedSource.SPEED_3S -> streamSpeed(SelectedSource.SPEED_3S, true)
SelectedSource.CADENCE -> streamCadence(false) SelectedSource.CADENCE -> streamCadence(SelectedSource.CADENCE, false)
SelectedSource.CADENCE_3S -> streamCadence(true) SelectedSource.CADENCE_3S -> streamCadence(SelectedSource.CADENCE_3S, true)
SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(::getRouteProgress) SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(SelectedSource.ROUTE_PROGRESS, ::getRouteProgress)
SelectedSource.REMAINING_ROUTE -> streamRouteProgress(::getRemainingRouteProgress) SelectedSource.REMAINING_ROUTE -> streamRouteProgress(SelectedSource.REMAINING_ROUTE, ::getRemainingRouteProgress)
SelectedSource.GRADE -> streamGrade() SelectedSource.GRADE -> streamGrade()
SelectedSource.NONE -> {} SelectedSource.NONE -> {}
} }
})
} }
try { try {
@ -195,7 +223,10 @@ class Window(
return BarProgress(routeProgress, "$distanceToDestinationInUserUnit") return BarProgress(routeProgress, "$distanceToDestinationInUserUnit")
} }
private suspend fun streamRouteProgress(routeProgressProvider: (UserProfile, Double?, Double?, Double?) -> BarProgress) { private suspend fun streamRouteProgress(
source: SelectedSource,
routeProgressProvider: (UserProfile, Double?, Double?, Double?) -> BarProgress
) {
data class StreamData( data class StreamData(
val userProfile: UserProfile, val userProfile: UserProfile,
val distanceToDestination: Double?, val distanceToDestination: Double?,
@ -238,14 +269,18 @@ class Window(
val routeEndAt = lastKnownRouteLength?.plus((distanceToDestination ?: 0.0)) val routeEndAt = lastKnownRouteLength?.plus((distanceToDestination ?: 0.0))
val barProgress = routeProgressProvider(userProfile, riddenDistance, routeEndAt, distanceToDestination) val barProgress = routeProgressProvider(userProfile, riddenDistance, routeEndAt, distanceToDestination)
val powerbarsWithRouteProgressSource = powerbars.values.filter { it.source == source }
powerbarsWithRouteProgressSource.forEach { powerbar ->
powerbar.progressColor = context.getColor(R.color.zone0) powerbar.progressColor = context.getColor(R.color.zone0)
powerbar.progress = barProgress.progress powerbar.progress = barProgress.progress
powerbar.label = barProgress.label powerbar.label = barProgress.label
powerbar.invalidate() powerbar.invalidate()
} }
} }
}
private suspend fun streamSpeed(smoothed: Boolean) { private suspend fun streamSpeed(source: SelectedSource, smoothed: Boolean) {
val speedFlow = karooSystem.streamDataFlow(if(smoothed) DataType.Type.SMOOTHED_3S_AVERAGE_SPEED else DataType.Type.SPEED) val speedFlow = karooSystem.streamDataFlow(if(smoothed) DataType.Type.SMOOTHED_3S_AVERAGE_SPEED else DataType.Type.SPEED)
.map { (it as? StreamState.Streaming)?.dataPoint?.singleValue } .map { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
.distinctUntilChanged() .distinctUntilChanged()
@ -263,7 +298,9 @@ class Window(
else -> valueMetersPerSecond?.times(3.6) else -> valueMetersPerSecond?.times(3.6)
}?.roundToInt() }?.roundToInt()
if (value != null && valueMetersPerSecond != null) { val powerbarsWithSpeedSource = powerbars.values.filter { it.source == source }
powerbarsWithSpeedSource.forEach { powerbar ->
if (value != null) {
val minSpeed = streamData.settings?.minSpeed ?: PowerbarSettings.defaultMinSpeedMs val minSpeed = streamData.settings?.minSpeed ?: PowerbarSettings.defaultMinSpeedMs
val maxSpeed = streamData.settings?.maxSpeed ?: PowerbarSettings.defaultMaxSpeedMs val maxSpeed = streamData.settings?.maxSpeed ?: PowerbarSettings.defaultMaxSpeedMs
val progress = remap(valueMetersPerSecond, minSpeed.toDouble(), maxSpeed.toDouble(), 0.0, 1.0) ?: 0.0 val progress = remap(valueMetersPerSecond, minSpeed.toDouble(), maxSpeed.toDouble(), 0.0, 1.0) ?: 0.0
@ -289,6 +326,7 @@ class Window(
powerbar.invalidate() powerbar.invalidate()
} }
} }
}
private suspend fun streamGrade() { private suspend fun streamGrade() {
@ColorRes @ColorRes
@ -297,6 +335,7 @@ class Window(
in -Float.MAX_VALUE..<-7.5f -> R.color.eleDarkBlue // Dark blue in -Float.MAX_VALUE..<-7.5f -> R.color.eleDarkBlue // Dark blue
in -7.5f..<-4.6f -> R.color.eleLightBlue // Light blue in -7.5f..<-4.6f -> R.color.eleLightBlue // Light blue
in -4.6f..<-2f -> R.color.eleWhite // White in -4.6f..<-2f -> R.color.eleWhite // White
in -2f..<2f -> R.color.eleGray // Gray
in 2f..<4.6f -> R.color.eleDarkGreen // Dark green in 2f..<4.6f -> R.color.eleDarkGreen // Dark green
in 4.6f..<7.5f -> R.color.eleLightGreen // Light green in 4.6f..<7.5f -> R.color.eleLightGreen // Light green
in 7.5f..<12.5f -> R.color.eleYellow // Yellow in 7.5f..<12.5f -> R.color.eleYellow // Yellow
@ -321,6 +360,9 @@ class Window(
}.distinctUntilChanged().throttle(1_000).collect { streamData -> }.distinctUntilChanged().throttle(1_000).collect { streamData ->
val value = streamData.value val value = streamData.value
val powerbarsWithGradeSource = powerbars.values.filter { it.source == SelectedSource.GRADE }
powerbarsWithGradeSource.forEach { powerbar ->
if (value != null) { if (value != null) {
val minGradient = streamData.settings?.minGradient ?: PowerbarSettings.defaultMinGradient val minGradient = streamData.settings?.minGradient ?: PowerbarSettings.defaultMinGradient
val maxGradient = streamData.settings?.maxGradient ?: PowerbarSettings.defaultMaxGradient val maxGradient = streamData.settings?.maxGradient ?: PowerbarSettings.defaultMaxGradient
@ -340,8 +382,9 @@ class Window(
powerbar.invalidate() powerbar.invalidate()
} }
} }
}
private suspend fun streamCadence(smoothed: Boolean) { private suspend fun streamCadence(source: SelectedSource, 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 }
.distinctUntilChanged() .distinctUntilChanged()
@ -357,7 +400,9 @@ class Window(
StreamData(userProfile, speed, settings, cadenceTarget) StreamData(userProfile, speed, settings, cadenceTarget)
}.distinctUntilChanged().throttle(1_000).collect { streamData -> }.distinctUntilChanged().throttle(1_000).collect { streamData ->
val value = streamData.value?.roundToInt() val value = streamData.value?.roundToInt()
val powerbarsWithCadenceSource = powerbars.values.filter { it.source == source }
powerbarsWithCadenceSource.forEach { powerbar ->
if (value != null) { if (value != null) {
val minCadence = streamData.settings?.minCadence ?: PowerbarSettings.defaultMinCadence val minCadence = streamData.settings?.minCadence ?: PowerbarSettings.defaultMinCadence
val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence
@ -388,6 +433,7 @@ class Window(
powerbar.invalidate() powerbar.invalidate()
} }
} }
}
private suspend fun streamHeartrate() { private suspend fun streamHeartrate() {
val hrFlow = karooSystem.streamDataFlow(DataType.Type.HEART_RATE) val hrFlow = karooSystem.streamDataFlow(DataType.Type.HEART_RATE)
@ -405,7 +451,9 @@ class Window(
StreamData(userProfile, hr, settings, hrTarget) StreamData(userProfile, hr, settings, hrTarget)
}.distinctUntilChanged().throttle(1_000).collect { streamData -> }.distinctUntilChanged().throttle(1_000).collect { streamData ->
val value = streamData.value?.roundToInt() val value = streamData.value?.roundToInt()
val powerbarsWithHrSource = powerbars.values.filter { it.source == SelectedSource.HEART_RATE }
powerbarsWithHrSource.forEach { powerbar ->
if (value != null) { if (value != null) {
val customMinHr = if (streamData.settings?.useCustomHrRange == true) streamData.settings.minHr else null val customMinHr = if (streamData.settings?.useCustomHrRange == true) streamData.settings.minHr else null
val customMaxHr = if (streamData.settings?.useCustomHrRange == true) streamData.settings.maxHr else null val customMaxHr = if (streamData.settings?.useCustomHrRange == true) streamData.settings.maxHr else null
@ -436,6 +484,7 @@ class Window(
powerbar.invalidate() powerbar.invalidate()
} }
} }
}
enum class PowerStreamSmoothing(val dataTypeId: String){ enum class PowerStreamSmoothing(val dataTypeId: String){
RAW(DataType.Type.POWER), RAW(DataType.Type.POWER),
@ -443,7 +492,7 @@ class Window(
SMOOTHED_10S(DataType.Type.SMOOTHED_10S_AVERAGE_POWER), SMOOTHED_10S(DataType.Type.SMOOTHED_10S_AVERAGE_POWER),
} }
private suspend fun streamPower(smoothed: PowerStreamSmoothing) { private suspend fun streamPower(source: SelectedSource, smoothed: PowerStreamSmoothing) {
val powerFlow = karooSystem.streamDataFlow(smoothed.dataTypeId) val powerFlow = karooSystem.streamDataFlow(smoothed.dataTypeId)
.map { (it as? StreamState.Streaming)?.dataPoint?.singleValue } .map { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
.distinctUntilChanged() .distinctUntilChanged()
@ -460,7 +509,9 @@ class Window(
StreamData(userProfile, hr, settings, powerTarget) StreamData(userProfile, hr, settings, powerTarget)
}.distinctUntilChanged().throttle(1_000).collect { streamData -> }.distinctUntilChanged().throttle(1_000).collect { streamData ->
val value = streamData.value?.roundToInt() val value = streamData.value?.roundToInt()
val powerbarsWithPowerSource = powerbars.values.filter { it.source == source }
powerbarsWithPowerSource.forEach { powerbar ->
if (value != null) { if (value != null) {
val customMinPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.minPower else null val customMinPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.minPower else null
val customMaxPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.maxPower else null val customMaxPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.maxPower else null
@ -491,6 +542,7 @@ class Window(
powerbar.invalidate() powerbar.invalidate()
} }
} }
}
private var currentHideJob: Job? = null private var currentHideJob: Job? = null
@ -501,7 +553,9 @@ class Window(
currentHideJob?.cancel() currentHideJob?.cancel()
currentHideJob = null currentHideJob = null
} }
serviceJob?.cancel() serviceJobs.forEach { job ->
job.cancel()
}
(context.getSystemService(WINDOW_SERVICE) as WindowManager).removeView(rootView) (context.getSystemService(WINDOW_SERVICE) as WindowManager).removeView(rootView)
rootView.invalidate() rootView.invalidate()
(rootView.parent as? ViewGroup)?.removeAllViews() (rootView.parent as? ViewGroup)?.removeAllViews()

View File

@ -84,7 +84,7 @@ enum class SelectedSource(val id: String, val label: String) {
POWER_3S("power_3s", "Power (3 sec avg)"), POWER_3S("power_3s", "Power (3 sec avg)"),
POWER_10S("power_10s", "Power (10 sec avg)"), POWER_10S("power_10s", "Power (10 sec avg)"),
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)"),
GRADE("grade", "Grade"), GRADE("grade", "Grade"),
@ -139,9 +139,22 @@ fun MainScreen(onFinish: () -> Unit) {
var bottomSelectedSource by remember { mutableStateOf(SelectedSource.POWER) } var bottomSelectedSource by remember { mutableStateOf(SelectedSource.POWER) }
var topSelectedSource by remember { mutableStateOf(SelectedSource.NONE) } var topSelectedSource by remember { mutableStateOf(SelectedSource.NONE) }
var splitTopBar by remember { mutableStateOf(false) }
var splitBottomBar by remember { mutableStateOf(false) }
var topSelectedSourceLeft by remember { mutableStateOf(SelectedSource.NONE) }
var topSelectedSourceRight by remember { mutableStateOf(SelectedSource.NONE) }
var bottomSelectedSourceLeft by remember { mutableStateOf(SelectedSource.NONE) }
var bottomSelectedSourceRight by remember { mutableStateOf(SelectedSource.NONE) }
var bottomBarDialogVisible by remember { mutableStateOf(false) } var bottomBarDialogVisible by remember { mutableStateOf(false) }
var topBarDialogVisible by remember { mutableStateOf(false) } var topBarDialogVisible by remember { mutableStateOf(false) }
var topBarLeftDialogVisible by remember { mutableStateOf(false) }
var topBarRightDialogVisible by remember { mutableStateOf(false) }
var bottomBarLeftDialogVisible by remember { mutableStateOf(false) }
var bottomBarRightDialogVisible by remember { mutableStateOf(false) }
var showAlerts by remember { mutableStateOf(false) } var showAlerts by remember { mutableStateOf(false) }
var givenPermissions by remember { mutableStateOf(false) } var givenPermissions by remember { mutableStateOf(false) }
@ -180,7 +193,13 @@ fun MainScreen(onFinish: () -> Unit) {
val maxSpeedSetting = (maxSpeed.toIntOrNull()?.toFloat()?.div((if(isImperial) 2.23694f else 3.6f))) ?: PowerbarSettings.defaultMaxSpeedMs val maxSpeedSetting = (maxSpeed.toIntOrNull()?.toFloat()?.div((if(isImperial) 2.23694f else 3.6f))) ?: PowerbarSettings.defaultMaxSpeedMs
val newSettings = PowerbarSettings( val newSettings = PowerbarSettings(
source = bottomSelectedSource, topBarSource = topSelectedSource, bottomBarSource = bottomSelectedSource, topBarSource = topSelectedSource,
splitTopBar = splitTopBar,
splitBottomBar = splitBottomBar,
topBarLeftSource = topSelectedSourceLeft,
topBarRightSource = topSelectedSourceRight,
bottomBarLeftSource = bottomSelectedSourceLeft,
bottomBarRightSource = bottomSelectedSourceRight,
onlyShowWhileRiding = onlyShowWhileRiding, showLabelOnBars = showLabelOnBars, onlyShowWhileRiding = onlyShowWhileRiding, showLabelOnBars = showLabelOnBars,
barBackground = barBackground, barBackground = barBackground,
useZoneColors = colorBasedOnZones, useZoneColors = colorBasedOnZones,
@ -231,8 +250,14 @@ fun MainScreen(onFinish: () -> Unit) {
.combine(karooSystem.streamUserProfile()) { settings, profile -> settings to profile } .combine(karooSystem.streamUserProfile()) { settings, profile -> settings to profile }
.distinctUntilChanged() .distinctUntilChanged()
.collect { (settings, profile) -> .collect { (settings, profile) ->
bottomSelectedSource = settings.source bottomSelectedSource = settings.bottomBarSource
topSelectedSource = settings.topBarSource topSelectedSource = settings.topBarSource
splitTopBar = settings.splitTopBar
splitBottomBar = settings.splitBottomBar
topSelectedSourceLeft = settings.topBarLeftSource
topSelectedSourceRight = settings.topBarRightSource
bottomSelectedSourceLeft = settings.bottomBarLeftSource
bottomSelectedSourceRight = settings.bottomBarRightSource
onlyShowWhileRiding = settings.onlyShowWhileRiding onlyShowWhileRiding = settings.onlyShowWhileRiding
showLabelOnBars = settings.showLabelOnBars showLabelOnBars = settings.showLabelOnBars
colorBasedOnZones = settings.useZoneColors colorBasedOnZones = settings.useZoneColors
@ -283,6 +308,56 @@ fun MainScreen(onFinish: () -> Unit) {
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) { .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 10.dp)) {
Text("Top Bar", style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.weight(1f))
Text("Split")
Spacer(modifier = Modifier.width(10.dp))
Switch(checked = splitTopBar, onCheckedChange = {
splitTopBar = it
coroutineScope.launch { updateSettings() }
})
}
if (splitTopBar) {
FilledTonalButton(modifier = Modifier
.fillMaxWidth()
.height(60.dp),
onClick = {
topBarLeftDialogVisible = true
}) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp))
Text("Top Bar (Left): ${topSelectedSourceLeft.label}", modifier = Modifier.weight(1.0f))
}
if (topBarLeftDialogVisible){
BarSelectDialog(topSelectedSourceLeft, onHide = { topBarLeftDialogVisible = false }, onSelect = { selected ->
topSelectedSourceLeft = selected
coroutineScope.launch { updateSettings() }
topBarLeftDialogVisible = false
})
}
FilledTonalButton(modifier = Modifier
.fillMaxWidth()
.height(60.dp),
onClick = {
topBarRightDialogVisible = true
}) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp))
Text("Top Bar (Right): ${topSelectedSourceRight.label}", modifier = Modifier.weight(1.0f))
}
if (topBarRightDialogVisible){
BarSelectDialog(topSelectedSourceRight, onHide = { topBarRightDialogVisible = false }, onSelect = { selected ->
topSelectedSourceRight = selected
coroutineScope.launch { updateSettings() }
topBarRightDialogVisible = false
})
}
} else {
FilledTonalButton(modifier = Modifier FilledTonalButton(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(60.dp), .height(60.dp),
@ -293,6 +368,7 @@ fun MainScreen(onFinish: () -> Unit) {
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
Text("Top Bar: ${topSelectedSource.label}", modifier = Modifier.weight(1.0f)) Text("Top Bar: ${topSelectedSource.label}", modifier = Modifier.weight(1.0f))
} }
}
if (topBarDialogVisible){ if (topBarDialogVisible){
BarSelectDialog(topSelectedSource, onHide = { topBarDialogVisible = false }, onSelect = { selected -> BarSelectDialog(topSelectedSource, onHide = { topBarDialogVisible = false }, onSelect = { selected ->
@ -302,6 +378,56 @@ fun MainScreen(onFinish: () -> Unit) {
}) })
} }
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 10.dp)) {
Text("Bottom Bar", style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.weight(1f))
Text("Split")
Spacer(modifier = Modifier.width(10.dp))
Switch(checked = splitBottomBar, onCheckedChange = {
splitBottomBar = it
coroutineScope.launch { updateSettings() }
})
}
if (splitBottomBar) {
FilledTonalButton(modifier = Modifier
.fillMaxWidth()
.height(60.dp),
onClick = {
bottomBarLeftDialogVisible = true
}) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp))
Text("Bottom Bar (Left): ${bottomSelectedSourceLeft.label}", modifier = Modifier.weight(1.0f))
}
if (bottomBarLeftDialogVisible){
BarSelectDialog(bottomSelectedSourceLeft, onHide = { bottomBarLeftDialogVisible = false }, onSelect = { selected ->
bottomSelectedSourceLeft = selected
coroutineScope.launch { updateSettings() }
bottomBarLeftDialogVisible = false
})
}
FilledTonalButton(modifier = Modifier
.fillMaxWidth()
.height(60.dp),
onClick = {
bottomBarRightDialogVisible = true
}) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp))
Text("Bottom Bar (Right): ${bottomSelectedSourceRight.label}", modifier = Modifier.weight(1.0f))
}
if (bottomBarRightDialogVisible){
BarSelectDialog(bottomSelectedSourceRight, onHide = { bottomBarRightDialogVisible = false }, onSelect = { selected ->
bottomSelectedSourceRight = selected
coroutineScope.launch { updateSettings() }
bottomBarRightDialogVisible = false
})
}
} else {
FilledTonalButton(modifier = Modifier FilledTonalButton(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(60.dp), .height(60.dp),
@ -312,6 +438,7 @@ fun MainScreen(onFinish: () -> Unit) {
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
Text("Bottom Bar: ${bottomSelectedSource.label}", modifier = Modifier.weight(1.0f)) Text("Bottom Bar: ${bottomSelectedSource.label}", modifier = Modifier.weight(1.0f))
} }
}
if (bottomBarDialogVisible){ if (bottomBarDialogVisible){
BarSelectDialog(bottomSelectedSource, onHide = { bottomBarDialogVisible = false }, onSelect = { selected -> BarSelectDialog(bottomSelectedSource, onHide = { bottomBarDialogVisible = false }, onSelect = { selected ->
@ -344,7 +471,10 @@ fun MainScreen(onFinish: () -> Unit) {
} }
if (topSelectedSource == SelectedSource.SPEED || topSelectedSource == SelectedSource.SPEED_3S || if (topSelectedSource == SelectedSource.SPEED || topSelectedSource == SelectedSource.SPEED_3S ||
bottomSelectedSource == SelectedSource.SPEED || bottomSelectedSource == SelectedSource.SPEED_3S){ bottomSelectedSource == SelectedSource.SPEED || bottomSelectedSource == SelectedSource.SPEED_3S ||
(splitTopBar && (topSelectedSourceLeft == SelectedSource.SPEED || topSelectedSourceLeft == SelectedSource.SPEED_3S || topSelectedSourceRight == SelectedSource.SPEED || topSelectedSourceRight == SelectedSource.SPEED_3S)) ||
(splitBottomBar && (bottomSelectedSourceLeft == SelectedSource.SPEED || bottomSelectedSourceLeft == SelectedSource.SPEED_3S || bottomSelectedSourceRight == SelectedSource.SPEED || bottomSelectedSourceRight == SelectedSource.SPEED_3S))
){
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
OutlinedTextField(value = minSpeed, modifier = Modifier OutlinedTextField(value = minSpeed, modifier = Modifier
@ -371,7 +501,10 @@ fun MainScreen(onFinish: () -> Unit) {
} }
} }
if (topSelectedSource.isPower() || bottomSelectedSource.isPower()){ if (topSelectedSource.isPower() || bottomSelectedSource.isPower() ||
(splitTopBar && (topSelectedSourceLeft.isPower() || topSelectedSourceRight.isPower())) ||
(splitBottomBar && (bottomSelectedSourceLeft.isPower() || bottomSelectedSourceRight.isPower()))
){
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = useCustomPowerRange, onCheckedChange = { Switch(checked = useCustomPowerRange, onCheckedChange = {
useCustomPowerRange = it useCustomPowerRange = it
@ -410,7 +543,10 @@ fun MainScreen(onFinish: () -> Unit) {
} }
} }
if (topSelectedSource == SelectedSource.HEART_RATE || bottomSelectedSource == SelectedSource.HEART_RATE){ if (topSelectedSource == SelectedSource.HEART_RATE || bottomSelectedSource == SelectedSource.HEART_RATE ||
(splitTopBar && (topSelectedSourceLeft == SelectedSource.HEART_RATE || topSelectedSourceRight == SelectedSource.HEART_RATE)) ||
(splitBottomBar && (bottomSelectedSourceLeft == SelectedSource.HEART_RATE || bottomSelectedSourceRight == SelectedSource.HEART_RATE))
){
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = useCustomHrRange, onCheckedChange = { Switch(checked = useCustomHrRange, onCheckedChange = {
useCustomHrRange = it useCustomHrRange = it
@ -450,7 +586,10 @@ fun MainScreen(onFinish: () -> Unit) {
} }
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 ||
(splitTopBar && (topSelectedSourceLeft == SelectedSource.CADENCE || topSelectedSourceLeft == SelectedSource.CADENCE_3S || topSelectedSourceRight == SelectedSource.CADENCE || topSelectedSourceRight == SelectedSource.CADENCE_3S)) ||
(splitBottomBar && (bottomSelectedSourceLeft == SelectedSource.CADENCE || bottomSelectedSourceLeft == SelectedSource.CADENCE_3S || bottomSelectedSourceRight == SelectedSource.CADENCE || bottomSelectedSourceRight == SelectedSource.CADENCE_3S))
){
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
OutlinedTextField(value = minCadence, modifier = Modifier OutlinedTextField(value = minCadence, modifier = Modifier
@ -477,7 +616,10 @@ fun MainScreen(onFinish: () -> Unit) {
} }
} }
if (topSelectedSource == SelectedSource.GRADE || bottomSelectedSource == SelectedSource.GRADE){ if (topSelectedSource == SelectedSource.GRADE || bottomSelectedSource == SelectedSource.GRADE ||
(splitTopBar && (topSelectedSourceLeft == SelectedSource.GRADE || topSelectedSourceRight == SelectedSource.GRADE)) ||
(splitBottomBar && (bottomSelectedSourceLeft == SelectedSource.GRADE || bottomSelectedSourceRight == SelectedSource.GRADE))
){
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField(value = minGrade, modifier = Modifier OutlinedTextField(value = minGrade, modifier = Modifier
.weight(1f) .weight(1f)

View File

@ -4,8 +4,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="80dp"> android:layout_height="80dp">
<de.timklge.karoopowerbar.CustomProgressBar <de.timklge.karoopowerbar.CustomView
android:id="@+id/progressBar" android:id="@+id/customView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="80dp" android:layout_height="80dp"
android:layout_gravity="center" /> android:layout_gravity="center" />

View File

@ -22,6 +22,7 @@
<color name="eleLightRed">#f05a28</color> <color name="eleLightRed">#f05a28</color>
<color name="elePurple">#b222a3</color> <color name="elePurple">#b222a3</color>
<color name="eleWhite">#ffffff</color> <color name="eleWhite">#ffffff</color>
<color name="eleGray">#9e9e9e</color>
<color name="eleLightBlue">#4fc3f7</color> <color name="eleLightBlue">#4fc3f7</color>
<color name="eleDarkBlue">#2D58AF</color> <color name="eleDarkBlue">#2D58AF</color>
</resources> </resources>