فهرست منبع

Add workout options page

Lee Morgan 2 هفته پیش
والد
کامیت
86b1e8ac61

+ 3 - 0
AGENTS.md

@@ -1,6 +1,9 @@
 # App Description
 This is meant to be an exercise application for all sorts of exercise. It will primarily store data locally. Storing to a database will be an optional upgrade. One of the big purposes of this is to avoid the garbage apps that have tons of ads and other stuff. Nice and simple and easy to use for any type of exercise.
 
+# Code
+Always use Kotlin over Java unless there is no other option
+
 # Style
 
 Dark only. Surfaces stay quiet so effort reads as heat. Prefer borders over shadows.

+ 40 - 38
app/src/main/java/com/example/torus/ui/TorusApp.kt

@@ -15,67 +15,69 @@ import androidx.compose.ui.Modifier
 import com.example.torus.data.WorkoutRepository
 import kotlinx.coroutines.launch
 
-private enum class Screen {
-    Home,
-    CreateWorkout,
-    WorkoutDetail,
+private object Routes {
+    const val HOME = "home"
+    const val CREATE = "create"
+
+    fun workout(id: String) = "workout/$id"
+
+    fun isWorkout(route: String) = route.startsWith("workout/")
+
+    fun workoutId(route: String) = route.removePrefix("workout/")
 }
 
 @Composable
 fun TorusApp(workoutRepository: WorkoutRepository) {
-    var screen by rememberSaveable { mutableStateOf(Screen.Home) }
-    var selectedWorkoutId by rememberSaveable { mutableStateOf<String?>(null) }
+    var navigationStack by rememberSaveable { mutableStateOf(listOf(Routes.HOME)) }
+    val currentRoute = navigationStack.last()
     val coroutineScope = rememberCoroutineScope()
     val workouts by workoutRepository.observeWorkouts().collectAsState(initial = emptyList())
 
-    BackHandler(enabled = screen != Screen.Home) {
-        screen = Screen.Home
-        selectedWorkoutId = null
+    fun navigateTo(route: String) {
+        navigationStack = navigationStack + route
+    }
+
+    fun popBack() {
+        if (navigationStack.size > 1) {
+            navigationStack = navigationStack.dropLast(1)
+        }
+    }
+
+    fun goHome() {
+        navigationStack = listOf(Routes.HOME)
+    }
+
+    BackHandler(enabled = navigationStack.size > 1) {
+        popBack()
     }
 
     Scaffold(
         modifier = Modifier.fillMaxSize(),
-        topBar = {
-            AppHeader(onClick = {
-                screen = Screen.Home
-                selectedWorkoutId = null
-            })
-        }
+        topBar = { AppHeader(onClick = { goHome() }) }
     ) { innerPadding ->
         val contentModifier = Modifier.padding(innerPadding)
-        when (screen) {
-            Screen.Home -> HomeScreen(
+        when {
+            currentRoute == Routes.HOME -> HomeScreen(
                 workouts = workouts,
                 modifier = contentModifier,
-                onAddExercise = { screen = Screen.CreateWorkout },
-                onWorkoutClick = { workoutId ->
-                    selectedWorkoutId = workoutId
-                    screen = Screen.WorkoutDetail
-                }
+                onAddExercise = { navigateTo(Routes.CREATE) },
+                onWorkoutClick = { workoutId -> navigateTo(Routes.workout(workoutId)) }
             )
-            Screen.CreateWorkout -> CreateWorkoutScreen(
+            currentRoute == Routes.CREATE -> CreateWorkoutScreen(
                 modifier = contentModifier,
                 onSaveWorkout = { name, exercises ->
                     coroutineScope.launch {
                         workoutRepository.saveWorkout(name, exercises)
-                        screen = Screen.Home
+                        goHome()
                     }
                 }
             )
-            Screen.WorkoutDetail -> {
-                val workoutId = selectedWorkoutId
-                if (workoutId != null) {
-                    WorkoutDetailScreen(
-                        workoutId = workoutId,
-                        workoutRepository = workoutRepository,
-                        modifier = contentModifier,
-                        onBackToHome = {
-                            screen = Screen.Home
-                            selectedWorkoutId = null
-                        }
-                    )
-                }
-            }
+            Routes.isWorkout(currentRoute) -> WorkoutDetailScreen(
+                workoutId = Routes.workoutId(currentRoute),
+                workoutRepository = workoutRepository,
+                modifier = contentModifier,
+                onHome = { goHome() }
+            )
         }
     }
 }

+ 62 - 86
app/src/main/java/com/example/torus/ui/WorkoutDetailScreen.kt

@@ -1,15 +1,12 @@
 package com.example.torus.ui
 
-import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
@@ -20,15 +17,12 @@ import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import com.example.torus.R
 import com.example.torus.data.WorkoutRepository
-import com.example.torus.model.ExerciseType
-import com.example.torus.model.SavedExercise
 import com.example.torus.model.WorkoutDetail
 import com.example.torus.ui.theme.Sand
 import com.example.torus.ui.theme.TorusTheme
@@ -38,7 +32,9 @@ fun WorkoutDetailScreen(
     workoutId: String,
     workoutRepository: WorkoutRepository,
     modifier: Modifier = Modifier,
-    onBackToHome: () -> Unit = {}
+    onStartWorkout: () -> Unit = {},
+    onEditWorkout: () -> Unit = {},
+    onHome: () -> Unit = {}
 ) {
     var detail by remember(workoutId) { mutableStateOf<WorkoutDetail?>(null) }
 
@@ -50,7 +46,9 @@ fun WorkoutDetailScreen(
         WorkoutDetailContent(
             workout = workout,
             modifier = modifier,
-            onBackToHome = onBackToHome
+            onStartWorkout = onStartWorkout,
+            onEditWorkout = onEditWorkout,
+            onHome = onHome
         )
     }
 }
@@ -59,75 +57,68 @@ fun WorkoutDetailScreen(
 private fun WorkoutDetailContent(
     workout: WorkoutDetail,
     modifier: Modifier = Modifier,
-    onBackToHome: () -> Unit = {}
+    onStartWorkout: () -> Unit = {},
+    onEditWorkout: () -> Unit = {},
+    onHome: () -> Unit = {}
 ) {
-    Column(modifier = modifier.fillMaxSize()) {
-        Column(
-            modifier = Modifier
-                .weight(1f)
-                .fillMaxWidth()
-                .verticalScroll(rememberScrollState())
-                .padding(horizontal = 16.dp, vertical = 8.dp),
-            verticalArrangement = Arrangement.spacedBy(16.dp)
-        ) {
-            Text(
-                text = workout.name.ifBlank { stringResource(R.string.untitled_workout) },
-                color = Sand,
-                style = MaterialTheme.typography.headlineMedium
-            )
-            workout.exercises.forEachIndexed { index, exercise ->
-                ExerciseRow(number = index + 1, exercise = exercise)
-            }
-        }
-        OutlinedButton(
-            onClick = onBackToHome,
-            modifier = Modifier
-                .fillMaxWidth()
-                .padding(horizontal = 16.dp, vertical = 12.dp),
-            shape = RoundedCornerShape(50),
-            colors = ButtonDefaults.outlinedButtonColors(
-                contentColor = MaterialTheme.colorScheme.onBackground
-            )
-        ) {
-            Text(text = stringResource(R.string.back_to_home))
-        }
+    Column(
+        modifier = modifier
+            .fillMaxSize()
+            .padding(horizontal = 16.dp, vertical = 8.dp),
+        verticalArrangement = Arrangement.spacedBy(12.dp)
+    ) {
+        Text(
+            text = workout.name.ifBlank { stringResource(R.string.untitled_workout) },
+            color = Sand,
+            style = MaterialTheme.typography.headlineMedium
+        )
+        EmberButton(
+            text = stringResource(R.string.start_workout),
+            onClick = onStartWorkout
+        )
+        GhostButton(
+            text = stringResource(R.string.edit_workout),
+            onClick = onEditWorkout
+        )
+        GhostButton(
+            text = stringResource(R.string.home),
+            onClick = onHome
+        )
     }
 }
 
 @Composable
-private fun ExerciseRow(
-    number: Int,
-    exercise: SavedExercise
+private fun EmberButton(
+    text: String,
+    onClick: () -> Unit
 ) {
-    val shape = RoundedCornerShape(14.dp)
-    Row(
-        modifier = Modifier
-            .fillMaxWidth()
-            .border(1.dp, MaterialTheme.colorScheme.outline, shape)
-            .padding(16.dp),
-        horizontalArrangement = Arrangement.SpaceBetween,
-        verticalAlignment = Alignment.CenterVertically
+    Button(
+        onClick = onClick,
+        modifier = Modifier.fillMaxWidth(),
+        shape = RoundedCornerShape(50),
+        colors = ButtonDefaults.buttonColors(
+            containerColor = MaterialTheme.colorScheme.primary,
+            contentColor = MaterialTheme.colorScheme.onPrimary
+        )
     ) {
-        Column(
-            modifier = Modifier.weight(1f),
-            verticalArrangement = Arrangement.spacedBy(4.dp)
-        ) {
-            Text(
-                text = stringResource(exercise.type.labelRes),
-                color = MaterialTheme.colorScheme.onSurfaceVariant,
-                style = MaterialTheme.typography.labelLarge
-            )
-            Text(
-                text = exercise.name.ifBlank { stringResource(R.string.untitled_exercise) },
-                color = MaterialTheme.colorScheme.onBackground,
-                style = MaterialTheme.typography.bodyLarge
-            )
-        }
-        Text(
-            text = number.toString(),
-            color = MaterialTheme.colorScheme.onSurfaceVariant,
-            style = MaterialTheme.typography.labelSmall
+        Text(text = text)
+    }
+}
+
+@Composable
+private fun GhostButton(
+    text: String,
+    onClick: () -> Unit
+) {
+    OutlinedButton(
+        onClick = onClick,
+        modifier = Modifier.fillMaxWidth(),
+        shape = RoundedCornerShape(50),
+        colors = ButtonDefaults.outlinedButtonColors(
+            contentColor = MaterialTheme.colorScheme.onBackground
         )
+    ) {
+        Text(text = text)
     }
 }
 
@@ -141,22 +132,7 @@ private fun WorkoutDetailContentPreview() {
                 workout = WorkoutDetail(
                     id = "1",
                     name = "Push day",
-                    exercises = listOf(
-                        SavedExercise(
-                            id = "a",
-                            name = "Bench",
-                            type = ExerciseType.Weights,
-                            isActive = true,
-                            notes = ""
-                        ),
-                        SavedExercise(
-                            id = "b",
-                            name = "Easy run",
-                            type = ExerciseType.Run,
-                            isActive = true,
-                            notes = ""
-                        )
-                    )
+                    exercises = emptyList()
                 )
             )
         }

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

@@ -10,6 +10,7 @@
     <string name="exercise_name">Exercise name</string>
     <string name="save_workout">Save Workout</string>
     <string name="untitled_workout">Untitled workout</string>
-    <string name="untitled_exercise">Untitled exercise</string>
-    <string name="back_to_home">Back to home</string>
+    <string name="start_workout">Start Workout</string>
+    <string name="edit_workout">Edit Workout</string>
+    <string name="home">Home</string>
 </resources>