simplify Exercise to only hold on to the unit of the metric, remove ValueKeyboard as input
This commit is contained in:
@@ -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: {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
33
WorkoutsPlus/Components/TextEditorWithPlaceholder.swift
Normal file
33
WorkoutsPlus/Components/TextEditorWithPlaceholder.swift
Normal 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)")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
WorkoutsPlus/Models/Equipment.swift
Normal file
24
WorkoutsPlus/Models/Equipment.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
20
WorkoutsPlus/Models/Food.swift
Normal file
20
WorkoutsPlus/Models/Food.swift
Normal 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
|
||||
//}
|
||||
27
WorkoutsPlus/Models/Unit.swift
Normal file
27
WorkoutsPlus/Models/Unit.swift
Normal 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 }
|
||||
//}
|
||||
@@ -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
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
352
WorkoutsPlus/workouts.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user