From 5fa79ece50644b531ec7d1678f3934731fdaed2e Mon Sep 17 00:00:00 2001 From: jkuehnemundt Date: Tue, 20 Oct 2026 06:56:17 +0000 Subject: [PATCH] feat: sync nutrition metrics from HealthKit (energy, macros, minerals, vitamins) The server allowlist already accepts 39 dietary metrics, but the iOS app only read caffeine and water. Apps like Yazio write calories, macros and micronutrients into HealthKit and users expected them to appear in FreeReps. Add the 18 most commonly tracked nutrition types: energy consumed, protein, total/saturated/mono/polyunsaturated fat, carbohydrates, sugar, fiber, cholesterol, sodium, potassium, calcium, iron, magnesium, zinc, vitamin C and vitamin D. All use aggregateCumulative to match their cumulative HealthKit semantics. Caffeine switches from g to mg and from individual to aggregateCumulative to align with how HealthKit stores it. Co-Authored-By: Claude Opus 4.6 --- .../FreeReps/Models/HealthDataType.swift | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/Sources/FreeReps/Models/HealthDataType.swift b/app/Sources/FreeReps/Models/HealthDataType.swift index fc02228..2c2843c 100644 --- a/app/Sources/FreeReps/Models/HealthDataType.swift +++ b/app/Sources/FreeReps/Models/HealthDataType.swift @@ -123,8 +123,26 @@ enum HealthDataTypes { // Mobility & Fitness .init(id: HKQuantityTypeIdentifier.vo2Max.rawValue, displayName: "VO2 Max", category: .mobility, unit: HKUnit.literUnit(with: .milli).unitDivided(by: HKUnit.gramUnit(with: .kilo).unitMultiplied(by: HKUnit.minute())), unitString: "mL/kg·min"), // Nutrition - .init(id: HKQuantityTypeIdentifier.dietaryCaffeine.rawValue, displayName: "Caffeine", category: .nutrition, unit: .gram(), unitString: "g"), - .init(id: HKQuantityTypeIdentifier.dietaryWater.rawValue, displayName: "Water", category: .nutrition, unit: .liter(), unitString: "L"), + .init(id: HKQuantityTypeIdentifier.dietaryEnergyConsumed.rawValue, displayName: "Energy Consumed", category: .nutrition, unit: .kilocalorie(), unitString: "kcal", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryProtein.rawValue, displayName: "Protein", category: .nutrition, unit: .gram(), unitString: "g", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryFatTotal.rawValue, displayName: "Total Fat", category: .nutrition, unit: .gram(), unitString: "g", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryCarbohydrates.rawValue, displayName: "Carbohydrates", category: .nutrition, unit: .gram(), unitString: "g", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietarySugar.rawValue, displayName: "Sugar", category: .nutrition, unit: .gram(), unitString: "g", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryFiber.rawValue, displayName: "Fiber", category: .nutrition, unit: .gram(), unitString: "g", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryFatSaturated.rawValue, displayName: "Saturated Fat", category: .nutrition, unit: .gram(), unitString: "g", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryFatMonounsaturated.rawValue, displayName: "Monounsaturated Fat", category: .nutrition, unit: .gram(), unitString: "g", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryFatPolyunsaturated.rawValue, displayName: "Polyunsaturated Fat", category: .nutrition, unit: .gram(), unitString: "g", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryCholesterol.rawValue, displayName: "Cholesterol", category: .nutrition, unit: .gramUnit(with: .milli), unitString: "mg", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietarySodium.rawValue, displayName: "Sodium", category: .nutrition, unit: .gramUnit(with: .milli), unitString: "mg", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryPotassium.rawValue, displayName: "Potassium", category: .nutrition, unit: .gramUnit(with: .milli), unitString: "mg", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryCalcium.rawValue, displayName: "Calcium", category: .nutrition, unit: .gramUnit(with: .milli), unitString: "mg", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryIron.rawValue, displayName: "Iron", category: .nutrition, unit: .gramUnit(with: .milli), unitString: "mg", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryMagnesium.rawValue, displayName: "Magnesium", category: .nutrition, unit: .gramUnit(with: .milli), unitString: "mg", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryZinc.rawValue, displayName: "Zinc", category: .nutrition, unit: .gramUnit(with: .milli), unitString: "mg", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryVitaminC.rawValue, displayName: "Vitamin C", category: .nutrition, unit: .gramUnit(with: .milli), unitString: "mg", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryVitaminD.rawValue, displayName: "Vitamin D", category: .nutrition, unit: .internationalUnit(), unitString: "IU", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryCaffeine.rawValue, displayName: "Caffeine", category: .nutrition, unit: .gramUnit(with: .milli), unitString: "mg", syncStrategy: .aggregateCumulative(interval: 3600)), + .init(id: HKQuantityTypeIdentifier.dietaryWater.rawValue, displayName: "Water", category: .nutrition, unit: .liter(), unitString: "L", syncStrategy: .aggregateCumulative(interval: 3600)), // iOS 16+ .init(id: HKQuantityTypeIdentifier.heartRateRecoveryOneMinute.rawValue, displayName: "Heart Rate Recovery", category: .vitals, unit: HKUnit(from: "count/min"), unitString: "bpm"), .init(id: HKQuantityTypeIdentifier.appleSleepingWristTemperature.rawValue, displayName: "Sleeping Wrist Temperature", category: .body, unit: .degreeCelsius(), unitString: "°C"),