Robust referensdata-hämtning + auto-retry i övningsväljaren
All checks were successful
release / build-release (push) Successful in 4m46s
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:
@@ -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<Int> = 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
|
||||
|
||||
@@ -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") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,16 @@ class PickerViewModel(private val repo: GymRepository) : ViewModel() {
|
||||
private val selectedGroup = 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 val filters = combine(query, selectedGroup, selectedMuscle) { q, g, m -> Filters(q, g, m) }
|
||||
|
||||
Reference in New Issue
Block a user