Преглед на файлове

Display a menu to switch between exercises.

Lee Morgan преди 2 седмици
родител
ревизия
44aafeb22e
променени са 2 файла, в които са добавени 136 реда и са изтрити 0 реда
  1. 133 0
      app/src/main/java/com/example/torus/ui/StartWorkoutScreen.kt
  2. 3 0
      app/src/main/res/values/strings.xml

+ 133 - 0
app/src/main/java/com/example/torus/ui/StartWorkoutScreen.kt

@@ -1,6 +1,8 @@
 package com.example.torus.ui
 
+import androidx.compose.foundation.background
 import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
@@ -13,7 +15,9 @@ import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
@@ -42,8 +46,10 @@ import com.example.torus.model.SavedExercise
 import com.example.torus.model.SessionExerciseInput
 import com.example.torus.model.SessionSetInput
 import com.example.torus.model.WorkoutDetail
+import com.example.torus.ui.theme.Ok
 import com.example.torus.ui.theme.Sand
 import com.example.torus.ui.theme.TorusTheme
+import com.example.torus.ui.theme.Warn
 
 private data class WeightSetDraft(
     val id: String,
@@ -86,6 +92,7 @@ fun StartWorkoutScreen(
         isCompleting = isCompleting,
         modifier = modifier,
         onNextExercise = { exerciseIndex += 1 },
+        onJumpToExercise = { exerciseIndex = it },
         onCompleteWorkout = {
             if (!isCompleting) {
                 isCompleting = true
@@ -103,8 +110,10 @@ private fun StartWorkoutContent(
     isCompleting: Boolean = false,
     modifier: Modifier = Modifier,
     onNextExercise: () -> Unit = {},
+    onJumpToExercise: (Int) -> Unit = {},
     onCompleteWorkout: () -> Unit = {}
 ) {
+    var showExerciseMenu by remember { mutableStateOf(false) }
     val exercises = workout.exercises
     Column(modifier = modifier.fillMaxSize()) {
         Column(
@@ -120,6 +129,18 @@ private fun StartWorkoutContent(
                 color = Sand,
                 style = MaterialTheme.typography.headlineMedium
             )
+            if (exercises.isNotEmpty()) {
+                OutlinedButton(
+                    onClick = { showExerciseMenu = true },
+                    modifier = Modifier.fillMaxWidth(),
+                    shape = RoundedCornerShape(50),
+                    colors = ButtonDefaults.outlinedButtonColors(
+                        contentColor = MaterialTheme.colorScheme.onBackground
+                    )
+                ) {
+                    Text(text = stringResource(R.string.exercises_menu))
+                }
+            }
             if (exercises.isEmpty()) {
                 Text(
                     text = stringResource(R.string.no_exercises),
@@ -202,6 +223,18 @@ private fun StartWorkoutContent(
             }
         }
     }
+    if (showExerciseMenu && exercises.isNotEmpty()) {
+        ExerciseJumpMenu(
+            exercises = exercises,
+            currentIndex = exerciseIndex.coerceIn(0, exercises.lastIndex),
+            setsByExercise = setsByExercise,
+            onSelect = { index ->
+                onJumpToExercise(index)
+                showExerciseMenu = false
+            },
+            onDismiss = { showExerciseMenu = false }
+        )
+    }
 }
 
 @Composable
@@ -291,6 +324,106 @@ private fun WeightSetCard(
     }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun ExerciseJumpMenu(
+    exercises: List<SavedExercise>,
+    currentIndex: Int,
+    setsByExercise: Map<String, List<WeightSetDraft>>,
+    onSelect: (Int) -> Unit,
+    onDismiss: () -> Unit
+) {
+    ModalBottomSheet(
+        onDismissRequest = onDismiss,
+        containerColor = MaterialTheme.colorScheme.surfaceVariant,
+        contentColor = MaterialTheme.colorScheme.onBackground
+    ) {
+        Column(
+            modifier = Modifier
+                .fillMaxWidth()
+                .verticalScroll(rememberScrollState())
+                .padding(horizontal = 16.dp)
+                .padding(bottom = 24.dp),
+            verticalArrangement = Arrangement.spacedBy(12.dp)
+        ) {
+            Text(
+                text = stringResource(R.string.exercises_menu),
+                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                style = MaterialTheme.typography.titleSmall
+            )
+            exercises.forEachIndexed { index, exercise ->
+                val selected = index == currentIndex
+                val completed = exercise.hasCompletedSets(setsByExercise)
+                val shape = RoundedCornerShape(14.dp)
+                Row(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .background(MaterialTheme.colorScheme.surface, shape)
+                        .border(
+                            width = 1.dp,
+                            color = if (selected) {
+                                MaterialTheme.colorScheme.primary
+                            } else {
+                                MaterialTheme.colorScheme.outline
+                            },
+                            shape = shape
+                        )
+                        .clickable { onSelect(index) }
+                        .padding(16.dp),
+                    horizontalArrangement = Arrangement.SpaceBetween,
+                    verticalAlignment = Alignment.CenterVertically
+                ) {
+                    Column(
+                        modifier = Modifier.weight(1f),
+                        verticalArrangement = Arrangement.spacedBy(4.dp)
+                    ) {
+                        Text(
+                            text = (index + 1).toString(),
+                            color = MaterialTheme.colorScheme.onSurfaceVariant,
+                            style = MaterialTheme.typography.labelSmall
+                        )
+                        Text(
+                            text = exercise.name.ifBlank {
+                                stringResource(R.string.untitled_exercise)
+                            },
+                            color = MaterialTheme.colorScheme.onBackground,
+                            style = MaterialTheme.typography.bodyLarge
+                        )
+                        Text(
+                            text = stringResource(exercise.type.labelRes),
+                            color = MaterialTheme.colorScheme.onSurfaceVariant,
+                            style = MaterialTheme.typography.labelLarge
+                        )
+                    }
+                    Text(
+                        text = stringResource(
+                            if (completed) {
+                                R.string.exercise_complete
+                            } else {
+                                R.string.exercise_incomplete
+                            }
+                        ),
+                        color = if (completed) Ok else Warn,
+                        style = MaterialTheme.typography.labelLarge
+                    )
+                }
+            }
+        }
+    }
+}
+
+private fun SavedExercise.hasCompletedSets(
+    setsByExercise: Map<String, List<WeightSetDraft>>
+): Boolean {
+    if (type != ExerciseType.Weights) {
+        return false
+    }
+    val sets = setsByExercise[id].orEmpty()
+    return sets.isNotEmpty() && sets.all { set ->
+        set.weight.isNotBlank() && set.reps.isNotBlank()
+    }
+}
+
 private fun defaultWeightSets(): SnapshotStateList<WeightSetDraft> {
     return mutableStateListOf(
         WeightSetDraft(id = UuidV7.generateString()),

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -27,4 +27,7 @@
     <string name="complete_workout">Complete workout</string>
     <string name="workout_completed">Workout completed and saved</string>
     <string name="no_exercises">This workout has no exercises.</string>
+    <string name="exercises_menu">Exercises</string>
+    <string name="exercise_complete">Complete</string>
+    <string name="exercise_incomplete">Incomplete</string>
 </resources>