parent
c202f20320
commit
31418b834d
@ -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<HorizontalPowerbarLocation, CustomProgressBar>? = 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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<HorizontalPowerbarLocation, CustomProgressBar> = 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<Job> = 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..<Zone.entries.size)].colorResource
|
||||
@ColorRes val zoneColorRes = Zone.entries[(progress * Zone.entries.size).roundToInt().coerceIn(0..<Zone.entries.size)].colorResource
|
||||
|
||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
||||
context.getColor(zoneColorRes)
|
||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
||||
context.getColor(zoneColorRes)
|
||||
} else {
|
||||
context.getColor(R.color.zone0)
|
||||
}
|
||||
powerbar.progress = if (value > 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..<Zone.entries.size)].colorResource
|
||||
@ColorRes val zoneColorRes = Zone.entries[(progress * Zone.entries.size).roundToInt().coerceIn(0..<Zone.entries.size)].colorResource
|
||||
|
||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
||||
context.getColor(zoneColorRes)
|
||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
||||
context.getColor(zoneColorRes)
|
||||
} else {
|
||||
context.getColor(R.color.zone0)
|
||||
}
|
||||
powerbar.progress = if (value > 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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp">
|
||||
|
||||
<de.timklge.karoopowerbar.CustomProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
<de.timklge.karoopowerbar.CustomView
|
||||
android:id="@+id/customView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
<color name="eleLightRed">#f05a28</color>
|
||||
<color name="elePurple">#b222a3</color>
|
||||
<color name="eleWhite">#ffffff</color>
|
||||
<color name="eleGray">#9e9e9e</color>
|
||||
<color name="eleLightBlue">#4fc3f7</color>
|
||||
<color name="eleDarkBlue">#2D58AF</color>
|
||||
</resources>
|
||||
Loading…
x
Reference in New Issue
Block a user