Assignment 3

Tests funktionieren nicht mehr, da Code schon etwas erweitert
This commit is contained in:
Paul-1108 2026-06-10 09:44:57 +02:00
parent 48e1b41a97
commit 72b110af36
9 changed files with 517 additions and 12 deletions

1
.idea/gradle.xml generated
View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>

1
.idea/misc.xml generated
View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">

View File

@ -0,0 +1,100 @@
package com.example.workouttimer.data
import android.content.Context
import com.example.workouttimer.domain.Exercise
import com.example.workouttimer.domain.ExerciseTarget
import com.example.workouttimer.domain.Workout
import org.json.JSONArray
import org.json.JSONObject
class WorkoutStorage(context: Context) {
private val prefs = context.getSharedPreferences("workout_storage", Context.MODE_PRIVATE)
fun saveWorkouts(workouts: List<Workout>) {
val jsonArray = JSONArray()
workouts.forEach { workout ->
val workoutObject = JSONObject()
workoutObject.put("id", workout.id)
workoutObject.put("name", workout.name)
val exercisesArray = JSONArray()
workout.exercises.forEach { exercise ->
val exerciseObject = JSONObject()
exerciseObject.put("id", exercise.id)
exerciseObject.put("name", exercise.name)
exerciseObject.put("info", exercise.info)
when (val target = exercise.target) {
is ExerciseTarget.Repetitions -> {
exerciseObject.put("targetType", "REPS")
exerciseObject.put("targetValue", target.reps)
}
is ExerciseTarget.Duration -> {
exerciseObject.put("targetType", "TIME")
exerciseObject.put("targetValue", target.seconds)
}
}
exercisesArray.put(exerciseObject)
}
workoutObject.put("exercises", exercisesArray)
jsonArray.put(workoutObject)
}
prefs.edit()
.putString("workouts", jsonArray.toString())
.apply()
}
fun loadWorkouts(): List<Workout> {
val json = prefs.getString("workouts", null) ?: return emptyList()
val jsonArray = JSONArray(json)
val workouts = mutableListOf<Workout>()
for (i in 0 until jsonArray.length()) {
val workoutObject = jsonArray.getJSONObject(i)
val exercisesArray = workoutObject.getJSONArray("exercises")
val exercises = mutableListOf<Exercise>()
for (j in 0 until exercisesArray.length()) {
val exerciseObject = exercisesArray.getJSONObject(j)
val targetType = exerciseObject.getString("targetType")
val targetValue = exerciseObject.getInt("targetValue")
val target = if (targetType == "REPS") {
ExerciseTarget.Repetitions(targetValue)
} else {
ExerciseTarget.Duration(targetValue)
}
exercises.add(
Exercise(
id = exerciseObject.getInt("id"),
name = exerciseObject.getString("name"),
target = target,
info = exerciseObject.getString("info")
)
)
}
workouts.add(
Workout(
id = workoutObject.getInt("id"),
name = workoutObject.getString("name"),
exercises = exercises,
tags = listOf("Gespeichert")
)
)
}
return workouts
}
}

View File

