diff --git a/packages/health/CHANGELOG.md b/packages/health/CHANGELOG.md index c5f6b020a..153d069b9 100644 --- a/packages/health/CHANGELOG.md +++ b/packages/health/CHANGELOG.md @@ -1,3 +1,8 @@ +## 13.1.4 + +* Fix adding mindfulness resulted in crash in iOS +* Support SPM for iOS + ## 13.1.3 * Fix permissions issues with iOS @@ -24,6 +29,7 @@ ## 13.0.1 * Refactored Swift native implementation - See PR [#1175](https://github.com/cph-cachet/flutter-plugins/pull/1175) and [#1208](https://github.com/cph-cachet/flutter-plugins/pull/1208) for more information: + ``` SwiftHealthPlugin (Main Plugin Class) ├── HealthDataReader (Reading health data) @@ -54,10 +60,12 @@ SwiftHealthPlugin (Main Plugin Class) * iOS: Parse metadata to remove unsupported types - PR [#1120](https://github.com/cph-cachet/flutter-plugins/pull/1120) * iOS: Add UV Index Types * Android: Add request access to historic data [#1126](https://github.com/cph-cachet/flutter-plugins/issues/1126) - PR [#1127](https://github.com/cph-cachet/flutter-plugins/pull/1127) + ```XML ``` + * Android: * Update `androidx.compose:compose-bom` to `2025.02.00` * Update `androidx.health.connect:connect-client` to `1.1.0-alpha11` @@ -85,10 +93,12 @@ SwiftHealthPlugin (Main Plugin Class) * Fix [#984](https://github.com/cph-cachet/flutter-plugins/issues/984) - PR [#1055](https://github.com/cph-cachet/flutter-plugins/pull/1055) * Add `LEAN_BODY_MASS` data type [#1078](https://github.com/cph-cachet/flutter-plugins/issues/1078) - PR [#1097](https://github.com/cph-cachet/flutter-plugins/pull/1097) * The following AndroidManifest values are required to READ/WRITE `LEAN_BODY_MASS`: + ```XML ``` + * iOS: Add `WATER_TEMPERATURE` and `UNDERWATER_DEPTH` health values [#1096](https://github.com/cph-cachet/flutter-plugins/issues/1096) * iOS: Add support for `Underwater Diving` workout [#1096](https://github.com/cph-cachet/flutter-plugins/issues/1096) * Fix [#1072](https://github.com/cph-cachet/flutter-plugins/issues/1072) and [#1074](https://github.com/cph-cachet/flutter-plugins/issues/1074) diff --git a/packages/health/example/ios/Flutter/AppFrameworkInfo.plist b/packages/health/example/ios/Flutter/AppFrameworkInfo.plist index 7c5696400..1dc6cf765 100644 --- a/packages/health/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/health/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/packages/health/example/ios/Runner.xcodeproj/project.pbxproj b/packages/health/example/ios/Runner.xcodeproj/project.pbxproj index b856ee37b..025a38b41 100644 --- a/packages/health/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/health/example/ios/Runner.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ ABEE568C2D6B9E4300551983 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; F97011D03CBC7A35D177D127 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; FCEA0A19EEB97E6889BA5240 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -112,6 +113,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -249,6 +251,7 @@ ); mainGroup = 97C146E51CF9000F007C117D; packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, ABB05D852D6BB16700FA4740 /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; @@ -485,7 +488,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -616,7 +619,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -667,7 +670,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -765,6 +768,10 @@ isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ diff --git a/packages/health/example/lib/main.dart b/packages/health/example/lib/main.dart index e94fa44ce..8c3387da8 100644 --- a/packages/health/example/lib/main.dart +++ b/packages/health/example/lib/main.dart @@ -409,6 +409,15 @@ class HealthAppState extends State { type: HealthDataType.HEART_RATE_VARIABILITY_RMSSD, startTime: earlier, endTime: now); + + // Mindfulness value should be counted based on start and end time + success &= await health.writeHealthData( + value: 10, + type: HealthDataType.MINDFULNESS, + startTime: earlier, + endTime: now, + recordingMethod: RecordingMethod.automatic, + ); } // Available on iOS or iOS 16.0+ only diff --git a/packages/health/ios/Classes/HealthDataWriter.swift b/packages/health/ios/Classes/HealthDataWriter.swift index 1ea3bf301..df19a505e 100644 --- a/packages/health/ios/Classes/HealthDataWriter.swift +++ b/packages/health/ios/Classes/HealthDataWriter.swift @@ -1,5 +1,5 @@ -import HealthKit import Flutter +import HealthKit /// Class responsible for writing health data to HealthKit class HealthDataWriter { @@ -7,45 +7,54 @@ class HealthDataWriter { let dataTypesDict: [String: HKSampleType] let unitDict: [String: HKUnit] let workoutActivityTypeMap: [String: HKWorkoutActivityType] - + /// - Parameters: /// - healthStore: The HealthKit store /// - dataTypesDict: Dictionary of data types /// - unitDict: Dictionary of units /// - workoutActivityTypeMap: Dictionary of workout activity types - init(healthStore: HKHealthStore, dataTypesDict: [String: HKSampleType], unitDict: [String: HKUnit], workoutActivityTypeMap: [String: HKWorkoutActivityType]) { + init( + healthStore: HKHealthStore, dataTypesDict: [String: HKSampleType], + unitDict: [String: HKUnit], workoutActivityTypeMap: [String: HKWorkoutActivityType] + ) { self.healthStore = healthStore self.dataTypesDict = dataTypesDict self.unitDict = unitDict self.workoutActivityTypeMap = workoutActivityTypeMap } - + /// Writes general health data /// - Parameters: /// - call: Flutter method call /// - result: Flutter result callback func writeData(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, - let value = (arguments["value"] as? Double), - let type = (arguments["dataTypeKey"] as? String), - let unit = (arguments["dataUnitKey"] as? String), - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber), - let recordingMethod = (arguments["recordingMethod"] as? Int) + let value = (arguments["value"] as? Double), + let type = (arguments["dataTypeKey"] as? String), + let unit = (arguments["dataUnitKey"] as? String), + let startTime = (arguments["startTime"] as? NSNumber), + let endTime = (arguments["endTime"] as? NSNumber), + let recordingMethod = (arguments["recordingMethod"] as? Int) else { throw PluginError(message: "Invalid Arguments") } - + + // Handle mindfulness sessions specifically + if type == HealthConstants.MINDFULNESS { + try writeMindfulness(call: call, result: result) + return + } + let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - + let isManualEntry = recordingMethod == HealthConstants.RecordingMethod.manual.rawValue let metadata: [String: Any] = [ HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry) ] - + let sample: HKObject - + if dataTypesDict[type]!.isKind(of: HKCategoryType.self) { sample = HKCategorySample( type: dataTypesDict[type] as! HKCategoryType, value: Int(value), start: dateFrom, @@ -56,7 +65,7 @@ class HealthDataWriter { type: dataTypesDict[type] as! HKQuantityType, quantity: quantity, start: dateFrom, end: dateTo, metadata: metadata) } - + healthStore.save( sample, withCompletion: { (success, error) in @@ -68,27 +77,27 @@ class HealthDataWriter { } }) } - + /// Writes audiogram data /// - Parameters: /// - call: Flutter method call /// - result: Flutter result callback func writeAudiogram(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, - let frequencies = (arguments["frequencies"] as? [Double]), - let leftEarSensitivities = (arguments["leftEarSensitivities"] as? [Double]), - let rightEarSensitivities = (arguments["rightEarSensitivities"] as? [Double]), - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber) + let frequencies = (arguments["frequencies"] as? [Double]), + let leftEarSensitivities = (arguments["leftEarSensitivities"] as? [Double]), + let rightEarSensitivities = (arguments["rightEarSensitivities"] as? [Double]), + let startTime = (arguments["startTime"] as? NSNumber), + let endTime = (arguments["endTime"] as? NSNumber) else { throw PluginError(message: "Invalid Arguments") } - + let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - + var sensitivityPoints = [HKAudiogramSensitivityPoint]() - + for index in 0...frequencies.count - 1 { let frequency = HKQuantity(unit: HKUnit.hertz(), doubleValue: frequencies[index]) let dbUnit = HKUnit.decibelHearingLevel() @@ -98,23 +107,25 @@ class HealthDataWriter { frequency: frequency, leftEarSensitivity: left, rightEarSensitivity: right) sensitivityPoints.append(sensitivityPoint) } - + let audiogram: HKAudiogramSample let metadataReceived = (arguments["metadata"] as? [String: Any]?) - + if (metadataReceived) != nil { guard let deviceName = metadataReceived?!["HKDeviceName"] as? String else { return } guard let externalUUID = metadataReceived?!["HKExternalUUID"] as? String else { return } - + audiogram = HKAudiogramSample( sensitivityPoints: sensitivityPoints, start: dateFrom, end: dateTo, - metadata: [HKMetadataKeyDeviceName: deviceName, HKMetadataKeyExternalUUID: externalUUID]) - + metadata: [ + HKMetadataKeyDeviceName: deviceName, HKMetadataKeyExternalUUID: externalUUID, + ]) + } else { audiogram = HKAudiogramSample( sensitivityPoints: sensitivityPoints, start: dateFrom, end: dateTo, metadata: nil) } - + healthStore.save( audiogram, withCompletion: { (success, error) in @@ -126,29 +137,29 @@ class HealthDataWriter { } }) } - + /// Writes blood pressure data /// - Parameters: /// - call: Flutter method call /// - result: Flutter result callback func writeBloodPressure(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, - let systolic = (arguments["systolic"] as? Double), - let diastolic = (arguments["diastolic"] as? Double), - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber), - let recordingMethod = (arguments["recordingMethod"] as? Int) + let systolic = (arguments["systolic"] as? Double), + let diastolic = (arguments["diastolic"] as? Double), + let startTime = (arguments["startTime"] as? NSNumber), + let endTime = (arguments["endTime"] as? NSNumber), + let recordingMethod = (arguments["recordingMethod"] as? Int) else { throw PluginError(message: "Invalid Arguments") } let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - + let isManualEntry = recordingMethod == HealthConstants.RecordingMethod.manual.rawValue let metadata = [ HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry) ] - + let systolic_sample = HKQuantitySample( type: HKSampleType.quantityType(forIdentifier: .bloodPressureSystolic)!, quantity: HKQuantity(unit: HKUnit.millimeterOfMercury(), doubleValue: systolic), @@ -159,8 +170,9 @@ class HealthDataWriter { start: dateFrom, end: dateTo, metadata: metadata) let bpCorrelationType = HKCorrelationType.correlationType(forIdentifier: .bloodPressure)! let bpCorrelation = Set(arrayLiteral: systolic_sample, diastolic_sample) - let blood_pressure_sample = HKCorrelation(type: bpCorrelationType , start: dateFrom, end: dateTo, objects: bpCorrelation) - + let blood_pressure_sample = HKCorrelation( + type: bpCorrelationType, start: dateFrom, end: dateTo, objects: bpCorrelation) + healthStore.save( [blood_pressure_sample], withCompletion: { (success, error) in @@ -172,128 +184,149 @@ class HealthDataWriter { } }) } - + /// Writes meal nutrition data /// - Parameters: /// - call: Flutter method call /// - result: Flutter result callback func writeMeal(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, - let name = (arguments["name"] as? String?), - let startTime = (arguments["start_time"] as? NSNumber), - let endTime = (arguments["end_time"] as? NSNumber), - let mealType = (arguments["meal_type"] as? String?), - let recordingMethod = arguments["recordingMethod"] as? Int + let name = (arguments["name"] as? String?), + let startTime = (arguments["start_time"] as? NSNumber), + let endTime = (arguments["end_time"] as? NSNumber), + let mealType = (arguments["meal_type"] as? String?), + let recordingMethod = arguments["recordingMethod"] as? Int else { throw PluginError(message: "Invalid Arguments") } - + let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - + let mealTypeString = mealType ?? "UNKNOWN" - + let isManualEntry = recordingMethod == HealthConstants.RecordingMethod.manual.rawValue - - var metadata = ["HKFoodMeal": mealTypeString, HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry)] as [String : Any] - if (name != nil) { + + var metadata = + [ + "HKFoodMeal": mealTypeString, + HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry), + ] as [String: Any] + if name != nil { metadata[HKMetadataKeyFoodType] = "\(name!)" } - + var nutrition = Set() for (key, identifier) in HealthConstants.NUTRITION_KEYS { let value = arguments[key] as? Double guard let unwrappedValue = value else { continue } - let unit = key == "calories" ? HKUnit.kilocalorie() : key == "water" ? HKUnit.literUnit(with: .milli) : HKUnit.gram() + let unit = + key == "calories" + ? HKUnit.kilocalorie() + : key == "water" ? HKUnit.literUnit(with: .milli) : HKUnit.gram() let nutritionSample = HKQuantitySample( - type: HKSampleType.quantityType(forIdentifier: identifier)!, quantity: HKQuantity(unit: unit, doubleValue: unwrappedValue), start: dateFrom, end: dateTo, metadata: metadata) + type: HKSampleType.quantityType(forIdentifier: identifier)!, + quantity: HKQuantity(unit: unit, doubleValue: unwrappedValue), start: dateFrom, + end: dateTo, metadata: metadata) nutrition.insert(nutritionSample) } - - if #available(iOS 15.0, *){ - let type = HKCorrelationType.correlationType(forIdentifier: HKCorrelationTypeIdentifier.food)! - let meal = HKCorrelation(type: type, start: dateFrom, end: dateTo, objects: nutrition, metadata: metadata) - - healthStore.save(meal, withCompletion: { (success, error) in - if let err = error { - print("Error Saving Meal Sample: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - }) + + if #available(iOS 15.0, *) { + let type = HKCorrelationType.correlationType( + forIdentifier: HKCorrelationTypeIdentifier.food)! + let meal = HKCorrelation( + type: type, start: dateFrom, end: dateTo, objects: nutrition, metadata: metadata) + + healthStore.save( + meal, + withCompletion: { (success, error) in + if let err = error { + print("Error Saving Meal Sample: \(err.localizedDescription)") + } + DispatchQueue.main.async { + result(success) + } + }) } else { result(false) } } - + /// Writes insulin delivery data /// - Parameters: /// - call: Flutter method call /// - result: Flutter result callback func writeInsulinDelivery(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, - let units = (arguments["units"] as? Double), - let reason = (arguments["reason"] as? NSNumber), - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber) + let units = (arguments["units"] as? Double), + let reason = (arguments["reason"] as? NSNumber), + let startTime = (arguments["startTime"] as? NSNumber), + let endTime = (arguments["endTime"] as? NSNumber) else { throw PluginError(message: "Invalid Arguments") } let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - + let type = HKSampleType.quantityType(forIdentifier: .insulinDelivery)! let quantity = HKQuantity(unit: HKUnit.internationalUnit(), doubleValue: units) let metadata = [HKMetadataKeyInsulinDeliveryReason: reason] - - let insulin_sample = HKQuantitySample(type: type, quantity: quantity, start: dateFrom, end: dateTo, metadata: metadata) - - healthStore.save(insulin_sample, withCompletion: { (success, error) in - if let err = error { - print("Error Saving Insulin Delivery Sample: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - }) + + let insulin_sample = HKQuantitySample( + type: type, quantity: quantity, start: dateFrom, end: dateTo, metadata: metadata) + + healthStore.save( + insulin_sample, + withCompletion: { (success, error) in + if let err = error { + print("Error Saving Insulin Delivery Sample: \(err.localizedDescription)") + } + DispatchQueue.main.async { + result(success) + } + }) } - + /// Writes menstruation flow data /// - Parameters: /// - call: Flutter method call /// - result: Flutter result callback func writeMenstruationFlow(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, - let flow = (arguments["value"] as? Int), - let endTime = (arguments["endTime"] as? NSNumber), - let isStartOfCycle = (arguments["isStartOfCycle"] as? NSNumber), - let recordingMethod = (arguments["recordingMethod"] as? Int) + let flow = (arguments["value"] as? Int), + let endTime = (arguments["endTime"] as? NSNumber), + let isStartOfCycle = (arguments["isStartOfCycle"] as? NSNumber), + let recordingMethod = (arguments["recordingMethod"] as? Int) else { - throw PluginError(message: "Invalid Arguments - value, startTime, endTime or isStartOfCycle invalid") + throw PluginError( + message: "Invalid Arguments - value, startTime, endTime or isStartOfCycle invalid") } guard let menstrualFlowType = HKCategoryValueMenstrualFlow(rawValue: flow) else { throw PluginError(message: "Invalid Menstrual Flow Type") } - + let dateTime = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - + let isManualEntry = recordingMethod == HealthConstants.RecordingMethod.manual.rawValue - + guard let categoryType = HKSampleType.categoryType(forIdentifier: .menstrualFlow) else { throw PluginError(message: "Invalid Menstrual Flow Type") } - - let metadata = [HKMetadataKeyMenstrualCycleStart: isStartOfCycle, HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry)] as [String : Any] - + + let metadata = + [ + HKMetadataKeyMenstrualCycleStart: isStartOfCycle, + HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry), + ] as [String: Any] + let sample = HKCategorySample( type: categoryType, - value: menstrualFlowType.rawValue, - start: dateTime, + value: menstrualFlowType.rawValue, + start: dateTime, end: dateTime, metadata: metadata ) - + healthStore.save( sample, withCompletion: { (success, error) in @@ -305,24 +338,71 @@ class HealthDataWriter { } }) } - + + /// Writes mindfulness session data + /// - Parameters: + /// - call: Flutter method call + /// - result: Flutter result callback + func writeMindfulness(call: FlutterMethodCall, result: @escaping FlutterResult) throws { + guard let arguments = call.arguments as? NSDictionary, + let startTime = (arguments["startTime"] as? NSNumber), + let endTime = (arguments["endTime"] as? NSNumber), + let recordingMethod = (arguments["recordingMethod"] as? Int) + else { + throw PluginError(message: "Invalid Arguments") + } + + let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) + let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) + + let isManualEntry = recordingMethod == HealthConstants.RecordingMethod.manual.rawValue + let metadata: [String: Any] = [ + HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry) + ] + + guard let categoryType = HKSampleType.categoryType(forIdentifier: .mindfulSession) else { + throw PluginError(message: "Invalid Mindfulness Session Type") + } + + // The duration is tracked by the start and end dates, not by the value + let sample = HKCategorySample( + type: categoryType, + value: HKCategoryValue.notApplicable.rawValue, + start: dateFrom, + end: dateTo, + metadata: metadata + ) + + healthStore.save( + sample, + withCompletion: { (success, error) in + if let err = error { + print("Error Saving Mindfulness Session: \(err.localizedDescription)") + } + DispatchQueue.main.async { + result(success) + } + }) + } + /// Writes workout data /// - Parameters: /// - call: Flutter method call /// - result: Flutter result callback func writeWorkoutData(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, - let activityType = (arguments["activityType"] as? String), - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber), - let activityTypeValue = workoutActivityTypeMap[activityType] + let activityType = (arguments["activityType"] as? String), + let startTime = (arguments["startTime"] as? NSNumber), + let endTime = (arguments["endTime"] as? NSNumber), + let activityTypeValue = workoutActivityTypeMap[activityType] else { - throw PluginError(message: "Invalid Arguments - activityType, startTime or endTime invalid") + throw PluginError( + message: "Invalid Arguments - activityType, startTime or endTime invalid") } - + var totalEnergyBurned: HKQuantity? var totalDistance: HKQuantity? = nil - + // Handle optional arguments if let teb = (arguments["totalEnergyBurned"] as? Double) { totalEnergyBurned = HKQuantity( @@ -332,20 +412,20 @@ class HealthDataWriter { totalDistance = HKQuantity( unit: unitDict[(arguments["totalDistanceUnit"] as! String)]!, doubleValue: td) } - + let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - + let workout = HKWorkout( - activityType: activityTypeValue, - start: dateFrom, - end: dateTo, + activityType: activityTypeValue, + start: dateFrom, + end: dateTo, duration: dateTo.timeIntervalSince(dateFrom), totalEnergyBurned: totalEnergyBurned ?? nil, - totalDistance: totalDistance ?? nil, + totalDistance: totalDistance ?? nil, metadata: nil ) - + healthStore.save( workout, withCompletion: { (success, error) in diff --git a/packages/health/ios/health.podspec b/packages/health/ios/health.podspec index d13878710..f0eb7cd7a 100644 --- a/packages/health/ios/health.podspec +++ b/packages/health/ios/health.podspec @@ -3,7 +3,7 @@ # Pod::Spec.new do |s| s.name = 'health' - s.version = '13.1.3' + s.version = '13.1.4' s.summary = 'Wrapper for Apple\'s HealthKit on iOS and Google\'s Health Connect on Android.' s.description = <<-DESC Wrapper for Apple's HealthKit on iOS and Google's Health Connect on Android. diff --git a/packages/health/lib/health.g.dart b/packages/health/lib/health.g.dart index 12f2d3ed8..d12054286 100644 --- a/packages/health/lib/health.g.dart +++ b/packages/health/lib/health.g.dart @@ -6,29 +6,31 @@ part of 'health.dart'; // JsonSerializableGenerator // ************************************************************************** -HealthDataPoint _$HealthDataPointFromJson(Map json) => - HealthDataPoint( - uuid: json['uuid'] as String, - value: HealthValue.fromJson(json['value'] as Map), - type: $enumDecode(_$HealthDataTypeEnumMap, json['type']), - unit: $enumDecode(_$HealthDataUnitEnumMap, json['unit']), - dateFrom: DateTime.parse(json['dateFrom'] as String), - dateTo: DateTime.parse(json['dateTo'] as String), - sourcePlatform: - $enumDecode(_$HealthPlatformTypeEnumMap, json['sourcePlatform']), - sourceDeviceId: json['sourceDeviceId'] as String, - sourceId: json['sourceId'] as String, - sourceName: json['sourceName'] as String, - recordingMethod: $enumDecodeNullable( - _$RecordingMethodEnumMap, json['recordingMethod']) ?? - RecordingMethod.unknown, - workoutSummary: json['workoutSummary'] == null - ? null - : WorkoutSummary.fromJson( - json['workoutSummary'] as Map), - metadata: json['metadata'] as Map?, - deviceModel: json['deviceModel'] as String?, - ); +HealthDataPoint _$HealthDataPointFromJson( + Map json, +) => HealthDataPoint( + uuid: json['uuid'] as String, + value: HealthValue.fromJson(json['value'] as Map), + type: $enumDecode(_$HealthDataTypeEnumMap, json['type']), + unit: $enumDecode(_$HealthDataUnitEnumMap, json['unit']), + dateFrom: DateTime.parse(json['dateFrom'] as String), + dateTo: DateTime.parse(json['dateTo'] as String), + sourcePlatform: $enumDecode( + _$HealthPlatformTypeEnumMap, + json['sourcePlatform'], + ), + sourceDeviceId: json['sourceDeviceId'] as String, + sourceId: json['sourceId'] as String, + sourceName: json['sourceName'] as String, + recordingMethod: + $enumDecodeNullable(_$RecordingMethodEnumMap, json['recordingMethod']) ?? + RecordingMethod.unknown, + workoutSummary: json['workoutSummary'] == null + ? null + : WorkoutSummary.fromJson(json['workoutSummary'] as Map), + metadata: json['metadata'] as Map?, + deviceModel: json['deviceModel'] as String?, +); Map _$HealthDataPointToJson(HealthDataPoint instance) => { @@ -43,10 +45,9 @@ Map _$HealthDataPointToJson(HealthDataPoint instance) => 'sourceId': instance.sourceId, 'sourceName': instance.sourceName, 'recordingMethod': _$RecordingMethodEnumMap[instance.recordingMethod]!, - if (instance.workoutSummary?.toJson() case final value?) - 'workoutSummary': value, - if (instance.metadata case final value?) 'metadata': value, - if (instance.deviceModel case final value?) 'deviceModel': value, + 'workoutSummary': ?instance.workoutSummary?.toJson(), + 'metadata': ?instance.metadata, + 'deviceModel': ?instance.deviceModel, }; const _$HealthDataTypeEnumMap = { @@ -228,74 +229,76 @@ HealthValue _$HealthValueFromJson(Map json) => HealthValue()..$type = json['__type'] as String?; Map _$HealthValueToJson(HealthValue instance) => - { - if (instance.$type case final value?) '__type': value, - }; + {'__type': ?instance.$type}; NumericHealthValue _$NumericHealthValueFromJson(Map json) => - NumericHealthValue( - numericValue: json['numericValue'] as num, - )..$type = json['__type'] as String?; + NumericHealthValue(numericValue: json['numericValue'] as num) + ..$type = json['__type'] as String?; Map _$NumericHealthValueToJson(NumericHealthValue instance) => { - if (instance.$type case final value?) '__type': value, + '__type': ?instance.$type, 'numericValue': instance.numericValue, }; AudiogramHealthValue _$AudiogramHealthValueFromJson( - Map json) => - AudiogramHealthValue( - frequencies: - (json['frequencies'] as List).map((e) => e as num).toList(), - leftEarSensitivities: (json['leftEarSensitivities'] as List) - .map((e) => e as num) - .toList(), - rightEarSensitivities: (json['rightEarSensitivities'] as List) - .map((e) => e as num) - .toList(), - )..$type = json['__type'] as String?; + Map json, +) => AudiogramHealthValue( + frequencies: (json['frequencies'] as List) + .map((e) => e as num) + .toList(), + leftEarSensitivities: (json['leftEarSensitivities'] as List) + .map((e) => e as num) + .toList(), + rightEarSensitivities: (json['rightEarSensitivities'] as List) + .map((e) => e as num) + .toList(), +)..$type = json['__type'] as String?; Map _$AudiogramHealthValueToJson( - AudiogramHealthValue instance) => - { - if (instance.$type case final value?) '__type': value, - 'frequencies': instance.frequencies, - 'leftEarSensitivities': instance.leftEarSensitivities, - 'rightEarSensitivities': instance.rightEarSensitivities, - }; + AudiogramHealthValue instance, +) => { + '__type': ?instance.$type, + 'frequencies': instance.frequencies, + 'leftEarSensitivities': instance.leftEarSensitivities, + 'rightEarSensitivities': instance.rightEarSensitivities, +}; WorkoutHealthValue _$WorkoutHealthValueFromJson(Map json) => WorkoutHealthValue( workoutActivityType: $enumDecode( - _$HealthWorkoutActivityTypeEnumMap, json['workoutActivityType']), + _$HealthWorkoutActivityTypeEnumMap, + json['workoutActivityType'], + ), totalEnergyBurned: (json['totalEnergyBurned'] as num?)?.toInt(), totalEnergyBurnedUnit: $enumDecodeNullable( - _$HealthDataUnitEnumMap, json['totalEnergyBurnedUnit']), + _$HealthDataUnitEnumMap, + json['totalEnergyBurnedUnit'], + ), totalDistance: (json['totalDistance'] as num?)?.toInt(), totalDistanceUnit: $enumDecodeNullable( - _$HealthDataUnitEnumMap, json['totalDistanceUnit']), + _$HealthDataUnitEnumMap, + json['totalDistanceUnit'], + ), totalSteps: (json['totalSteps'] as num?)?.toInt(), - totalStepsUnit: - $enumDecodeNullable(_$HealthDataUnitEnumMap, json['totalStepsUnit']), + totalStepsUnit: $enumDecodeNullable( + _$HealthDataUnitEnumMap, + json['totalStepsUnit'], + ), )..$type = json['__type'] as String?; Map _$WorkoutHealthValueToJson(WorkoutHealthValue instance) => { - if (instance.$type case final value?) '__type': value, + '__type': ?instance.$type, 'workoutActivityType': _$HealthWorkoutActivityTypeEnumMap[instance.workoutActivityType]!, - if (instance.totalEnergyBurned case final value?) - 'totalEnergyBurned': value, - if (_$HealthDataUnitEnumMap[instance.totalEnergyBurnedUnit] - case final value?) - 'totalEnergyBurnedUnit': value, - if (instance.totalDistance case final value?) 'totalDistance': value, - if (_$HealthDataUnitEnumMap[instance.totalDistanceUnit] case final value?) - 'totalDistanceUnit': value, - if (instance.totalSteps case final value?) 'totalSteps': value, - if (_$HealthDataUnitEnumMap[instance.totalStepsUnit] case final value?) - 'totalStepsUnit': value, + 'totalEnergyBurned': ?instance.totalEnergyBurned, + 'totalEnergyBurnedUnit': + ?_$HealthDataUnitEnumMap[instance.totalEnergyBurnedUnit], + 'totalDistance': ?instance.totalDistance, + 'totalDistanceUnit': ?_$HealthDataUnitEnumMap[instance.totalDistanceUnit], + 'totalSteps': ?instance.totalSteps, + 'totalStepsUnit': ?_$HealthDataUnitEnumMap[instance.totalStepsUnit], }; const _$HealthWorkoutActivityTypeEnumMap = { @@ -405,31 +408,32 @@ const _$HealthWorkoutActivityTypeEnumMap = { }; ElectrocardiogramHealthValue _$ElectrocardiogramHealthValueFromJson( - Map json) => - ElectrocardiogramHealthValue( - voltageValues: (json['voltageValues'] as List) - .map((e) => - ElectrocardiogramVoltageValue.fromJson(e as Map)) - .toList(), - averageHeartRate: json['averageHeartRate'] as num?, - samplingFrequency: (json['samplingFrequency'] as num?)?.toDouble(), - classification: $enumDecodeNullable( - _$ElectrocardiogramClassificationEnumMap, json['classification']), - )..$type = json['__type'] as String?; + Map json, +) => ElectrocardiogramHealthValue( + voltageValues: (json['voltageValues'] as List) + .map( + (e) => + ElectrocardiogramVoltageValue.fromJson(e as Map), + ) + .toList(), + averageHeartRate: json['averageHeartRate'] as num?, + samplingFrequency: (json['samplingFrequency'] as num?)?.toDouble(), + classification: $enumDecodeNullable( + _$ElectrocardiogramClassificationEnumMap, + json['classification'], + ), +)..$type = json['__type'] as String?; Map _$ElectrocardiogramHealthValueToJson( - ElectrocardiogramHealthValue instance) => - { - if (instance.$type case final value?) '__type': value, - 'voltageValues': instance.voltageValues.map((e) => e.toJson()).toList(), - if (instance.averageHeartRate case final value?) - 'averageHeartRate': value, - if (instance.samplingFrequency case final value?) - 'samplingFrequency': value, - if (_$ElectrocardiogramClassificationEnumMap[instance.classification] - case final value?) - 'classification': value, - }; + ElectrocardiogramHealthValue instance, +) => { + '__type': ?instance.$type, + 'voltageValues': instance.voltageValues.map((e) => e.toJson()).toList(), + 'averageHeartRate': ?instance.averageHeartRate, + 'samplingFrequency': ?instance.samplingFrequency, + 'classification': + ?_$ElectrocardiogramClassificationEnumMap[instance.classification], +}; const _$ElectrocardiogramClassificationEnumMap = { ElectrocardiogramClassification.NOT_SET: 'NOT_SET', @@ -446,34 +450,34 @@ const _$ElectrocardiogramClassificationEnumMap = { }; ElectrocardiogramVoltageValue _$ElectrocardiogramVoltageValueFromJson( - Map json) => - ElectrocardiogramVoltageValue( - voltage: json['voltage'] as num, - timeSinceSampleStart: json['timeSinceSampleStart'] as num, - )..$type = json['__type'] as String?; + Map json, +) => ElectrocardiogramVoltageValue( + voltage: json['voltage'] as num, + timeSinceSampleStart: json['timeSinceSampleStart'] as num, +)..$type = json['__type'] as String?; Map _$ElectrocardiogramVoltageValueToJson( - ElectrocardiogramVoltageValue instance) => - { - if (instance.$type case final value?) '__type': value, - 'voltage': instance.voltage, - 'timeSinceSampleStart': instance.timeSinceSampleStart, - }; + ElectrocardiogramVoltageValue instance, +) => { + '__type': ?instance.$type, + 'voltage': instance.voltage, + 'timeSinceSampleStart': instance.timeSinceSampleStart, +}; InsulinDeliveryHealthValue _$InsulinDeliveryHealthValueFromJson( - Map json) => - InsulinDeliveryHealthValue( - units: (json['units'] as num).toDouble(), - reason: $enumDecode(_$InsulinDeliveryReasonEnumMap, json['reason']), - )..$type = json['__type'] as String?; + Map json, +) => InsulinDeliveryHealthValue( + units: (json['units'] as num).toDouble(), + reason: $enumDecode(_$InsulinDeliveryReasonEnumMap, json['reason']), +)..$type = json['__type'] as String?; Map _$InsulinDeliveryHealthValueToJson( - InsulinDeliveryHealthValue instance) => - { - if (instance.$type case final value?) '__type': value, - 'units': instance.units, - 'reason': _$InsulinDeliveryReasonEnumMap[instance.reason]!, - }; + InsulinDeliveryHealthValue instance, +) => { + '__type': ?instance.$type, + 'units': instance.units, + 'reason': _$InsulinDeliveryReasonEnumMap[instance.reason]!, +}; const _$InsulinDeliveryReasonEnumMap = { InsulinDeliveryReason.NOT_SET: 'NOT_SET', @@ -482,127 +486,122 @@ const _$InsulinDeliveryReasonEnumMap = { }; NutritionHealthValue _$NutritionHealthValueFromJson( - Map json) => - NutritionHealthValue( - name: json['name'] as String?, - mealType: json['meal_type'] as String?, - calories: (json['calories'] as num?)?.toDouble(), - protein: (json['protein'] as num?)?.toDouble(), - fat: (json['fat'] as num?)?.toDouble(), - carbs: (json['carbs'] as num?)?.toDouble(), - caffeine: (json['caffeine'] as num?)?.toDouble(), - vitaminA: (json['vitamin_a'] as num?)?.toDouble(), - b1Thiamine: (json['b1_thiamine'] as num?)?.toDouble(), - b2Riboflavin: (json['b2_riboflavin'] as num?)?.toDouble(), - b3Niacin: (json['b3_niacin'] as num?)?.toDouble(), - b5PantothenicAcid: (json['b5_pantothenic_acid'] as num?)?.toDouble(), - b6Pyridoxine: (json['b6_pyridoxine'] as num?)?.toDouble(), - b7Biotin: (json['b7_biotin'] as num?)?.toDouble(), - b9Folate: (json['b9_folate'] as num?)?.toDouble(), - b12Cobalamin: (json['b12_cobalamin'] as num?)?.toDouble(), - vitaminC: (json['vitamin_c'] as num?)?.toDouble(), - vitaminD: (json['vitamin_d'] as num?)?.toDouble(), - vitaminE: (json['vitamin_e'] as num?)?.toDouble(), - vitaminK: (json['vitamin_k'] as num?)?.toDouble(), - calcium: (json['calcium'] as num?)?.toDouble(), - chloride: (json['chloride'] as num?)?.toDouble(), - cholesterol: (json['cholesterol'] as num?)?.toDouble(), - choline: (json['choline'] as num?)?.toDouble(), - chromium: (json['chromium'] as num?)?.toDouble(), - copper: (json['copper'] as num?)?.toDouble(), - fatUnsaturated: (json['fat_unsaturated'] as num?)?.toDouble(), - fatMonounsaturated: (json['fat_monounsaturated'] as num?)?.toDouble(), - fatPolyunsaturated: (json['fat_polyunsaturated'] as num?)?.toDouble(), - fatSaturated: (json['fat_saturated'] as num?)?.toDouble(), - fatTransMonoenoic: (json['fat_trans_monoenoic'] as num?)?.toDouble(), - fiber: (json['fiber'] as num?)?.toDouble(), - iodine: (json['iodine'] as num?)?.toDouble(), - iron: (json['iron'] as num?)?.toDouble(), - magnesium: (json['magnesium'] as num?)?.toDouble(), - manganese: (json['manganese'] as num?)?.toDouble(), - molybdenum: (json['molybdenum'] as num?)?.toDouble(), - phosphorus: (json['phosphorus'] as num?)?.toDouble(), - potassium: (json['potassium'] as num?)?.toDouble(), - selenium: (json['selenium'] as num?)?.toDouble(), - sodium: (json['sodium'] as num?)?.toDouble(), - sugar: (json['sugar'] as num?)?.toDouble(), - water: (json['water'] as num?)?.toDouble(), - zinc: (json['zinc'] as num?)?.toDouble(), - )..$type = json['__type'] as String?; + Map json, +) => NutritionHealthValue( + name: json['name'] as String?, + mealType: json['meal_type'] as String?, + calories: (json['calories'] as num?)?.toDouble(), + protein: (json['protein'] as num?)?.toDouble(), + fat: (json['fat'] as num?)?.toDouble(), + carbs: (json['carbs'] as num?)?.toDouble(), + caffeine: (json['caffeine'] as num?)?.toDouble(), + vitaminA: (json['vitamin_a'] as num?)?.toDouble(), + b1Thiamine: (json['b1_thiamine'] as num?)?.toDouble(), + b2Riboflavin: (json['b2_riboflavin'] as num?)?.toDouble(), + b3Niacin: (json['b3_niacin'] as num?)?.toDouble(), + b5PantothenicAcid: (json['b5_pantothenic_acid'] as num?)?.toDouble(), + b6Pyridoxine: (json['b6_pyridoxine'] as num?)?.toDouble(), + b7Biotin: (json['b7_biotin'] as num?)?.toDouble(), + b9Folate: (json['b9_folate'] as num?)?.toDouble(), + b12Cobalamin: (json['b12_cobalamin'] as num?)?.toDouble(), + vitaminC: (json['vitamin_c'] as num?)?.toDouble(), + vitaminD: (json['vitamin_d'] as num?)?.toDouble(), + vitaminE: (json['vitamin_e'] as num?)?.toDouble(), + vitaminK: (json['vitamin_k'] as num?)?.toDouble(), + calcium: (json['calcium'] as num?)?.toDouble(), + chloride: (json['chloride'] as num?)?.toDouble(), + cholesterol: (json['cholesterol'] as num?)?.toDouble(), + choline: (json['choline'] as num?)?.toDouble(), + chromium: (json['chromium'] as num?)?.toDouble(), + copper: (json['copper'] as num?)?.toDouble(), + fatUnsaturated: (json['fat_unsaturated'] as num?)?.toDouble(), + fatMonounsaturated: (json['fat_monounsaturated'] as num?)?.toDouble(), + fatPolyunsaturated: (json['fat_polyunsaturated'] as num?)?.toDouble(), + fatSaturated: (json['fat_saturated'] as num?)?.toDouble(), + fatTransMonoenoic: (json['fat_trans_monoenoic'] as num?)?.toDouble(), + fiber: (json['fiber'] as num?)?.toDouble(), + iodine: (json['iodine'] as num?)?.toDouble(), + iron: (json['iron'] as num?)?.toDouble(), + magnesium: (json['magnesium'] as num?)?.toDouble(), + manganese: (json['manganese'] as num?)?.toDouble(), + molybdenum: (json['molybdenum'] as num?)?.toDouble(), + phosphorus: (json['phosphorus'] as num?)?.toDouble(), + potassium: (json['potassium'] as num?)?.toDouble(), + selenium: (json['selenium'] as num?)?.toDouble(), + sodium: (json['sodium'] as num?)?.toDouble(), + sugar: (json['sugar'] as num?)?.toDouble(), + water: (json['water'] as num?)?.toDouble(), + zinc: (json['zinc'] as num?)?.toDouble(), +)..$type = json['__type'] as String?; Map _$NutritionHealthValueToJson( - NutritionHealthValue instance) => - { - if (instance.$type case final value?) '__type': value, - if (instance.name case final value?) 'name': value, - if (instance.mealType case final value?) 'meal_type': value, - if (instance.calories case final value?) 'calories': value, - if (instance.protein case final value?) 'protein': value, - if (instance.fat case final value?) 'fat': value, - if (instance.carbs case final value?) 'carbs': value, - if (instance.caffeine case final value?) 'caffeine': value, - if (instance.vitaminA case final value?) 'vitamin_a': value, - if (instance.b1Thiamine case final value?) 'b1_thiamine': value, - if (instance.b2Riboflavin case final value?) 'b2_riboflavin': value, - if (instance.b3Niacin case final value?) 'b3_niacin': value, - if (instance.b5PantothenicAcid case final value?) - 'b5_pantothenic_acid': value, - if (instance.b6Pyridoxine case final value?) 'b6_pyridoxine': value, - if (instance.b7Biotin case final value?) 'b7_biotin': value, - if (instance.b9Folate case final value?) 'b9_folate': value, - if (instance.b12Cobalamin case final value?) 'b12_cobalamin': value, - if (instance.vitaminC case final value?) 'vitamin_c': value, - if (instance.vitaminD case final value?) 'vitamin_d': value, - if (instance.vitaminE case final value?) 'vitamin_e': value, - if (instance.vitaminK case final value?) 'vitamin_k': value, - if (instance.calcium case final value?) 'calcium': value, - if (instance.chloride case final value?) 'chloride': value, - if (instance.cholesterol case final value?) 'cholesterol': value, - if (instance.choline case final value?) 'choline': value, - if (instance.chromium case final value?) 'chromium': value, - if (instance.copper case final value?) 'copper': value, - if (instance.fatUnsaturated case final value?) 'fat_unsaturated': value, - if (instance.fatMonounsaturated case final value?) - 'fat_monounsaturated': value, - if (instance.fatPolyunsaturated case final value?) - 'fat_polyunsaturated': value, - if (instance.fatSaturated case final value?) 'fat_saturated': value, - if (instance.fatTransMonoenoic case final value?) - 'fat_trans_monoenoic': value, - if (instance.fiber case final value?) 'fiber': value, - if (instance.iodine case final value?) 'iodine': value, - if (instance.iron case final value?) 'iron': value, - if (instance.magnesium case final value?) 'magnesium': value, - if (instance.manganese case final value?) 'manganese': value, - if (instance.molybdenum case final value?) 'molybdenum': value, - if (instance.phosphorus case final value?) 'phosphorus': value, - if (instance.potassium case final value?) 'potassium': value, - if (instance.selenium case final value?) 'selenium': value, - if (instance.sodium case final value?) 'sodium': value, - if (instance.sugar case final value?) 'sugar': value, - if (instance.water case final value?) 'water': value, - if (instance.zinc case final value?) 'zinc': value, - }; + NutritionHealthValue instance, +) => { + '__type': ?instance.$type, + 'name': ?instance.name, + 'meal_type': ?instance.mealType, + 'calories': ?instance.calories, + 'protein': ?instance.protein, + 'fat': ?instance.fat, + 'carbs': ?instance.carbs, + 'caffeine': ?instance.caffeine, + 'vitamin_a': ?instance.vitaminA, + 'b1_thiamine': ?instance.b1Thiamine, + 'b2_riboflavin': ?instance.b2Riboflavin, + 'b3_niacin': ?instance.b3Niacin, + 'b5_pantothenic_acid': ?instance.b5PantothenicAcid, + 'b6_pyridoxine': ?instance.b6Pyridoxine, + 'b7_biotin': ?instance.b7Biotin, + 'b9_folate': ?instance.b9Folate, + 'b12_cobalamin': ?instance.b12Cobalamin, + 'vitamin_c': ?instance.vitaminC, + 'vitamin_d': ?instance.vitaminD, + 'vitamin_e': ?instance.vitaminE, + 'vitamin_k': ?instance.vitaminK, + 'calcium': ?instance.calcium, + 'chloride': ?instance.chloride, + 'cholesterol': ?instance.cholesterol, + 'choline': ?instance.choline, + 'chromium': ?instance.chromium, + 'copper': ?instance.copper, + 'fat_unsaturated': ?instance.fatUnsaturated, + 'fat_monounsaturated': ?instance.fatMonounsaturated, + 'fat_polyunsaturated': ?instance.fatPolyunsaturated, + 'fat_saturated': ?instance.fatSaturated, + 'fat_trans_monoenoic': ?instance.fatTransMonoenoic, + 'fiber': ?instance.fiber, + 'iodine': ?instance.iodine, + 'iron': ?instance.iron, + 'magnesium': ?instance.magnesium, + 'manganese': ?instance.manganese, + 'molybdenum': ?instance.molybdenum, + 'phosphorus': ?instance.phosphorus, + 'potassium': ?instance.potassium, + 'selenium': ?instance.selenium, + 'sodium': ?instance.sodium, + 'sugar': ?instance.sugar, + 'water': ?instance.water, + 'zinc': ?instance.zinc, +}; MenstruationFlowHealthValue _$MenstruationFlowHealthValueFromJson( - Map json) => - MenstruationFlowHealthValue( - flow: $enumDecodeNullable(_$MenstrualFlowEnumMap, json['flow']), - dateTime: DateTime.parse(json['dateTime'] as String), - isStartOfCycle: json['isStartOfCycle'] as bool?, - wasUserEntered: json['wasUserEntered'] as bool?, - )..$type = json['__type'] as String?; + Map json, +) => MenstruationFlowHealthValue( + flow: $enumDecodeNullable(_$MenstrualFlowEnumMap, json['flow']), + dateTime: DateTime.parse(json['dateTime'] as String), + isStartOfCycle: json['isStartOfCycle'] as bool?, + wasUserEntered: json['wasUserEntered'] as bool?, +)..$type = json['__type'] as String?; Map _$MenstruationFlowHealthValueToJson( - MenstruationFlowHealthValue instance) => - { - if (instance.$type case final value?) '__type': value, - if (_$MenstrualFlowEnumMap[instance.flow] case final value?) - 'flow': value, - if (instance.isStartOfCycle case final value?) 'isStartOfCycle': value, - if (instance.wasUserEntered case final value?) 'wasUserEntered': value, - 'dateTime': instance.dateTime.toIso8601String(), - }; + MenstruationFlowHealthValue instance, +) => { + '__type': ?instance.$type, + 'flow': ?_$MenstrualFlowEnumMap[instance.flow], + 'isStartOfCycle': ?instance.isStartOfCycle, + 'wasUserEntered': ?instance.wasUserEntered, + 'dateTime': instance.dateTime.toIso8601String(), +}; const _$MenstrualFlowEnumMap = { MenstrualFlow.unspecified: 'unspecified', diff --git a/packages/health/lib/src/health_value_types.dart b/packages/health/lib/src/health_value_types.dart index 6a8b28819..fd3b8c0aa 100644 --- a/packages/health/lib/src/health_value_types.dart +++ b/packages/health/lib/src/health_value_types.dart @@ -73,14 +73,18 @@ class AudiogramHealthValue extends HealthValue { /// Create a [AudiogramHealthValue] based on a health data point from native data format. factory AudiogramHealthValue.fromHealthDataPoint(dynamic dataPoint) => AudiogramHealthValue( - frequencies: List.from(dataPoint['frequencies'] as List), - leftEarSensitivities: - List.from(dataPoint['leftEarSensitivities'] as List), - rightEarSensitivities: - List.from(dataPoint['rightEarSensitivities'] as List)); + frequencies: List.from(dataPoint['frequencies'] as List), + leftEarSensitivities: List.from( + dataPoint['leftEarSensitivities'] as List, + ), + rightEarSensitivities: List.from( + dataPoint['rightEarSensitivities'] as List, + ), + ); @override - String toString() => """$runtimeType - frequencies: ${frequencies.toString()}, + String toString() => + """$runtimeType - frequencies: ${frequencies.toString()}, left ear sensitivities: ${leftEarSensitivities.toString()}, right ear sensitivities: ${rightEarSensitivities.toString()}"""; @@ -140,43 +144,48 @@ class WorkoutHealthValue extends HealthValue { /// Might not be available for all workouts. HealthDataUnit? totalStepsUnit; - WorkoutHealthValue( - {required this.workoutActivityType, - this.totalEnergyBurned, - this.totalEnergyBurnedUnit, - this.totalDistance, - this.totalDistanceUnit, - this.totalSteps, - this.totalStepsUnit}); + WorkoutHealthValue({ + required this.workoutActivityType, + this.totalEnergyBurned, + this.totalEnergyBurnedUnit, + this.totalDistance, + this.totalDistanceUnit, + this.totalSteps, + this.totalStepsUnit, + }); /// Create a [WorkoutHealthValue] based on a health data point from native data format. factory WorkoutHealthValue.fromHealthDataPoint(dynamic dataPoint) => WorkoutHealthValue( - workoutActivityType: HealthWorkoutActivityType.values.firstWhere( - (element) => element.name == dataPoint['workoutActivityType'], - orElse: () => HealthWorkoutActivityType.OTHER, - ), - totalEnergyBurned: dataPoint['totalEnergyBurned'] != null - ? (dataPoint['totalEnergyBurned'] as num).toInt() - : null, - totalEnergyBurnedUnit: dataPoint['totalEnergyBurnedUnit'] != null - ? HealthDataUnit.values.firstWhere((element) => - element.name == dataPoint['totalEnergyBurnedUnit']) - : null, - totalDistance: dataPoint['totalDistance'] != null - ? (dataPoint['totalDistance'] as num).toInt() - : null, - totalDistanceUnit: dataPoint['totalDistanceUnit'] != null - ? HealthDataUnit.values.firstWhere( - (element) => element.name == dataPoint['totalDistanceUnit']) - : null, - totalSteps: dataPoint['totalSteps'] != null - ? (dataPoint['totalSteps'] as num).toInt() - : null, - totalStepsUnit: dataPoint['totalStepsUnit'] != null - ? HealthDataUnit.values.firstWhere( - (element) => element.name == dataPoint['totalStepsUnit']) - : null); + workoutActivityType: HealthWorkoutActivityType.values.firstWhere( + (element) => element.name == dataPoint['workoutActivityType'], + orElse: () => HealthWorkoutActivityType.OTHER, + ), + totalEnergyBurned: dataPoint['totalEnergyBurned'] != null + ? (dataPoint['totalEnergyBurned'] as num).toInt() + : null, + totalEnergyBurnedUnit: dataPoint['totalEnergyBurnedUnit'] != null + ? HealthDataUnit.values.firstWhere( + (element) => element.name == dataPoint['totalEnergyBurnedUnit'], + ) + : null, + totalDistance: dataPoint['totalDistance'] != null + ? (dataPoint['totalDistance'] as num).toInt() + : null, + totalDistanceUnit: dataPoint['totalDistanceUnit'] != null + ? HealthDataUnit.values.firstWhere( + (element) => element.name == dataPoint['totalDistanceUnit'], + ) + : null, + totalSteps: dataPoint['totalSteps'] != null + ? (dataPoint['totalSteps'] as num).toInt() + : null, + totalStepsUnit: dataPoint['totalStepsUnit'] != null + ? HealthDataUnit.values.firstWhere( + (element) => element.name == dataPoint['totalStepsUnit'], + ) + : null, + ); @override Function get fromJsonFunction => _$WorkoutHealthValueFromJson; @@ -208,13 +217,14 @@ class WorkoutHealthValue extends HealthValue { @override int get hashCode => Object.hash( - workoutActivityType, - totalEnergyBurned, - totalEnergyBurnedUnit, - totalDistance, - totalDistanceUnit, - totalSteps, - totalStepsUnit); + workoutActivityType, + totalEnergyBurned, + totalEnergyBurnedUnit, + totalDistance, + totalDistanceUnit, + totalSteps, + totalStepsUnit, + ); } /// A [HealthValue] object for ECGs @@ -256,13 +266,18 @@ class ElectrocardiogramHealthValue extends HealthValue { factory ElectrocardiogramHealthValue.fromHealthDataPoint(dynamic dataPoint) => ElectrocardiogramHealthValue( voltageValues: (dataPoint['voltageValues'] as List) - .map((voltageValue) => - ElectrocardiogramVoltageValue.fromHealthDataPoint(voltageValue)) + .map( + (voltageValue) => + ElectrocardiogramVoltageValue.fromHealthDataPoint( + voltageValue, + ), + ) .toList(), averageHeartRate: dataPoint['averageHeartRate'] as num?, samplingFrequency: dataPoint['samplingFrequency'] as double?, - classification: ElectrocardiogramClassification.values - .firstWhere((c) => c.value == dataPoint['classification']), + classification: ElectrocardiogramClassification.values.firstWhere( + (c) => c.value == dataPoint['classification'], + ), ); @override @@ -275,7 +290,11 @@ class ElectrocardiogramHealthValue extends HealthValue { @override int get hashCode => Object.hash( - voltageValues, averageHeartRate, samplingFrequency, classification); + voltageValues, + averageHeartRate, + samplingFrequency, + classification, + ); @override String toString() => @@ -298,10 +317,11 @@ class ElectrocardiogramVoltageValue extends HealthValue { /// Create a [ElectrocardiogramVoltageValue] based on a health data point from native data format. factory ElectrocardiogramVoltageValue.fromHealthDataPoint( - dynamic dataPoint) => - ElectrocardiogramVoltageValue( - voltage: dataPoint['voltage'] as num, - timeSinceSampleStart: dataPoint['timeSinceSampleStart'] as num); + dynamic dataPoint, + ) => ElectrocardiogramVoltageValue( + voltage: dataPoint['voltage'] as num, + timeSinceSampleStart: dataPoint['timeSinceSampleStart'] as num, + ); @override Function get fromJsonFunction => _$ElectrocardiogramVoltageValueFromJson; @@ -332,10 +352,7 @@ class InsulinDeliveryHealthValue extends HealthValue { /// If it's basal, bolus or unknown reason for insulin dosage InsulinDeliveryReason reason; - InsulinDeliveryHealthValue({ - required this.units, - required this.reason, - }); + InsulinDeliveryHealthValue({required this.units, required this.reason}); factory InsulinDeliveryHealthValue.fromHealthDataPoint(dynamic dataPoint) { final units = dataPoint['value'] as num; @@ -345,8 +362,8 @@ class InsulinDeliveryHealthValue extends HealthValue { : Map.from(dataPoint['metadata'] as Map); final reasonIndex = metadata == null || !metadata.containsKey('HKInsulinDeliveryReason') - ? 0 - : metadata['HKInsulinDeliveryReason'] as double; + ? 0 + : metadata['HKInsulinDeliveryReason'] as double; final reason = InsulinDeliveryReason.values[reasonIndex.toInt()]; return InsulinDeliveryHealthValue(units: units.toDouble(), reason: reason); @@ -632,19 +649,20 @@ class NutritionHealthValue extends HealthValue { dataPoint = dataPoint as Map; // Convert to Map and ensure all expected fields are present final Map dataPointMap = {}; - + // Add all entries from the native data dataPoint.forEach((key, value) { if (key != null) { dataPointMap[key as String] = value; } }); - + return _$NutritionHealthValueFromJson(dataPointMap); } @override - String toString() => """$runtimeType - protein: ${protein.toString()}, + String toString() => + """$runtimeType - protein: ${protein.toString()}, calories: ${calories.toString()}, fat: ${fat.toString()}, name: ${name.toString()}, @@ -739,50 +757,50 @@ class NutritionHealthValue extends HealthValue { @override int get hashCode => Object.hashAll([ - protein, - calories, - fat, - name, - carbs, - caffeine, - vitaminA, - b1Thiamine, - b2Riboflavin, - b3Niacin, - b5PantothenicAcid, - b6Pyridoxine, - b7Biotin, - b9Folate, - b12Cobalamin, - vitaminC, - vitaminD, - vitaminE, - vitaminK, - calcium, - chloride, - cholesterol, - choline, - chromium, - copper, - fatUnsaturated, - fatMonounsaturated, - fatPolyunsaturated, - fatSaturated, - fatTransMonoenoic, - fiber, - iodine, - iron, - magnesium, - manganese, - molybdenum, - phosphorus, - potassium, - selenium, - sodium, - sugar, - water, - zinc, - ]); + protein, + calories, + fat, + name, + carbs, + caffeine, + vitaminA, + b1Thiamine, + b2Riboflavin, + b3Niacin, + b5PantothenicAcid, + b6Pyridoxine, + b7Biotin, + b9Folate, + b12Cobalamin, + vitaminC, + vitaminD, + vitaminE, + vitaminK, + calcium, + chloride, + cholesterol, + choline, + chromium, + copper, + fatUnsaturated, + fatMonounsaturated, + fatPolyunsaturated, + fatSaturated, + fatTransMonoenoic, + fiber, + iodine, + iron, + magnesium, + manganese, + molybdenum, + phosphorus, + potassium, + selenium, + sodium, + sugar, + water, + zinc, + ]); } enum MenstrualFlow { @@ -920,14 +938,15 @@ class MenstruationFlowHealthValue extends HealthValue { flow: menstrualFlow, isStartOfCycle: dataPoint['metadata']?.containsKey('HKMenstrualCycleStart') == true - ? dataPoint['metadata']['HKMenstrualCycleStart'] == 1.0 - : null, + ? dataPoint['metadata']['HKMenstrualCycleStart'] == 1.0 + : null, wasUserEntered: dataPoint['metadata']?.containsKey('HKWasUserEntered') == true - ? dataPoint['metadata']['HKWasUserEntered'] == 1.0 - : null, - dateTime: - DateTime.fromMillisecondsSinceEpoch(dataPoint['date_from'] as int), + ? dataPoint['metadata']['HKWasUserEntered'] == 1.0 + : null, + dateTime: DateTime.fromMillisecondsSinceEpoch( + dataPoint['date_from'] as int, + ), ); } diff --git a/packages/health/pubspec.yaml b/packages/health/pubspec.yaml index 91aeade3a..90d81da50 100644 --- a/packages/health/pubspec.yaml +++ b/packages/health/pubspec.yaml @@ -1,10 +1,10 @@ name: health description: Wrapper for Apple's HealthKit on iOS and Google's Health Connect on Android. -version: 13.1.3 +version: 13.1.4 homepage: https://github.com/cph-cachet/flutter-plugins/tree/master/packages/health environment: - sdk: ">=3.2.0 <4.0.0" + sdk: ">=3.8.0 <4.0.0" flutter: ">=3.6.0" dependencies: