Rätta loggade set: tryck på ett klart set för att ändra eller ta bort
All checks were successful
release / build-release (push) Successful in 4m46s
All checks were successful
release / build-release (push) Successful in 4m46s
Samma steppers som vid loggning. Ändring på osynkat set bärs av den väntande add-opsen; synkat set får en update-op. Borttagning: lokalt direkt, remove-op mot servern när setet redan var synkat. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kf4MAbZ3c3B4Xy5XfuGsCP
This commit is contained in:
@@ -124,6 +124,23 @@ class GymRepository(
|
||||
if (set.serverId != null) enqueue(OpKind.UPDATE_SET, set.id)
|
||||
}
|
||||
|
||||
suspend fun deleteSet(setId: Long) {
|
||||
val set = db.sessionDao().set(setId) ?: return
|
||||
db.sessionDao().deleteSet(setId)
|
||||
// Osynkat set: den väntande ADD-opsen blir en no-op när raden är borta.
|
||||
// Synkat set: ta bort på servern också (server-id:t i op:en, raden finns inte kvar).
|
||||
set.serverId?.let { serverId ->
|
||||
db.opDao().enqueue(
|
||||
PendingOp(
|
||||
kind = OpKind.REMOVE_SET,
|
||||
targetLocalId = serverId.toLong(),
|
||||
createdAtEpochMs = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
sync.kick()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun completeSession(sessionId: Long) {
|
||||
val session = db.sessionDao().session(sessionId) ?: return
|
||||
db.sessionDao().updateSession(session.copy(status = "completed"))
|
||||
|
||||
@@ -21,6 +21,8 @@ object OpKind {
|
||||
const val ADD_EXERCISE = "add_exercise"
|
||||
const val ADD_SET = "add_set"
|
||||
const val UPDATE_SET = "update_set"
|
||||
/** targetLocalId bär server-id:t — den lokala raden är redan borta */
|
||||
const val REMOVE_SET = "remove_set"
|
||||
const val COMPLETE_SESSION = "complete_session"
|
||||
}
|
||||
|
||||
@@ -169,6 +171,14 @@ class SyncEngine(
|
||||
}
|
||||
}
|
||||
|
||||
OpKind.REMOVE_SET -> {
|
||||
client.execute(
|
||||
"mutation(\$id: Int!){removeSessionSet(id:\$id)}",
|
||||
buildJsonObject { put("id", op.targetLocalId.toInt()) },
|
||||
auth.bearerToken(),
|
||||
)
|
||||
}
|
||||
|
||||
OpKind.COMPLETE_SESSION -> {
|
||||
val session = dao.session(op.targetLocalId) ?: return
|
||||
val sessionServerId = session.serverId
|
||||
|
||||
@@ -66,6 +66,7 @@ fun SessionScreen(
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
var confirmComplete by remember { mutableStateOf(false) }
|
||||
var editTarget by remember { mutableStateOf<Pair<ExercisePage, LocalSet>?>(null) }
|
||||
|
||||
// Passtimern (uppe till höger) tickar en gång i sekunden.
|
||||
var nowMs by remember { mutableStateOf(System.currentTimeMillis()) }
|
||||
@@ -138,6 +139,7 @@ fun SessionScreen(
|
||||
pageCount = uiState.pages.size,
|
||||
onDraft = viewModel::updateDraft,
|
||||
onLog = viewModel::logSet,
|
||||
onEditSet = { page, set -> editTarget = page to set },
|
||||
)
|
||||
}
|
||||
FilledTonalButton(
|
||||
@@ -162,6 +164,22 @@ fun SessionScreen(
|
||||
}
|
||||
}
|
||||
|
||||
editTarget?.let { (page, set) ->
|
||||
EditSetDialog(
|
||||
page = page,
|
||||
set = set,
|
||||
onSave = { draft ->
|
||||
viewModel.saveEditedSet(set.id, draft)
|
||||
editTarget = null
|
||||
},
|
||||
onDelete = {
|
||||
viewModel.deleteSet(set.id)
|
||||
editTarget = null
|
||||
},
|
||||
onDismiss = { editTarget = null },
|
||||
)
|
||||
}
|
||||
|
||||
if (confirmComplete) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { confirmComplete = false },
|
||||
@@ -243,6 +261,7 @@ private fun ExercisePageContent(
|
||||
pageCount: Int,
|
||||
onDraft: (Long, (SetDraft) -> SetDraft) -> Unit,
|
||||
onLog: (Long) -> Unit,
|
||||
onEditSet: (ExercisePage, LocalSet) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -263,7 +282,7 @@ private fun ExercisePageContent(
|
||||
)
|
||||
}
|
||||
|
||||
page.doneSets.forEach { set -> DoneSetRow(set) }
|
||||
page.doneSets.forEach { set -> DoneSetRow(set, onClick = { onEditSet(page, set) }) }
|
||||
|
||||
Card {
|
||||
Column(
|
||||
@@ -356,8 +375,9 @@ private fun ExercisePageContent(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DoneSetRow(set: LocalSet) {
|
||||
private fun DoneSetRow(set: LocalSet, onClick: () -> Unit) {
|
||||
Surface(
|
||||
onClick = onClick,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -423,6 +443,106 @@ private fun Stepper(label: String, value: String, onMinus: () -> Unit, onPlus: (
|
||||
}
|
||||
}
|
||||
|
||||
/** Rätta ett redan loggat set: samma steppers som vid loggning + ta bort. */
|
||||
@Composable
|
||||
private fun EditSetDialog(
|
||||
page: ExercisePage,
|
||||
set: LocalSet,
|
||||
onSave: (SetDraft) -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
var draft by remember {
|
||||
mutableStateOf(
|
||||
SetDraft(
|
||||
weight = set.weight,
|
||||
reps = set.reps,
|
||||
durationSeconds = set.durationSeconds,
|
||||
distanceMeters = set.distanceMeters,
|
||||
rpe = set.rpe,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Rätta set ${set.order}") },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
if (page.type?.tracksWeight != false) {
|
||||
Stepper(
|
||||
label = "kg",
|
||||
value = draft.weight?.compact() ?: "—",
|
||||
onMinus = { draft = draft.copy(weight = ((draft.weight ?: 0.0) - 2.5).coerceAtLeast(0.0)) },
|
||||
onPlus = { draft = draft.copy(weight = (draft.weight ?: 0.0) + 2.5) },
|
||||
)
|
||||
}
|
||||
if (page.type?.tracksReps != false) {
|
||||
Stepper(
|
||||
label = "reps",
|
||||
value = draft.reps?.toString() ?: "—",
|
||||
onMinus = { draft = draft.copy(reps = ((draft.reps ?: 1) - 1).coerceAtLeast(1)) },
|
||||
onPlus = { draft = draft.copy(reps = (draft.reps ?: 0) + 1) },
|
||||
)
|
||||
}
|
||||
if (page.type?.tracksDuration == true) {
|
||||
Stepper(
|
||||
label = "sekunder",
|
||||
value = draft.durationSeconds?.toString() ?: "—",
|
||||
onMinus = { draft = draft.copy(durationSeconds = ((draft.durationSeconds ?: 15) - 15).coerceAtLeast(5)) },
|
||||
onPlus = { draft = draft.copy(durationSeconds = (draft.durationSeconds ?: 0) + 15) },
|
||||
)
|
||||
}
|
||||
if (page.type?.tracksDistance == true) {
|
||||
Stepper(
|
||||
label = "meter",
|
||||
value = draft.distanceMeters?.compact() ?: "—",
|
||||
onMinus = { draft = draft.copy(distanceMeters = ((draft.distanceMeters ?: 100.0) - 100).coerceAtLeast(0.0)) },
|
||||
onPlus = { draft = draft.copy(distanceMeters = (draft.distanceMeters ?: 0.0) + 100) },
|
||||
)
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Text(
|
||||
"RPE",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
listOf(7.0, 8.0, 9.0, 10.0).forEach { rpe ->
|
||||
val selected = draft.rpe == rpe
|
||||
Surface(
|
||||
onClick = { draft = draft.copy(rpe = if (selected) null else rpe) },
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = if (selected) {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceVariant
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
rpe.compact(),
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
TextButton(onClick = onDelete) {
|
||||
Text("Ta bort setet", color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(onClick = { onSave(draft) }) { Text("Spara") }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) { Text("Avbryt") }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/* ---------- Vilotimer, två presentationer ---------- */
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -154,6 +154,26 @@ class SessionViewModel(
|
||||
fun adjustRest(delta: Int) = restTimer.adjust(delta)
|
||||
fun skipRest() = restTimer.dismiss()
|
||||
|
||||
/** Rätta ett redan loggat set (synkas som update, eller bärs av väntande add). */
|
||||
fun saveEditedSet(setId: Long, draft: SetDraft) {
|
||||
val set = uiState.value.pages.flatMap { it.doneSets }.firstOrNull { it.id == setId } ?: return
|
||||
viewModelScope.launch {
|
||||
repo.updateSet(
|
||||
set.copy(
|
||||
weight = draft.weight,
|
||||
reps = draft.reps,
|
||||
durationSeconds = draft.durationSeconds,
|
||||
distanceMeters = draft.distanceMeters,
|
||||
rpe = draft.rpe,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteSet(setId: Long) {
|
||||
viewModelScope.launch { repo.deleteSet(setId) }
|
||||
}
|
||||
|
||||
fun completeSession(onDone: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val id = uiState.value.sessionId ?: sessionFlow.first()?.id ?: return@launch
|
||||
|
||||
Reference in New Issue
Block a user