diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2b6e968..7f4be0f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,7 +29,7 @@ android { minSdk = 31 targetSdk = 35 versionCode = ciRunNumber ?: 1 - versionName = "0.8.0" + (ciRunNumber?.let { "+build.$it" } ?: "-dev") + versionName = "0.8.1" + (ciRunNumber?.let { "+build.$it" } ?: "-dev") } signingConfigs { diff --git a/app/src/main/java/eu/brassepc/fitnessdroid/data/TrackingService.kt b/app/src/main/java/eu/brassepc/fitnessdroid/data/TrackingService.kt index 208dd5b..b706f71 100644 --- a/app/src/main/java/eu/brassepc/fitnessdroid/data/TrackingService.kt +++ b/app/src/main/java/eu/brassepc/fitnessdroid/data/TrackingService.kt @@ -48,6 +48,8 @@ data class TrackingState( val elevationGainMeters: Double = 0.0, /** Senaste GPS-position (lat, lon) + hela spåret */ val points: List> = emptyList(), + /** Senast kända position oavsett kvalitet — för kartcentrering/markör */ + val currentPosition: Pair? = null, val currentSpeedKmh: Double? = null, val kcal: Double? = null, val gpsFix: Boolean = false, @@ -164,15 +166,34 @@ class TrackingService : Service(), LocationListener { TrackLog.log("FEL: platsbehörighet saknas — ingen GPS-lyssning") return } - locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager - TrackLog.log("GPS-lyssning begärd (GPS_PROVIDER, 2 s / 3 m)") - runCatching { - locationManager?.requestLocationUpdates( - LocationManager.GPS_PROVIDER, - 2000L, // ms - 3f, // meter - this, - Looper.getMainLooper(), + val lm = getSystemService(Context.LOCATION_SERVICE) as LocationManager + locationManager = lm + + // Billiga telefoner levererar ofta bara via fused/network — lyssna på + // allt som finns och filtrera på noggrannhet istället för provider. + val providers = listOf( + LocationManager.GPS_PROVIDER, + LocationManager.FUSED_PROVIDER, + LocationManager.NETWORK_PROVIDER, + ) + for (provider in providers) { + val exists = runCatching { lm.allProviders.contains(provider) }.getOrDefault(false) + val enabled = exists && runCatching { lm.isProviderEnabled(provider) }.getOrDefault(false) + TrackLog.log("provider $provider: ${if (!exists) "saknas" else if (enabled) "på" else "AVSTÄNGD"}") + if (!enabled) continue + runCatching { + lm.requestLocationUpdates(provider, 2000L, 0f, this, Looper.getMainLooper()) + TrackLog.log("lyssnar på $provider (2 s)") + }.onFailure { TrackLog.log("FEL: kunde inte lyssna på $provider: ${it.message}") } + } + + // Senast kända position → omedelbar kartcentrering (läggs inte i spåret) + providers.firstNotNullOfOrNull { p -> + runCatching { lm.getLastKnownLocation(p) }.getOrNull() + }?.let { last -> + TrackLog.log("lastKnown: ${last.provider} acc=${if (last.hasAccuracy()) "%.0f".format(last.accuracy) else "?"} m") + _state.value = _state.value.copy( + currentPosition = last.latitude to last.longitude, ) } } @@ -192,16 +213,21 @@ class TrackingService : Service(), LocationListener { val s = _state.value if (!s.isActive || !s.isDistanceBased) return val acc = if (location.hasAccuracy()) location.accuracy else -1f - // Släng riktigt dåliga fixar - if (location.hasAccuracy() && location.accuracy > 30f) { - TrackLog.log("fix förkastad: noggrannhet ${"%.0f".format(acc)} m (>30)") + + // Position för kartan uppdateras alltid, oavsett kvalitet + _state.value = _state.value.copy(currentPosition = location.latitude to location.longitude) + + // Spårpunkter kräver hygglig noggrannhet + if (location.hasAccuracy() && location.accuracy > 35f) { + TrackLog.log("${location.provider}: förkastad för spåret, acc=${"%.0f".format(acc)} m (>35)") return } TrackLog.log( - "fix: acc=${"%.0f".format(acc)} m alt=${if (location.hasAltitude()) "%.0f".format(location.altitude) else "?"}" + + "${location.provider}: fix acc=${"%.0f".format(acc)} m" + + " alt=${if (location.hasAltitude()) "%.0f".format(location.altitude) else "?"}" + " fart=${if (location.hasSpeed()) "%.1f".format(location.speed * 3.6) else "?"} km/h" ) - if (s.phase == TrackingPhase.SEARCHING_GPS) activate("GPS-fix") + if (s.phase == TrackingPhase.SEARCHING_GPS) activate("fix från ${location.provider}") var distance = s.distanceMeters var elevGain = s.elevationGainMeters diff --git a/app/src/main/java/eu/brassepc/fitnessdroid/ui/track/TrackScreen.kt b/app/src/main/java/eu/brassepc/fitnessdroid/ui/track/TrackScreen.kt index dd4f5a5..2c9429a 100644 --- a/app/src/main/java/eu/brassepc/fitnessdroid/ui/track/TrackScreen.kt +++ b/app/src/main/java/eu/brassepc/fitnessdroid/ui/track/TrackScreen.kt @@ -339,6 +339,7 @@ private fun LiveContent( if (tracking.isDistanceBased) { TrackMap( points = tracking.points, + currentPosition = tracking.currentPosition, modifier = Modifier .fillMaxWidth() .weight(1f), @@ -484,7 +485,11 @@ private fun StatBox(label: String, value: String, modifier: Modifier = Modifier) } @Composable -private fun TrackMap(points: List>, modifier: Modifier = Modifier) { +private fun TrackMap( + points: List>, + currentPosition: Pair?, + modifier: Modifier = Modifier, +) { val context = LocalContext.current val lineColor = MaterialTheme.colorScheme.primary.toArgb() @@ -506,6 +511,14 @@ private fun TrackMap(points: List>, modifier: Modifier = Mo outlinePaint.strokeWidth = 10f }.also { mapView.overlays.add(it) } } + val positionMarker = remember { + org.osmdroid.views.overlay.Marker(mapView).apply { + setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER, org.osmdroid.views.overlay.Marker.ANCHOR_CENTER) + setInfoWindow(null) + }.also { mapView.overlays.add(it) } + } + // Centrera bara automatiskt tills användaren själv panorerat + var hasCentered by remember { mutableStateOf(false) } DisposableEffect(Unit) { mapView.onResume() @@ -518,13 +531,25 @@ private fun TrackMap(points: List>, modifier: Modifier = Mo AndroidView( factory = { mapView }, modifier = modifier, - update = { + update = { map -> + val pos = points.lastOrNull() ?: currentPosition + if (pos != null) { + positionMarker.position = GeoPoint(pos.first, pos.second) + positionMarker.isEnabled = true + if (!hasCentered) { + map.controller.setZoom(17.0) + map.controller.setCenter(GeoPoint(pos.first, pos.second)) + hasCentered = true + } else { + map.controller.animateTo(GeoPoint(pos.first, pos.second)) + } + } else { + positionMarker.isEnabled = false + } if (points.isNotEmpty()) { polyline.setPoints(points.map { (lat, lon) -> GeoPoint(lat, lon) }) - val last = points.last() - it.controller.animateTo(GeoPoint(last.first, last.second)) - it.invalidate() } + map.invalidate() }, ) }