@kingstinct/react-native-healthkit 8.3.0 → 8.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +10 -3
  2. package/ios/Constants.swift +1 -0
  3. package/ios/Helpers.swift +40 -30
  4. package/ios/ReactNativeHealthkit.m +7 -0
  5. package/ios/ReactNativeHealthkit.swift +356 -208
  6. package/lib/commonjs/index.ios.js +10 -1
  7. package/lib/commonjs/index.ios.js.map +1 -1
  8. package/lib/commonjs/index.native.js +7 -4
  9. package/lib/commonjs/index.native.js.map +1 -1
  10. package/lib/commonjs/native-types.js +94 -1
  11. package/lib/commonjs/native-types.js.map +1 -1
  12. package/lib/commonjs/test-setup.js +2 -1
  13. package/lib/commonjs/test-setup.js.map +1 -1
  14. package/lib/commonjs/utils/queryStateOfMindSamples.js +22 -0
  15. package/lib/commonjs/utils/queryStateOfMindSamples.js.map +1 -0
  16. package/lib/module/index.ios.js +4 -2
  17. package/lib/module/index.ios.js.map +1 -1
  18. package/lib/module/index.native.js +5 -3
  19. package/lib/module/index.native.js.map +1 -1
  20. package/lib/module/native-types.js +96 -0
  21. package/lib/module/native-types.js.map +1 -1
  22. package/lib/module/test-setup.js +2 -1
  23. package/lib/module/test-setup.js.map +1 -1
  24. package/lib/module/utils/queryStateOfMindSamples.js +14 -0
  25. package/lib/module/utils/queryStateOfMindSamples.js.map +1 -0
  26. package/lib/typescript/src/index.ios.d.ts +8 -1
  27. package/lib/typescript/src/index.native.d.ts +2 -2
  28. package/lib/typescript/src/native-types.d.ts +117 -1
  29. package/lib/typescript/src/utils/queryStateOfMindSamples.d.ts +7 -0
  30. package/package.json +1 -1
  31. package/src/index.ios.tsx +3 -0
  32. package/src/index.native.tsx +4 -1
  33. package/src/native-types.ts +131 -0
  34. package/src/test-setup.ts +1 -0
  35. package/src/utils/queryStateOfMindSamples.ts +14 -0
  36. package/ios/ReactNativeHealthkit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  37. package/ios/ReactNativeHealthkit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  38. package/ios/ReactNativeHealthkit.xcodeproj/project.xcworkspace/xcuserdata/robertherber.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  39. package/ios/ReactNativeHealthkit.xcodeproj/xcuserdata/robertherber.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
@@ -1,8 +1,8 @@
1
- import HealthKit
2
1
  import CoreLocation
2
+ import HealthKit
3
3
 
4
4
  #if canImport(WorkoutKit)
5
- import WorkoutKit
5
+ import WorkoutKit
6
6
  #endif
7
7
 
8
8
  @objc(ReactNativeHealthkit)
