Compare commits
No commits in common. "91c8eec977a631df5922d7e65df9aa1ae19be28b" and "63bda81fbefa1d93603da9ac4d9c6043a7a7c60f" have entirely different histories.
91c8eec977
...
63bda81fbe
21
.github/workflows/android.yml
vendored
21
.github/workflows/android.yml
vendored
@ -27,7 +27,6 @@ jobs:
|
|||||||
echo "KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}" >> $GITHUB_ENV
|
echo "KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}" >> $GITHUB_ENV
|
||||||
echo "KEYSTORE_BASE64=${{ secrets.KEYSTORE_BASE64 }}" >> $GITHUB_ENV
|
echo "KEYSTORE_BASE64=${{ secrets.KEYSTORE_BASE64 }}" >> $GITHUB_ENV
|
||||||
echo "BUILD_NUMBER=${{ github.run_number }}" >> $GITHUB_ENV
|
echo "BUILD_NUMBER=${{ github.run_number }}" >> $GITHUB_ENV
|
||||||
echo "BASE_URL=${{ secrets.BASE_URL || 'https://github.com/timklge/karoo-powerbar/releases/latest/download' }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: set up JDK 17
|
- name: set up JDK 17
|
||||||
@ -45,22 +44,20 @@ 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: ncipollo/release-action@v1
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
files: |
|
|
||||||
app/build/outputs/apk/release/app-release.apk
|
|
||||||
app/manifest.json
|
|
||||||
app/karoo-powerbar.png
|
|
||||||
powerbar_min.gif
|
|
||||||
powerbar0.png
|
|
||||||
powerbar2.png
|
|
||||||
name: ${{ github.ref_name }}
|
name: ${{ github.ref_name }}
|
||||||
prerelease: false
|
prerelease: false
|
||||||
draft: false
|
generateReleaseNotes: true
|
||||||
generate_release_notes: true
|
artifacts: app/build/outputs/apk/release/app-release.apk, app/manifest.json, app/karoo-powerbar.png, powerbar_min.gif, powerbar0.png, powerbar2.png
|
||||||
make_latest: true
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,4 +9,3 @@
|
|||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
/app/release
|
/app/release
|
||||||
app/manifest.json
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import com.android.build.gradle.tasks.ProcessApplicationManifest
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
@ -61,36 +60,27 @@ tasks.register("generateManifest") {
|
|||||||
group = "build"
|
group = "build"
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
val baseUrl = System.getenv("BASE_URL") ?: "https://github.com/timklge/karoo-powerbar/releases/latest/download"
|
|
||||||
val manifestFile = file("$projectDir/manifest.json")
|
val manifestFile = file("$projectDir/manifest.json")
|
||||||
val manifest = mapOf(
|
val manifest = mapOf(
|
||||||
"label" to "Powerbar",
|
"label" to "Powerbar",
|
||||||
"packageName" to "de.timklge.karoopowerbar",
|
"packageName" to "de.timklge.karoopowerbar",
|
||||||
"iconUrl" to "$baseUrl/karoo-powerbar.png",
|
"iconUrl" to "https://github.com/timklge/karoo-powerbar/releases/latest/download/karoo-powerbar.png",
|
||||||
"latestApkUrl" to "$baseUrl/app-release.apk",
|
"latestApkUrl" to "https://github.com/timklge/karoo-powerbar/releases/latest/download/app-release.apk",
|
||||||
"latestVersion" to android.defaultConfig.versionName,
|
"latestVersion" to android.defaultConfig.versionName,
|
||||||
"latestVersionCode" to android.defaultConfig.versionCode,
|
"latestVersionCode" to android.defaultConfig.versionCode,
|
||||||
"developer" to "github.com/timklge",
|
"developer" to "github.com/timklge",
|
||||||
"description" to "Open-source extension that adds colored power or heart rate progress bars to the edges of the screen, similar to the LEDs on Wahoo computers",
|
"description" to "Open-source extension that adds colored power or heart rate progress bars to the edges of the screen, similar to the LEDs on Wahoo computers",
|
||||||
"releaseNotes" to "* Add route progress data source\n* Add workout target range indicator\n* Replace dropdown popup with fullscreen dialog",
|
"releaseNotes" to "* Replace dropdown popup with fullscreen dialog",
|
||||||
"screenshotUrls" to listOf(
|
"screenshotUrls" to listOf(
|
||||||
"$baseUrl/powerbar_min.gif",
|
"https://github.com/timklge/karoo-powerbar/releases/latest/download/powerbar_min.gif",
|
||||||
"$baseUrl/powerbar0.png",
|
"https://github.com/timklge/karoo-powerbar/releases/latest/download/powerbar0.png",
|
||||||
"$baseUrl/powerbar2.png",
|
"https://github.com/timklge/karoo-powerbar/releases/latest/download/powerbar2.png",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val gson = groovy.json.JsonBuilder(manifest).toPrettyString()
|
val gson = groovy.json.JsonBuilder(manifest).toPrettyString()
|
||||||
manifestFile.writeText(gson)
|
manifestFile.writeText(gson)
|
||||||
println("Generated manifest.json with version ${android.defaultConfig.versionName} (${android.defaultConfig.versionCode})")
|
println("Generated manifest.json with version ${android.defaultConfig.versionName} (${android.defaultConfig.versionCode})")
|
||||||
|
|
||||||
if (System.getenv()["BASE_URL"] != null){
|
|
||||||
val androidManifestFile = file("$projectDir/src/main/AndroidManifest.xml")
|
|
||||||
var androidManifestContent = androidManifestFile.readText()
|
|
||||||
androidManifestContent = androidManifestContent.replace("\$BASE_URL\$", baseUrl)
|
|
||||||
androidManifestFile.writeText(androidManifestContent)
|
|
||||||
println("Replaced \$BASE_URL$ in AndroidManifest.xml")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,11 +88,6 @@ tasks.named("assemble") {
|
|||||||
dependsOn("generateManifest")
|
dependsOn("generateManifest")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<ProcessApplicationManifest>().configureEach {
|
|
||||||
if (name == "processDebugMainManifest" || name == "processReleaseMainManifest") {
|
|
||||||
dependsOn(tasks.named("generateManifest"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.hammerhead.karoo.ext)
|
implementation(libs.hammerhead.karoo.ext)
|
||||||
@ -117,6 +102,4 @@ dependencies {
|
|||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
implementation(libs.androidx.cardview)
|
implementation(libs.androidx.cardview)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.mapbox.sdk.turf)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,7 @@
|
|||||||
<!-- Provide Karoo System with information about delivery of your app -->
|
<!-- Provide Karoo System with information about delivery of your app -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.hammerhead.karooext.MANIFEST_URL"
|
android:name="io.hammerhead.karooext.MANIFEST_URL"
|
||||||
android:value="$BASE_URL$/manifest.json" />
|
android:value="https://github.com/timklge/karoo-powerbar/releases/latest/download/manifest.json" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,14 @@ import android.util.AttributeSet
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class CustomProgressBarSize(val id: String, val label: String, val fontSize: Float, val barHeight: Float) {
|
||||||
|
SMALL("small", "Small", 35f, 10f),
|
||||||
|
MEDIUM("medium", "Medium", 40f, 15f),
|
||||||
|
LARGE("large", "Large", 60f, 25f),
|
||||||
|
}
|
||||||
|
|
||||||
class CustomProgressBar @JvmOverloads constructor(
|
class CustomProgressBar @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null
|
context: Context, attrs: AttributeSet? = null
|
||||||
@ -18,50 +26,13 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
var progress: Double? = 0.5
|
var progress: Double? = 0.5
|
||||||
var location: PowerbarLocation = PowerbarLocation.BOTTOM
|
var location: PowerbarLocation = PowerbarLocation.BOTTOM
|
||||||
var label: String = ""
|
var label: String = ""
|
||||||
var minTarget: Double? = null
|
|
||||||
var maxTarget: Double? = null
|
|
||||||
var target: Double? = null
|
|
||||||
var showLabel: Boolean = true
|
var showLabel: Boolean = true
|
||||||
var barBackground: Boolean = false
|
|
||||||
@ColorInt var progressColor: Int = 0xFF2b86e6.toInt()
|
@ColorInt var progressColor: Int = 0xFF2b86e6.toInt()
|
||||||
|
|
||||||
var fontSize = CustomProgressBarFontSize.MEDIUM
|
var size = CustomProgressBarSize.MEDIUM
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
textPaint.textSize = value.fontSize
|
textPaint.textSize = value.fontSize
|
||||||
invalidate() // Redraw to apply new font size
|
|
||||||
}
|
|
||||||
|
|
||||||
var barSize = CustomProgressBarBarSize.MEDIUM
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
targetZoneStrokePaint.strokeWidth = when(value){
|
|
||||||
CustomProgressBarBarSize.NONE, CustomProgressBarBarSize.SMALL -> 3f
|
|
||||||
CustomProgressBarBarSize.MEDIUM -> 6f
|
|
||||||
CustomProgressBarBarSize.LARGE -> 8f
|
|
||||||
}
|
|
||||||
targetIndicatorPaint.strokeWidth = when(value){
|
|
||||||
CustomProgressBarBarSize.NONE, CustomProgressBarBarSize.SMALL -> 6f
|
|
||||||
CustomProgressBarBarSize.MEDIUM -> 8f
|
|
||||||
CustomProgressBarBarSize.LARGE -> 10f
|
|
||||||
}
|
|
||||||
invalidate() // Redraw to apply new bar size
|
|
||||||
}
|
|
||||||
|
|
||||||
private val targetColor = 0xFF9933FF.toInt()
|
|
||||||
|
|
||||||
private val targetZoneFillPaint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
style = Paint.Style.FILL
|
|
||||||
color = targetColor
|
|
||||||
alpha = 100 // Semi-transparent fill
|
|
||||||
}
|
|
||||||
|
|
||||||
private val targetZoneStrokePaint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
strokeWidth = 6f
|
|
||||||
style = Paint.Style.STROKE
|
|
||||||
color = targetColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val linePaint = Paint().apply {
|
private val linePaint = Paint().apply {
|
||||||
@ -80,7 +51,7 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
|
|
||||||
private val blurPaint = Paint().apply {
|
private val blurPaint = Paint().apply {
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
strokeWidth = 6f
|
strokeWidth = 4f
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
color = progressColor
|
color = progressColor
|
||||||
maskFilter = BlurMaskFilter(3f, BlurMaskFilter.Blur.NORMAL)
|
maskFilter = BlurMaskFilter(3f, BlurMaskFilter.Blur.NORMAL)
|
||||||
@ -88,7 +59,7 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
|
|
||||||
private val blurPaintHighlight = Paint().apply {
|
private val blurPaintHighlight = Paint().apply {
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
strokeWidth = 10f
|
strokeWidth = 8f
|
||||||
style = Paint.Style.FILL_AND_STROKE
|
style = Paint.Style.FILL_AND_STROKE
|
||||||
color = ColorUtils.blendARGB(progressColor, 0xFFFFFF, 0.5f)
|
color = ColorUtils.blendARGB(progressColor, 0xFFFFFF, 0.5f)
|
||||||
maskFilter = BlurMaskFilter(6f, BlurMaskFilter.Blur.NORMAL)
|
maskFilter = BlurMaskFilter(6f, BlurMaskFilter.Blur.NORMAL)
|
||||||
@ -109,207 +80,90 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
private val textPaint = Paint().apply {
|
private val textPaint = Paint().apply {
|
||||||
color = Color.WHITE
|
color = Color.WHITE
|
||||||
strokeWidth = 3f
|
strokeWidth = 3f
|
||||||
textSize = fontSize.fontSize
|
textSize = size.fontSize
|
||||||
typeface = Typeface.create(Typeface.MONOSPACE, Typeface.BOLD)
|
typeface = Typeface.create(Typeface.MONOSPACE, Typeface.BOLD);
|
||||||
textAlign = Paint.Align.CENTER
|
textAlign = Paint.Align.CENTER
|
||||||
}
|
}
|
||||||
|
|
||||||
private val targetIndicatorPaint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
strokeWidth = 8f
|
|
||||||
style = Paint.Style.STROKE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDrawForeground(canvas: Canvas) {
|
override fun onDrawForeground(canvas: Canvas) {
|
||||||
super.onDrawForeground(canvas)
|
super.onDrawForeground(canvas)
|
||||||
|
|
||||||
// Determine if the current progress is within the target range
|
|
||||||
val isTargetMet =
|
|
||||||
progress != null && minTarget != null && maxTarget != null && progress!! >= minTarget!! && progress!! <= maxTarget!!
|
|
||||||
|
|
||||||
linePaint.color = progressColor
|
linePaint.color = progressColor
|
||||||
lineStrokePaint.color = progressColor
|
lineStrokePaint.color = progressColor
|
||||||
blurPaint.color = progressColor
|
blurPaint.color = progressColor
|
||||||
blurPaintHighlight.color = ColorUtils.blendARGB(progressColor, 0xFFFFFF, 0.5f)
|
blurPaintHighlight.color = ColorUtils.blendARGB(progressColor, 0xFFFFFF, 0.5f)
|
||||||
|
|
||||||
when (location) {
|
when(location){
|
||||||
PowerbarLocation.TOP -> {
|
PowerbarLocation.TOP -> {
|
||||||
val rect = RectF(
|
val rect = RectF(
|
||||||
1f,
|
1f,
|
||||||
15f,
|
15f,
|
||||||
((canvas.width.toDouble() - 1f) * (progress ?: 0.0).coerceIn(0.0, 1.0)).toFloat(),
|
((canvas.width.toDouble() - 1f) * (progress ?: 0.0).coerceIn(0.0, 1.0)).toFloat(),
|
||||||
15f + barSize.barHeight // barSize.barHeight will be 0f if NONE
|
15f + size.barHeight
|
||||||
)
|
)
|
||||||
|
|
||||||
// Draw bar components only if barSize is not NONE
|
canvas.drawRect(0f, 15f, canvas.width.toFloat(), 15f + size.barHeight, backgroundPaint)
|
||||||
if (barSize != CustomProgressBarBarSize.NONE) {
|
|
||||||
if (barBackground){
|
|
||||||
canvas.drawRect(0f, 15f, canvas.width.toFloat(), 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,
|
|
||||||
maxTargetX,
|
|
||||||
15f + barSize.barHeight,
|
|
||||||
2f,
|
|
||||||
2f,
|
|
||||||
targetZoneFillPaint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress != null) {
|
if (progress != null) {
|
||||||
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
||||||
canvas.drawRoundRect(rect, 2f, 2f, linePaint)
|
canvas.drawRoundRect(rect, 2f, 2f, linePaint)
|
||||||
canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Draw label (if progress is not null and showLabel is true)
|
|
||||||
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,
|
|
||||||
15f,
|
|
||||||
maxTargetX,
|
|
||||||
15f + barSize.barHeight,
|
|
||||||
2f,
|
|
||||||
2f,
|
|
||||||
targetZoneStrokePaint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw vertical target indicator line if target is present
|
canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showLabel){
|
if (showLabel){
|
||||||
lineStrokePaint.color = if (target != null){
|
|
||||||
if (isTargetMet) Color.GREEN else Color.RED
|
|
||||||
} else progressColor
|
|
||||||
|
|
||||||
blurPaint.color = lineStrokePaint.color
|
|
||||||
blurPaintHighlight.color = ColorUtils.blendARGB(lineStrokePaint.color, 0xFFFFFF, 0.5f)
|
|
||||||
|
|
||||||
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 yOffset = when(size){
|
||||||
|
CustomProgressBarSize.SMALL -> (size.fontSize - size.barHeight) / 2 + 2f
|
||||||
|
CustomProgressBarSize.MEDIUM, CustomProgressBarSize.LARGE -> (size.fontSize - size.barHeight) / 2
|
||||||
|
}
|
||||||
val x = (rect.right - xOffset).coerceIn(0f..canvas.width-xOffset*2f)
|
val x = (rect.right - xOffset).coerceIn(0f..canvas.width-xOffset*2f)
|
||||||
|
val y = rect.top - yOffset
|
||||||
val r = x + xOffset * 2
|
val r = x + xOffset * 2
|
||||||
|
val b = rect.bottom + yOffset
|
||||||
|
|
||||||
val fm = textPaint.fontMetrics
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, textBackgroundPaint)
|
||||||
// barCenterY calculation uses barSize.barHeight, which is 0f for NONE,
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, blurPaint)
|
||||||
// correctly centering the label on the 15f line.
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, lineStrokePaint)
|
||||||
val barCenterY = rect.top + barSize.barHeight / 2f
|
|
||||||
val centeredTextBaselineY = barCenterY - (fm.ascent + fm.descent) / 2f
|
|
||||||
val calculatedTextBoxTop = centeredTextBaselineY + fm.ascent
|
|
||||||
val finalTextBoxTop = calculatedTextBoxTop.coerceAtLeast(0f)
|
|
||||||
val finalTextBaselineY = finalTextBoxTop - fm.ascent
|
|
||||||
val finalTextBoxBottom = finalTextBaselineY + fm.descent
|
|
||||||
|
|
||||||
canvas.drawRoundRect(x, finalTextBoxTop, r, finalTextBoxBottom, 2f, 2f, textBackgroundPaint)
|
canvas.drawText(label, x + xOffset, rect.top + size.barHeight + 6, textPaint)
|
||||||
canvas.drawRoundRect(x, finalTextBoxTop, r, finalTextBoxBottom, 2f, 2f, blurPaint)
|
|
||||||
canvas.drawRoundRect(x, finalTextBoxTop, r, finalTextBoxBottom, 2f, 2f, lineStrokePaint)
|
|
||||||
canvas.drawText(label, x + xOffset, finalTextBaselineY, textPaint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PowerbarLocation.BOTTOM -> {
|
PowerbarLocation.BOTTOM -> {
|
||||||
val rect = RectF(
|
val rect = RectF(
|
||||||
1f,
|
1f,
|
||||||
canvas.height.toFloat() - 1f - barSize.barHeight, // barSize.barHeight will be 0f if NONE
|
canvas.height.toFloat() - 1f - size.barHeight,
|
||||||
((canvas.width.toDouble() - 1f) * (progress ?: 0.0).coerceIn(0.0, 1.0)).toFloat(),
|
((canvas.width.toDouble() - 1f) * (progress ?: 0.0).coerceIn(0.0, 1.0)).toFloat(),
|
||||||
canvas.height.toFloat()
|
canvas.height.toFloat()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Draw bar components only if barSize is not NONE
|
canvas.drawRect(0f, canvas.height.toFloat() - size.barHeight, canvas.width.toFloat(), canvas.height.toFloat(), backgroundPaint)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
maxTargetX,
|
|
||||||
canvas.height.toFloat(),
|
|
||||||
2f,
|
|
||||||
2f,
|
|
||||||
targetZoneFillPaint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress != null) {
|
if (progress != null) {
|
||||||
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
||||||
canvas.drawRoundRect(rect, 2f, 2f, linePaint)
|
canvas.drawRoundRect(rect, 2f, 2f, linePaint)
|
||||||
|
|
||||||
canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight)
|
canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw label (if progress is not null and showLabel is true)
|
|
||||||
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,
|
|
||||||
canvas.height.toFloat() - barSize.barHeight,
|
|
||||||
maxTargetX,
|
|
||||||
canvas.height.toFloat(),
|
|
||||||
2f,
|
|
||||||
2f,
|
|
||||||
targetZoneStrokePaint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showLabel){
|
if (showLabel){
|
||||||
lineStrokePaint.color = if (target != null){
|
|
||||||
if (isTargetMet) Color.GREEN else Color.RED
|
|
||||||
} else progressColor
|
|
||||||
|
|
||||||
blurPaint.color = lineStrokePaint.color
|
|
||||||
blurPaintHighlight.color = ColorUtils.blendARGB(lineStrokePaint.color, 0xFFFFFF, 0.5f)
|
|
||||||
|
|
||||||
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 yOffset = when(size){
|
||||||
|
CustomProgressBarSize.SMALL -> size.fontSize / 2 + 2f
|
||||||
|
CustomProgressBarSize.MEDIUM -> size.fontSize / 2
|
||||||
|
CustomProgressBarSize.LARGE -> size.fontSize / 2 - 5f
|
||||||
|
}
|
||||||
val x = (rect.right - xOffset).coerceIn(0f..canvas.width-xOffset*2f)
|
val x = (rect.right - xOffset).coerceIn(0f..canvas.width-xOffset*2f)
|
||||||
|
val y = (rect.top - yOffset)
|
||||||
val r = x + xOffset * 2
|
val r = x + xOffset * 2
|
||||||
|
val b = rect.bottom + 5
|
||||||
|
|
||||||
// textDrawBaselineY calculation uses rect.top and barSize.barHeight.
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, textBackgroundPaint)
|
||||||
// If NONE, barSize.barHeight is 0f. rect.top becomes canvas.height - 1f.
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, blurPaint)
|
||||||
// So, baseline is (canvas.height - 1f) + 0f - 1f = canvas.height - 2f.
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, lineStrokePaint)
|
||||||
val textDrawBaselineY = rect.top + barSize.barHeight - 1f
|
|
||||||
val yBox = textDrawBaselineY + textPaint.ascent()
|
|
||||||
val bBox = textDrawBaselineY + textPaint.descent()
|
|
||||||
|
|
||||||
canvas.drawRoundRect(x, yBox, r, bBox, 2f, 2f, textBackgroundPaint)
|
canvas.drawText(label, x + xOffset, rect.top + size.barHeight - 1, textPaint)
|
||||||
canvas.drawRoundRect(x, yBox, r, bBox, 2f, 2f, blurPaint)
|
|
||||||
canvas.drawRoundRect(x, yBox, r, bBox, 2f, 2f, lineStrokePaint)
|
|
||||||
canvas.drawText(label, x + xOffset, textDrawBaselineY, textPaint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,45 +0,0 @@
|
|||||||
package de.timklge.karoopowerbar
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
enum class CustomProgressBarSize(val id: String, val label: String, val fontSize: Float, val barHeight: Float) {
|
|
||||||
SMALL("small", "Small", 35f, 10f),
|
|
||||||
MEDIUM("medium", "Medium", 40f, 15f),
|
|
||||||
LARGE("large", "Large", 60f, 25f),
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
enum class CustomProgressBarFontSize(val id: String, val label: String, val fontSize: Float) {
|
|
||||||
SMALL("small", "Small", 35f),
|
|
||||||
MEDIUM("medium", "Medium", 40f),
|
|
||||||
LARGE("large", "Large", 60f);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromSize(size: CustomProgressBarSize): CustomProgressBarFontSize {
|
|
||||||
return when (size) {
|
|
||||||
CustomProgressBarSize.SMALL -> SMALL
|
|
||||||
CustomProgressBarSize.MEDIUM -> MEDIUM
|
|
||||||
CustomProgressBarSize.LARGE -> LARGE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
enum class CustomProgressBarBarSize(val id: String, val label: String, val barHeight: Float) {
|
|
||||||
NONE("none", "None", 0f),
|
|
||||||
SMALL("small", "Small", 10f),
|
|
||||||
MEDIUM("medium", "Medium", 15f),
|
|
||||||
LARGE("large", "Large", 25f);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromSize(size: CustomProgressBarSize): CustomProgressBarBarSize {
|
|
||||||
return when (size) {
|
|
||||||
CustomProgressBarSize.SMALL -> SMALL
|
|
||||||
CustomProgressBarSize.MEDIUM -> MEDIUM
|
|
||||||
CustomProgressBarSize.LARGE -> LARGE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +1,14 @@
|
|||||||
package de.timklge.karoopowerbar
|
package de.timklge.karoopowerbar
|
||||||
|
|
||||||
import io.hammerhead.karooext.KarooSystemService
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
import io.hammerhead.karooext.models.OnNavigationState
|
|
||||||
import io.hammerhead.karooext.models.OnStreamState
|
import io.hammerhead.karooext.models.OnStreamState
|
||||||
import io.hammerhead.karooext.models.RideState
|
import io.hammerhead.karooext.models.RideState
|
||||||
import io.hammerhead.karooext.models.StreamState
|
import io.hammerhead.karooext.models.StreamState
|
||||||
import io.hammerhead.karooext.models.UserProfile
|
import io.hammerhead.karooext.models.UserProfile
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.channels.trySendBlocking
|
import kotlinx.coroutines.channels.trySendBlocking
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.flow.conflate
|
|
||||||
import kotlinx.coroutines.flow.transform
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
val jsonWithUnknownKeys = Json { ignoreUnknownKeys = true }
|
val jsonWithUnknownKeys = Json { ignoreUnknownKeys = true }
|
||||||
@ -39,17 +35,6 @@ fun KarooSystemService.streamRideState(): Flow<RideState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KarooSystemService.streamNavigationState(): Flow<OnNavigationState> {
|
|
||||||
return callbackFlow {
|
|
||||||
val listenerId = addConsumer { navigationState: OnNavigationState ->
|
|
||||||
trySendBlocking(navigationState)
|
|
||||||
}
|
|
||||||
awaitClose {
|
|
||||||
removeConsumer(listenerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KarooSystemService.streamUserProfile(): Flow<UserProfile> {
|
fun KarooSystemService.streamUserProfile(): Flow<UserProfile> {
|
||||||
return callbackFlow {
|
return callbackFlow {
|
||||||
val listenerId = addConsumer { userProfile: UserProfile ->
|
val listenerId = addConsumer { userProfile: UserProfile ->
|
||||||
@ -60,10 +45,3 @@ fun KarooSystemService.streamUserProfile(): Flow<UserProfile> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun<T> Flow<T>.throttle(timeout: Long): Flow<T> = this
|
|
||||||
.conflate()
|
|
||||||
.transform {
|
|
||||||
emit(it)
|
|
||||||
delay(timeout)
|
|
||||||
}
|
|
||||||
@ -53,7 +53,7 @@ class ForegroundService : Service() {
|
|||||||
windows.clear()
|
windows.clear()
|
||||||
|
|
||||||
if (settings.source != SelectedSource.NONE && showBars) {
|
if (settings.source != SelectedSource.NONE && showBars) {
|
||||||
Window(this@ForegroundService, PowerbarLocation.BOTTOM, settings.showLabelOnBars, settings.barBackground, settings.barBarSize, settings.barFontSize).apply {
|
Window(this@ForegroundService, PowerbarLocation.BOTTOM, settings.showLabelOnBars, settings.barSize).apply {
|
||||||
selectedSource = settings.source
|
selectedSource = settings.source
|
||||||
windows.add(this)
|
windows.add(this)
|
||||||
open()
|
open()
|
||||||
@ -61,7 +61,7 @@ class ForegroundService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (settings.topBarSource != SelectedSource.NONE && showBars){
|
if (settings.topBarSource != SelectedSource.NONE && showBars){
|
||||||
Window(this@ForegroundService, PowerbarLocation.TOP, settings.showLabelOnBars, settings.barBackground, settings.barBarSize, settings.barFontSize).apply {
|
Window(this@ForegroundService, PowerbarLocation.TOP, settings.showLabelOnBars, settings.barSize).apply {
|
||||||
selectedSource = settings.topBarSource
|
selectedSource = settings.topBarSource
|
||||||
open()
|
open()
|
||||||
windows.add(this)
|
windows.add(this)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package de.timklge.karoopowerbar
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import de.timklge.karoopowerbar.screens.SelectedSource
|
import de.timklge.karoopowerbar.screens.SelectedSource
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@ -20,10 +21,7 @@ data class PowerbarSettings(
|
|||||||
val onlyShowWhileRiding: Boolean = true,
|
val onlyShowWhileRiding: Boolean = true,
|
||||||
val showLabelOnBars: Boolean = true,
|
val showLabelOnBars: Boolean = true,
|
||||||
val useZoneColors: Boolean = true,
|
val useZoneColors: Boolean = true,
|
||||||
val barBackground: Boolean = false,
|
|
||||||
val barSize: CustomProgressBarSize = CustomProgressBarSize.MEDIUM,
|
val barSize: CustomProgressBarSize = CustomProgressBarSize.MEDIUM,
|
||||||
val barFontSize: CustomProgressBarFontSize = CustomProgressBarFontSize.fromSize(barSize),
|
|
||||||
val barBarSize: CustomProgressBarBarSize = CustomProgressBarBarSize.fromSize(barSize),
|
|
||||||
|
|
||||||
val minCadence: Int = defaultMinCadence, val maxCadence: Int = defaultMaxCadence,
|
val minCadence: Int = defaultMinCadence, val maxCadence: Int = defaultMaxCadence,
|
||||||
val minSpeed: Float = defaultMinSpeedMs, val maxSpeed: Float = defaultMaxSpeedMs, // 50 km/h in m/s
|
val minSpeed: Float = defaultMinSpeedMs, val maxSpeed: Float = defaultMaxSpeedMs, // 50 km/h in m/s
|
||||||
|
|||||||
@ -17,15 +17,10 @@ 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 androidx.annotation.ColorRes
|
||||||
import com.mapbox.geojson.LineString
|
|
||||||
import com.mapbox.turf.TurfConstants.UNIT_METERS
|
|
||||||
import com.mapbox.turf.TurfMeasurement
|
|
||||||
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
|
||||||
import io.hammerhead.karooext.models.DataPoint
|
|
||||||
import io.hammerhead.karooext.models.DataType
|
import io.hammerhead.karooext.models.DataType
|
||||||
import io.hammerhead.karooext.models.OnNavigationState
|
|
||||||
import io.hammerhead.karooext.models.StreamState
|
import io.hammerhead.karooext.models.StreamState
|
||||||
import io.hammerhead.karooext.models.UserProfile
|
import io.hammerhead.karooext.models.UserProfile
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -34,15 +29,12 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
fun remap(value: Double?, fromMin: Double, fromMax: Double, toMin: Double, toMax: Double): Double? {
|
fun remap(value: Double, fromMin: Double, fromMax: Double, toMin: Double, toMax: Double): Double {
|
||||||
if (value == null) return null
|
|
||||||
|
|
||||||
return (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin
|
return (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,16 +46,8 @@ class Window(
|
|||||||
private val context: Context,
|
private val context: Context,
|
||||||
val powerbarLocation: PowerbarLocation = PowerbarLocation.BOTTOM,
|
val powerbarLocation: PowerbarLocation = PowerbarLocation.BOTTOM,
|
||||||
val showLabel: Boolean,
|
val showLabel: Boolean,
|
||||||
val barBackground: Boolean,
|
val powerbarSize: CustomProgressBarSize
|
||||||
val powerbarBarSize: CustomProgressBarBarSize,
|
|
||||||
val powerbarFontSize: CustomProgressBarFontSize,
|
|
||||||
) {
|
) {
|
||||||
companion object {
|
|
||||||
val FIELD_TARGET_VALUE_ID = "FIELD_WORKOUT_TARGET_VALUE_ID";
|
|
||||||
val FIELD_TARGET_MIN_ID = "FIELD_WORKOUT_TARGET_MIN_VALUE_ID";
|
|
||||||
val FIELD_TARGET_MAX_ID = "FIELD_WORKOUT_TARGET_MAX_VALUE_ID";
|
|
||||||
}
|
|
||||||
|
|
||||||
private val rootView: View
|
private val rootView: View
|
||||||
private var layoutParams: WindowManager.LayoutParams? = null
|
private var layoutParams: WindowManager.LayoutParams? = null
|
||||||
private val windowManager: WindowManager
|
private val windowManager: WindowManager
|
||||||
@ -116,6 +100,8 @@ class Window(
|
|||||||
|
|
||||||
private val karooSystem: KarooSystemService = KarooSystemService(context)
|
private val karooSystem: KarooSystemService = KarooSystemService(context)
|
||||||
|
|
||||||
|
data class StreamData(val userProfile: UserProfile, val value: Double?, val settings: PowerbarSettings? = null)
|
||||||
|
|
||||||
private var serviceJob: Job? = null
|
private var serviceJob: Job? = null
|
||||||
|
|
||||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
@ -136,9 +122,7 @@ class Window(
|
|||||||
powerbar.progress = null
|
powerbar.progress = null
|
||||||
powerbar.location = powerbarLocation
|
powerbar.location = powerbarLocation
|
||||||
powerbar.showLabel = showLabel
|
powerbar.showLabel = showLabel
|
||||||
powerbar.barBackground = barBackground
|
powerbar.size = powerbarSize
|
||||||
powerbar.fontSize = powerbarFontSize
|
|
||||||
powerbar.barSize = powerbarBarSize
|
|
||||||
powerbar.invalidate()
|
powerbar.invalidate()
|
||||||
|
|
||||||
Log.i(TAG, "Streaming $selectedSource")
|
Log.i(TAG, "Streaming $selectedSource")
|
||||||
@ -152,7 +136,6 @@ class Window(
|
|||||||
SelectedSource.SPEED_3S -> streamSpeed(true)
|
SelectedSource.SPEED_3S -> streamSpeed(true)
|
||||||
SelectedSource.CADENCE -> streamCadence(false)
|
SelectedSource.CADENCE -> streamCadence(false)
|
||||||
SelectedSource.CADENCE_3S -> streamCadence(true)
|
SelectedSource.CADENCE_3S -> streamCadence(true)
|
||||||
SelectedSource.ROUTE_PROGRESS -> streamRouteProgress()
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,55 +151,6 @@ class Window(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun streamRouteProgress() {
|
|
||||||
data class StreamData(
|
|
||||||
val userProfile: UserProfile,
|
|
||||||
val distanceToDestination: Double?,
|
|
||||||
val navigationState: OnNavigationState
|
|
||||||
)
|
|
||||||
|
|
||||||
var lastKnownRoutePolyline: String? = null
|
|
||||||
var lastKnownRouteLength: Double? = null
|
|
||||||
|
|
||||||
combine(karooSystem.streamUserProfile(), karooSystem.streamDataFlow(DataType.Type.DISTANCE_TO_DESTINATION), karooSystem.streamNavigationState()) { userProfile, distanceToDestination, navigationState ->
|
|
||||||
StreamData(userProfile, (distanceToDestination as? StreamState.Streaming)?.dataPoint?.values[DataType.Field.DISTANCE_TO_DESTINATION], navigationState)
|
|
||||||
}.distinctUntilChanged().throttle(5_000).collect { (userProfile, distanceToDestination, navigationState) ->
|
|
||||||
val state = navigationState.state
|
|
||||||
val routePolyline = when (state) {
|
|
||||||
is OnNavigationState.NavigationState.NavigatingRoute -> state.routePolyline
|
|
||||||
is OnNavigationState.NavigationState.NavigatingToDestination -> state.polyline
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (routePolyline != lastKnownRoutePolyline) {
|
|
||||||
lastKnownRoutePolyline = routePolyline
|
|
||||||
lastKnownRouteLength = when (state){
|
|
||||||
is OnNavigationState.NavigationState.NavigatingRoute -> state.routeDistance
|
|
||||||
is OnNavigationState.NavigationState.NavigatingToDestination -> try {
|
|
||||||
TurfMeasurement.length(LineString.fromPolyline(state.polyline, 5), UNIT_METERS)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Failed to calculate route length", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val routeLength = lastKnownRouteLength
|
|
||||||
val routeProgressMeters = routeLength?.let { routeLength - (distanceToDestination ?: 0.0) }?.coerceAtLeast(0.0)
|
|
||||||
val routeProgress = if (routeLength != null && routeProgressMeters != null) remap(routeProgressMeters, 0.0, routeLength, 0.0, 1.0) else null
|
|
||||||
val routeProgressInUserUnit = when (userProfile.preferredUnit.distance) {
|
|
||||||
UserProfile.PreferredUnit.UnitType.IMPERIAL -> routeProgressMeters?.times(0.000621371)?.roundToInt() // Miles
|
|
||||||
else -> routeProgressMeters?.times(0.001)?.roundToInt() // Kilometers
|
|
||||||
}
|
|
||||||
|
|
||||||
powerbar.progressColor = context.getColor(R.color.zone0)
|
|
||||||
powerbar.progress = routeProgress
|
|
||||||
powerbar.label = "$routeProgressInUserUnit"
|
|
||||||
powerbar.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }
|
||||||
@ -224,11 +158,12 @@ class Window(
|
|||||||
|
|
||||||
val settingsFlow = context.streamSettings()
|
val settingsFlow = context.streamSettings()
|
||||||
|
|
||||||
data class StreamData(val userProfile: UserProfile, val value: Double?, val settings: PowerbarSettings? = null)
|
karooSystem.streamUserProfile()
|
||||||
|
.distinctUntilChanged()
|
||||||
combine(karooSystem.streamUserProfile(), speedFlow, settingsFlow) { userProfile, speed, settings ->
|
.combine(speedFlow) { userProfile, speed -> StreamData(userProfile, speed) }
|
||||||
StreamData(userProfile, speed, settings)
|
.combine(settingsFlow) { streamData, settings -> streamData.copy(settings = settings) }
|
||||||
}.distinctUntilChanged().throttle(1_000).collect { streamData ->
|
.distinctUntilChanged()
|
||||||
|
.collect { streamData ->
|
||||||
val valueMetersPerSecond = streamData.value
|
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)
|
||||||
@ -238,7 +173,8 @@ class Window(
|
|||||||
if (value != null && valueMetersPerSecond != null) {
|
if (value != null && valueMetersPerSecond != null) {
|
||||||
val minSpeed = streamData.settings?.minSpeed ?: PowerbarSettings.defaultMinSpeedMs
|
val minSpeed = streamData.settings?.minSpeed ?: PowerbarSettings.defaultMinSpeedMs
|
||||||
val maxSpeed = streamData.settings?.maxSpeed ?: PowerbarSettings.defaultMaxSpeedMs
|
val maxSpeed = streamData.settings?.maxSpeed ?: PowerbarSettings.defaultMaxSpeedMs
|
||||||
val progress = remap(valueMetersPerSecond, minSpeed.toDouble(), maxSpeed.toDouble(), 0.0, 1.0) ?: 0.0
|
val progress =
|
||||||
|
remap(valueMetersPerSecond, minSpeed.toDouble(), maxSpeed.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
|
||||||
|
|
||||||
@ -263,30 +199,25 @@ class Window(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun streamCadence(smoothed: Boolean) {
|
private suspend fun streamCadence(smoothed: Boolean) {
|
||||||
val cadenceFlow = karooSystem.streamDataFlow(if(smoothed) DataType.Type.SMOOTHED_3S_AVERAGE_CADENCE else DataType.Type.CADENCE)
|
val speedFlow = karooSystem.streamDataFlow(if(smoothed) DataType.Type.SMOOTHED_3S_AVERAGE_CADENCE else DataType.Type.CADENCE)
|
||||||
.map { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
.map { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
||||||
data class StreamData(val userProfile: UserProfile, val value: Double?, val settings: PowerbarSettings? = null, val cadenceTarget: DataPoint? = null)
|
|
||||||
|
|
||||||
val settingsFlow = context.streamSettings()
|
val settingsFlow = context.streamSettings()
|
||||||
val cadenceTargetFlow = karooSystem.streamDataFlow("TYPE_WORKOUT_CADENCE_TARGET_ID")
|
|
||||||
.map { (it as? StreamState.Streaming)?.dataPoint }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
|
|
||||||
combine(karooSystem.streamUserProfile(), cadenceFlow, settingsFlow, cadenceTargetFlow) { userProfile, speed, settings, cadenceTarget ->
|
karooSystem.streamUserProfile()
|
||||||
StreamData(userProfile, speed, settings, cadenceTarget)
|
.distinctUntilChanged()
|
||||||
}.distinctUntilChanged().throttle(1_000).collect { streamData ->
|
.combine(speedFlow) { userProfile, speed -> StreamData(userProfile, speed) }
|
||||||
|
.combine(settingsFlow) { streamData, settings -> streamData.copy(settings = settings) }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect { streamData ->
|
||||||
val value = streamData.value?.roundToInt()
|
val value = streamData.value?.roundToInt()
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
val minCadence = streamData.settings?.minCadence ?: PowerbarSettings.defaultMinCadence
|
val minCadence = streamData.settings?.minCadence ?: PowerbarSettings.defaultMinCadence
|
||||||
val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence
|
val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence
|
||||||
val progress = remap(value.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) ?: 0.0
|
val progress =
|
||||||
|
remap(value.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0)
|
||||||
powerbar.minTarget = remap(streamData.cadenceTarget?.values[FIELD_TARGET_MIN_ID]?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0)
|
|
||||||
powerbar.maxTarget = remap(streamData.cadenceTarget?.values[FIELD_TARGET_MAX_ID]?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0)
|
|
||||||
powerbar.target = remap(streamData.cadenceTarget?.values[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
|
||||||
|
|
||||||
@ -316,15 +247,13 @@ class Window(
|
|||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
||||||
val settingsFlow = context.streamSettings()
|
val settingsFlow = context.streamSettings()
|
||||||
val hrTargetFlow = karooSystem.streamDataFlow("TYPE_WORKOUT_HEART_RATE_TARGET_ID")
|
|
||||||
.map { (it as? StreamState.Streaming)?.dataPoint }
|
karooSystem.streamUserProfile()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
.combine(hrFlow) { userProfile, hr -> StreamData(userProfile, hr) }
|
||||||
data class StreamData(val userProfile: UserProfile, val value: Double?, val settings: PowerbarSettings? = null, val heartrateTarget: DataPoint? = null)
|
.combine(settingsFlow) { streamData, settings -> streamData.copy(settings = settings) }
|
||||||
|
.distinctUntilChanged()
|
||||||
combine(karooSystem.streamUserProfile(), hrFlow, settingsFlow, hrTargetFlow) { userProfile, hr, settings, hrTarget ->
|
.collect { streamData ->
|
||||||
StreamData(userProfile, hr, settings, hrTarget)
|
|
||||||
}.distinctUntilChanged().throttle(1_000).collect { streamData ->
|
|
||||||
val value = streamData.value?.roundToInt()
|
val value = streamData.value?.roundToInt()
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@ -334,10 +263,6 @@ class Window(
|
|||||||
val maxHr = customMaxHr ?: streamData.userProfile.maxHr
|
val maxHr = customMaxHr ?: streamData.userProfile.maxHr
|
||||||
val progress = remap(value.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
|
val progress = remap(value.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
|
||||||
|
|
||||||
powerbar.minTarget = remap(streamData.heartrateTarget?.values[FIELD_TARGET_MIN_ID]?.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
|
|
||||||
powerbar.maxTarget = remap(streamData.heartrateTarget?.values[FIELD_TARGET_MAX_ID]?.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
|
|
||||||
powerbar.target = remap(streamData.heartrateTarget?.values[FIELD_TARGET_VALUE_ID]?.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
|
|
||||||
|
|
||||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
||||||
context.getColor(getZone(streamData.userProfile.heartRateZones, value)?.colorResource ?: R.color.zone7)
|
context.getColor(getZone(streamData.userProfile.heartRateZones, value)?.colorResource ?: R.color.zone7)
|
||||||
} else {
|
} else {
|
||||||
@ -371,28 +296,21 @@ class Window(
|
|||||||
|
|
||||||
val settingsFlow = context.streamSettings()
|
val settingsFlow = context.streamSettings()
|
||||||
|
|
||||||
val powerTargetFlow = karooSystem.streamDataFlow("TYPE_WORKOUT_POWER_TARGET_ID") // TYPE_WORKOUT_HEART_RATE_TARGET_ID, TYPE_WORKOUT_CADENCE_TARGET_ID,
|
karooSystem.streamUserProfile()
|
||||||
.map { (it as? StreamState.Streaming)?.dataPoint }
|
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
.combine(powerFlow) { userProfile, hr -> StreamData(userProfile, hr) }
|
||||||
data class StreamData(val userProfile: UserProfile, val value: Double?, val settings: PowerbarSettings? = null, val powerTarget: DataPoint? = null)
|
.combine(settingsFlow) { streamData, settings -> streamData.copy(settings = settings) }
|
||||||
|
.distinctUntilChanged()
|
||||||
combine(karooSystem.streamUserProfile(), powerFlow, settingsFlow, powerTargetFlow) { userProfile, hr, settings, powerTarget ->
|
.collect { streamData ->
|
||||||
StreamData(userProfile, hr, settings, powerTarget)
|
|
||||||
}.distinctUntilChanged().throttle(1_000).collect { streamData ->
|
|
||||||
val value = streamData.value?.roundToInt()
|
val value = streamData.value?.roundToInt()
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
val customMinPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.minPower else 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 customMaxPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.maxPower else null
|
||||||
val minPower = customMinPower ?: streamData.userProfile.powerZones.first().min
|
val minPower = customMinPower ?: streamData.userProfile.powerZones.first().min
|
||||||
val maxPower = customMaxPower ?: (streamData.userProfile.powerZones.last().min + 30)
|
val maxPower = customMaxPower ?: (streamData.userProfile.powerZones.last().min + 50)
|
||||||
val progress = remap(value.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
|
val progress = remap(value.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
|
||||||
|
|
||||||
powerbar.minTarget = remap(streamData.powerTarget?.values[FIELD_TARGET_MIN_ID]?.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
|
|
||||||
powerbar.maxTarget = remap(streamData.powerTarget?.values[FIELD_TARGET_MAX_ID]?.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
|
|
||||||
powerbar.target = remap(streamData.powerTarget?.values[FIELD_TARGET_VALUE_ID]?.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
|
|
||||||
|
|
||||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
||||||
context.getColor(getZone(streamData.userProfile.powerZones, value)?.colorResource ?: R.color.zone7)
|
context.getColor(getZone(streamData.userProfile.powerZones, value)?.colorResource ?: R.color.zone7)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -57,8 +57,6 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||||
import de.timklge.karoopowerbar.CustomProgressBarBarSize
|
|
||||||
import de.timklge.karoopowerbar.CustomProgressBarFontSize
|
|
||||||
import de.timklge.karoopowerbar.CustomProgressBarSize
|
import de.timklge.karoopowerbar.CustomProgressBarSize
|
||||||
import de.timklge.karoopowerbar.KarooPowerbarExtension
|
import de.timklge.karoopowerbar.KarooPowerbarExtension
|
||||||
import de.timklge.karoopowerbar.PowerbarSettings
|
import de.timklge.karoopowerbar.PowerbarSettings
|
||||||
@ -68,6 +66,8 @@ import de.timklge.karoopowerbar.settingsKey
|
|||||||
import de.timklge.karoopowerbar.streamSettings
|
import de.timklge.karoopowerbar.streamSettings
|
||||||
import de.timklge.karoopowerbar.streamUserProfile
|
import de.timklge.karoopowerbar.streamUserProfile
|
||||||
import io.hammerhead.karooext.KarooSystemService
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
|
import io.hammerhead.karooext.models.HardwareType
|
||||||
|
import io.hammerhead.karooext.models.PlayBeepPattern
|
||||||
import io.hammerhead.karooext.models.UserProfile
|
import io.hammerhead.karooext.models.UserProfile
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
@ -87,8 +87,7 @@ enum class SelectedSource(val id: String, val label: String) {
|
|||||||
SPEED("speed", "Speed"),
|
SPEED("speed", "Speed"),
|
||||||
SPEED_3S("speed_3s", "Speed (3 sec avg"),
|
SPEED_3S("speed_3s", "Speed (3 sec avg"),
|
||||||
CADENCE("cadence", "Cadence"),
|
CADENCE("cadence", "Cadence"),
|
||||||
CADENCE_3S("cadence_3s", "Cadence (3 sec avg)"),
|
CADENCE_3S("cadence_3s", "Cadence (3 sec avg)");
|
||||||
ROUTE_PROGRESS("route_progress", "Route Progress");
|
|
||||||
|
|
||||||
fun isPower() = this == POWER || this == POWER_3S || this == POWER_10S
|
fun isPower() = this == POWER || this == POWER_3S || this == POWER_10S
|
||||||
}
|
}
|
||||||
@ -147,9 +146,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
var onlyShowWhileRiding by remember { mutableStateOf(false) }
|
var onlyShowWhileRiding by remember { mutableStateOf(false) }
|
||||||
var colorBasedOnZones by remember { mutableStateOf(false) }
|
var colorBasedOnZones by remember { mutableStateOf(false) }
|
||||||
var showLabelOnBars by remember { mutableStateOf(true) }
|
var showLabelOnBars by remember { mutableStateOf(true) }
|
||||||
var barBarSize by remember { mutableStateOf(CustomProgressBarBarSize.MEDIUM) }
|
var barSize by remember { mutableStateOf(CustomProgressBarSize.MEDIUM) }
|
||||||
var barFontSize by remember { mutableStateOf(CustomProgressBarFontSize.MEDIUM) }
|
|
||||||
var barBackground by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
var minCadence by remember { mutableStateOf("0") }
|
var minCadence by remember { mutableStateOf("0") }
|
||||||
var maxCadence by remember { mutableStateOf("0") }
|
var maxCadence by remember { mutableStateOf("0") }
|
||||||
@ -179,7 +176,6 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
val newSettings = PowerbarSettings(
|
val newSettings = PowerbarSettings(
|
||||||
source = bottomSelectedSource, topBarSource = topSelectedSource,
|
source = bottomSelectedSource, topBarSource = topSelectedSource,
|
||||||
onlyShowWhileRiding = onlyShowWhileRiding, showLabelOnBars = showLabelOnBars,
|
onlyShowWhileRiding = onlyShowWhileRiding, showLabelOnBars = showLabelOnBars,
|
||||||
barBackground = barBackground,
|
|
||||||
useZoneColors = colorBasedOnZones,
|
useZoneColors = colorBasedOnZones,
|
||||||
minCadence = minCadence.toIntOrNull() ?: PowerbarSettings.defaultMinCadence,
|
minCadence = minCadence.toIntOrNull() ?: PowerbarSettings.defaultMinCadence,
|
||||||
maxCadence = maxCadence.toIntOrNull() ?: PowerbarSettings.defaultMaxCadence,
|
maxCadence = maxCadence.toIntOrNull() ?: PowerbarSettings.defaultMaxCadence,
|
||||||
@ -188,8 +184,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
maxPower = customMaxPower.toIntOrNull(),
|
maxPower = customMaxPower.toIntOrNull(),
|
||||||
minHr = customMinHr.toIntOrNull(),
|
minHr = customMinHr.toIntOrNull(),
|
||||||
maxHr = customMaxHr.toIntOrNull(),
|
maxHr = customMaxHr.toIntOrNull(),
|
||||||
barBarSize = barBarSize,
|
barSize = barSize,
|
||||||
barFontSize = barFontSize,
|
|
||||||
useCustomPowerRange = useCustomPowerRange,
|
useCustomPowerRange = useCustomPowerRange,
|
||||||
useCustomHrRange = useCustomHrRange,
|
useCustomHrRange = useCustomHrRange,
|
||||||
)
|
)
|
||||||
@ -231,9 +226,7 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
onlyShowWhileRiding = settings.onlyShowWhileRiding
|
onlyShowWhileRiding = settings.onlyShowWhileRiding
|
||||||
showLabelOnBars = settings.showLabelOnBars
|
showLabelOnBars = settings.showLabelOnBars
|
||||||
colorBasedOnZones = settings.useZoneColors
|
colorBasedOnZones = settings.useZoneColors
|
||||||
barBarSize = settings.barBarSize
|
barSize = settings.barSize
|
||||||
barFontSize = settings.barFontSize
|
|
||||||
barBackground = settings.barBackground
|
|
||||||
minCadence = settings.minCadence.toString()
|
minCadence = settings.minCadence.toString()
|
||||||
maxCadence = settings.maxCadence.toString()
|
maxCadence = settings.maxCadence.toString()
|
||||||
isImperial = profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
|
isImperial = profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
|
||||||
@ -315,23 +308,12 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply {
|
apply {
|
||||||
val dropdownOptions = CustomProgressBarBarSize.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
|
val dropdownOptions = CustomProgressBarSize.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
|
||||||
val dropdownInitialSelection by remember(barBarSize) {
|
val dropdownInitialSelection by remember(barSize) {
|
||||||
mutableStateOf(dropdownOptions.find { option -> option.id == barBarSize.id }!!)
|
mutableStateOf(dropdownOptions.find { option -> option.id == barSize.id }!!)
|
||||||
}
|
}
|
||||||
Dropdown(label = "Bar Size", options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption ->
|
Dropdown(label = "Bar Size", options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption ->
|
||||||
barBarSize = CustomProgressBarBarSize.entries.find { unit -> unit.id == selectedOption.id }!!
|
barSize = CustomProgressBarSize.entries.find { unit -> unit.id == selectedOption.id }!!
|
||||||
coroutineScope.launch { updateSettings() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply {
|
|
||||||
val dropdownOptions = CustomProgressBarFontSize.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
|
|
||||||
val dropdownInitialSelection by remember(barFontSize) {
|
|
||||||
mutableStateOf(dropdownOptions.find { option -> option.id == barFontSize.id }!!)
|
|
||||||
}
|
|
||||||
Dropdown(label = "Text Size", options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption ->
|
|
||||||
barFontSize = CustomProgressBarFontSize.entries.find { unit -> unit.id == selectedOption.id }!!
|
|
||||||
coroutineScope.launch { updateSettings() }
|
coroutineScope.launch { updateSettings() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -488,15 +470,6 @@ fun MainScreen(onFinish: () -> Unit) {
|
|||||||
Text("Show value on bars")
|
Text("Show value on bars")
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Switch(checked = barBackground, onCheckedChange = {
|
|
||||||
barBackground = it
|
|
||||||
coroutineScope.launch { updateSettings() }
|
|
||||||
})
|
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
|
||||||
Text("Opaque background")
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Switch(checked = onlyShowWhileRiding, onCheckedChange = {
|
Switch(checked = onlyShowWhileRiding, onCheckedChange = {
|
||||||
onlyShowWhileRiding = it
|
onlyShowWhileRiding = it
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="80dp">
|
android:layout_height="40dp">
|
||||||
|
|
||||||
<de.timklge.karoopowerbar.CustomProgressBar
|
<de.timklge.karoopowerbar.CustomProgressBar
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="80dp"
|
android:layout_height="40dp"
|
||||||
android:layout_gravity="center" />
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@ -14,7 +14,6 @@ lifecycleRuntimeKtx = "2.8.7"
|
|||||||
navigationRuntimeKtx = "2.8.4"
|
navigationRuntimeKtx = "2.8.4"
|
||||||
navigationCompose = "2.8.4"
|
navigationCompose = "2.8.4"
|
||||||
cardview = "1.0.0"
|
cardview = "1.0.0"
|
||||||
mapboxSdkTurf = "7.3.1"
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
@ -25,7 +24,7 @@ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "
|
|||||||
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||||
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
color = { module = "com.maxkeppeler.sheets-compose-dialogs:color", version.ref = "color" }
|
color = { module = "com.maxkeppeler.sheets-compose-dialogs:color", version.ref = "color" }
|
||||||
hammerhead-karoo-ext = { group = "io.hammerhead", name = "karoo-ext", version = "1.1.5" }
|
hammerhead-karoo-ext = { group = "io.hammerhead", name = "karoo-ext", version = "1.1.2" }
|
||||||
|
|
||||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
||||||
|
|
||||||
@ -44,7 +43,6 @@ androidx-navigation-runtime-ktx = { group = "androidx.navigation", name = "navig
|
|||||||
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
||||||
androidx-cardview = { group = "androidx.cardview", name = "cardview", version.ref = "cardview" }
|
androidx-cardview = { group = "androidx.cardview", name = "cardview", version.ref = "cardview" }
|
||||||
mapbox-sdk-turf = { module = "com.mapbox.mapboxsdk:mapbox-sdk-turf", version.ref = "mapboxSdkTurf" }
|
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
androidx-lifeycle = ["androidx-lifecycle-runtime-compose", "androidx-lifecycle-viewmodel-compose"]
|
androidx-lifeycle = ["androidx-lifecycle-runtime-compose", "androidx-lifecycle-viewmodel-compose"]
|
||||||
|
|||||||
@ -34,11 +34,6 @@ dependencyResolutionManagement {
|
|||||||
password = gprKey
|
password = gprKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mapbox
|
|
||||||
maven {
|
|
||||||
url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user