diff --git a/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBar.kt b/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBar.kt index 2c5d459..99c60a9 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBar.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBar.kt @@ -11,14 +11,6 @@ import android.util.AttributeSet import android.view.View import androidx.annotation.ColorInt import androidx.core.graphics.ColorUtils -import kotlinx.serialization.Serializable - -@Serializable -enum class CustomProgressBarSize(val id: String, val label: String, val fontSize: Float, val barHeight: Float) { - SMALL("small", "Small", 35f, 10f), - MEDIUM("medium", "Medium", 40f, 15f), - LARGE("large", "Large", 60f, 25f), -} class CustomProgressBar @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -30,10 +22,17 @@ class CustomProgressBar @JvmOverloads constructor( var barBackground: Boolean = false @ColorInt var progressColor: Int = 0xFF2b86e6.toInt() - var size = CustomProgressBarSize.MEDIUM + var fontSize = CustomProgressBarFontSize.MEDIUM set(value) { field = value textPaint.textSize = value.fontSize + invalidate() // Redraw to apply new font size + } + + var barSize = CustomProgressBarBarSize.MEDIUM + set(value) { + field = value + invalidate() // Redraw to apply new bar size } private val linePaint = Paint().apply { @@ -81,7 +80,7 @@ class CustomProgressBar @JvmOverloads constructor( private val textPaint = Paint().apply { color = Color.WHITE strokeWidth = 3f - textSize = size.fontSize + textSize = fontSize.fontSize typeface = Typeface.create(Typeface.MONOSPACE, Typeface.BOLD); textAlign = Paint.Align.CENTER } @@ -100,78 +99,91 @@ class CustomProgressBar @JvmOverloads constructor( 1f, 15f, ((canvas.width.toDouble() - 1f) * (progress ?: 0.0).coerceIn(0.0, 1.0)).toFloat(), - 15f + size.barHeight + 15f + barSize.barHeight // barSize.barHeight will be 0f if NONE ) - if (barBackground){ - canvas.drawRect(0f, 15f, canvas.width.toFloat(), 15f + size.barHeight, backgroundPaint) + // 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) + } + + if (progress != null) { + canvas.drawRoundRect(rect, 2f, 2f, blurPaint) + canvas.drawRoundRect(rect, 2f, 2f, linePaint) + canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight) + } } + // Draw label (if progress is not null and showLabel is true) if (progress != null) { - canvas.drawRoundRect(rect, 2f, 2f, blurPaint) - canvas.drawRoundRect(rect, 2f, 2f, linePaint) - - canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight) - if (showLabel){ val textBounds = textPaint.measureText(label) val xOffset = (textBounds + 20).coerceAtLeast(10f) / 2f - val yOffset = when(size){ - CustomProgressBarSize.SMALL -> (size.fontSize - size.barHeight) / 2 + 2f - CustomProgressBarSize.MEDIUM, CustomProgressBarSize.LARGE -> (size.fontSize - size.barHeight) / 2 - } val x = (rect.right - xOffset).coerceIn(0f..canvas.width-xOffset*2f) - val y = rect.top - yOffset val r = x + xOffset * 2 - val b = rect.bottom + yOffset - canvas.drawRoundRect(x, y, r, b, 2f, 2f, textBackgroundPaint) - canvas.drawRoundRect(x, y, r, b, 2f, 2f, blurPaint) - canvas.drawRoundRect(x, y, r, b, 2f, 2f, lineStrokePaint) + val fm = textPaint.fontMetrics + // barCenterY calculation uses barSize.barHeight, which is 0f for NONE, + // correctly centering the label on the 15f line. + val barCenterY = rect.top + barSize.barHeight / 2f + val centeredTextBaselineY = barCenterY - (fm.ascent + fm.descent) / 2f + val calculatedTextBoxTop = centeredTextBaselineY + fm.ascent + val finalTextBoxTop = calculatedTextBoxTop.coerceAtLeast(0f) + val finalTextBaselineY = finalTextBoxTop - fm.ascent + val finalTextBoxBottom = finalTextBaselineY + fm.descent - canvas.drawText(label, x + xOffset, rect.top + size.barHeight + 6, textPaint) + canvas.drawRoundRect(x, finalTextBoxTop, r, finalTextBoxBottom, 2f, 2f, textBackgroundPaint) + canvas.drawRoundRect(x, finalTextBoxTop, r, finalTextBoxBottom, 2f, 2f, blurPaint) + canvas.drawRoundRect(x, finalTextBoxTop, r, finalTextBoxBottom, 2f, 2f, lineStrokePaint) + canvas.drawText(label, x + xOffset, finalTextBaselineY, textPaint) } } } PowerbarLocation.BOTTOM -> { val rect = RectF( 1f, - canvas.height.toFloat() - 1f - size.barHeight, + 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(), canvas.height.toFloat() ) - if (barBackground){ - canvas.drawRect(0f, canvas.height.toFloat() - size.barHeight, canvas.width.toFloat(), canvas.height.toFloat(), backgroundPaint) + // Draw bar components only if barSize is not NONE + 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) + } + + if (progress != null) { + canvas.drawRoundRect(rect, 2f, 2f, blurPaint) + canvas.drawRoundRect(rect, 2f, 2f, linePaint) + canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight) + } } + // Draw label (if progress is not null and showLabel is true) if (progress != null) { - canvas.drawRoundRect(rect, 2f, 2f, blurPaint) - canvas.drawRoundRect(rect, 2f, 2f, linePaint) - - canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight) - if (showLabel){ val textBounds = textPaint.measureText(label) val xOffset = (textBounds + 20).coerceAtLeast(10f) / 2f - val yOffset = when(size){ - CustomProgressBarSize.SMALL -> size.fontSize / 2 + 2f - CustomProgressBarSize.MEDIUM -> size.fontSize / 2 - CustomProgressBarSize.LARGE -> size.fontSize / 2 - 5f - } val x = (rect.right - xOffset).coerceIn(0f..canvas.width-xOffset*2f) - val y = (rect.top - yOffset) val r = x + xOffset * 2 - val b = rect.bottom + 5 - canvas.drawRoundRect(x, y, r, b, 2f, 2f, textBackgroundPaint) - canvas.drawRoundRect(x, y, r, b, 2f, 2f, blurPaint) - canvas.drawRoundRect(x, y, r, b, 2f, 2f, lineStrokePaint) + // textDrawBaselineY calculation uses rect.top and barSize.barHeight. + // If NONE, barSize.barHeight is 0f. rect.top becomes canvas.height - 1f. + // So, baseline is (canvas.height - 1f) + 0f - 1f = canvas.height - 2f. + val textDrawBaselineY = rect.top + barSize.barHeight - 1f + val yBox = textDrawBaselineY + textPaint.ascent() + val bBox = textDrawBaselineY + textPaint.descent() - canvas.drawText(label, x + xOffset, rect.top + size.barHeight - 1, textPaint) + canvas.drawRoundRect(x, yBox, r, bBox, 2f, 2f, textBackgroundPaint) + canvas.drawRoundRect(x, yBox, r, bBox, 2f, 2f, blurPaint) + canvas.drawRoundRect(x, yBox, r, bBox, 2f, 2f, lineStrokePaint) + canvas.drawText(label, x + xOffset, textDrawBaselineY, textPaint) } } } } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBarSize.kt b/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBarSize.kt new file mode 100644 index 0000000..c91f77d --- /dev/null +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/CustomProgressBarSize.kt @@ -0,0 +1,45 @@ +package de.timklge.karoopowerbar + +import kotlinx.serialization.Serializable + +@Serializable +enum class CustomProgressBarSize(val id: String, val label: String, val fontSize: Float, val barHeight: Float) { + SMALL("small", "Small", 35f, 10f), + MEDIUM("medium", "Medium", 40f, 15f), + LARGE("large", "Large", 60f, 25f), +} + +@Serializable +enum class CustomProgressBarFontSize(val id: String, val label: String, val fontSize: Float) { + SMALL("small", "Small", 35f), + MEDIUM("medium", "Medium", 40f), + LARGE("large", "Large", 60f); + + companion object { + fun fromSize(size: CustomProgressBarSize): CustomProgressBarFontSize { + return when (size) { + CustomProgressBarSize.SMALL -> SMALL + CustomProgressBarSize.MEDIUM -> MEDIUM + CustomProgressBarSize.LARGE -> LARGE + } + } + } +} + +@Serializable +enum class CustomProgressBarBarSize(val id: String, val label: String, val barHeight: Float) { + NONE("none", "None", 0f), + SMALL("small", "Small", 10f), + MEDIUM("medium", "Medium", 15f), + LARGE("large", "Large", 25f); + + companion object { + fun fromSize(size: CustomProgressBarSize): CustomProgressBarBarSize { + return when (size) { + CustomProgressBarSize.SMALL -> SMALL + CustomProgressBarSize.MEDIUM -> MEDIUM + CustomProgressBarSize.LARGE -> LARGE + } + } + } +} diff --git a/app/src/main/kotlin/de/timklge/karoopowerbar/ForegroundService.kt b/app/src/main/kotlin/de/timklge/karoopowerbar/ForegroundService.kt index 2b869bd..cd55528 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/ForegroundService.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/ForegroundService.kt @@ -53,7 +53,7 @@ class ForegroundService : Service() { windows.clear() if (settings.source != SelectedSource.NONE && showBars) { - Window(this@ForegroundService, PowerbarLocation.BOTTOM, settings.showLabelOnBars, settings.barBackground, settings.barSize).apply { + Window(this@ForegroundService, PowerbarLocation.BOTTOM, settings.showLabelOnBars, settings.barBackground, settings.barBarSize, settings.barFontSize).apply { selectedSource = settings.source windows.add(this) open() @@ -61,7 +61,7 @@ class ForegroundService : Service() { } if (settings.topBarSource != SelectedSource.NONE && showBars){ - Window(this@ForegroundService, PowerbarLocation.TOP, settings.showLabelOnBars, settings.barBackground, settings.barSize).apply { + Window(this@ForegroundService, PowerbarLocation.TOP, settings.showLabelOnBars, settings.barBackground, settings.barBarSize, settings.barFontSize).apply { selectedSource = settings.topBarSource 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 fd9523c..4acb37e 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/Settings.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/Settings.kt @@ -2,7 +2,6 @@ package de.timklge.karoopowerbar import android.content.Context import android.util.Log -import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import de.timklge.karoopowerbar.screens.SelectedSource import kotlinx.coroutines.flow.Flow @@ -23,6 +22,8 @@ data class PowerbarSettings( val useZoneColors: Boolean = true, val barBackground: Boolean = false, val barSize: CustomProgressBarSize = CustomProgressBarSize.MEDIUM, + val barFontSize: CustomProgressBarFontSize = CustomProgressBarFontSize.fromSize(barSize), + val barBarSize: CustomProgressBarBarSize = CustomProgressBarBarSize.fromSize(barSize), val minCadence: Int = defaultMinCadence, val maxCadence: Int = defaultMaxCadence, val minSpeed: Float = defaultMinSpeedMs, val maxSpeed: Float = defaultMaxSpeedMs, // 50 km/h in m/s diff --git a/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt b/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt index 93bc41e..f5d7e9b 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/Window.kt @@ -47,7 +47,8 @@ class Window( val powerbarLocation: PowerbarLocation = PowerbarLocation.BOTTOM, val showLabel: Boolean, val barBackground: Boolean, - val powerbarSize: CustomProgressBarSize + val powerbarBarSize: CustomProgressBarBarSize, + val powerbarFontSize: CustomProgressBarFontSize, ) { private val rootView: View private var layoutParams: WindowManager.LayoutParams? = null @@ -124,7 +125,8 @@ class Window( powerbar.location = powerbarLocation powerbar.showLabel = showLabel powerbar.barBackground = barBackground - powerbar.size = powerbarSize + powerbar.fontSize = powerbarFontSize + powerbar.barSize = powerbarBarSize powerbar.invalidate() Log.i(TAG, "Streaming $selectedSource") 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 1359af6..6e2f128 100644 --- a/app/src/main/kotlin/de/timklge/karoopowerbar/screens/MainScreen.kt +++ b/app/src/main/kotlin/de/timklge/karoopowerbar/screens/MainScreen.kt @@ -57,6 +57,8 @@ import androidx.compose.ui.window.Dialog import androidx.core.content.ContextCompat.startActivity import androidx.datastore.preferences.core.edit import androidx.lifecycle.compose.LifecycleResumeEffect +import de.timklge.karoopowerbar.CustomProgressBarBarSize +import de.timklge.karoopowerbar.CustomProgressBarFontSize import de.timklge.karoopowerbar.CustomProgressBarSize import de.timklge.karoopowerbar.KarooPowerbarExtension import de.timklge.karoopowerbar.PowerbarSettings @@ -144,7 +146,8 @@ fun MainScreen(onFinish: () -> Unit) { var onlyShowWhileRiding by remember { mutableStateOf(false) } var colorBasedOnZones by remember { mutableStateOf(false) } var showLabelOnBars by remember { mutableStateOf(true) } - var barSize by remember { mutableStateOf(CustomProgressBarSize.MEDIUM) } + var barBarSize by remember { mutableStateOf(CustomProgressBarBarSize.MEDIUM) } + var barFontSize by remember { mutableStateOf(CustomProgressBarFontSize.MEDIUM) } var barBackground by remember { mutableStateOf(false) } var minCadence by remember { mutableStateOf("0") } @@ -184,7 +187,8 @@ fun MainScreen(onFinish: () -> Unit) { maxPower = customMaxPower.toIntOrNull(), minHr = customMinHr.toIntOrNull(), maxHr = customMaxHr.toIntOrNull(), - barSize = barSize, + barBarSize = barBarSize, + barFontSize = barFontSize, useCustomPowerRange = useCustomPowerRange, useCustomHrRange = useCustomHrRange, ) @@ -226,7 +230,8 @@ fun MainScreen(onFinish: () -> Unit) { onlyShowWhileRiding = settings.onlyShowWhileRiding showLabelOnBars = settings.showLabelOnBars colorBasedOnZones = settings.useZoneColors - barSize = settings.barSize + barBarSize = settings.barBarSize + barFontSize = settings.barFontSize barBackground = settings.barBackground minCadence = settings.minCadence.toString() maxCadence = settings.maxCadence.toString() @@ -309,12 +314,23 @@ fun MainScreen(onFinish: () -> Unit) { } apply { - val dropdownOptions = CustomProgressBarSize.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) } - val dropdownInitialSelection by remember(barSize) { - mutableStateOf(dropdownOptions.find { option -> option.id == barSize.id }!!) + val dropdownOptions = CustomProgressBarBarSize.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) } + val dropdownInitialSelection by remember(barBarSize) { + mutableStateOf(dropdownOptions.find { option -> option.id == barBarSize.id }!!) } Dropdown(label = "Bar Size", options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption -> - barSize = CustomProgressBarSize.entries.find { unit -> unit.id == selectedOption.id }!! + barBarSize = CustomProgressBarBarSize.entries.find { unit -> unit.id == selectedOption.id }!! + coroutineScope.launch { updateSettings() } + } + } + + apply { + val dropdownOptions = CustomProgressBarFontSize.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) } + val dropdownInitialSelection by remember(barFontSize) { + mutableStateOf(dropdownOptions.find { option -> option.id == barFontSize.id }!!) + } + Dropdown(label = "Text Size", options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption -> + barFontSize = CustomProgressBarFontSize.entries.find { unit -> unit.id == selectedOption.id }!! coroutineScope.launch { updateSettings() } } }