Behörigheter: snabblänk till appinställningarna + robustare GPS-flöde
Some checks failed
release / build-release (push) Failing after 1h10m44s
Some checks failed
release / build-release (push) Failing after 1h10m44s
- 'Öppna appinställningarna'-knapp när plats- eller BT-behörighet saknas (spårning, vågparning, vägning) — systemets platslista visar bara appar som frågat, så det här är snabbaste vägen att slå på/av manuellt - 'Försök igen'-knapp som ställer behörighetsfrågan på nytt - POST_NOTIFICATIONS begärs bara på Android 13+ (fanns inte på 12) - Typladdningen i spårskärmen kan inte längre hänga i evig spinner — felmeddelande + försök igen vid nätfel. Version 0.7.1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C2GA94E1f3cmrvdQLQhYdg
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package eu.brassepc.fitnessdroid.ui.common
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
|
||||
/**
|
||||
* Öppna systemets inställningssida för appen — snabbvägen att slå på
|
||||
* behörigheter (plats, Bluetooth, notiser) som nekats eller aldrig frågats.
|
||||
*/
|
||||
fun Context.openAppSettings() {
|
||||
runCatching {
|
||||
startActivity(
|
||||
Intent(
|
||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||
Uri.fromParts("package", packageName, null),
|
||||
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@ import eu.brassepc.fitnessdroid.data.ScaleManager
|
||||
import eu.brassepc.fitnessdroid.data.SettingsStore
|
||||
import eu.brassepc.fitnessdroid.ui.appContainer
|
||||
import eu.brassepc.fitnessdroid.ui.common.compact
|
||||
import eu.brassepc.fitnessdroid.ui.common.openAppSettings
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
@@ -235,6 +236,10 @@ fun ScalePairScreen(
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
FilledTonalButton(
|
||||
onClick = { context.openAppSettings() },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text("Öppna appinställningarna") }
|
||||
}
|
||||
scanError?.let {
|
||||
Text(it, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.error)
|
||||
@@ -533,6 +538,10 @@ fun WeighScreen(
|
||||
"Bluetooth-behörighet nekades — kan inte nå vågen.",
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
FilledTonalButton(
|
||||
onClick = { context.openAppSettings() },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text("Öppna appinställningarna") }
|
||||
}
|
||||
|
||||
when (val s = state) {
|
||||
|
||||
@@ -62,6 +62,7 @@ import eu.brassepc.fitnessdroid.data.encodePolyline
|
||||
import eu.brassepc.fitnessdroid.ui.activities.RPE_LABELS
|
||||
import eu.brassepc.fitnessdroid.ui.appContainer
|
||||
import eu.brassepc.fitnessdroid.ui.common.compact
|
||||
import eu.brassepc.fitnessdroid.ui.common.openAppSettings
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.osmdroid.config.Configuration
|
||||
@@ -82,21 +83,25 @@ class TrackViewModel(
|
||||
/** Sammanfattningen som visas efter stopp (null = spårning pågår/ej startad) */
|
||||
val summary = MutableStateFlow<TrackingState?>(null)
|
||||
|
||||
var pendingType: ActivityType? = null
|
||||
private set
|
||||
val pendingType = MutableStateFlow<ActivityType?>(null)
|
||||
val typeLoadError = MutableStateFlow(false)
|
||||
|
||||
fun loadType(typeId: Int, onMissing: () -> Unit) {
|
||||
fun loadType(typeId: Int) {
|
||||
viewModelScope.launch {
|
||||
typeLoadError.value = false
|
||||
if (TrackingService.state.value.isActive) return@launch
|
||||
runCatching { gymApi.activityTypes().find { it.id == typeId } }
|
||||
.getOrNull()
|
||||
?.let { pendingType = it }
|
||||
?: onMissing()
|
||||
.fold(
|
||||
onSuccess = { t ->
|
||||
if (t != null) pendingType.value = t else typeLoadError.value = true
|
||||
},
|
||||
onFailure = { typeLoadError.value = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun start(context: android.content.Context) {
|
||||
val type = pendingType ?: return
|
||||
val type = pendingType.value ?: return
|
||||
viewModelScope.launch {
|
||||
TrackingService.start(context, type, repo.bodyWeightKg())
|
||||
}
|
||||
@@ -154,6 +159,8 @@ fun TrackScreen(
|
||||
val saved by viewModel.saved.collectAsStateWithLifecycle()
|
||||
val message by viewModel.message.collectAsStateWithLifecycle()
|
||||
|
||||
val pendingType by viewModel.pendingType.collectAsStateWithLifecycle()
|
||||
val typeLoadError by viewModel.typeLoadError.collectAsStateWithLifecycle()
|
||||
var permissionDenied by remember { mutableStateOf(false) }
|
||||
var started by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -162,7 +169,7 @@ fun TrackScreen(
|
||||
) { granted ->
|
||||
// Notis-permission är trevlig men inte nödvändig; GPS krävs för distans
|
||||
val fineOk = granted[Manifest.permission.ACCESS_FINE_LOCATION] ?: false
|
||||
val needsGps = viewModel.pendingType?.isDistanceBased == true
|
||||
val needsGps = viewModel.pendingType.value?.isDistanceBased == true
|
||||
if (!needsGps || fineOk) {
|
||||
viewModel.start(context)
|
||||
started = true
|
||||
@@ -171,21 +178,19 @@ fun TrackScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Ladda typen och starta (om inte en spårning redan pågår)
|
||||
LaunchedEffect(typeId) {
|
||||
if (tracking.isActive) { started = true; return@LaunchedEffect }
|
||||
viewModel.loadType(typeId, onMissing = onBack)
|
||||
// Vänta in typen
|
||||
while (viewModel.pendingType == null && !tracking.isActive) {
|
||||
kotlinx.coroutines.delay(100)
|
||||
}
|
||||
val type = viewModel.pendingType ?: return@LaunchedEffect
|
||||
/** Begär saknade behörigheter (eller starta direkt) — körs även från "Försök igen". */
|
||||
fun tryStart() {
|
||||
val type = pendingType ?: return
|
||||
permissionDenied = false
|
||||
val needed = buildList {
|
||||
if (type.isDistanceBased) {
|
||||
add(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
add(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
}
|
||||
add(Manifest.permission.POST_NOTIFICATIONS)
|
||||
// POST_NOTIFICATIONS finns först i Android 13 (API 33)
|
||||
if (android.os.Build.VERSION.SDK_INT >= 33) {
|
||||
add(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
}.filter {
|
||||
ContextCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
@@ -197,6 +202,16 @@ fun TrackScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Ladda typen (om inte en spårning redan pågår) …
|
||||
LaunchedEffect(typeId) {
|
||||
if (tracking.isActive) { started = true; return@LaunchedEffect }
|
||||
viewModel.loadType(typeId)
|
||||
}
|
||||
// … och starta när den kommit
|
||||
LaunchedEffect(pendingType) {
|
||||
if (pendingType != null && !started && !tracking.isActive) tryStart()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@@ -208,7 +223,7 @@ fun TrackScreen(
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
tracking.typeName.ifBlank { viewModel.pendingType?.nameSv ?: "Aktivitet" },
|
||||
tracking.typeName.ifBlank { pendingType?.nameSv ?: "Aktivitet" },
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
)
|
||||
}
|
||||
@@ -234,12 +249,43 @@ fun TrackScreen(
|
||||
onSave = { rpe, overrideId -> viewModel.save(rpe, overrideId) },
|
||||
onDone = onBack,
|
||||
)
|
||||
permissionDenied -> Column(modifier = Modifier.padding(16.dp)) {
|
||||
permissionDenied -> Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Text(
|
||||
"Platsbehörighet nekades — GPS-spårning kräver den. " +
|
||||
"Ge appen platsåtkomst i systeminställningarna och försök igen.",
|
||||
"Platsbehörighet saknas — GPS-spårning kräver den.",
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
Text(
|
||||
"Öppna appens inställningar och ge \"Plats\"-behörigheten " +
|
||||
"(Tillåt endast när appen används räcker), kom sen tillbaka " +
|
||||
"och tryck Försök igen.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Button(
|
||||
onClick = { context.openAppSettings() },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text("Öppna appinställningarna") }
|
||||
androidx.compose.material3.FilledTonalButton(
|
||||
onClick = { tryStart() },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text("Försök igen") }
|
||||
}
|
||||
typeLoadError -> Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Text(
|
||||
"Kunde inte hämta aktivitetstypen — offline?",
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
Button(
|
||||
onClick = { viewModel.loadType(typeId) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text("Försök igen") }
|
||||
}
|
||||
!tracking.isActive && !started -> Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
||||
Reference in New Issue
Block a user