diff --git a/WorkoutsPlus/ContentView.swift b/WorkoutsPlus/ContentView.swift index 06f230a..00713dc 100644 --- a/WorkoutsPlus/ContentView.swift +++ b/WorkoutsPlus/ContentView.swift @@ -13,6 +13,11 @@ struct ContentView: View { var body: some View { TabView { + Text("Log") + .tabItem { + Image(systemName: "pencil.and.list.clipboard") + Text("Log") + } WorkoutLibrary() .tabItem { Image(systemName: "figure.run.square.stack") diff --git a/WorkoutsPlus/Exercise/AddExercise.swift b/WorkoutsPlus/Exercise/AddExercise.swift index bc1e795..57c4dee 100644 --- a/WorkoutsPlus/Exercise/AddExercise.swift +++ b/WorkoutsPlus/Exercise/AddExercise.swift @@ -11,16 +11,16 @@ struct AddExercise: View { @Environment(\.modelContext) private var modelContext @Environment(\.dismiss) private var dismiss - @Bindable var exercise: Exercise + @Bindable var exerciseTemplate: ExerciseTemplate var body : some View { Form { - TextField("Workout Name", text: $exercise.name) + TextField("Workout Name", text: $exerciseTemplate.name) } .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { - modelContext.delete(exercise) + modelContext.delete(exerciseTemplate) dismiss() } } @@ -36,6 +36,6 @@ struct AddExercise: View { #Preview { Color.clear .sheet(isPresented: .constant(true)) { - AddExercise(exercise: Exercise("")) + AddExercise(exerciseTemplate: ExerciseTemplate("")) } } diff --git a/WorkoutsPlus/Exercise/Exercise.swift b/WorkoutsPlus/Exercise/Exercise.swift index b26152e..c04ae03 100644 --- a/WorkoutsPlus/Exercise/Exercise.swift +++ b/WorkoutsPlus/Exercise/Exercise.swift @@ -1,5 +1,5 @@ // -// Item.swift +// Exercise.swift // WorkoutsPlus // // Created by Felix Förtsch on 10.08.24. @@ -9,24 +9,34 @@ import Foundation import SwiftData @Model -final class Exercise { - @Attribute(.unique) var name: String - static var systemImage = "figure.run" +class Exercise { + var name: String - var timestamp: Date + var exerciseTemplate: ExerciseTemplate? { + didSet { + self.name = exerciseTemplate?.name ?? "self.name" + }} + var reps: Int = 0 - init(_ name: String = "", timestamp: Date = Date.now) { - self.name = name - self.timestamp = timestamp + init(_ reps: Int, _ exercise: String) { + self.name = exercise + self.reps = reps + self.exerciseTemplate = ExerciseTemplate(exercise) } - static let sampleData: [Exercise] = [ - Exercise("Warm-up"), Exercise("Pull-up Progression"), Exercise("Squat Progression"), - Exercise("Dip Progression"), Exercise("Hinge Progression"), Exercise("Row Progression"), - Exercise("Push-up Progression"), Exercise("Core Triplet"), Exercise("Push"), Exercise("Pull"), - Exercise("Legs"), Exercise("Core"), Exercise("Dips"), Exercise("Chin-ups"), - Exercise("Push-ups"), Exercise("Inverted Rows"), Exercise("Hanging Knee Raises"), - Exercise("Pistol Squats"), Exercise("Hanging Leg Curls"), Exercise("Sissy Squats"), - Exercise("400 m schnell"), Exercise("200 m langsam") - ] + // 8x Dips + init(from exerciseTemplate: ExerciseTemplate) { + self.name = exerciseTemplate.name + self.exerciseTemplate = exerciseTemplate + } + + static let sampleData: [Exercise] = { + var exercises = [Exercise]() + + for exerciseTemplate in ExerciseTemplate.sampleData { + exercises.append(Exercise(from: exerciseTemplate)) + } + + return exercises + }() } diff --git a/WorkoutsPlus/Exercise/ExerciseDetail.swift b/WorkoutsPlus/Exercise/ExerciseDetail.swift index 1f3f6e8..182b131 100644 --- a/WorkoutsPlus/Exercise/ExerciseDetail.swift +++ b/WorkoutsPlus/Exercise/ExerciseDetail.swift @@ -11,12 +11,12 @@ struct ExerciseDetail: View { @Environment(\.dismiss) private var dismiss @Environment(\.modelContext) private var modelContext - @Bindable var exercise: Exercise + @Bindable var exerciseTemplate: ExerciseTemplate var body: some View { Form { - TextField("Exercise Name", text: $exercise.name) + TextField("Exercise Name", text: $exerciseTemplate.name) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Save") { @@ -41,5 +41,5 @@ struct ExerciseDetail: View { } #Preview { - ExerciseDetail(exercise: Exercise("New Exercise")) + ExerciseDetail(exerciseTemplate: ExerciseTemplate.sampleData.first!) } diff --git a/WorkoutsPlus/Exercise/ExerciseLibrary.swift b/WorkoutsPlus/Exercise/ExerciseLibrary.swift index 1360765..bffd1bd 100644 --- a/WorkoutsPlus/Exercise/ExerciseLibrary.swift +++ b/WorkoutsPlus/Exercise/ExerciseLibrary.swift @@ -10,29 +10,29 @@ import SwiftData struct ExerciseLibrary: View { @Environment(\.modelContext) private var modelContext - @Query(sort: \Exercise.name) private var exercises: [Exercise] + @Query(sort: \ExerciseTemplate.name) private var exerciseTemplates: [ExerciseTemplate] - @State private var newExercise: Exercise? + @State private var newExercise: ExerciseTemplate? // TODO: Add search bar to the top var body: some View { NavigationSplitView { Group { - if !exercises.isEmpty { + if !exerciseTemplates.isEmpty { List { - ForEach(exercises) { exercise in + ForEach(exerciseTemplates) { exerciseTemplate in NavigationLink { - ExerciseDetail(exercise: exercise) + ExerciseDetail(exerciseTemplate: exerciseTemplate) } label: { - Text(exercise.name) + Text(exerciseTemplate.name) } } .onDelete(perform: deleteExercise) } } else { ContentUnavailableView { - Label("No Exercises", systemImage: Exercise.systemImage) + Label("No Exercises", systemImage: ExerciseTemplate.systemImage) } } } @@ -49,7 +49,7 @@ struct ExerciseLibrary: View { } .sheet(item: $newExercise) { exercise in NavigationStack { - AddExercise(exercise: exercise) + AddExercise(exerciseTemplate: exercise) } // TODO: It's possible to add a boolean here ("Terms accepted y/n"). Maybe add this for empty string .presentationDetents([.medium]) @@ -64,15 +64,15 @@ struct ExerciseLibrary: View { private func addExercise() { withAnimation { - let item = Exercise("") + let item = ExerciseTemplate("") modelContext.insert(item) newExercise = item } } - private func saveExercise(exercise: Exercise) { - if !exercise.name.isEmpty { - modelContext.insert(exercise) + private func saveExercise(exerciseTemplate: ExerciseTemplate) { + if !exerciseTemplate.name.isEmpty { + modelContext.insert(exerciseTemplate) try? modelContext.save() } } @@ -80,7 +80,7 @@ struct ExerciseLibrary: View { private func deleteExercise(offsets: IndexSet) { withAnimation { for index in offsets { - modelContext.delete(exercises[index]) + modelContext.delete(exerciseTemplates[index]) } try? modelContext.save() } diff --git a/WorkoutsPlus/Exercise/ExerciseTemplate.swift b/WorkoutsPlus/Exercise/ExerciseTemplate.swift new file mode 100644 index 0000000..88ea5e3 --- /dev/null +++ b/WorkoutsPlus/Exercise/ExerciseTemplate.swift @@ -0,0 +1,33 @@ +// +// ExerciseTemplate.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 25.08.24. +// + +import Foundation +import SwiftData + +@Model +final class ExerciseTemplate { + static var systemImage = "figure.run" + var name: String + // var exerciseDescription: ExerciseDescription? + + var timestamp: Date = Date.now + + init(_ name: String = "") { + self.name = name + } + + static let sampleData: [ExerciseTemplate] = [ + ExerciseTemplate("Dips"), + ExerciseTemplate("Chin-ups"), + ExerciseTemplate("Push-ups"), + ExerciseTemplate("Inverted Rows"), + ExerciseTemplate("Hanging Knee Raises"), + ExerciseTemplate("Pistol Squats"), + ExerciseTemplate("Hanging Leg Curls"), + ExerciseTemplate("Sissy Squats") + ] +} diff --git a/WorkoutsPlus/SampleData.swift b/WorkoutsPlus/SampleData.swift index ecd43d0..ee4e060 100644 --- a/WorkoutsPlus/SampleData.swift +++ b/WorkoutsPlus/SampleData.swift @@ -19,7 +19,7 @@ class SampleData { } private init() { - let schema = Schema([Exercise.self, Workout.self]) + let schema = Schema([Exercise.self, ExerciseTemplate.self, Workout.self]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true) do { @@ -31,19 +31,15 @@ class SampleData { } func insertSampleData() { - // Erstellt ein Dictionary, um Übungen nach Namen nachzuschlagen - var exercisesDict = [String: Exercise]() - - // Alle Übungen in der Datenbank speichern und im Dictionary ablegen - for exercise in Exercise.sampleData { - if exercisesDict[exercise.name] == nil { - context.insert(exercise) - exercisesDict[exercise.name] = exercise - } + for exerciseTemplate in ExerciseTemplate.sampleData { + context.insert(exerciseTemplate) } - // Workouts erstellen und dabei vorhandene Übungen referenzieren - for workout in Workout.sampleData(using: exercisesDict) { + for exercise in Exercise.sampleData { + context.insert(exercise) + } + + for workout in Workout.sampleData { context.insert(workout) } diff --git a/WorkoutsPlus/Workout/AddExerciseToWorkout.swift b/WorkoutsPlus/Workout/AddExerciseToWorkout.swift index 153aab1..dd62cca 100644 --- a/WorkoutsPlus/Workout/AddExerciseToWorkout.swift +++ b/WorkoutsPlus/Workout/AddExerciseToWorkout.swift @@ -10,32 +10,24 @@ import SwiftData struct AddExerciseToWorkout: View { @Environment(\.modelContext) private var modelContext - @Query(sort: \Exercise.name) private var exercises: [Exercise] + @Query(sort: \ExerciseTemplate.name) private var exerciseTemplates: [ExerciseTemplate] @Bindable var workout: Workout - var selectedExercises: [Exercise] = [Exercise("Selected")] - var body: some View { Group { - if !exercises.isEmpty { + if !exerciseTemplates.isEmpty { List { - Section(header: Text("Utility")) { - // TODO: Loop, Warm-up, Cool-down are not unique yet. They are special utility types - AddExerciseToWorkoutListItem(Exercise("Loop"), workout) - AddExerciseToWorkoutListItem(Exercise("Warm-up"),workout) - AddExerciseToWorkoutListItem(Exercise("Cool-down"),workout) - } Section(header: Text("Excersises")) { - ForEach(exercises) { exercise in - AddExerciseToWorkoutListItem(exercise, workout) + ForEach(exerciseTemplates) { exerciseTemplate in + AddExerciseToWorkoutListItem(exerciseTemplate, workout) } } } } else { ContentUnavailableView { // TODO: Add Button that allows adding an exercise - Label("No Exercises", systemImage: Exercise.systemImage) + Label("No Exercises", systemImage: ExerciseTemplate.systemImage) } } } @@ -43,27 +35,22 @@ struct AddExerciseToWorkout: View { } struct AddExerciseToWorkoutListItem: View { - var exercise: Exercise + @Environment(\.modelContext) private var modelContext + + var exerciseTemplate: ExerciseTemplate var workout: Workout - init(_ exercise: Exercise, _ workout: Workout) { - self.exercise = exercise + init(_ exerciseTemplate: ExerciseTemplate, _ workout: Workout) { + self.exerciseTemplate = exerciseTemplate self.workout = workout } var body: some View { Button(action: { - workout.addExercise(exercise) + workout.addExercise(from: exerciseTemplate) }) { HStack { - Text(String(workout.exercises.filter { $0 == exercise }.count)) - .font(.system(size: 14, weight: .bold)) - .foregroundColor(.white) - .frame(width: 20, height: 10) - .padding(8) - .background(Color.blue) - .clipShape(RoundedRectangle(cornerRadius: 8)) - Text(exercise.name) + Text(exerciseTemplate.name) .foregroundColor(.black) Spacer() Image(systemName: "plus.circle.fill") @@ -74,11 +61,11 @@ struct AddExerciseToWorkoutListItem: View { } #Preview("With Sample Data") { - AddExerciseToWorkout(workout: Workout(name: "New Workout")) + AddExerciseToWorkout(workout: Workout.sampleData.first!) .modelContainer(SampleData.shared.modelContainer) } #Preview("Empty Database") { - AddExerciseToWorkout(workout: Workout(name: "New Workout")) - .modelContainer(for: Exercise.self, inMemory: true) + AddExerciseToWorkout(workout: Workout.sampleData.first!) + .modelContainer(for: ExerciseTemplate.self, inMemory: true) } diff --git a/WorkoutsPlus/Workout/AddWorkout.swift b/WorkoutsPlus/Workout/AddWorkout.swift index 914b600..086ac3a 100644 --- a/WorkoutsPlus/Workout/AddWorkout.swift +++ b/WorkoutsPlus/Workout/AddWorkout.swift @@ -36,6 +36,6 @@ struct AddWorkout: View { #Preview { Color.clear .sheet(isPresented: .constant(true)) { - AddWorkout(workout: Workout(name: "")) + AddWorkout(workout: Workout.sampleData.first!) } } diff --git a/WorkoutsPlus/Workout/Workout.swift b/WorkoutsPlus/Workout/Workout.swift index 3530e77..490ad73 100644 --- a/WorkoutsPlus/Workout/Workout.swift +++ b/WorkoutsPlus/Workout/Workout.swift @@ -1,5 +1,5 @@ // -// Item.swift +// Workout.swift // WorkoutsPlus // // Created by Felix Förtsch on 10.08.24. @@ -18,51 +18,34 @@ final class Workout { static var systemImage = "figure.run.square.stack" // Other properties and methods - var timestamp: Date + var timestamp: Date = Date.now var exercises: [Exercise] = [] - init(name: String = "", timestamp: Date = Date.now) { + init(name: String) { self.name = name - self.timestamp = timestamp } - private init(name: String, exercises: [Exercise]) { + init(name: String, exercises: [Exercise]) { self.name = name - self.timestamp = Date.now self.exercises = exercises } - func addExercise(_ exercise: Exercise) { - exercises.append(exercise) + func addExercise(from exerciseTemplate: ExerciseTemplate) { + self.exercises.append(Exercise(from: exerciseTemplate)) } - static func sampleData(using exercisesDict: [String: Exercise]) -> [Workout] { - return [ - Workout(name: "RR", exercises: [ - exercisesDict["Warm-up"]!, exercisesDict["Pull-up Progression"]!, - exercisesDict["Squat Progression"]!, exercisesDict["Dip Progression"]!, - exercisesDict["Hinge Progression"]!, exercisesDict["Row Progression"]!, - exercisesDict["Push-up Progression"]!, exercisesDict["Core Triplet"]! - ]), - Workout(name: "Minimalist", exercises: [ - exercisesDict["Push"]!, exercisesDict["Pull"]!, - exercisesDict["Legs"]!, exercisesDict["Core"]! - ]), - Workout(name: "Rings", exercises: [ - exercisesDict["Dips"]!, exercisesDict["Chin-ups"]!, - exercisesDict["Push-ups"]!, exercisesDict["Inverted Rows"]!, - exercisesDict["Hanging Knee Raises"]!, exercisesDict["Pistol Squats"]!, - exercisesDict["Hanging Leg Curls"]!, exercisesDict["Sissy Squats"]! - ]), - Workout(name: "Intervalltraining", exercises: [ - exercisesDict["400 m schnell"]!, exercisesDict["200 m langsam"]!, - exercisesDict["400 m schnell"]!, exercisesDict["200 m langsam"]!, - exercisesDict["400 m schnell"]!, exercisesDict["200 m langsam"]!, - exercisesDict["400 m schnell"]!, exercisesDict["200 m langsam"]!, - exercisesDict["400 m schnell"]!, exercisesDict["200 m langsam"]!, - exercisesDict["400 m schnell"]!, exercisesDict["200 m langsam"]! - ]) - ] + func addExercise(_ exercise: Exercise) { + self.exercises.append(exercise) } + + func addExercise(_ exercises: [Exercise]) { + for exercise in exercises { + self.exercises.append(exercise) + } + } + + static let sampleData: [Workout] = [ + Workout(name: "RR", exercises: Exercise.sampleData) + ] } diff --git a/WorkoutsPlus/Workout/WorkoutDetail.swift b/WorkoutsPlus/Workout/WorkoutDetail.swift index f563a83..5a10461 100644 --- a/WorkoutsPlus/Workout/WorkoutDetail.swift +++ b/WorkoutsPlus/Workout/WorkoutDetail.swift @@ -24,20 +24,22 @@ struct WorkoutDetail: View { Section(header: Text("Exercises")) { List { ForEach(workout.exercises) { exercise in - Text(exercise.name) + ExerciseListItem(workout, exercise) } + .onDelete(perform: deleteExerciseFromWorkout) + .onMove(perform: move) } + .environment(\.editMode, .constant(.active)) // Always active drag mode Button(action: addExerciseToWorkout) { - Text("Add") + Text("Add Exercise") } } } + .navigationBarTitle("Edit \(workout.name)") .toolbar { - ToolbarItem(placement: .confirmationAction) { - Button("Done") { - dismiss() - } + ToolbarItem { + EditButton() } } .sheet(isPresented: $isPresenting) { @@ -64,11 +66,54 @@ struct WorkoutDetail: View { } } } + + private func deleteExerciseFromWorkout(offsets: IndexSet) { + withAnimation { + for index in offsets { + modelContext.delete(workout.exercises[index]) + } + try? modelContext.save() + } + } + + private func move(from source: IndexSet, to destination: Int) { + workout.exercises.move(fromOffsets: source, toOffset: destination) + } +} + +struct ExerciseListItem: View { + var workout: Workout + var exercise: Exercise + + init(_ workout: Workout, _ exercise: Exercise ) { + self.workout = workout + self.exercise = exercise + } + + var body: some View { + Button(action: { +// workout.addExercise(from: exercise) + }) { + HStack { + Text(String(workout.exercises.filter { $0 == exercise }.count)) + .font(.system(size: 14, weight: .bold)) + .foregroundColor(.white) + .frame(width: 20, height: 10) + .padding(8) + .background(Color.blue) + .clipShape(RoundedRectangle(cornerRadius: 8)) + Text(exercise.name) + .foregroundColor(.black) + Spacer() + Image(systemName: "info.circle") + } + } + } } #Preview { NavigationStack { - WorkoutDetail(workout: Workout(name: "RR")) - .modelContainer(SampleData.shared.modelContainer) + WorkoutDetail(workout: Workout.sampleData.first!) + .modelContainer(SampleData.shared.modelContainer) } } diff --git a/WorkoutsPlus/WorkoutsPlusApp.swift b/WorkoutsPlus/WorkoutsPlusApp.swift index ced7c0f..b30556a 100644 --- a/WorkoutsPlus/WorkoutsPlusApp.swift +++ b/WorkoutsPlus/WorkoutsPlusApp.swift @@ -13,6 +13,7 @@ struct WorkoutsPlusApp: App { var sharedModelContainer: ModelContainer = { let schema = Schema([ Exercise.self, + ExerciseTemplate.self, Workout.self ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)