From 988cce86e3067fb7aa90196808113cd4aec0ea2e Mon Sep 17 00:00:00 2001 From: bernd70 Date: Sat, 28 Dec 2024 01:02:08 +0100 Subject: [PATCH 1/2] Deleting entries only selects own entries When deleting entries on iOS a compound predicate is used to limit the query to entries the calling app has created. Otherwise the call fails is the sample contains entries from other apps. --- .../ios/Classes/SwiftHealthPlugin.swift | 303 +++++++++--------- 1 file changed, 153 insertions(+), 150 deletions(-) diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift index d7af83e05..fb9e1fe49 100644 --- a/packages/health/ios/Classes/SwiftHealthPlugin.swift +++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift @@ -10,7 +10,7 @@ enum RecordingMethod: Int { } public class SwiftHealthPlugin: NSObject, FlutterPlugin { - + let healthStore = HKHealthStore() var healthDataTypes = [HKSampleType]() var healthDataQuantityTypes = [HKQuantityType]() @@ -24,7 +24,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { var workoutActivityTypeMap: [String: HKWorkoutActivityType] = [:] var characteristicsTypesDict: [String: HKCharacteristicType] = [:] var nutritionList: [String] = [] - + // Health Data Type Keys let ACTIVE_ENERGY_BURNED = "ACTIVE_ENERGY_BURNED" let ATRIAL_FIBRILLATION_BURDEN = "ATRIAL_FIBRILLATION_BURDEN" @@ -147,7 +147,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let SLEEP_IN_BED = "SLEEP_IN_BED" let SLEEP_LIGHT = "SLEEP_LIGHT" let SLEEP_REM = "SLEEP_REM" - + let EXERCISE_TIME = "EXERCISE_TIME" let WORKOUT = "WORKOUT" let HEADACHE_UNSPECIFIED = "HEADACHE_UNSPECIFIED" @@ -161,8 +161,8 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let GENDER = "GENDER" let BLOOD_TYPE = "BLOOD_TYPE" let MENSTRUATION_FLOW = "MENSTRUATION_FLOW" - - + + // Health Unit types // MOLE_UNIT_WITH_MOLAR_MASS, // requires molar mass input - not supported yet // MOLE_UNIT_WITH_PREFIX_MOLAR_MASS, // requires molar mass & prefix input - not supported yet @@ -214,22 +214,22 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let MILLIGRAM_PER_DECILITER = "MILLIGRAM_PER_DECILITER" let UNKNOWN_UNIT = "UNKNOWN_UNIT" let NO_UNIT = "NO_UNIT" - + struct PluginError: Error { let message: String } - + public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel( name: "flutter_health", binaryMessenger: registrar.messenger()) let instance = SwiftHealthPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } - + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { // Set up all data types initializeTypes() - + /// Handle checkIfHealthDataAvailable if call.method.elementsEqual("checkIfHealthDataAvailable") { checkIfHealthDataAvailable(call: call, result: result) @@ -237,72 +237,72 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else if call.method.elementsEqual("requestAuthorization") { try! requestAuthorization(call: call, result: result) } - + /// Handle getData else if call.method.elementsEqual("getData") { getData(call: call, result: result) } - + /// Handle getIntervalData else if (call.method.elementsEqual("getIntervalData")){ getIntervalData(call: call, result: result) } - + /// Handle getTotalStepsInInterval else if call.method.elementsEqual("getTotalStepsInInterval") { getTotalStepsInInterval(call: call, result: result) } - + /// Handle writeData else if call.method.elementsEqual("writeData") { try! writeData(call: call, result: result) } - + /// Handle writeAudiogram else if call.method.elementsEqual("writeAudiogram") { try! writeAudiogram(call: call, result: result) } - + /// Handle writeBloodPressure else if call.method.elementsEqual("writeBloodPressure") { try! writeBloodPressure(call: call, result: result) } - + /// Handle writeMeal else if (call.method.elementsEqual("writeMeal")){ try! writeMeal(call: call, result: result) } - + /// Handle writeInsulinDelivery else if (call.method.elementsEqual("writeInsulinDelivery")){ try! writeInsulinDelivery(call: call, result: result) } - + /// Handle writeWorkoutData else if call.method.elementsEqual("writeWorkoutData") { try! writeWorkoutData(call: call, result: result) } - + /// Handle writeMenstruationFlow else if call.method.elementsEqual("writeMenstruationFlow") { try! writeMenstruationFlow(call: call, result: result) } - + /// Handle hasPermission else if call.method.elementsEqual("hasPermissions") { try! hasPermissions(call: call, result: result) } - + /// Handle delete data else if call.method.elementsEqual("delete") { try! delete(call: call, result: result) } } - + func checkIfHealthDataAvailable(call: FlutterMethodCall, result: @escaping FlutterResult) { result(HKHealthStore.isHealthDataAvailable()) } - + func hasPermissions(call: FlutterMethodCall, result: @escaping FlutterResult) throws { let arguments = call.arguments as? NSDictionary guard var types = arguments?["types"] as? [String], @@ -311,18 +311,18 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { throw PluginError(message: "Invalid Arguments!") } - + if let nutritionIndex = types.firstIndex(of: NUTRITION) { types.remove(at: nutritionIndex) let nutritionPermission = permissions[nutritionIndex] permissions.remove(at: nutritionIndex) - + for nutritionType in nutritionList { types.append(nutritionType) permissions.append(nutritionPermission) } } - + for (index, type) in types.enumerated() { let sampleType = dataTypeLookUp(key: type) let success = hasPermission(type: sampleType, access: permissions[index]) @@ -338,12 +338,12 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } } } - + result(false) } - + func hasPermission(type: HKObjectType, access: Int) -> Bool? { - + if #available(iOS 13.0, *) { let status = healthStore.authorizationStatus(for: type) switch access { @@ -358,7 +358,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { return nil } } - + func requestAuthorization(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let types = arguments["types"] as? [String], @@ -367,7 +367,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { throw PluginError(message: "Invalid Arguments!") } - + var typesToRead = Set() var typesToWrite = Set() for (index, key) in types.enumerated() { @@ -401,7 +401,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } } } - + if #available(iOS 13.0, *) { healthStore.requestAuthorization(toShare: typesToWrite, read: typesToRead) { (success, error) in @@ -413,7 +413,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { result(false) // Handle the error here. } } - + func writeData(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let value = (arguments["value"] as? Double), @@ -425,7 +425,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { throw PluginError(message: "Invalid Arguments") } - + let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) @@ -433,9 +433,9 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let metadata: [String: Any] = [ HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry) ] - + let sample: HKObject - + if dataTypeLookUp(key: type).isKind(of: HKCategoryType.self) { sample = HKCategorySample( type: dataTypeLookUp(key: type) as! HKCategoryType, value: Int(value), start: dateFrom, @@ -446,7 +446,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { type: dataTypeLookUp(key: type) as! HKQuantityType, quantity: quantity, start: dateFrom, end: dateTo, metadata: metadata) } - + HKHealthStore().save( sample, withCompletion: { (success, error) in @@ -458,7 +458,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func writeAudiogram(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let frequencies = (arguments["frequencies"] as? [Double]), @@ -469,12 +469,12 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { throw PluginError(message: "Invalid Arguments") } - + let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + var sensitivityPoints = [HKAudiogramSensitivityPoint]() - + for index in 0...frequencies.count - 1 { let frequency = HKQuantity(unit: HKUnit.hertz(), doubleValue: frequencies[index]) let dbUnit = HKUnit.decibelHearingLevel() @@ -484,23 +484,23 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { 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]) - + } else { audiogram = HKAudiogramSample( sensitivityPoints: sensitivityPoints, start: dateFrom, end: dateTo, metadata: nil) } - + HKHealthStore().save( audiogram, withCompletion: { (success, error) in @@ -512,7 +512,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func writeBloodPressure(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let systolic = (arguments["systolic"] as? Double), @@ -530,7 +530,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let metadata = [ HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry) ] - + let systolic_sample = HKQuantitySample( type: HKSampleType.quantityType(forIdentifier: .bloodPressureSystolic)!, quantity: HKQuantity(unit: HKUnit.millimeterOfMercury(), doubleValue: systolic), @@ -542,7 +542,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { 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) - + HKHealthStore().save( [blood_pressure_sample], withCompletion: { (success, error) in @@ -554,7 +554,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func writeMeal(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let name = (arguments["name"] as? String?), @@ -565,14 +565,14 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { throw PluginError(message: "Invalid Arguments") } - + let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + let mealTypeString = mealType ?? "UNKNOWN" let isManualEntry = recordingMethod == RecordingMethod.manual.rawValue - + var metadata = ["HKFoodMeal": mealTypeString, HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry)] as [String : Any] if (name != nil) { metadata[HKMetadataKeyFoodType] = "\(name!)" @@ -587,11 +587,11 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { 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) - + HKHealthStore().save(meal, withCompletion: { (success, error) in if let err = error { print("Error Saving Meal Sample: \(err.localizedDescription)") @@ -615,13 +615,13 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + 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) - + HKHealthStore().save(insulin_sample, withCompletion: { (success, error) in if let err = error { print("Error Saving Insulin Delivery Sample: \(err.localizedDescription)") @@ -631,7 +631,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func writeMenstruationFlow(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let flow = (arguments["value"] as? Int), @@ -644,7 +644,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { guard let menstrualFlowType = HKCategoryValueMenstrualFlow(rawValue: flow) else { throw PluginError(message: "Invalid Menstrual Flow Type") } - + let dateTime = Date(timeIntervalSince1970: endTime.doubleValue / 1000) let isManualEntry = recordingMethod == RecordingMethod.manual.rawValue @@ -654,15 +654,15 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } 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 ) - + HKHealthStore().save( sample, withCompletion: { (success, error) in @@ -674,7 +674,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func writeWorkoutData(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let activityType = (arguments["activityType"] as? String), @@ -684,10 +684,10 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { 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( @@ -697,17 +697,17 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { totalDistance = HKQuantity( unit: unitDict[(arguments["totalDistanceUnit"] as! String)]!, doubleValue: td) } - + let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + var workout: HKWorkout - + workout = HKWorkout( activityType: ac, start: dateFrom, end: dateTo, duration: dateTo.timeIntervalSince(dateFrom), totalEnergyBurned: totalEnergyBurned ?? nil, totalDistance: totalDistance ?? nil, metadata: nil) - + HKHealthStore().save( workout, withCompletion: { (success, error) in @@ -719,33 +719,36 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func delete(call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary let dataTypeKey = (arguments?["dataTypeKey"] as? String)! let startTime = (arguments?["startTime"] as? NSNumber) ?? 0 let endTime = (arguments?["endTime"] as? NSNumber) ?? 0 - + let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + let dataType = dataTypeLookUp(key: dataTypeKey) - - let predicate = HKQuery.predicateForSamples( + + let samplePredicate = HKQuery.predicateForSamples( withStart: dateFrom, end: dateTo, options: .strictStartDate) + let ownerPredicate = HKQuery.predicateForObjects(from: HKSource.default()) let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) - + let deleteQuery = HKSampleQuery( - sampleType: dataType, predicate: predicate, limit: HKObjectQueryNoLimit, + sampleType: dataType, + predicate: NSCompoundPredicate(andPredicateWithSubpredicates: [samplePredicate, ownerPredicate]), + limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor] ) { [self] x, samplesOrNil, error in - + guard let samplesOrNil = samplesOrNil, error == nil else { // Handle the error if necessary print("Error deleting \(dataType)") return } - + // Delete the retrieved objects from the HealthKit store HKHealthStore().delete(samplesOrNil) { (success, error) in if let err = error { @@ -756,10 +759,10 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } } } - + HKHealthStore().execute(deleteQuery) } - + func getData(call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary let dataTypeKey = (arguments?["dataTypeKey"] as? String)! @@ -769,20 +772,20 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let limit = (arguments?["limit"] as? Int) ?? HKObjectQueryNoLimit let recordingMethodsToFilter = (arguments?["recordingMethodsToFilter"] as? [Int]) ?? [] let includeManualEntry = !recordingMethodsToFilter.contains(RecordingMethod.manual.rawValue) - + // Convert dates from milliseconds to Date() let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + let dataType = dataTypeLookUp(key: dataTypeKey) var unit: HKUnit? if let dataUnitKey = dataUnitKey { unit = unitDict[dataUnitKey] } - + let sourceIdForCharacteristic = "com.apple.Health" let sourceNameForCharacteristic = "Health" - + switch(dataTypeKey) { case "BIRTH_DATE": let dateOfBirth = getBirthDate() @@ -826,7 +829,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { default: break } - + var predicate = HKQuery.predicateForSamples( withStart: dateFrom, end: dateTo, options: .strictStartDate) if (!includeManualEntry) { @@ -834,13 +837,13 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { predicate = NSCompoundPredicate(type: .and, subpredicates: [predicate, manualPredicate]) } let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) - + let query = HKSampleQuery( sampleType: dataType, predicate: predicate, limit: limit, sortDescriptors: [sortDescriptor] ) { [self] x, samplesOrNil, error in - + switch samplesOrNil { case let (samples as [HKQuantitySample]) as Any: let dictionaries = samples.map { sample -> NSDictionary in @@ -860,9 +863,9 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { DispatchQueue.main.async { result(dictionaries) } - + case var (samplesCategory as [HKCategorySample]) as Any: - + if dataTypeKey == self.SLEEP_IN_BED { samplesCategory = samplesCategory.filter { $0.value == 0 } } @@ -898,13 +901,13 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } let categories = samplesCategory.map { sample -> NSDictionary in var metadata: [String: Any] = [:] - + if let sampleMetadata = sample.metadata { for (key, value) in sampleMetadata { metadata[key] = value } } - + return [ "uuid": "\(sample.uuid)", "value": sample.value, @@ -919,9 +922,9 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { DispatchQueue.main.async { result(categories) } - + case let (samplesWorkout as [HKWorkout]) as Any: - + let dictionaries = samplesWorkout.map { sample -> NSDictionary in return [ "uuid": "\(sample.uuid)", @@ -942,11 +945,11 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { "total_energy_burned": sample.totalEnergyBurned != nil ? Int(sample.totalEnergyBurned!.doubleValue(for: HKUnit.kilocalorie())) : 0 ] } - + DispatchQueue.main.async { result(dictionaries) } - + case let (samplesAudiogram as [HKAudiogramSample]) as Any: let dictionaries = samplesAudiogram.map { sample -> NSDictionary in var frequencies = [Double]() @@ -973,7 +976,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { DispatchQueue.main.async { result(dictionaries) } - + case let (nutritionSample as [HKCorrelation]) as Any: var foods: [[String: Any?]] = [] for food in nutritionSample { @@ -1007,11 +1010,11 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { foods.append(sampleDict) } } - + DispatchQueue.main.async { result(foods) } - + default: if #available(iOS 14.0, *), let ecgSamples = samplesOrNil as? [HKElectrocardiogram] { let dictionaries = ecgSamples.map(fetchEcgMeasurements) @@ -1026,10 +1029,10 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } } } - + HKHealthStore().execute(query) } - + @available(iOS 14.0, *) private func fetchEcgMeasurements(_ sample: HKElectrocardiogram) -> NSDictionary { let semaphore = DispatchSemaphore(value: 0) @@ -1063,7 +1066,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { "source_name": sample.sourceRevision.source.name, ] } - + func getIntervalData(call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary let dataTypeKey = (arguments?["dataTypeKey"] as? String) ?? "DEFAULT" @@ -1073,24 +1076,24 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let intervalInSecond = (arguments?["interval"] as? Int) ?? 1 let recordingMethodsToFilter = (arguments?["recordingMethodsToFilter"] as? [Int]) ?? [] let includeManualEntry = !recordingMethodsToFilter.contains(RecordingMethod.manual.rawValue) - + // Set interval in seconds. var interval = DateComponents() interval.second = intervalInSecond - + // Convert dates from milliseconds to Date() let dateFrom = Date(timeIntervalSince1970: startDate.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endDate.doubleValue / 1000) - + let quantityType: HKQuantityType! = dataQuantityTypesDict[dataTypeKey] var predicate = HKQuery.predicateForSamples(withStart: dateFrom, end: dateTo, options: []) if (!includeManualEntry) { let manualPredicate = NSPredicate(format: "metadata.%K != YES", HKMetadataKeyWasUserEntered) predicate = NSCompoundPredicate(type: .and, subpredicates: [predicate, manualPredicate]) } - + let query = HKStatisticsCollectionQuery(quantityType: quantityType, quantitySamplePredicate: predicate, options: [.cumulativeSum, .separateBySource], anchorDate: dateFrom, intervalComponents: interval) - + query.initialResultsHandler = { [weak self] _, statisticCollectionOrNil, error in guard let self = self else { @@ -1101,7 +1104,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return } - + // Error detected. if let error = error { print("Query error: \(error.localizedDescription)") @@ -1110,7 +1113,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return } - + guard let collection = statisticCollectionOrNil as? HKStatisticsCollection else { print("Unexpected result from query") DispatchQueue.main.async { @@ -1118,7 +1121,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return } - + var dictionaries = [[String: Any]]() collection.enumerateStatistics(from: dateFrom, to: dateTo) { [weak self] statisticData, _ in @@ -1127,7 +1130,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { print("Self is nil during enumeration") return } - + do { if let quantity = statisticData.sumQuantity(), let dataUnitKey = dataUnitKey, @@ -1151,18 +1154,18 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } HKHealthStore().execute(query) } - + func getTotalStepsInInterval(call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary let startTime = (arguments?["startTime"] as? NSNumber) ?? 0 let endTime = (arguments?["endTime"] as? NSNumber) ?? 0 let recordingMethodsToFilter = (arguments?["recordingMethodsToFilter"] as? [Int]) ?? [] let includeManualEntry = !recordingMethodsToFilter.contains(RecordingMethod.manual.rawValue) - + // Convert dates from milliseconds to Date() let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + let sampleType = HKQuantityType.quantityType(forIdentifier: .stepCount)! var predicate = HKQuery.predicateForSamples( withStart: dateFrom, end: dateTo, options: .strictStartDate) @@ -1170,53 +1173,53 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let manualPredicate = NSPredicate(format: "metadata.%K != YES", HKMetadataKeyWasUserEntered) predicate = NSCompoundPredicate(type: .and, subpredicates: [predicate, manualPredicate]) } - + let query = HKStatisticsQuery( quantityType: sampleType, quantitySamplePredicate: predicate, options: .cumulativeSum ) { query, queryResult, error in - + guard let queryResult = queryResult else { let error = error! as NSError print("Error getting total steps in interval \(error.localizedDescription)") - + DispatchQueue.main.async { result(nil) } return } - + var steps = 0.0 - + if let quantity = queryResult.sumQuantity() { let unit = HKUnit.count() steps = quantity.doubleValue(for: unit) } - + let totalSteps = Int(steps) DispatchQueue.main.async { result(totalSteps) } } - + HKHealthStore().execute(query) } - + func unitLookUp(key: String) -> HKUnit { guard let unit = unitDict[key] else { return HKUnit.count() } return unit } - + func dataTypeLookUp(key: String) -> HKSampleType { guard let dataType_ = dataTypesDict[key] else { return HKSampleType.quantityType(forIdentifier: .bodyMass)! } return dataType_ } - + func getGender() -> HKBiologicalSex? { var bioSex:HKBiologicalSex? do { @@ -1227,7 +1230,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return bioSex } - + func getBirthDate() -> Date? { var dob:Date? do { @@ -1238,7 +1241,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return dob } - + func getBloodType() -> HKBloodType? { var bloodType:HKBloodType? do { @@ -1249,7 +1252,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return bloodType } - + func initializeTypes() { // Initialize units unitDict[GRAM] = HKUnit.gram() @@ -1298,7 +1301,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { unitDict[MILLIGRAM_PER_DECILITER] = HKUnit.init(from: "mg/dL") unitDict[UNKNOWN_UNIT] = HKUnit.init(from: "") unitDict[NO_UNIT] = HKUnit.init(from: "") - + // Initialize workout types workoutActivityTypeMap["ARCHERY"] = .archery workoutActivityTypeMap["BOWLING"] = .bowling @@ -1402,7 +1405,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataTypesDict[RESPIRATORY_RATE] = HKSampleType.quantityType(forIdentifier: .respiratoryRate)! dataTypesDict[PERIPHERAL_PERFUSION_INDEX] = HKSampleType.quantityType( forIdentifier: .peripheralPerfusionIndex)! - + dataTypesDict[BLOOD_PRESSURE_DIASTOLIC] = HKSampleType.quantityType( forIdentifier: .bloodPressureDiastolic)! dataTypesDict[BLOOD_PRESSURE_SYSTOLIC] = HKSampleType.quantityType( @@ -1411,7 +1414,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { forIdentifier: .bodyFatPercentage)! dataTypesDict[BODY_MASS_INDEX] = HKSampleType.quantityType(forIdentifier: .bodyMassIndex)! dataTypesDict[BODY_TEMPERATURE] = HKSampleType.quantityType(forIdentifier: .bodyTemperature)! - + // Nutrition dataTypesDict[DIETARY_CARBS_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryCarbohydrates)! dataTypesDict[DIETARY_CAFFEINE] = HKSampleType.quantityType(forIdentifier: .dietaryCaffeine)! @@ -1452,7 +1455,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataTypesDict[DIETARY_MANGANESE] = HKSampleType.quantityType(forIdentifier: .dietaryManganese)! dataTypesDict[DIETARY_MOLYBDENUM] = HKSampleType.quantityType(forIdentifier: .dietaryMolybdenum)! dataTypesDict[DIETARY_SELENIUM] = HKSampleType.quantityType(forIdentifier: .dietarySelenium)! - + dataTypesDict[ELECTRODERMAL_ACTIVITY] = HKSampleType.quantityType( forIdentifier: .electrodermalActivity)! dataTypesDict[FORCED_EXPIRATORY_VOLUME] = HKSampleType.quantityType( @@ -1483,21 +1486,21 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataTypesDict[SLEEP_REM] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! dataTypesDict[SLEEP_ASLEEP] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! dataTypesDict[MENSTRUATION_FLOW] = HKSampleType.categoryType(forIdentifier: .menstrualFlow)! - - + + dataTypesDict[EXERCISE_TIME] = HKSampleType.quantityType(forIdentifier: .appleExerciseTime)! dataTypesDict[WORKOUT] = HKSampleType.workoutType() dataTypesDict[NUTRITION] = HKSampleType.correlationType( forIdentifier: .food)! - + healthDataTypes = Array(dataTypesDict.values) - + characteristicsTypesDict[BIRTH_DATE] = HKObjectType.characteristicType(forIdentifier: .dateOfBirth)! characteristicsTypesDict[GENDER] = HKObjectType.characteristicType(forIdentifier: .biologicalSex)! characteristicsTypesDict[BLOOD_TYPE] = HKObjectType.characteristicType(forIdentifier: .bloodType)! characteristicsDataTypes = Array(characteristicsTypesDict.values) } - + // Set up iOS 11 specific types (ordinary health data quantity types) if #available(iOS 11.0, *) { dataQuantityTypesDict[ACTIVE_ENERGY_BURNED] = HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)! @@ -1509,7 +1512,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataQuantityTypesDict[BODY_FAT_PERCENTAGE] = HKQuantityType.quantityType(forIdentifier: .bodyFatPercentage)! dataQuantityTypesDict[BODY_MASS_INDEX] = HKQuantityType.quantityType(forIdentifier: .bodyMassIndex)! dataQuantityTypesDict[BODY_TEMPERATURE] = HKQuantityType.quantityType(forIdentifier: .bodyTemperature)! - + // Nutrition dataQuantityTypesDict[DIETARY_CARBS_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryCarbohydrates)! dataQuantityTypesDict[DIETARY_CAFFEINE] = HKSampleType.quantityType(forIdentifier: .dietaryCaffeine)! @@ -1550,7 +1553,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataQuantityTypesDict[DIETARY_MANGANESE] = HKSampleType.quantityType(forIdentifier: .dietaryManganese)! dataQuantityTypesDict[DIETARY_MOLYBDENUM] = HKSampleType.quantityType(forIdentifier: .dietaryMolybdenum)! dataQuantityTypesDict[DIETARY_SELENIUM] = HKSampleType.quantityType(forIdentifier: .dietarySelenium)! - + dataQuantityTypesDict[ELECTRODERMAL_ACTIVITY] = HKQuantityType.quantityType(forIdentifier: .electrodermalActivity)! dataQuantityTypesDict[FORCED_EXPIRATORY_VOLUME] = HKQuantityType.quantityType(forIdentifier: .forcedExpiratoryVolume1)! dataQuantityTypesDict[HEART_RATE] = HKQuantityType.quantityType(forIdentifier: .heartRate)! @@ -1565,10 +1568,10 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataQuantityTypesDict[DISTANCE_SWIMMING] = HKQuantityType.quantityType(forIdentifier: .distanceSwimming)! dataQuantityTypesDict[DISTANCE_CYCLING] = HKQuantityType.quantityType(forIdentifier: .distanceCycling)! dataQuantityTypesDict[FLIGHTS_CLIMBED] = HKQuantityType.quantityType(forIdentifier: .flightsClimbed)! - + healthDataQuantityTypes = Array(dataQuantityTypesDict.values) } - + // Set up heart rate data types specific to the apple watch, requires iOS 12 if #available(iOS 12.2, *) { dataTypesDict[HIGH_HEART_RATE_EVENT] = HKSampleType.categoryType( @@ -1577,32 +1580,32 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { forIdentifier: .lowHeartRateEvent)! dataTypesDict[IRREGULAR_HEART_RATE_EVENT] = HKSampleType.categoryType( forIdentifier: .irregularHeartRhythmEvent)! - + heartRateEventTypes = Set([ HKSampleType.categoryType(forIdentifier: .highHeartRateEvent)!, HKSampleType.categoryType(forIdentifier: .lowHeartRateEvent)!, HKSampleType.categoryType(forIdentifier: .irregularHeartRhythmEvent)!, ]) } - + if #available(iOS 13.6, *) { dataTypesDict[HEADACHE_UNSPECIFIED] = HKSampleType.categoryType(forIdentifier: .headache)! dataTypesDict[HEADACHE_NOT_PRESENT] = HKSampleType.categoryType(forIdentifier: .headache)! dataTypesDict[HEADACHE_MILD] = HKSampleType.categoryType(forIdentifier: .headache)! dataTypesDict[HEADACHE_MODERATE] = HKSampleType.categoryType(forIdentifier: .headache)! dataTypesDict[HEADACHE_SEVERE] = HKSampleType.categoryType(forIdentifier: .headache)! - + headacheType = Set([ HKSampleType.categoryType(forIdentifier: .headache)! ]) } - + if #available(iOS 14.0, *) { dataTypesDict[ELECTROCARDIOGRAM] = HKSampleType.electrocardiogramType() - + unitDict[VOLT] = HKUnit.volt() unitDict[INCHES_OF_MERCURY] = HKUnit.inchesOfMercury() - + workoutActivityTypeMap["CARDIO_DANCE"] = HKWorkoutActivityType.cardioDance workoutActivityTypeMap["SOCIAL_DANCE"] = HKWorkoutActivityType.socialDance workoutActivityTypeMap["PICKLEBALL"] = HKWorkoutActivityType.pickleball @@ -1611,13 +1614,13 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { if #available(iOS 16.0, *) { dataTypesDict[ATRIAL_FIBRILLATION_BURDEN] = HKQuantityType.quantityType(forIdentifier: .atrialFibrillationBurden)! - } - + } + // Concatenate heart events, headache and health data types (both may be empty) allDataTypes = Set(heartRateEventTypes + healthDataTypes) allDataTypes = allDataTypes.union(headacheType) } - + func getWorkoutType(type: HKWorkoutActivityType) -> String { switch type { case .americanFootball: From c1ddc2c035e4476cca4f96fcacaa2228f4259eb8 Mon Sep 17 00:00:00 2001 From: bernd70 Date: Sat, 28 Dec 2024 01:27:30 +0100 Subject: [PATCH 2/2] Undid whitespace changes --- .../ios/Classes/SwiftHealthPlugin.swift | 296 +++++++++--------- 1 file changed, 148 insertions(+), 148 deletions(-) diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift index fb9e1fe49..7bb71ca2b 100644 --- a/packages/health/ios/Classes/SwiftHealthPlugin.swift +++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift @@ -10,7 +10,7 @@ enum RecordingMethod: Int { } public class SwiftHealthPlugin: NSObject, FlutterPlugin { - + let healthStore = HKHealthStore() var healthDataTypes = [HKSampleType]() var healthDataQuantityTypes = [HKQuantityType]() @@ -24,7 +24,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { var workoutActivityTypeMap: [String: HKWorkoutActivityType] = [:] var characteristicsTypesDict: [String: HKCharacteristicType] = [:] var nutritionList: [String] = [] - + // Health Data Type Keys let ACTIVE_ENERGY_BURNED = "ACTIVE_ENERGY_BURNED" let ATRIAL_FIBRILLATION_BURDEN = "ATRIAL_FIBRILLATION_BURDEN" @@ -147,7 +147,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let SLEEP_IN_BED = "SLEEP_IN_BED" let SLEEP_LIGHT = "SLEEP_LIGHT" let SLEEP_REM = "SLEEP_REM" - + let EXERCISE_TIME = "EXERCISE_TIME" let WORKOUT = "WORKOUT" let HEADACHE_UNSPECIFIED = "HEADACHE_UNSPECIFIED" @@ -161,8 +161,8 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let GENDER = "GENDER" let BLOOD_TYPE = "BLOOD_TYPE" let MENSTRUATION_FLOW = "MENSTRUATION_FLOW" - - + + // Health Unit types // MOLE_UNIT_WITH_MOLAR_MASS, // requires molar mass input - not supported yet // MOLE_UNIT_WITH_PREFIX_MOLAR_MASS, // requires molar mass & prefix input - not supported yet @@ -214,22 +214,22 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let MILLIGRAM_PER_DECILITER = "MILLIGRAM_PER_DECILITER" let UNKNOWN_UNIT = "UNKNOWN_UNIT" let NO_UNIT = "NO_UNIT" - + struct PluginError: Error { let message: String } - + public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel( name: "flutter_health", binaryMessenger: registrar.messenger()) let instance = SwiftHealthPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } - + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { // Set up all data types initializeTypes() - + /// Handle checkIfHealthDataAvailable if call.method.elementsEqual("checkIfHealthDataAvailable") { checkIfHealthDataAvailable(call: call, result: result) @@ -237,72 +237,72 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else if call.method.elementsEqual("requestAuthorization") { try! requestAuthorization(call: call, result: result) } - + /// Handle getData else if call.method.elementsEqual("getData") { getData(call: call, result: result) } - + /// Handle getIntervalData else if (call.method.elementsEqual("getIntervalData")){ getIntervalData(call: call, result: result) } - + /// Handle getTotalStepsInInterval else if call.method.elementsEqual("getTotalStepsInInterval") { getTotalStepsInInterval(call: call, result: result) } - + /// Handle writeData else if call.method.elementsEqual("writeData") { try! writeData(call: call, result: result) } - + /// Handle writeAudiogram else if call.method.elementsEqual("writeAudiogram") { try! writeAudiogram(call: call, result: result) } - + /// Handle writeBloodPressure else if call.method.elementsEqual("writeBloodPressure") { try! writeBloodPressure(call: call, result: result) } - + /// Handle writeMeal else if (call.method.elementsEqual("writeMeal")){ try! writeMeal(call: call, result: result) } - + /// Handle writeInsulinDelivery else if (call.method.elementsEqual("writeInsulinDelivery")){ try! writeInsulinDelivery(call: call, result: result) } - + /// Handle writeWorkoutData else if call.method.elementsEqual("writeWorkoutData") { try! writeWorkoutData(call: call, result: result) } - + /// Handle writeMenstruationFlow else if call.method.elementsEqual("writeMenstruationFlow") { try! writeMenstruationFlow(call: call, result: result) } - + /// Handle hasPermission else if call.method.elementsEqual("hasPermissions") { try! hasPermissions(call: call, result: result) } - + /// Handle delete data else if call.method.elementsEqual("delete") { try! delete(call: call, result: result) } } - + func checkIfHealthDataAvailable(call: FlutterMethodCall, result: @escaping FlutterResult) { result(HKHealthStore.isHealthDataAvailable()) } - + func hasPermissions(call: FlutterMethodCall, result: @escaping FlutterResult) throws { let arguments = call.arguments as? NSDictionary guard var types = arguments?["types"] as? [String], @@ -311,18 +311,18 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { throw PluginError(message: "Invalid Arguments!") } - + if let nutritionIndex = types.firstIndex(of: NUTRITION) { types.remove(at: nutritionIndex) let nutritionPermission = permissions[nutritionIndex] permissions.remove(at: nutritionIndex) - + for nutritionType in nutritionList { types.append(nutritionType) permissions.append(nutritionPermission) } } - + for (index, type) in types.enumerated() { let sampleType = dataTypeLookUp(key: type) let success = hasPermission(type: sampleType, access: permissions[index]) @@ -338,12 +338,12 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } } } - + result(false) } - + func hasPermission(type: HKObjectType, access: Int) -> Bool? { - + if #available(iOS 13.0, *) { let status = healthStore.authorizationStatus(for: type) switch access { @@ -358,7 +358,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { return nil } } - + func requestAuthorization(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let types = arguments["types"] as? [String], @@ -367,7 +367,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { throw PluginError(message: "Invalid Arguments!") } - + var typesToRead = Set() var typesToWrite = Set() for (index, key) in types.enumerated() { @@ -401,7 +401,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } } } - + if #available(iOS 13.0, *) { healthStore.requestAuthorization(toShare: typesToWrite, read: typesToRead) { (success, error) in @@ -413,7 +413,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { result(false) // Handle the error here. } } - + func writeData(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let value = (arguments["value"] as? Double), @@ -425,7 +425,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { throw PluginError(message: "Invalid Arguments") } - + let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) @@ -433,9 +433,9 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let metadata: [String: Any] = [ HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry) ] - + let sample: HKObject - + if dataTypeLookUp(key: type).isKind(of: HKCategoryType.self) { sample = HKCategorySample( type: dataTypeLookUp(key: type) as! HKCategoryType, value: Int(value), start: dateFrom, @@ -446,7 +446,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { type: dataTypeLookUp(key: type) as! HKQuantityType, quantity: quantity, start: dateFrom, end: dateTo, metadata: metadata) } - + HKHealthStore().save( sample, withCompletion: { (success, error) in @@ -458,7 +458,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func writeAudiogram(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let frequencies = (arguments["frequencies"] as? [Double]), @@ -469,12 +469,12 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { throw PluginError(message: "Invalid Arguments") } - + let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + var sensitivityPoints = [HKAudiogramSensitivityPoint]() - + for index in 0...frequencies.count - 1 { let frequency = HKQuantity(unit: HKUnit.hertz(), doubleValue: frequencies[index]) let dbUnit = HKUnit.decibelHearingLevel() @@ -484,23 +484,23 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { 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]) - + } else { audiogram = HKAudiogramSample( sensitivityPoints: sensitivityPoints, start: dateFrom, end: dateTo, metadata: nil) } - + HKHealthStore().save( audiogram, withCompletion: { (success, error) in @@ -512,7 +512,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func writeBloodPressure(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let systolic = (arguments["systolic"] as? Double), @@ -530,7 +530,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let metadata = [ HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry) ] - + let systolic_sample = HKQuantitySample( type: HKSampleType.quantityType(forIdentifier: .bloodPressureSystolic)!, quantity: HKQuantity(unit: HKUnit.millimeterOfMercury(), doubleValue: systolic), @@ -542,7 +542,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { 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) - + HKHealthStore().save( [blood_pressure_sample], withCompletion: { (success, error) in @@ -554,7 +554,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func writeMeal(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let name = (arguments["name"] as? String?), @@ -565,14 +565,14 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { throw PluginError(message: "Invalid Arguments") } - + let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + let mealTypeString = mealType ?? "UNKNOWN" let isManualEntry = recordingMethod == RecordingMethod.manual.rawValue - + var metadata = ["HKFoodMeal": mealTypeString, HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry)] as [String : Any] if (name != nil) { metadata[HKMetadataKeyFoodType] = "\(name!)" @@ -587,11 +587,11 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { 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) - + HKHealthStore().save(meal, withCompletion: { (success, error) in if let err = error { print("Error Saving Meal Sample: \(err.localizedDescription)") @@ -615,13 +615,13 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + 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) - + HKHealthStore().save(insulin_sample, withCompletion: { (success, error) in if let err = error { print("Error Saving Insulin Delivery Sample: \(err.localizedDescription)") @@ -631,7 +631,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func writeMenstruationFlow(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let flow = (arguments["value"] as? Int), @@ -644,7 +644,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { guard let menstrualFlowType = HKCategoryValueMenstrualFlow(rawValue: flow) else { throw PluginError(message: "Invalid Menstrual Flow Type") } - + let dateTime = Date(timeIntervalSince1970: endTime.doubleValue / 1000) let isManualEntry = recordingMethod == RecordingMethod.manual.rawValue @@ -654,15 +654,15 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } 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 ) - + HKHealthStore().save( sample, withCompletion: { (success, error) in @@ -674,7 +674,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func writeWorkoutData(call: FlutterMethodCall, result: @escaping FlutterResult) throws { guard let arguments = call.arguments as? NSDictionary, let activityType = (arguments["activityType"] as? String), @@ -684,10 +684,10 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else { 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( @@ -697,17 +697,17 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { totalDistance = HKQuantity( unit: unitDict[(arguments["totalDistanceUnit"] as! String)]!, doubleValue: td) } - + let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + var workout: HKWorkout - + workout = HKWorkout( activityType: ac, start: dateFrom, end: dateTo, duration: dateTo.timeIntervalSince(dateFrom), totalEnergyBurned: totalEnergyBurned ?? nil, totalDistance: totalDistance ?? nil, metadata: nil) - + HKHealthStore().save( workout, withCompletion: { (success, error) in @@ -719,36 +719,36 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } }) } - + func delete(call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary let dataTypeKey = (arguments?["dataTypeKey"] as? String)! let startTime = (arguments?["startTime"] as? NSNumber) ?? 0 let endTime = (arguments?["endTime"] as? NSNumber) ?? 0 - + let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + let dataType = dataTypeLookUp(key: dataTypeKey) - + let samplePredicate = HKQuery.predicateForSamples( withStart: dateFrom, end: dateTo, options: .strictStartDate) let ownerPredicate = HKQuery.predicateForObjects(from: HKSource.default()) let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) - + let deleteQuery = HKSampleQuery( sampleType: dataType, predicate: NSCompoundPredicate(andPredicateWithSubpredicates: [samplePredicate, ownerPredicate]), limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor] ) { [self] x, samplesOrNil, error in - + guard let samplesOrNil = samplesOrNil, error == nil else { // Handle the error if necessary print("Error deleting \(dataType)") return } - + // Delete the retrieved objects from the HealthKit store HKHealthStore().delete(samplesOrNil) { (success, error) in if let err = error { @@ -759,10 +759,10 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } } } - + HKHealthStore().execute(deleteQuery) } - + func getData(call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary let dataTypeKey = (arguments?["dataTypeKey"] as? String)! @@ -772,20 +772,20 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let limit = (arguments?["limit"] as? Int) ?? HKObjectQueryNoLimit let recordingMethodsToFilter = (arguments?["recordingMethodsToFilter"] as? [Int]) ?? [] let includeManualEntry = !recordingMethodsToFilter.contains(RecordingMethod.manual.rawValue) - + // Convert dates from milliseconds to Date() let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + let dataType = dataTypeLookUp(key: dataTypeKey) var unit: HKUnit? if let dataUnitKey = dataUnitKey { unit = unitDict[dataUnitKey] } - + let sourceIdForCharacteristic = "com.apple.Health" let sourceNameForCharacteristic = "Health" - + switch(dataTypeKey) { case "BIRTH_DATE": let dateOfBirth = getBirthDate() @@ -829,7 +829,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { default: break } - + var predicate = HKQuery.predicateForSamples( withStart: dateFrom, end: dateTo, options: .strictStartDate) if (!includeManualEntry) { @@ -837,13 +837,13 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { predicate = NSCompoundPredicate(type: .and, subpredicates: [predicate, manualPredicate]) } let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) - + let query = HKSampleQuery( sampleType: dataType, predicate: predicate, limit: limit, sortDescriptors: [sortDescriptor] ) { [self] x, samplesOrNil, error in - + switch samplesOrNil { case let (samples as [HKQuantitySample]) as Any: let dictionaries = samples.map { sample -> NSDictionary in @@ -863,9 +863,9 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { DispatchQueue.main.async { result(dictionaries) } - + case var (samplesCategory as [HKCategorySample]) as Any: - + if dataTypeKey == self.SLEEP_IN_BED { samplesCategory = samplesCategory.filter { $0.value == 0 } } @@ -901,13 +901,13 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } let categories = samplesCategory.map { sample -> NSDictionary in var metadata: [String: Any] = [:] - + if let sampleMetadata = sample.metadata { for (key, value) in sampleMetadata { metadata[key] = value } } - + return [ "uuid": "\(sample.uuid)", "value": sample.value, @@ -922,9 +922,9 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { DispatchQueue.main.async { result(categories) } - + case let (samplesWorkout as [HKWorkout]) as Any: - + let dictionaries = samplesWorkout.map { sample -> NSDictionary in return [ "uuid": "\(sample.uuid)", @@ -945,11 +945,11 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { "total_energy_burned": sample.totalEnergyBurned != nil ? Int(sample.totalEnergyBurned!.doubleValue(for: HKUnit.kilocalorie())) : 0 ] } - + DispatchQueue.main.async { result(dictionaries) } - + case let (samplesAudiogram as [HKAudiogramSample]) as Any: let dictionaries = samplesAudiogram.map { sample -> NSDictionary in var frequencies = [Double]() @@ -976,7 +976,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { DispatchQueue.main.async { result(dictionaries) } - + case let (nutritionSample as [HKCorrelation]) as Any: var foods: [[String: Any?]] = [] for food in nutritionSample { @@ -1010,11 +1010,11 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { foods.append(sampleDict) } } - + DispatchQueue.main.async { result(foods) } - + default: if #available(iOS 14.0, *), let ecgSamples = samplesOrNil as? [HKElectrocardiogram] { let dictionaries = ecgSamples.map(fetchEcgMeasurements) @@ -1029,10 +1029,10 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } } } - + HKHealthStore().execute(query) } - + @available(iOS 14.0, *) private func fetchEcgMeasurements(_ sample: HKElectrocardiogram) -> NSDictionary { let semaphore = DispatchSemaphore(value: 0) @@ -1066,7 +1066,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { "source_name": sample.sourceRevision.source.name, ] } - + func getIntervalData(call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary let dataTypeKey = (arguments?["dataTypeKey"] as? String) ?? "DEFAULT" @@ -1076,24 +1076,24 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let intervalInSecond = (arguments?["interval"] as? Int) ?? 1 let recordingMethodsToFilter = (arguments?["recordingMethodsToFilter"] as? [Int]) ?? [] let includeManualEntry = !recordingMethodsToFilter.contains(RecordingMethod.manual.rawValue) - + // Set interval in seconds. var interval = DateComponents() interval.second = intervalInSecond - + // Convert dates from milliseconds to Date() let dateFrom = Date(timeIntervalSince1970: startDate.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endDate.doubleValue / 1000) - + let quantityType: HKQuantityType! = dataQuantityTypesDict[dataTypeKey] var predicate = HKQuery.predicateForSamples(withStart: dateFrom, end: dateTo, options: []) if (!includeManualEntry) { let manualPredicate = NSPredicate(format: "metadata.%K != YES", HKMetadataKeyWasUserEntered) predicate = NSCompoundPredicate(type: .and, subpredicates: [predicate, manualPredicate]) } - + let query = HKStatisticsCollectionQuery(quantityType: quantityType, quantitySamplePredicate: predicate, options: [.cumulativeSum, .separateBySource], anchorDate: dateFrom, intervalComponents: interval) - + query.initialResultsHandler = { [weak self] _, statisticCollectionOrNil, error in guard let self = self else { @@ -1104,7 +1104,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return } - + // Error detected. if let error = error { print("Query error: \(error.localizedDescription)") @@ -1113,7 +1113,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return } - + guard let collection = statisticCollectionOrNil as? HKStatisticsCollection else { print("Unexpected result from query") DispatchQueue.main.async { @@ -1121,7 +1121,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return } - + var dictionaries = [[String: Any]]() collection.enumerateStatistics(from: dateFrom, to: dateTo) { [weak self] statisticData, _ in @@ -1130,7 +1130,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { print("Self is nil during enumeration") return } - + do { if let quantity = statisticData.sumQuantity(), let dataUnitKey = dataUnitKey, @@ -1154,18 +1154,18 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } HKHealthStore().execute(query) } - + func getTotalStepsInInterval(call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary let startTime = (arguments?["startTime"] as? NSNumber) ?? 0 let endTime = (arguments?["endTime"] as? NSNumber) ?? 0 let recordingMethodsToFilter = (arguments?["recordingMethodsToFilter"] as? [Int]) ?? [] let includeManualEntry = !recordingMethodsToFilter.contains(RecordingMethod.manual.rawValue) - + // Convert dates from milliseconds to Date() let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - + let sampleType = HKQuantityType.quantityType(forIdentifier: .stepCount)! var predicate = HKQuery.predicateForSamples( withStart: dateFrom, end: dateTo, options: .strictStartDate) @@ -1173,53 +1173,53 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let manualPredicate = NSPredicate(format: "metadata.%K != YES", HKMetadataKeyWasUserEntered) predicate = NSCompoundPredicate(type: .and, subpredicates: [predicate, manualPredicate]) } - + let query = HKStatisticsQuery( quantityType: sampleType, quantitySamplePredicate: predicate, options: .cumulativeSum ) { query, queryResult, error in - + guard let queryResult = queryResult else { let error = error! as NSError print("Error getting total steps in interval \(error.localizedDescription)") - + DispatchQueue.main.async { result(nil) } return } - + var steps = 0.0 - + if let quantity = queryResult.sumQuantity() { let unit = HKUnit.count() steps = quantity.doubleValue(for: unit) } - + let totalSteps = Int(steps) DispatchQueue.main.async { result(totalSteps) } } - + HKHealthStore().execute(query) } - + func unitLookUp(key: String) -> HKUnit { guard let unit = unitDict[key] else { return HKUnit.count() } return unit } - + func dataTypeLookUp(key: String) -> HKSampleType { guard let dataType_ = dataTypesDict[key] else { return HKSampleType.quantityType(forIdentifier: .bodyMass)! } return dataType_ } - + func getGender() -> HKBiologicalSex? { var bioSex:HKBiologicalSex? do { @@ -1230,7 +1230,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return bioSex } - + func getBirthDate() -> Date? { var dob:Date? do { @@ -1241,7 +1241,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return dob } - + func getBloodType() -> HKBloodType? { var bloodType:HKBloodType? do { @@ -1252,7 +1252,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } return bloodType } - + func initializeTypes() { // Initialize units unitDict[GRAM] = HKUnit.gram() @@ -1301,7 +1301,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { unitDict[MILLIGRAM_PER_DECILITER] = HKUnit.init(from: "mg/dL") unitDict[UNKNOWN_UNIT] = HKUnit.init(from: "") unitDict[NO_UNIT] = HKUnit.init(from: "") - + // Initialize workout types workoutActivityTypeMap["ARCHERY"] = .archery workoutActivityTypeMap["BOWLING"] = .bowling @@ -1405,7 +1405,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataTypesDict[RESPIRATORY_RATE] = HKSampleType.quantityType(forIdentifier: .respiratoryRate)! dataTypesDict[PERIPHERAL_PERFUSION_INDEX] = HKSampleType.quantityType( forIdentifier: .peripheralPerfusionIndex)! - + dataTypesDict[BLOOD_PRESSURE_DIASTOLIC] = HKSampleType.quantityType( forIdentifier: .bloodPressureDiastolic)! dataTypesDict[BLOOD_PRESSURE_SYSTOLIC] = HKSampleType.quantityType( @@ -1414,7 +1414,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { forIdentifier: .bodyFatPercentage)! dataTypesDict[BODY_MASS_INDEX] = HKSampleType.quantityType(forIdentifier: .bodyMassIndex)! dataTypesDict[BODY_TEMPERATURE] = HKSampleType.quantityType(forIdentifier: .bodyTemperature)! - + // Nutrition dataTypesDict[DIETARY_CARBS_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryCarbohydrates)! dataTypesDict[DIETARY_CAFFEINE] = HKSampleType.quantityType(forIdentifier: .dietaryCaffeine)! @@ -1455,7 +1455,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataTypesDict[DIETARY_MANGANESE] = HKSampleType.quantityType(forIdentifier: .dietaryManganese)! dataTypesDict[DIETARY_MOLYBDENUM] = HKSampleType.quantityType(forIdentifier: .dietaryMolybdenum)! dataTypesDict[DIETARY_SELENIUM] = HKSampleType.quantityType(forIdentifier: .dietarySelenium)! - + dataTypesDict[ELECTRODERMAL_ACTIVITY] = HKSampleType.quantityType( forIdentifier: .electrodermalActivity)! dataTypesDict[FORCED_EXPIRATORY_VOLUME] = HKSampleType.quantityType( @@ -1486,21 +1486,21 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataTypesDict[SLEEP_REM] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! dataTypesDict[SLEEP_ASLEEP] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! dataTypesDict[MENSTRUATION_FLOW] = HKSampleType.categoryType(forIdentifier: .menstrualFlow)! - - + + dataTypesDict[EXERCISE_TIME] = HKSampleType.quantityType(forIdentifier: .appleExerciseTime)! dataTypesDict[WORKOUT] = HKSampleType.workoutType() dataTypesDict[NUTRITION] = HKSampleType.correlationType( forIdentifier: .food)! - + healthDataTypes = Array(dataTypesDict.values) - + characteristicsTypesDict[BIRTH_DATE] = HKObjectType.characteristicType(forIdentifier: .dateOfBirth)! characteristicsTypesDict[GENDER] = HKObjectType.characteristicType(forIdentifier: .biologicalSex)! characteristicsTypesDict[BLOOD_TYPE] = HKObjectType.characteristicType(forIdentifier: .bloodType)! characteristicsDataTypes = Array(characteristicsTypesDict.values) } - + // Set up iOS 11 specific types (ordinary health data quantity types) if #available(iOS 11.0, *) { dataQuantityTypesDict[ACTIVE_ENERGY_BURNED] = HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)! @@ -1512,7 +1512,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataQuantityTypesDict[BODY_FAT_PERCENTAGE] = HKQuantityType.quantityType(forIdentifier: .bodyFatPercentage)! dataQuantityTypesDict[BODY_MASS_INDEX] = HKQuantityType.quantityType(forIdentifier: .bodyMassIndex)! dataQuantityTypesDict[BODY_TEMPERATURE] = HKQuantityType.quantityType(forIdentifier: .bodyTemperature)! - + // Nutrition dataQuantityTypesDict[DIETARY_CARBS_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryCarbohydrates)! dataQuantityTypesDict[DIETARY_CAFFEINE] = HKSampleType.quantityType(forIdentifier: .dietaryCaffeine)! @@ -1553,7 +1553,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataQuantityTypesDict[DIETARY_MANGANESE] = HKSampleType.quantityType(forIdentifier: .dietaryManganese)! dataQuantityTypesDict[DIETARY_MOLYBDENUM] = HKSampleType.quantityType(forIdentifier: .dietaryMolybdenum)! dataQuantityTypesDict[DIETARY_SELENIUM] = HKSampleType.quantityType(forIdentifier: .dietarySelenium)! - + dataQuantityTypesDict[ELECTRODERMAL_ACTIVITY] = HKQuantityType.quantityType(forIdentifier: .electrodermalActivity)! dataQuantityTypesDict[FORCED_EXPIRATORY_VOLUME] = HKQuantityType.quantityType(forIdentifier: .forcedExpiratoryVolume1)! dataQuantityTypesDict[HEART_RATE] = HKQuantityType.quantityType(forIdentifier: .heartRate)! @@ -1568,10 +1568,10 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { dataQuantityTypesDict[DISTANCE_SWIMMING] = HKQuantityType.quantityType(forIdentifier: .distanceSwimming)! dataQuantityTypesDict[DISTANCE_CYCLING] = HKQuantityType.quantityType(forIdentifier: .distanceCycling)! dataQuantityTypesDict[FLIGHTS_CLIMBED] = HKQuantityType.quantityType(forIdentifier: .flightsClimbed)! - + healthDataQuantityTypes = Array(dataQuantityTypesDict.values) } - + // Set up heart rate data types specific to the apple watch, requires iOS 12 if #available(iOS 12.2, *) { dataTypesDict[HIGH_HEART_RATE_EVENT] = HKSampleType.categoryType( @@ -1580,32 +1580,32 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { forIdentifier: .lowHeartRateEvent)! dataTypesDict[IRREGULAR_HEART_RATE_EVENT] = HKSampleType.categoryType( forIdentifier: .irregularHeartRhythmEvent)! - + heartRateEventTypes = Set([ HKSampleType.categoryType(forIdentifier: .highHeartRateEvent)!, HKSampleType.categoryType(forIdentifier: .lowHeartRateEvent)!, HKSampleType.categoryType(forIdentifier: .irregularHeartRhythmEvent)!, ]) } - + if #available(iOS 13.6, *) { dataTypesDict[HEADACHE_UNSPECIFIED] = HKSampleType.categoryType(forIdentifier: .headache)! dataTypesDict[HEADACHE_NOT_PRESENT] = HKSampleType.categoryType(forIdentifier: .headache)! dataTypesDict[HEADACHE_MILD] = HKSampleType.categoryType(forIdentifier: .headache)! dataTypesDict[HEADACHE_MODERATE] = HKSampleType.categoryType(forIdentifier: .headache)! dataTypesDict[HEADACHE_SEVERE] = HKSampleType.categoryType(forIdentifier: .headache)! - + headacheType = Set([ HKSampleType.categoryType(forIdentifier: .headache)! ]) } - + if #available(iOS 14.0, *) { dataTypesDict[ELECTROCARDIOGRAM] = HKSampleType.electrocardiogramType() - + unitDict[VOLT] = HKUnit.volt() unitDict[INCHES_OF_MERCURY] = HKUnit.inchesOfMercury() - + workoutActivityTypeMap["CARDIO_DANCE"] = HKWorkoutActivityType.cardioDance workoutActivityTypeMap["SOCIAL_DANCE"] = HKWorkoutActivityType.socialDance workoutActivityTypeMap["PICKLEBALL"] = HKWorkoutActivityType.pickleball @@ -1614,13 +1614,13 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { if #available(iOS 16.0, *) { dataTypesDict[ATRIAL_FIBRILLATION_BURDEN] = HKQuantityType.quantityType(forIdentifier: .atrialFibrillationBurden)! - } - + } + // Concatenate heart events, headache and health data types (both may be empty) allDataTypes = Set(heartRateEventTypes + healthDataTypes) allDataTypes = allDataTypes.union(headacheType) } - + func getWorkoutType(type: HKWorkoutActivityType) -> String { switch type { case .americanFootball: