simplify Exercise to only hold on to the unit of the metric, remove ValueKeyboard as input

This commit is contained in:
Felix Förtsch
2024-10-02 12:41:04 +02:00
parent 41a82f081a
commit b7f5caf9dd
15 changed files with 630 additions and 243 deletions

View File

@@ -15,16 +15,7 @@ struct ActiveWorkoutSessionListItem: View {
switch workoutItem.workoutItemType { switch workoutItem.workoutItemType {
case .set: case .set:
Text(workoutItem.name) Text(workoutItem.name)
case .exerciseWithReps: case .exercise:
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:
Text(workoutItem.name) Text(workoutItem.name)
Spacer() Spacer()
Button(action: { Button(action: {

View File

@@ -51,9 +51,9 @@ struct SetListItem: View {
#Preview { #Preview {
@Previewable @State var set = WorkoutItem(set: [ @Previewable @State var set = WorkoutItem(set: [
WorkoutItem(reps: 10, "Squat"), WorkoutItem(Exercise("Squat")),
WorkoutItem(reps: 10, "Squat"), WorkoutItem(Exercise("Squat")),
WorkoutItem(reps: 10, "Squat")]) WorkoutItem(Exercise("Squat"))])
List { List {
SetListItem(workout: Workout(name: "RR"), set: $set) SetListItem(workout: Workout(name: "RR"), set: $set)
} }

View File

@@ -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)")
}
}

View File

@@ -5,41 +5,12 @@
// Created by Felix Förtsch on 24.09.24. // 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 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 { struct KeyboardButtonStyle: ButtonStyle {
var width: CGFloat = 100 var width: CGFloat = 100
var height: CGFloat = 50 var height: CGFloat = 50
@@ -57,9 +28,16 @@ struct KeyboardButtonStyle: ButtonStyle {
} }
} }
struct ValueKeyboard<Value: ValueType>: View { protocol ValueKeyboardProtocol {
var value: String { get set }
var availableUnits: [String] { get set }
}
struct ValueKeyboard: View {
@Binding var isPresented: Bool @Binding var isPresented: Bool
@Binding var value: Value
@Binding var value: String
@Binding var unit: [String]
var body: some View { var body: some View {
VStack { VStack {
@@ -69,12 +47,12 @@ struct ValueKeyboard<Value: ValueType>: View {
.frame(height: 80) .frame(height: 80)
HStack { HStack {
TextField("0", text: $value.value) TextField("0", text: $value)
.font(.system(size: 32, weight: .bold, design: .rounded)) .font(.system(size: 32, weight: .bold, design: .rounded))
.padding() .padding()
Picker("Unit", selection: $value.unit) { Picker("Unit", selection: $unit) {
ForEach(Array(Value.UnitType.allCases), id: \.self) { unit in ForEach(unit, id: \.self) { unit in
Text(unit.description).tag(unit) Text(unit.description).tag(unit)
} }
} }
@@ -145,31 +123,28 @@ struct ValueKeyboard<Value: ValueType>: View {
} }
private func handleButtonTap(_ button: String) { private func handleButtonTap(_ button: String) {
switch button { // switch button {
case "": // case "":
if !value.value.isEmpty { // if !value.value.isEmpty {
value.value.removeLast() // value.value.removeLast()
} // }
case "": // case "":
isPresented.toggle() // isPresented.toggle()
default: // default:
value.value.append(button) // value.value.append(button)
} // }
// }
} }
} }
#Preview("ExerciseValue") { #Preview("ExerciseValue") {
@Previewable @State var exerciseValue: ExerciseValue = .init()
@Previewable @State var isPresented: Bool = true @Previewable @State var isPresented: Bool = true
Text(exerciseValue.value)
Text(exerciseValue.unit.rawValue) let value = 0
ValueKeyboard(isPresented: $isPresented, value: $exerciseValue) let unit = ExerciseMetric.allCases
}
VStack {
#Preview("FoodValue") { // ValueKeyboard(isPresented: $isPresented, value: value, unit: unit)
@Previewable @State var foodValue: FoodValue = .init() }
@Previewable @State var isPresented: Bool = true
Text(foodValue.value)
Text(foodValue.unit.rawValue)
ValueKeyboard(isPresented: $isPresented, value: $foodValue)
} }

View File

@@ -17,36 +17,34 @@ struct ExerciseEditor: View {
@State private var name: String = "" @State private var name: String = ""
@State private var description: String = "" @State private var description: String = ""
@State private var metric: ExerciseMetric = .none
@State private var metric: ExerciseMetric = .reps
@State private var reps: String = ""
@State private var duration: String = ""
@State private var distance: String = ""
@State private var isPartOfProgression: Bool = false @State private var isPartOfProgression: Bool = false
@State private var exerciseValue = ExerciseValue()
@State private var isValueKeyboardPresented = false
var body: some View { var body: some View {
NavigationStack { NavigationStack {
VStack { VStack {
ScrollViewReader { proxy in ScrollViewReader { proxy in
VStack { VStack {
Form { Form {
Section(footer: Text("The exercise description is optional.")) { Section {
TextField("Exercise Name", text: $name) TextField("Exercise Name", text: $name)
// TODO: Add Autocomplete // TODO: Add Autocomplete
TextField("Description", text: $description) TextEditorWithPlaceholder(text: $description, placeholder: "Description (optional)")
} }
Section { Section(footer: Text("""
Button(action: { Examples:
isValueKeyboardPresented.toggle() • Pull-up → None
proxy.scrollTo("valueButton", anchor: .center) • Weighted Pull-up → Weight
}, label: { • Sprint → Time or Distance
Text("\(exerciseValue.value) \(exerciseValue.unit.rawValue)") """)) {
}) Picker("Exercise Metric", selection: $metric) {
.id("valueButton") // Assign a unique ID to the button ForEach(ExerciseMetric.allCases) { metric in
Text(metric.rawValue.isEmpty ? "None" : metric.rawValue)
.tag(metric)
}
}
.pickerStyle(SegmentedPickerStyle())
} }
Section(footer: Text("Feature coming soon.")) { Section(footer: Text("Feature coming soon.")) {
Toggle(isOn: $isPartOfProgression) { Toggle(isOn: $isPartOfProgression) {
@@ -79,39 +77,29 @@ struct ExerciseEditor: View {
if let exercise { if let exercise {
self.name = exercise.name self.name = exercise.name
self.description = exercise.exerciseDescription self.description = exercise.exerciseDescription
self.metric = exercise.metric self.metric = exercise.metric
self.reps = exercise.suggestedReps
self.duration = exercise.suggestedDuration
self.distance = exercise.suggestedDistance
self.isPartOfProgression = exercise.isPartOfProgression self.isPartOfProgression = exercise.isPartOfProgression
} }
} }
} }
if isValueKeyboardPresented {
ValueKeyboard(isPresented: $isValueKeyboardPresented, value: $exerciseValue)
}
} }
} }
} }
private func save() { private func save() {
if let exercise { let exerciseToSave: Exercise
exercise.name = name
exercise.exerciseDescription = description if let exercise = exercise {
exerciseToSave = exercise
exercise.metric = metric
exercise.suggestedReps = reps
exercise.suggestedDuration = duration
exercise.suggestedDistance = distance
exercise.isPartOfProgression = isPartOfProgression
} else { } else {
let newExercise = Exercise(name, metric) exerciseToSave = Exercise(name)
modelContext.insert(newExercise) modelContext.insert(exerciseToSave)
// try? modelContext.save()
} }
exerciseToSave.name = name
exerciseToSave.exerciseDescription = description
exerciseToSave.metric = metric
exerciseToSave.isPartOfProgression = isPartOfProgression
} }
} }

View File

@@ -12,7 +12,7 @@ struct ExerciseLibrary: View {
@Environment(\.modelContext) private var modelContext @Environment(\.modelContext) private var modelContext
@Query(sort: \Exercise.name) private var exercises: [Exercise] @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 newExerciseName: String = ""
@State private var isAddingExercise: Bool = false @State private var isAddingExercise: Bool = false
@FocusState private var isInputFieldFocused: Bool @FocusState private var isInputFieldFocused: Bool
@@ -37,9 +37,9 @@ struct ExerciseLibrary: View {
HStack { HStack {
Text(exercise.name) Text(exercise.name)
Spacer() Spacer()
Text(exercise.metric.rawValue) // Text(exercise.metric.rawValue)
.font(.footnote) // .font(.footnote)
.foregroundStyle(.gray) // .foregroundStyle(.gray)
} }
} }
} }

View File

@@ -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
}
}

View File

@@ -5,6 +5,14 @@
// Created by Felix Förtsch on 25.08.24. // 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 Foundation
import SwiftData import SwiftData
@@ -15,74 +23,53 @@ final class Exercise: Nameable {
var id = UUID() var id = UUID()
var creationDate: Date = Date.now 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 @Attribute(.unique) var name: String
// Performing a push-up correctly has the following form cues
var exerciseDescription: String = "" 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 var isPartOfProgression: Bool = false
// The focus of the push-up is strength var metric: ExerciseMetric
// var focus: ExerciseFocus? // Strength, Flexibility, Speed, etc
init(_ name: String, _ metric: ExerciseMetric) { var equipment: [Equipment] = []
init(_ name: String, _ metric: ExerciseMetric = .none) {
self.name = name self.name = name
self.metric = metric self.metric = metric
} }
} }
enum ExerciseMetric: String, Codable { enum ExerciseMetric: String, Codable, CaseIterable, Identifiable {
case reps = "Reps" // Repeat the exrcise for a given amount of repetitions case none = ""
case duration = "Duration" // Do the exercise for given amount of time case distance = "Distance"
case distance = "Distance" // Do the exercise for a given amount of distance case time = "Time"
// Other possible metrics: case weight = "Weight"
// - Open exercise: this exercise does not bring a metric (eg running)
} var id: Self { self }
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."
}
}
} }
extension Exercise { extension Exercise {
static let sampleDataRecommendedRoutine: [Exercise] = [ static let sampleDataRecommendedRoutine: [Exercise] = [
Exercise("Shoulder Band Warm-up", .duration), Exercise("Shoulder Band Warm-up"),
Exercise("Squat Sky Reaches", .reps), Exercise("Squat Sky Reaches"),
Exercise("GMB Wrist Prep", .duration), Exercise("GMB Wrist Prep", .time),
Exercise("Dead Bugs", .reps), Exercise("Dead Bugs"),
Exercise("Pull-up Progression", .reps), Exercise("Pull-up Progression", .weight),
Exercise("Dip Progression", .reps), Exercise("Dip Progression", .weight),
Exercise("Squat Progression", .reps), Exercise("Squat Progression", .weight),
Exercise("Hinge Progression", .reps), Exercise("Hinge Progression", .weight),
Exercise("Row Progression", .reps), Exercise("Row Progression", .weight),
Exercise("Push-up Progression", .reps), Exercise("Push-up Progression", .weight),
Exercise("Handstand Practice", .duration), Exercise("Handstand Practice", .time),
Exercise("Support Practice", .duration) Exercise("Support Practice", .time)
] ]
static let sampleDataRings: [Exercise] = [ static let sampleDataRings: [Exercise] = [
Exercise("Dips", .reps), Exercise("Dips", .weight),
Exercise("Chin-ups", .reps), Exercise("Chin-ups", .weight),
Exercise("Push-ups", .reps), Exercise("Push-ups", .weight),
Exercise("Inverted Rows", .reps), Exercise("Inverted Rows", .weight),
Exercise("Hanging Knee Raises", .reps), Exercise("Hanging Knee Raises", .weight),
Exercise("Pistol Squats", .reps), Exercise("Pistol Squats", .weight),
Exercise("Hanging Leg Curls", .reps), Exercise("Hanging Leg Curls", .weight),
Exercise("Sissy Squats", .reps), Exercise("Sissy Squats")
] ]
} }

