From 419e2bc699f7bd6ce5b387c05a8aec845225cbd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Mon, 19 Aug 2024 15:33:32 +0200 Subject: [PATCH] add Workout, Exercise and their Library, Add, and Details views --- WorkoutsPlus/ContentView.swift | 10 +- WorkoutsPlus/Exercise.swift | 20 ---- WorkoutsPlus/Exercise/AddExercise.swift | 41 ++++++++ WorkoutsPlus/Exercise/Exercise.swift | 30 ++++++ WorkoutsPlus/Exercise/ExerciseDetail.swift | 45 +++++++++ WorkoutsPlus/Exercise/ExerciseLibrary.swift | 96 ++++++++++++++++++ WorkoutsPlus/ExerciseDetailsView.swift | 59 ----------- WorkoutsPlus/ExerciseLibraryView.swift | 100 ------------------- WorkoutsPlus/SampleData.swift | 48 +++++++++ WorkoutsPlus/Templates/Item.swift | 20 ---- WorkoutsPlus/Templates/ItemDetailsView.swift | 61 ----------- WorkoutsPlus/Templates/ItemLibrary.swift | 100 ------------------- WorkoutsPlus/Workout.swift | 26 ----- WorkoutsPlus/Workout/AddWorkout.swift | 41 ++++++++ WorkoutsPlus/Workout/Workout.swift | 63 ++++++++++++ WorkoutsPlus/Workout/WorkoutDetail.swift | 57 +++++++++++ WorkoutsPlus/Workout/WorkoutLibrary.swift | 96 ++++++++++++++++++ WorkoutsPlus/WorkoutDetailsView.swift | 60 ----------- WorkoutsPlus/WorkoutLibraryView.swift | 98 ------------------ 19 files changed, 522 insertions(+), 549 deletions(-) delete mode 100644 WorkoutsPlus/Exercise.swift create mode 100644 WorkoutsPlus/Exercise/AddExercise.swift create mode 100644 WorkoutsPlus/Exercise/Exercise.swift create mode 100644 WorkoutsPlus/Exercise/ExerciseDetail.swift create mode 100644 WorkoutsPlus/Exercise/ExerciseLibrary.swift delete mode 100644 WorkoutsPlus/ExerciseDetailsView.swift delete mode 100644 WorkoutsPlus/ExerciseLibraryView.swift create mode 100644 WorkoutsPlus/SampleData.swift delete mode 100644 WorkoutsPlus/Templates/Item.swift delete mode 100644 WorkoutsPlus/Templates/ItemDetailsView.swift delete mode 100644 WorkoutsPlus/Templates/ItemLibrary.swift delete mode 100644 WorkoutsPlus/Workout.swift create mode 100644 WorkoutsPlus/Workout/AddWorkout.swift create mode 100644 WorkoutsPlus/Workout/Workout.swift create mode 100644 WorkoutsPlus/Workout/WorkoutDetail.swift create mode 100644 WorkoutsPlus/Workout/WorkoutLibrary.swift delete mode 100644 WorkoutsPlus/WorkoutDetailsView.swift delete mode 100644 WorkoutsPlus/WorkoutLibraryView.swift diff --git a/WorkoutsPlus/ContentView.swift b/WorkoutsPlus/ContentView.swift index 38d0555..06f230a 100644 --- a/WorkoutsPlus/ContentView.swift +++ b/WorkoutsPlus/ContentView.swift @@ -13,14 +13,14 @@ struct ContentView: View { var body: some View { TabView { - WorkoutLibraryView() + WorkoutLibrary() .tabItem { - Image(systemName: "gauge.with.needle.fill") + Image(systemName: "figure.run.square.stack") Text("Workouts") } - ExerciseLibraryView() + ExerciseLibrary() .tabItem { - Image(systemName: "figure.run.square.stack.fill") + Image(systemName: "figure.run") Text("Exercises") } Text("Settings") @@ -34,5 +34,5 @@ struct ContentView: View { #Preview { ContentView() - .modelContainer(for: [Exercise.self, Workout.self], inMemory: true) + .modelContainer(SampleData.shared.modelContainer) } diff --git a/WorkoutsPlus/Exercise.swift b/WorkoutsPlus/Exercise.swift deleted file mode 100644 index 5e7eb4d..0000000 --- a/WorkoutsPlus/Exercise.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Item.swift -// WorkoutsPlus -// -// Created by Felix Förtsch on 10.08.24. -// - -import Foundation -import SwiftData - -@Model -final class Exercise { - var name: String - var timestamp: Date - - init(name: String = "", timestamp: Date = Date.now) { - self.name = name - self.timestamp = timestamp - } -} diff --git a/WorkoutsPlus/Exercise/AddExercise.swift b/WorkoutsPlus/Exercise/AddExercise.swift new file mode 100644 index 0000000..bc1e795 --- /dev/null +++ b/WorkoutsPlus/Exercise/AddExercise.swift @@ -0,0 +1,41 @@ +// +// AddExercise.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 18.08.24. +// + +import SwiftUI + +struct AddExercise: View { + @Environment(\.modelContext) private var modelContext + @Environment(\.dismiss) private var dismiss + + @Bindable var exercise: Exercise + + var body : some View { + Form { + TextField("Workout Name", text: $exercise.name) + } + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { + modelContext.delete(exercise) + dismiss() + } + } + ToolbarItem(placement: .confirmationAction) { + Button("Save") { + dismiss() + } + } + } + } +} + +#Preview { + Color.clear + .sheet(isPresented: .constant(true)) { + AddExercise(exercise: Exercise("")) + } +} diff --git a/WorkoutsPlus/Exercise/Exercise.swift b/WorkoutsPlus/Exercise/Exercise.swift new file mode 100644 index 0000000..5c3cdad --- /dev/null +++ b/WorkoutsPlus/Exercise/Exercise.swift @@ -0,0 +1,30 @@ +// +// Item.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 10.08.24. +// + +import Foundation +import SwiftData + +@Model +final class Exercise { + var name: String + static var systemImage = "figure.run" + + var timestamp: Date + + init(_ name: String = "", timestamp: Date = Date.now) { + self.name = name + self.timestamp = timestamp + } + + static let sampleData = [ + Exercise("Pull-up"), + Exercise("Push-up"), + Exercise("Dips"), + Exercise("Rows"), + Exercise("Split Squat") + ] +} diff --git a/WorkoutsPlus/Exercise/ExerciseDetail.swift b/WorkoutsPlus/Exercise/ExerciseDetail.swift new file mode 100644 index 0000000..1f3f6e8 --- /dev/null +++ b/WorkoutsPlus/Exercise/ExerciseDetail.swift @@ -0,0 +1,45 @@ +// +// ExerciseDetailsView.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 10.08.24. +// + +import SwiftUI + +struct ExerciseDetail: View { + @Environment(\.dismiss) private var dismiss + @Environment(\.modelContext) private var modelContext + + @Bindable var exercise: Exercise + + var body: some View { + + Form { + TextField("Exercise Name", text: $exercise.name) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Save") { + saveItem() + dismiss() + } + } + } + } + .navigationTitle("Exercise Details") + } + + private func saveItem() { + if modelContext.hasChanges { + do { + try modelContext.save() + } catch { + print("Failed to save exercise: \(error.localizedDescription)") + } + } + } +} + +#Preview { + ExerciseDetail(exercise: Exercise("New Exercise")) +} diff --git a/WorkoutsPlus/Exercise/ExerciseLibrary.swift b/WorkoutsPlus/Exercise/ExerciseLibrary.swift new file mode 100644 index 0000000..d8627d5 --- /dev/null +++ b/WorkoutsPlus/Exercise/ExerciseLibrary.swift @@ -0,0 +1,96 @@ +// +// ExerciseLibraryView.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 10.08.24. +// + +import SwiftUI +import SwiftData + +struct ExerciseLibrary: View { + @Environment(\.modelContext) private var modelContext + @Query(sort: \Exercise.name) private var exercises: [Exercise] + + @State private var newExercise: Exercise? + + var body: some View { + NavigationSplitView { + Group { + if !exercises.isEmpty { + List { + ForEach(exercises) { exercise in + NavigationLink { + ExerciseDetail(exercise: exercise) + } label: { + Text(exercise.name) + } + } + .onDelete(perform: deleteExercise) + } + } else { + ContentUnavailableView { + Label("No Exercises", systemImage: Exercise.systemImage) + } + } + } + .navigationBarTitle("Exercises") + .toolbar { + ToolbarItem(placement: .topBarLeading) { + EditButton() + } + ToolbarItem { + Button(action: addExercise) { + Label("Add Exercise", systemImage: "plus") + } + } + } + .sheet(item: $newExercise) { exercise in + NavigationStack { + AddExercise(exercise: exercise) + } + // TODO: It's possible to add a boolean here ("Terms accepted y/n"). Maybe add this for empty string + .interactiveDismissDisabled() + } + } detail: { + // TODO: What does this Detail do? + Text("Select a workout") + .navigationTitle("Movie") + } + } + + private func addExercise() { + withAnimation { + let item = Exercise("") + modelContext.insert(item) + newExercise = item + } + } + + private func saveExercise(exercise: Exercise) { + if !exercise.name.isEmpty { + modelContext.insert(exercise) + try? modelContext.save() + } + } + + private func deleteExercise(offsets: IndexSet) { + withAnimation { + for index in offsets { + modelContext.delete(exercises[index]) + } + try? modelContext.save() + } + } +} + +#Preview("With Sample Data") { + ExerciseLibrary() + .modelContainer(SampleData.shared.modelContainer) +} + +#Preview("Empty Database") { + ExerciseLibrary() + .modelContainer(for: Exercise.self, inMemory: true) +} + diff --git a/WorkoutsPlus/ExerciseDetailsView.swift b/WorkoutsPlus/ExerciseDetailsView.swift deleted file mode 100644 index e855c06..0000000 --- a/WorkoutsPlus/ExerciseDetailsView.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// ExerciseDetailsView.swift -// WorkoutsPlus -// -// Created by Felix Förtsch on 10.08.24. -// - -import SwiftUI - -struct ExerciseDetailsView: View { - @Environment(\.dismiss) private var dismiss - @Environment(\.modelContext) private var modelContext - - var exercise: Exercise? - var isPresentedAsSheet: Bool = false - - var body: some View { - - Form { - TextField("Exercise Name", text: Binding( - get: { exercise?.name ?? "" }, - set: { newName in - if exercise != nil { - exercise?.name = newName - } - } - )) - .toolbar { - if (isPresentedAsSheet) { - ToolbarItem(placement: .topBarLeading) { - Button("Cancel") { - dismiss() - } - } - } - ToolbarItem(placement: .topBarTrailing) { - Button("Save") { - saveItem() - dismiss() - } - } - } - } - } - - private func saveItem() { - if modelContext.hasChanges { - do { - try modelContext.save() - } catch { - print("Failed to save exercise: \(error.localizedDescription)") - } - } - } -} - -#Preview { - ExerciseDetailsView(exercise: Exercise(name: "New Exercises Preview")) -} diff --git a/WorkoutsPlus/ExerciseLibraryView.swift b/WorkoutsPlus/ExerciseLibraryView.swift deleted file mode 100644 index c5963d8..0000000 --- a/WorkoutsPlus/ExerciseLibraryView.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// ExerciseLibraryView.swift -// WorkoutsPlus -// -// Created by Felix Förtsch on 10.08.24. -// - -import SwiftUI -import SwiftData - -struct ExerciseLibraryView: View { - @Environment(\.modelContext) private var modelContext - @Query private var exercises: [Exercise] - - @State private var isPresentingNewItemSheet = false - - let initialDataSet = [ - "Pull-up", - "Push-up", - "Dips", - "Rows", - "Split Squat" - ] - - var body: some View { - NavigationView { - List { - ForEach(exercises.sorted(by: { $0.name < $1.name })) { item in - NavigationLink(destination: ExerciseDetailsView(exercise: item)) { - Text(item.name) - } - } - .onDelete(perform: deleteExercise) - } - .onAppear { - if exercises.isEmpty { - loadInitialData(exercises: initialDataSet) - } - } - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button { - createNewExercise() - } label: { - Label("Add Exercise", systemImage: "plus") - } - } - } - .sheet(isPresented: $isPresentingNewItemSheet) { - let newItem = Exercise(name: "") - NavigationView { - ExerciseDetailsView(exercise: newItem, isPresentedAsSheet: true) - .onDisappear { - if !newItem.name.isEmpty { - saveExercise(item: newItem) - } - } - } - } - } - } - - private func createNewExercise() { - isPresentingNewItemSheet = true - } - - private func saveExercise(item: Exercise) { - modelContext.insert(item) - try? modelContext.save() - } - - private func deleteExercise(offsets: IndexSet) { - withAnimation { - for index in offsets { - modelContext.delete(exercises[index]) - } - try? modelContext.save() - } - } - - private func loadInitialData(exercises: [String]) { - var items: [Exercise] = [] - - for exercise in exercises { - let item = Exercise(name: exercise) - items.append(item) - } - - for item in items { - modelContext.insert(item) - } - - try? modelContext.save() - } -} - -#Preview { - ExerciseLibraryView() - .modelContainer(for: Exercise.self, inMemory: true) -} diff --git a/WorkoutsPlus/SampleData.swift b/WorkoutsPlus/SampleData.swift new file mode 100644 index 0000000..4d4b106 --- /dev/null +++ b/WorkoutsPlus/SampleData.swift @@ -0,0 +1,48 @@ +// +// SampleData.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 17.08.24. +// + +import Foundation +import SwiftData + +@MainActor // With your annotation, you’re declaring that all code in this class must run on the main actor, including access to the mainContext property. Since all the SwiftUI code in an app runs on the main actor by default, you’ve satisfied the condition. +class SampleData { + static let shared = SampleData() + + let modelContainer: ModelContainer + + var context: ModelContext { + modelContainer.mainContext + } + + private init() { + let schema = Schema([Exercise.self, Workout.self]) + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true) + + do { + modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration]) + insertSampleData() + } catch { + fatalError("Could not create ModelContainer: \(error)") + } + } + + func insertSampleData() { + for exercise in Exercise.sampleData { + context.insert(exercise) + } + + for workout in Workout.sampleData { + context.insert(workout) + } + + do { + try context.save() + } catch { + print("Sample data context failed to save.") + } + } +} diff --git a/WorkoutsPlus/Templates/Item.swift b/WorkoutsPlus/Templates/Item.swift deleted file mode 100644 index 5b9f234..0000000 --- a/WorkoutsPlus/Templates/Item.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Item.swift -// WorkoutsPlus -// -// Created by Felix Förtsch on 10.08.24. -// - -import Foundation -import SwiftData - -@Model -final class Item { - var name: String - var timestamp: Date - - init(name: String = "", timestamp: Date = Date.now) { - self.name = name - self.timestamp = timestamp - } -} diff --git a/WorkoutsPlus/Templates/ItemDetailsView.swift b/WorkoutsPlus/Templates/ItemDetailsView.swift deleted file mode 100644 index 25a1d1e..0000000 --- a/WorkoutsPlus/Templates/ItemDetailsView.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// ExerciseDetailsView.swift -// WorkoutsPlus -// -// Created by Felix Förtsch on 10.08.24. -// - -import SwiftUI - -struct ItemDetailsView: View { - @Environment(\.dismiss) private var dismiss - @Environment(\.modelContext) private var modelContext - - var item: Item? - var isPresentedAsSheet: Bool = false - - var body: some View { - - Form { - TextField("Exercise Name", text: Binding( - get: { item?.name ?? "" }, - set: { newName in - if item != nil { - item?.name = newName - } - } - )) - .toolbar { - if (isPresentedAsSheet) { - ToolbarItem(placement: .topBarLeading) { - Button("Cancel") { - dismiss() - } - } - } - ToolbarItem(placement: .topBarTrailing) { - Button("Save") { - saveItem() - dismiss() - } - } - } - } - } - - private func saveItem() { - if modelContext.hasChanges { - do { - try modelContext.save() - } catch { - print("Failed to save item: \(error.localizedDescription)") - } - } - } -} - -#Preview { - let sampleItem = Item(name: "Sample Item") - ItemDetailsView(item: sampleItem) -} - diff --git a/WorkoutsPlus/Templates/ItemLibrary.swift b/WorkoutsPlus/Templates/ItemLibrary.swift deleted file mode 100644 index 327b509..0000000 --- a/WorkoutsPlus/Templates/ItemLibrary.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// ContentView.swift -// WorkoutsPlus -// -// Created by Felix Förtsch on 10.08.24. -// - -import SwiftUI -import SwiftData - -struct ItemLibrary: View { - @Environment(\.modelContext) private var modelContext - @Query private var items: [Item] - - @State private var isPresentingNewItemSheet = false - - let initialDataSet = [ - "Pull-up", - "Push-up", - "Dips", - "Rows", - "Split Squat" - ] - - var body: some View { - NavigationView { - List { - ForEach(items.sorted(by: { $0.name < $1.name })) { item in - NavigationLink(destination: ItemDetailsView(item: item)) { - Text(item.name) - } - } - .onDelete(perform: deleteItem) - } - .onAppear { - if items.isEmpty { - loadInitialData(exercises: initialDataSet) - } - } - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button { - createNewItem() - } label: { - Label("Add Exercise", systemImage: "plus") - } - } - } - .sheet(isPresented: $isPresentingNewItemSheet) { - let newItem = Item(name: "") - NavigationView { - ItemDetailsView(item: newItem, isPresentedAsSheet: true) - .onDisappear { - if !newItem.name.isEmpty { - saveItem(item: newItem) - } - } - } - } - } - } - - private func createNewItem() { - isPresentingNewItemSheet = true - } - - private func saveItem(item: Item) { - modelContext.insert(item) - try? modelContext.save() - } - - private func deleteItem(offsets: IndexSet) { - withAnimation { - for index in offsets { - modelContext.delete(items[index]) - } - try? modelContext.save() - } - } - - private func loadInitialData(exercises: [String]) { - var items: [Item] = [] - - for exercise in exercises { - let item = Item(name: exercise) - items.append(item) - } - - for item in items { - modelContext.insert(item) - } - - try? modelContext.save() - } -} - -#Preview { - ItemLibrary() - .modelContainer(for: Item.self, inMemory: true) -} diff --git a/WorkoutsPlus/Workout.swift b/WorkoutsPlus/Workout.swift deleted file mode 100644 index 0ba8c4a..0000000 --- a/WorkoutsPlus/Workout.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// Item.swift -// WorkoutsPlus -// -// Created by Felix Förtsch on 10.08.24. -// - -import Foundation -import SwiftData - -@Model -final class Workout { - var name: String - var timestamp: Date - - private var exercises: [Exercise] = [] - - init(name: String = "", timestamp: Date = Date.now) { - self.name = name - self.timestamp = timestamp - } - - func addExercise(_ exercise: Exercise) { - exercises.append(exercise) - } -} diff --git a/WorkoutsPlus/Workout/AddWorkout.swift b/WorkoutsPlus/Workout/AddWorkout.swift new file mode 100644 index 0000000..914b600 --- /dev/null +++ b/WorkoutsPlus/Workout/AddWorkout.swift @@ -0,0 +1,41 @@ +// +// AddWorkoutView.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 17.08.24. +// + +import SwiftUI + +struct AddWorkout: View { + @Environment(\.modelContext) private var modelContext + @Environment(\.dismiss) private var dismiss + + @Bindable var workout: Workout + + var body: some View { + Form { + TextField("Workout Name", text: $workout.name) + } + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { + modelContext.delete(workout) + dismiss() + } + } + ToolbarItem(placement: .confirmationAction) { + Button("Save") { + dismiss() + } + } + } + } +} + +#Preview { + Color.clear + .sheet(isPresented: .constant(true)) { + AddWorkout(workout: Workout(name: "")) + } +} diff --git a/WorkoutsPlus/Workout/Workout.swift b/WorkoutsPlus/Workout/Workout.swift new file mode 100644 index 0000000..5e2f763 --- /dev/null +++ b/WorkoutsPlus/Workout/Workout.swift @@ -0,0 +1,63 @@ +// +// Item.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 10.08.24. +// + +import Foundation +import SwiftData + +@Model +final class Workout { + var isEdited = false + + var name: String { + didSet { isEdited = true } + } + static var systemImage = "figure.run.square.stack" + + // Other properties and methods + var timestamp: Date + + var exercises: [Exercise] = [] + + init(name: String = "", timestamp: Date = Date.now) { + self.name = name + self.timestamp = timestamp + } + + private init(name: String, exercises: [Exercise]) { + self.name = name + self.timestamp = Date.now + self.exercises = exercises + } + + func addExercise(_ exercise: Exercise) { + exercises.append(exercise) + } + + static let sampleData = [ + Workout(name: "RR", exercises: [ + 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 Trilet")]), + Workout(name: "Minimalist", exercises:[ + Exercise("Push"), Exercise("Pull"), + Exercise("Legs"), Exercise("Core")]), + Workout(name: "Rings", exercises: [ + 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")]), + Workout(name: "Intervalltraining", exercises: [ + Exercise("400 m schnell"), Exercise("200 m langsam"), + Exercise("400 m schnell"), Exercise("200 m langsam"), + Exercise("400 m schnell"), Exercise("200 m langsam"), + Exercise("400 m schnell"), Exercise("200 m langsam"), + Exercise("400 m schnell"), Exercise("200 m langsam"), + Exercise("400 m schnell"), Exercise("200 m langsam")]) + ] +} diff --git a/WorkoutsPlus/Workout/WorkoutDetail.swift b/WorkoutsPlus/Workout/WorkoutDetail.swift new file mode 100644 index 0000000..2462298 --- /dev/null +++ b/WorkoutsPlus/Workout/WorkoutDetail.swift @@ -0,0 +1,57 @@ +// +// WorkoutDetailsView.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 10.08.24. +// + +import SwiftUI +import SwiftData + +struct WorkoutDetail: View { + @Environment(\.dismiss) private var dismiss + @Environment(\.modelContext) private var modelContext + + @Bindable var workout: Workout + + var body: some View { + + Form { + Section(header: Text("Workout Name")) { + TextField("Workout Name", text: $workout.name) + } + Section(header: Text("Exercises")) { + List { + ForEach(workout.exercises) { exercise in + Text(exercise.name) + } + } + } + } + .navigationBarTitle("Edit \(workout.name)") + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { + dismiss() + } + } + } + } + + private func saveWorkout() { + if modelContext.hasChanges { + do { + try modelContext.save() + } catch { + print("Failed to save workout: \(error.localizedDescription)") + } + } + } +} + +#Preview { + NavigationStack { + WorkoutDetail(workout: Workout.sampleData[0]) + .modelContainer(SampleData.shared.modelContainer) + } +} diff --git a/WorkoutsPlus/Workout/WorkoutLibrary.swift b/WorkoutsPlus/Workout/WorkoutLibrary.swift new file mode 100644 index 0000000..f6eb98b --- /dev/null +++ b/WorkoutsPlus/Workout/WorkoutLibrary.swift @@ -0,0 +1,96 @@ +// +// WorkoutLibraryView.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 10.08.24. +// + +import SwiftUI +import SwiftData + +struct WorkoutLibrary: View { + @Environment(\.modelContext) private var modelContext + @Query(sort: \Workout.name) private var workouts: [Workout] + + @State private var newWorkout: Workout? + + var body: some View { + NavigationSplitView { + Group { + if !workouts.isEmpty { + List { + ForEach(workouts) { workout in + NavigationLink { + WorkoutDetail(workout: workout) + } label: { + Text(workout.name) + } + } + .onDelete(perform: deleteWorkout) + } + } else { + ContentUnavailableView { + Label("No Workouts", systemImage: "figure.run.square.stack") + } + } + } + .navigationBarTitle("Workouts") + .toolbar { + ToolbarItem(placement: .topBarLeading) { + EditButton() + } + ToolbarItem { + Button(action: addWorkout) { + Label("Add Workout", systemImage: "plus") + } + } + } + .sheet(item: $newWorkout) { workout in + NavigationStack { + AddWorkout(workout: workout) + } + .interactiveDismissDisabled() + } + } detail: { + // TODO: What does this Detail do? + Text("Select a workout") + .navigationTitle("Movie") + } + } + + private func addWorkout() { + withAnimation { + let item = Workout(name: "") + modelContext.insert(item) + newWorkout = item + } + } + + // TODO: Brauchen wir das? + private func saveWorkout(workout: Workout) { + if !workout.name.isEmpty { + modelContext.insert(workout) + try? modelContext.save() + } + } + + private func deleteWorkout(offsets: IndexSet) { + withAnimation { + for index in offsets { + modelContext.delete(workouts[index]) + } + try? modelContext.save() + } + } +} + +#Preview("With Sample Data") { + WorkoutLibrary() + .modelContainer(SampleData.shared.modelContainer) +} + +#Preview("Empty Database") { + WorkoutLibrary() + .modelContainer(for: Workout.self, inMemory: true) +} + diff --git a/WorkoutsPlus/WorkoutDetailsView.swift b/WorkoutsPlus/WorkoutDetailsView.swift deleted file mode 100644 index 217b6c1..0000000 --- a/WorkoutsPlus/WorkoutDetailsView.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// ExerciseDetailsView.swift -// WorkoutsPlus -// -// Created by Felix Förtsch on 10.08.24. -// - -import SwiftUI - -struct WorkoutDetailsView: View { - @Environment(\.dismiss) private var dismiss - @Environment(\.modelContext) private var modelContext - - var workout: Workout? - var isPresentedAsSheet: Bool = false - - var body: some View { - - Form { - TextField("Workout Name", text: Binding( - get: { workout?.name ?? "" }, - set: { newName in - if workout != nil { - workout?.name = newName - } - } - )) - .toolbar { - if (isPresentedAsSheet) { - ToolbarItem(placement: .topBarLeading) { - Button("Cancel") { - dismiss() - } - } - } - ToolbarItem(placement: .topBarTrailing) { - Button("Save") { - saveWorkout() - dismiss() - } - } - } - } - } - - private func saveWorkout() { - if modelContext.hasChanges { - do { - try modelContext.save() - } catch { - print("Failed to save workout: \(error.localizedDescription)") - } - } - } -} - -#Preview { - WorkoutDetailsView(workout: Workout(name: "")) -} - diff --git a/WorkoutsPlus/WorkoutLibraryView.swift b/WorkoutsPlus/WorkoutLibraryView.swift deleted file mode 100644 index db5479e..0000000 --- a/WorkoutsPlus/WorkoutLibraryView.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// WorkoutLibraryView.swift -// WorkoutsPlus -// -// Created by Felix Förtsch on 10.08.24. -// - -import SwiftUI -import SwiftData - -struct WorkoutLibraryView: View { - @Environment(\.modelContext) private var modelContext - @Query private var workouts: [Workout] - - @State private var isPresentingNewItemSheet = false - - let initialDataSet = [ - "RR", - "Minimalist" - ] - - var body: some View { - NavigationView { - List { - ForEach(workouts.sorted(by: { $0.name < $1.name })) { workout in - NavigationLink(destination: WorkoutDetailsView(workout: workout)) { - Text(workout.name) - } - } - .onDelete(perform: deleteWorkout) - } - .onAppear { - if workouts.isEmpty { - // TODO: This behaviour puts something into [workouts] whenever(!) it is empty. It's not bound to the first ever start of the app or anything. Check if that's the behaviour we want. - loadInitialData(workouts: initialDataSet) - } - } - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button { - createNewWorkout() - } label: { - Label("Add Workout", systemImage: "plus") - } - } - } - .sheet(isPresented: $isPresentingNewItemSheet) { - let newWorkout = Workout(name: "") - NavigationView { - WorkoutDetailsView(workout: newWorkout, isPresentedAsSheet: true) - .onDisappear { - if !newWorkout.name.isEmpty { - saveWorkout(workout: newWorkout) - } - } - } - } - } - } - - private func createNewWorkout() { - isPresentingNewItemSheet = true - } - - private func saveWorkout(workout: Workout) { - modelContext.insert(workout) - try? modelContext.save() - } - - private func deleteWorkout(offsets: IndexSet) { - withAnimation { - for index in offsets { - modelContext.delete(workouts[index]) - } - try? modelContext.save() - } - } - - private func loadInitialData(workouts: [String]) { - var items: [Workout] = [] - - for exercise in workouts { - let item = Workout(name: exercise) - items.append(item) - } - - for item in items { - modelContext.insert(item) - } - - try? modelContext.save() - } -} - -#Preview { - WorkoutLibraryView() - .modelContainer(for: Workout.self, inMemory: true) -}