|
|
@@ -33,11 +33,13 @@ import androidx.compose.runtime.LaunchedEffect
|
|
|
import androidx.compose.runtime.getValue
|
|
|
import androidx.compose.runtime.key
|
|
|
import androidx.compose.runtime.mutableIntStateOf
|
|
|
+import androidx.compose.runtime.mutableLongStateOf
|
|
|
import androidx.compose.runtime.mutableStateListOf
|
|
|
import androidx.compose.runtime.mutableStateMapOf
|
|
|
import androidx.compose.runtime.mutableStateOf
|
|
|
import androidx.compose.runtime.remember
|
|
|
import androidx.compose.runtime.setValue
|
|
|
+import androidx.compose.runtime.snapshotFlow
|
|
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
|
|
import androidx.compose.ui.Alignment
|
|
|
import androidx.compose.ui.Modifier
|
|
|
@@ -50,6 +52,9 @@ import com.example.torus.data.WorkoutRepository
|
|
|
import com.example.torus.id.UuidV7
|
|
|
import com.example.torus.model.DistanceUnit
|
|
|
import com.example.torus.model.ExerciseType
|
|
|
+import com.example.torus.model.InProgressDistanceLog
|
|
|
+import com.example.torus.model.InProgressSession
|
|
|
+import com.example.torus.model.InProgressWeightSet
|
|
|
import com.example.torus.model.LastExerciseLog
|
|
|
import com.example.torus.model.durationHours
|
|
|
import com.example.torus.model.durationMinutes
|
|
|
@@ -93,8 +98,9 @@ fun StartWorkoutScreen(
|
|
|
var detail by remember(workoutId) { mutableStateOf<WorkoutDetail?>(null) }
|
|
|
var exerciseIndex by remember(workoutId) { mutableIntStateOf(0) }
|
|
|
var isCompleting by remember { mutableStateOf(false) }
|
|
|
+ var loaded by remember(workoutId) { mutableStateOf(false) }
|
|
|
var lastLogs by remember(workoutId) { mutableStateOf<Map<String, LastExerciseLog>>(emptyMap()) }
|
|
|
- val startedAt = remember(workoutId) { System.currentTimeMillis() }
|
|
|
+ var startedAt by remember(workoutId) { mutableLongStateOf(0L) }
|
|
|
val setsByExercise = remember(workoutId) {
|
|
|
mutableStateMapOf<String, SnapshotStateList<WeightSetDraft>>()
|
|
|
}
|
|
|
@@ -103,21 +109,46 @@ fun StartWorkoutScreen(
|
|
|
}
|
|
|
|
|
|
LaunchedEffect(workoutId) {
|
|
|
+ loaded = false
|
|
|
+ workoutRepository.allowInProgressSaves(workoutId)
|
|
|
val workout = workoutRepository.getWorkoutDetail(workoutId)
|
|
|
+ val draft = workoutRepository.getInProgressSession(workoutId)
|
|
|
detail = workout
|
|
|
- exerciseIndex = 0
|
|
|
setsByExercise.clear()
|
|
|
distanceByExercise.clear()
|
|
|
- workout?.exercises?.forEach { exercise ->
|
|
|
- when (exercise.type) {
|
|
|
- ExerciseType.Weights -> setsByExercise[exercise.id] = defaultWeightSets()
|
|
|
- ExerciseType.Distance -> distanceByExercise[exercise.id] = DistanceDraft()
|
|
|
- }
|
|
|
- }
|
|
|
+ restoreInProgress(
|
|
|
+ workout = workout,
|
|
|
+ draft = draft,
|
|
|
+ setsByExercise = setsByExercise,
|
|
|
+ distanceByExercise = distanceByExercise,
|
|
|
+ onIndex = { exerciseIndex = it },
|
|
|
+ onStartedAt = { startedAt = it }
|
|
|
+ )
|
|
|
lastLogs = workoutRepository.getLastExerciseLogs(workoutId)
|
|
|
+ loaded = true
|
|
|
+ }
|
|
|
+
|
|
|
+ LaunchedEffect(workoutId, loaded, isCompleting) {
|
|
|
+ if (!loaded || isCompleting) {
|
|
|
+ return@LaunchedEffect
|
|
|
+ }
|
|
|
+ snapshotFlow {
|
|
|
+ captureInProgress(
|
|
|
+ workoutId = workoutId,
|
|
|
+ startedAt = startedAt,
|
|
|
+ exerciseIndex = exerciseIndex,
|
|
|
+ setsByExercise = setsByExercise,
|
|
|
+ distanceByExercise = distanceByExercise
|
|
|
+ )
|
|
|
+ }.collect { session ->
|
|
|
+ workoutRepository.saveInProgressSession(session)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
val workout = detail ?: return
|
|
|
+ if (!loaded) {
|
|
|
+ return
|
|
|
+ }
|
|
|
StartWorkoutContent(
|
|
|
workout = workout,
|
|
|
exerciseIndex = exerciseIndex,
|
|
|
@@ -717,6 +748,92 @@ private fun SavedExercise.isComplete(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+private fun restoreInProgress(
|
|
|
+ workout: WorkoutDetail?,
|
|
|
+ draft: InProgressSession?,
|
|
|
+ setsByExercise: MutableMap<String, SnapshotStateList<WeightSetDraft>>,
|
|
|
+ distanceByExercise: MutableMap<String, DistanceDraft>,
|
|
|
+ onIndex: (Int) -> Unit,
|
|
|
+ onStartedAt: (Long) -> Unit
|
|
|
+) {
|
|
|
+ val exercises = workout?.exercises.orEmpty()
|
|
|
+ val setsByDraftId = draft?.weightSets.orEmpty().groupBy { it.workoutExerciseId }
|
|
|
+ val distanceByDraftId = draft?.distanceLogs.orEmpty().associateBy { it.workoutExerciseId }
|
|
|
+ exercises.forEach { exercise ->
|
|
|
+ when (exercise.type) {
|
|
|
+ ExerciseType.Weights -> {
|
|
|
+ val saved = setsByDraftId[exercise.id].orEmpty().sortedBy { it.sortOrder }
|
|
|
+ setsByExercise[exercise.id] = if (saved.isEmpty()) {
|
|
|
+ defaultWeightSets()
|
|
|
+ } else {
|
|
|
+ mutableStateListOf<WeightSetDraft>().also { list ->
|
|
|
+ saved.forEach { set ->
|
|
|
+ list += WeightSetDraft(
|
|
|
+ id = set.id,
|
|
|
+ weight = set.weight,
|
|
|
+ reps = set.reps
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ExerciseType.Distance -> {
|
|
|
+ val saved = distanceByDraftId[exercise.id]
|
|
|
+ distanceByExercise[exercise.id] = if (saved == null) {
|
|
|
+ DistanceDraft()
|
|
|
+ } else {
|
|
|
+ DistanceDraft(
|
|
|
+ amount = saved.amount,
|
|
|
+ unit = DistanceUnit.entries.find { it.name == saved.unit }
|
|
|
+ ?: DistanceUnit.Kilometers,
|
|
|
+ hours = saved.hours,
|
|
|
+ minutes = saved.minutes,
|
|
|
+ seconds = saved.seconds
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ val lastIndex = (exercises.size - 1).coerceAtLeast(0)
|
|
|
+ onIndex((draft?.currentExerciseIndex ?: 0).coerceIn(0, lastIndex))
|
|
|
+ onStartedAt(draft?.startedAt ?: System.currentTimeMillis())
|
|
|
+}
|
|
|
+
|
|
|
+private fun captureInProgress(
|
|
|
+ workoutId: String,
|
|
|
+ startedAt: Long,
|
|
|
+ exerciseIndex: Int,
|
|
|
+ setsByExercise: Map<String, List<WeightSetDraft>>,
|
|
|
+ distanceByExercise: Map<String, DistanceDraft>
|
|
|
+): InProgressSession {
|
|
|
+ return InProgressSession(
|
|
|
+ workoutId = workoutId,
|
|
|
+ startedAt = startedAt,
|
|
|
+ currentExerciseIndex = exerciseIndex,
|
|
|
+ weightSets = setsByExercise.flatMap { (exerciseId, sets) ->
|
|
|
+ sets.mapIndexed { index, set ->
|
|
|
+ InProgressWeightSet(
|
|
|
+ id = set.id,
|
|
|
+ workoutExerciseId = exerciseId,
|
|
|
+ sortOrder = index,
|
|
|
+ weight = set.weight,
|
|
|
+ reps = set.reps
|
|
|
+ )
|
|
|
+ }
|
|
|
+ },
|
|
|
+ distanceLogs = distanceByExercise.map { (exerciseId, draft) ->
|
|
|
+ InProgressDistanceLog(
|
|
|
+ workoutExerciseId = exerciseId,
|
|
|
+ amount = draft.amount,
|
|
|
+ unit = draft.unit.name,
|
|
|
+ hours = draft.hours,
|
|
|
+ minutes = draft.minutes,
|
|
|
+ seconds = draft.seconds
|
|
|
+ )
|
|
|
+ }
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
private fun defaultWeightSets(): SnapshotStateList<WeightSetDraft> {
|
|
|
return mutableStateListOf(
|
|
|
WeightSetDraft(id = UuidV7.generateString()),
|