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 {
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: {

View File

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

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.
//
// 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<Value: ValueType>: 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<Value: ValueType>: 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<Value: ValueType>: 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)
}
}

View File

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

View File

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

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.
//
// 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")
]
}

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.
//
// 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 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<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
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
}()
}

View File

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

View File

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

View File

@@ -32,6 +32,7 @@ struct WorkoutsPlusApp: App {
extension WorkoutsPlusApp {
static let swiftDataSchema = Schema([
Exercise.self,
Equipment.self,
Workout.self,
WorkoutItem.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"
}
]
}
}
}