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
|
- 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
|
||||||
|
|||||||
@ -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) {
|
||||||
|
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) {
|
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.
|
||||||
|
|||||||
@ -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?,
|
||||||
|
|||||||
@ -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");
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user