View File

@@ -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
//}

View File

@@ -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 }
//}

View File

@@ -5,6 +5,30 @@
// Created by Felix Förtsch on 10.08.24. // 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<WorkoutItem> {
// 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 Foundation
import SwiftData import SwiftData
@@ -12,61 +36,36 @@ import SwiftData
final class WorkoutItem: Nameable, Positionable { final class WorkoutItem: Nameable, Positionable {
var id = UUID() var id = UUID()
var name: String var name: String
var workout: Workout? 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 { enum WorkoutItemType: Codable {
// TODO: Add workout as WorkoutItemType (needs recursive dealing) case exercise
// case workout
case rest case rest
case set case set
case exerciseWithReps
case exerciseWithDuration
} }
var position: Int = 0 init(_ exercise: Exercise) {
// 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<WorkoutItem> {
// 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
self.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] = []) { init(set: [WorkoutItem] = []) {
self.workoutItemType = .set self.workoutItemType = .set
self.name = "Set" self.name = "Set"
@@ -80,38 +79,30 @@ final class WorkoutItem: Nameable, Positionable {
} }
} }
// PAUSE init(rest: Double) {
init (rest: Int) {
self.workoutItemType = .rest self.workoutItemType = .rest
self.name = "Rest" self.name = "Rest"
self.duration = rest self.reps = 1
self.value = rest
self.metric = .time
} }
} }
extension WorkoutItem { extension WorkoutItem {
static let sampleDataRecommendedRoutine: [WorkoutItem] = { static let sampleDataRecommendedRoutine: [WorkoutItem] = {
var exercises = [WorkoutItem]() var exercises = [WorkoutItem]()
for exercise in Exercise.sampleDataRecommendedRoutine { 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 return exercises
}() }()
static let sampleDataRings: [WorkoutItem] = { static let sampleDataRings: [WorkoutItem] = {
var exercises = [WorkoutItem]() var exercises = [WorkoutItem]()
for exercise in Exercise.sampleDataRings { for exercise in Exercise.sampleDataRings {
exercises.append(WorkoutItem(exercise: exercise)) exercises.append(WorkoutItem(exercise))
} }
return exercises return exercises
}() }()
} }

View File

@@ -22,8 +22,8 @@ struct WorkoutItemLibrarySheet: View {
Section(header: Text("Utilities")) { Section(header: Text("Utilities")) {
AddItemButton(label: "Set") { AddItemButton(label: "Set") {
addWorkoutItemtoWorkout(WorkoutItem(set: [ addWorkoutItemtoWorkout(WorkoutItem(set: [
WorkoutItem(exercise: Exercise("Set item 1", .reps)), WorkoutItem(Exercise("Set item 1")),
WorkoutItem(exercise: Exercise("Set item 2", .reps)) WorkoutItem(Exercise("Set item 2"))
])) ]))
} }
AddItemButton(label: "Rest") { AddItemButton(label: "Rest") {
@@ -34,7 +34,7 @@ struct WorkoutItemLibrarySheet: View {
if !exercises.isEmpty { if !exercises.isEmpty {
ForEach(exercises) { exercise in ForEach(exercises) { exercise in
AddItemButton(label: exercise.name) { AddItemButton(label: exercise.name) {
let workoutItem = WorkoutItem(exercise: exercise) let workoutItem = WorkoutItem(exercise)
addWorkoutItemtoWorkout(workoutItem) addWorkoutItemtoWorkout(workoutItem)
} }
} }

View File

@@ -23,9 +23,7 @@ struct WorkoutListItem: View {
switch workoutItem.workoutItemType { switch workoutItem.workoutItemType {
case .set: case .set:
SetListItem(workout: workout, set: $workoutItem) SetListItem(workout: workout, set: $workoutItem)
case .exerciseWithReps: case .exercise:
Text(workoutItem.name)
case .exerciseWithDuration:
Text(workoutItem.name) Text(workoutItem.name)
case .rest: case .rest:
Text(workoutItem.name) Text(workoutItem.name)
@@ -38,12 +36,12 @@ struct WorkoutListItem: View {
#Preview { #Preview {
List { List {
WorkoutListItem(Workout(name: "RR"), WorkoutItem(set: [ WorkoutListItem(Workout(name: "RR"), WorkoutItem(set: [
WorkoutItem(reps: 10, "Squat"), WorkoutItem(Exercise("Squat")),
WorkoutItem(reps: 10, "Squat"), WorkoutItem(Exercise("Squat")),
WorkoutItem(reps: 10, "Squat")])) WorkoutItem(Exercise("Squat"))]))
WorkoutListItem(Workout(name: "RR"), WorkoutItem(exercise: Exercise("Push-ups", .reps))) WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Push-ups")))
WorkoutListItem(Workout(name: "RR"), WorkoutItem(exercise: Exercise("Push-ups", .reps))) WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Push-ups")))
WorkoutListItem(Workout(name: "RR"), WorkoutItem(exercise: Exercise("Sprint Interval", .duration))) WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Sprint", .distance)))
WorkoutListItem(Workout(name: "RR"), WorkoutItem(exercise: Exercise("Run", .distance))) WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Run", .time)))
} }
} }

View File

@@ -32,6 +32,7 @@ struct WorkoutsPlusApp: App {
extension WorkoutsPlusApp { extension WorkoutsPlusApp {
static let swiftDataSchema = Schema([ static let swiftDataSchema = Schema([
Exercise.self, Exercise.self,
Equipment.self,
Workout.self, Workout.self,
WorkoutItem.self, WorkoutItem.self,
WorkoutSession.self]) WorkoutSession.self])

352
WorkoutsPlus/workouts.json Normal file
View File

@@ -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"
}
]
}
}
}