From 97d4038fb67ab9bb2d14b37f098ee81f0d384304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Fri, 16 Aug 2024 16:52:52 +0200 Subject: [PATCH] add minimal workout interface, not linked with exercises yet --- WorkoutsPlus/ContentView.swift | 11 ++- WorkoutsPlus/ExerciseDetailsView.swift | 13 ++-- WorkoutsPlus/ExerciseLibraryView.swift | 12 ++-- WorkoutsPlus/Workout.swift | 26 +++++++ WorkoutsPlus/WorkoutDetailsView.swift | 60 ++++++++++++++++ WorkoutsPlus/WorkoutLibraryView.swift | 98 ++++++++++++++++++++++++++ WorkoutsPlus/WorkoutsPlusApp.swift | 1 + 7 files changed, 205 insertions(+), 16 deletions(-) create mode 100644 WorkoutsPlus/Workout.swift create mode 100644 WorkoutsPlus/WorkoutDetailsView.swift create mode 100644 WorkoutsPlus/WorkoutLibraryView.swift diff --git a/WorkoutsPlus/ContentView.swift b/WorkoutsPlus/ContentView.swift index 6ee68f7..38d0555 100644 --- a/WorkoutsPlus/ContentView.swift +++ b/WorkoutsPlus/ContentView.swift @@ -13,10 +13,15 @@ struct ContentView: View { var body: some View { TabView { + WorkoutLibraryView() + .tabItem { + Image(systemName: "gauge.with.needle.fill") + Text("Workouts") + } ExerciseLibraryView() .tabItem { - Image(systemName: "figure.run.square.stack") - Text("Library") + Image(systemName: "figure.run.square.stack.fill") + Text("Exercises") } Text("Settings") .tabItem { @@ -29,5 +34,5 @@ struct ContentView: View { #Preview { ContentView() - .modelContainer(for: Exercise.self, inMemory: true) + .modelContainer(for: [Exercise.self, Workout.self], inMemory: true) } diff --git a/WorkoutsPlus/ExerciseDetailsView.swift b/WorkoutsPlus/ExerciseDetailsView.swift index e4a5dce..e855c06 100644 --- a/WorkoutsPlus/ExerciseDetailsView.swift +++ b/WorkoutsPlus/ExerciseDetailsView.swift @@ -11,17 +11,17 @@ struct ExerciseDetailsView: View { @Environment(\.dismiss) private var dismiss @Environment(\.modelContext) private var modelContext - var item: Exercise? + var exercise: Exercise? var isPresentedAsSheet: Bool = false var body: some View { Form { TextField("Exercise Name", text: Binding( - get: { item?.name ?? "" }, + get: { exercise?.name ?? "" }, set: { newName in - if item != nil { - item?.name = newName + if exercise != nil { + exercise?.name = newName } } )) @@ -48,13 +48,12 @@ struct ExerciseDetailsView: View { do { try modelContext.save() } catch { - print("Failed to save item: \(error.localizedDescription)") + print("Failed to save exercise: \(error.localizedDescription)") } } } } #Preview { - let sampleItem = Exercise(name: "Sample Item") - ExerciseDetailsView(item: sampleItem) + ExerciseDetailsView(exercise: Exercise(name: "New Exercises Preview")) } diff --git a/WorkoutsPlus/ExerciseLibraryView.swift b/WorkoutsPlus/ExerciseLibraryView.swift index 9be6a3d..c5963d8 100644 --- a/WorkoutsPlus/ExerciseLibraryView.swift +++ b/WorkoutsPlus/ExerciseLibraryView.swift @@ -10,7 +10,7 @@ import SwiftData struct ExerciseLibraryView: View { @Environment(\.modelContext) private var modelContext - @Query private var items: [Exercise] + @Query private var exercises: [Exercise] @State private var isPresentingNewItemSheet = false @@ -25,15 +25,15 @@ struct ExerciseLibraryView: View { var body: some View { NavigationView { List { - ForEach(items.sorted(by: { $0.name < $1.name })) { item in - NavigationLink(destination: ExerciseDetailsView(item: item)) { + ForEach(exercises.sorted(by: { $0.name < $1.name })) { item in + NavigationLink(destination: ExerciseDetailsView(exercise: item)) { Text(item.name) } } .onDelete(perform: deleteExercise) } .onAppear { - if items.isEmpty { + if exercises.isEmpty { loadInitialData(exercises: initialDataSet) } } @@ -49,7 +49,7 @@ struct ExerciseLibraryView: View { .sheet(isPresented: $isPresentingNewItemSheet) { let newItem = Exercise(name: "") NavigationView { - ExerciseDetailsView(item: newItem, isPresentedAsSheet: true) + ExerciseDetailsView(exercise: newItem, isPresentedAsSheet: true) .onDisappear { if !newItem.name.isEmpty { saveExercise(item: newItem) @@ -72,7 +72,7 @@ struct ExerciseLibraryView: View { private func deleteExercise(offsets: IndexSet) { withAnimation { for index in offsets { - modelContext.delete(items[index]) + modelContext.delete(exercises[index]) } try? modelContext.save() } diff --git a/WorkoutsPlus/Workout.swift b/WorkoutsPlus/Workout.swift new file mode 100644 index 0000000..0ba8c4a --- /dev/null +++ b/WorkoutsPlus/Workout.swift @@ -0,0 +1,26 @@ +// +// 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/WorkoutDetailsView.swift b/WorkoutsPlus/WorkoutDetailsView.swift new file mode 100644 index 0000000..217b6c1 --- /dev/null +++ b/WorkoutsPlus/WorkoutDetailsView.swift @@ -0,0 +1,60 @@ +// +// 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 new file mode 100644 index 0000000..db5479e --- /dev/null +++ b/WorkoutsPlus/WorkoutLibraryView.swift @@ -0,0 +1,98 @@ +// +// 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) +} diff --git a/WorkoutsPlus/WorkoutsPlusApp.swift b/WorkoutsPlus/WorkoutsPlusApp.swift index 160d464..ced7c0f 100644 --- a/WorkoutsPlus/WorkoutsPlusApp.swift +++ b/WorkoutsPlus/WorkoutsPlusApp.swift @@ -13,6 +13,7 @@ struct WorkoutsPlusApp: App { var sharedModelContainer: ModelContainer = { let schema = Schema([ Exercise.self, + Workout.self ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)