Add settings for custom cadence, speed ranges (#16)
* Add settings for custom cadence, speed ranges * Remove unused imports
This commit is contained in:
parent
546f219d6d
commit
4bbf79ecb0
@ -15,8 +15,8 @@ android {
|
|||||||
applicationId = "de.timklge.karoopowerbar"
|
applicationId = "de.timklge.karoopowerbar"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
versionCode = 10
|
versionCode = 11
|
||||||
versionName = "1.3.1"
|
versionName = "1.3.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
"packageName": "de.timklge.karoopowerbar",
|
"packageName": "de.timklge.karoopowerbar",
|
||||||
"iconUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/karoo-powerbar.png",
|
"iconUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/karoo-powerbar.png",
|
||||||
"latestApkUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/app-release.apk",
|
"latestApkUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/app-release.apk",
|
||||||
"latestVersion": "1.3.1",
|
"latestVersion": "1.3.2",
|
||||||
"latestVersionCode": 10,
|
"latestVersionCode": 11,
|
||||||
"developer": "timklge",
|
"developer": "timklge",
|
||||||
"description": "Adds a colored power bar to the bottom of the screen",
|
"description": "Adds a colored power bar to the bottom of the screen",
|
||||||
"releaseNotes": "Add size setting, cadence and speed data sources"
|
"releaseNotes": "Add size setting, cadence and speed data sources with custom ranges"
|
||||||
}
|
}
|
||||||
@ -24,6 +24,7 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
context: Context, attrs: AttributeSet? = null
|
context: Context, attrs: AttributeSet? = null
|
||||||
) : View(context, attrs) {
|
) : View(context, attrs) {
|
||||||
var progress: Double = 0.5
|
var progress: Double = 0.5
|
||||||
|
var showValueIfNull: Boolean = false
|
||||||
var location: PowerbarLocation = PowerbarLocation.BOTTOM
|
var location: PowerbarLocation = PowerbarLocation.BOTTOM
|
||||||
var label: String = ""
|
var label: String = ""
|
||||||
var showLabel: Boolean = true
|
var showLabel: Boolean = true
|
||||||
@ -104,7 +105,7 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
|
|
||||||
canvas.drawRect(0f, 15f, canvas.width.toFloat(), 15f + size.barHeight, backgroundPaint)
|
canvas.drawRect(0f, 15f, canvas.width.toFloat(), 15f + size.barHeight, backgroundPaint)
|
||||||
|
|
||||||
if (progress > 0.0) {
|
if (progress > 0.0 || showValueIfNull) {
|
||||||
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
||||||
canvas.drawRoundRect(rect, 2f, 2f, linePaint)
|
canvas.drawRoundRect(rect, 2f, 2f, linePaint)
|
||||||
|
|
||||||
@ -140,7 +141,7 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
|
|
||||||
canvas.drawRect(0f, canvas.height.toFloat() - size.barHeight, canvas.width.toFloat(), canvas.height.toFloat(), backgroundPaint)
|
canvas.drawRect(0f, canvas.height.toFloat() - size.barHeight, canvas.width.toFloat(), canvas.height.toFloat(), backgroundPaint)
|
||||||
|
|
||||||
if (progress > 0.0) {
|
if (progress > 0.0 || showValueIfNull) {
|
||||||
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
||||||
canvas.drawRoundRect(rect, 2f, 2f, linePaint)
|
canvas.drawRoundRect(rect, 2f, 2f, linePaint)
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
package de.timklge.karoopowerbar
|
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 io.hammerhead.karooext.KarooSystemService
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
import io.hammerhead.karooext.models.OnStreamState
|
import io.hammerhead.karooext.models.OnStreamState
|
||||||
import io.hammerhead.karooext.models.RideState
|
import io.hammerhead.karooext.models.RideState
|
||||||
@ -14,49 +9,10 @@ import kotlinx.coroutines.channels.awaitClose
|
|||||||
import kotlinx.coroutines.channels.trySendBlocking
|
import kotlinx.coroutines.channels.trySendBlocking
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
val jsonWithUnknownKeys = Json { ignoreUnknownKeys = true }
|
val jsonWithUnknownKeys = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
val settingsKey = stringPreferencesKey("settings")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class PowerbarSettings(
|
|
||||||
val source: SelectedSource = SelectedSource.POWER,
|
|
||||||
val topBarSource: SelectedSource = SelectedSource.NONE,
|
|
||||||
val onlyShowWhileRiding: Boolean = true,
|
|
||||||
val showLabelOnBars: Boolean = true,
|
|
||||||
val useZoneColors: Boolean = true,
|
|
||||||
val barSize: CustomProgressBarSize = CustomProgressBarSize.MEDIUM
|
|
||||||
){
|
|
||||||
companion object {
|
|
||||||
val defaultSettings = Json.encodeToString(PowerbarSettings())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun saveSettings(context: Context, settings: PowerbarSettings) {
|
|
||||||
context.dataStore.edit { t ->
|
|
||||||
t[settingsKey] = Json.encodeToString(settings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.streamSettings(): Flow<PowerbarSettings> {
|
|
||||||
return dataStore.data.map { settingsJson ->
|
|
||||||
try {
|
|
||||||
jsonWithUnknownKeys.decodeFromString<PowerbarSettings>(
|
|
||||||
settingsJson[settingsKey] ?: PowerbarSettings.defaultSettings
|
|
||||||
)
|
|
||||||
} catch(e: Throwable){
|
|
||||||
Log.e(KarooPowerbarExtension.TAG, "Failed to read preferences", e)
|
|
||||||
jsonWithUnknownKeys.decodeFromString<PowerbarSettings>(PowerbarSettings.defaultSettings)
|
|
||||||
}
|
|
||||||
}.distinctUntilChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KarooSystemService.streamDataFlow(dataTypeId: String): Flow<StreamState> {
|
fun KarooSystemService.streamDataFlow(dataTypeId: String): Flow<StreamState> {
|
||||||
return callbackFlow {
|
return callbackFlow {
|
||||||
val listenerId = addConsumer(OnStreamState.StartStreaming(dataTypeId)) { event: OnStreamState ->
|
val listenerId = addConsumer(OnStreamState.StartStreaming(dataTypeId)) { event: OnStreamState ->
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class KarooPowerbarExtension : KarooExtension("karoo-powerbar", "1.3.1") {
|
class KarooPowerbarExtension : KarooExtension("karoo-powerbar", "1.3.2") {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "karoo-powerbar"
|
const val TAG = "karoo-powerbar"
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
package de.timklge.karoopowerbar
|
package de.timklge.karoopowerbar
|
||||||
|
|
||||||
import android.R
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
|||||||
55
app/src/main/kotlin/de/timklge/karoopowerbar/Settings.kt
Normal file
55
app/src/main/kotlin/de/timklge/karoopowerbar/Settings.kt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
val settingsKey = stringPreferencesKey("settings")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PowerbarSettings(
|
||||||
|
val source: SelectedSource = SelectedSource.POWER,
|
||||||
|
val topBarSource: SelectedSource = SelectedSource.NONE,
|
||||||
|
val onlyShowWhileRiding: Boolean = true,
|
||||||
|
val showLabelOnBars: Boolean = true,
|
||||||
|
val useZoneColors: Boolean = true,
|
||||||
|
val barSize: CustomProgressBarSize = CustomProgressBarSize.MEDIUM,
|
||||||
|
|
||||||
|
val minCadence: Int = defaultMinCadence, val maxCadence: Int = defaultMaxCadence,
|
||||||
|
val minSpeed: Float = defaultMinSpeedMs, val maxSpeed: Float = defaultMaxSpeedMs, // 50 km/h in m/s
|
||||||
|
){
|
||||||
|
companion object {
|
||||||
|
val defaultSettings = Json.encodeToString(PowerbarSettings())
|
||||||
|
val defaultMinSpeedMs = 0f
|
||||||
|
val defaultMaxSpeedMs = 13.89f
|
||||||
|
val defaultMinCadence = 50
|
||||||
|
val defaultMaxCadence = 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveSettings(context: Context, settings: PowerbarSettings) {
|
||||||
|
context.dataStore.edit { t ->
|
||||||
|
t[settingsKey] = Json.encodeToString(settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.streamSettings(): Flow<PowerbarSettings> {
|
||||||
|
return dataStore.data.map { settingsJson ->
|
||||||
|
try {
|
||||||
|
jsonWithUnknownKeys.decodeFromString<PowerbarSettings>(
|
||||||
|
settingsJson[settingsKey] ?: PowerbarSettings.defaultSettings
|
||||||
|
)
|
||||||
|
} catch(e: Throwable){
|
||||||
|
Log.e(KarooPowerbarExtension.TAG, "Failed to read preferences", e)
|
||||||
|
jsonWithUnknownKeys.decodeFromString<PowerbarSettings>(PowerbarSettings.defaultSettings)
|
||||||
|
}
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
}
|
||||||
@ -16,6 +16,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
import de.timklge.karoopowerbar.KarooPowerbarExtension.Companion.TAG
|
import de.timklge.karoopowerbar.KarooPowerbarExtension.Companion.TAG
|
||||||
import de.timklge.karoopowerbar.screens.SelectedSource
|
import de.timklge.karoopowerbar.screens.SelectedSource
|
||||||
import io.hammerhead.karooext.KarooSystemService
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
@ -150,28 +151,6 @@ class Window(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
val speedZones = listOf(
|
|
||||||
UserProfile.Zone(0, 9),
|
|
||||||
UserProfile.Zone(10, 19),
|
|
||||||
UserProfile.Zone(20, 24),
|
|
||||||
UserProfile.Zone(25, 29),
|
|
||||||
UserProfile.Zone(30, 34),
|
|
||||||
UserProfile.Zone(34, 39),
|
|
||||||
UserProfile.Zone(40, 44),
|
|
||||||
)
|
|
||||||
|
|
||||||
val cadenceZones = listOf(
|
|
||||||
UserProfile.Zone(0, 59),
|
|
||||||
UserProfile.Zone(60, 79),
|
|
||||||
UserProfile.Zone(80, 89),
|
|
||||||
UserProfile.Zone(90, 99),
|
|
||||||
UserProfile.Zone(100, 109),
|
|
||||||
UserProfile.Zone(110, 119),
|
|
||||||
UserProfile.Zone(120, 129),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun streamSpeed(smoothed: Boolean) {
|
private suspend fun streamSpeed(smoothed: Boolean) {
|
||||||
val speedFlow = karooSystem.streamDataFlow(if(smoothed) DataType.Type.SMOOTHED_3S_AVERAGE_SPEED else DataType.Type.SPEED)
|
val speedFlow = karooSystem.streamDataFlow(if(smoothed) DataType.Type.SMOOTHED_3S_AVERAGE_SPEED else DataType.Type.SPEED)
|
||||||
.map { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
.map { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||||
@ -185,20 +164,23 @@ class Window(
|
|||||||
.combine(settingsFlow) { streamData, settings -> streamData.copy(settings = settings) }
|
.combine(settingsFlow) { streamData, settings -> streamData.copy(settings = settings) }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.collect { streamData ->
|
.collect { streamData ->
|
||||||
val valueMetersPerSecond = streamData.value?.roundToInt()
|
val valueMetersPerSecond = streamData.value
|
||||||
val value = when (streamData.userProfile.preferredUnit.distance){
|
val value = when (streamData.userProfile.preferredUnit.distance){
|
||||||
UserProfile.PreferredUnit.UnitType.IMPERIAL -> valueMetersPerSecond?.times(2.23694)
|
UserProfile.PreferredUnit.UnitType.IMPERIAL -> valueMetersPerSecond?.times(2.23694)
|
||||||
else -> valueMetersPerSecond?.times(3.6)
|
else -> valueMetersPerSecond?.times(3.6)
|
||||||
}?.roundToInt()
|
}?.roundToInt()
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null && valueMetersPerSecond != null) {
|
||||||
val minSpeed = speedZones.first().min
|
val minSpeed = streamData.settings?.minSpeed ?: PowerbarSettings.defaultMinSpeedMs
|
||||||
val maxSpeed = speedZones.last().min + 5
|
val maxSpeed = streamData.settings?.maxSpeed ?: PowerbarSettings.defaultMaxSpeedMs
|
||||||
val progress =
|
val progress =
|
||||||
remap(value.toDouble(), minSpeed.toDouble(), maxSpeed.toDouble(), 0.0, 1.0)
|
remap(valueMetersPerSecond, minSpeed.toDouble(), maxSpeed.toDouble(), 0.0, 1.0)
|
||||||
|
powerbar.showValueIfNull = valueMetersPerSecond != 0.0
|
||||||
|
|
||||||
|
@ColorRes val zoneColorRes = Zone.entries[(progress * Zone.entries.size).roundToInt().coerceIn(0..<Zone.entries.size)].colorResource
|
||||||
|
|
||||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
||||||
context.getColor(getZone(speedZones, value)?.colorResource ?: R.color.zone7)
|
context.getColor(zoneColorRes)
|
||||||
} else {
|
} else {
|
||||||
context.getColor(R.color.zone0)
|
context.getColor(R.color.zone0)
|
||||||
}
|
}
|
||||||
@ -209,6 +191,7 @@ class Window(
|
|||||||
} else {
|
} else {
|
||||||
powerbar.progressColor = context.getColor(R.color.zone0)
|
powerbar.progressColor = context.getColor(R.color.zone0)
|
||||||
powerbar.progress = 0.0
|
powerbar.progress = 0.0
|
||||||
|
powerbar.showValueIfNull = false
|
||||||
powerbar.label = "?"
|
powerbar.label = "?"
|
||||||
|
|
||||||
Log.d(TAG, "Speed: Unavailable")
|
Log.d(TAG, "Speed: Unavailable")
|
||||||
@ -233,13 +216,16 @@ class Window(
|
|||||||
val value = streamData.value?.roundToInt()
|
val value = streamData.value?.roundToInt()
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
val minCadence = cadenceZones.first().min
|
val minCadence = streamData.settings?.minCadence ?: PowerbarSettings.defaultMinCadence
|
||||||
val maxCadence = cadenceZones.last().min + 5
|
val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence
|
||||||
val progress =
|
val progress =
|
||||||
remap(value.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0)
|
remap(value.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
|
||||||
|
|
||||||
|
powerbar.showValueIfNull = value != 0
|
||||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
||||||
context.getColor(getZone(cadenceZones, value)?.colorResource ?: R.color.zone7)
|
context.getColor(zoneColorRes)
|
||||||
} else {
|
} else {
|
||||||
context.getColor(R.color.zone0)
|
context.getColor(R.color.zone0)
|
||||||
}
|
}
|
||||||
@ -250,6 +236,7 @@ class Window(
|
|||||||
} else {
|
} else {
|
||||||
powerbar.progressColor = context.getColor(R.color.zone0)
|
powerbar.progressColor = context.getColor(R.color.zone0)
|
||||||
powerbar.progress = 0.0
|
powerbar.progress = 0.0
|
||||||
|
powerbar.showValueIfNull = false
|
||||||
powerbar.label = "?"
|
powerbar.label = "?"
|
||||||
|
|
||||||
Log.d(TAG, "Cadence: Unavailable")
|
Log.d(TAG, "Cadence: Unavailable")
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import androidx.compose.material3.MenuAnchorType
|
|||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|||||||
@ -7,12 +7,14 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.absolutePadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Build
|
import androidx.compose.material.icons.filled.Build
|
||||||
@ -23,6 +25,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
@ -36,6 +39,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||||
@ -43,20 +47,25 @@ import de.timklge.karoopowerbar.CustomProgressBarSize
|
|||||||
import de.timklge.karoopowerbar.PowerbarSettings
|
import de.timklge.karoopowerbar.PowerbarSettings
|
||||||
import de.timklge.karoopowerbar.saveSettings
|
import de.timklge.karoopowerbar.saveSettings
|
||||||
import de.timklge.karoopowerbar.streamSettings
|
import de.timklge.karoopowerbar.streamSettings
|
||||||
|
import de.timklge.karoopowerbar.streamUserProfile
|
||||||
import io.hammerhead.karooext.KarooSystemService
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
|
import io.hammerhead.karooext.models.UserProfile
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
enum class SelectedSource(val id: String, val label: String) {
|
enum class SelectedSource(val id: String, val label: String) {
|
||||||
NONE("none", "None"),
|
NONE("none", "None"),
|
||||||
HEART_RATE("hr", "Heart Rate"),
|
HEART_RATE("hr", "Heart Rate"),
|
||||||
POWER("power", "Power"),
|
POWER("power", "Power"),
|
||||||
POWER_3S("power_3s", "Power (3 second avg)"),
|
POWER_3S("power_3s", "Power (3 sec avg)"),
|
||||||
POWER_10S("power_10s", "Power (10 second avg)"),
|
POWER_10S("power_10s", "Power (10 sec avg)"),
|
||||||
SPEED("speed", "Speed"),
|
SPEED("speed", "Speed"),
|
||||||
SPEED_3S("speed_3s", "Speed (3 second avg"),
|
SPEED_3S("speed_3s", "Speed (3 sec avg"),
|
||||||
CADENCE("cadence", "Cadence"),
|
CADENCE("cadence", "Cadence"),
|
||||||
CADENCE_3S("cadence_3s", "Cadence (3 second avg)"),
|
CADENCE_3S("cadence_3s", "Cadence (3 sec avg)"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@ -79,16 +88,36 @@ fun MainScreen() {
|
|||||||
var showLabelOnBars by remember { mutableStateOf(true) }
|
var showLabelOnBars by remember { mutableStateOf(true) }
|
||||||
var barSize by remember { mutableStateOf(CustomProgressBarSize.MEDIUM) }
|
var barSize by remember { mutableStateOf(CustomProgressBarSize.MEDIUM) }
|
||||||
|
|
||||||
|
var minCadence by remember { mutableStateOf("0") }
|
||||||
|
var maxCadence by remember { mutableStateOf("0") }
|
||||||
|
var minSpeed by remember { mutableStateOf("0") }
|
||||||
|
var maxSpeed by remember { mutableStateOf("0") }
|
||||||
|
var isImperial by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
karooSystem.streamUserProfile().distinctUntilChanged().collect {
|
||||||
|
isImperial = it.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(isImperial) {
|
||||||
givenPermissions = Settings.canDrawOverlays(ctx)
|
givenPermissions = Settings.canDrawOverlays(ctx)
|
||||||
|
|
||||||
ctx.streamSettings().collect { settings ->
|
ctx.streamSettings()
|
||||||
|
.combine(karooSystem.streamUserProfile()) { settings, profile -> settings to profile }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect { (settings, profile) ->
|
||||||
bottomSelectedSource = settings.source
|
bottomSelectedSource = settings.source
|
||||||
topSelectedSource = settings.topBarSource
|
topSelectedSource = settings.topBarSource
|
||||||
onlyShowWhileRiding = settings.onlyShowWhileRiding
|
onlyShowWhileRiding = settings.onlyShowWhileRiding
|
||||||
showLabelOnBars = settings.showLabelOnBars
|
showLabelOnBars = settings.showLabelOnBars
|
||||||
colorBasedOnZones = settings.useZoneColors
|
colorBasedOnZones = settings.useZoneColors
|
||||||
barSize = settings.barSize
|
barSize = settings.barSize
|
||||||
|
minCadence = settings.minCadence.toString()
|
||||||
|
maxCadence = settings.maxCadence.toString()
|
||||||
|
isImperial = profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
|
||||||
|
minSpeed = (if(isImperial) settings.minSpeed * 2.23694f else settings.minSpeed * 3.6f).roundToInt().toString()
|
||||||
|
maxSpeed = (if(isImperial) settings.maxSpeed * 2.23694f else settings.maxSpeed * 3.6f).roundToInt().toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +178,50 @@ fun MainScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (topSelectedSource == SelectedSource.SPEED || topSelectedSource == SelectedSource.SPEED_3S ||
|
||||||
|
bottomSelectedSource == SelectedSource.SPEED || bottomSelectedSource == SelectedSource.SPEED_3S){
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
OutlinedTextField(value = minSpeed, modifier = Modifier.weight(1f).absolutePadding(right = 2.dp),
|
||||||
|
onValueChange = { minSpeed = it },
|
||||||
|
label = { Text("Min Speed") },
|
||||||
|
suffix = { Text(if (isImperial) "mph" else "kph") },
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(value = maxSpeed, modifier = Modifier.weight(1f).absolutePadding(left = 2.dp),
|
||||||
|
onValueChange = { maxSpeed = it },
|
||||||
|
label = { Text("Max Speed") },
|
||||||
|
suffix = { Text(if (isImperial) "mph" else "kph") },
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bottomSelectedSource == SelectedSource.CADENCE || topSelectedSource == SelectedSource.CADENCE ||
|
||||||
|
bottomSelectedSource == SelectedSource.CADENCE_3S || topSelectedSource == SelectedSource.CADENCE_3S){
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
OutlinedTextField(value = minCadence, modifier = Modifier.weight(1f).absolutePadding(right = 2.dp),
|
||||||
|
onValueChange = { minCadence = it },
|
||||||
|
label = { Text("Min Cadence") },
|
||||||
|
suffix = { Text("rpm") },
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(value = maxCadence, modifier = Modifier.weight(1f).absolutePadding(left = 2.dp),
|
||||||
|
onValueChange = { maxCadence = it },
|
||||||
|
label = { Text("Min Cadence") },
|
||||||
|
suffix = { Text("rpm") },
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Switch(checked = colorBasedOnZones, onCheckedChange = { colorBasedOnZones = it})
|
Switch(checked = colorBasedOnZones, onCheckedChange = { colorBasedOnZones = it})
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
@ -170,10 +243,16 @@ fun MainScreen() {
|
|||||||
FilledTonalButton(modifier = Modifier
|
FilledTonalButton(modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(50.dp), onClick = {
|
.height(50.dp), onClick = {
|
||||||
|
val minSpeedSetting = (minSpeed.toIntOrNull()?.toFloat()?.div((if(isImperial) 2.23694f else 3.6f))) ?: PowerbarSettings.defaultMinSpeedMs
|
||||||
|
val maxSpeedSetting = (maxSpeed.toIntOrNull()?.toFloat()?.div((if(isImperial) 2.23694f else 3.6f))) ?: PowerbarSettings.defaultMaxSpeedMs
|
||||||
|
|
||||||
val newSettings = PowerbarSettings(
|
val newSettings = PowerbarSettings(
|
||||||
source = bottomSelectedSource, topBarSource = topSelectedSource,
|
source = bottomSelectedSource, topBarSource = topSelectedSource,
|
||||||
onlyShowWhileRiding = onlyShowWhileRiding, showLabelOnBars = showLabelOnBars,
|
onlyShowWhileRiding = onlyShowWhileRiding, showLabelOnBars = showLabelOnBars,
|
||||||
useZoneColors = colorBasedOnZones,
|
useZoneColors = colorBasedOnZones,
|
||||||
|
minCadence = minCadence.toIntOrNull() ?: PowerbarSettings.defaultMinCadence,
|
||||||
|
maxCadence = maxCadence.toIntOrNull() ?: PowerbarSettings.defaultMaxCadence,
|
||||||
|
minSpeed = minSpeedSetting, maxSpeed = maxSpeedSetting,
|
||||||
barSize = barSize
|
barSize = barSize
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user