Assignment 3
Tests funktionieren nicht mehr, da Code schon etwas erweitert
This commit is contained in:
parent
48e1b41a97
commit
72b110af36
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
|
||||
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -10,11 +10,12 @@ import com.example.workouttimer.ui.screens.ReadyToStartScreen
|
||||
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
|
||||
|
||||
class AppNavigation(private val context: Context) {
|
||||
|
||||
private val container = FrameLayout(context)
|
||||
private val viewModel = WorkoutViewModel()
|
||||
private val viewModel = WorkoutViewModel(context)
|
||||
|
||||
fun createContent(): FrameLayout {
|
||||
showHome()
|
||||
@ -44,11 +45,52 @@ class AppNavigation(private val context: Context) {
|
||||
viewModel.selectWorkout(workout)
|
||||
showReadyToStart()
|
||||
},
|
||||
onCreateWorkout = {
|
||||
showCreateWorkout()
|
||||
},
|
||||
onEditWorkout = { workout ->
|
||||
showEditWorkout(workout)
|
||||
},
|
||||
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() {
|
||||
setScreen(
|
||||
ReadyToStartScreen(
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
package com.example.workouttimer.ui.screens
|
||||
|
||||
class CreateOrModifyScreen {
|
||||
}
|
||||
@ -11,6 +11,8 @@ class SavedWorkoutsScreen(
|
||||
context: Context,
|
||||
workouts: List<Workout>,
|
||||
onWorkoutSelected: (Workout) -> Unit,
|
||||
onCreateWorkout: () -> Unit,
|
||||
onEditWorkout: (Workout) -> Unit,
|
||||
onBack: () -> Unit
|
||||
) : LinearLayout(context) {
|
||||
|
||||
@ -26,13 +28,35 @@ class SavedWorkoutsScreen(
|
||||
|
||||
addView(title)
|
||||
|
||||
val createButton = Button(context)
|
||||
createButton.text = "Neues Workout erstellen"
|
||||
createButton.setOnClickListener {
|
||||
onCreateWorkout()
|
||||
}
|
||||
addView(createButton)
|
||||
|
||||
workouts.forEach { workout ->
|
||||
|
||||
val workoutRow = LinearLayout(context)
|
||||
workoutRow.orientation = HORIZONTAL
|
||||
workoutRow.gravity = Gravity.CENTER
|
||||
|
||||
val workoutButton = Button(context)
|
||||
workoutButton.text = workout.name
|
||||
workoutButton.setOnClickListener {
|
||||
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)
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,14 +2,24 @@ package com.example.workouttimer.viewmodel
|
||||
|
||||
import com.example.workouttimer.data.MockWorkoutRepository
|
||||
import com.example.workouttimer.domain.Exercise
|
||||
import com.example.workouttimer.domain.ExerciseTarget
|
||||
import com.example.workouttimer.domain.Workout
|
||||
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 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
|
||||
private set
|
||||
@ -26,6 +36,42 @@ class WorkoutViewModel {
|
||||
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() {
|
||||
workoutState = WorkoutState.RUNNING
|
||||
}
|
||||
@ -43,7 +89,6 @@ class WorkoutViewModel {
|
||||
}
|
||||
|
||||
fun nextExercise() {
|
||||
|
||||
val workout = currentWorkout ?: return
|
||||
|
||||
if (currentExerciseIndex < workout.exercises.lastIndex) {
|
||||
@ -54,9 +99,7 @@ class WorkoutViewModel {
|
||||
}
|
||||
|
||||
fun getCurrentExercise(): Exercise? {
|
||||
|
||||
val workout = currentWorkout ?: return null
|
||||
|
||||
return workout.exercises[currentExerciseIndex]
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user