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)
|
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) {
|
suspend fun completeSession(sessionId: Long) {
|
||||||
val session = db.sessionDao().session(sessionId) ?: return
|
val session = db.sessionDao().session(sessionId) ?: return
|
||||||
db.sessionDao().updateSession(session.copy(status = "completed"))
|
db.sessionDao().updateSession(session.copy(status = "completed"))
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ object OpKind {
|
|||||||
const val ADD_EXERCISE = "add_exercise"
|
const val ADD_EXERCISE = "add_exercise"
|
||||||
const val ADD_SET = "add_set"
|
const val ADD_SET = "add_set"
|
||||||
const val UPDATE_SET = "update_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"
|
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 -> {
|
OpKind.COMPLETE_SESSION -> {
|
||||||
val session = dao.session(op.targetLocalId) ?: return
|
val session = dao.session(op.targetLocalId) ?: return
|
||||||
val sessionServerId = session.serverId
|
val sessionServerId = session.serverId
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ fun SessionScreen(
|
|||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
var confirmComplete by remember { mutableStateOf(false) }
|
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.
|
// Passtimern (uppe till höger) tickar en gång i sekunden.
|
||||||
var nowMs by remember { mutableStateOf(System.currentTimeMillis()) }
|
var nowMs by remember { mutableStateOf(System.currentTimeMillis()) }
|
||||||
@@ -138,6 +139,7 @@ fun SessionScreen(
|
|||||||
pageCount = uiState.pages.size,
|
pageCount = uiState.pages.size,
|
||||||
onDraft = viewModel::updateDraft,
|
onDraft = viewModel::updateDraft,
|
||||||
onLog = viewModel::logSet,
|
onLog = viewModel::logSet,
|
||||||
|
onEditSet = { page, set -> editTarget = page to set },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
FilledTonalButton(
|
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) {
|
if (confirmComplete) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { confirmComplete = false },
|
onDismissRequest = { confirmComplete = false },
|
||||||
@@ -243,6 +261,7 @@ private fun ExercisePageContent(
|
|||||||
pageCount: Int,
|
pageCount: Int,
|
||||||
onDraft: (Long, (SetDraft) -> SetDraft) -> Unit,
|
onDraft: (Long, (SetDraft) -> SetDraft) -> Unit,
|
||||||
onLog: (Long) -> Unit,
|
onLog: (Long) -> Unit,
|
||||||
|
onEditSet: (ExercisePage, LocalSet) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
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 {
|
Card {
|
||||||
Column(
|
Column(
|
||||||
@@ -356,8 +375,9 @@ private fun ExercisePageContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DoneSetRow(set: LocalSet) {
|
private fun DoneSetRow(set: LocalSet, onClick: () -> Unit) {
|
||||||
Surface(
|
Surface(
|
||||||
|
onClick = onClick,
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
color = MaterialTheme.colorScheme.surface,
|
color = MaterialTheme.colorScheme.surface,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
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 ---------- */
|
/* ---------- Vilotimer, två presentationer ---------- */
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -154,6 +154,26 @@ class SessionViewModel(
|
|||||||
fun adjustRest(delta: Int) = restTimer.adjust(delta)
|
fun adjustRest(delta: Int) = restTimer.adjust(delta)
|
||||||
fun skipRest() = restTimer.dismiss()
|
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) {
|
fun completeSession(onDone: () -> Unit) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val id = uiState.value.sessionId ?: sessionFlow.first()?.id ?: return@launch
|
val id = uiState.value.sessionId ?: sessionFlow.first()?.id ?: return@launch
|
||||||
|
|||||||
Reference in New Issue
Block a user