This commit is contained in:
Paul-1108 2026-07-01 02:37:31 +02:00
parent 8450a791d5
commit d9093ba007
17 changed files with 803 additions and 210 deletions

View File

@ -11,6 +11,7 @@ import com.example.workouttimer.ui.screens.RunningWorkoutScreen
import com.example.workouttimer.ui.screens.SavedWorkoutsScreen import com.example.workouttimer.ui.screens.SavedWorkoutsScreen
import com.example.workouttimer.viewmodel.WorkoutViewModel import com.example.workouttimer.viewmodel.WorkoutViewModel
import com.example.workouttimer.ui.screens.WorkoutEditorScreen import com.example.workouttimer.ui.screens.WorkoutEditorScreen
import com.example.workouttimer.ui.screens.PreparationCountdownScreen
class AppNavigation(private val context: Context) { class AppNavigation(private val context: Context) {
@ -31,7 +32,12 @@ class AppNavigation(private val context: Context) {
setScreen( setScreen(
HomeScreen( HomeScreen(
context = context, context = context,
onShowWorkouts = { showSavedWorkouts() } onShowWorkouts = {
showSavedWorkouts()
},
onCreateWorkout = {
showCreateWorkout()
}
) )
) )
} }
@ -101,20 +107,32 @@ class AppNavigation(private val context: Context) {
context = context, context = context,
workout = viewModel.currentWorkout, workout = viewModel.currentWorkout,
onStart = { onStart = {
viewModel.startWorkout() showPreparationCountdown()
showRunningWorkout()
}, },
onBack = { showSavedWorkouts() } onBack = { showSavedWorkouts() }
) )
) )
} }
fun showPreparationCountdown() {
setScreen(
PreparationCountdownScreen(
context = context,
onSkip = {
viewModel.startWorkout()
showRunningWorkout()
}
)
)
}
fun showRunningWorkout() { fun showRunningWorkout() {
setScreen( setScreen(
RunningWorkoutScreen( RunningWorkoutScreen(
context = context, context = context,
workout = viewModel.currentWorkout, workout = viewModel.currentWorkout,
exercise = viewModel.getCurrentExercise(), exercise = viewModel.getCurrentExercise(),
nextExercise = viewModel.getNextExercise(),
exerciseNumber = viewModel.currentExerciseIndex + 1, exerciseNumber = viewModel.currentExerciseIndex + 1,
onNextExercise = { onNextExercise = {
viewModel.nextExercise() viewModel.nextExercise()

View File

@ -6,6 +6,7 @@ import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import com.example.workouttimer.domain.Workout import com.example.workouttimer.domain.Workout
import com.example.workouttimer.ui.theme.AppStyle
class FinishedScreen( class FinishedScreen(
context: Context, context: Context,
@ -14,34 +15,33 @@ class FinishedScreen(
) : LinearLayout(context) { ) : LinearLayout(context) {
init { init {
orientation = VERTICAL AppStyle.applyGradientBackground(this)
gravity = Gravity.CENTER gravity = Gravity.CENTER_VERTICAL
setPadding(48, 48, 48, 48)
val title = TextView(context) val title = TextView(context)
title.text = "Workout beendet!" title.text = "Workout\nDone"
title.textSize = 32f AppStyle.headline(title)
title.gravity = Gravity.CENTER addView(title)
val workoutText = TextView(context) val workoutText = TextView(context)
workoutText.text = workout?.name ?: "" workoutText.text = workout?.name ?: "Training abgeschlossen"
workoutText.textSize = 22f AppStyle.title(workoutText)
workoutText.gravity = Gravity.CENTER AppStyle.setTextMargin(workoutText)
addView(workoutText)
val summary = TextView(context) val summary = TextView(context)
summary.text = "Gute Arbeit. Dein Workout wurde erfolgreich abgeschlossen." summary.text = "Gute Arbeit. Dein Workout wurde erfolgreich abgeschlossen."
summary.textSize = 18f AppStyle.muted(summary)
summary.gravity = Gravity.CENTER AppStyle.setTextMargin(summary)
addView(summary)
val homeButton = Button(context) val homeButton = Button(context)
homeButton.text = "Zur Startseite" homeButton.text = "Zur Startseite -->"
AppStyle.primaryButton(homeButton)
homeButton.setOnClickListener { homeButton.setOnClickListener {
onBackHome() onBackHome()
} }
addView(title)
addView(workoutText)
addView(summary)
addView(homeButton) addView(homeButton)
} }
} }

View File

@ -2,38 +2,70 @@ package com.example.workouttimer.ui.screens
import android.content.Context import android.content.Context
import android.view.Gravity import android.view.Gravity
import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Space
import android.widget.TextView import android.widget.TextView
import com.example.workouttimer.ui.theme.AppStyle
class HomeScreen( class HomeScreen(
context: Context, context: Context,
onShowWorkouts: () -> Unit onShowWorkouts: () -> Unit,
onCreateWorkout: () -> Unit
) : LinearLayout(context) { ) : LinearLayout(context) {
init { init {
orientation = VERTICAL AppStyle.applyGradientBackground(this)
gravity = Gravity.CENTER gravity = Gravity.CENTER_VERTICAL
setPadding(48, 48, 48, 48)
val topSpace = Space(context)
topSpace.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
AppStyle.dp(this, 90)
)
val title = TextView(context) val title = TextView(context)
title.text = "Workout Timer" title.text = "Workout Timer"
title.textSize = 32f title.gravity = Gravity.START
title.gravity = Gravity.CENTER AppStyle.headline(title)
val subtitle = TextView(context) val subtitle = TextView(context)
subtitle.text = "Plane und starte dein Workout" subtitle.text = "Build your own rhythm."
subtitle.textSize = 18f subtitle.gravity = Gravity.START
subtitle.gravity = Gravity.CENTER AppStyle.muted(subtitle)
AppStyle.setTextMargin(subtitle)
val button = Button(context) val buttonSpace = Space(context)
button.text = "Gespeicherte Workouts" buttonSpace.layoutParams = LayoutParams(
button.setOnClickListener { LayoutParams.MATCH_PARENT,
onShowWorkouts() 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(title)
addView(subtitle) addView(subtitle)
addView(button) addView(buttonSpace)
addView(showWorkoutsCard)
addView(createWorkoutCard)
addView(bottomNote)
} }
} }

View File

@ -5,6 +5,7 @@ import android.view.Gravity
import android.widget.Button import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import com.example.workouttimer.ui.theme.AppStyle
class PauseScreen( class PauseScreen(
context: Context, context: Context,
@ -13,28 +14,34 @@ class PauseScreen(
) : LinearLayout(context) { ) : LinearLayout(context) {
init { init {
orientation = VERTICAL AppStyle.applyGradientBackground(this)
gravity = Gravity.CENTER gravity = Gravity.CENTER_VERTICAL
setPadding(48, 48, 48, 48)
val title = TextView(context) val title = TextView(context)
title.text = "Pause" title.text = "Pause"
title.textSize = 32f AppStyle.headline(title)
title.gravity = Gravity.CENTER 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) val resumeButton = Button(context)
resumeButton.text = "Weiter trainieren" resumeButton.text = "Weiter trainieren -->"
AppStyle.primaryButton(resumeButton)
resumeButton.setOnClickListener { resumeButton.setOnClickListener {
onResume() onResume()
} }
val finishButton = Button(context) val finishButton = Button(context)
finishButton.text = "Workout beenden" finishButton.text = "Workout beenden"
AppStyle.secondaryButton(finishButton)
finishButton.setOnClickListener { finishButton.setOnClickListener {
onFinish() onFinish()
} }
addView(title)
addView(resumeButton) addView(resumeButton)
addView(finishButton) addView(finishButton)
} }

View File

@ -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()
}
}

View File

@ -4,9 +4,11 @@ import android.content.Context
import android.view.Gravity import android.view.Gravity
import android.widget.Button import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Space
import android.widget.TextView import android.widget.TextView
import com.example.workouttimer.domain.ExerciseTarget import com.example.workouttimer.domain.ExerciseTarget
import com.example.workouttimer.domain.Workout import com.example.workouttimer.domain.Workout
import com.example.workouttimer.ui.theme.AppStyle
class ReadyToStartScreen( class ReadyToStartScreen(
context: Context, context: Context,
@ -16,36 +18,70 @@ class ReadyToStartScreen(
) : LinearLayout(context) { ) : LinearLayout(context) {
init { init {
orientation = VERTICAL AppStyle.applyGradientBackground(this)
gravity = Gravity.CENTER_HORIZONTAL
setPadding(48, 48, 48, 48) layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
gravity = Gravity.TOP
val title = TextView(context) val title = TextView(context)
title.text = workout?.name ?: "Kein Workout ausgewählt" title.text = "Ready\nto Start"
title.textSize = 28f AppStyle.headline(title)
title.gravity = Gravity.CENTER
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 exerciseList = LinearLayout(context)
val exerciseText = TextView(context) exerciseList.orientation = VERTICAL
exerciseText.text = "${exercise.name} - ${formatTarget(exercise.target)}"
exerciseText.textSize = 18f workout?.exercises?.forEachIndexed { index, exercise ->
addView(exerciseText)
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) val startButton = Button(context)
startButton.text = "Workout starten" startButton.text = "Workout starten -->"
AppStyle.primaryButton(startButton)
startButton.setOnClickListener { startButton.setOnClickListener {
onStart() onStart()
} }
val backButton = Button(context) val backButton = Button(context)
backButton.text = "Zurück" backButton.text = "Zurück"
AppStyle.secondaryButton(backButton)
backButton.setOnClickListener { backButton.setOnClickListener {
onBack() onBack()
} }
addView(title)
addView(workoutName)
addView(exerciseList)
addView(spacer)
addView(startButton) addView(startButton)
addView(backButton) addView(backButton)
} }

View File

@ -5,110 +5,146 @@ import android.os.CountDownTimer
import android.view.Gravity import android.view.Gravity
import android.widget.Button import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Space
import android.widget.TextView import android.widget.TextView
import com.example.workouttimer.domain.Exercise import com.example.workouttimer.domain.Exercise
import com.example.workouttimer.domain.ExerciseTarget import com.example.workouttimer.domain.ExerciseTarget
import com.example.workouttimer.domain.Workout import com.example.workouttimer.domain.Workout
import com.example.workouttimer.ui.theme.AppStyle
class RunningWorkoutScreen( class RunningWorkoutScreen(
context: Context, context: Context,
workout: Workout?, workout: Workout?,
exercise: Exercise?, exercise: Exercise?,
nextExercise: Exercise?,
exerciseNumber: Int, exerciseNumber: Int,
onNextExercise: () -> Unit, onNextExercise: () -> Unit,
onPause: () -> Unit onPause: () -> Unit
) : LinearLayout(context) { ) : LinearLayout(context) {
private var countDownTimer: CountDownTimer? = null private var timer: CountDownTimer? = null
private val mainValueText = TextView(context)
init { init {
orientation = VERTICAL AppStyle.applyGradientBackground(this)
gravity = Gravity.CENTER gravity = Gravity.CENTER_VERTICAL
setPadding(48, 48, 48, 48)
val workoutTitle = TextView(context) val workoutLabel = TextView(context)
workoutTitle.text = workout?.name ?: "Workout läuft" workoutLabel.text = workout?.name ?: "Workout läuft"
workoutTitle.textSize = 22f workoutLabel.gravity = Gravity.CENTER
workoutTitle.gravity = Gravity.CENTER AppStyle.muted(workoutLabel)
val topSpace = Space(context)
topSpace.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
AppStyle.dp(this, 28)
)
val exerciseTitle = TextView(context) val exerciseTitle = TextView(context)
exerciseTitle.text = "Übung $exerciseNumber: ${exercise?.name ?: "-"}" exerciseTitle.text = "${exerciseNumber}. ${exercise?.name ?: "-"}"
exerciseTitle.textSize = 30f
exerciseTitle.gravity = Gravity.CENTER exerciseTitle.gravity = Gravity.CENTER
AppStyle.headline(exerciseTitle)
val targetText = TextView(context) val middleSpace = Space(context)
targetText.text = exercise?.target?.let { formatTarget(it) } ?: "" middleSpace.layoutParams = LayoutParams(
targetText.textSize = 24f LayoutParams.MATCH_PARENT,
targetText.gravity = Gravity.CENTER AppStyle.dp(this, 26)
)
val timerText = TextView(context) mainValueText.gravity = Gravity.CENTER
timerText.textSize = 36f AppStyle.timer(mainValueText)
timerText.gravity = Gravity.CENTER
val infoText = TextView(context) val unitText = TextView(context)
infoText.text = exercise?.info ?: "" unitText.gravity = Gravity.CENTER
infoText.textSize = 16f AppStyle.title(unitText)
infoText.gravity = Gravity.CENTER
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) 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 { nextButton.setOnClickListener {
countDownTimer?.cancel() timer?.cancel()
onNextExercise() onNextExercise()
} }
val pauseButton = Button(context) val pauseButton = Button(context)
pauseButton.text = "Pause" pauseButton.text = "Pause"
AppStyle.secondaryButton(pauseButton)
pauseButton.setOnClickListener { pauseButton.setOnClickListener {
countDownTimer?.cancel() timer?.cancel()
onPause() onPause()
} }
addView(workoutTitle) addView(workoutLabel)
addView(topSpace)
addView(exerciseTitle) addView(exerciseTitle)
addView(targetText) addView(middleSpace)
addView(timerText) addView(mainValueText)
addView(infoText) addView(unitText)
addView(nextText)
addView(buttonSpace)
addView(nextButton) addView(nextButton)
addView(pauseButton) 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( private fun startTimer(seconds: Int, onFinished: () -> Unit) {
seconds: Int, mainValueText.text = seconds.toString()
timerText: TextView,
onFinished: () -> Unit
) {
timerText.text = "$seconds"
countDownTimer = object : CountDownTimer(seconds * 1000L, 1000L) { timer = object : CountDownTimer(seconds * 1000L, 1000L) {
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
val remainingSeconds = (millisUntilFinished / 1000L).toInt() val remaining = (millisUntilFinished / 1000L).toInt()
timerText.text = remainingSeconds.toString() mainValueText.text = remaining.toString()
} }
override fun onFinish() { override fun onFinish() {
timerText.text = "0" mainValueText.text = "0"
onFinished() onFinished()
} }
} }
countDownTimer?.start() timer?.start()
} }
override fun onDetachedFromWindow() { override fun onDetachedFromWindow() {
super.onDetachedFromWindow() super.onDetachedFromWindow()
countDownTimer?.cancel() timer?.cancel()
} }
private fun formatTarget(target: ExerciseTarget): String { private fun formatTarget(target: ExerciseTarget): String {

View File

@ -4,8 +4,10 @@ import android.content.Context
import android.view.Gravity import android.view.Gravity
import android.widget.Button import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView import android.widget.TextView
import com.example.workouttimer.domain.Workout import com.example.workouttimer.domain.Workout
import com.example.workouttimer.ui.theme.AppStyle
class SavedWorkoutsScreen( class SavedWorkoutsScreen(
context: Context, context: Context,
@ -15,64 +17,72 @@ class SavedWorkoutsScreen(
onEditWorkout: (Workout) -> Unit, onEditWorkout: (Workout) -> Unit,
onDeleteWorkout: (Workout) -> Unit, onDeleteWorkout: (Workout) -> Unit,
onBack: () -> Unit onBack: () -> Unit
) : LinearLayout(context) { ) : ScrollView(context) {
init { init {
orientation = VERTICAL background = AppStyle.gradientDrawable()
gravity = Gravity.CENTER_HORIZONTAL
setPadding(48, 48, 48, 48) val content = LinearLayout(context)
AppStyle.applyGradientBackground(content)
content.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
)
addView(content)
val title = TextView(context) val title = TextView(context)
title.text = "Gespeicherte Workouts" title.text = "Saved\nWorkouts"
title.textSize = 28f AppStyle.headline(title)
title.gravity = Gravity.CENTER content.addView(title)
addView(title)
val createButton = Button(context) val createButton = Button(context)
createButton.text = "Neues Workout erstellen" createButton.text = "Neues Workout erstellen -->"
createButton.setOnClickListener { AppStyle.primaryButton(createButton)
onCreateWorkout() createButton.setOnClickListener { onCreateWorkout() }
} content.addView(createButton)
addView(createButton)
workouts.forEach { workout -> workouts.forEach { workout ->
val card = LinearLayout(context)
AppStyle.card(card)
val workoutRow = LinearLayout(context) val name = TextView(context)
workoutRow.orientation = HORIZONTAL name.text = workout.name
workoutRow.gravity = Gravity.CENTER AppStyle.title(name)
val workoutButton = Button(context) val details = TextView(context)
workoutButton.text = workout.name details.text = "${workout.exercises.size} Übungen"
workoutButton.setOnClickListener { AppStyle.muted(details)
onWorkoutSelected(workout)
} val startButton = Button(context)
startButton.text = "Starten -->"
AppStyle.primaryButton(startButton)
startButton.setOnClickListener { onWorkoutSelected(workout) }
val editButton = Button(context) val editButton = Button(context)
editButton.text = "Bearbeiten" editButton.text = "Bearbeiten"
editButton.setOnClickListener { AppStyle.secondaryButton(editButton)
onEditWorkout(workout) editButton.setOnClickListener { onEditWorkout(workout) }
}
val deleteButton = Button(context) val deleteButton = Button(context)
deleteButton.text = "Löschen" deleteButton.text = "Workout löschen"
deleteButton.setOnClickListener { AppStyle.secondaryButton(deleteButton)
onDeleteWorkout(workout) deleteButton.setOnClickListener { onDeleteWorkout(workout) }
}
workoutRow.addView(workoutButton) card.addView(name)
workoutRow.addView(editButton) card.addView(details)
workoutRow.addView(deleteButton) card.addView(startButton)
card.addView(editButton)
card.addView(deleteButton)
addView(workoutRow) content.addView(card)
} }
val backButton = Button(context) val backButton = Button(context)
backButton.text = "Zurück" backButton.text = "Zurück"
backButton.setOnClickListener { AppStyle.secondaryButton(backButton)
onBack() backButton.setOnClickListener { onBack() }
} content.addView(backButton)
addView(backButton)
} }
} }

View File

@ -12,56 +12,64 @@ import android.widget.TextView
import com.example.workouttimer.domain.Exercise import com.example.workouttimer.domain.Exercise
import com.example.workouttimer.domain.ExerciseTarget import com.example.workouttimer.domain.ExerciseTarget
import com.example.workouttimer.domain.Workout import com.example.workouttimer.domain.Workout
import com.example.workouttimer.ui.theme.AppStyle
class WorkoutEditorScreen( class WorkoutEditorScreen(
context: Context, context: Context,
workoutToEdit: Workout?, workoutToEdit: Workout?,
onSave: ( onSave: (name: String, exercises: List<Exercise>) -> Unit,
name: String,
exercises: List<Exercise>
) -> Unit,
onCancel: () -> Unit onCancel: () -> Unit
) : ScrollView(context) { ) : ScrollView(context) {
private val exercises = mutableListOf<Exercise>() private val exercises = mutableListOf<Exercise>()
private val exerciseListContainer = LinearLayout(context) private val exerciseListContainer = LinearLayout(context)
private var editingIndex: Int? = null
init { init {
exerciseListContainer.orientation = LinearLayout.VERTICAL background = AppStyle.gradientDrawable()
}
init {
val content = LinearLayout(context) val content = LinearLayout(context)
content.orientation = LinearLayout.VERTICAL AppStyle.applyGradientBackground(content)
content.gravity = Gravity.CENTER_HORIZONTAL content.layoutParams = LayoutParams(
content.setPadding(48, 48, 48, 48) LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
)
addView(content) addView(content)
workoutToEdit?.exercises?.let { workoutToEdit?.exercises?.let { exercises.addAll(it) }
exercises.addAll(it)
}
val title = TextView(context) val title = TextView(context)
title.text = if (workoutToEdit == null) "Workout erstellen" else "Workout bearbeiten" title.text = if (workoutToEdit == null) "Create\nWorkout" else "Edit\nWorkout"
title.textSize = 28f AppStyle.headline(title)
title.gravity = Gravity.CENTER content.addView(title)
val workoutNameInput = EditText(context) val workoutNameInput = EditText(context)
workoutNameInput.hint = "Workout Name" workoutNameInput.hint = "Workout Name"
workoutNameInput.setText(workoutToEdit?.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) val exerciseNameInput = EditText(context)
exerciseNameInput.hint = "Übungsname" exerciseNameInput.hint = "Übungsname"
AppStyle.input(exerciseNameInput)
content.addView(exerciseNameInput)
val targetGroup = RadioGroup(context) val targetGroup = RadioGroup(context)
targetGroup.orientation = RadioGroup.HORIZONTAL targetGroup.orientation = RadioGroup.HORIZONTAL
targetGroup.gravity = Gravity.CENTER
val repsButton = RadioButton(context) val repsButton = RadioButton(context)
repsButton.text = "Wiederholungen" repsButton.text = "Wiederholungen"
AppStyle.body(repsButton)
val timeButton = RadioButton(context) val timeButton = RadioButton(context)
timeButton.text = "Zeit" timeButton.text = "Zeit"
AppStyle.body(timeButton)
val repsButtonId = generateViewId() val repsButtonId = generateViewId()
val timeButtonId = generateViewId() val timeButtonId = generateViewId()
@ -73,15 +81,63 @@ class WorkoutEditorScreen(
targetGroup.addView(timeButton) targetGroup.addView(timeButton)
targetGroup.check(repsButtonId) targetGroup.check(repsButtonId)
AppStyle.setTextMargin(targetGroup)
content.addView(targetGroup)
val targetValueInput = EditText(context) val targetValueInput = EditText(context)
targetValueInput.hint = "Wert, z.B. 12 oder 30" targetValueInput.hint = "Wert, z.B. 12 oder 30"
targetValueInput.inputType = android.text.InputType.TYPE_CLASS_NUMBER targetValueInput.inputType = android.text.InputType.TYPE_CLASS_NUMBER
AppStyle.input(targetValueInput)
content.addView(targetValueInput)
val infoInput = EditText(context) val infoInput = EditText(context)
infoInput.hint = "Info / Anmerkung" infoInput.hint = "Info / Anmerkung"
AppStyle.input(infoInput)
content.addView(infoInput)
val addExerciseButton = Button(context) 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 { addExerciseButton.setOnClickListener {
val exerciseName = exerciseNameInput.text.toString() val exerciseName = exerciseNameInput.text.toString()
@ -98,26 +154,34 @@ class WorkoutEditorScreen(
ExerciseTarget.Duration(targetValueText.toInt()) ExerciseTarget.Duration(targetValueText.toInt())
} }
val exercise = Exercise( val indexToEdit = editingIndex
id = exercises.size + 1,
name = exerciseName,
target = target,
info = info
)
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() updateExerciseIds()
targetValueInput.text.clear() clearExerciseInputs()
infoInput.text.clear() updateExerciseList(::fillExerciseInputs)
targetGroup.check(repsButtonId)
updateExerciseList()
} }
val saveButton = Button(context) val saveButton = Button(context)
saveButton.text = "Workout speichern" saveButton.text = "Workout speichern -->"
AppStyle.primaryButton(saveButton)
saveButton.setOnClickListener { saveButton.setOnClickListener {
val workoutName = workoutNameInput.text.toString() val workoutName = workoutNameInput.text.toString()
@ -130,70 +194,99 @@ class WorkoutEditorScreen(
val cancelButton = Button(context) val cancelButton = Button(context)
cancelButton.text = "Abbrechen" cancelButton.text = "Abbrechen"
AppStyle.secondaryButton(cancelButton)
cancelButton.setOnClickListener { cancelButton.setOnClickListener {
onCancel() 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(saveButton)
content.addView(cancelButton) content.addView(cancelButton)
updateExerciseList() updateExerciseList(::fillExerciseInputs)
} }
private fun updateExerciseList() { private fun updateExerciseList(onEditExercise: (Int) -> Unit) {
exerciseListContainer.removeAllViews() exerciseListContainer.removeAllViews()
if (exercises.isEmpty()) { if (exercises.isEmpty()) {
val emptyText = TextView(context) val emptyText = TextView(context)
emptyText.text = "Noch keine Übungen hinzugefügt." emptyText.text = "Noch keine Übungen hinzugefügt."
AppStyle.muted(emptyText)
AppStyle.setTextMargin(emptyText)
exerciseListContainer.addView(emptyText) exerciseListContainer.addView(emptyText)
return return
} }
exercises.forEachIndexed { index, exercise -> exercises.forEachIndexed { index, exercise ->
val card = LinearLayout(context)
AppStyle.card(card)
val row = LinearLayout(context) val exerciseTitle = TextView(context)
row.orientation = LinearLayout.HORIZONTAL exerciseTitle.text = "${index + 1}. ${exercise.name}"
row.gravity = Gravity.CENTER_VERTICAL AppStyle.title(exerciseTitle)
val exerciseText = TextView(context) val targetText = TextView(context)
exerciseText.text = "${index + 1}. ${exercise.name} - ${formatTarget(exercise.target)}" targetText.text = formatTarget(exercise.target)
exerciseText.textSize = 16f 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) val deleteButton = Button(context)
deleteButton.text = "Löschen" deleteButton.text = "Löschen"
AppStyle.secondaryButton(deleteButton)
deleteButton.setOnClickListener { deleteButton.setOnClickListener {
exercises.removeAt(index) exercises.removeAt(index)
editingIndex = null
updateExerciseIds() updateExerciseIds()
updateExerciseList() updateExerciseList(onEditExercise)
} }
row.addView(exerciseText) card.addView(exerciseTitle)
row.addView(deleteButton) 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)
} }
} }

View File

@ -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)
)
}
}

View File

@ -114,4 +114,15 @@ class WorkoutViewModel(context: Context) {
val workout = currentWorkout ?: return null val workout = currentWorkout ?: return null
return workout.exercises[currentExerciseIndex] 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
}
}
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.