|
@@ -0,0 +1,277 @@
|
|
|
|
|
+package com.example.torus.ui
|
|
|
|
|
+
|
|
|
|
|
+import androidx.compose.foundation.BorderStroke
|
|
|
|
|
+import androidx.compose.foundation.background
|
|
|
|
|
+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
|
|
|
|
|
+import androidx.compose.material3.OutlinedTextField
|
|
|
|
|
+import androidx.compose.material3.OutlinedTextFieldDefaults
|
|
|
|
|
+import androidx.compose.material3.Text
|
|
|
|
|
+import androidx.compose.material3.TextFieldColors
|
|
|
|
|
+import androidx.compose.runtime.Composable
|
|
|
|
|
+import androidx.compose.runtime.getValue
|
|
|
|
|
+import androidx.compose.runtime.key
|
|
|
|
|
+import androidx.compose.runtime.mutableStateListOf
|
|
|
|
|
+import androidx.compose.runtime.mutableStateOf
|
|
|
|
|
+import androidx.compose.runtime.remember
|
|
|
|
|
+import androidx.compose.runtime.rememberCoroutineScope
|
|
|
|
|
+import androidx.compose.runtime.saveable.rememberSaveable
|
|
|
|
|
+import androidx.compose.runtime.setValue
|
|
|
|
|
+import androidx.compose.runtime.withFrameNanos
|
|
|
|
|
+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.ui.theme.Sand
|
|
|
|
|
+import com.example.torus.ui.theme.TorusTheme
|
|
|
|
|
+import java.util.UUID
|
|
|
|
|
+import kotlinx.coroutines.launch
|
|
|
|
|
+
|
|
|
|
|
+private enum class ExerciseType {
|
|
|
|
|
+ Weights,
|
|
|
|
|
+ Run,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+private data class DraftExercise(
|
|
|
|
|
+ val id: String,
|
|
|
|
|
+ val type: ExerciseType,
|
|
|
|
|
+ val name: String = "",
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+fun CreateWorkoutScreen(modifier: Modifier = Modifier) {
|
|
|
|
|
+ var workoutName by rememberSaveable { mutableStateOf("") }
|
|
|
|
|
+ var showExerciseTypes by rememberSaveable { mutableStateOf(false) }
|
|
|
|
|
+ val exercises = remember { mutableStateListOf<DraftExercise>() }
|
|
|
|
|
+ val scrollState = rememberScrollState()
|
|
|
|
|
+ val coroutineScope = rememberCoroutineScope()
|
|
|
|
|
+
|
|
|
|
|
+ fun addExercise(type: ExerciseType) {
|
|
|
|
|
+ exercises += DraftExercise(id = UUID.randomUUID().toString(), type = type)
|
|
|
|
|
+ showExerciseTypes = false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Column(modifier = modifier.fillMaxSize()) {
|
|
|
|
|
+ Column(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .weight(1f)
|
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
|
+ .verticalScroll(scrollState)
|
|
|
|
|
+ .padding(horizontal = 16.dp, vertical = 8.dp),
|
|
|
|
|
+ verticalArrangement = Arrangement.spacedBy(16.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = stringResource(R.string.create_workout),
|
|
|
|
|
+ color = Sand,
|
|
|
|
|
+ style = MaterialTheme.typography.headlineMedium
|
|
|
|
|
+ )
|
|
|
|
|
+ TorusTextField(
|
|
|
|
|
+ value = workoutName,
|
|
|
|
|
+ onValueChange = { workoutName = it },
|
|
|
|
|
+ label = stringResource(R.string.workout_name)
|
|
|
|
|
+ )
|
|
|
|
|
+ exercises.forEachIndexed { index, exercise ->
|
|
|
|
|
+ key(exercise.id) {
|
|
|
|
|
+ NamedExerciseEditor(
|
|
|
|
|
+ number = index + 1,
|
|
|
|
|
+ typeLabel = stringResource(exercise.type.labelRes),
|
|
|
|
|
+ name = exercise.name,
|
|
|
|
|
+ onNameChange = { name ->
|
|
|
|
|
+ val exerciseIndex = exercises.indexOfFirst { it.id == exercise.id }
|
|
|
|
|
+ if (exerciseIndex >= 0) {
|
|
|
|
|
+ exercises[exerciseIndex] = exercises[exerciseIndex].copy(name = name)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Button(
|
|
|
|
|
+ onClick = {
|
|
|
|
|
+ showExerciseTypes = true
|
|
|
|
|
+ coroutineScope.launch {
|
|
|
|
|
+ withFrameNanos { }
|
|
|
|
|
+ withFrameNanos { }
|
|
|
|
|
+ scrollState.animateScrollTo(scrollState.maxValue)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ shape = RoundedCornerShape(50),
|
|
|
|
|
+ colors = ButtonDefaults.buttonColors(
|
|
|
|
|
+ containerColor = MaterialTheme.colorScheme.primary,
|
|
|
|
|
+ contentColor = MaterialTheme.colorScheme.onPrimary
|
|
|
|
|
+ )
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text(text = stringResource(R.string.add_exercise))
|
|
|
|
|
+ }
|
|
|
|
|
+ if (showExerciseTypes) {
|
|
|
|
|
+ ExerciseTypePicker(
|
|
|
|
|
+ onWeightsClick = { addExercise(ExerciseType.Weights) },
|
|
|
|
|
+ onRunClick = { addExercise(ExerciseType.Run) }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Button(
|
|
|
|
|
+ onClick = {},
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
|
+ .padding(horizontal = 16.dp, vertical = 12.dp),
|
|
|
|
|
+ shape = RoundedCornerShape(50),
|
|
|
|
|
+ colors = ButtonDefaults.buttonColors(
|
|
|
|
|
+ containerColor = MaterialTheme.colorScheme.primary,
|
|
|
|
|
+ contentColor = MaterialTheme.colorScheme.onPrimary
|
|
|
|
|
+ )
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text(text = stringResource(R.string.save_workout))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+private val ExerciseType.labelRes: Int
|
|
|
|
|
+ get() = when (this) {
|
|
|
|
|
+ ExerciseType.Weights -> R.string.exercise_type_weights
|
|
|
|
|
+ ExerciseType.Run -> R.string.exercise_type_run
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun NamedExerciseEditor(
|
|
|
|
|
+ number: Int,
|
|
|
|
|
+ typeLabel: String,
|
|
|
|
|
+ name: String,
|
|
|
|
|
+ onNameChange: (String) -> Unit
|
|
|
|
|
+) {
|
|
|
|
|
+ val shape = RoundedCornerShape(14.dp)
|
|
|
|
|
+ Column(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
|
+ .border(1.dp, MaterialTheme.colorScheme.outline, shape)
|
|
|
|
|
+ .padding(16.dp),
|
|
|
|
|
+ verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Row(
|
|
|
|
|
+ modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
+ horizontalArrangement = Arrangement.SpaceBetween,
|
|
|
|
|
+ verticalAlignment = Alignment.CenterVertically
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = typeLabel,
|
|
|
|
|
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
|
|
|
+ style = MaterialTheme.typography.labelLarge
|
|
|
|
|
+ )
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = number.toString(),
|
|
|
|
|
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
|
|
|
+ style = MaterialTheme.typography.labelSmall
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ TorusTextField(
|
|
|
|
|
+ value = name,
|
|
|
|
|
+ onValueChange = onNameChange,
|
|
|
|
|
+ label = stringResource(R.string.exercise_name)
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun ExerciseTypePicker(
|
|
|
|
|
+ onWeightsClick: () -> Unit,
|
|
|
|
|
+ onRunClick: () -> Unit = {}
|
|
|
|
|
+) {
|
|
|
|
|
+ val shape = RoundedCornerShape(14.dp)
|
|
|
|
|
+ Column(
|
|
|
|
|
+ modifier = Modifier
|
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
|
+ .background(MaterialTheme.colorScheme.surfaceVariant, shape)
|
|
|
|
|
+ .border(1.dp, MaterialTheme.colorScheme.primary, shape)
|
|
|
|
|
+ .padding(16.dp),
|
|
|
|
|
+ verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text(
|
|
|
|
|
+ text = stringResource(R.string.choose_exercise_type),
|
|
|
|
|
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
|
|
|
+ style = MaterialTheme.typography.titleSmall
|
|
|
|
|
+ )
|
|
|
|
|
+ ExerciseTypeOption(
|
|
|
|
|
+ label = stringResource(R.string.exercise_type_weights),
|
|
|
|
|
+ onClick = onWeightsClick
|
|
|
|
|
+ )
|
|
|
|
|
+ ExerciseTypeOption(
|
|
|
|
|
+ label = stringResource(R.string.exercise_type_run),
|
|
|
|
|
+ onClick = onRunClick
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun ExerciseTypeOption(
|
|
|
|
|
+ label: String,
|
|
|
|
|
+ onClick: () -> Unit = {}
|
|
|
|
|
+) {
|
|
|
|
|
+ OutlinedButton(
|
|
|
|
|
+ onClick = onClick,
|
|
|
|
|
+ modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
+ shape = RoundedCornerShape(50),
|
|
|
|
|
+ colors = ButtonDefaults.outlinedButtonColors(
|
|
|
|
|
+ contentColor = MaterialTheme.colorScheme.onBackground
|
|
|
|
|
+ ),
|
|
|
|
|
+ border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Text(text = label)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun TorusTextField(
|
|
|
|
|
+ value: String,
|
|
|
|
|
+ onValueChange: (String) -> Unit,
|
|
|
|
|
+ label: String,
|
|
|
|
|
+ modifier: Modifier = Modifier
|
|
|
|
|
+) {
|
|
|
|
|
+ OutlinedTextField(
|
|
|
|
|
+ value = value,
|
|
|
|
|
+ onValueChange = onValueChange,
|
|
|
|
|
+ modifier = modifier.fillMaxWidth(),
|
|
|
|
|
+ label = { Text(text = label) },
|
|
|
|
|
+ singleLine = true,
|
|
|
|
|
+ shape = RoundedCornerShape(14.dp),
|
|
|
|
|
+ colors = torusTextFieldColors()
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun torusTextFieldColors(): TextFieldColors {
|
|
|
|
|
+ return OutlinedTextFieldDefaults.colors(
|
|
|
|
|
+ focusedTextColor = MaterialTheme.colorScheme.onBackground,
|
|
|
|
|
+ unfocusedTextColor = MaterialTheme.colorScheme.onBackground,
|
|
|
|
|
+ focusedBorderColor = MaterialTheme.colorScheme.primary,
|
|
|
|
|
+ unfocusedBorderColor = MaterialTheme.colorScheme.outline,
|
|
|
|
|
+ focusedLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
|
|
|
+ unfocusedLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
|
|
|
+ cursorColor = MaterialTheme.colorScheme.primary,
|
|
|
|
|
+ focusedContainerColor = MaterialTheme.colorScheme.surface,
|
|
|
|
|
+ unfocusedContainerColor = MaterialTheme.colorScheme.surface
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Preview(showBackground = true, backgroundColor = 0xFF0C0F14)
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun CreateWorkoutScreenPreview() {
|
|
|
|
|
+ TorusTheme {
|
|
|
|
|
+ Column {
|
|
|
|
|
+ AppHeader()
|
|
|
|
|
+ CreateWorkoutScreen()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|