@ -10,11 +10,12 @@ import com.example.workouttimer.ui.screens.ReadyToStartScreen
import com.example.workouttimer.ui.screens.RunningWorkoutScreen 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
class AppNavigation(private val context: Context) { class AppNavigation(private val context: Context) {
private val container = FrameLayout(context) private val container = FrameLayout(context)
private val viewModel = WorkoutViewModel() private val viewModel = WorkoutViewModel(context)
fun createContent(): FrameLayout { fun createContent(): FrameLayout {
showHome() showHome()
@ -44,11 +45,52 @@ class AppNavigation(private val context: Context) {
viewModel.selectWorkout(workout) viewModel.selectWorkout(workout)
showReadyToStart() showReadyToStart()
}, },
onCreateWorkout = {
showCreateWorkout()
},
onEditWorkout = { workout ->
showEditWorkout(workout)
},
onBack = { showHome() } onBack = { showHome() }
) )
) )
} }
fun showCreateWorkout() {
setScreen(
WorkoutEditorScreen(
context = context,
workoutToEdit = null,
onSave = { name, exercises ->
viewModel.createWorkout(
name = name,
exercises = exercises
)
showSavedWorkouts()
},
onCancel = { showSavedWorkouts() }
)
)
}
fun showEditWorkout(workout: com.example.workouttimer.domain.Workout) {
setScreen(
WorkoutEditorScreen(
context = context,
workoutToEdit = workout,
onSave = { name, exercises ->
viewModel.updateWorkout(
workoutId = workout.id,
name = name,
exercises = exercises
)
showSavedWorkouts()
},
onCancel = { showSavedWorkouts() }
)
)
}
fun showReadyToStart() { fun showReadyToStart() {
setScreen( setScreen(
ReadyToStartScreen( ReadyToStartScreen(

View File

@ -1,4 +0,0 @@
package com.example.workouttimer.ui.screens
class CreateOrModifyScreen {
}

View File

@ -11,6 +11,8 @@ class SavedWorkoutsScreen(
context: Context, context: Context,
workouts: List<Workout>, workouts: List<Workout>,
onWorkoutSelected: (Workout) -> Unit, onWorkoutSelected: (Workout) -> Unit,
onCreateWorkout: () -> Unit,
onEditWorkout: (Workout) -> Unit,
onBack: () -> Unit onBack: () -> Unit
) : LinearLayout(context) { ) : LinearLayout(context) {
@ -26,13 +28,35 @@ class SavedWorkoutsScreen(
addView(title) addView(title)
val createButton = Button(context)
createButton.text = "Neues Workout erstellen"
createButton.setOnClickListener {
onCreateWorkout()
}
addView(createButton)
workouts.forEach { workout -> workouts.forEach { workout ->
val workoutRow = LinearLayout(context)
workoutRow.orientation = HORIZONTAL
workoutRow.gravity = Gravity.CENTER
val workoutButton = Button(context) val workoutButton = Button(context)
workoutButton.text = workout.name workoutButton.text = workout.name
workoutButton.setOnClickListener { workoutButton.setOnClickListener {
onWorkoutSelected(workout) onWorkoutSelected(workout)
} }
addView(workoutButton)
val editButton = Button(context)
editButton.text = "Bearbeiten"
editButton.setOnClickListener {
onEditWorkout(workout)
}
workoutRow.addView(workoutButton)
workoutRow.addView(editButton)
addView(workoutRow)
} }
val backButton = Button(context) val backButton = Button(context)

View File

@ -0,0 +1,177 @@
package com.example.workouttimer.ui.screens
import android.content.Context
import android.view.Gravity
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.RadioButton
import android.widget.RadioGroup
import android.widget.ScrollView
import android.widget.TextView
import com.example.workouttimer.domain.Exercise
import com.example.workouttimer.domain.ExerciseTarget
import com.example.workouttimer.domain.Workout
class WorkoutEditorScreen(
context: Context,
workoutToEdit: Workout?,
onSave: (
name: String,
exercises: List<Exercise>
) -> Unit,
onCancel: () -> Unit
) : ScrollView(context) {
private val exercises = mutableListOf<Exercise>()
private val exerciseListText = TextView(context)
init {
val content = LinearLayout(context)
content.orientation = LinearLayout.VERTICAL
content.gravity = Gravity.CENTER_HORIZONTAL
content.setPadding(48, 48, 48, 48)
addView(content)
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
val workoutNameInput = EditText(context)
workoutNameInput.hint = "Workout Name"
workoutNameInput.setText(workoutToEdit?.name ?: "")
val exerciseNameInput = EditText(context)
exerciseNameInput.hint = "Übungsname"
val targetGroup = RadioGroup(context)
targetGroup.orientation = RadioGroup.HORIZONTAL
val repsButton = RadioButton(context)
repsButton.text = "Wiederholungen"
val timeButton = RadioButton(context)
timeButton.text = "Zeit"
val repsButtonId = generateViewId()
val timeButtonId = generateViewId()
repsButton.id = repsButtonId
timeButton.id = timeButtonId
targetGroup.addView(repsButton)
targetGroup.addView(timeButton)
targetGroup.check(repsButtonId)
val targetValueInput = EditText(context)
targetValueInput.hint = "Wert, z.B. 12 oder 30"
targetValueInput.inputType = android.text.InputType.TYPE_CLASS_NUMBER
val infoInput = EditText(context)
infoInput.hint = "Info / Anmerkung"
val addExerciseButton = Button(context)
addExerciseButton.text = "Übung hinzufügen"
addExerciseButton.setOnClickListener {
val exerciseName = exerciseNameInput.text.toString()
val targetValueText = targetValueInput.text.toString()
val info = infoInput.text.toString()
if (exerciseName.isBlank() || targetValueText.isBlank()) {
return@setOnClickListener
}
val target = if (targetGroup.checkedRadioButtonId == repsButtonId) {
ExerciseTarget.Repetitions(targetValueText.toInt())
} else {
ExerciseTarget.Duration(targetValueText.toInt())
}
val exercise = Exercise(
id = exercises.size + 1,
name = exerciseName,
target = target,
info = info
)
exercises.add(exercise)
exerciseNameInput.text.clear()
targetValueInput.text.clear()
infoInput.text.clear()
targetGroup.check(repsButtonId)
updateExerciseList()
}
val saveButton = Button(context)
saveButton.text = "Workout speichern"
saveButton.setOnClickListener {
val workoutName = workoutNameInput.text.toString()
if (workoutName.isBlank() || exercises.isEmpty()) {
return@setOnClickListener
}
onSave(workoutName, exercises.toList())
}
val cancelButton = Button(context)
cancelButton.text = "Abbrechen"
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)
content.addView(exerciseListText)
content.addView(saveButton)
content.addView(cancelButton)
updateExerciseList()
}
private fun updateExerciseList() {
if (exercises.isEmpty()) {
exerciseListText.text = "Noch keine Übungen hinzugefügt."
return
}
exerciseListText.text = exercises.mapIndexed { index, exercise ->
"${index + 1}. ${exercise.name} - ${formatTarget(exercise.target)}"
}.joinToString("\n")
}
private fun formatTarget(target: ExerciseTarget): String {
return when (target) {
is ExerciseTarget.Repetitions -> "${target.reps} Wiederholungen"
is ExerciseTarget.Duration -> "${target.seconds} Sekunden"
}
}
}

View File

@ -2,14 +2,24 @@ package com.example.workouttimer.viewmodel
import com.example.workouttimer.data.MockWorkoutRepository import com.example.workouttimer.data.MockWorkoutRepository
import com.example.workouttimer.domain.Exercise import com.example.workouttimer.domain.Exercise
import com.example.workouttimer.domain.ExerciseTarget
import com.example.workouttimer.domain.Workout import com.example.workouttimer.domain.Workout
import com.example.workouttimer.domain.WorkoutState import com.example.workouttimer.domain.WorkoutState
class WorkoutViewModel { import android.content.Context
import com.example.workouttimer.data.WorkoutStorage
class WorkoutViewModel(context: Context) {
private val repository = MockWorkoutRepository() private val repository = MockWorkoutRepository()
private val storage = WorkoutStorage(context)
val workouts: List<Workout> = repository.getWorkouts() private val workoutList = storage.loadWorkouts().ifEmpty {
repository.getWorkouts()
}.toMutableList()
val workouts: List<Workout>
get() = workoutList
var currentWorkout: Workout? = null var currentWorkout: Workout? = null
private set private set
@ -26,6 +36,42 @@ class WorkoutViewModel {
workoutState = WorkoutState.READY workoutState = WorkoutState.READY
} }
fun createWorkout(
name: String,
exercises: List<Exercise>
) {
val newId = (workoutList.maxOfOrNull { it.id } ?: 0) + 1
val workout = Workout(
id = newId,
name = name,
tags = listOf("Eigene Workouts"),
exercises = exercises
)
workoutList.add(workout)
storage.saveWorkouts(workoutList)
}
fun updateWorkout(
workoutId: Int,
name: String,
exercises: List<Exercise>
) {
val index = workoutList.indexOfFirst { it.id == workoutId }
if (index == -1) return
val updatedWorkout = workoutList[index].copy(
name = name,
exercises = exercises
)
workoutList[index] = updatedWorkout
currentWorkout = updatedWorkout
storage.saveWorkouts(workoutList)
}
fun startWorkout() { fun startWorkout() {
workoutState = WorkoutState.RUNNING workoutState = WorkoutState.RUNNING
} }
@ -43,7 +89,6 @@ class WorkoutViewModel {
} }
fun nextExercise() { fun nextExercise() {
val workout = currentWorkout ?: return val workout = currentWorkout ?: return
if (currentExerciseIndex < workout.exercises.lastIndex) { if (currentExerciseIndex < workout.exercises.lastIndex) {
@ -54,9 +99,7 @@ class WorkoutViewModel {
} }
fun getCurrentExercise(): Exercise? { fun getCurrentExercise(): Exercise? {
val workout = currentWorkout ?: return null val workout = currentWorkout ?: return null
return workout.exercises[currentExerciseIndex] return workout.exercises[currentExerciseIndex]
} }
} }

View File

@ -0,0 +1,123 @@
package com.example.workouttimer
import com.example.workouttimer.domain.Exercise
import com.example.workouttimer.domain.ExerciseTarget
import com.example.workouttimer.domain.WorkoutState
import com.example.workouttimer.viewmodel.WorkoutViewModel
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Test
class WorkoutViewModelTest {
@Test
fun createWorkout_addsWorkoutToList() {
val viewModel = WorkoutViewModel()
val initialCount = viewModel.workouts.size
val exercises = listOf(
Exercise(
id = 1,
name = "Burpees",
target = ExerciseTarget.Repetitions(10),
info = "Full body exercise"
)
)
viewModel.createWorkout(
name = "Test Workout",
exercises = exercises
)
assertEquals(initialCount + 1, viewModel.workouts.size)
assertEquals("Test Workout", viewModel.workouts.last().name)
}
@Test
fun selectWorkout_setsCurrentWorkout() {
val viewModel = WorkoutViewModel()
val workout = viewModel.workouts.first()
viewModel.selectWorkout(workout)
assertNotNull(viewModel.currentWorkout)
assertEquals(workout.id, viewModel.currentWorkout?.id)
assertEquals(WorkoutState.READY, viewModel.workoutState)
}
@Test
fun startWorkout_setsStateToRunning() {
val viewModel = WorkoutViewModel()
val workout = viewModel.workouts.first()
viewModel.selectWorkout(workout)
viewModel.startWorkout()
assertEquals(WorkoutState.RUNNING, viewModel.workoutState)
}
@Test
fun nextExercise_movesToNextExercise() {
val viewModel = WorkoutViewModel()
val workout = viewModel.workouts.first()
viewModel.selectWorkout(workout)
viewModel.startWorkout()
val firstExercise = viewModel.getCurrentExercise()
viewModel.nextExercise()
val secondExercise = viewModel.getCurrentExercise()
assertNotNull(firstExercise)
assertNotNull(secondExercise)
assertEquals(2, viewModel.currentExerciseIndex + 1)
}
@Test
fun nextExercise_afterLastExercise_finishesWorkout() {
val viewModel = WorkoutViewModel()
val workout = viewModel.workouts.first()
viewModel.selectWorkout(workout)
viewModel.startWorkout()
repeat(workout.exercises.size) {
viewModel.nextExercise()
}
assertEquals(WorkoutState.FINISHED, viewModel.workoutState)
}
@Test
fun updateWorkout_changesWorkoutNameAndExercises() {
val viewModel = WorkoutViewModel()
val workout = viewModel.workouts.first()
val updatedExercises = listOf(
Exercise(
id = 1,
name = "Updated Push-ups",
target = ExerciseTarget.Repetitions(20),
info = "Updated info"
)
)
viewModel.updateWorkout(
workoutId = workout.id,
name = "Updated Workout",
exercises = updatedExercises
)
val updatedWorkout = viewModel.workouts.first { it.id == workout.id }
assertEquals("Updated Workout", updatedWorkout.name)
assertEquals(1, updatedWorkout.exercises.size)
assertEquals("Updated Push-ups", updatedWorkout.exercises.first().name)
}
}