@@ -87,10 +87,11 @@ class ReactNativeHealthkit: RCTEventEmitter {
87
87
  let share = sampleTypesFromDictionary(typeIdentifiers: toShare)
88
88
  let toRead = objectTypesFromDictionary(typeIdentifiers: read)
89
89
 
90
- store.getRequestStatusForAuthorization(toShare: share, read: toRead) { (
91
- status: HKAuthorizationRequestStatus,
92
- error: Error?
93
- ) in
90
+ store.getRequestStatusForAuthorization(toShare: share, read: toRead) {
91
+ (
92
+ status: HKAuthorizationRequestStatus,
93
+ error: Error?
94
+ ) in
94
95
  guard let err = error else {
95
96
  return resolve(status.rawValue)
96
97
  }
@@ -116,7 +117,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
116
117
  }
117
118
  }
118
119
 
119
- store.preferredUnits(for: quantityTypes) { (typePerUnits: [HKQuantityType: HKUnit], _: Error?) in
120
+ store.preferredUnits(for: quantityTypes) {
121
+ (typePerUnits: [HKQuantityType: HKUnit], _: Error?) in
120
122
  let dic: NSMutableDictionary = NSMutableDictionary()
121
123
 
122
124
  for typePerUnit in typePerUnits {
@@ -283,15 +285,16 @@ class ReactNativeHealthkit: RCTEventEmitter {
283
285
  }
284
286
 
285
287
  let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
288
+ let sampleUuid = UUID.init(uuidString: uuid)!
286
289
 
287
290
  guard let sampleType = HKObjectType.quantityType(forIdentifier: identifier) else {
288
291
  return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
289
292
  }
290
293
 
291
- // https://developer.apple.com/documentation/healthkit/hkquery/1614783-predicateforobjectwithuuid
292
- let samplePredicate: NSPredicate = NSPredicate(format: "%K == %@", HKPredicateKeyPathUUID, uuid)
294
+ let samplePredicate = HKQuery.predicateForObject(with: sampleUuid)
293
295
 
294
- store.deleteObjects(of: sampleType, predicate: samplePredicate) { (success: Bool, _: Int, error: Error?) in
296
+ store.deleteObjects(of: sampleType, predicate: samplePredicate) {
297
+ (success: Bool, _: Int, error: Error?) in
295
298
  guard let err = error else {
296
299
  return resolve(success)
297
300
  }
@@ -323,7 +326,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
323
326
  options: HKQueryOptions.strictStartDate
324
327
  )
325
328
 
326
- store.deleteObjects(of: sampleType, predicate: samplePredicate) { (success: Bool, _: Int, error: Error?) in
329
+ store.deleteObjects(of: sampleType, predicate: samplePredicate) {
330
+ (success: Bool, _: Int, error: Error?) in
327
331
  guard let err = error else {
328
332
  return resolve(success)
329
333
  }
@@ -421,7 +425,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
421
425
  }
422
426
 
423
427
  guard let type = HKWorkoutActivityType.init(rawValue: typeIdentifier) else {
424
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize HKWorkoutActivityType " + typeIdentifier.description, nil)
428
+ return reject(
429
+ TYPE_IDENTIFIER_ERROR,
430
+ "Failed to initialize HKWorkoutActivityType " + typeIdentifier.description, nil)
425
431
  }
426
432
 
427
433
  // if start and end both exist, ensure that start date is before end date
@@ -572,13 +578,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
572
578
  var clLocations: [CLLocation] = []
573
579
  for location in locations {
574
580
  guard let latitude = location["latitude"] as? CLLocationDegrees,
575
- let longitude = location["longitude"] as? CLLocationDegrees,
576
- let altitude = location["altitude"] as? CLLocationDistance,
577
- let horizontalAccuracy = location["horizontalAccuracy"] as? CLLocationAccuracy,
578
- let verticalAccuracy = location["verticalAccuracy"] as? CLLocationAccuracy,
579
- let course = location["course"] as? CLLocationDirection,
580
- let speed = location["speed"] as? CLLocationSpeed,
581
- let timestamp = location["timestamp"] as? String else {
581
+ let longitude = location["longitude"] as? CLLocationDegrees,
582
+ let altitude = location["altitude"] as? CLLocationDistance,
583
+ let horizontalAccuracy = location["horizontalAccuracy"] as? CLLocationAccuracy,
584
+ let verticalAccuracy = location["verticalAccuracy"] as? CLLocationAccuracy,
585
+ let course = location["course"] as? CLLocationDirection,
586
+ let speed = location["speed"] as? CLLocationSpeed,
587
+ let timestamp = location["timestamp"] as? String
588
+ else {
582
589
  continue
583
590
  }
584
591
 
@@ -698,7 +705,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
698
705
  return reject("UpdateFrequency not valid", "UpdateFrequency not valid", nil)
699
706
  }
700
707
 
701
- store.enableBackgroundDelivery(for: sampleType, frequency: frequency ) { (success, error) in
708
+ store.enableBackgroundDelivery(for: sampleType, frequency: frequency) { (success, error) in
702
709
  guard let err = error else {
703
710
  return resolve(success)
704
711
  }
@@ -767,13 +774,17 @@ class ReactNativeHealthkit: RCTEventEmitter {
767
774
 
768
775
  let queryId = UUID().uuidString
769
776
 
770
- func responder(query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?) {
777
+ func responder(
778
+ query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?
779
+ ) {
771
780
  if error == nil {
772
781
  DispatchQueue.main.async {
773
782
  if self.bridge != nil && self.bridge.isValid {
774
- self.sendEvent(withName: "onChange", body: [
775
- "typeIdentifier": typeIdentifier
776
- ])
783
+ self.sendEvent(
784
+ withName: "onChange",
785
+ body: [
786
+ "typeIdentifier": typeIdentifier
787
+ ])
777
788
  }
778
789
 
779
790
  }
@@ -784,7 +795,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
784
795
  let query = HKObserverQuery(
785
796
  sampleType: sampleType,
786
797
  predicate: predicate
787
- ) { (query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?) in
798
+ ) {
799
+ (query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?)
800
+ in
788
801
  guard let err = error else {
789
802
  return responder(query: query, handler: handler, error: error)
790
803
  }
@@ -894,34 +907,38 @@ class ReactNativeHealthkit: RCTEventEmitter {
894
907
 
895
908
  let unit = HKUnit.init(from: unitString)
896
909
  if let averageQuantity = gottenStats.averageQuantity() {
897
- dic.updateValue(serializeQuantity(unit: unit, quantity: averageQuantity), forKey: "averageQuantity")
910
+ dic.updateValue(
911
+ serializeQuantity(unit: unit, quantity: averageQuantity), forKey: "averageQuantity")
898
912
  }
899
913
  if let maximumQuantity = gottenStats.maximumQuantity() {
900
- dic.updateValue(serializeQuantity(unit: unit, quantity: maximumQuantity), forKey: "maximumQuantity")
914
+ dic.updateValue(
915
+ serializeQuantity(unit: unit, quantity: maximumQuantity), forKey: "maximumQuantity")
901
916
  }
902
917
  if let minimumQuantity = gottenStats.minimumQuantity() {
903
- dic.updateValue(serializeQuantity(unit: unit, quantity: minimumQuantity), forKey: "minimumQuantity")
918
+ dic.updateValue(
919
+ serializeQuantity(unit: unit, quantity: minimumQuantity), forKey: "minimumQuantity")
904
920
  }
905
921
  if let sumQuantity = gottenStats.sumQuantity() {
906
922
  dic.updateValue(serializeQuantity(unit: unit, quantity: sumQuantity), forKey: "sumQuantity")
907
923
  }
908
924
  if #available(iOS 12, *) {
909
925
  if let mostRecent = gottenStats.mostRecentQuantity() {
910
- dic.updateValue(serializeQuantity(unit: unit, quantity: mostRecent), forKey: "mostRecentQuantity")
926
+ dic.updateValue(
927
+ serializeQuantity(unit: unit, quantity: mostRecent), forKey: "mostRecentQuantity")
911
928
  }
912
929
 
913
930
  if let mostRecentDateInterval = gottenStats.mostRecentQuantityDateInterval() {
914
- dic.updateValue([
915
- "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
916
- "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
917
- ], forKey: "mostRecentQuantityDateInterval")
931
+ dic.updateValue(
932
+ [
933
+ "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
934
+ "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
935
+ ], forKey: "mostRecentQuantityDateInterval")
918
936
  }
919
937
  }
920
938
  if #available(iOS 13, *) {
921
939
  let durationUnit = HKUnit.second()
922
- if let duration = gottenStats.duration() {
923
- dic.updateValue(serializeQuantity(unit: durationUnit, quantity: duration), forKey: "duration")
924
- }
940
+ dic["duration"] = serializeQuantityIfExists(
941
+ unit: durationUnit, quantity: gottenStats.duration())
925
942
  }
926
943
 
927
944
  resolve(dic)
@@ -930,83 +947,97 @@ class ReactNativeHealthkit: RCTEventEmitter {
930
947
  store.execute(query)
931
948
  }
932
949
 
933
- @objc(queryStatisticsCollectionForQuantity:unitString:options:anchorDate:interval:startDate:endDate:resolve:reject:)
950
+ @objc(
951
+ queryStatisticsCollectionForQuantity:unitString:options:anchorDate:interval:startDate:endDate:
952
+ resolve:reject:
953
+ )
934
954
  func queryStatisticsCollectionForQuantity(
935
- typeIdentifier: String,
936
- unitString: String,
937
- options: NSArray,
938
- anchorDate: Date,
939
- interval: NSDictionary,
940
- startDate: Date,
941
- endDate: Date,
942
- resolve: @escaping RCTPromiseResolveBlock,
943
- reject: @escaping RCTPromiseRejectBlock
955
+ typeIdentifier: String,
956
+ unitString: String,
957
+ options: NSArray,
958
+ anchorDate: Date,
959
+ interval: NSDictionary,
960
+ startDate: Date,
961
+ endDate: Date,
962
+ resolve: @escaping RCTPromiseResolveBlock,
963
+ reject: @escaping RCTPromiseRejectBlock
944
964
  ) {
945
- guard let store = _store else {
946
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
947
- }
948
-
949
- guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier(rawValue: typeIdentifier)) else {
950
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize quantity type for identifier: \(typeIdentifier)", nil)
951
- }
952
-
953
- let opts = hkStatisticsOptionsFromOptions(options)
954
- let intervalComponents = componentsFromInterval(interval)
965
+ guard let store = _store else {
966
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
967
+ }
955
968
 
956
- let query = HKStatisticsCollectionQuery(
957
- quantityType: quantityType,
958
- quantitySamplePredicate: nil, // We will use enumerateStatistics to filter with date
959
- options: opts,
960
- anchorDate: anchorDate,
961
- intervalComponents: intervalComponents
962
- )
969
+ guard
970
+ let quantityType = HKObjectType.quantityType(
971
+ forIdentifier: HKQuantityTypeIdentifier(rawValue: typeIdentifier))
972
+ else {
973
+ return reject(
974
+ TYPE_IDENTIFIER_ERROR,
975
+ "Failed to initialize quantity type for identifier: \(typeIdentifier)", nil)
976
+ }
963
977
 
964
- query.initialResultsHandler = { _, statsCollection, error in
965
- if let error = error {
966
- return reject(QUERY_ERROR, error.localizedDescription, error)
967
- }
978
+ let opts = hkStatisticsOptionsFromOptions(options)
979
+ let intervalComponents = componentsFromInterval(interval)
968
980
 
969
- var results = [[String: [String: Any]?]]()
981
+ let query = HKStatisticsCollectionQuery(
982
+ quantityType: quantityType,
983
+ quantitySamplePredicate: nil, // We will use enumerateStatistics to filter with date
984
+ options: opts,
985
+ anchorDate: anchorDate,
986
+ intervalComponents: intervalComponents
987
+ )
970
988
 
971
- guard let collection = statsCollection else {
972
- return resolve(results)
973
- }
989
+ query.initialResultsHandler = { _, statsCollection, error in
990
+ if let error = error {
991
+ return reject(QUERY_ERROR, error.localizedDescription, error)
992
+ }
974
993
 
975
- let unit = HKUnit(from: unitString)
994
+ var results = [[String: [String: Any]?]]()
976
995
 
977
- collection.enumerateStatistics(from: startDate, to: endDate) { stats, _ in
978
- var dic = [String: [String: Any]?]()
996
+ guard let collection = statsCollection else {
997
+ return resolve(results)
998
+ }
979
999
 
980
- let startDate = self._dateFormatter.string(from: stats.startDate)
981
- let endDate = self._dateFormatter.string(from: stats.endDate)
1000
+ let unit = HKUnit(from: unitString)
982
1001
 
983
- dic["averageQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.averageQuantity())
984
- dic["maximumQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.maximumQuantity())
985
- dic["minimumQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.minimumQuantity())
986
- dic["sumQuantity"] = serializeStatisticIfExists(unit: unit, quantity: stats.sumQuantity(), stats: stats)
1002
+ collection.enumerateStatistics(from: startDate, to: endDate) { stats, _ in
1003
+ var dic = [String: [String: Any]?]()
987
1004
 
988
- if #available(iOS 12, *) {
989
- dic["mostRecentQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.mostRecentQuantity())
990
- if let mostRecentDateInterval = stats.mostRecentQuantityDateInterval() {
991
- dic["mostRecentQuantityDateInterval"] = [
992
- "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
993
- "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
994
- ]
995
- }
996
- }
1005
+ let startDate = self._dateFormatter.string(from: stats.startDate)
1006
+ let endDate = self._dateFormatter.string(from: stats.endDate)
997
1007
 
998
- if #available(iOS 13, *) {
999
- let durationUnit = HKUnit.second()
1000
- dic["duration"] = serializeQuantityIfExists(unit: durationUnit, quantity: stats.duration())
1001
- }
1008
+ dic["averageQuantity"] = serializeQuantityIfExists(
1009
+ unit: unit, quantity: stats.averageQuantity())
1010
+ dic["maximumQuantity"] = serializeQuantityIfExists(
1011
+ unit: unit, quantity: stats.maximumQuantity())
1012
+ dic["minimumQuantity"] = serializeQuantityIfExists(
1013
+ unit: unit, quantity: stats.minimumQuantity())
1014
+ dic["sumQuantity"] = serializeStatisticIfExists(
1015
+ unit: unit, quantity: stats.sumQuantity(), stats: stats)
1002
1016
 
1003
- results.append(dic)
1017
+ if #available(iOS 12, *) {
1018
+ dic["mostRecentQuantity"] = serializeQuantityIfExists(
1019
+ unit: unit, quantity: stats.mostRecentQuantity())
1020
+ if let mostRecentDateInterval = stats.mostRecentQuantityDateInterval() {
1021
+ dic["mostRecentQuantityDateInterval"] = [
1022
+ "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
1023
+ "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
1024
+ ]
1004
1025
  }
1026
+ }
1027
+
1028
+ if #available(iOS 13, *) {
1029
+ let durationUnit = HKUnit.second()
1030
+ dic["duration"] = serializeQuantityIfExists(
1031
+ unit: durationUnit, quantity: stats.duration())
1032
+ }
1005
1033
 
1006
- resolve(results)
1034
+ results.append(dic)
1007
1035
  }
1008
1036
 
1009
- store.execute(query)
1037
+ resolve(results)
1038
+ }
1039
+
1040
+ store.execute(query)
1010
1041
  }
1011
1042
 
1012
1043
  func mapWorkout(
@@ -1021,9 +1052,12 @@ class ReactNativeHealthkit: RCTEventEmitter {
1021
1052
  "uuid": workout.uuid.uuidString,
1022
1053
  "device": serializeDevice(_device: workout.device) as Any,
1023
1054
  "duration": workout.duration,
1024
- "totalDistance": serializeQuantity(unit: distanceUnit, quantity: workout.totalDistance) as Any,
1025
- "totalEnergyBurned": serializeQuantity(unit: energyUnit, quantity: workout.totalEnergyBurned) as Any,
1026
- "totalSwimmingStrokeCount": serializeQuantity(unit: HKUnit.count(), quantity: workout.totalSwimmingStrokeCount) as Any,
1055
+ "totalDistance": serializeQuantity(unit: distanceUnit, quantity: workout.totalDistance)
1056
+ as Any,
1057
+ "totalEnergyBurned": serializeQuantity(unit: energyUnit, quantity: workout.totalEnergyBurned)
1058
+ as Any,
1059
+ "totalSwimmingStrokeCount": serializeQuantity(
1060
+ unit: HKUnit.count(), quantity: workout.totalSwimmingStrokeCount) as Any,
1027
1061
  "workoutActivityType": workout.workoutActivityType.rawValue,
1028
1062
  "startDate": startDate,
1029
1063
  "endDate": endDate,
@@ -1039,7 +1073,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
1039
1073
  let eventStartDate = self._dateFormatter.string(from: event.dateInterval.start)
1040
1074
  let eventEndDate = self._dateFormatter.string(from: event.dateInterval.end)
1041
1075
  let eventDict: [String: Any] = [
1042
- "type": event.type.rawValue, // https://developer.apple.com/documentation/healthkit/hkworkouteventtype
1076
+ "type": event.type.rawValue, // https://developer.apple.com/documentation/healthkit/hkworkouteventtype
1043
1077
  "startDate": eventStartDate,
1044
1078
  "endDate": eventEndDate
1045
1079
  ]
@@ -1078,7 +1112,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
1078
1112
  dict["activities"] = activitiesArray
1079
1113
 
1080
1114
  if #available(iOS 11, *) {
1081
- dict.setValue(serializeQuantity(unit: HKUnit.count(), quantity: workout.totalFlightsClimbed), forKey: "totalFlightsClimbed")
1115
+ dict.setValue(
1116
+ serializeQuantity(unit: HKUnit.count(), quantity: workout.totalFlightsClimbed),
1117
+ forKey: "totalFlightsClimbed")
1082
1118
  }
1083
1119
  return dict
1084
1120
  }
@@ -1167,13 +1203,16 @@ class ReactNativeHealthkit: RCTEventEmitter {
1167
1203
 
1168
1204
  let actualAnchor = deserializeHKQueryAnchor(anchor: anchor)
1169
1205
 
1170
- let q = HKAnchoredObjectQuery(type: .workoutType(), predicate: predicate, anchor: actualAnchor, limit: limit) { (
1171
- _: HKAnchoredObjectQuery,
1172
- s: [HKSample]?,
1173
- deletedSamples: [HKDeletedObject]?,
1174
- newAnchor: HKQueryAnchor?,
1175
- error: Error?
1176
- ) in
1206
+ let q = HKAnchoredObjectQuery(
1207
+ type: .workoutType(), predicate: predicate, anchor: actualAnchor, limit: limit
1208
+ ) {
1209
+ (
1210
+ _: HKAnchoredObjectQuery,
1211
+ s: [HKSample]?,
1212
+ deletedSamples: [HKDeletedObject]?,
1213
+ newAnchor: HKQueryAnchor?,
1214
+ error: Error?
1215
+ ) in
1177
1216
  guard let err = error else {
1178
1217
  guard let samples = s else {
1179
1218
  return resolve([])
@@ -1246,7 +1285,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
1246
1285
 
1247
1286
  for s in samples {
1248
1287
  if let sample = s as? HKQuantitySample {
1249
- let serialized = serializeQuantitySample(sample: sample, unit: HKUnit.init(from: unitString))
1288
+ let serialized = serializeQuantitySample(
1289
+ sample: sample, unit: HKUnit.init(from: unitString))
1250
1290
 
1251
1291
  arr.add(serialized)
1252
1292
  }
@@ -1365,7 +1405,10 @@ class ReactNativeHealthkit: RCTEventEmitter {
1365
1405
  let predicate = createPredicate(from: from, to: to)
1366
1406
  let limit = limitOrNilIfZero(limit: limit)
1367
1407
 
1368
- let q = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: limit, sortDescriptors: getSortDescriptors(ascending: ascending)) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
1408
+ let q = HKSampleQuery(
1409
+ sampleType: sampleType, predicate: predicate, limit: limit,
1410
+ sortDescriptors: getSortDescriptors(ascending: ascending)
1411
+ ) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
1369
1412
  guard let err = error else {
1370
1413
  guard let samples = sample else {
1371
1414
  return resolve([])
@@ -1420,13 +1463,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
1420
1463
  predicate: predicate,
1421
1464
  anchor: actualAnchor,
1422
1465
  limit: limit
1423
- ) { (
1424
- _: HKAnchoredObjectQuery,
1425
- s: [HKSample]?,
1426
- deletedSamples: [HKDeletedObject]?,
1427
- newAnchor: HKQueryAnchor?,
1428
- error: Error?
1429
- ) in
1466
+ ) {
1467
+ (
1468
+ _: HKAnchoredObjectQuery,
1469
+ s: [HKSample]?,
1470
+ deletedSamples: [HKDeletedObject]?,
1471
+ newAnchor: HKQueryAnchor?,
1472
+ error: Error?
1473
+ ) in
1430
1474
  guard let err = error else {
1431
1475
  guard let samples = s else {
1432
1476
  return resolve([])
@@ -1482,13 +1526,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
1482
1526
  predicate: predicate,
1483
1527
  anchor: anchor != "" ? base64StringToHKQueryAnchor(base64String: anchor) : nil,
1484
1528
  limit: limit
1485
- ) { (
1486
- _: HKAnchoredObjectQuery,
1487
- s: [HKSample]?,
1488
- deletedSamples: [HKDeletedObject]?,
1489
- newAnchor: HKQueryAnchor?,
1490
- error: Error?
1491
- ) in
1529
+ ) {
1530
+ (
1531
+ _: HKAnchoredObjectQuery,
1532
+ s: [HKSample]?,
1533
+ deletedSamples: [HKDeletedObject]?,
1534
+ newAnchor: HKQueryAnchor?,
1535
+ error: Error?
1536
+ ) in
1492
1537
  guard let err = error else {
1493
1538
  guard let samples = s else {
1494
1539
  return resolve([])
@@ -1536,11 +1581,12 @@ class ReactNativeHealthkit: RCTEventEmitter {
1536
1581
  let query = HKSourceQuery(
1537
1582
  sampleType: type as! HKSampleType,
1538
1583
  samplePredicate: nil
1539
- ) { (
1540
- _: HKSourceQuery,
1541
- source: Set<HKSource>?,
1542
- error: Error?
1543
- ) in
1584
+ ) {
1585
+ (
1586
+ _: HKSourceQuery,
1587
+ source: Set<HKSource>?,
1588
+ error: Error?
1589
+ ) in
1544
1590
  guard let err = error else {
1545
1591
  guard let sources = source else {
1546
1592
  return resolve([])
@@ -1585,8 +1631,10 @@ class ReactNativeHealthkit: RCTEventEmitter {
1585
1631
  }
1586
1632
 
1587
1633
  @available(iOS 13.0.0, *)
1588
- func getWorkoutByID(store: HKHealthStore,
1589
- workoutUUID: UUID) async -> HKWorkout? {
1634
+ func getWorkoutByID(
1635
+ store: HKHealthStore,
1636
+ workoutUUID: UUID
1637
+ ) async -> HKWorkout? {
1590
1638
  let workoutPredicate = HKQuery.predicateForObject(with: workoutUUID)
1591
1639
 
1592
1640
  let samples = try! await withCheckedThrowingContinuation {
@@ -1698,28 +1746,30 @@ class ReactNativeHealthkit: RCTEventEmitter {
1698
1746
  func getSerializedWorkoutLocations(
1699
1747
  store: HKHealthStore,
1700
1748
  workoutUUID: UUID
1701
- ) async -> [Dictionary<String, Any>]? {
1749
+ ) async -> [[String: Any]]? {
1702
1750
  let routes = await _getWorkoutRoutes(
1703
1751
  store: store,
1704
1752
  workoutUUID: workoutUUID
1705
1753
  )
1706
1754
 
1707
- var allRoutes: [Dictionary<String, Any>] = []
1755
+ var allRoutes: [[String: Any]] = []
1708
1756
  guard let _routes = routes else {
1709
1757
  return nil
1710
1758
  }
1711
1759
  for route in _routes {
1712
- let routeMetadata = serializeMetadata(
1713
- metadata: route.metadata
1714
- ) as! [String: Any]
1760
+ let routeMetadata =
1761
+ serializeMetadata(
1762
+ metadata: route.metadata
1763
+ ) as! [String: Any]
1715
1764
  let routeCLLocations = await getRouteLocations(
1716
1765
  store: store,
1717
1766
  route: route
1718
1767
  )
1719
1768
  let routeLocations = routeCLLocations.enumerated().map {
1720
- (i, loc) in serializeLocation(
1769
+ (i, loc) in
1770
+ serializeLocation(
1721
1771
  location: loc,
1722
- previousLocation: i == 0 ? nil: routeCLLocations[i - 1]
1772
+ previousLocation: i == 0 ? nil : routeCLLocations[i - 1]
1723
1773
  )
1724
1774
  }
1725
1775
  let routeInfos: [String: Any] = ["locations": routeLocations]
@@ -1731,59 +1781,60 @@ class ReactNativeHealthkit: RCTEventEmitter {
1731
1781
 
1732
1782
  @available(iOS 17.0.0, *)
1733
1783
  func getWorkoutPlan(workout: HKWorkout) async -> [String: Any]? {
1734
- #if canImport(WorkoutKit)
1735
- do {
1736
- let workoutPlan = try await workout.workoutPlan
1784
+ #if canImport(WorkoutKit)
1785
+ do {
1786
+ let workoutPlan = try await workout.workoutPlan
1737
1787
 
1738
- var dict = [String: Any]()
1739
- if (workoutPlan?.id) != nil {
1740
- dict["id"] = workoutPlan?.id.uuidString
1788
+ var dict = [String: Any]()
1789
+ if (workoutPlan?.id) != nil {
1790
+ dict["id"] = workoutPlan?.id.uuidString
1741
1791
 
1742
- }
1743
- if (workoutPlan?.workout.activity) != nil {
1744
- dict["activityType"] = workoutPlan?.workout.activity.rawValue
1745
- }
1792
+ }
1793
+ if (workoutPlan?.workout.activity) != nil {
1794
+ dict["activityType"] = workoutPlan?.workout.activity.rawValue
1795
+ }
1746
1796
 
1747
- if dict.isEmpty {
1797
+ if dict.isEmpty {
1798
+ return nil
1799
+ }
1800
+ return dict
1801
+ } catch {
1748
1802
  return nil
1749
1803
  }
1750
- return dict
1751
- } catch {
1804
+ #else
1752
1805
  return nil
1753
- }
1754
- #else
1755
- return nil
1756
- #endif
1806
+ #endif
1757
1807
  }
1758
1808
 
1759
1809
  @objc(getWorkoutPlanById:resolve:reject:)
1760
1810
  func getWorkoutPlanById(
1761
1811
  workoutUUID: String,
1762
1812
  resolve: @escaping RCTPromiseResolveBlock,
1763
- reject: @escaping RCTPromiseRejectBlock) {
1813
+ reject: @escaping RCTPromiseRejectBlock
1814
+ ) {
1764
1815
  if #available(iOS 17.0, *) {
1765
- #if canImport(WorkoutKit)
1766
- guard let store = _store else {
1767
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1768
- }
1816
+ #if canImport(WorkoutKit)
1817
+ guard let store = _store else {
1818
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1819
+ }
1769
1820
 
1770
- Task {
1771
- if let uuid = UUID(uuidString: workoutUUID) {
1772
- let workout = await self.getWorkoutByID(store: store, workoutUUID: uuid)
1773
- if let workout {
1774
- let workoutPlan = await self.getWorkoutPlan(workout: workout)
1821
+ Task {
1822
+ if let uuid = UUID(uuidString: workoutUUID) {
1823
+ let workout = await self.getWorkoutByID(store: store, workoutUUID: uuid)
1824
+ if let workout {
1825
+ let workoutPlan = await self.getWorkoutPlan(workout: workout)
1775
1826
 
1776
- return resolve(workoutPlan)
1827
+ return resolve(workoutPlan)
1828
+ } else {
1829
+ return reject(GENERIC_ERROR, "No workout found", nil)
1830
+ }
1777
1831
  } else {
1778
- return reject(GENERIC_ERROR, "No workout found", nil)
1832
+ return reject(GENERIC_ERROR, "Invalid UUID", nil)
1779
1833
  }
1780
- } else {
1781
- return reject(GENERIC_ERROR, "Invalid UUID", nil)
1782
1834
  }
1783
- }
1784
- #else
1785
- return resolve(nil)
1786
- #endif
1835
+ #else
1836
+ return resolve(nil)
1837
+ #endif
1787
1838
  } else {
1788
1839
  return resolve(nil)
1789
1840
  }
@@ -1814,25 +1865,26 @@ class ReactNativeHealthkit: RCTEventEmitter {
1814
1865
  func getWorkoutRoutes(
1815
1866
  workoutUUID: String,
1816
1867
  resolve: @escaping RCTPromiseResolveBlock,
1817
- reject: @escaping RCTPromiseRejectBlock) {
1868
+ reject: @escaping RCTPromiseRejectBlock
1869
+ ) {
1818
1870
 
1819
- guard let store = _store else {
1820
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1821
- }
1871
+ guard let store = _store else {
1872
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1873
+ }
1822
1874
 
1823
- guard let _workoutUUID = UUID(uuidString: workoutUUID) else {
1824
- return reject("INVALID_UUID_ERROR", "Invalid UUID received", nil)
1825
- }
1875
+ guard let _workoutUUID = UUID(uuidString: workoutUUID) else {
1876
+ return reject("INVALID_UUID_ERROR", "Invalid UUID received", nil)
1877
+ }
1826
1878
 
1827
- Task {
1828
- do {
1829
- let locations = await getSerializedWorkoutLocations(store: store, workoutUUID: _workoutUUID)
1830
- resolve(locations)
1831
- } catch {
1832
- reject("WORKOUT_LOCATION_ERROR", "Failed to retrieve workout locations", nil)
1833
- }
1879
+ Task {
1880
+ do {
1881
+ let locations = await getSerializedWorkoutLocations(store: store, workoutUUID: _workoutUUID)
1882
+ resolve(locations)
1883
+ } catch {
1884
+ reject("WORKOUT_LOCATION_ERROR", "Failed to retrieve workout locations", nil)
1834
1885
  }
1835
1886
  }
1887
+ }
1836
1888
 
1837
1889
  typealias HKAnchoredObjectQueryResult = (
1838
1890
  samples: [HKSample],
@@ -1854,13 +1906,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
1854
1906
  predicate: predicate,
1855
1907
  anchor: anchor,
1856
1908
  limit: limit
1857
- ) { (
1858
- _: HKAnchoredObjectQuery,
1859
- s: [HKSample]?,
1860
- deletedSamples: [HKDeletedObject]?,
1861
- newAnchor: HKQueryAnchor?,
1862
- error: Error?
1863
- ) in
1909
+ ) {
1910
+ (
1911
+ _: HKAnchoredObjectQuery,
1912
+ s: [HKSample]?,
1913
+ deletedSamples: [HKDeletedObject]?,
1914
+ newAnchor: HKQueryAnchor?,
1915
+ error: Error?
1916
+ ) in
1864
1917
  if let err = error {
1865
1918
  return continuation.resume(throwing: err)
1866
1919
  } else {
@@ -1887,18 +1940,19 @@ class ReactNativeHealthkit: RCTEventEmitter {
1887
1940
  func getHeartbeatSeriesHeartbeats(
1888
1941
  store: HKHealthStore,
1889
1942
  sample: HKHeartbeatSeriesSample
1890
- ) async throws -> [Dictionary<String, Any>] {
1943
+ ) async throws -> [[String: Any]] {
1891
1944
  let beatTimes = try await withCheckedThrowingContinuation {
1892
- (continuation: CheckedContinuation<[Dictionary<String, Any>], Error>) in
1893
- var allBeats: [Dictionary<String, Any>] = []
1894
-
1895
- let query = HKHeartbeatSeriesQuery(heartbeatSeries: sample) { (
1896
- _: HKHeartbeatSeriesQuery,
1897
- timeSinceSeriesStart: TimeInterval,
1898
- precededByGap: Bool,
1899
- done: Bool,
1900
- error: Error?
1901
- ) in
1945
+ (continuation: CheckedContinuation<[[String: Any]], Error>) in
1946
+ var allBeats: [[String: Any]] = []
1947
+
1948
+ let query = HKHeartbeatSeriesQuery(heartbeatSeries: sample) {
1949
+ (
1950
+ _: HKHeartbeatSeriesQuery,
1951
+ timeSinceSeriesStart: TimeInterval,
1952
+ precededByGap: Bool,
1953
+ done: Bool,
1954
+ error: Error?
1955
+ ) in
1902
1956
  if let err = error {
1903
1957
  return continuation.resume(throwing: err)
1904
1958
  } else {
@@ -1975,7 +2029,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
1975
2029
  anchor: actualAnchor
1976
2030
  )
1977
2031
 
1978
- var allHeartbeatSamples: [Dictionary<String, Any>] = []
2032
+ var allHeartbeatSamples: [[String: Any]] = []
1979
2033
  for sample in queryResult.samples as! [HKHeartbeatSeriesSample] {
1980
2034
  allHeartbeatSamples.append(
1981
2035
  try await getSerializedHeartbeatSeriesSample(
@@ -2012,7 +2066,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
2012
2066
  sampleType: HKSeriesType.heartbeat(),
2013
2067
  predicate: predicate,
2014
2068
  limit: limit,
2015
- sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)]
2069
+ sortDescriptors: [
2070
+ NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)
2071
+ ]
2016
2072
  ) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
2017
2073
  if let err = error {
2018
2074
  continuation.resume(throwing: err)
@@ -2065,7 +2121,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
2065
2121
  ascending: ascending
2066
2122
  )
2067
2123
 
2068
- var allHeartbeatSamples: [Dictionary<String, Any>] = []
2124
+ var allHeartbeatSamples: [[String: Any]] = []
2069
2125
  for sample in samples as! [HKHeartbeatSeriesSample] {
2070
2126
  allHeartbeatSamples.append(
2071
2127
  try await getSerializedHeartbeatSeriesSample(
@@ -2105,4 +2161,96 @@ class ReactNativeHealthkit: RCTEventEmitter {
2105
2161
  }
2106
2162
  }
2107
2163
 
2164
+ @objc(queryStateOfMindSamples:to:limit:ascending:resolve:reject:)
2165
+ func queryStateOfMindSamples(
2166
+ from: Date,
2167
+ to: Date,
2168
+ limit: Int,
2169
+ ascending: Bool,
2170
+ resolve: @escaping RCTPromiseResolveBlock,
2171
+ reject: @escaping RCTPromiseRejectBlock
2172
+ ) {
2173
+ #if compiler(>=6)
2174
+ if #available(iOS 18.0, *) {
2175
+ guard let store = _store else {
2176
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
2177
+ }
2178
+
2179
+ Task {
2180
+ let from = dateOrNilIfZero(date: from)
2181
+ let to = dateOrNilIfZero(date: to)
2182
+
2183
+ let predicate = createPredicate(
2184
+ from: from,
2185
+ to: to
2186
+ )
2187
+
2188
+ let limit = limitOrNilIfZero(
2189
+ limit: limit
2190
+ )
2191
+
2192
+ let type = HKStateOfMindType.stateOfMindType()
2193
+
2194
+ let query = HKSampleQuery(
2195
+ sampleType: type,
2196
+ predicate: predicate,
2197
+ limit: limit,
2198
+ sortDescriptors: [
2199
+ NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)
2200
+ ]
2201
+ ) { (_, samples, error) in
2202
+ if let error = error {
2203
+ reject(GENERIC_ERROR, error.localizedDescription, error)
2204
+ return
2205
+ }
2206
+
2207
+ guard let samples = samples as? [HKStateOfMind] else {
2208
+ resolve([])
2209
+ return
2210
+ }
2211
+
2212
+ let serializedSamples = samples.map { sample in
2213
+ var associations: [Int] = []
2214
+ if #available(iOS 18.0, *) {
2215
+ associations = sample.associations.map({ association in
2216
+ return association.rawValue
2217
+ })
2218
+ }
2219
+
2220
+ var labels: [Int] = []
2221
+ if #available(iOS 18.0, *) {
2222
+ labels = sample.labels.map({ label in
2223
+ return label.rawValue
2224
+ })
2225
+ }
2226
+
2227
+ return [
2228
+ "uuid": sample.uuid.uuidString,
2229
+ "device": serializeDevice(_device: sample.device) as Any,
2230
+ "startDate": self._dateFormatter.string(from: sample.startDate),
2231
+ "endDate": self._dateFormatter.string(from: sample.endDate),
2232
+ "valence": sample.valence,
2233
+ "kind": sample.kind.rawValue,
2234
+ "valenceClassification": sample.valenceClassification.rawValue,
2235
+ "associations": associations,
2236
+ "labels": labels,
2237
+ "metadata": serializeMetadata(metadata: sample.metadata),
2238
+ "sourceRevision": serializeSourceRevision(_sourceRevision: sample.sourceRevision)
2239
+ as Any
2240
+ ]
2241
+ }
2242
+
2243
+ resolve(serializedSamples)
2244
+ }
2245
+
2246
+ store.execute(query)
2247
+ }
2248
+ } else {
2249
+ reject("STATE_OF_MIND_ERROR", "State of Mind features require iOS 18.0 or later", nil)
2250
+ }
2251
+ #else
2252
+ reject(
2253
+ "STATE_OF_MIND_ERROR", "State of Mind features require Xcode 16 or later to compile", nil)
2254
+ #endif
2255
+ }
2108
2256
  }