diff --git a/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBar.kt b/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBar.kt index 9d29b7e..7cb750d 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBar.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBar.kt @@ -8,15 +8,33 @@ import android.graphics.Paint import android.graphics.RectF import android.graphics.Typeface import android.util.AttributeSet +import android.util.Log import android.view.View import androidx.annotation.ColorInt import androidx.core.graphics.ColorUtils +import de.timklge.karoopowerbar.screens.SelectedSource -class CustomProgressBar @JvmOverloads constructor( +class CustomView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : View(context, attrs) { + var progressBars: Map? = 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 location: PowerbarLocation = PowerbarLocation.BOTTOM var label: String = "" var minTarget: Double? = null var maxTarget: Double? = null @@ -29,7 +47,7 @@ class CustomProgressBar @JvmOverloads constructor( set(value) { field = value textPaint.textSize = value.fontSize - invalidate() // Redraw to apply new font size + view.invalidate() // Redraw to apply new font size } var barSize = CustomProgressBarBarSize.MEDIUM @@ -45,7 +63,7 @@ class CustomProgressBar @JvmOverloads constructor( CustomProgressBarBarSize.MEDIUM -> 8f CustomProgressBarBarSize.LARGE -> 10f } - invalidate() // Redraw to apply new bar size + view.invalidate() // Redraw to apply new bar size } private val targetColor = 0xFF9933FF.toInt() @@ -120,9 +138,7 @@ class CustomProgressBar @JvmOverloads constructor( style = Paint.Style.STROKE } - override fun onDrawForeground(canvas: Canvas) { - super.onDrawForeground(canvas) - + fun onDrawForeground(canvas: Canvas) { // Determine if the current progress is within the target range val isTargetMet = progress != null && minTarget != null && maxTarget != null && progress!! >= minTarget!! && progress!! <= maxTarget!! @@ -132,25 +148,65 @@ class CustomProgressBar @JvmOverloads constructor( blurPaint.color = progressColor 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) { PowerbarLocation.TOP -> { val rect = RectF( - 1f, + barLeft, 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 ) // Draw bar components only if barSize is not NONE if (barSize != CustomProgressBarBarSize.NONE) { 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 if (minTarget != null && maxTarget != null) { - val minTargetX = (canvas.width * minTarget!!).toFloat() - val maxTargetX = (canvas.width * maxTarget!!).toFloat() canvas.drawRoundRect( minTargetX, 15f, @@ -172,8 +228,6 @@ class CustomProgressBar @JvmOverloads constructor( if (progress != null) { // Draw target zone stroke after progress bar, before label 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 canvas.drawRoundRect( minTargetX, @@ -188,7 +242,6 @@ class CustomProgressBar @JvmOverloads constructor( // Draw vertical target indicator line if target is present if (target != null) { - val targetX = (canvas.width * target!!).toFloat() targetIndicatorPaint.color = if (isTargetMet) Color.GREEN else Color.RED canvas.drawLine(targetX, 15f, targetX, 15f + barSize.barHeight, targetIndicatorPaint) } @@ -203,7 +256,7 @@ class CustomProgressBar @JvmOverloads constructor( val textBounds = textPaint.measureText(label) 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 fm = textPaint.fontMetrics @@ -226,9 +279,9 @@ class CustomProgressBar @JvmOverloads constructor( PowerbarLocation.BOTTOM -> { val rect = RectF( - 1f, + barLeft, 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() ) @@ -236,13 +289,11 @@ class CustomProgressBar @JvmOverloads constructor( if (barSize != CustomProgressBarBarSize.NONE) { if (barBackground){ // 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 if (minTarget != null && maxTarget != null) { - val minTargetX = (canvas.width * minTarget!!).toFloat() - val maxTargetX = (canvas.width * maxTarget!!).toFloat() canvas.drawRoundRect( minTargetX, canvas.height.toFloat() - barSize.barHeight, @@ -265,8 +316,6 @@ class CustomProgressBar @JvmOverloads constructor( if (progress != null) { // Draw target zone stroke after progress bar, before label 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 canvas.drawRoundRect( minTargetX, @@ -281,7 +330,6 @@ class CustomProgressBar @JvmOverloads constructor( // Draw vertical target indicator line if target is present if (target != null) { - val targetX = (canvas.width * target!!).toFloat() targetIndicatorPaint.color = if (isTargetMet) Color.GREEN else Color.RED 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 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 // 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() + } } diff --git a/app/src/main/kotlin/de/timklge/karoopowerbar/ForegroundService.kt b/app/src/main/kotlin/de/timklge/karoopowerbar/ForegroundService.kt index cd55528..a3470f8 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/ForegroundService.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/ForegroundService.kt @@ -52,19 +52,21 @@ class ForegroundService : Service() { windows.forEach { it.close() } windows.clear() - if (settings.source != SelectedSource.NONE && showBars) { - Window(this@ForegroundService, PowerbarLocation.BOTTOM, settings.showLabelOnBars, settings.barBackground, settings.barBarSize, settings.barFontSize).apply { - selectedSource = settings.source - windows.add(this) - open() + if (showBars){ + if (settings.bottomBarSource != SelectedSource.NONE || settings.bottomBarLeftSource != SelectedSource.NONE || settings.bottomBarRightSource != SelectedSource.NONE) { + 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) + open() + } } - } - if (settings.topBarSource != SelectedSource.NONE && showBars){ - Window(this@ForegroundService, PowerbarLocation.TOP, settings.showLabelOnBars, settings.barBackground, settings.barBarSize, settings.barFontSize).apply { - selectedSource = settings.topBarSource - open() - windows.add(this) + 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, + settings.splitTopBar, settings.topBarSource, settings.topBarLeftSource, settings.topBarRightSource).apply { + open() + windows.add(this) + } } } } diff --git a/app/src/main/kotlin/de/timklge/karoopowerbar/Settings.kt b/app/src/main/kotlin/de/timklge/karoopowerbar/Settings.kt index a6da74a..ae963ab 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/Settings.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/Settings.kt @@ -7,6 +7,7 @@ import de.timklge.karoopowerbar.screens.SelectedSource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -15,8 +16,16 @@ val settingsKey = stringPreferencesKey("settings") @Serializable data class PowerbarSettings( - val source: SelectedSource = SelectedSource.POWER, + @SerialName("source") val bottomBarSource: SelectedSource = SelectedSource.POWER, 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 showLabelOnBars: Boolean = true, val useZoneColors: Boolean = true, diff --git a/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt b/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt index bcc360c..5a128f0 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt @@ -50,6 +50,10 @@ enum class PowerbarLocation { TOP, BOTTOM } +enum class HorizontalPowerbarLocation { + FULL, LEFT, RIGHT +} + class Window( private val context: Context, val powerbarLocation: PowerbarLocation = PowerbarLocation.BOTTOM, @@ -57,6 +61,10 @@ class Window( val barBackground: Boolean, val powerbarBarSize: CustomProgressBarBarSize, val powerbarFontSize: CustomProgressBarFontSize, + val splitBars: Boolean, + val selectedSource: SelectedSource = SelectedSource.NONE, + val selectedLeftSource: SelectedSource = SelectedSource.NONE, + val selectedRightSource: SelectedSource = SelectedSource.NONE ) { companion object { val FIELD_TARGET_VALUE_ID = "FIELD_WORKOUT_TARGET_VALUE_ID"; @@ -69,9 +77,8 @@ class Window( private val windowManager: WindowManager private val layoutInflater: LayoutInflater - private val powerbar: CustomProgressBar - - var selectedSource: SelectedSource = SelectedSource.POWER + private val powerbars: MutableMap = mutableMapOf() + private val view: CustomView init { layoutParams = WindowManager.LayoutParams( @@ -84,8 +91,8 @@ class Window( layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater rootView = layoutInflater.inflate(R.layout.popup_window, null) - powerbar = rootView.findViewById(R.id.progressBar) - powerbar.progress = null + view = rootView.findViewById(R.id.customView) + view.progressBars = powerbars windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager val displayMetrics = DisplayMetrics() @@ -116,47 +123,68 @@ class Window( private val karooSystem: KarooSystemService = KarooSystemService(context) - private var serviceJob: Job? = null + private var serviceJobs: MutableSet = mutableSetOf() @SuppressLint("UnspecifiedRegisterReceiverFlag") suspend fun open() { - serviceJob = CoroutineScope(Dispatchers.Default).launch { - val filter = IntentFilter("de.timklge.HIDE_POWERBAR") - if (Build.VERSION.SDK_INT >= 33) { - context.registerReceiver(hideReceiver, filter, Context.RECEIVER_EXPORTED) - } else { - context.registerReceiver(hideReceiver, filter) - } + val filter = IntentFilter("de.timklge.HIDE_POWERBAR") + if (Build.VERSION.SDK_INT >= 33) { + context.registerReceiver(hideReceiver, filter, Context.RECEIVER_EXPORTED) + } else { + context.registerReceiver(hideReceiver, filter) + } - karooSystem.connect { connected -> - Log.i(TAG, "Karoo system service connected: $connected") - } + karooSystem.connect { 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.progress = null - powerbar.location = powerbarLocation powerbar.showLabel = showLabel powerbar.barBackground = barBackground powerbar.fontSize = powerbarFontSize powerbar.barSize = powerbarBarSize powerbar.invalidate() + } - Log.i(TAG, "Streaming $selectedSource") + Log.i(TAG, "Streaming $selectedSource") - when (selectedSource){ - SelectedSource.POWER -> streamPower(PowerStreamSmoothing.RAW) - SelectedSource.POWER_3S -> streamPower(PowerStreamSmoothing.SMOOTHED_3S) - SelectedSource.POWER_10S -> streamPower(PowerStreamSmoothing.SMOOTHED_10S) - SelectedSource.HEART_RATE -> streamHeartrate() - SelectedSource.SPEED -> streamSpeed(false) - SelectedSource.SPEED_3S -> streamSpeed(true) - SelectedSource.CADENCE -> streamCadence(false) - SelectedSource.CADENCE_3S -> streamCadence(true) - SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(::getRouteProgress) - SelectedSource.REMAINING_ROUTE -> streamRouteProgress(::getRemainingRouteProgress) - SelectedSource.GRADE -> streamGrade() - SelectedSource.NONE -> {} - } + 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){ + SelectedSource.POWER -> streamPower(SelectedSource.POWER, PowerStreamSmoothing.RAW) + SelectedSource.POWER_3S -> streamPower(SelectedSource.POWER_3S, PowerStreamSmoothing.SMOOTHED_3S) + SelectedSource.POWER_10S -> streamPower(SelectedSource.POWER_10S, PowerStreamSmoothing.SMOOTHED_10S) + SelectedSource.HEART_RATE -> streamHeartrate() + SelectedSource.SPEED -> streamSpeed(SelectedSource.SPEED, false) + SelectedSource.SPEED_3S -> streamSpeed(SelectedSource.SPEED_3S, true) + SelectedSource.CADENCE -> streamCadence(SelectedSource.CADENCE, false) + SelectedSource.CADENCE_3S -> streamCadence(SelectedSource.CADENCE_3S, true) + SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(SelectedSource.ROUTE_PROGRESS, ::getRouteProgress) + SelectedSource.REMAINING_ROUTE -> streamRouteProgress(SelectedSource.REMAINING_ROUTE, ::getRemainingRouteProgress) + SelectedSource.GRADE -> streamGrade() + SelectedSource.NONE -> {} + } + }) } try { @@ -195,7 +223,10 @@ class Window( 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( val userProfile: UserProfile, val distanceToDestination: Double?, @@ -238,14 +269,18 @@ class Window( val routeEndAt = lastKnownRouteLength?.plus((distanceToDestination ?: 0.0)) val barProgress = routeProgressProvider(userProfile, riddenDistance, routeEndAt, distanceToDestination) - powerbar.progressColor = context.getColor(R.color.zone0) - powerbar.progress = barProgress.progress - powerbar.label = barProgress.label - powerbar.invalidate() + val powerbarsWithRouteProgressSource = powerbars.values.filter { it.source == source } + + powerbarsWithRouteProgressSource.forEach { powerbar -> + powerbar.progressColor = context.getColor(R.color.zone0) + powerbar.progress = barProgress.progress + powerbar.label = barProgress.label + 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) .map { (it as? StreamState.Streaming)?.dataPoint?.singleValue } .distinctUntilChanged() @@ -263,30 +298,33 @@ class Window( else -> valueMetersPerSecond?.times(3.6) }?.roundToInt() - if (value != null && valueMetersPerSecond != null) { - val minSpeed = streamData.settings?.minSpeed ?: PowerbarSettings.defaultMinSpeedMs - val maxSpeed = streamData.settings?.maxSpeed ?: PowerbarSettings.defaultMaxSpeedMs - val progress = remap(valueMetersPerSecond, minSpeed.toDouble(), maxSpeed.toDouble(), 0.0, 1.0) ?: 0.0 + val powerbarsWithSpeedSource = powerbars.values.filter { it.source == source } + powerbarsWithSpeedSource.forEach { powerbar -> + if (value != null) { + val minSpeed = streamData.settings?.minSpeed ?: PowerbarSettings.defaultMinSpeedMs + val maxSpeed = streamData.settings?.maxSpeed ?: PowerbarSettings.defaultMaxSpeedMs + val progress = remap(valueMetersPerSecond, minSpeed.toDouble(), maxSpeed.toDouble(), 0.0, 1.0) ?: 0.0 - @ColorRes val zoneColorRes = Zone.entries[(progress * Zone.entries.size).roundToInt().coerceIn(0.. 0) progress else null + powerbar.label = "$value" + + Log.d(TAG, "Speed: $value min: $minSpeed max: $maxSpeed") } else { - context.getColor(R.color.zone0) + powerbar.progressColor = context.getColor(R.color.zone0) + powerbar.progress = null + powerbar.label = "?" + + Log.d(TAG, "Speed: Unavailable") } - powerbar.progress = if (value > 0) progress else null - powerbar.label = "$value" - - Log.d(TAG, "Speed: $value min: $minSpeed max: $maxSpeed") - } else { - powerbar.progressColor = context.getColor(R.color.zone0) - powerbar.progress = null - powerbar.label = "?" - - Log.d(TAG, "Speed: Unavailable") + powerbar.invalidate() } - powerbar.invalidate() } } @@ -297,6 +335,7 @@ class Window( in -Float.MAX_VALUE..<-7.5f -> R.color.eleDarkBlue // Dark blue in -7.5f..<-4.6f -> R.color.eleLightBlue // Light blue in -4.6f..<-2f -> R.color.eleWhite // White + in -2f..<2f -> R.color.eleGray // Gray in 2f..<4.6f -> R.color.eleDarkGreen // Dark green in 4.6f..<7.5f -> R.color.eleLightGreen // Light green in 7.5f..<12.5f -> R.color.eleYellow // Yellow @@ -321,27 +360,31 @@ class Window( }.distinctUntilChanged().throttle(1_000).collect { streamData -> val value = streamData.value - if (value != null) { - val minGradient = streamData.settings?.minGradient ?: PowerbarSettings.defaultMinGradient - val maxGradient = streamData.settings?.maxGradient ?: PowerbarSettings.defaultMaxGradient + val powerbarsWithGradeSource = powerbars.values.filter { it.source == SelectedSource.GRADE } - powerbar.progressColor = getInclineIndicatorColor(value.toFloat()) ?: context.getColor(R.color.zone0) - powerbar.progress = remap(value.toDouble(), minGradient.toDouble(), maxGradient.toDouble(), 0.0, 1.0) - powerbar.label = "${String.format(Locale.getDefault(), "%.1f", value)}%" + powerbarsWithGradeSource.forEach { powerbar -> + if (value != null) { + val minGradient = streamData.settings?.minGradient ?: PowerbarSettings.defaultMinGradient + val maxGradient = streamData.settings?.maxGradient ?: PowerbarSettings.defaultMaxGradient - Log.d(TAG, "Grade: $value") - } else { - powerbar.progressColor = context.getColor(R.color.zone0) - powerbar.progress = null - powerbar.label = "?" + powerbar.progressColor = getInclineIndicatorColor(value.toFloat()) ?: context.getColor(R.color.zone0) + powerbar.progress = remap(value.toDouble(), minGradient.toDouble(), maxGradient.toDouble(), 0.0, 1.0) + powerbar.label = "${String.format(Locale.getDefault(), "%.1f", value)}%" - Log.d(TAG, "Grade: Unavailable") + Log.d(TAG, "Grade: $value") + } else { + powerbar.progressColor = context.getColor(R.color.zone0) + powerbar.progress = null + powerbar.label = "?" + + Log.d(TAG, "Grade: Unavailable") + } + powerbar.invalidate() } - 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) .map { (it as? StreamState.Streaming)?.dataPoint?.singleValue } .distinctUntilChanged() @@ -357,35 +400,38 @@ class Window( StreamData(userProfile, speed, settings, cadenceTarget) }.distinctUntilChanged().throttle(1_000).collect { streamData -> val value = streamData.value?.roundToInt() + val powerbarsWithCadenceSource = powerbars.values.filter { it.source == source } - if (value != null) { - val minCadence = streamData.settings?.minCadence ?: PowerbarSettings.defaultMinCadence - val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence - val progress = remap(value.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) ?: 0.0 + powerbarsWithCadenceSource.forEach { powerbar -> + if (value != null) { + val minCadence = streamData.settings?.minCadence ?: PowerbarSettings.defaultMinCadence + val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence + val progress = remap(value.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) ?: 0.0 - powerbar.minTarget = remap(streamData.cadenceTarget?.values?.get(FIELD_TARGET_MIN_ID)?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) - powerbar.maxTarget = remap(streamData.cadenceTarget?.values?.get(FIELD_TARGET_MAX_ID)?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) - powerbar.target = remap(streamData.cadenceTarget?.values?.get(FIELD_TARGET_VALUE_ID)?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) + powerbar.minTarget = remap(streamData.cadenceTarget?.values?.get(FIELD_TARGET_MIN_ID)?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) + powerbar.maxTarget = remap(streamData.cadenceTarget?.values?.get(FIELD_TARGET_MAX_ID)?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) + powerbar.target = remap(streamData.cadenceTarget?.values?.get(FIELD_TARGET_VALUE_ID)?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) - @ColorRes val zoneColorRes = Zone.entries[(progress * Zone.entries.size).roundToInt().coerceIn(0.. 0) progress else null + powerbar.label = "$value" + + Log.d(TAG, "Cadence: $value min: $minCadence max: $maxCadence") } else { - context.getColor(R.color.zone0) + powerbar.progressColor = context.getColor(R.color.zone0) + powerbar.progress = null + powerbar.label = "?" + + Log.d(TAG, "Cadence: Unavailable") } - powerbar.progress = if (value > 0) progress else null - powerbar.label = "$value" - - Log.d(TAG, "Cadence: $value min: $minCadence max: $maxCadence") - } else { - powerbar.progressColor = context.getColor(R.color.zone0) - powerbar.progress = null - powerbar.label = "?" - - Log.d(TAG, "Cadence: Unavailable") + powerbar.invalidate() } - powerbar.invalidate() } } @@ -405,35 +451,38 @@ class Window( StreamData(userProfile, hr, settings, hrTarget) }.distinctUntilChanged().throttle(1_000).collect { streamData -> val value = streamData.value?.roundToInt() + val powerbarsWithHrSource = powerbars.values.filter { it.source == SelectedSource.HEART_RATE } - if (value != 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 minHr = customMinHr ?: streamData.userProfile.restingHr - val maxHr = customMaxHr ?: streamData.userProfile.maxHr - val progress = remap(value.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) + powerbarsWithHrSource.forEach { powerbar -> + if (value != 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 minHr = customMinHr ?: streamData.userProfile.restingHr + val maxHr = customMaxHr ?: streamData.userProfile.maxHr + val progress = remap(value.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) - powerbar.minTarget = remap(streamData.heartrateTarget?.values?.get(FIELD_TARGET_MIN_ID), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) - powerbar.maxTarget = remap(streamData.heartrateTarget?.values?.get(FIELD_TARGET_MAX_ID), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) - powerbar.target = remap(streamData.heartrateTarget?.values?.get(FIELD_TARGET_VALUE_ID), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) + powerbar.minTarget = remap(streamData.heartrateTarget?.values?.get(FIELD_TARGET_MIN_ID), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) + powerbar.maxTarget = remap(streamData.heartrateTarget?.values?.get(FIELD_TARGET_MAX_ID), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) + powerbar.target = remap(streamData.heartrateTarget?.values?.get(FIELD_TARGET_VALUE_ID), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) - powerbar.progressColor = if (streamData.settings?.useZoneColors == true) { - context.getColor(getZone(streamData.userProfile.heartRateZones, value)?.colorResource ?: R.color.zone7) + powerbar.progressColor = if (streamData.settings?.useZoneColors == true) { + context.getColor(getZone(streamData.userProfile.heartRateZones, value)?.colorResource ?: R.color.zone7) + } else { + context.getColor(R.color.zone0) + } + powerbar.progress = if (value > 0) progress else null + powerbar.label = "$value" + + Log.d(TAG, "Hr: $value min: $minHr max: $maxHr") } else { - context.getColor(R.color.zone0) + powerbar.progressColor = context.getColor(R.color.zone0) + powerbar.progress = null + powerbar.label = "?" + + Log.d(TAG, "Hr: Unavailable") } - powerbar.progress = if (value > 0) progress else null - powerbar.label = "$value" - - Log.d(TAG, "Hr: $value min: $minHr max: $maxHr") - } else { - powerbar.progressColor = context.getColor(R.color.zone0) - powerbar.progress = null - powerbar.label = "?" - - Log.d(TAG, "Hr: Unavailable") + powerbar.invalidate() } - powerbar.invalidate() } } @@ -443,7 +492,7 @@ class Window( 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) .map { (it as? StreamState.Streaming)?.dataPoint?.singleValue } .distinctUntilChanged() @@ -460,35 +509,38 @@ class Window( StreamData(userProfile, hr, settings, powerTarget) }.distinctUntilChanged().throttle(1_000).collect { streamData -> val value = streamData.value?.roundToInt() + val powerbarsWithPowerSource = powerbars.values.filter { it.source == source } - if (value != 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 minPower = customMinPower ?: streamData.userProfile.powerZones.first().min - val maxPower = customMaxPower ?: (streamData.userProfile.powerZones.last().min + 30) - val progress = remap(value.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) + powerbarsWithPowerSource.forEach { powerbar -> + if (value != 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 minPower = customMinPower ?: streamData.userProfile.powerZones.first().min + val maxPower = customMaxPower ?: (streamData.userProfile.powerZones.last().min + 30) + val progress = remap(value.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) - powerbar.minTarget = remap(streamData.powerTarget?.values?.get(FIELD_TARGET_MIN_ID), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) - powerbar.maxTarget = remap(streamData.powerTarget?.values?.get(FIELD_TARGET_MAX_ID), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) - powerbar.target = remap(streamData.powerTarget?.values?.get(FIELD_TARGET_VALUE_ID), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) + powerbar.minTarget = remap(streamData.powerTarget?.values?.get(FIELD_TARGET_MIN_ID), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) + powerbar.maxTarget = remap(streamData.powerTarget?.values?.get(FIELD_TARGET_MAX_ID), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) + powerbar.target = remap(streamData.powerTarget?.values?.get(FIELD_TARGET_VALUE_ID), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) - powerbar.progressColor = if (streamData.settings?.useZoneColors == true) { - context.getColor(getZone(streamData.userProfile.powerZones, value)?.colorResource ?: R.color.zone7) + powerbar.progressColor = if (streamData.settings?.useZoneColors == true) { + context.getColor(getZone(streamData.userProfile.powerZones, value)?.colorResource ?: R.color.zone7) + } else { + context.getColor(R.color.zone0) + } + powerbar.progress = if (value > 0) progress else null + powerbar.label = "${value}W" + + Log.d(TAG, "Power: $value min: $minPower max: $maxPower") } else { - context.getColor(R.color.zone0) + powerbar.progressColor = context.getColor(R.color.zone0) + powerbar.progress = null + powerbar.label = "?" + + Log.d(TAG, "Power: Unavailable") } - powerbar.progress = if (value > 0) progress else null - powerbar.label = "${value}W" - - Log.d(TAG, "Power: $value min: $minPower max: $maxPower") - } else { - powerbar.progressColor = context.getColor(R.color.zone0) - powerbar.progress = null - powerbar.label = "?" - - Log.d(TAG, "Power: Unavailable") + powerbar.invalidate() } - powerbar.invalidate() } } @@ -501,7 +553,9 @@ class Window( currentHideJob?.cancel() currentHideJob = null } - serviceJob?.cancel() + serviceJobs.forEach { job -> + job.cancel() + } (context.getSystemService(WINDOW_SERVICE) as WindowManager).removeView(rootView) rootView.invalidate() (rootView.parent as? ViewGroup)?.removeAllViews() diff --git a/app/src/main/kotlin/de/timklge/karoopowerbar/screens/MainScreen.kt b/app/src/main/kotlin/de/timklge/karoopowerbar/screens/MainScreen.kt index 4876d4d..a5d11c5 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/screens/MainScreen.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/screens/MainScreen.kt @@ -84,7 +84,7 @@ enum class SelectedSource(val id: String, val label: String) { POWER_3S("power_3s", "Power (3 sec avg)"), POWER_10S("power_10s", "Power (10 sec avg)"), SPEED("speed", "Speed"), - SPEED_3S("speed_3s", "Speed (3 sec avg"), + SPEED_3S("speed_3s", "Speed (3 sec avg)"), CADENCE("cadence", "Cadence"), CADENCE_3S("cadence_3s", "Cadence (3 sec avg)"), GRADE("grade", "Grade"), @@ -139,9 +139,22 @@ fun MainScreen(onFinish: () -> Unit) { var bottomSelectedSource by remember { mutableStateOf(SelectedSource.POWER) } 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 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 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 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, barBackground = barBackground, useZoneColors = colorBasedOnZones, @@ -231,8 +250,14 @@ fun MainScreen(onFinish: () -> Unit) { .combine(karooSystem.streamUserProfile()) { settings, profile -> settings to profile } .distinctUntilChanged() .collect { (settings, profile) -> - bottomSelectedSource = settings.source + bottomSelectedSource = settings.bottomBarSource topSelectedSource = settings.topBarSource + splitTopBar = settings.splitTopBar + splitBottomBar = settings.splitBottomBar + topSelectedSourceLeft = settings.topBarLeftSource + topSelectedSourceRight = settings.topBarRightSource + bottomSelectedSourceLeft = settings.bottomBarLeftSource + bottomSelectedSourceRight = settings.bottomBarRightSource onlyShowWhileRiding = settings.onlyShowWhileRiding showLabelOnBars = settings.showLabelOnBars colorBasedOnZones = settings.useZoneColors @@ -283,15 +308,66 @@ fun MainScreen(onFinish: () -> Unit) { .verticalScroll(rememberScrollState()) .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) { - FilledTonalButton(modifier = Modifier - .fillMaxWidth() - .height(60.dp), - onClick = { - topBarDialogVisible = true - }) { - Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp)) - Spacer(modifier = Modifier.width(5.dp)) - Text("Top Bar: ${topSelectedSource.label}", modifier = Modifier.weight(1.0f)) + 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 + .fillMaxWidth() + .height(60.dp), + onClick = { + topBarDialogVisible = true + }) { + Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp)) + Spacer(modifier = Modifier.width(5.dp)) + Text("Top Bar: ${topSelectedSource.label}", modifier = Modifier.weight(1.0f)) + } } if (topBarDialogVisible){ @@ -302,15 +378,66 @@ fun MainScreen(onFinish: () -> Unit) { }) } - FilledTonalButton(modifier = Modifier - .fillMaxWidth() - .height(60.dp), - onClick = { - bottomBarDialogVisible = true - }) { - Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp)) - Spacer(modifier = Modifier.width(5.dp)) - Text("Bottom Bar: ${bottomSelectedSource.label}", modifier = Modifier.weight(1.0f)) + 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 + .fillMaxWidth() + .height(60.dp), + onClick = { + bottomBarDialogVisible = true + }) { + Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp)) + Spacer(modifier = Modifier.width(5.dp)) + Text("Bottom Bar: ${bottomSelectedSource.label}", modifier = Modifier.weight(1.0f)) + } } if (bottomBarDialogVisible){ @@ -344,7 +471,10 @@ fun MainScreen(onFinish: () -> Unit) { } 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()) { 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) { Switch(checked = useCustomPowerRange, onCheckedChange = { 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) { Switch(checked = useCustomHrRange, onCheckedChange = { useCustomHrRange = it @@ -450,7 +586,10 @@ fun MainScreen(onFinish: () -> Unit) { } 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()) { 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) { OutlinedTextField(value = minGrade, modifier = Modifier .weight(1f) diff --git a/app/src/main/res/layout/popup_window.xml b/app/src/main/res/layout/popup_window.xml index d5ff363..c7ef216 100644 --- a/app/src/main/res/layout/popup_window.xml +++ b/app/src/main/res/layout/popup_window.xml @@ -4,8 +4,8 @@ android:layout_width="match_parent" android:layout_height="80dp"> - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 709b7c5..650f2a4 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -22,6 +22,7 @@ #f05a28 #b222a3 #ffffff + #9e9e9e #4fc3f7 #2D58AF \ No newline at end of file