Add power balance source with center outward drawing mode (#52)

* Add power balance source with center outward drawing mode

* Add apk archive step
This commit is contained in:
timklge 2025-08-09 18:20:31 +02:00 committed by GitHub
parent 304a984110
commit 97fadc742e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 192 additions and 11 deletions

View File

@ -45,6 +45,12 @@ jobs:
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew build run: ./gradlew build
- name: Archive APK
uses: actions/upload-artifact@v4
with:
name: app-release.apk
path: app/build/outputs/apk/release/app-release.apk
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2

View File

@ -42,6 +42,7 @@ class CustomProgressBar(private val view: CustomView,
var showLabel: Boolean = true var showLabel: Boolean = true
var barBackground: Boolean = false var barBackground: Boolean = false
@ColorInt var progressColor: Int = 0xFF2b86e6.toInt() @ColorInt var progressColor: Int = 0xFF2b86e6.toInt()
var drawMode: ProgressBarDrawMode = ProgressBarDrawMode.STANDARD
var fontSize = CustomProgressBarFontSize.MEDIUM var fontSize = CustomProgressBarFontSize.MEDIUM
set(value) { set(value) {
@ -152,15 +153,63 @@ class CustomProgressBar(private val view: CustomView,
val fullWidth = canvas.width.toFloat() val fullWidth = canvas.width.toFloat()
val halfWidth = fullWidth / 2f val halfWidth = fullWidth / 2f
val barLeft = when (horizontalLocation) { // Calculate bar left and right positions based on draw mode
HorizontalPowerbarLocation.LEFT -> 0f val (barLeft, barRight) = when (drawMode) {
HorizontalPowerbarLocation.RIGHT -> fullWidth - (halfWidth * p).toFloat() ProgressBarDrawMode.STANDARD -> {
HorizontalPowerbarLocation.FULL -> 0f // Standard left-to-right progress bar
} when (horizontalLocation) {
val barRight = when (horizontalLocation) { HorizontalPowerbarLocation.LEFT -> Pair(0f, (halfWidth * p).toFloat())
HorizontalPowerbarLocation.LEFT -> (halfWidth * p).toFloat() HorizontalPowerbarLocation.RIGHT -> Pair(fullWidth - (halfWidth * p).toFloat(), fullWidth)
HorizontalPowerbarLocation.RIGHT -> fullWidth HorizontalPowerbarLocation.FULL -> Pair(0f, (fullWidth * p).toFloat())
HorizontalPowerbarLocation.FULL -> (fullWidth * p).toFloat() }
}
ProgressBarDrawMode.CENTER_OUT -> {
// Center-outward progress bar: 0.5 = invisible, <0.5 = extend left, >0.5 = extend right
when (horizontalLocation) {
HorizontalPowerbarLocation.LEFT -> {
val centerPoint = halfWidth / 2f // Center of left half
when {
p == 0.5 -> Pair(centerPoint, centerPoint) // Invisible at 0.5
p < 0.5 -> {
val leftExtent = centerPoint * (0.5 - p) * 2.0 // Map 0.0-0.5 to full left extension
Pair((centerPoint - leftExtent).toFloat(), centerPoint)
}
else -> {
val rightExtent = centerPoint * (p - 0.5) * 2.0 // Map 0.5-1.0 to full right extension
Pair(centerPoint, (centerPoint + rightExtent).toFloat())
}
}
}
HorizontalPowerbarLocation.RIGHT -> {
val centerPoint = halfWidth + (halfWidth / 2f) // Center of right half
when {
p == 0.5 -> Pair(centerPoint, centerPoint) // Invisible at 0.5
p < 0.5 -> {
val leftExtent = (halfWidth / 2f) * (0.5 - p) * 2.0 // Map 0.0-0.5 to full left extension
Pair((centerPoint - leftExtent).toFloat(), centerPoint)
}
else -> {
val rightExtent = (halfWidth / 2f) * (p - 0.5) * 2.0 // Map 0.5-1.0 to full right extension
Pair(centerPoint, (centerPoint + rightExtent).toFloat())
}
}
}
HorizontalPowerbarLocation.FULL -> {
val centerPoint = halfWidth // Center of full width
when {
p == 0.5 -> Pair(centerPoint, centerPoint) // Invisible at 0.5
p < 0.5 -> {
val leftExtent = halfWidth * (0.5 - p) * 2.0 // Map 0.0-0.5 to full left extension
Pair((centerPoint - leftExtent).toFloat(), centerPoint)
}
else -> {
val rightExtent = halfWidth * (p - 0.5) * 2.0 // Map 0.5-1.0 to full right extension
Pair(centerPoint, (centerPoint + rightExtent).toFloat())
}
}
}
}
}
} }
val minTargetX = when (horizontalLocation) { val minTargetX = when (horizontalLocation) {
@ -256,7 +305,44 @@ class CustomProgressBar(private val view: CustomView,
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 = (if (horizontalLocation != HorizontalPowerbarLocation.RIGHT) rect.right - xOffset else rect.left - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
// Calculate label position based on draw mode
val x = when (drawMode) {
ProgressBarDrawMode.STANDARD -> {
// Original logic for standard mode
(if (horizontalLocation != HorizontalPowerbarLocation.RIGHT) rect.right - xOffset else rect.left - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
ProgressBarDrawMode.CENTER_OUT -> {
// For center outward mode, position label at the edge of the bar
when {
p == 0.5 -> {
// When bar is invisible (at center), position at center
when (horizontalLocation) {
HorizontalPowerbarLocation.LEFT -> {
val centerPoint = halfWidth / 2f
(centerPoint - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
HorizontalPowerbarLocation.RIGHT -> {
val centerPoint = halfWidth + (halfWidth / 2f)
(centerPoint - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
HorizontalPowerbarLocation.FULL -> {
val centerPoint = halfWidth
(centerPoint - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
}
}
p < 0.5 -> {
// Bar extends left from center, place label at left edge
(rect.left - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
else -> {
// Bar extends right from center, place label at right edge
(rect.right - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
}
}
}
val r = x + xOffset * 2 val r = x + xOffset * 2
val fm = textPaint.fontMetrics val fm = textPaint.fontMetrics
@ -344,7 +430,44 @@ class CustomProgressBar(private val view: CustomView,
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 = (if (horizontalLocation != HorizontalPowerbarLocation.RIGHT) rect.right - xOffset else rect.left - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
// Calculate label position based on draw mode
val x = when (drawMode) {
ProgressBarDrawMode.STANDARD -> {
// Original logic for standard mode
(if (horizontalLocation != HorizontalPowerbarLocation.RIGHT) rect.right - xOffset else rect.left - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
ProgressBarDrawMode.CENTER_OUT -> {
// For center outward mode, position label at the edge of the bar
when {
p == 0.5 -> {
// When bar is invisible (at center), position at center
when (horizontalLocation) {
HorizontalPowerbarLocation.LEFT -> {
val centerPoint = halfWidth / 2f
(centerPoint - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
HorizontalPowerbarLocation.RIGHT -> {
val centerPoint = halfWidth + (halfWidth / 2f)
(centerPoint - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
HorizontalPowerbarLocation.FULL -> {
val centerPoint = halfWidth
(centerPoint - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
}
}
p < 0.5 -> {
// Bar extends left from center, place label at left edge
(rect.left - xOffset).coerceIn(backgroundLeft..backgroundRight-xOffset*2f)
}
else -> {
// Bar extends right from center, place label at right edge
(rect.right - 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.

View File

@ -55,6 +55,11 @@ enum class HorizontalPowerbarLocation {
FULL, LEFT, RIGHT FULL, LEFT, RIGHT
} }
enum class ProgressBarDrawMode {
STANDARD, // Normal left-to-right progress
CENTER_OUT // Progress extends outward from center (0.5 = invisible, <0.5 = left, >0.5 = right)
}
class Window( class Window(
private val context: Context, private val context: Context,
val powerbarLocation: PowerbarLocation = PowerbarLocation.BOTTOM, val powerbarLocation: PowerbarLocation = PowerbarLocation.BOTTOM,
@ -183,6 +188,7 @@ class Window(
SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(SelectedSource.ROUTE_PROGRESS, ::getRouteProgress) SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(SelectedSource.ROUTE_PROGRESS, ::getRouteProgress)
SelectedSource.REMAINING_ROUTE -> streamRouteProgress(SelectedSource.REMAINING_ROUTE, ::getRemainingRouteProgress) SelectedSource.REMAINING_ROUTE -> streamRouteProgress(SelectedSource.REMAINING_ROUTE, ::getRemainingRouteProgress)
SelectedSource.GRADE -> streamGrade() SelectedSource.GRADE -> streamGrade()
SelectedSource.POWER_BALANCE -> streamBalance()
SelectedSource.NONE -> {} SelectedSource.NONE -> {}
} }
}) })
@ -199,6 +205,51 @@ class Window(
} }
} }
private suspend fun streamBalance() {
data class StreamData(val powerBalanceLeft: Double?, val power: Double?)
karooSystem.streamDataFlow(DataType.Type.PEDAL_POWER_BALANCE)
.map {
val values = (it as? StreamState.Streaming)?.dataPoint?.values
StreamData(values?.get(DataType.Field.PEDAL_POWER_BALANCE_LEFT), values?.get(DataType.Field.POWER))
}
.distinctUntilChanged()
.throttle(1_000).collect { streamData ->
val powerBalanceLeft = streamData.powerBalanceLeft
val powerbarsWithBalanceSource = powerbars.values.filter { it.source == SelectedSource.POWER_BALANCE }
powerbarsWithBalanceSource.forEach { powerbar ->
powerbar.drawMode = ProgressBarDrawMode.CENTER_OUT
if (streamData.powerBalanceLeft != null) {
val value = remap(1.0 - (powerBalanceLeft ?: 0.5).coerceIn(0.0, 1.0), 0.4, 0.6, 0.0, 1.0)
val percentLeft = ((powerBalanceLeft ?: 0.5) * 100).roundToInt()
val percentDiffTo50 = (percentLeft - 50).absoluteValue
@ColorRes val zoneColorRes = Zone.entries[percentDiffTo50.toInt().coerceIn(0, Zone.entries.size-1)].colorResource
powerbar.progressColor = context.getColor(zoneColorRes)
powerbar.progress = value
val percentRight = 100 - percentLeft
powerbar.label = "${percentLeft}-${percentRight}"
Log.d(TAG, "Balance: $powerBalanceLeft power: ${streamData.power}")
} else {
powerbar.progressColor = context.getColor(R.color.zone0)
powerbar.progress = null
powerbar.label = "?"
Log.d(TAG, "Balance: Unavailable")
}
powerbar.invalidate()
}
}
}
data class BarProgress( data class BarProgress(
val progress: Double?, val progress: Double?,
val label: String?, val label: String?,

View File

@ -88,6 +88,7 @@ enum class SelectedSource(val id: String, val label: String) {
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"),
POWER_BALANCE("power_balance", "Power Balance"),
ROUTE_PROGRESS("route_progress", "Route Progress"), ROUTE_PROGRESS("route_progress", "Route Progress"),
REMAINING_ROUTE("route_progress_remaining", "Route Remaining"); REMAINING_ROUTE("route_progress_remaining", "Route Remaining");