Add route distance data source, workout target indication
This commit is contained in:
parent
f343f7c318
commit
72b9a0f57e
@ -102,4 +102,5 @@ 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)
|
||||||
}
|
}
|
||||||
@ -26,6 +26,9 @@ 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
|
||||||
@ColorInt var progressColor: Int = 0xFF2b86e6.toInt()
|
@ColorInt var progressColor: Int = 0xFF2b86e6.toInt()
|
||||||
|
|
||||||
@ -35,6 +38,15 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
textPaint.textSize = value.fontSize
|
textPaint.textSize = value.fontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val targetColor = 0xFF9933FF.toInt()
|
||||||
|
|
||||||
|
private val targetZonePaint = Paint().apply {
|
||||||
|
isAntiAlias = true
|
||||||
|
strokeWidth = 10f
|
||||||
|
style = Paint.Style.STROKE
|
||||||
|
color = targetColor
|
||||||
|
}
|
||||||
|
|
||||||
private val linePaint = Paint().apply {
|
private val linePaint = Paint().apply {
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
strokeWidth = 1f
|
strokeWidth = 1f
|
||||||
@ -81,28 +93,39 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
color = Color.WHITE
|
color = Color.WHITE
|
||||||
strokeWidth = 3f
|
strokeWidth = 3f
|
||||||
textSize = size.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
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDrawForeground(canvas: Canvas) {
|
override fun onDrawForeground(canvas: Canvas) {
|
||||||
super.onDrawForeground(canvas)
|
super.onDrawForeground(canvas)
|
||||||
|
|
||||||
linePaint.color = progressColor
|
// Determine if the current progress is within the target range
|
||||||
lineStrokePaint.color = progressColor
|
val isTargetMet = progress != null && minTarget != null && maxTarget != null && progress!! >= minTarget!! && progress!! <= maxTarget!!
|
||||||
blurPaint.color = progressColor
|
|
||||||
blurPaintHighlight.color = ColorUtils.blendARGB(progressColor, 0xFFFFFF, 0.5f)
|
val color = if (isTargetMet) {
|
||||||
|
targetColor // Use Zone 8 color if target is met
|
||||||
|
} else {
|
||||||
|
progressColor // Use default progress color otherwise
|
||||||
|
}
|
||||||
|
|
||||||
|
linePaint.color = color
|
||||||
|
lineStrokePaint.color = color
|
||||||
|
blurPaint.color = color
|
||||||
|
blurPaintHighlight.color = ColorUtils.blendARGB(color, 0xFFFFFF, 0.5f)
|
||||||
|
|
||||||
when(location){
|
when(location){
|
||||||
PowerbarLocation.TOP -> {
|
PowerbarLocation.TOP -> {
|
||||||
|
val barTop = 15f
|
||||||
|
val barBottom = barTop + size.barHeight
|
||||||
val rect = RectF(
|
val rect = RectF(
|
||||||
1f,
|
1f,
|
||||||
15f,
|
barTop,
|
||||||
((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 + size.barHeight
|
barBottom
|
||||||
)
|
)
|
||||||
|
|
||||||
canvas.drawRect(0f, 15f, canvas.width.toFloat(), 15f + size.barHeight, backgroundPaint)
|
canvas.drawRect(0f, barTop, canvas.width.toFloat(), barBottom, backgroundPaint)
|
||||||
|
|
||||||
if (progress != null) {
|
if (progress != null) {
|
||||||
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
||||||
@ -110,6 +133,19 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
|
|
||||||
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 target indicator lines first to be "behind" the label
|
||||||
|
if (minTarget != null && maxTarget != null) {
|
||||||
|
val minTargetX = (canvas.width * minTarget!!).toFloat()
|
||||||
|
val maxTargetX = (canvas.width * maxTarget!!).toFloat()
|
||||||
|
val edgeOffset = targetZonePaint.strokeWidth / 2f
|
||||||
|
|
||||||
|
// Horizontal line at the top edge of the canvas
|
||||||
|
canvas.drawLine(minTargetX, edgeOffset, maxTargetX, edgeOffset, targetZonePaint)
|
||||||
|
// Vertical lines spanning most of the canvas height
|
||||||
|
canvas.drawLine(minTargetX, edgeOffset, minTargetX, edgeOffset + 40, targetZonePaint)
|
||||||
|
canvas.drawLine(maxTargetX, edgeOffset, maxTargetX, edgeOffset + 40, targetZonePaint)
|
||||||
|
}
|
||||||
|
|
||||||
if (showLabel){
|
if (showLabel){
|
||||||
val textBounds = textPaint.measureText(label)
|
val textBounds = textPaint.measureText(label)
|
||||||
val xOffset = (textBounds + 20).coerceAtLeast(10f) / 2f
|
val xOffset = (textBounds + 20).coerceAtLeast(10f) / 2f
|
||||||
@ -122,23 +158,28 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
val r = x + xOffset * 2
|
val r = x + xOffset * 2
|
||||||
val b = rect.bottom + yOffset
|
val b = rect.bottom + yOffset
|
||||||
|
|
||||||
|
// Use targetZonePaint for outline if target is met
|
||||||
|
val currentOutlinePaint = if (isTargetMet) targetZonePaint else lineStrokePaint
|
||||||
|
|
||||||
canvas.drawRoundRect(x, y, r, b, 2f, 2f, textBackgroundPaint)
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, textBackgroundPaint)
|
||||||
canvas.drawRoundRect(x, y, r, b, 2f, 2f, blurPaint)
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, blurPaint)
|
||||||
canvas.drawRoundRect(x, y, r, b, 2f, 2f, lineStrokePaint)
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, currentOutlinePaint)
|
||||||
|
|
||||||
canvas.drawText(label, x + xOffset, rect.top + size.barHeight + 6, textPaint)
|
canvas.drawText(label, x + xOffset, rect.top + size.barHeight + 6, textPaint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PowerbarLocation.BOTTOM -> {
|
PowerbarLocation.BOTTOM -> {
|
||||||
|
val barTop = canvas.height.toFloat() - 1f - size.barHeight
|
||||||
|
val barBottom = canvas.height.toFloat()
|
||||||
val rect = RectF(
|
val rect = RectF(
|
||||||
1f,
|
1f,
|
||||||
canvas.height.toFloat() - 1f - size.barHeight,
|
barTop,
|
||||||
((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()
|
barBottom
|
||||||
)
|
)
|
||||||
|
|
||||||
canvas.drawRect(0f, canvas.height.toFloat() - size.barHeight, canvas.width.toFloat(), canvas.height.toFloat(), backgroundPaint)
|
canvas.drawRect(0f, barTop, canvas.width.toFloat(), barBottom, backgroundPaint)
|
||||||
|
|
||||||
if (progress != null) {
|
if (progress != null) {
|
||||||
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
||||||
@ -146,6 +187,19 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
|
|
||||||
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 target indicator lines first to be "behind" the label
|
||||||
|
if (minTarget != null && maxTarget != null) {
|
||||||
|
val minTargetX = (canvas.width * minTarget!!).toFloat()
|
||||||
|
val maxTargetX = (canvas.width * maxTarget!!).toFloat()
|
||||||
|
val edgeOffset = targetZonePaint.strokeWidth / 2f
|
||||||
|
|
||||||
|
// Horizontal line at the bottom edge of the canvas
|
||||||
|
canvas.drawLine(minTargetX, canvas.height - edgeOffset, maxTargetX, canvas.height - edgeOffset, targetZonePaint)
|
||||||
|
// Vertical lines spanning most of the canvas height
|
||||||
|
canvas.drawLine(minTargetX, canvas.height - edgeOffset - 40, minTargetX, canvas.height - edgeOffset, targetZonePaint)
|
||||||
|
canvas.drawLine(maxTargetX, canvas.height - edgeOffset - 40, maxTargetX, canvas.height - edgeOffset, targetZonePaint)
|
||||||
|
}
|
||||||
|
|
||||||
if (showLabel){
|
if (showLabel){
|
||||||
val textBounds = textPaint.measureText(label)
|
val textBounds = textPaint.measureText(label)
|
||||||
val xOffset = (textBounds + 20).coerceAtLeast(10f) / 2f
|
val xOffset = (textBounds + 20).coerceAtLeast(10f) / 2f
|
||||||
@ -159,9 +213,12 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
val r = x + xOffset * 2
|
val r = x + xOffset * 2
|
||||||
val b = rect.bottom + 5
|
val b = rect.bottom + 5
|
||||||
|
|
||||||
|
// Use targetZonePaint for outline if target is met
|
||||||
|
val currentOutlinePaint = if (isTargetMet) targetZonePaint else lineStrokePaint
|
||||||
|
|
||||||
canvas.drawRoundRect(x, y, r, b, 2f, 2f, textBackgroundPaint)
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, textBackgroundPaint)
|
||||||
canvas.drawRoundRect(x, y, r, b, 2f, 2f, blurPaint)
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, blurPaint)
|
||||||
canvas.drawRoundRect(x, y, r, b, 2f, 2f, lineStrokePaint)
|
canvas.drawRoundRect(x, y, r, b, 2f, 2f, currentOutlinePaint)
|
||||||
|
|
||||||
canvas.drawText(label, x + xOffset, rect.top + size.barHeight - 1, textPaint)
|
canvas.drawText(label, x + xOffset, rect.top + size.barHeight - 1, textPaint)
|
||||||
}
|
}
|
||||||
@ -169,4 +226,4 @@ class CustomProgressBar @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
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 }
|
||||||
@ -35,6 +39,17 @@ 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 ->
|
||||||
@ -44,4 +59,11 @@ fun KarooSystemService.streamUserProfile(): Flow<UserProfile> {
|
|||||||
removeConsumer(listenerId)
|
removeConsumer(listenerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun<T> Flow<T>.throttle(timeout: Long): Flow<T> = this
|
||||||
|
.conflate()
|
||||||
|
.transform {
|
||||||
|
emit(it)
|
||||||
|
delay(timeout)
|
||||||
|
}
|
||||||
@ -17,10 +17,15 @@ 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
|
||||||
@ -29,12 +34,15 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +56,12 @@ class Window(
|
|||||||
val showLabel: Boolean,
|
val showLabel: Boolean,
|
||||||
val powerbarSize: CustomProgressBarSize
|
val powerbarSize: CustomProgressBarSize
|
||||||
) {
|
) {
|
||||||
|
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
|
||||||
@ -100,8 +114,6 @@ 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,6 +148,7 @@ 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 -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,6 +164,54 @@ 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?.singleValue, 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 }
|
||||||
@ -158,12 +219,11 @@ class Window(
|
|||||||
|
|
||||||
val settingsFlow = context.streamSettings()
|
val settingsFlow = context.streamSettings()
|
||||||
|
|
||||||
karooSystem.streamUserProfile()
|
data class StreamData(val userProfile: UserProfile, val value: Double?, val settings: PowerbarSettings? = null)
|
||||||
.distinctUntilChanged()
|
|
||||||
.combine(speedFlow) { userProfile, speed -> StreamData(userProfile, speed) }
|
combine(karooSystem.streamUserProfile(), speedFlow, settingsFlow) { userProfile, speed, settings ->
|
||||||
.combine(settingsFlow) { streamData, settings -> streamData.copy(settings = settings) }
|
StreamData(userProfile, speed, settings)
|
||||||
.distinctUntilChanged()
|
}.distinctUntilChanged().throttle(1_000).collect { streamData ->
|
||||||
.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)
|
||||||
@ -173,8 +233,7 @@ 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 =
|
val progress = remap(valueMetersPerSecond, minSpeed.toDouble(), maxSpeed.toDouble(), 0.0, 1.0) ?: 0.0
|
||||||
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
|
||||||
|
|
||||||
@ -199,46 +258,51 @@ class Window(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun streamCadence(smoothed: Boolean) {
|
private suspend fun streamCadence(smoothed: Boolean) {
|
||||||
val speedFlow = karooSystem.streamDataFlow(if(smoothed) DataType.Type.SMOOTHED_3S_AVERAGE_CADENCE else DataType.Type.CADENCE)
|
val cadenceFlow = 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")
|
||||||
karooSystem.streamUserProfile()
|
.map { (it as? StreamState.Streaming)?.dataPoint }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.combine(speedFlow) { userProfile, speed -> StreamData(userProfile, speed) }
|
|
||||||
.combine(settingsFlow) { streamData, settings -> streamData.copy(settings = settings) }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.collect { streamData ->
|
|
||||||
val value = streamData.value?.roundToInt()
|
|
||||||
|
|
||||||
if (value != null) {
|
combine(karooSystem.streamUserProfile(), cadenceFlow, settingsFlow, cadenceTargetFlow) { userProfile, speed, settings, cadenceTarget ->
|
||||||
val minCadence = streamData.settings?.minCadence ?: PowerbarSettings.defaultMinCadence
|
StreamData(userProfile, speed, settings, cadenceTarget)
|
||||||
val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence
|
}.distinctUntilChanged().throttle(1_000).collect { streamData ->
|
||||||
val progress =
|
val value = streamData.value?.roundToInt()
|
||||||
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
|
if (value != null) {
|
||||||
|
val minCadence = streamData.settings?.minCadence ?: PowerbarSettings.defaultMinCadence
|
||||||
|
val maxCadence = streamData.settings?.maxCadence ?: PowerbarSettings.defaultMaxCadence
|
||||||
|
val progress = remap(value.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0) ?: 0.0
|
||||||
|
|
||||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
powerbar.minTarget = remap(streamData.cadenceTarget?.values[FIELD_TARGET_MIN_ID]?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0)
|
||||||
context.getColor(zoneColorRes)
|
powerbar.maxTarget = remap(streamData.cadenceTarget?.values[FIELD_TARGET_MAX_ID]?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0)
|
||||||
} else {
|
powerbar.target = remap(streamData.cadenceTarget?.values[FIELD_TARGET_VALUE_ID]?.toDouble(), minCadence.toDouble(), maxCadence.toDouble(), 0.0, 1.0)
|
||||||
context.getColor(R.color.zone0)
|
|
||||||
}
|
|
||||||
powerbar.progress = if (value > 0) progress else null
|
|
||||||
powerbar.label = "$value"
|
|
||||||
|
|
||||||
Log.d(TAG, "Cadence: $value min: $minCadence max: $maxCadence")
|
@ColorRes val zoneColorRes = Zone.entries[(progress * Zone.entries.size).roundToInt().coerceIn(0..<Zone.entries.size)].colorResource
|
||||||
|
|
||||||
|
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
||||||
|
context.getColor(zoneColorRes)
|
||||||
} else {
|
} else {
|
||||||
powerbar.progressColor = context.getColor(R.color.zone0)
|
context.getColor(R.color.zone0)
|
||||||
powerbar.progress = null
|
|
||||||
powerbar.label = "?"
|
|
||||||
|
|
||||||
Log.d(TAG, "Cadence: Unavailable")
|
|
||||||
}
|
}
|
||||||
powerbar.invalidate()
|
powerbar.progress = if (value > 0) progress else null
|
||||||
|
powerbar.label = "$value"
|
||||||
|
|
||||||
|
Log.d(TAG, "Cadence: $value min: $minCadence max: $maxCadence")
|
||||||
|
} else {
|
||||||
|
powerbar.progressColor = context.getColor(R.color.zone0)
|
||||||
|
powerbar.progress = null
|
||||||
|
powerbar.label = "?"
|
||||||
|
|
||||||
|
Log.d(TAG, "Cadence: Unavailable")
|
||||||
}
|
}
|
||||||
|
powerbar.invalidate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun streamHeartrate() {
|
private suspend fun streamHeartrate() {
|
||||||
@ -247,40 +311,46 @@ class Window(
|
|||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
||||||
val settingsFlow = context.streamSettings()
|
val settingsFlow = context.streamSettings()
|
||||||
|
val hrTargetFlow = karooSystem.streamDataFlow("TYPE_WORKOUT_HEART_RATE_TARGET_ID")
|
||||||
karooSystem.streamUserProfile()
|
.map { (it as? StreamState.Streaming)?.dataPoint }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.combine(hrFlow) { userProfile, hr -> StreamData(userProfile, hr) }
|
|
||||||
.combine(settingsFlow) { streamData, settings -> streamData.copy(settings = settings) }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.collect { streamData ->
|
|
||||||
val value = streamData.value?.roundToInt()
|
|
||||||
|
|
||||||
if (value != null) {
|
data class StreamData(val userProfile: UserProfile, val value: Double?, val settings: PowerbarSettings? = null, val heartrateTarget: DataPoint? = null)
|
||||||
val customMinHr = if (streamData.settings?.useCustomHrRange == true) streamData.settings.minHr else null
|
|
||||||
val customMaxHr = if (streamData.settings?.useCustomHrRange == true) streamData.settings.maxHr else null
|
|
||||||
val minHr = customMinHr ?: streamData.userProfile.restingHr
|
|
||||||
val maxHr = customMaxHr ?: streamData.userProfile.maxHr
|
|
||||||
val progress = remap(value.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
|
|
||||||
|
|
||||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
combine(karooSystem.streamUserProfile(), hrFlow, settingsFlow, hrTargetFlow) { userProfile, hr, settings, hrTarget ->
|
||||||
context.getColor(getZone(streamData.userProfile.heartRateZones, value)?.colorResource ?: R.color.zone7)
|
StreamData(userProfile, hr, settings, hrTarget)
|
||||||
} else {
|
}.distinctUntilChanged().throttle(1_000).collect { streamData ->
|
||||||
context.getColor(R.color.zone0)
|
val value = streamData.value?.roundToInt()
|
||||||
}
|
|
||||||
powerbar.progress = if (value > 0) progress else null
|
|
||||||
powerbar.label = "$value"
|
|
||||||
|
|
||||||
Log.d(TAG, "Hr: $value min: $minHr max: $maxHr")
|
if (value != null) {
|
||||||
|
val customMinHr = if (streamData.settings?.useCustomHrRange == true) streamData.settings.minHr else null
|
||||||
|
val customMaxHr = if (streamData.settings?.useCustomHrRange == true) streamData.settings.maxHr else null
|
||||||
|
val minHr = customMinHr ?: streamData.userProfile.restingHr
|
||||||
|
val maxHr = customMaxHr ?: streamData.userProfile.maxHr
|
||||||
|
val progress = remap(value.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
|
||||||
|
|
||||||
|
powerbar.minTarget = remap(streamData.heartrateTarget?.values[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) {
|
||||||
|
context.getColor(getZone(streamData.userProfile.heartRateZones, value)?.colorResource ?: R.color.zone7)
|
||||||
} else {
|
} else {
|
||||||
powerbar.progressColor = context.getColor(R.color.zone0)
|
context.getColor(R.color.zone0)
|
||||||
powerbar.progress = null
|
|
||||||
powerbar.label = "?"
|
|
||||||
|
|
||||||
Log.d(TAG, "Hr: Unavailable")
|
|
||||||
}
|
}
|
||||||
powerbar.invalidate()
|
powerbar.progress = if (value > 0) progress else null
|
||||||
|
powerbar.label = "$value"
|
||||||
|
|
||||||
|
Log.d(TAG, "Hr: $value min: $minHr max: $maxHr")
|
||||||
|
} else {
|
||||||
|
powerbar.progressColor = context.getColor(R.color.zone0)
|
||||||
|
powerbar.progress = null
|
||||||
|
powerbar.label = "?"
|
||||||
|
|
||||||
|
Log.d(TAG, "Hr: Unavailable")
|
||||||
}
|
}
|
||||||
|
powerbar.invalidate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class PowerStreamSmoothing(val dataTypeId: String){
|
enum class PowerStreamSmoothing(val dataTypeId: String){
|
||||||
@ -296,39 +366,46 @@ class Window(
|
|||||||
|
|
||||||
val settingsFlow = context.streamSettings()
|
val settingsFlow = context.streamSettings()
|
||||||
|
|
||||||
karooSystem.streamUserProfile()
|
val powerTargetFlow = karooSystem.streamDataFlow("TYPE_WORKOUT_POWER_TARGET_ID") // TYPE_WORKOUT_HEART_RATE_TARGET_ID, TYPE_WORKOUT_CADENCE_TARGET_ID,
|
||||||
|
.map { (it as? StreamState.Streaming)?.dataPoint }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.combine(powerFlow) { userProfile, hr -> StreamData(userProfile, hr) }
|
|
||||||
.combine(settingsFlow) { streamData, settings -> streamData.copy(settings = settings) }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.collect { streamData ->
|
|
||||||
val value = streamData.value?.roundToInt()
|
|
||||||
|
|
||||||
if (value != null) {
|
data class StreamData(val userProfile: UserProfile, val value: Double?, val settings: PowerbarSettings? = null, val powerTarget: DataPoint? = null)
|
||||||
val customMinPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.minPower else null
|
|
||||||
val customMaxPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.maxPower else null
|
|
||||||
val minPower = customMinPower ?: streamData.userProfile.powerZones.first().min
|
|
||||||
val maxPower = customMaxPower ?: (streamData.userProfile.powerZones.last().min + 50)
|
|
||||||
val progress = remap(value.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
|
|
||||||
|
|
||||||
powerbar.progressColor = if (streamData.settings?.useZoneColors == true) {
|
combine(karooSystem.streamUserProfile(), powerFlow, settingsFlow, powerTargetFlow) { userProfile, hr, settings, powerTarget ->
|
||||||
context.getColor(getZone(streamData.userProfile.powerZones, value)?.colorResource ?: R.color.zone7)
|
StreamData(userProfile, hr, settings, powerTarget)
|
||||||
} else {
|
}.distinctUntilChanged().throttle(1_000).collect { streamData ->
|
||||||
context.getColor(R.color.zone0)
|
val value = streamData.value?.roundToInt()
|
||||||
}
|
|
||||||
powerbar.progress = if (value > 0) progress else null
|
|
||||||
powerbar.label = "${value}W"
|
|
||||||
|
|
||||||
Log.d(TAG, "Power: $value min: $minPower max: $maxPower")
|
if (value != null) {
|
||||||
|
val customMinPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.minPower else null
|
||||||
|
val customMaxPower = if (streamData.settings?.useCustomPowerRange == true) streamData.settings.maxPower else null
|
||||||
|
val minPower = customMinPower ?: streamData.userProfile.powerZones.first().min
|
||||||
|
val maxPower = customMaxPower ?: (streamData.userProfile.powerZones.last().min + 30)
|
||||||
|
val progress = remap(value.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
|
||||||
|
|
||||||
|
powerbar.minTarget = remap(streamData.powerTarget?.values[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) {
|
||||||
|
context.getColor(getZone(streamData.userProfile.powerZones, value)?.colorResource ?: R.color.zone7)
|
||||||
} else {
|
} else {
|
||||||
powerbar.progressColor = context.getColor(R.color.zone0)
|
context.getColor(R.color.zone0)
|
||||||
powerbar.progress = null
|
|
||||||
powerbar.label = "?"
|
|
||||||
|
|
||||||
Log.d(TAG, "Power: Unavailable")
|
|
||||||
}
|
}
|
||||||
powerbar.invalidate()
|
powerbar.progress = if (value > 0) progress else null
|
||||||
|
powerbar.label = "${value}W"
|
||||||
|
|
||||||
|
Log.d(TAG, "Power: $value min: $minPower max: $maxPower")
|
||||||
|
} else {
|
||||||
|
powerbar.progressColor = context.getColor(R.color.zone0)
|
||||||
|
powerbar.progress = null
|
||||||
|
powerbar.label = "?"
|
||||||
|
|
||||||
|
Log.d(TAG, "Power: Unavailable")
|
||||||
}
|
}
|
||||||
|
powerbar.invalidate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentHideJob: Job? = null
|
private var currentHideJob: Job? = null
|
||||||
|
|||||||
@ -87,7 +87,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ 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" }
|
||||||
@ -24,7 +25,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.2" }
|
hammerhead-karoo-ext = { group = "io.hammerhead", name = "karoo-ext", version = "1.1.5" }
|
||||||
|
|
||||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ 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,6 +34,11 @@ 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