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.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()

View File

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

View File

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

View File

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

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

View File

@ -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 {

View File

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

View File

@ -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<Exercise>
) -> Unit,
onSave: (name: String, exercises: List<Exercise>) -> Unit,
onCancel: () -> Unit
) : ScrollView(context) {
private val exercises = mutableListOf<Exercise>()
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)
}
}

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
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.