diff --git a/app/src/main/java/com/example/workouttimer/ui/navigation/AppNavigation.kt b/app/src/main/java/com/example/workouttimer/ui/navigation/AppNavigation.kt index 49cba4c..d2fd8ee 100644 --- a/app/src/main/java/com/example/workouttimer/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/example/workouttimer/ui/navigation/AppNavigation.kt @@ -11,6 +11,7 @@ import com.example.workouttimer.ui.screens.RunningWorkoutScreen import com.example.workouttimer.ui.screens.SavedWorkoutsScreen import com.example.workouttimer.viewmodel.WorkoutViewModel import com.example.workouttimer.ui.screens.WorkoutEditorScreen +import com.example.workouttimer.ui.screens.PreparationCountdownScreen class AppNavigation(private val context: Context) { @@ -31,7 +32,12 @@ class AppNavigation(private val context: Context) { setScreen( HomeScreen( context = context, - onShowWorkouts = { showSavedWorkouts() } + onShowWorkouts = { + showSavedWorkouts() + }, + onCreateWorkout = { + showCreateWorkout() + } ) ) } @@ -101,20 +107,32 @@ class AppNavigation(private val context: Context) { context = context, workout = viewModel.currentWorkout, onStart = { - viewModel.startWorkout() - showRunningWorkout() + showPreparationCountdown() }, onBack = { showSavedWorkouts() } ) ) } + fun showPreparationCountdown() { + setScreen( + PreparationCountdownScreen( + context = context, + onSkip = { + viewModel.startWorkout() + showRunningWorkout() + } + ) + ) + } + fun showRunningWorkout() { setScreen( RunningWorkoutScreen( context = context, workout = viewModel.currentWorkout, exercise = viewModel.getCurrentExercise(), + nextExercise = viewModel.getNextExercise(), exerciseNumber = viewModel.currentExerciseIndex + 1, onNextExercise = { viewModel.nextExercise() diff --git a/app/src/main/java/com/example/workouttimer/ui/screens/FinishedScreen.kt b/app/src/main/java/com/example/workouttimer/ui/screens/FinishedScreen.kt index 0313f9d..5b8a55b 100644 --- a/app/src/main/java/com/example/workouttimer/ui/screens/FinishedScreen.kt +++ b/app/src/main/java/com/example/workouttimer/ui/screens/FinishedScreen.kt @@ -6,6 +6,7 @@ import android.widget.Button import android.widget.LinearLayout import android.widget.TextView import com.example.workouttimer.domain.Workout +import com.example.workouttimer.ui.theme.AppStyle class FinishedScreen( context: Context, @@ -14,34 +15,33 @@ class FinishedScreen( ) : LinearLayout(context) { init { - orientation = VERTICAL - gravity = Gravity.CENTER - setPadding(48, 48, 48, 48) + AppStyle.applyGradientBackground(this) + gravity = Gravity.CENTER_VERTICAL val title = TextView(context) - title.text = "Workout beendet!" - title.textSize = 32f - title.gravity = Gravity.CENTER + title.text = "Workout\nDone" + AppStyle.headline(title) + addView(title) val workoutText = TextView(context) - workoutText.text = workout?.name ?: "" - workoutText.textSize = 22f - workoutText.gravity = Gravity.CENTER + workoutText.text = workout?.name ?: "Training abgeschlossen" + AppStyle.title(workoutText) + AppStyle.setTextMargin(workoutText) + addView(workoutText) val summary = TextView(context) summary.text = "Gute Arbeit. Dein Workout wurde erfolgreich abgeschlossen." - summary.textSize = 18f - summary.gravity = Gravity.CENTER + AppStyle.muted(summary) + AppStyle.setTextMargin(summary) + addView(summary) val homeButton = Button(context) - homeButton.text = "Zur Startseite" + homeButton.text = "Zur Startseite -->" + AppStyle.primaryButton(homeButton) homeButton.setOnClickListener { onBackHome() } - addView(title) - addView(workoutText) - addView(summary) addView(homeButton) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/workouttimer/ui/screens/HomeScreen.kt b/app/src/main/java/com/example/workouttimer/ui/screens/HomeScreen.kt index a50228a..056db3f 100644 --- a/app/src/main/java/com/example/workouttimer/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/example/workouttimer/ui/screens/HomeScreen.kt @@ -2,38 +2,70 @@ package com.example.workouttimer.ui.screens import android.content.Context import android.view.Gravity -import android.widget.Button import android.widget.LinearLayout +import android.widget.Space import android.widget.TextView +import com.example.workouttimer.ui.theme.AppStyle class HomeScreen( context: Context, - onShowWorkouts: () -> Unit + onShowWorkouts: () -> Unit, + onCreateWorkout: () -> Unit ) : LinearLayout(context) { init { - orientation = VERTICAL - gravity = Gravity.CENTER - setPadding(48, 48, 48, 48) + AppStyle.applyGradientBackground(this) + gravity = Gravity.CENTER_VERTICAL + + val topSpace = Space(context) + topSpace.layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + AppStyle.dp(this, 90) + ) val title = TextView(context) title.text = "Workout Timer" - title.textSize = 32f - title.gravity = Gravity.CENTER + title.gravity = Gravity.START + AppStyle.headline(title) val subtitle = TextView(context) - subtitle.text = "Plane und starte dein Workout" - subtitle.textSize = 18f - subtitle.gravity = Gravity.CENTER + subtitle.text = "Build your own rhythm." + subtitle.gravity = Gravity.START + AppStyle.muted(subtitle) + AppStyle.setTextMargin(subtitle) - val button = Button(context) - button.text = "Gespeicherte Workouts" - button.setOnClickListener { - onShowWorkouts() - } + val buttonSpace = Space(context) + buttonSpace.layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + AppStyle.dp(this, 32) + ) + val showWorkoutsCard = AppStyle.actionCard( + context = context, + text = "Gespeicherte Workouts", + dark = true, + onClick = onShowWorkouts + ) + + val createWorkoutCard = AppStyle.actionCard( + context = context, + text = "Workout erstellen", + dark = false, + onClick = onCreateWorkout + ) + + val bottomNote = TextView(context) + bottomNote.text = "time based? rep based? your choice!" + bottomNote.gravity = Gravity.START + AppStyle.muted(bottomNote) + AppStyle.setTextMargin(bottomNote) + + addView(topSpace) addView(title) addView(subtitle) - addView(button) + addView(buttonSpace) + addView(showWorkoutsCard) + addView(createWorkoutCard) + addView(bottomNote) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/workouttimer/ui/screens/PauseScreen.kt b/app/src/main/java/com/example/workouttimer/ui/screens/PauseScreen.kt index 1fbac3f..295da57 100644 --- a/app/src/main/java/com/example/workouttimer/ui/screens/PauseScreen.kt +++ b/app/src/main/java/com/example/workouttimer/ui/screens/PauseScreen.kt @@ -5,6 +5,7 @@ import android.view.Gravity import android.widget.Button import android.widget.LinearLayout import android.widget.TextView +import com.example.workouttimer.ui.theme.AppStyle class PauseScreen( context: Context, @@ -13,28 +14,34 @@ class PauseScreen( ) : LinearLayout(context) { init { - orientation = VERTICAL - gravity = Gravity.CENTER - setPadding(48, 48, 48, 48) + AppStyle.applyGradientBackground(this) + gravity = Gravity.CENTER_VERTICAL val title = TextView(context) title.text = "Pause" - title.textSize = 32f - title.gravity = Gravity.CENTER + AppStyle.headline(title) + addView(title) + + val info = TextView(context) + info.text = "Atme kurz durch. Du kannst jederzeit weitermachen." + AppStyle.muted(info) + AppStyle.setTextMargin(info) + addView(info) val resumeButton = Button(context) - resumeButton.text = "Weiter trainieren" + resumeButton.text = "Weiter trainieren -->" + AppStyle.primaryButton(resumeButton) resumeButton.setOnClickListener { onResume() } val finishButton = Button(context) finishButton.text = "Workout beenden" + AppStyle.secondaryButton(finishButton) finishButton.setOnClickListener { onFinish() } - addView(title) addView(resumeButton) addView(finishButton) } diff --git a/app/src/main/java/com/example/workouttimer/ui/screens/PreparationCountdownScreen.kt b/app/src/main/java/com/example/workouttimer/ui/screens/PreparationCountdownScreen.kt new file mode 100644 index 0000000..0f984b9 --- /dev/null +++ b/app/src/main/java/com/example/workouttimer/ui/screens/PreparationCountdownScreen.kt @@ -0,0 +1,84 @@ +package com.example.workouttimer.ui.screens + +import android.content.Context +import android.os.CountDownTimer +import android.view.Gravity +import android.widget.Button +import android.widget.LinearLayout +import android.widget.TextView +import com.example.workouttimer.ui.theme.AppStyle + +class PreparationCountdownScreen( + context: Context, + onSkip: () -> Unit +) : LinearLayout(context) { + + private var remainingSeconds = 15 + private var timer: CountDownTimer? = null + private val timerText = TextView(context) + + init { + AppStyle.applyGradientBackground(this) + gravity = Gravity.CENTER_VERTICAL + + val title = TextView(context) + title.text = "Get\nReady" + AppStyle.headline(title) + addView(title) + + val info = TextView(context) + info.text = "Dein Workout startet gleich." + AppStyle.muted(info) + AppStyle.setTextMargin(info) + addView(info) + + timerText.gravity = Gravity.START + AppStyle.timer(timerText) + AppStyle.setTextMargin(timerText) + addView(timerText) + + val skipButton = Button(context) + skipButton.text = "Jetzt starten -->" + AppStyle.primaryButton(skipButton) + skipButton.setOnClickListener { + timer?.cancel() + onSkip() + } + addView(skipButton) + + val extendButton = Button(context) + extendButton.text = "+10 Sekunden" + AppStyle.secondaryButton(extendButton) + extendButton.setOnClickListener { + remainingSeconds += 10 + restartTimer(onSkip) + } + addView(extendButton) + + restartTimer(onSkip) + } + + private fun restartTimer(onFinished: () -> Unit) { + timer?.cancel() + timerText.text = remainingSeconds.toString() + + timer = object : CountDownTimer(remainingSeconds * 1000L, 1000L) { + override fun onTick(millisUntilFinished: Long) { + remainingSeconds = (millisUntilFinished / 1000L).toInt() + timerText.text = remainingSeconds.toString() + } + + override fun onFinish() { + timerText.text = "0" + onFinished() + } + } + + timer?.start() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + timer?.cancel() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/workouttimer/ui/screens/ReadyToStartScreen.kt b/app/src/main/java/com/example/workouttimer/ui/screens/ReadyToStartScreen.kt index ea487a3..98bfdd2 100644 --- a/app/src/main/java/com/example/workouttimer/ui/screens/ReadyToStartScreen.kt +++ b/app/src/main/java/com/example/workouttimer/ui/screens/ReadyToStartScreen.kt @@ -4,9 +4,11 @@ import android.content.Context import android.view.Gravity import android.widget.Button import android.widget.LinearLayout +import android.widget.Space import android.widget.TextView import com.example.workouttimer.domain.ExerciseTarget import com.example.workouttimer.domain.Workout +import com.example.workouttimer.ui.theme.AppStyle class ReadyToStartScreen( context: Context, @@ -16,36 +18,70 @@ class ReadyToStartScreen( ) : LinearLayout(context) { init { - orientation = VERTICAL - gravity = Gravity.CENTER_HORIZONTAL - setPadding(48, 48, 48, 48) + AppStyle.applyGradientBackground(this) + + layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT + ) + + gravity = Gravity.TOP val title = TextView(context) - title.text = workout?.name ?: "Kein Workout ausgewählt" - title.textSize = 28f - title.gravity = Gravity.CENTER + title.text = "Ready\nto Start" + AppStyle.headline(title) - addView(title) + val workoutName = TextView(context) + workoutName.text = workout?.name ?: "Kein Workout ausgewählt" + AppStyle.title(workoutName) + AppStyle.setTextMargin(workoutName) - workout?.exercises?.forEach { exercise -> - val exerciseText = TextView(context) - exerciseText.text = "${exercise.name} - ${formatTarget(exercise.target)}" - exerciseText.textSize = 18f - addView(exerciseText) + val exerciseList = LinearLayout(context) + exerciseList.orientation = VERTICAL + + workout?.exercises?.forEachIndexed { index, exercise -> + + val card = LinearLayout(context) + AppStyle.card(card) + + val exerciseTitle = TextView(context) + exerciseTitle.text = "${index + 1}. ${exercise.name}" + AppStyle.title(exerciseTitle) + + val target = TextView(context) + target.text = formatTarget(exercise.target) + AppStyle.body(target) + + card.addView(exerciseTitle) + card.addView(target) + + exerciseList.addView(card) } + val spacer = Space(context) + spacer.layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + AppStyle.dp(this, 32) + ) + val startButton = Button(context) - startButton.text = "Workout starten" + startButton.text = "Workout starten -->" + AppStyle.primaryButton(startButton) startButton.setOnClickListener { onStart() } val backButton = Button(context) backButton.text = "Zurück" + AppStyle.secondaryButton(backButton) backButton.setOnClickListener { onBack() } + addView(title) + addView(workoutName) + addView(exerciseList) + addView(spacer) addView(startButton) addView(backButton) } diff --git a/app/src/main/java/com/example/workouttimer/ui/screens/RunningWorkoutScreen.kt b/app/src/main/java/com/example/workouttimer/ui/screens/RunningWorkoutScreen.kt index e883748..0ce0f55 100644 --- a/app/src/main/java/com/example/workouttimer/ui/screens/RunningWorkoutScreen.kt +++ b/app/src/main/java/com/example/workouttimer/ui/screens/RunningWorkoutScreen.kt @@ -5,110 +5,146 @@ import android.os.CountDownTimer import android.view.Gravity import android.widget.Button import android.widget.LinearLayout +import android.widget.Space import android.widget.TextView import com.example.workouttimer.domain.Exercise import com.example.workouttimer.domain.ExerciseTarget import com.example.workouttimer.domain.Workout +import com.example.workouttimer.ui.theme.AppStyle class RunningWorkoutScreen( context: Context, workout: Workout?, exercise: Exercise?, + nextExercise: Exercise?, exerciseNumber: Int, onNextExercise: () -> Unit, onPause: () -> Unit ) : LinearLayout(context) { - private var countDownTimer: CountDownTimer? = null + private var timer: CountDownTimer? = null + private val mainValueText = TextView(context) init { - orientation = VERTICAL - gravity = Gravity.CENTER - setPadding(48, 48, 48, 48) + AppStyle.applyGradientBackground(this) + gravity = Gravity.CENTER_VERTICAL - val workoutTitle = TextView(context) - workoutTitle.text = workout?.name ?: "Workout läuft" - workoutTitle.textSize = 22f - workoutTitle.gravity = Gravity.CENTER + val workoutLabel = TextView(context) + workoutLabel.text = workout?.name ?: "Workout läuft" + workoutLabel.gravity = Gravity.CENTER + AppStyle.muted(workoutLabel) + + val topSpace = Space(context) + topSpace.layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + AppStyle.dp(this, 28) + ) val exerciseTitle = TextView(context) - exerciseTitle.text = "Übung $exerciseNumber: ${exercise?.name ?: "-"}" - exerciseTitle.textSize = 30f + exerciseTitle.text = "${exerciseNumber}. ${exercise?.name ?: "-"}" exerciseTitle.gravity = Gravity.CENTER + AppStyle.headline(exerciseTitle) - val targetText = TextView(context) - targetText.text = exercise?.target?.let { formatTarget(it) } ?: "" - targetText.textSize = 24f - targetText.gravity = Gravity.CENTER + val middleSpace = Space(context) + middleSpace.layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + AppStyle.dp(this, 26) + ) - val timerText = TextView(context) - timerText.textSize = 36f - timerText.gravity = Gravity.CENTER + mainValueText.gravity = Gravity.CENTER + AppStyle.timer(mainValueText) - val infoText = TextView(context) - infoText.text = exercise?.info ?: "" - infoText.textSize = 16f - infoText.gravity = Gravity.CENTER + val unitText = TextView(context) + unitText.gravity = Gravity.CENTER + AppStyle.title(unitText) + + when (val target = exercise?.target) { + is ExerciseTarget.Repetitions -> { + mainValueText.text = target.reps.toString() + unitText.text = "Wiederholungen" + } + + is ExerciseTarget.Duration -> { + mainValueText.text = target.seconds.toString() + unitText.text = "Sekunden" + startTimer(target.seconds, onNextExercise) + } + + null -> { + mainValueText.text = "-" + unitText.text = "" + } + } + + val nextText = TextView(context) + nextText.gravity = Gravity.CENTER + nextText.text = if (nextExercise != null) { + "Next: ${nextExercise.name} / ${formatTarget(nextExercise.target)}" + } else { + "Next: Workout done" + } + AppStyle.body(nextText) + AppStyle.setTextMargin(nextText) + + val buttonSpace = Space(context) + buttonSpace.layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + AppStyle.dp(this, 34) + ) val nextButton = Button(context) - nextButton.text = "Übung geschafft / Weiter" + nextButton.text = if (exercise?.target is ExerciseTarget.Duration) { + "Übung überspringen -->" + } else { + "Übung geschafft -->" + } + AppStyle.primaryButton(nextButton) nextButton.setOnClickListener { - countDownTimer?.cancel() + timer?.cancel() onNextExercise() } val pauseButton = Button(context) pauseButton.text = "Pause" + AppStyle.secondaryButton(pauseButton) pauseButton.setOnClickListener { - countDownTimer?.cancel() + timer?.cancel() onPause() } - addView(workoutTitle) + addView(workoutLabel) + addView(topSpace) addView(exerciseTitle) - addView(targetText) - addView(timerText) - addView(infoText) + addView(middleSpace) + addView(mainValueText) + addView(unitText) + addView(nextText) + addView(buttonSpace) addView(nextButton) addView(pauseButton) - - if (exercise?.target is ExerciseTarget.Duration) { - nextButton.text = "Überspringen" - startCountdown( - seconds = exercise.target.seconds, - timerText = timerText, - onFinished = onNextExercise - ) - } else { - timerText.text = "" - } } - private fun startCountdown( - seconds: Int, - timerText: TextView, - onFinished: () -> Unit - ) { - timerText.text = "$seconds" + private fun startTimer(seconds: Int, onFinished: () -> Unit) { + mainValueText.text = seconds.toString() - countDownTimer = object : CountDownTimer(seconds * 1000L, 1000L) { + timer = object : CountDownTimer(seconds * 1000L, 1000L) { override fun onTick(millisUntilFinished: Long) { - val remainingSeconds = (millisUntilFinished / 1000L).toInt() - timerText.text = remainingSeconds.toString() + val remaining = (millisUntilFinished / 1000L).toInt() + mainValueText.text = remaining.toString() } override fun onFinish() { - timerText.text = "0" + mainValueText.text = "0" onFinished() } } - countDownTimer?.start() + timer?.start() } override fun onDetachedFromWindow() { super.onDetachedFromWindow() - countDownTimer?.cancel() + timer?.cancel() } private fun formatTarget(target: ExerciseTarget): String { diff --git a/app/src/main/java/com/example/workouttimer/ui/screens/SavedWorkoutsScreen.kt b/app/src/main/java/com/example/workouttimer/ui/screens/SavedWorkoutsScreen.kt index b3bc40f..1b3ebee 100644 --- a/app/src/main/java/com/example/workouttimer/ui/screens/SavedWorkoutsScreen.kt +++ b/app/src/main/java/com/example/workouttimer/ui/screens/SavedWorkoutsScreen.kt @@ -4,8 +4,10 @@ import android.content.Context import android.view.Gravity import android.widget.Button import android.widget.LinearLayout +import android.widget.ScrollView import android.widget.TextView import com.example.workouttimer.domain.Workout +import com.example.workouttimer.ui.theme.AppStyle class SavedWorkoutsScreen( context: Context, @@ -15,64 +17,72 @@ class SavedWorkoutsScreen( onEditWorkout: (Workout) -> Unit, onDeleteWorkout: (Workout) -> Unit, onBack: () -> Unit -) : LinearLayout(context) { +) : ScrollView(context) { init { - orientation = VERTICAL - gravity = Gravity.CENTER_HORIZONTAL - setPadding(48, 48, 48, 48) + background = AppStyle.gradientDrawable() + + val content = LinearLayout(context) + AppStyle.applyGradientBackground(content) + + content.layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT + ) + + addView(content) val title = TextView(context) - title.text = "Gespeicherte Workouts" - title.textSize = 28f - title.gravity = Gravity.CENTER - - addView(title) + title.text = "Saved\nWorkouts" + AppStyle.headline(title) + content.addView(title) val createButton = Button(context) - createButton.text = "Neues Workout erstellen" - createButton.setOnClickListener { - onCreateWorkout() - } - addView(createButton) + createButton.text = "Neues Workout erstellen -->" + AppStyle.primaryButton(createButton) + createButton.setOnClickListener { onCreateWorkout() } + content.addView(createButton) workouts.forEach { workout -> + val card = LinearLayout(context) + AppStyle.card(card) - val workoutRow = LinearLayout(context) - workoutRow.orientation = HORIZONTAL - workoutRow.gravity = Gravity.CENTER + val name = TextView(context) + name.text = workout.name + AppStyle.title(name) - val workoutButton = Button(context) - workoutButton.text = workout.name - workoutButton.setOnClickListener { - onWorkoutSelected(workout) - } + val details = TextView(context) + details.text = "${workout.exercises.size} Übungen" + AppStyle.muted(details) + + val startButton = Button(context) + startButton.text = "Starten -->" + AppStyle.primaryButton(startButton) + startButton.setOnClickListener { onWorkoutSelected(workout) } val editButton = Button(context) editButton.text = "Bearbeiten" - editButton.setOnClickListener { - onEditWorkout(workout) - } + AppStyle.secondaryButton(editButton) + editButton.setOnClickListener { onEditWorkout(workout) } val deleteButton = Button(context) - deleteButton.text = "Löschen" - deleteButton.setOnClickListener { - onDeleteWorkout(workout) - } + deleteButton.text = "Workout löschen" + AppStyle.secondaryButton(deleteButton) + deleteButton.setOnClickListener { onDeleteWorkout(workout) } - workoutRow.addView(workoutButton) - workoutRow.addView(editButton) - workoutRow.addView(deleteButton) + card.addView(name) + card.addView(details) + card.addView(startButton) + card.addView(editButton) + card.addView(deleteButton) - addView(workoutRow) + content.addView(card) } val backButton = Button(context) backButton.text = "Zurück" - backButton.setOnClickListener { - onBack() - } - - addView(backButton) + AppStyle.secondaryButton(backButton) + backButton.setOnClickListener { onBack() } + content.addView(backButton) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/workouttimer/ui/screens/WorkoutEditorScreen.kt b/app/src/main/java/com/example/workouttimer/ui/screens/WorkoutEditorScreen.kt index cfdd776..52283b7 100644 --- a/app/src/main/java/com/example/workouttimer/ui/screens/WorkoutEditorScreen.kt +++ b/app/src/main/java/com/example/workouttimer/ui/screens/WorkoutEditorScreen.kt @@ -12,56 +12,64 @@ import android.widget.TextView import com.example.workouttimer.domain.Exercise import com.example.workouttimer.domain.ExerciseTarget import com.example.workouttimer.domain.Workout +import com.example.workouttimer.ui.theme.AppStyle class WorkoutEditorScreen( context: Context, workoutToEdit: Workout?, - onSave: ( - name: String, - exercises: List - ) -> Unit, + onSave: (name: String, exercises: List) -> Unit, onCancel: () -> Unit ) : ScrollView(context) { private val exercises = mutableListOf() private val exerciseListContainer = LinearLayout(context) + private var editingIndex: Int? = null init { - exerciseListContainer.orientation = LinearLayout.VERTICAL - } + background = AppStyle.gradientDrawable() - init { val content = LinearLayout(context) - content.orientation = LinearLayout.VERTICAL - content.gravity = Gravity.CENTER_HORIZONTAL - content.setPadding(48, 48, 48, 48) - + AppStyle.applyGradientBackground(content) + content.layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT + ) addView(content) - workoutToEdit?.exercises?.let { - exercises.addAll(it) - } + workoutToEdit?.exercises?.let { exercises.addAll(it) } val title = TextView(context) - title.text = if (workoutToEdit == null) "Workout erstellen" else "Workout bearbeiten" - title.textSize = 28f - title.gravity = Gravity.CENTER + title.text = if (workoutToEdit == null) "Create\nWorkout" else "Edit\nWorkout" + AppStyle.headline(title) + content.addView(title) val workoutNameInput = EditText(context) workoutNameInput.hint = "Workout Name" workoutNameInput.setText(workoutToEdit?.name ?: "") + AppStyle.input(workoutNameInput) + content.addView(workoutNameInput) + + val sectionTitle = TextView(context) + sectionTitle.text = "Neue Übung" + AppStyle.sectionTitle(sectionTitle) + content.addView(sectionTitle) val exerciseNameInput = EditText(context) exerciseNameInput.hint = "Übungsname" + AppStyle.input(exerciseNameInput) + content.addView(exerciseNameInput) val targetGroup = RadioGroup(context) targetGroup.orientation = RadioGroup.HORIZONTAL + targetGroup.gravity = Gravity.CENTER val repsButton = RadioButton(context) repsButton.text = "Wiederholungen" + AppStyle.body(repsButton) val timeButton = RadioButton(context) timeButton.text = "Zeit" + AppStyle.body(timeButton) val repsButtonId = generateViewId() val timeButtonId = generateViewId() @@ -73,15 +81,63 @@ class WorkoutEditorScreen( targetGroup.addView(timeButton) targetGroup.check(repsButtonId) + AppStyle.setTextMargin(targetGroup) + content.addView(targetGroup) + val targetValueInput = EditText(context) targetValueInput.hint = "Wert, z.B. 12 oder 30" targetValueInput.inputType = android.text.InputType.TYPE_CLASS_NUMBER + AppStyle.input(targetValueInput) + content.addView(targetValueInput) val infoInput = EditText(context) infoInput.hint = "Info / Anmerkung" + AppStyle.input(infoInput) + content.addView(infoInput) val addExerciseButton = Button(context) - addExerciseButton.text = "Übung hinzufügen" + addExerciseButton.text = "Übung hinzufügen -->" + AppStyle.primaryButton(addExerciseButton) + content.addView(addExerciseButton) + + val listTitle = TextView(context) + listTitle.text = "Workout Plan" + AppStyle.sectionTitle(listTitle) + content.addView(listTitle) + + exerciseListContainer.orientation = LinearLayout.VERTICAL + content.addView(exerciseListContainer) + + fun clearExerciseInputs() { + exerciseNameInput.text.clear() + targetValueInput.text.clear() + infoInput.text.clear() + targetGroup.check(repsButtonId) + editingIndex = null + addExerciseButton.text = "Übung hinzufügen -->" + } + + fun fillExerciseInputs(index: Int) { + val exercise = exercises[index] + + exerciseNameInput.setText(exercise.name) + infoInput.setText(exercise.info) + + when (val target = exercise.target) { + is ExerciseTarget.Repetitions -> { + targetGroup.check(repsButtonId) + targetValueInput.setText(target.reps.toString()) + } + + is ExerciseTarget.Duration -> { + targetGroup.check(timeButtonId) + targetValueInput.setText(target.seconds.toString()) + } + } + + editingIndex = index + addExerciseButton.text = "Änderung übernehmen -->" + } addExerciseButton.setOnClickListener { val exerciseName = exerciseNameInput.text.toString() @@ -98,26 +154,34 @@ class WorkoutEditorScreen( ExerciseTarget.Duration(targetValueText.toInt()) } - val exercise = Exercise( - id = exercises.size + 1, - name = exerciseName, - target = target, - info = info - ) + val indexToEdit = editingIndex - exercises.add(exercise) + if (indexToEdit == null) { + exercises.add( + Exercise( + id = exercises.size + 1, + name = exerciseName, + target = target, + info = info + ) + ) + } else { + exercises[indexToEdit] = Exercise( + id = indexToEdit + 1, + name = exerciseName, + target = target, + info = info + ) + } - exerciseNameInput.text.clear() - targetValueInput.text.clear() - infoInput.text.clear() - targetGroup.check(repsButtonId) - - updateExerciseList() + updateExerciseIds() + clearExerciseInputs() + updateExerciseList(::fillExerciseInputs) } val saveButton = Button(context) - saveButton.text = "Workout speichern" - + saveButton.text = "Workout speichern -->" + AppStyle.primaryButton(saveButton) saveButton.setOnClickListener { val workoutName = workoutNameInput.text.toString() @@ -130,70 +194,99 @@ class WorkoutEditorScreen( val cancelButton = Button(context) cancelButton.text = "Abbrechen" + AppStyle.secondaryButton(cancelButton) cancelButton.setOnClickListener { onCancel() } - content.addView(title) - content.addView(workoutNameInput) - - val exerciseSectionTitle = TextView(context) - exerciseSectionTitle.text = "Übung hinzufügen" - exerciseSectionTitle.textSize = 22f - - content.addView(exerciseSectionTitle) - content.addView(exerciseNameInput) - content.addView(targetGroup) - content.addView(targetValueInput) - content.addView(infoInput) - content.addView(addExerciseButton) - - val listTitle = TextView(context) - listTitle.text = "Übungen im Workout" - listTitle.textSize = 22f - - content.addView(listTitle) - exerciseListContainer.orientation = LinearLayout.VERTICAL - content.addView(exerciseListContainer) - content.addView(saveButton) content.addView(cancelButton) - updateExerciseList() + updateExerciseList(::fillExerciseInputs) } - private fun updateExerciseList() { + private fun updateExerciseList(onEditExercise: (Int) -> Unit) { exerciseListContainer.removeAllViews() if (exercises.isEmpty()) { val emptyText = TextView(context) emptyText.text = "Noch keine Übungen hinzugefügt." + AppStyle.muted(emptyText) + AppStyle.setTextMargin(emptyText) exerciseListContainer.addView(emptyText) return } exercises.forEachIndexed { index, exercise -> + val card = LinearLayout(context) + AppStyle.card(card) - val row = LinearLayout(context) - row.orientation = LinearLayout.HORIZONTAL - row.gravity = Gravity.CENTER_VERTICAL + val exerciseTitle = TextView(context) + exerciseTitle.text = "${index + 1}. ${exercise.name}" + AppStyle.title(exerciseTitle) - val exerciseText = TextView(context) - exerciseText.text = "${index + 1}. ${exercise.name} - ${formatTarget(exercise.target)}" - exerciseText.textSize = 16f + val targetText = TextView(context) + targetText.text = formatTarget(exercise.target) + AppStyle.body(targetText) + + val infoText = TextView(context) + infoText.text = exercise.info + AppStyle.muted(infoText) + + val editButton = Button(context) + editButton.text = "Bearbeiten" + AppStyle.primaryButton(editButton) + editButton.setOnClickListener { + onEditExercise(index) + } + + val upButton = Button(context) + upButton.text = "Nach oben" + AppStyle.secondaryButton(upButton) + upButton.isEnabled = index > 0 + upButton.setOnClickListener { + val temp = exercises[index - 1] + exercises[index - 1] = exercises[index] + exercises[index] = temp + updateExerciseIds() + updateExerciseList(onEditExercise) + } + + val downButton = Button(context) + downButton.text = "Nach unten" + AppStyle.secondaryButton(downButton) + downButton.isEnabled = index < exercises.lastIndex + downButton.setOnClickListener { + val temp = exercises[index + 1] + exercises[index + 1] = exercises[index] + exercises[index] = temp + updateExerciseIds() + updateExerciseList(onEditExercise) + } val deleteButton = Button(context) deleteButton.text = "Löschen" + AppStyle.secondaryButton(deleteButton) deleteButton.setOnClickListener { exercises.removeAt(index) + editingIndex = null updateExerciseIds() - updateExerciseList() + updateExerciseList(onEditExercise) } - row.addView(exerciseText) - row.addView(deleteButton) + card.addView(exerciseTitle) + card.addView(targetText) - exerciseListContainer.addView(row) + if (exercise.info.isNotBlank()) { + card.addView(infoText) + } + + card.addView(editButton) + card.addView(upButton) + card.addView(downButton) + card.addView(deleteButton) + + exerciseListContainer.addView(card) } } diff --git a/app/src/main/java/com/example/workouttimer/ui/theme/AppStyle.kt b/app/src/main/java/com/example/workouttimer/ui/theme/AppStyle.kt new file mode 100644 index 0000000..f87a818 --- /dev/null +++ b/app/src/main/java/com/example/workouttimer/ui/theme/AppStyle.kt @@ -0,0 +1,266 @@ +package com.example.workouttimer.ui.theme + +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.res.ResourcesCompat +import com.example.workouttimer.R + +object AppStyle { + + val yellow = Color.rgb(245, 218, 49) + val green = Color.rgb(172, 232, 122) + val cyan = Color.rgb(111, 219, 232) + val black = Color.rgb(0, 0, 0) + val white = Color.rgb(255, 255, 255) + val softWhite = Color.rgb(250, 250, 250) + val lightBlack = Color.rgb(24, 24, 24) + val mutedText = Color.rgb(70, 70, 70) + + const val screenPadding = 48 + const val smallSpacing = 16 + const val mediumSpacing = 24 + const val largeSpacing = 40 + const val cornerRadius = 4f + + private val regularFontId = R.font.funnel_display_regular + private val boldFontId = R.font.funnel_display_bold + private val mediumFontId = R.font.funnel_display_medium + private val extraBoldFontId = R.font.funnel_display_extra_bold + + fun regular(view: TextView) { + view.typeface = ResourcesCompat.getFont(view.context, regularFontId) + } + + fun medium(view: TextView) { + view.typeface = ResourcesCompat.getFont(view.context, mediumFontId) + } + + fun bold(view: TextView) { + view.typeface = ResourcesCompat.getFont(view.context, boldFontId) + } + + fun extraBold(view: TextView) { + view.typeface = ResourcesCompat.getFont(view.context, extraBoldFontId) + } + + fun dp(view: View, value: Int): Int { + return (value * view.context.resources.displayMetrics.density).toInt() + } + + fun applyGradientBackground(layout: LinearLayout) { + layout.orientation = LinearLayout.VERTICAL + layout.setPadding( + dp(layout, 28), + dp(layout, 56), + dp(layout, 28), + dp(layout, 28) + ) + + val gradient = GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(yellow, green, cyan) + ) + + layout.background = gradientDrawable() + } + + fun headline(textView: TextView) { + extraBold(textView) + textView.textSize = 48f + textView.setTextColor(black) + } + + fun title(textView: TextView) { + bold(textView) + textView.textSize = 30f + textView.setTextColor(black) + } + + fun sectionTitle(textView: TextView) { + bold(textView) + textView.textSize = 22f + textView.setTextColor(black) + setTextMargin(textView) + } + + fun body(textView: TextView) { + regular(textView) + textView.textSize = 17f + textView.setTextColor(black) + } + + fun muted(textView: TextView) { + regular(textView) + textView.textSize = 15f + textView.setTextColor(mutedText) + } + + fun timer(textView: TextView) { + extraBold(textView) + textView.textSize = 76f + textView.setTextColor(black) + } + + fun primaryButton(button: Button) { + bold(button) + button.textSize = 16f + button.setTextColor(white) + button.background = roundedBackground(black) + button.isAllCaps = false + setFullWidthMargin(button) + } + + fun secondaryButton(button: Button) { + bold(button) + button.textSize = 16f + button.setTextColor(black) + button.background = roundedBackground(white) + button.isAllCaps = false + setFullWidthMargin(button) + } + + fun dangerButton(button: Button) { + bold(button) + button.textSize = 16f + button.setTextColor(white) + button.background = roundedBackground(Color.rgb(210, 55, 55)) + button.isAllCaps = false + setFullWidthMargin(button) + } + + fun compactButton(button: Button) { + medium(button) + button.textSize = 14f + button.setTextColor(black) + button.background = roundedBackground(white) + button.isAllCaps = false + } + + fun input(editText: EditText) { + regular(editText) + editText.textSize = 16f + editText.setTextColor(black) + editText.setHintTextColor(mutedText) + editText.background = roundedStrokeBackground(white) + editText.setPadding(28, 12, 28, 12) + setFullWidthMargin(editText) + } + + fun card(layout: LinearLayout) { + layout.orientation = LinearLayout.VERTICAL + layout.background = roundedBackground(softWhite) + layout.setPadding(28, 24, 28, 24) + + val params = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + params.setMargins(0, mediumSpacing, 0, 0) + layout.layoutParams = params + } + + fun row(layout: LinearLayout) { + layout.orientation = LinearLayout.HORIZONTAL + } + + fun setFullWidthMargin(view: View) { + val params = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + dp(view, 58) + ) + params.setMargins(0, dp(view, 14), 0, 0) + view.layoutParams = params + } + + fun setTextMargin(view: View) { + val params = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + params.setMargins(0, mediumSpacing, 0, 0) + view.layoutParams = params + } + + fun roundedBackground(color: Int): GradientDrawable { + return GradientDrawable().apply { + setColor(color) + cornerRadius = AppStyle.cornerRadius + } + } + + fun roundedStrokeBackground(color: Int): GradientDrawable { + return GradientDrawable().apply { + setColor(color) + cornerRadius = AppStyle.cornerRadius + setStroke(2, black) + } + } + + fun squareBackground(color: Int): GradientDrawable { + return GradientDrawable().apply { + setColor(color) + cornerRadius = 0f + } + } + + fun actionCard( + context: android.content.Context, + text: String, + dark: Boolean, + onClick: () -> Unit + ): LinearLayout { + val card = LinearLayout(context) + card.orientation = LinearLayout.HORIZONTAL + card.gravity = android.view.Gravity.CENTER_VERTICAL + card.setPadding(dp(card, 20), 0, dp(card, 20), 0) + card.background = squareBackground(if (dark) black else white) + card.setOnClickListener { onClick() } + + val label = TextView(context) + label.text = text + bold(label) + label.textSize = 20f + label.setTextColor(if (dark) white else black) + + val arrow = TextView(context) + arrow.text = " -->" + bold(arrow) + arrow.textSize = 30f + arrow.setTextColor(if (dark) white else black) + arrow.gravity = android.view.Gravity.END + + val labelParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ) + labelParams.setMargins(dp(card, 16), 0, dp(card, 16), 0) + + label.layoutParams = labelParams + + val cardParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + dp(card, 62) + ) + cardParams.setMargins(0, dp(card, 14), 0, 0) + card.layoutParams = cardParams + + card.addView(label) + card.addView(arrow) + + return card + } + + fun gradientDrawable(): GradientDrawable { + return GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(yellow, green, cyan) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/workouttimer/viewmodel/WorkoutViewModel.kt b/app/src/main/java/com/example/workouttimer/viewmodel/WorkoutViewModel.kt index 30045aa..faa61ef 100644 --- a/app/src/main/java/com/example/workouttimer/viewmodel/WorkoutViewModel.kt +++ b/app/src/main/java/com/example/workouttimer/viewmodel/WorkoutViewModel.kt @@ -114,4 +114,15 @@ class WorkoutViewModel(context: Context) { val workout = currentWorkout ?: return null return workout.exercises[currentExerciseIndex] } + + fun getNextExercise(): Exercise? { + val workout = currentWorkout ?: return null + val nextIndex = currentExerciseIndex + 1 + + return if (nextIndex <= workout.exercises.lastIndex) { + workout.exercises[nextIndex] + } else { + null + } + } } \ No newline at end of file diff --git a/app/src/main/res/font/funnel_display_bold.ttf b/app/src/main/res/font/funnel_display_bold.ttf new file mode 100644 index 0000000..c496f21 Binary files /dev/null and b/app/src/main/res/font/funnel_display_bold.ttf differ diff --git a/app/src/main/res/font/funnel_display_extra_bold.ttf b/app/src/main/res/font/funnel_display_extra_bold.ttf new file mode 100644 index 0000000..7214f45 Binary files /dev/null and b/app/src/main/res/font/funnel_display_extra_bold.ttf differ diff --git a/app/src/main/res/font/funnel_display_light.ttf b/app/src/main/res/font/funnel_display_light.ttf new file mode 100644 index 0000000..de2dfc1 Binary files /dev/null and b/app/src/main/res/font/funnel_display_light.ttf differ diff --git a/app/src/main/res/font/funnel_display_medium.ttf b/app/src/main/res/font/funnel_display_medium.ttf new file mode 100644 index 0000000..0457ea6 Binary files /dev/null and b/app/src/main/res/font/funnel_display_medium.ttf differ diff --git a/app/src/main/res/font/funnel_display_regular.ttf b/app/src/main/res/font/funnel_display_regular.ttf new file mode 100644 index 0000000..eaee9c9 Binary files /dev/null and b/app/src/main/res/font/funnel_display_regular.ttf differ diff --git a/app/src/main/res/font/funnel_display_semi_bold.ttf b/app/src/main/res/font/funnel_display_semi_bold.ttf new file mode 100644 index 0000000..af83212 Binary files /dev/null and b/app/src/main/res/font/funnel_display_semi_bold.ttf differ