Passhistoriken visar passlängden i både listan och detaljvyn
All checks were successful
release / build-release (push) Successful in 5m43s
All checks were successful
release / build-release (push) Successful in 5m43s
Manuell tidsrättning (durationSecondsOverride) vinner, annars slut − start. Format: '47 min' eller '1h 12m'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kf4MAbZ3c3B4Xy5XfuGsCP
This commit is contained in:
@@ -420,7 +420,7 @@ class GymRepository(
|
|||||||
/** Full detalj för ett avslutat pass. */
|
/** Full detalj för ett avslutat pass. */
|
||||||
suspend fun fetchSessionDetail(id: Int): SessionDetail {
|
suspend fun fetchSessionDetail(id: Int): SessionDetail {
|
||||||
val data = client.execute(
|
val data = client.execute(
|
||||||
"query(\$id:Int!){gymSession(id:\$id){id name notes date startTime estimatedCalories gymSessionExercises{order exerciseType{name} sets{order reps weight distanceMeters durationSeconds rpe isWarmup}}}}",
|
"query(\$id:Int!){gymSession(id:\$id){id name notes date startTime endTime durationSecondsOverride estimatedCalories gymSessionExercises{order exerciseType{name} sets{order reps weight distanceMeters durationSeconds rpe isWarmup}}}}",
|
||||||
buildJsonObject { put("id", id) },
|
buildJsonObject { put("id", id) },
|
||||||
auth.bearerToken(),
|
auth.bearerToken(),
|
||||||
)
|
)
|
||||||
@@ -432,6 +432,11 @@ class GymRepository(
|
|||||||
?: o["startTime"]?.jsonPrimitive?.contentOrNull()).orEmpty(),
|
?: o["startTime"]?.jsonPrimitive?.contentOrNull()).orEmpty(),
|
||||||
notes = o["notes"]?.jsonPrimitive?.contentOrNull(),
|
notes = o["notes"]?.jsonPrimitive?.contentOrNull(),
|
||||||
calories = o["estimatedCalories"]?.jsonPrimitive?.doubleOrNull,
|
calories = o["estimatedCalories"]?.jsonPrimitive?.doubleOrNull,
|
||||||
|
durationMinutes = durationMinutes(
|
||||||
|
o["durationSecondsOverride"]?.jsonPrimitive?.intOrNull,
|
||||||
|
o["startTime"]?.jsonPrimitive?.contentOrNull(),
|
||||||
|
o["endTime"]?.jsonPrimitive?.contentOrNull(),
|
||||||
|
),
|
||||||
exercises = o["gymSessionExercises"]!!.jsonArray
|
exercises = o["gymSessionExercises"]!!.jsonArray
|
||||||
.sortedBy { it.jsonObject["order"]?.jsonPrimitive?.intOrNull ?: 0 }
|
.sortedBy { it.jsonObject["order"]?.jsonPrimitive?.intOrNull ?: 0 }
|
||||||
.map { e ->
|
.map { e ->
|
||||||
@@ -621,7 +626,7 @@ class GymRepository(
|
|||||||
/** Historik hämtas direkt (ingen offline-garanti i v1). */
|
/** Historik hämtas direkt (ingen offline-garanti i v1). */
|
||||||
suspend fun fetchHistory(limit: Int = 30): List<HistoryItem> {
|
suspend fun fetchHistory(limit: Int = 30): List<HistoryItem> {
|
||||||
val data = client.execute(
|
val data = client.execute(
|
||||||
"query(\$s:String,\$l:Int){gymSessions(status:\$s,limit:\$l){id name date startTime durationSecondsOverride estimatedCalories gymSessionExercises{id sets{id weight reps}}}}",
|
"query(\$s:String,\$l:Int){gymSessions(status:\$s,limit:\$l){id name date startTime endTime durationSecondsOverride estimatedCalories gymSessionExercises{id sets{id weight reps}}}}",
|
||||||
buildJsonObject { put("s", "completed"); put("l", limit) },
|
buildJsonObject { put("s", "completed"); put("l", limit) },
|
||||||
auth.bearerToken(),
|
auth.bearerToken(),
|
||||||
)
|
)
|
||||||
@@ -641,9 +646,25 @@ class GymRepository(
|
|||||||
setCount = sets.size,
|
setCount = sets.size,
|
||||||
volumeKg = volume,
|
volumeKg = volume,
|
||||||
calories = o["estimatedCalories"]?.jsonPrimitive?.doubleOrNull,
|
calories = o["estimatedCalories"]?.jsonPrimitive?.doubleOrNull,
|
||||||
|
durationMinutes = durationMinutes(
|
||||||
|
o["durationSecondsOverride"]?.jsonPrimitive?.intOrNull,
|
||||||
|
o["startTime"]?.jsonPrimitive?.contentOrNull(),
|
||||||
|
o["endTime"]?.jsonPrimitive?.contentOrNull(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Passlängd i minuter: manuell rättning vinner, annars slut − start. */
|
||||||
|
private fun durationMinutes(overrideSeconds: Int?, start: String?, end: String?): Int? {
|
||||||
|
overrideSeconds?.let { return it / 60 }
|
||||||
|
if (start == null || end == null) return null
|
||||||
|
return runCatching {
|
||||||
|
val s = java.time.OffsetDateTime.parse(start)
|
||||||
|
val e = java.time.OffsetDateTime.parse(end)
|
||||||
|
java.time.Duration.between(s, e).toMinutes().toInt().takeIf { it >= 0 }
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class HistoryItem(
|
data class HistoryItem(
|
||||||
@@ -654,6 +675,7 @@ data class HistoryItem(
|
|||||||
val setCount: Int,
|
val setCount: Int,
|
||||||
val volumeKg: Double,
|
val volumeKg: Double,
|
||||||
val calories: Double?,
|
val calories: Double?,
|
||||||
|
val durationMinutes: Int? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class HistorySet(
|
data class HistorySet(
|
||||||
@@ -672,6 +694,7 @@ data class SessionDetail(
|
|||||||
val date: String,
|
val date: String,
|
||||||
val notes: String?,
|
val notes: String?,
|
||||||
val calories: Double?,
|
val calories: Double?,
|
||||||
|
val durationMinutes: Int? = null,
|
||||||
val exercises: List<SessionDetailExercise>,
|
val exercises: List<SessionDetailExercise>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -162,7 +162,10 @@ fun HistoryDetailScreen(
|
|||||||
Text(
|
Text(
|
||||||
buildString {
|
buildString {
|
||||||
append(d.date.take(10))
|
append(d.date.take(10))
|
||||||
stats?.durationMinutes?.let { append(" · $it min") }
|
(d.durationMinutes ?: stats?.durationMinutes)?.let {
|
||||||
|
append(" · ")
|
||||||
|
append(if (it >= 60) "${it / 60}h ${it % 60}m" else "$it min")
|
||||||
|
}
|
||||||
if (stats != null) {
|
if (stats != null) {
|
||||||
append(" · ${stats.totalSets} set · ${stats.totalVolumeKg.toInt()} kg volym")
|
append(" · ${stats.totalSets} set · ${stats.totalVolumeKg.toInt()} kg volym")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ fun HistoryScreen(
|
|||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
buildString {
|
buildString {
|
||||||
|
item.durationMinutes?.let {
|
||||||
|
append(if (it >= 60) "${it / 60}h ${it % 60}m" else "$it min")
|
||||||
|
append(" · ")
|
||||||
|
}
|
||||||
append("${item.exerciseCount} övningar · ${item.setCount} set")
|
append("${item.exerciseCount} övningar · ${item.setCount} set")
|
||||||
if (item.volumeKg > 0) append(" · ${item.volumeKg.toInt()} kg volym")
|
if (item.volumeKg > 0) append(" · ${item.volumeKg.toInt()} kg volym")
|
||||||
item.calories?.let { append(" · ${it.toInt()} kcal") }
|
item.calories?.let { append(" · ${it.toInt()} kcal") }
|
||||||
|
|||||||
Reference in New Issue
Block a user