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:
parent
304a984110
commit
97fadc742e
6
.github/workflows/android.yml
vendored
6
.github/workflows/android.yml
vendored
@ -45,6 +45,12 @@ jobs:
|
||||
- name: Build with Gradle
|
||||
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
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
||||
@ -42,6 +42,7 @@ class CustomProgressBar(private val view: CustomView,
|
||||
var showLabel: Boolean = true
|
||||
var barBackground: Boolean = false
|
||||
@ColorInt var progressColor: Int = 0xFF2b86e6.toInt()
|
||||
var drawMode: ProgressBarDrawMode = ProgressBarDrawMode.STANDARD
|
||||
|
||||
var fontSize = CustomProgressBarFontSize.MEDIUM
|
||||
set(value) {
|
||||
@ -152,15 +153,63 @@ class CustomProgressBar(private val view: CustomView,
|
||||
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
|
||||
// Calculate bar left and right positions based on draw mode
|
||||
val (barLeft, barRight) = when (drawMode) {
|
||||
ProgressBarDrawMode.STANDARD -> {
|
||||
// Standard left-to-right progress bar
|
||||
when (horizontalLocation) {
|
||||
HorizontalPowerbarLocation.LEFT -> Pair(0f, (halfWidth * p).toFloat())
|
||||
HorizontalPowerbarLocation.RIGHT -> Pair(fullWidth - (halfWidth * p).toFloat(), fullWidth)
|
||||
HorizontalPowerbarLocation.FULL -> Pair(0f, (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 barRight = when (horizontalLocation) {
|
||||
HorizontalPowerbarLocation.LEFT -> (halfWidth * p).toFloat()
|
||||
HorizontalPowerbarLocation.RIGHT -> fullWidth
|
||||
HorizontalPowerbarLocation.FULL -> (fullWidth * p).toFloat()
|
||||
}
|
||||
|
||||
val minTargetX = when (horizontalLocation) {
|
||||
@ -256,7 +305,44 @@ class CustomProgressBar(private val view: CustomView,
|
||||
|
||||
val textBounds = textPaint.measureText(label)
|
||||
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 fm = textPaint.fontMetrics
|
||||
@ -344,7 +430,44 @@ class CustomProgressBar(private val view: CustomView,
|
||||
|
||||
val textBounds = textPaint.measureText(label)
|
||||
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
|
||||
|
||||
// textDrawBaselineY calculation uses rect.top and barSize.barHeight.
|
||||
|
||||
@ -55,6 +55,11 @@ enum class HorizontalPowerbarLocation {
|
||||
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(
|
||||
private val context: Context,
|
||||
val powerbarLocation: PowerbarLocation = PowerbarLocation.BOTTOM,
|
||||
@ -183,6 +188,7 @@ class Window(
|
||||
SelectedSource.ROUTE_PROGRESS -> streamRouteProgress(SelectedSource.ROUTE_PROGRESS, ::getRouteProgress)
|
||||
SelectedSource.REMAINING_ROUTE -> streamRouteProgress(SelectedSource.REMAINING_ROUTE, ::getRemainingRouteProgress)
|
||||
SelectedSource.GRADE -> streamGrade()
|
||||
SelectedSource.POWER_BALANCE -> streamBalance()
|
||||
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(
|
||||
val progress: Double?,
|
||||
val label: String?,
|
||||
|
||||
@ -88,6 +88,7 @@ enum class SelectedSource(val id: String, val label: String) {
|
||||
CADENCE("cadence", "Cadence"),
|
||||
CADENCE_3S("cadence_3s", "Cadence (3 sec avg)"),
|
||||
GRADE("grade", "Grade"),
|
||||
POWER_BALANCE("power_balance", "Power Balance"),
|
||||
ROUTE_PROGRESS("route_progress", "Route Progress"),
|
||||
REMAINING_ROUTE("route_progress_remaining", "Route Remaining");
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user