diff --git a/packages/health/README.md b/packages/health/README.md index fc83a2b94..e6f9346dd 100644 --- a/packages/health/README.md +++ b/packages/health/README.md @@ -22,7 +22,7 @@ Note that for Android, the target phone **needs** to have [Google Fit](https://w ## Data Types | **Data Type** | **Unit** | **iOS** | **Android (Google Fit)** | **Android (Health Connect)** | **Comments** | -| --------------------------- | ----------------------- | ------- | ------------------------ |------------------------------| -------------------------------------- | +|-----------------------------|-------------------------|---------| ------------------------ |------------------------------|----------------------------------------| | ACTIVE_ENERGY_BURNED | CALORIES | yes | yes | yes | | | BASAL_ENERGY_BURNED | CALORIES | yes | | yes | | | BLOOD_GLUCOSE | MILLIGRAM_PER_DECILITER | yes | yes | yes | | @@ -69,6 +69,7 @@ Note that for Android, the target phone **needs** to have [Google Fit](https://w | HEADACHE_UNSPECIFIED | MINUTES | yes | | | | | AUDIOGRAM | DECIBEL_HEARING_LEVEL | yes | | | | | ELECTROCARDIOGRAM | VOLT | yes | | | Requires Apple Watch to write the data | +| VO2MAX | ? | | | yes | | ## Setup diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt index e6fe1c119..cfc288fba 100644 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt +++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt @@ -91,6 +91,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : private var BASAL_ENERGY_BURNED = "BASAL_ENERGY_BURNED" private var FLIGHTS_CLIMBED = "FLIGHTS_CLIMBED" private var RESPIRATORY_RATE = "RESPIRATORY_RATE" + private var VO2MAX = "VO2MAX" // TODO support unknown? private var SLEEP_ASLEEP = "SLEEP_ASLEEP" @@ -1795,6 +1796,15 @@ class HealthPlugin(private var channel: MethodChannel? = null) : "source_name" to metadata.dataOrigin.packageName, ) ) + is Vo2MaxRecord -> return listOf( + mapOf( + "value" to record.vo2MillilitersPerMinuteKilogram, + "date_from" to record.time.toEpochMilli(), + "date_to" to record.time.toEpochMilli(), + "source_id" to "", + "source_name" to metadata.dataOrigin.packageName, + ) + ) // is ExerciseSessionRecord -> return listOf(mapOf("value" to , // "date_from" to , // "date_to" to , @@ -1952,6 +1962,11 @@ class HealthPlugin(private var channel: MethodChannel? = null) : rate = value, zoneOffset = null, ) + VO2MAX -> Vo2MaxRecord( + time = Instant.ofEpochMilli(startTime), + vo2MillilitersPerMinuteKilogram = value, + zoneOffset = null, + ) // AGGREGATE_STEP_COUNT -> StepsRecord() BLOOD_PRESSURE_SYSTOLIC -> throw IllegalArgumentException("You must use the [writeBloodPressure] API ") BLOOD_PRESSURE_DIASTOLIC -> throw IllegalArgumentException("You must use the [writeBloodPressure] API ") @@ -2118,6 +2133,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : BASAL_ENERGY_BURNED to BasalMetabolicRateRecord::class, FLIGHTS_CLIMBED to FloorsClimbedRecord::class, RESPIRATORY_RATE to RespiratoryRateRecord::class, + VO2MAX to Vo2MaxRecord::class, // MOVE_MINUTES to TODO: Find alternative? // TODO: Implement remaining types // "ActiveCaloriesBurned" to ActiveCaloriesBurnedRecord::class, @@ -2153,7 +2169,6 @@ class HealthPlugin(private var channel: MethodChannel? = null) : // "StepsCadence" to StepsCadenceRecord::class, // "Steps" to StepsRecord::class, // "TotalCaloriesBurned" to TotalCaloriesBurnedRecord::class, - // "Vo2Max" to Vo2MaxRecord::class, // "Weight" to WeightRecord::class, // "WheelchairPushes" to WheelchairPushesRecord::class, ) diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift index 2c7586d51..dd2ed7f06 100644 --- a/packages/health/ios/Classes/SwiftHealthPlugin.swift +++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift @@ -52,6 +52,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let SLEEP_AWAKE = "SLEEP_AWAKE" let SLEEP_DEEP = "SLEEP_DEEP" let SLEEP_REM = "SLEEP_REM" + let VO2MAX = "VO2MAX" let EXERCISE_TIME = "EXERCISE_TIME" let WORKOUT = "WORKOUT" @@ -111,6 +112,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let BEATS_PER_MINUTE = "BEATS_PER_MINUTE" let RESPIRATIONS_PER_MINUTE = "RESPIRATIONS_PER_MINUTE" let MILLIGRAM_PER_DECILITER = "MILLIGRAM_PER_DECILITER" + let VO2MAX_UNIT = "VO2MAX_UNIT" let UNKNOWN_UNIT = "UNKNOWN_UNIT" let NO_UNIT = "NO_UNIT" @@ -766,6 +768,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { unitDict[BEATS_PER_MINUTE] = HKUnit.init(from: "count/min") unitDict[RESPIRATIONS_PER_MINUTE] = HKUnit.init(from: "count/min") unitDict[MILLIGRAM_PER_DECILITER] = HKUnit.init(from: "mg/dL") + unitDict[VO2MAX_UNIT] = HKUnit.init(from: "ml/kg*min") unitDict[UNKNOWN_UNIT] = HKUnit.init(from: "") unitDict[NO_UNIT] = HKUnit.init(from: "") @@ -907,6 +910,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataTypesDict[SLEEP_AWAKE] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! dataTypesDict[SLEEP_DEEP] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! dataTypesDict[SLEEP_REM] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! + dataTypesDict[VO2MAX] = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.vo2Max)! dataTypesDict[EXERCISE_TIME] = HKSampleType.quantityType(forIdentifier: .appleExerciseTime)! dataTypesDict[WORKOUT] = HKSampleType.workoutType() diff --git a/packages/health/lib/src/data_types.dart b/packages/health/lib/src/data_types.dart index 0b7951d93..ed16fffcc 100644 --- a/packages/health/lib/src/data_types.dart +++ b/packages/health/lib/src/data_types.dart @@ -48,6 +48,7 @@ enum HealthDataType { HEADACHE_MODERATE, HEADACHE_SEVERE, HEADACHE_UNSPECIFIED, + VO2MAX, // Heart Rate events (specific to Apple Watch) HIGH_HEART_RATE_EVENT, @@ -112,6 +113,7 @@ const List _dataTypeKeysIOS = [ HealthDataType.HEADACHE_SEVERE, HealthDataType.HEADACHE_UNSPECIFIED, HealthDataType.ELECTROCARDIOGRAM, + HealthDataType.VO2MAX, ]; /// List of data types available on Android @@ -143,6 +145,7 @@ const List _dataTypeKeysAndroid = [ HealthDataType.FLIGHTS_CLIMBED, HealthDataType.BASAL_ENERGY_BURNED, HealthDataType.RESPIRATORY_RATE, + HealthDataType.VO2MAX, ]; /// Maps a [HealthDataType] to a [HealthDataUnit]. @@ -176,6 +179,7 @@ const Map _dataTypeToUnit = { HealthDataType.FLIGHTS_CLIMBED: HealthDataUnit.COUNT, HealthDataType.MOVE_MINUTES: HealthDataUnit.MINUTE, HealthDataType.DISTANCE_DELTA: HealthDataUnit.METER, + HealthDataType.VO2MAX: HealthDataUnit.VO2MAX_UNIT, HealthDataType.WATER: HealthDataUnit.LITER, HealthDataType.SLEEP_IN_BED: HealthDataUnit.MINUTE, @@ -287,6 +291,7 @@ enum HealthDataUnit { BEATS_PER_MINUTE, RESPIRATIONS_PER_MINUTE, MILLIGRAM_PER_DECILITER, + VO2MAX_UNIT, UNKNOWN_UNIT, NO_UNIT, }