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,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
|
||||||
|
|||||||
@@ -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") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
|||||||
Reference in New Issue
Block a user