Robust referensdata-hämtning + auto-retry i övningsväljaren
All checks were successful
release / build-release (push) Successful in 4m46s

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kf4MAbZ3c3B4Xy5XfuGsCP
This commit is contained in:
2026-07-24 18:21:24 +02:00
parent 2c17873ca4
commit 93a1aa249a
3 changed files with 75 additions and 31 deletions

View File

@@ -144,7 +144,15 @@ class GymRepository(
private suspend fun refreshReferenceData() { private suspend fun refreshReferenceData() {
val token = auth.bearerToken() val token = auth.bearerToken()
val groups = client.execute("query{muscleGroups{id name iconKey}}", token = token) // 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( db.cacheDao().upsertMuscleGroups(
groups["muscleGroups"]!!.jsonArray.map { groups["muscleGroups"]!!.jsonArray.map {
val o = it.jsonObject val o = it.jsonObject
@@ -155,7 +163,9 @@ class GymRepository(
) )
} }
) )
}
runCatching {
val muscles = client.execute("query{muscles{id name muscleGroupId}}", token = token) val muscles = client.execute("query{muscles{id name muscleGroupId}}", token = token)
db.cacheDao().upsertMuscles( db.cacheDao().upsertMuscles(
muscles["muscles"]!!.jsonArray.map { muscles["muscles"]!!.jsonArray.map {
@@ -167,16 +177,26 @@ class GymRepository(
) )
} }
) )
}
val favIds: Set<Int> = runCatching { val favIds: Set<Int> = runCatching {
client.execute("query{favoriteExercises{id}}", token = token)["favoriteExercises"]!! client.execute("query{favoriteExercises{id}}", token = token)["favoriteExercises"]!!
.jsonArray.map { it.jsonObject["id"]!!.jsonPrimitive.int }.toSet() .jsonArray.map { it.jsonObject["id"]!!.jsonPrimitive.int }.toSet()
}.getOrDefault(emptySet()) }.getOrDefault(emptySet())
val types = client.execute( val typeFields =
"query{exerciseTypes{id name description metValue tracksWeight tracksReps tracksDistance tracksDuration isBodyweight defaultRestSeconds exerciseTypeMuscles{muscle{id}}}}", "id name description metValue tracksWeight tracksReps tracksDistance tracksDuration isBodyweight"
val types = try {
client.execute(
"query{exerciseTypes{$typeFields defaultRestSeconds exerciseTypeMuscles{muscle{id}}}}",
token = token, token = token,
) )
} catch (e: GraphQlException) {
client.execute(
"query{exerciseTypes{$typeFields exerciseTypeMuscles{muscle{id}}}}",
token = token,
)
}
db.cacheDao().replaceExerciseTypes( db.cacheDao().replaceExerciseTypes(
types["exerciseTypes"]!!.jsonArray.map { types["exerciseTypes"]!!.jsonArray.map {
val o = it.jsonObject val o = it.jsonObject

View File

@@ -180,12 +180,26 @@ fun ExercisePickerScreen(
} }
if (uiState.exercises.isEmpty()) { if (uiState.exercises.isEmpty()) {
item { item {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text( Text(
"Inga övningar matchar — testa ett annat filter.", 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, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(16.dp),
) )
androidx.compose.material3.FilledTonalButton(
onClick = viewModel::refresh,
modifier = Modifier.padding(top = 12.dp),
) { Text("Uppdatera från servern") }
}
} }
} }
} }

View File

@@ -43,6 +43,16 @@ class PickerViewModel(private val repo: GymRepository) : ViewModel() {
private val selectedGroup = MutableStateFlow<Int?>(null) private val selectedGroup = MutableStateFlow<Int?>(null)
private val selectedMuscle = MutableStateFlow<Int?>(null) private val selectedMuscle = MutableStateFlow<Int?>(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 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) } private val filters = combine(query, selectedGroup, selectedMuscle) { q, g, m -> Filters(q, g, m) }