Exercise Library/ holds per-exercise reference docs (setup, cues, mistakes, progressions) with SVG visuals and a Python-rendered motion pipeline; Workouts/ExerciseFigure renders the bundled *.motion.json rigs as animated stick figures on the exercise screen. Exercises gain a warm-up/main-circuit category, timed exercises display hold time via planSummary, and a completed exercise reopens to a check screen instead of its timers.
136 lines
4.1 KiB
Swift
136 lines
4.1 KiB
Swift
import Foundation
|
||
|
||
/// Completion state of a workout or an individual exercise log. Persisted as its
|
||
/// raw string in both the JSON documents and the SwiftData cache.
|
||
enum WorkoutStatus: String, CaseIterable, Codable, Sendable {
|
||
case notStarted
|
||
case inProgress
|
||
case completed
|
||
case skipped
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .notStarted: "Not Started"
|
||
case .inProgress: "In Progress"
|
||
case .completed: "Completed"
|
||
case .skipped: "Skipped"
|
||
}
|
||
}
|
||
|
||
var name: String { displayName }
|
||
}
|
||
|
||
/// How an exercise's effort is measured. Persisted as its raw `Int` value.
|
||
enum LoadType: Int, CaseIterable, Codable, Sendable {
|
||
case none = 0
|
||
case weight = 1
|
||
case duration = 2
|
||
|
||
var name: String {
|
||
switch self {
|
||
case .none: "None"
|
||
case .weight: "Weight"
|
||
case .duration: "Duration"
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Which segment of a split an exercise belongs to — the split screen groups its
|
||
/// exercise list by this. Persisted as its raw `Int` value; `main` is the default
|
||
/// (and what older documents without the field decode to).
|
||
enum ExerciseCategory: Int, CaseIterable, Codable, Sendable {
|
||
case main = 0
|
||
case warmup = 1
|
||
|
||
/// Order the categories appear on screen: warm-up first, then the main circuit.
|
||
static let displayOrder: [ExerciseCategory] = [.warmup, .main]
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .main: "Main Circuit"
|
||
case .warmup: "Warm-up"
|
||
}
|
||
}
|
||
}
|
||
|
||
/// The kind of training a split represents — used to tag the Apple Health workout
|
||
/// so it lands in the right category (and credits the rings correctly), and to pick
|
||
/// a MET value when the phone has to estimate calories without watch sensor data.
|
||
/// Persisted as its raw `Int` on `SplitDocument`; the `HKWorkoutActivityType`
|
||
/// mapping lives in `Shared/HealthKit/HealthKitMapping.swift` to keep this enum
|
||
/// HealthKit-free.
|
||
enum WorkoutActivityType: Int, CaseIterable, Codable, Sendable {
|
||
case traditionalStrength = 0 // default
|
||
case functionalStrength = 1
|
||
case hiit = 2
|
||
case coreTraining = 3
|
||
case cardio = 4
|
||
case cycling = 5
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .traditionalStrength: "Strength Training"
|
||
case .functionalStrength: "Functional Strength"
|
||
case .hiit: "HIIT"
|
||
case .coreTraining: "Core Training"
|
||
case .cardio: "Cardio"
|
||
case .cycling: "Cycling"
|
||
}
|
||
}
|
||
|
||
var systemImage: String {
|
||
switch self {
|
||
case .traditionalStrength: "dumbbell.fill"
|
||
case .functionalStrength: "figure.strengthtraining.functional"
|
||
case .hiit: "figure.highintensity.intervaltraining"
|
||
case .coreTraining: "figure.core.training"
|
||
case .cardio: "figure.run"
|
||
case .cycling: "figure.outdoor.cycle"
|
||
}
|
||
}
|
||
|
||
/// Metabolic-equivalent value used only for the phone-side calorie estimate
|
||
/// (`kcal ≈ MET × bodyweightKg × hours`) when no watch metrics are available.
|
||
var met: Double {
|
||
switch self {
|
||
case .traditionalStrength: 5.0
|
||
case .functionalStrength: 4.0
|
||
case .hiit: 8.0
|
||
case .coreTraining: 4.0
|
||
case .cardio: 7.0
|
||
case .cycling: 7.5
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Where a workout's health metrics came from: real watch sensors, or a phone-side
|
||
/// estimate written when no watch session ran. Persisted in `WorkoutMetrics`.
|
||
enum MetricSource: String, Codable, Sendable {
|
||
case watch
|
||
case phoneEstimate
|
||
}
|
||
|
||
/// Display unit for weights. The stored weight values are plain integers and are
|
||
/// never rewritten when this changes — switching only relabels what's shown.
|
||
enum WeightUnit: String, CaseIterable, Codable, Sendable {
|
||
case lb
|
||
case kg
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .lb: "Pounds (lb)"
|
||
case .kg: "Kilograms (kg)"
|
||
}
|
||
}
|
||
|
||
var abbreviation: String {
|
||
switch self {
|
||
case .lb: "lb"
|
||
case .kg: "kg"
|
||
}
|
||
}
|
||
|
||
/// Render a stored weight value with the current unit's label, e.g. "135 lb".
|
||
func format(_ value: Int) -> String { "\(value) \(abbreviation)" }
|
||
}
|