From 93a1aa249a6c2d50dd3ecfb12dda4b555b666204 Mon Sep 17 00:00:00 2001 From: claude Date: Fri, 24 Jul 2026 18:21:24 +0200 Subject: [PATCH] =?UTF-8?q?Robust=20referensdata-h=C3=A4mtning=20+=20auto-?= =?UTF-8?q?retry=20i=20=C3=B6vningsv=C3=A4ljaren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Om servern saknar de nya fälten (iconKey/defaultRestSeconds) faller queries tillbaka på den äldre formen istället för att lämna cachen tom. Varje cachesektion hämtas oberoende. Väljaren triggar en ny hämtning när cachen är tom och har en Uppdatera-knapp i tomläget. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Kf4MAbZ3c3B4Xy5XfuGsCP --- .../fitnessdroid/data/GymRepository.kt | 70 ++++++++++++------- .../ui/picker/ExercisePickerScreen.kt | 26 +++++-- .../fitnessdroid/ui/picker/PickerViewModel.kt | 10 +++ 3 files changed, 75 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/eu/brassepc/fitnessdroid/data/GymRepository.kt b/app/src/main/java/eu/brassepc/fitnessdroid/data/GymRepository.kt index af49e28..ff4f0d4 100644 --- a/app/src/main/java/eu/brassepc/fitnessdroid/data/GymRepository.kt +++ b/app/src/main/java/eu/brassepc/fitnessdroid/data/GymRepository.kt @@ -144,39 +144,59 @@ class GymRepository( private suspend fun refreshReferenceData() { val token = auth.bearerToken() - val groups = client.execute("query{muscleGroups{id name iconKey}}", token = token) - db.cacheDao().upsertMuscleGroups( - groups["muscleGroups"]!!.jsonArray.map { - val o = it.jsonObject - CachedMuscleGroup( - o["id"]!!.jsonPrimitive.int, - o["name"]!!.jsonPrimitive.content, - o["iconKey"]?.jsonPrimitive?.contentOrNull(), - ) + // Varje sektion för sig, och de nya API-fälten (iconKey, + // defaultRestSeconds) med fallback — en äldre server ska aldrig + // kunna lämna hela cachen tom. + runCatching { + val groups = try { + client.execute("query{muscleGroups{id name iconKey}}", token = token) + } catch (e: GraphQlException) { + client.execute("query{muscleGroups{id name}}", token = token) } - ) + db.cacheDao().upsertMuscleGroups( + groups["muscleGroups"]!!.jsonArray.map { + val o = it.jsonObject + CachedMuscleGroup( + o["id"]!!.jsonPrimitive.int, + o["name"]!!.jsonPrimitive.content, + o["iconKey"]?.jsonPrimitive?.contentOrNull(), + ) + } + ) + } - val muscles = client.execute("query{muscles{id name muscleGroupId}}", token = token) - db.cacheDao().upsertMuscles( - muscles["muscles"]!!.jsonArray.map { - val o = it.jsonObject - CachedMuscle( - o["id"]!!.jsonPrimitive.int, - o["name"]!!.jsonPrimitive.content, - o["muscleGroupId"]?.jsonPrimitive?.intOrNull, - ) - } - ) + runCatching { + val muscles = client.execute("query{muscles{id name muscleGroupId}}", token = token) + db.cacheDao().upsertMuscles( + muscles["muscles"]!!.jsonArray.map { + val o = it.jsonObject + CachedMuscle( + o["id"]!!.jsonPrimitive.int, + o["name"]!!.jsonPrimitive.content, + o["muscleGroupId"]?.jsonPrimitive?.intOrNull, + ) + } + ) + } val favIds: Set = runCatching { client.execute("query{favoriteExercises{id}}", token = token)["favoriteExercises"]!! .jsonArray.map { it.jsonObject["id"]!!.jsonPrimitive.int }.toSet() }.getOrDefault(emptySet()) - val types = client.execute( - "query{exerciseTypes{id name description metValue tracksWeight tracksReps tracksDistance tracksDuration isBodyweight defaultRestSeconds exerciseTypeMuscles{muscle{id}}}}", - token = token, - ) + val typeFields = + "id name description metValue tracksWeight tracksReps tracksDistance tracksDuration isBodyweight" + val types = try { + client.execute( + "query{exerciseTypes{$typeFields defaultRestSeconds exerciseTypeMuscles{muscle{id}}}}", + token = token, + ) + } catch (e: GraphQlException) { + client.execute( + "query{exerciseTypes{$typeFields exerciseTypeMuscles{muscle{id}}}}", + token = token, + ) + } db.cacheDao().replaceExerciseTypes( types["exerciseTypes"]!!.jsonArray.map { val o = it.jsonObject diff --git a/app/src/main/java/eu/brassepc/fitnessdroid/ui/picker/ExercisePickerScreen.kt b/app/src/main/java/eu/brassepc/fitnessdroid/ui/picker/ExercisePickerScreen.kt index 2a3c24a..954a100 100644 --- a/app/src/main/java/eu/brassepc/fitnessdroid/ui/picker/ExercisePickerScreen.kt +++ b/app/src/main/java/eu/brassepc/fitnessdroid/ui/picker/ExercisePickerScreen.kt @@ -180,12 +180,26 @@ fun ExercisePickerScreen( } if (uiState.exercises.isEmpty()) { item { - Text( - "Inga övningar matchar — testa ett annat filter.", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(16.dp), - ) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + if (uiState.query.isBlank() && uiState.selectedGroupId == null) { + "Övningslistan är tom — hämtar från servern …" + } else { + "Inga övningar matchar — testa ett annat filter." + }, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + androidx.compose.material3.FilledTonalButton( + onClick = viewModel::refresh, + modifier = Modifier.padding(top = 12.dp), + ) { Text("Uppdatera från servern") } + } } } } diff --git a/app/src/main/java/eu/brassepc/fitnessdroid/ui/picker/PickerViewModel.kt b/app/src/main/java/eu/brassepc/fitnessdroid/ui/picker/PickerViewModel.kt index fb278f5..b382e73 100644 --- a/app/src/main/java/eu/brassepc/fitnessdroid/ui/picker/PickerViewModel.kt +++ b/app/src/main/java/eu/brassepc/fitnessdroid/ui/picker/PickerViewModel.kt @@ -43,6 +43,16 @@ class PickerViewModel(private val repo: GymRepository) : ViewModel() { private val selectedGroup = MutableStateFlow(null) private val selectedMuscle = MutableStateFlow(null) + init { + // Tom cache (t.ex. första start eller misslyckad förra hämtning)? + // Försök fylla den direkt när väljaren öppnas. + viewModelScope.launch { + if (repo.exerciseTypes.first().isEmpty()) repo.refreshAllAsync() + } + } + + fun refresh() = repo.refreshAllAsync() + private data class Filters(val query: String, val groupId: Int?, val muscleId: Int?) private val filters = combine(query, selectedGroup, selectedMuscle) { q, g, m -> Filters(q, g, m) }