Passhistoriken visar passlängden i både listan och detaljvyn
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:
2026-07-25 17:00:23 +02:00
parent 34c2a6c4da
commit bb3502ed26
3 changed files with 33 additions and 3 deletions

View File

@@ -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>,
) )

View File

@@ -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")
} }

View File

@@ -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") }