diff --git a/WorkoutsPlus/ActiveWorkout/ActiveWorkoutSessionListItem.swift b/WorkoutsPlus/ActiveWorkout/ActiveWorkoutSessionListItem.swift index 3491fcb..b65e8c7 100644 --- a/WorkoutsPlus/ActiveWorkout/ActiveWorkoutSessionListItem.swift +++ b/WorkoutsPlus/ActiveWorkout/ActiveWorkoutSessionListItem.swift @@ -15,16 +15,7 @@ struct ActiveWorkoutSessionListItem: View { switch workoutItem.workoutItemType { case .set: Text(workoutItem.name) - case .exerciseWithReps: - Text(workoutItem.name) - Spacer() - Button(action: { - // TODO: Implement a sheet view; don't use ExerciseDetail since its purpose is editing - }) { - Image(systemName: "info.circle") - .foregroundColor(.blue) - } - case .exerciseWithDuration: + case .exercise: Text(workoutItem.name) Spacer() Button(action: { diff --git a/WorkoutsPlus/Components/SetListItem.swift b/WorkoutsPlus/Components/SetListItem.swift index 98ae2a6..8badf2b 100644 --- a/WorkoutsPlus/Components/SetListItem.swift +++ b/WorkoutsPlus/Components/SetListItem.swift @@ -51,9 +51,9 @@ struct SetListItem: View { #Preview { @Previewable @State var set = WorkoutItem(set: [ - WorkoutItem(reps: 10, "Squat"), - WorkoutItem(reps: 10, "Squat"), - WorkoutItem(reps: 10, "Squat")]) + WorkoutItem(Exercise("Squat")), + WorkoutItem(Exercise("Squat")), + WorkoutItem(Exercise("Squat"))]) List { SetListItem(workout: Workout(name: "RR"), set: $set) } diff --git a/WorkoutsPlus/Components/TextEditorWithPlaceholder.swift b/WorkoutsPlus/Components/TextEditorWithPlaceholder.swift new file mode 100644 index 0000000..ff5326a --- /dev/null +++ b/WorkoutsPlus/Components/TextEditorWithPlaceholder.swift @@ -0,0 +1,33 @@ +// +// TextEditorWithPlaceholder.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 01.10.24. +// + +import SwiftUI + +struct TextEditorWithPlaceholder: View { + @Binding var text: String + var placeholder: String + + var body: some View { + ZStack(alignment: .topLeading) { + // TODO: If focused, hide placeholder. + if text.isEmpty { + Text(placeholder) + .foregroundColor(Color(UIColor.placeholderText)) + } + TextEditor(text: $text) + .frame(minHeight: 40, maxHeight: .infinity) + .cornerRadius(8) + } + } +} + +#Preview { + @Previewable @State var text = "" + Form { + TextEditorWithPlaceholder(text: $text, placeholder: "Description (optional)") + } +} diff --git a/WorkoutsPlus/Components/ValueKeyboard.swift b/WorkoutsPlus/Components/ValueKeyboard.swift index 419c275..71d9ed1 100644 --- a/WorkoutsPlus/Components/ValueKeyboard.swift +++ b/WorkoutsPlus/Components/ValueKeyboard.swift @@ -5,41 +5,12 @@ // Created by Felix Förtsch on 24.09.24. // +// TODO: Value keyboard binds to a metric (value + unit). You type in the $value and select the unit [m, s, kg] on the wheel. +// Example: Push-up (value: reps, unit: nil), Push-up (value: additional weight, unit: kg), Run (value: distance/time, unit: m/s) +// This means to use the valuekeyboard you would need to know which unit is allowed and then style the value depending on the unit + import SwiftUI -protocol ValueType { - associatedtype UnitType: CaseIterable & Hashable & CustomStringConvertible - var value: String { get set } - var unit: UnitType { get set } -} - -enum ExerciseUnit: String, CaseIterable, CustomStringConvertible { - case reps = "reps" - case meter = "m" - case second = "s" - case speed = "m/s" - case pace = "s/m" - - var description: String { rawValue } -} - -struct ExerciseValue: ValueType { - var value: String = "" - var unit: ExerciseUnit = .reps -} - -enum FoodUnit: String, CaseIterable, CustomStringConvertible { - case gram = "g" - case milliliter = "ml" - - var description: String { rawValue } -} - -struct FoodValue: ValueType { - var value: String = "" - var unit: FoodUnit = .gram -} - struct KeyboardButtonStyle: ButtonStyle { var width: CGFloat = 100 var height: CGFloat = 50 @@ -57,9 +28,16 @@ struct KeyboardButtonStyle: ButtonStyle { } } -struct ValueKeyboard: View { +protocol ValueKeyboardProtocol { + var value: String { get set } + var availableUnits: [String] { get set } +} + +struct ValueKeyboard: View { @Binding var isPresented: Bool - @Binding var value: Value + + @Binding var value: String + @Binding var unit: [String] var body: some View { VStack { @@ -69,12 +47,12 @@ struct ValueKeyboard: View { .frame(height: 80) HStack { - TextField("0", text: $value.value) + TextField("0", text: $value) .font(.system(size: 32, weight: .bold, design: .rounded)) .padding() - Picker("Unit", selection: $value.unit) { - ForEach(Array(Value.UnitType.allCases), id: \.self) { unit in + Picker("Unit", selection: $unit) { + ForEach(unit, id: \.self) { unit in Text(unit.description).tag(unit) } } @@ -145,31 +123,28 @@ struct ValueKeyboard: View { } private func handleButtonTap(_ button: String) { - switch button { - case "⌫": - if !value.value.isEmpty { - value.value.removeLast() - } - case "→": - isPresented.toggle() - default: - value.value.append(button) - } + // switch button { + // case "⌫": + // if !value.value.isEmpty { + // value.value.removeLast() + // } + // case "→": + // isPresented.toggle() + // default: + // value.value.append(button) + // } + // } } + } #Preview("ExerciseValue") { - @Previewable @State var exerciseValue: ExerciseValue = .init() @Previewable @State var isPresented: Bool = true - Text(exerciseValue.value) - Text(exerciseValue.unit.rawValue) - ValueKeyboard(isPresented: $isPresented, value: $exerciseValue) -} - -#Preview("FoodValue") { - @Previewable @State var foodValue: FoodValue = .init() - @Previewable @State var isPresented: Bool = true - Text(foodValue.value) - Text(foodValue.unit.rawValue) - ValueKeyboard(isPresented: $isPresented, value: $foodValue) + + let value = 0 + let unit = ExerciseMetric.allCases + + VStack { +// ValueKeyboard(isPresented: $isPresented, value: value, unit: unit) + } } diff --git a/WorkoutsPlus/Exercise/ExerciseEditor.swift b/WorkoutsPlus/Exercise/ExerciseEditor.swift index ef00b1d..edfe0b2 100644 --- a/WorkoutsPlus/Exercise/ExerciseEditor.swift +++ b/WorkoutsPlus/Exercise/ExerciseEditor.swift @@ -17,36 +17,34 @@ struct ExerciseEditor: View { @State private var name: String = "" @State private var description: String = "" - - @State private var metric: ExerciseMetric = .reps - @State private var reps: String = "" - @State private var duration: String = "" - @State private var distance: String = "" + @State private var metric: ExerciseMetric = .none @State private var isPartOfProgression: Bool = false - @State private var exerciseValue = ExerciseValue() - @State private var isValueKeyboardPresented = false - var body: some View { NavigationStack { VStack { ScrollViewReader { proxy in VStack { Form { - Section(footer: Text("The exercise description is optional.")) { + Section { TextField("Exercise Name", text: $name) // TODO: Add Autocomplete - TextField("Description", text: $description) + TextEditorWithPlaceholder(text: $description, placeholder: "Description (optional)") } - Section { - Button(action: { - isValueKeyboardPresented.toggle() - proxy.scrollTo("valueButton", anchor: .center) - }, label: { - Text("\(exerciseValue.value) \(exerciseValue.unit.rawValue)") - }) - .id("valueButton") // Assign a unique ID to the button + Section(footer: Text(""" + Examples: + • Pull-up → None + • Weighted Pull-up → Weight + • Sprint → Time or Distance + """)) { + Picker("Exercise Metric", selection: $metric) { + ForEach(ExerciseMetric.allCases) { metric in + Text(metric.rawValue.isEmpty ? "None" : metric.rawValue) + .tag(metric) + } + } + .pickerStyle(SegmentedPickerStyle()) } Section(footer: Text("Feature coming soon.")) { Toggle(isOn: $isPartOfProgression) { @@ -79,39 +77,29 @@ struct ExerciseEditor: View { if let exercise { self.name = exercise.name self.description = exercise.exerciseDescription - self.metric = exercise.metric - self.reps = exercise.suggestedReps - self.duration = exercise.suggestedDuration - self.distance = exercise.suggestedDistance - self.isPartOfProgression = exercise.isPartOfProgression } } } - if isValueKeyboardPresented { - ValueKeyboard(isPresented: $isValueKeyboardPresented, value: $exerciseValue) - } } } } private func save() { - if let exercise { - exercise.name = name - exercise.exerciseDescription = description - - exercise.metric = metric - exercise.suggestedReps = reps - exercise.suggestedDuration = duration - exercise.suggestedDistance = distance - - exercise.isPartOfProgression = isPartOfProgression + let exerciseToSave: Exercise + + if let exercise = exercise { + exerciseToSave = exercise } else { - let newExercise = Exercise(name, metric) - modelContext.insert(newExercise) - // try? modelContext.save() + exerciseToSave = Exercise(name) + modelContext.insert(exerciseToSave) } + + exerciseToSave.name = name + exerciseToSave.exerciseDescription = description + exerciseToSave.metric = metric + exerciseToSave.isPartOfProgression = isPartOfProgression } } diff --git a/WorkoutsPlus/Exercise/ExerciseLibrary.swift b/WorkoutsPlus/Exercise/ExerciseLibrary.swift index 9707362..07566a5 100644 --- a/WorkoutsPlus/Exercise/ExerciseLibrary.swift +++ b/WorkoutsPlus/Exercise/ExerciseLibrary.swift @@ -12,7 +12,7 @@ struct ExerciseLibrary: View { @Environment(\.modelContext) private var modelContext @Query(sort: \Exercise.name) private var exercises: [Exercise] - @State private var newExercise: Exercise = Exercise("", .reps) + @State private var newExercise: Exercise = Exercise("") @State private var newExerciseName: String = "" @State private var isAddingExercise: Bool = false @FocusState private var isInputFieldFocused: Bool @@ -37,9 +37,9 @@ struct ExerciseLibrary: View { HStack { Text(exercise.name) Spacer() - Text(exercise.metric.rawValue) - .font(.footnote) - .foregroundStyle(.gray) +// Text(exercise.metric.rawValue) +// .font(.footnote) +// .foregroundStyle(.gray) } } } diff --git a/WorkoutsPlus/Models/Equipment.swift b/WorkoutsPlus/Models/Equipment.swift new file mode 100644 index 0000000..3681c62 --- /dev/null +++ b/WorkoutsPlus/Models/Equipment.swift @@ -0,0 +1,24 @@ +// +// Equipment.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 01.10.24. +// + +import Foundation +import SwiftData + +@Model +final class Equipment: Nameable { + static var systemImage = "dumbbell.fill" + + var id = UUID() + var creationDate: Date = Date.now + + @Attribute(.unique) var name: String + var exercises: [Exercise] = [] + + init(name: String) { + self.name = name + } +} diff --git a/WorkoutsPlus/Models/Exercise.swift b/WorkoutsPlus/Models/Exercise.swift index 7c6a106..270a88e 100644 --- a/WorkoutsPlus/Models/Exercise.swift +++ b/WorkoutsPlus/Models/Exercise.swift @@ -5,6 +5,14 @@ // Created by Felix Förtsch on 25.08.24. // +// TODO: The model currently has an issue. I think An exercise always (!) has reps 1x 30s or 8x 1 0 kg Push-up. I stumbled upon the issue through the fact that bodyweight exercises can be loaded and for the user it makes sense that a Push-up is the same a Push-up with 10 kg load. +// var equipment: [Equipment] +// var isPartOfProgression: Bool = false +// var type: ExerciseType = [.cardio, .flexibility, .strength, .balance] +// var focus: ExerciseFocus? // Strength, Flexibility, Speed, etc +// TODO: add a way to save suggested metrics (distance, reps, etc) + + import Foundation import SwiftData @@ -15,74 +23,53 @@ final class Exercise: Nameable { var id = UUID() var creationDate: Date = Date.now - // The example for a exercise is the Push-up (but could also be: a sprint interval, a jump, a marathon, etc). @Attribute(.unique) var name: String - // Performing a push-up correctly has the following form cues var exerciseDescription: String = "" - - // A push-up is measured in reps. - var metric: ExerciseMetric - // In a typical push-up exercise you perform 8 reps. - var suggestedReps = "8" // TODO: Make a Rep - var suggestedDuration = "" // TODO: Make a Duration - var suggestedDistance = "" // TODO: Make a Distance - - // A push-up is part of the Push-up Progression. var isPartOfProgression: Bool = false - // The focus of the push-up is strength - // var focus: ExerciseFocus? // Strength, Flexibility, Speed, etc + var metric: ExerciseMetric - init(_ name: String, _ metric: ExerciseMetric) { + var equipment: [Equipment] = [] + + init(_ name: String, _ metric: ExerciseMetric = .none) { self.name = name self.metric = metric } } -enum ExerciseMetric: String, Codable { - case reps = "Reps" // Repeat the exrcise for a given amount of repetitions - case duration = "Duration" // Do the exercise for given amount of time - case distance = "Distance" // Do the exercise for a given amount of distance - // Other possible metrics: - // - Open exercise: this exercise does not bring a metric (eg running) -} - -extension Exercise { - static func getAdvice(for name: String, with metric: ExerciseMetric) -> String { - switch metric { - case .reps: - return "Repeat \(name == "" ? "New Exercise" : name) 8 times." - case .duration: - return "Do \(name == "" ? "New Exercise" : name) for 30 seconds." - case .distance: - return "Do \(name == "" ? "New Exercise" : name) for 500 meters." - } - } +enum ExerciseMetric: String, Codable, CaseIterable, Identifiable { + case none = "" + case distance = "Distance" + case time = "Time" + case weight = "Weight" + + var id: Self { self } } extension Exercise { static let sampleDataRecommendedRoutine: [Exercise] = [ - Exercise("Shoulder Band Warm-up", .duration), - Exercise("Squat Sky Reaches", .reps), - Exercise("GMB Wrist Prep", .duration), - Exercise("Dead Bugs", .reps), - Exercise("Pull-up Progression", .reps), - Exercise("Dip Progression", .reps), - Exercise("Squat Progression", .reps), - Exercise("Hinge Progression", .reps), - Exercise("Row Progression", .reps), - Exercise("Push-up Progression", .reps), - Exercise("Handstand Practice", .duration), - Exercise("Support Practice", .duration) + Exercise("Shoulder Band Warm-up"), + Exercise("Squat Sky Reaches"), + Exercise("GMB Wrist Prep", .time), + Exercise("Dead Bugs"), + Exercise("Pull-up Progression", .weight), + Exercise("Dip Progression", .weight), + Exercise("Squat Progression", .weight), + Exercise("Hinge Progression", .weight), + Exercise("Row Progression", .weight), + Exercise("Push-up Progression", .weight), + Exercise("Handstand Practice", .time), + Exercise("Support Practice", .time) ] static let sampleDataRings: [Exercise] = [ - Exercise("Dips", .reps), - Exercise("Chin-ups", .reps), - Exercise("Push-ups", .reps), - Exercise("Inverted Rows", .reps), - Exercise("Hanging Knee Raises", .reps), - Exercise("Pistol Squats", .reps), - Exercise("Hanging Leg Curls", .reps), - Exercise("Sissy Squats", .reps), + Exercise("Dips", .weight), + Exercise("Chin-ups", .weight), + Exercise("Push-ups", .weight), + Exercise("Inverted Rows", .weight), + Exercise("Hanging Knee Raises", .weight), + Exercise("Pistol Squats", .weight), + Exercise("Hanging Leg Curls", .weight), + Exercise("Sissy Squats") ] + } diff --git a/WorkoutsPlus/Models/Food.swift b/WorkoutsPlus/Models/Food.swift new file mode 100644 index 0000000..ad35475 --- /dev/null +++ b/WorkoutsPlus/Models/Food.swift @@ -0,0 +1,20 @@ +// +// Food.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 28.09.24. +// + +import Foundation + +//enum FoodUnit: String, CaseIterable, CustomStringConvertible { +// case gram = "g" +// case milliliter = "ml" +// +// var description: String { rawValue } +//} + +//struct FoodValue: ValueType { +// var value: String = "" +// var unit: FoodUnit = .gram +//} diff --git a/WorkoutsPlus/Models/Unit.swift b/WorkoutsPlus/Models/Unit.swift new file mode 100644 index 0000000..46f4c16 --- /dev/null +++ b/WorkoutsPlus/Models/Unit.swift @@ -0,0 +1,27 @@ +// +// Random.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 01.10.24. +// + +//import Foundation +// +//enum Unit: String, CaseIterable, Codable { +// case none = "" +// +// case kilogram = "kg" +// case gram = "g" +// +// case liter = "l" +// case milliliter = "ml" +// +// case kilometer = "km" +// case meter = "m" +// +// case hour = "h" +// case minute = "min" +// case second = "s" +// +// var description: String { rawValue } +//} diff --git a/WorkoutsPlus/Models/WorkoutItem.swift b/WorkoutsPlus/Models/WorkoutItem.swift index c52cc1c..92d52f5 100644 --- a/WorkoutsPlus/Models/WorkoutItem.swift +++ b/WorkoutsPlus/Models/WorkoutItem.swift @@ -5,6 +5,30 @@ // Created by Felix Förtsch on 10.08.24. // +// TODO: Think about what's happening when an exercise (template) is deleted or changed +// If it is delete -> we just keep the WorkoutItem with the name :) +// The only relevant delete is delete Workout -> Delete Workoutitems +// { didSet { self.name = exercise?.name ?? "self.name" } } + +// TODO: Wondering if a SortDescriptor in the Model is useful? +// https://old.reddit.com/r/SwiftUI/comments/1fnvkud/extracting_the_creation_of_swiftdata_query_into/ +// static var sorted: SortDescriptor { +// SortDescriptor(\.position, order: .forward) +// } + +// struct ExerciseValue: ValueType { +// var value: String = "" +// var unit: ExerciseUnit +// } + +// TODO: Deload items -> Think about how to model deload/sick/rest/holiday week +//enum PerformanceMetric: String, CaseIterable, CustomStringConvertible { +// case speed = "m/s" +// case pace = "s/m" +// +// var description: String { rawValue } +//} + import Foundation import SwiftData @@ -12,61 +36,36 @@ import SwiftData final class WorkoutItem: Nameable, Positionable { var id = UUID() var name: String - var workout: Workout? - var workoutItemType: WorkoutItemType + var workoutItemType: WorkoutItemType // Differentiates between exercise/rest/set + var position: Int = 0 + var set: [WorkoutItem] = [] + + var exercise: Exercise? // Do Push-up | Run Marathon + var reps: Int // 8 times | 1 time + var value: Double? // With 10 | 42,187 + var metric: ExerciseMetric? // kg (weight) | km (distance) + enum WorkoutItemType: Codable { - // TODO: Add workout as WorkoutItemType (needs recursive dealing) - // case workout + case exercise case rest case set - case exerciseWithReps - case exerciseWithDuration } - var position: Int = 0 - // TODO: Wondering if a SortDescriptor in the Model is useful? - // https://old.reddit.com/r/SwiftUI/comments/1fnvkud/extracting_the_creation_of_swiftdata_query_into/ - // static var sorted: SortDescriptor { - // SortDescriptor(\.position, order: .forward) - // } - - var reps: Int = 0 - var duration: Int = 0 - - // EXERCISE - var exercise: Exercise? - // TODO: Think about what's happening when an exercise (template) is deleted or changed - // If it is delete -> we just keep the WorkoutItem with the name :) - // The only relevant delete is delete Workout -> Delete Workoutitems - // { didSet { self.name = exercise?.name ?? "self.name" } } - - // TODO: Deload items -> Think about how to model deload/sick/rest/holiday week - - // Exercise - init(reps: Int, _ exercise: String) { - self.workoutItemType = .exerciseWithReps - self.name = exercise - self.reps = reps - self.exercise = Exercise(exercise, .reps) - } - - init(duration: Int, _ exercise: String) { - self.workoutItemType = .exerciseWithDuration - self.name = exercise - self.duration = duration - self.exercise = Exercise(exercise, .duration) - } - - init(exercise: Exercise) { - self.workoutItemType = .exerciseWithReps - self.name = exercise.name + init(_ exercise: Exercise) { self.exercise = exercise + self.workoutItemType = .exercise + + // Push-up + self.name = exercise.name + // 8x + self.reps = 1 + // 0 + self.value = 0 + // kg + self.metric = exercise.metric } - // SET - var set: [WorkoutItem] = [] - init(set: [WorkoutItem] = []) { self.workoutItemType = .set self.name = "Set" @@ -80,38 +79,30 @@ final class WorkoutItem: Nameable, Positionable { } } - // PAUSE - init (rest: Int) { + init(rest: Double) { self.workoutItemType = .rest self.name = "Rest" - self.duration = rest + self.reps = 1 + self.value = rest + self.metric = .time } } extension WorkoutItem { static let sampleDataRecommendedRoutine: [WorkoutItem] = { var exercises = [WorkoutItem]() - for exercise in Exercise.sampleDataRecommendedRoutine { - exercises.append(WorkoutItem(exercise: exercise)) + exercises.append(WorkoutItem(exercise)) } - - // var set = WorkoutItem(workoutItems: [ - // WorkoutItem(from: Exercise("Set item 1")), - // WorkoutItem(from: Exercise("Set item 2")) - // ]) - // exercises.append(set) - return exercises }() static let sampleDataRings: [WorkoutItem] = { var exercises = [WorkoutItem]() - for exercise in Exercise.sampleDataRings { - exercises.append(WorkoutItem(exercise: exercise)) + exercises.append(WorkoutItem(exercise)) } - return exercises }() } + diff --git a/WorkoutsPlus/Workout/WorkoutItemLibrarySheet.swift b/WorkoutsPlus/Workout/WorkoutItemLibrarySheet.swift index 515ebb0..e64df73 100644 --- a/WorkoutsPlus/Workout/WorkoutItemLibrarySheet.swift +++ b/WorkoutsPlus/Workout/WorkoutItemLibrarySheet.swift @@ -22,8 +22,8 @@ struct WorkoutItemLibrarySheet: View { Section(header: Text("Utilities")) { AddItemButton(label: "Set") { addWorkoutItemtoWorkout(WorkoutItem(set: [ - WorkoutItem(exercise: Exercise("Set item 1", .reps)), - WorkoutItem(exercise: Exercise("Set item 2", .reps)) + WorkoutItem(Exercise("Set item 1")), + WorkoutItem(Exercise("Set item 2")) ])) } AddItemButton(label: "Rest") { @@ -34,7 +34,7 @@ struct WorkoutItemLibrarySheet: View { if !exercises.isEmpty { ForEach(exercises) { exercise in AddItemButton(label: exercise.name) { - let workoutItem = WorkoutItem(exercise: exercise) + let workoutItem = WorkoutItem(exercise) addWorkoutItemtoWorkout(workoutItem) } } diff --git a/WorkoutsPlus/Workout/WorkoutListItem.swift b/WorkoutsPlus/Workout/WorkoutListItem.swift index 58f0889..ff38cc2 100644 --- a/WorkoutsPlus/Workout/WorkoutListItem.swift +++ b/WorkoutsPlus/Workout/WorkoutListItem.swift @@ -23,9 +23,7 @@ struct WorkoutListItem: View { switch workoutItem.workoutItemType { case .set: SetListItem(workout: workout, set: $workoutItem) - case .exerciseWithReps: - Text(workoutItem.name) - case .exerciseWithDuration: + case .exercise: Text(workoutItem.name) case .rest: Text(workoutItem.name) @@ -38,12 +36,12 @@ struct WorkoutListItem: View { #Preview { List { WorkoutListItem(Workout(name: "RR"), WorkoutItem(set: [ - WorkoutItem(reps: 10, "Squat"), - WorkoutItem(reps: 10, "Squat"), - WorkoutItem(reps: 10, "Squat")])) - WorkoutListItem(Workout(name: "RR"), WorkoutItem(exercise: Exercise("Push-ups", .reps))) - WorkoutListItem(Workout(name: "RR"), WorkoutItem(exercise: Exercise("Push-ups", .reps))) - WorkoutListItem(Workout(name: "RR"), WorkoutItem(exercise: Exercise("Sprint Interval", .duration))) - WorkoutListItem(Workout(name: "RR"), WorkoutItem(exercise: Exercise("Run", .distance))) + WorkoutItem(Exercise("Squat")), + WorkoutItem(Exercise("Squat")), + WorkoutItem(Exercise("Squat"))])) + WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Push-ups"))) + WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Push-ups"))) + WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Sprint", .distance))) + WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Run", .time))) } } diff --git a/WorkoutsPlus/WorkoutsPlusApp.swift b/WorkoutsPlus/WorkoutsPlusApp.swift index f045b7d..898774d 100644 --- a/WorkoutsPlus/WorkoutsPlusApp.swift +++ b/WorkoutsPlus/WorkoutsPlusApp.swift @@ -32,6 +32,7 @@ struct WorkoutsPlusApp: App { extension WorkoutsPlusApp { static let swiftDataSchema = Schema([ Exercise.self, + Equipment.self, Workout.self, WorkoutItem.self, WorkoutSession.self]) diff --git a/WorkoutsPlus/workouts.json b/WorkoutsPlus/workouts.json new file mode 100644 index 0000000..3364ff6 --- /dev/null +++ b/WorkoutsPlus/workouts.json @@ -0,0 +1,352 @@ +{ + "workouts": { + "bodyweight": { + "warmup": [ + { + "name": "Shoulder Band Warm-up", + "reps": 1, + "load": 60, + "load_unit": "s" + }, + { + "name": "Squat Sky Reaches", + "reps": 10, + "load": 0, + "load_unit": "g" + }, + { + "name": "GMB Wrist Prep", + "reps": 1, + "load": 60, + "load_unit": "s" + }, + { + "name": "Dead Bugs", + "reps": 10, + "load": 0, + "load_unit": "g" + } + ], + "skill_work": [ + { + "name": "Handstand Practice", + "reps": 1, + "load": 300, + "load_unit": "s" + }, + { + "name": "Support Practice", + "reps": 1, + "load": 300, + "load_unit": "s" + } + ], + "main_workout": [ + { + "name": "Pull-up Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Pull-up Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Pull-up Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Dip Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Dip Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Dip Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Squat Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Squat Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Squat Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Hinge Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Hinge Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Hinge Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Row Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Row Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Row Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Push-up Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Push-up Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + }, + { + "name": "Push-up Progression", + "reps": 8, + "load": 0, + "load_unit": "g" + } + ] + }, + "running": { + "warmup": [ + { + "name": "Light Jog", + "reps": 1, + "load": 300, + "load_unit": "s" + }, + { + "name": "Dynamic Stretching", + "reps": 1, + "load": 180, + "load_unit": "s" + }, + { + "name": "High Knees", + "reps": 20, + "load": 0, + "load_unit": "m" + }, + { + "name": "Butt Kicks", + "reps": 20, + "load": 0, + "load_unit": "m" + } + ], + "skill_work": [ + { + "name": "Drills - A-Skip", + "reps": 1, + "load": 60, + "load_unit": "s" + }, + { + "name": "Drills - B-Skip", + "reps": 1, + "load": 60, + "load_unit": "s" + } + ], + "main_workout": [ + { + "name": "Sprint", + "reps": 1, + "load": 400, + "load_unit": "m" + }, + { + "name": "Sprint", + "reps": 1, + "load": 400, + "load_unit": "m" + }, + { + "name": "Sprint", + "reps": 1, + "load": 400, + "load_unit": "m" + }, + { + "name": "Tempo Run", + "reps": 1, + "load": 800, + "load_unit": "m" + }, + { + "name": "Tempo Run", + "reps": 1, + "load": 800, + "load_unit": "m" + }, + { + "name": "Tempo Run", + "reps": 1, + "load": 800, + "load_unit": "m" + }, + { + "name": "Easy Run", + "reps": 1, + "load": 1000, + "load_unit": "m" + }, + { + "name": "Easy Run", + "reps": 1, + "load": 1000, + "load_unit": "m" + }, + { + "name": "Easy Run", + "reps": 1, + "load": 1000, + "load_unit": "m" + } + ] + }, + "swimming": { + "warmup": [ + { + "name": "Easy Freestyle", + "reps": 1, + "load": 200, + "load_unit": "m" + }, + { + "name": "Kick Drill", + "reps": 1, + "load": 100, + "load_unit": "m" + }, + { + "name": "Pull Drill", + "reps": 1, + "load": 100, + "load_unit": "m" + }, + { + "name": "Catch-up Drill", + "reps": 1, + "load": 100, + "load_unit": "m" + } + ], + "skill_work": [ + { + "name": "Streamline Glides", + "reps": 10, + "load": 0, + "load_unit": "m" + }, + { + "name": "Flip Turn Practice", + "reps": 1, + "load": 300, + "load_unit": "s" + } + ], + "main_workout": [ + { + "name": "Freestyle Sprint", + "reps": 1, + "load": 50, + "load_unit": "m" + }, + { + "name": "Freestyle Sprint", + "reps": 1, + "load": 50, + "load_unit": "m" + }, + { + "name": "Freestyle Sprint", + "reps": 1, + "load": 50, + "load_unit": "m" + }, + { + "name": "Backstroke", + "reps": 1, + "load": 100, + "load_unit": "m" + }, + { + "name": "Backstroke", + "reps": 1, + "load": 100, + "load_unit": "m" + }, + { + "name": "Backstroke", + "reps": 1, + "load": 100, + "load_unit": "m" + }, + { + "name": "Individual Medley", + "reps": 1, + "load": 200, + "load_unit": "m" + }, + { + "name": "Individual Medley", + "reps": 1, + "load": 200, + "load_unit": "m" + }, + { + "name": "Individual Medley", + "reps": 1, + "load": 200, + "load_unit": "m" + } + ] + } + } +}