diff --git a/WorkoutsPlus/Components/AddItemButton.swift b/WorkoutsPlus/Components/AddItemButton.swift new file mode 100644 index 0000000..4fead36 --- /dev/null +++ b/WorkoutsPlus/Components/AddItemButton.swift @@ -0,0 +1,30 @@ +// +// AddItemButton.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 29.08.24. +// + +import SwiftUI + +struct AddItemButton: View { + var label: String + var action: () -> Void + + var body: some View { + Button(action: { + action() + }) { + HStack { + Image(systemName: "plus.circle.fill") + .foregroundStyle(.green) + Text(label) + .foregroundStyle(.blue) + } + } + } +} + +#Preview { + AddItemButton(label: "Add Item", action: {}) +} diff --git a/WorkoutsPlus/ContentView.swift b/WorkoutsPlus/ContentView.swift index fdec528..c50f846 100644 --- a/WorkoutsPlus/ContentView.swift +++ b/WorkoutsPlus/ContentView.swift @@ -13,83 +13,7 @@ struct ContentView: View { var body: some View { TabView { - NavigationView() { - List() { - NavigationLink(destination: Text("WorkoutLogDetails")) { - HStack(alignment: .top) { - Image(systemName: "figure.run") - .padding(.trailing) - .padding(.top) - VStack(alignment: .leading) { - HStack { - Text("23.01.1988") - Text("14:37 Uhr") - Spacer() - } - Text("Marathon") - .fontWeight(.semibold) - HStack { - Text("34 km/42 km") - Text("•") - Text("5:12 min/km") - }.foregroundStyle(.gray) - HStack { - Text("1 h") - Text("•") - Text("1.337 kcal") - } - .foregroundStyle(.gray) - } - } - } - NavigationLink(destination: Text("WorkoutLogDetails")) { - HStack { - Image(systemName: "figure.run") - .padding(.trailing) - VStack(alignment: .leading) { - HStack { - Text("23.01.1988") - Text("14:37 Uhr") - Spacer() - - } - Text("Recommended Routine") - .fontWeight(.semibold) - HStack { - Text("6/12 sets") - Text("•") - Text("50 %") - }.foregroundStyle(.gray) - HStack { - Text("57 m") - Text("•") - Text("357 kcal") - } - .foregroundStyle(.gray) - } - } - } - } - .listStyle(.plain) - .navigationBarTitle("Workout Logs") - .toolbar { - ToolbarItem(placement: .topBarLeading) { - EditButton() - } - ToolbarItem(placement: .topBarLeading) { - - } - } - } - .tabItem { - Image(systemName: "pencil.and.list.clipboard") - Text("Log") - } - Text("Training Plans") - .tabItem { - Image(systemName: "calendar.badge.clock") - Text("Plans & Goals") - } + WorkoutLog() WorkoutLibrary() .tabItem { Image(systemName: "figure.run.square.stack") @@ -100,11 +24,16 @@ struct ContentView: View { Image(systemName: "figure.run") Text("Exercises") } -// Text("Settings") -// .tabItem { -// Image(systemName: "gear") -// Text("Settings") -// } + DebugExerciseList() + .tabItem { + Image(systemName: "hammer") + Text("Debug") + } + Settings() + .tabItem { + Image(systemName: "gear") + Text("Settings") + } } } } diff --git a/WorkoutsPlus/DebugExerciseList.swift b/WorkoutsPlus/DebugExerciseList.swift new file mode 100644 index 0000000..6db0f8d --- /dev/null +++ b/WorkoutsPlus/DebugExerciseList.swift @@ -0,0 +1,29 @@ +// +// DebugExerciseList.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 30.08.24. +// + +import SwiftUI +import SwiftData + +struct DebugExerciseList: View { + @Query(sort: \Exercise.name) private var exercises: [Exercise] + + var body: some View { + List { + ForEach(exercises) { exercise in + Text("\(exercise.name), pos: \(exercise.position)") + } + } + .tabItem { + Image(systemName: "figure.run.square.stack") + Text("Exercise Debug") + } + } +} + +#Preview { + DebugExerciseList() +} diff --git a/WorkoutsPlus/Exercise/Exercise.swift b/WorkoutsPlus/Exercise/Exercise.swift index c04ae03..08a123e 100644 --- a/WorkoutsPlus/Exercise/Exercise.swift +++ b/WorkoutsPlus/Exercise/Exercise.swift @@ -9,13 +9,18 @@ import Foundation import SwiftData @Model -class Exercise { +final class Exercise: Identifiable { + var id = UUID() var name: String + var workout: Workout? + var position: Int = 0 + var exerciseTemplate: ExerciseTemplate? { didSet { self.name = exerciseTemplate?.name ?? "self.name" - }} + } + } var reps: Int = 0 init(_ reps: Int, _ exercise: String) { @@ -29,7 +34,9 @@ class Exercise { self.name = exerciseTemplate.name self.exerciseTemplate = exerciseTemplate } - +} + +extension Exercise { static let sampleData: [Exercise] = { var exercises = [Exercise]() diff --git a/WorkoutsPlus/Exercise/ExerciseLibrary.swift b/WorkoutsPlus/Exercise/ExerciseLibrary.swift index 36c5f46..336804f 100644 --- a/WorkoutsPlus/Exercise/ExerciseLibrary.swift +++ b/WorkoutsPlus/Exercise/ExerciseLibrary.swift @@ -49,15 +49,7 @@ struct ExerciseLibrary: View { .textInputAutocapitalization(.words) .focused($isInputFieldFocused) } - Button(action: { - addExerciseTemplate() - }) { - HStack { - Image(systemName: "plus.circle.fill") - .foregroundStyle(.green) - Text("Add Exercise") - } - } + AddItemButton(label: "Exercise", action: addExerciseTemplate) } .searchable(text: $searchText) } diff --git a/WorkoutsPlus/Settings.swift b/WorkoutsPlus/Settings.swift new file mode 100644 index 0000000..45a9390 --- /dev/null +++ b/WorkoutsPlus/Settings.swift @@ -0,0 +1,18 @@ +// +// Settings.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 30.08.24. +// + +import SwiftUI + +struct Settings: View { + var body: some View { + Text("Settings") + } +} + +#Preview { + Settings() +} diff --git a/WorkoutsPlus/Workout/Workout.swift b/WorkoutsPlus/Workout/Workout.swift index b58d534..2130b0c 100644 --- a/WorkoutsPlus/Workout/Workout.swift +++ b/WorkoutsPlus/Workout/Workout.swift @@ -9,40 +9,54 @@ import Foundation import SwiftData @Model -final class Workout { - var isEdited = false - - var name: String { - didSet { isEdited = true } - } +final class Workout: Identifiable { + var id = UUID() + var name: String static var systemImage = "figure.run.square.stack" // Other properties and methods var timestamp: Date = Date.now - var exercises: [Exercise] = [] + @Relationship(deleteRule: .cascade) var exercises: [Exercise] = [] init(name: String) { self.name = name } - init(name: String, exercises: [Exercise]) { - self.name = name - self.exercises = exercises - } - func addExercise(from exerciseTemplate: ExerciseTemplate) { self.exercises.append(Exercise(from: exerciseTemplate)) + updateExercisePositions() } func addExercise(_ exercise: Exercise) { self.exercises.append(exercise) + updateExercisePositions() } func addExercise(_ exercises: [Exercise]) { for exercise in exercises { self.exercises.append(exercise) } + updateExercisePositions() + } + + func moveExercise(from source: IndexSet, to destination: Int) { + exercises.move(fromOffsets: source, toOffset: destination) + updateExercisePositions() + } + + private func updateExercisePositions() { + for (index, exercise) in exercises.enumerated() { + exercise.position = index + } + } + +} + +extension Workout { + private convenience init(name: String, exercises: [Exercise]) { + self.init(name: name) + self.exercises = exercises } static let sampleData: [Workout] = [ diff --git a/WorkoutsPlus/Workout/WorkoutDetail.swift b/WorkoutsPlus/Workout/WorkoutDetail.swift index 7643600..2d46fb0 100644 --- a/WorkoutsPlus/Workout/WorkoutDetail.swift +++ b/WorkoutsPlus/Workout/WorkoutDetail.swift @@ -23,16 +23,15 @@ struct WorkoutDetail: View { } Section(header: Text("Exercises")) { List { - ForEach(workout.exercises) { exercise in + ForEach(workout.exercises + .sorted(by: { $0.position < $1.position})) { exercise in ExerciseListItem(workout, exercise) } .onDelete(perform: deleteExerciseFromWorkout) .onMove(perform: move) } .environment(\.editMode, .constant(.active)) // Always active drag mode - Button(action: addExerciseToWorkout) { - Text("Add Exercise") - } + AddItemButton(label: "Exercise", action: addExerciseToWorkout) } } @@ -77,7 +76,7 @@ struct WorkoutDetail: View { } private func move(from source: IndexSet, to destination: Int) { - workout.exercises.move(fromOffsets: source, toOffset: destination) + workout.moveExercise(from: source, to: destination) } } @@ -95,19 +94,7 @@ struct ExerciseListItem: View { // workout.addExercise(from: exercise) }) { HStack { - -// TextField("Enter Reps", text: $exercise.reps) -// .keyboardType(.numberPad) // Set the keyboard to number pad -// .padding() -// .frame(width: 100, height: 50) -// .background(Color(.systemGray6)) -// .cornerRadius(8) -// .multilineTextAlignment(.center) // Center the text -// .overlay( -// RoundedRectangle(cornerRadius: 8) -// .stroke(Color.gray, lineWidth: 1) -// ) - Text(String(workout.exercises.filter { $0 == exercise }.count)) + Text(String(exercise.reps)) .font(.system(size: 14, weight: .bold)) .foregroundStyle(.white) .frame(width: 20, height: 10) @@ -117,7 +104,11 @@ struct ExerciseListItem: View { Text(exercise.name) .foregroundStyle(.black) Spacer() - Image(systemName: "info.circle") + Stepper( + value: $exercise.reps, + in: 0...100, + step: 1 + ) {} } } } diff --git a/WorkoutsPlus/Workout/WorkoutLibrary.swift b/WorkoutsPlus/Workout/WorkoutLibrary.swift index 8fded12..ed963fb 100644 --- a/WorkoutsPlus/Workout/WorkoutLibrary.swift +++ b/WorkoutsPlus/Workout/WorkoutLibrary.swift @@ -39,15 +39,17 @@ struct WorkoutLibrary: View { } .onDelete(perform: deleteWorkout) if isAddingWorkout { + // TODO: On tap-out of the text field, it should lose focus TextField("New Workout", text: $newWorkoutName, onCommit: { - newWorkout.name = newWorkoutName saveWorkout(workout: newWorkout) - isAddingWorkout = false }) .textInputAutocapitalization(.words) .focused($isInputFieldFocused) } Button(action: { + if isAddingWorkout { + saveWorkout(workout: newWorkout) + } addWorkout() }) { HStack { @@ -59,11 +61,11 @@ struct WorkoutLibrary: View { } .searchable(text: $searchText) } - } - .navigationBarTitle("Workouts") - .toolbar { - ToolbarItem() { - EditButton() + .navigationBarTitle("Workouts") + .toolbar { + ToolbarItem() { + EditButton() + } } } } @@ -79,9 +81,13 @@ struct WorkoutLibrary: View { // TODO: Brauchen wir das? private func saveWorkout(workout: Workout) { - if !workout.name.isEmpty { - modelContext.insert(workout) - try? modelContext.save() + withAnimation { + newWorkout.name = newWorkoutName + if !workout.name.isEmpty { + modelContext.insert(workout) + try? modelContext.save() + } + isAddingWorkout = false } } diff --git a/WorkoutsPlus/WorkoutLog.swift b/WorkoutsPlus/WorkoutLog.swift new file mode 100644 index 0000000..5bb9e41 --- /dev/null +++ b/WorkoutsPlus/WorkoutLog.swift @@ -0,0 +1,93 @@ +// +// WorkoutLog.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 30.08.24. +// + +import SwiftUI + +struct WorkoutLog: View { + var body: some View { + NavigationView() { + List() { + NavigationLink(destination: Text("WorkoutLogDetails")) { + HStack(alignment: .top) { + Image(systemName: "figure.run") + .padding(.trailing) + .padding(.top) + VStack(alignment: .leading) { + HStack { + Text("23.01.1988") + Text("14:37 Uhr") + Spacer() + } + Text("Marathon") + .fontWeight(.semibold) + HStack { + Text("34 km/42 km") + Text("•") + Text("5:12 min/km") + }.foregroundStyle(.gray) + HStack { + Text("1 h") + Text("•") + Text("1.337 kcal") + } + .foregroundStyle(.gray) + } + } + } + NavigationLink(destination: Text("WorkoutLogDetails")) { + HStack { + Image(systemName: "figure.run") + .padding(.trailing) + VStack(alignment: .leading) { + HStack { + Text("23.01.1988") + Text("14:37 Uhr") + Spacer() + } + Text("Recommended Routine") + .fontWeight(.semibold) + HStack { + Text("6/12 sets") + Text("•") + Text("50 %") + }.foregroundStyle(.gray) + HStack { + Text("57 m") + Text("•") + Text("357 kcal") + } + .foregroundStyle(.gray) + } + } + } + } + .listStyle(.plain) + .navigationBarTitle("Workout Logs") + .toolbar { + ToolbarItem(placement: .topBarLeading) { + EditButton() + } + ToolbarItem(placement: .topBarLeading) { + + } + } + } + .tabItem { + Image(systemName: "pencil.and.list.clipboard") + Text("Log") + } + Text("Training Plans") + .tabItem { + Image(systemName: "calendar.badge.clock") + Text("Plans & Goals") + } + } +} + +#Preview { + WorkoutLog() +}