Statistik: favoritövningar överst i PB-listan (stjärna på korten, återanvänder toggleFavoriteExercise)
All checks were successful
release / build-release (push) Successful in 4m31s
All checks were successful
release / build-release (push) Successful in 4m31s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AFvSx1pF9pymyEuK7MFkEG
This commit is contained in:
@@ -53,7 +53,8 @@ ett rekord så öppnas ett ark med vikt, est. 1RM, datum, pass och anteckning, d
|
|||||||
märkas som **Träning**, **Tävling** eller **Räknas ej** (fusk, felregistrerat, ej godkänt)
|
märkas som **Träning**, **Tävling** eller **Räknas ej** (fusk, felregistrerat, ej godkänt)
|
||||||
med en kommentar. Under arket visas topplistan för samma övning × reps ("näst i tur"),
|
med en kommentar. Under arket visas topplistan för samma övning × reps ("näst i tur"),
|
||||||
så ett bortplockat lyft kan väljas och återställas. Samma ark öppnas när man trycker på
|
så ett bortplockat lyft kan väljas och återställas. Samma ark öppnas när man trycker på
|
||||||
ett set i passhistoriken. "Räknas ej" tas ur rekord, 1RM och PB-höjdpunkter på servern
|
ett set i passhistoriken. Stjärnan på ett PB-kort gör övningen till **favorit** (samma
|
||||||
|
favoriter som övningsväljaren) så att den ligger överst i PB-listan i både app och webb. "Räknas ej" tas ur rekord, 1RM och PB-höjdpunkter på servern
|
||||||
(kod: `ui/common/LiftStatusSheet.kt`, API: `setLiftStatus`/`setSessionSetStatus`).
|
(kod: `ui/common/LiftStatusSheet.kt`, API: `setLiftStatus`/`setSessionSetStatus`).
|
||||||
|
|
||||||
## Signeringsnyckeln
|
## Signeringsnyckeln
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ class GymRepository(
|
|||||||
/** PB-lista med detaljer per rekord. mode: all | competition | training. */
|
/** PB-lista med detaljer per rekord. mode: all | competition | training. */
|
||||||
suspend fun personalBestsDetailed(mode: String = "all"): List<PbEntry> {
|
suspend fun personalBestsDetailed(mode: String = "all"): List<PbEntry> {
|
||||||
val pb = client.execute(
|
val pb = client.execute(
|
||||||
"query(\$m:String){personalBests(mode:\$m){exerciseTypeId exerciseTypeName repRecords{reps weight date liftId notes status statusNote sessionName estimated1Rm}}}",
|
"query(\$m:String){personalBests(mode:\$m){exerciseTypeId exerciseTypeName isFavorite repRecords{reps weight date liftId notes status statusNote sessionName estimated1Rm}}}",
|
||||||
buildJsonObject { put("m", mode) },
|
buildJsonObject { put("m", mode) },
|
||||||
auth.bearerToken(),
|
auth.bearerToken(),
|
||||||
)
|
)
|
||||||
@@ -311,6 +311,7 @@ class GymRepository(
|
|||||||
PbEntry(
|
PbEntry(
|
||||||
exerciseName = po["exerciseTypeName"]?.jsonPrimitive?.contentOrNull() ?: "Övning",
|
exerciseName = po["exerciseTypeName"]?.jsonPrimitive?.contentOrNull() ?: "Övning",
|
||||||
exerciseTypeId = po["exerciseTypeId"]?.jsonPrimitive?.intOrNull ?: 0,
|
exerciseTypeId = po["exerciseTypeId"]?.jsonPrimitive?.intOrNull ?: 0,
|
||||||
|
isFavorite = po["isFavorite"]?.jsonPrimitive?.booleanOrNull ?: false,
|
||||||
records = po["repRecords"]!!.jsonArray.mapNotNull { r ->
|
records = po["repRecords"]!!.jsonArray.mapNotNull { r ->
|
||||||
val ro = r.jsonObject
|
val ro = r.jsonObject
|
||||||
PbRecord(
|
PbRecord(
|
||||||
@@ -326,7 +327,7 @@ class GymRepository(
|
|||||||
)
|
)
|
||||||
}.sortedBy { it.reps },
|
}.sortedBy { it.reps },
|
||||||
)
|
)
|
||||||
}.sortedBy { it.exerciseName }
|
}.sortedWith(compareByDescending<PbEntry> { it.isFavorite }.thenBy { it.exerciseName })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Topplista för övning × reps, alla statusar (så bortplockade kan återställas). */
|
/** Topplista för övning × reps, alla statusar (så bortplockade kan återställas). */
|
||||||
@@ -944,7 +945,13 @@ data class PbRecord(
|
|||||||
val sessionName: String? = null,
|
val sessionName: String? = null,
|
||||||
val est1Rm: Double = 0.0,
|
val est1Rm: Double = 0.0,
|
||||||
)
|
)
|
||||||
data class PbEntry(val exerciseName: String, val records: List<PbRecord>, val exerciseTypeId: Int = 0)
|
data class PbEntry(
|
||||||
|
val exerciseName: String,
|
||||||
|
val records: List<PbRecord>,
|
||||||
|
val exerciseTypeId: Int = 0,
|
||||||
|
/** Favoritövning (samma FavoriteExercise som övningsväljaren) – ligger överst i PB-listan. */
|
||||||
|
val isFavorite: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
/** Ett lyft med allt PB-arket behöver (pbCandidates / liftDetail). */
|
/** Ett lyft med allt PB-arket behöver (pbCandidates / liftDetail). */
|
||||||
data class LiftDetail(
|
data class LiftDetail(
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import androidx.compose.foundation.horizontalScroll
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Favorite
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
|
import androidx.compose.material.icons.filled.Star
|
||||||
|
import androidx.compose.material.icons.outlined.StarOutline
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -145,6 +147,15 @@ class StatsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Favorit av/på för en övning: optimistisk omsortering, servern uppdateras i bakgrunden. */
|
||||||
|
fun togglePbFavorite(entry: eu.brassepc.fitnessdroid.data.PbEntry) {
|
||||||
|
val next = !entry.isFavorite
|
||||||
|
pbs.value = pbs.value
|
||||||
|
.map { if (it.exerciseTypeId == entry.exerciseTypeId) it.copy(isFavorite = next) else it }
|
||||||
|
.sortedWith(compareByDescending<eu.brassepc.fitnessdroid.data.PbEntry> { it.isFavorite }.thenBy { it.exerciseName })
|
||||||
|
repo.toggleFavoriteExercise(entry.exerciseTypeId, next)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||||
initializer {
|
initializer {
|
||||||
@@ -408,8 +419,18 @@ fun StatsScreen(viewModel: StatsViewModel = viewModel(factory = StatsViewModel.F
|
|||||||
}
|
}
|
||||||
items(pbs, key = { "${it.exerciseTypeId}-${it.exerciseName}" }) { pb ->
|
items(pbs, key = { "${it.exerciseTypeId}-${it.exerciseName}" }) { pb ->
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
Card(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(modifier = Modifier.padding(14.dp)) {
|
Column(modifier = Modifier.padding(start = 14.dp, end = 6.dp, top = 4.dp, bottom = 14.dp)) {
|
||||||
Text(pb.exerciseName, style = MaterialTheme.typography.titleSmall)
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(pb.exerciseName, style = MaterialTheme.typography.titleSmall, modifier = Modifier.weight(1f))
|
||||||
|
// Favorit = ligger överst (samma favoriter som övningsväljaren)
|
||||||
|
androidx.compose.material3.IconButton(onClick = { viewModel.togglePbFavorite(pb) }) {
|
||||||
|
androidx.compose.material3.Icon(
|
||||||
|
if (pb.isFavorite) Icons.Filled.Star else Icons.Outlined.StarOutline,
|
||||||
|
contentDescription = if (pb.isFavorite) "Ta bort favorit" else "Gör till favorit",
|
||||||
|
tint = if (pb.isFavorite) MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 6.dp)
|
.padding(top = 6.dp)
|
||||||
|
|||||||
Reference in New Issue
Block a user