Add Warm-Up and Stretching activity types
Adds WorkoutActivityType.warmUp (HealthKit .preparationAndRecovery) and .stretching (.flexibility), and retags the six starter splits that were all mislabeled as Functional Strength: - Warm-Up: Upper Body Warm-Up, Lower Body Warm-Up, Morning Wake-Up - Stretching: Morning Mobility, Full Body Stretch, Evening Stretch The split editor's activity picker surfaces them automatically (CaseIterable). Older app versions decode the new raw values as the default type — additive and not schema-gated, so no quarantine.
This commit is contained in:
@@ -19,7 +19,26 @@ import Foundation
|
||||
/// exported by `render.py --export` into `Resources/ExerciseMotions/` — `skeleton.json`
|
||||
/// plus one `<Exercise Name>.motion.json` per library entry.
|
||||
|
||||
/// Bone lengths for one figure profile (`neutral` is the only one the app renders).
|
||||
/// The figure build the exercise animation renders. A presentation-only choice of
|
||||
/// skeleton profile (bone proportions) — the motion scripts are identical across builds,
|
||||
/// so switching never touches an exercise's animation, only its dimensions. The raw value
|
||||
/// is the `skeleton.json` profile key; persisted via `@AppStorage("figureProfile")`.
|
||||
enum FigureProfile: String, CaseIterable {
|
||||
case neutral
|
||||
case feminine = "female"
|
||||
case masculine = "male"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .neutral: "Neutral"
|
||||
case .feminine: "Feminine"
|
||||
case .masculine: "Masculine"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bone lengths for one figure profile (`neutral`, `female`, or `male`; the app picks
|
||||
/// one per the `figureProfile` setting — see `FigureProfile`).
|
||||
struct SkeletonProfile: Codable {
|
||||
let headR: Double
|
||||
let neck: Double
|
||||
@@ -270,10 +289,10 @@ enum ExerciseMotionLibrary {
|
||||
.sorted()
|
||||
}()
|
||||
|
||||
/// The motion script plus the neutral skeleton profile for `exerciseName`, or `nil`
|
||||
/// when no bundled motion matches (most exercises have none — the caller keeps
|
||||
/// its space empty).
|
||||
static func resources(for exerciseName: String) -> Resources? {
|
||||
/// The motion script plus the requested skeleton `profile` for `exerciseName`, or
|
||||
/// `nil` when no bundled motion matches (most exercises have none — the caller keeps
|
||||
/// its space empty). An unknown profile key falls back to `neutral`.
|
||||
static func resources(for exerciseName: String, profile: FigureProfile = .neutral) -> Resources? {
|
||||
guard
|
||||
let motionURL = Bundle.main.url(forResource: exerciseName, withExtension: "motion.json"),
|
||||
let skeletonURL = Bundle.main.url(forResource: "skeleton", withExtension: "json"),
|
||||
@@ -281,7 +300,7 @@ enum ExerciseMotionLibrary {
|
||||
let skeletonData = try? Data(contentsOf: skeletonURL),
|
||||
let motion = try? JSONDecoder().decode(ExerciseMotion.self, from: motionData),
|
||||
let skeleton = try? JSONDecoder().decode(Skeleton.self, from: skeletonData),
|
||||
let profile = skeleton.profiles["neutral"]
|
||||
let profile = skeleton.profiles[profile.rawValue] ?? skeleton.profiles[FigureProfile.neutral.rawValue]
|
||||
else { return nil }
|
||||
return Resources(motion: motion, profile: profile)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user