@kingstinct/react-native-healthkit 4.1.1 → 4.4.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 (41) hide show
  1. package/README.md +1 -1
  2. package/ios/ReactNativeHealthkit.m +15 -10
  3. package/ios/ReactNativeHealthkit.swift +365 -177
  4. package/lib/commonjs/index.ios.js +104 -106
  5. package/lib/commonjs/index.ios.js.map +1 -1
  6. package/lib/commonjs/index.js +11 -10
  7. package/lib/commonjs/index.js.map +1 -1
  8. package/lib/commonjs/native-types.js +25 -1
  9. package/lib/commonjs/native-types.js.map +1 -1
  10. package/lib/commonjs/types.js +4 -0
  11. package/lib/commonjs/types.js.map +1 -1
  12. package/lib/example/App.js +197 -0
  13. package/lib/index.ios.js +310 -0
  14. package/lib/index.js +44 -0
  15. package/lib/module/index.ios.js +104 -106
  16. package/lib/module/index.ios.js.map +1 -1
  17. package/lib/module/index.js +4 -3
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/native-types.js +20 -1
  20. package/lib/module/native-types.js.map +1 -1
  21. package/lib/module/types.js +1 -1
  22. package/lib/module/types.js.map +1 -1
  23. package/lib/native-types.js +447 -0
  24. package/lib/src/index.ios.js +314 -0
  25. package/lib/src/index.js +45 -0
  26. package/lib/src/native-types.js +453 -0
  27. package/lib/src/types.js +1 -0
  28. package/lib/types.js +1 -0
  29. package/lib/typescript/src/index.d.ts +1 -1
  30. package/lib/typescript/src/index.ios.d.ts +1 -1
  31. package/lib/typescript/src/native-types.d.ts +142 -108
  32. package/lib/typescript/src/types.d.ts +80 -78
  33. package/package.json +25 -39
  34. package/src/index.ios.tsx +260 -264
  35. package/src/index.tsx +14 -13
  36. package/src/native-types.ts +213 -179
  37. package/src/types.ts +106 -100
  38. package/ios/ReactNativeHealthkit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -4
  39. package/ios/ReactNativeHealthkit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  40. package/ios/ReactNativeHealthkit.xcodeproj/project.xcworkspace/xcuserdata/robertherber.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  41. package/ios/ReactNativeHealthkit.xcodeproj/xcuserdata/robertherber.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
@@ -1,18 +1,24 @@
1
1
  import HealthKit;
2
- import Combine;
2
+ import CoreLocation;
3
3
 
4
4
  let INIT_ERROR = "HEALTHKIT_INIT_ERROR"
5
5
  let INIT_ERROR_MESSAGE = "HealthKit not initialized"
6
6
  let TYPE_IDENTIFIER_ERROR = "HEALTHKIT_TYPE_IDENTIFIER_NOT_RECOGNIZED_ERROR"
7
- let GENERIC_ERROR = "HEALTHKIT_ERROR";
7
+ let GENERIC_ERROR = "HEALTHKIT_ERROR"
8
8
 
9
9
  let HKCharacteristicTypeIdentifier_PREFIX = "HKCharacteristicTypeIdentifier"
10
10
  let HKQuantityTypeIdentifier_PREFIX = "HKQuantityTypeIdentifier"
11
11
  let HKCategoryTypeIdentifier_PREFIX = "HKCategoryTypeIdentifier"
12
12
  let HKCorrelationTypeIdentifier_PREFIX = "HKCorrelationTypeIdentifier"
13
13
  let HKActivitySummaryTypeIdentifier = "HKActivitySummaryTypeIdentifier"
14
- let HKAudiogramTypeIdentifier = "HKAudiogramTypeIdentifier";
14
+ let HKAudiogramTypeIdentifier = "HKAudiogramTypeIdentifier"
15
15
  let HKWorkoutTypeIdentifier = "HKWorkoutTypeIdentifier"
16
+ let HKWorkoutRouteTypeIdentifier = "HKWorkoutRouteTypeIdentifier"
17
+ let HKDataTypeIdentifierHeartbeatSeries = "HKDataTypeIdentifierHeartbeatSeries"
18
+
19
+ let SpeedUnit = HKUnit(from: "m/s") // HKUnit.meter().unitDivided(by: HKUnit.second())
20
+ // Support for MET data: HKAverageMETs 8.24046 kcal/hr·kg
21
+ let METUnit = HKUnit(from: "kcal/hr·kg")
16
22
 
17
23
  @objc(ReactNativeHealthkit)
18
24
  @available(iOS 10.0, *)
@@ -21,17 +27,17 @@ class ReactNativeHealthkit: RCTEventEmitter {
21
27
  var _runningQueries : Dictionary<String, HKQuery>;
22
28
  var _dateFormatter : ISO8601DateFormatter;
23
29
  var _hasListeners = false
24
-
30
+
25
31
  override init() {
26
32
  self._runningQueries = Dictionary<String, HKQuery>();
27
33
  self._dateFormatter = ISO8601DateFormatter();
28
-
34
+
29
35
  if(HKHealthStore.isHealthDataAvailable()){
30
36
  self._store = HKHealthStore.init();
31
37
  }
32
38
  super.init();
33
39
  }
34
-
40
+
35
41
  deinit {
36
42
  if let store = _store {
37
43
  for query in self._runningQueries {
@@ -39,7 +45,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
39
45
  }
40
46
  }
41
47
  }
42
-
48
+
43
49
  override func stopObserving() -> Void {
44
50
  self._hasListeners = false
45
51
  if let store = _store {
@@ -48,78 +54,88 @@ class ReactNativeHealthkit: RCTEventEmitter {
48
54
  }
49
55
  }
50
56
  }
51
-
57
+
52
58
  override func startObserving() -> Void {
53
59
  self._hasListeners = true
54
60
  }
55
-
61
+
56
62
  func objectTypeFromString(typeIdentifier: String) -> HKObjectType? {
57
63
  if(typeIdentifier.starts(with: HKCharacteristicTypeIdentifier_PREFIX)){
58
64
  let identifier = HKCharacteristicTypeIdentifier.init(rawValue: typeIdentifier);
59
65
  return HKObjectType.characteristicType(forIdentifier: identifier) as HKObjectType?
60
66
  }
61
-
67
+
62
68
  if(typeIdentifier.starts(with: HKQuantityTypeIdentifier_PREFIX)){
63
69
  let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier);
64
70
  return HKObjectType.quantityType(forIdentifier: identifier) as HKObjectType?
65
71
  }
66
-
72
+
67
73
  if(typeIdentifier.starts(with: HKCategoryTypeIdentifier_PREFIX)){
68
74
  let identifier = HKCategoryTypeIdentifier.init(rawValue: typeIdentifier);
69
75
  return HKObjectType.categoryType(forIdentifier: identifier) as HKObjectType?
70
76
  }
71
-
77
+
72
78
  if(typeIdentifier.starts(with: HKCorrelationTypeIdentifier_PREFIX)){
73
79
  let identifier = HKCorrelationTypeIdentifier.init(rawValue: typeIdentifier);
74
80
  return HKObjectType.correlationType(forIdentifier: identifier) as HKObjectType?
75
81
  }
76
-
82
+
77
83
  if(typeIdentifier == HKActivitySummaryTypeIdentifier){
78
84
  return HKObjectType.activitySummaryType();
79
85
  }
80
-
86
+
81
87
  if #available(iOS 13, *) {
82
88
  if(typeIdentifier == HKAudiogramTypeIdentifier){
83
89
  return HKObjectType.audiogramSampleType();
84
90
  }
91
+
92
+ if(typeIdentifier == HKDataTypeIdentifierHeartbeatSeries){
93
+ return HKObjectType.seriesType(forIdentifier: typeIdentifier);
94
+ }
85
95
  }
86
-
96
+
87
97
  if(typeIdentifier == HKWorkoutTypeIdentifier){
88
98
  return HKObjectType.workoutType()
89
99
  }
90
-
100
+
101
+ if #available(iOS 11.0, *){
102
+ if typeIdentifier == HKWorkoutRouteTypeIdentifier{
103
+ return HKObjectType.seriesType(forIdentifier: typeIdentifier);
104
+ }
105
+ }
106
+
91
107
  return nil;
92
108
  }
93
-
109
+
94
110
  func sampleTypeFromString(typeIdentifier: String) -> HKSampleType? {
95
111
  if(typeIdentifier.starts(with: HKQuantityTypeIdentifier_PREFIX)){
96
112
  let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier);
97
113
  return HKSampleType.quantityType(forIdentifier: identifier) as HKSampleType?
98
114
  }
99
-
115
+
100
116
  if(typeIdentifier.starts(with: HKCategoryTypeIdentifier_PREFIX)){
101
117
  let identifier = HKCategoryTypeIdentifier.init(rawValue: typeIdentifier);
102
118
  return HKSampleType.categoryType(forIdentifier: identifier) as HKSampleType?
103
119
  }
104
-
120
+
105
121
  if(typeIdentifier.starts(with: HKCorrelationTypeIdentifier_PREFIX)){
106
122
  let identifier = HKCorrelationTypeIdentifier.init(rawValue: typeIdentifier);
107
123
  return HKSampleType.correlationType(forIdentifier: identifier) as HKSampleType?
108
124
  }
109
-
125
+
110
126
  if #available(iOS 13, *) {
111
127
  if(typeIdentifier == HKAudiogramTypeIdentifier){
112
128
  return HKSampleType.audiogramSampleType();
113
129
  }
114
130
  }
115
-
131
+
116
132
  if(typeIdentifier == HKWorkoutTypeIdentifier){
117
133
  return HKSampleType.workoutType();
118
134
  }
119
-
135
+
120
136
  return nil;
121
137
  }
122
-
138
+
123
139
  func objectTypesFromDictionary(typeIdentifiers: NSDictionary) -> Set<HKObjectType> {
124
140
  var share = Set<HKObjectType>();
125
141
  for item in typeIdentifiers {
@@ -132,7 +148,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
132
148
  }
133
149
  return share;
134
150
  }
135
-
151
+
136
152
  func sampleTypesFromDictionary(typeIdentifiers: NSDictionary) -> Set<HKSampleType> {
137
153
  var share = Set<HKSampleType>();
138
154
  for item in typeIdentifiers {
@@ -145,12 +161,12 @@ class ReactNativeHealthkit: RCTEventEmitter {
145
161
  }
146
162
  return share;
147
163
  }
148
-
164
+
149
165
  @objc(isHealthDataAvailable:withRejecter:)
150
166
  func isHealthDataAvailable(resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
151
167
  resolve(HKHealthStore.isHealthDataAvailable())
152
168
  }
153
-
169
+
154
170
  @available(iOS 12.0, *)
155
171
  @objc(supportsHealthRecords:withRejecter:)
156
172
  func supportsHealthRecords(resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
@@ -159,7 +175,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
159
175
  }
160
176
  resolve(store.supportsHealthRecords());
161
177
  }
162
-
178
+
163
179
  @available(iOS 12.0, *)
164
180
  @objc(getRequestStatusForAuthorization:read:resolve:withRejecter:)
165
181
  func getRequestStatusForAuthorization(toShare: NSDictionary, read: NSDictionary, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
@@ -175,7 +191,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
175
191
  reject(GENERIC_ERROR, err.localizedDescription, err);
176
192
  }
177
193
  }
178
-
194
+
179
195
  @objc(getPreferredUnits:resolve:reject:)
180
196
  func getPreferredUnits(forIdentifiers: NSArray, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
181
197
  guard let store = _store else {
@@ -189,35 +205,35 @@ class ReactNativeHealthkit: RCTEventEmitter {
189
205
  quantityTypes.insert(type!);
190
206
  }
191
207
  }
192
-
208
+
193
209
  store.preferredUnits(for: quantityTypes) { (typePerUnits: [HKQuantityType : HKUnit], error: Error?) in
194
210
  let dic: NSMutableDictionary = NSMutableDictionary();
195
-
211
+
196
212
  for typePerUnit in typePerUnits {
197
213
  dic.setObject(typePerUnit.value.unitString, forKey: typePerUnit.key.identifier as NSCopying);
198
214
  }
199
-
215
+
200
216
  resolve(dic);
201
217
  }
202
218
  }
203
-
219
+
204
220
  func serializeQuantity(unit: HKUnit, quantity: HKQuantity?) -> Dictionary<String, Any>? {
205
221
  guard let q = quantity else {
206
222
  return nil;
207
223
  }
208
-
224
+
209
225
  return [
210
226
  "quantity": q.doubleValue(for: unit),
211
227
  "unit": unit.unitString
212
228
  ]
213
229
  }
214
-
230
+
215
231
  func serializeQuantitySample(sample: HKQuantitySample, unit: HKUnit) -> NSDictionary {
216
232
  let endDate = _dateFormatter.string(from: sample.endDate)
217
233
  let startDate = _dateFormatter.string(from: sample.startDate);
218
-
234
+
219
235
  let quantity = sample.quantity.doubleValue(for: unit);
220
-
236
+
221
237
  return [
222
238
  "uuid": sample.uuid.uuidString,
223
239
  "device": self.serializeDevice(_device: sample.device),
@@ -230,11 +246,11 @@ class ReactNativeHealthkit: RCTEventEmitter {
230
246
  "sourceRevision": self.serializeSourceRevision(_sourceRevision: sample.sourceRevision) as Any,
231
247
  ]
232
248
  }
233
-
249
+
234
250
  func serializeCategorySample(sample: HKCategorySample) -> NSDictionary {
235
251
  let endDate = _dateFormatter.string(from: sample.endDate)
236
252
  let startDate = _dateFormatter.string(from: sample.startDate);
237
-
253
+
238
254
  return [
239
255
  "uuid": sample.uuid.uuidString,
240
256
  "device": self.serializeDevice(_device: sample.device),
@@ -246,13 +262,13 @@ class ReactNativeHealthkit: RCTEventEmitter {
246
262
  "sourceRevision": self.serializeSourceRevision(_sourceRevision: sample.sourceRevision) as Any,
247
263
  ]
248
264
  }
249
-
265
+
250
266
  @objc(getBiologicalSex:withRejecter:)
251
267
  func getBiologicalSex(resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
252
268
  guard let store = _store else {
253
269
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
254
270
  }
255
-
271
+
256
272
  do {
257
273
  let bioSex = try store.biologicalSex();
258
274
  resolve(bioSex.biologicalSex.rawValue);
@@ -260,13 +276,13 @@ class ReactNativeHealthkit: RCTEventEmitter {
260
276
  reject(GENERIC_ERROR, error.localizedDescription, error);
261
277
  }
262
278
  }
263
-
279
+
264
280
  @objc(getDateOfBirth:withRejecter:)
265
281
  func getDateOfBirth(resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
266
282
  guard let store = _store else {
267
283
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
268
284
  }
269
-
285
+
270
286
  do {
271
287
  let dateOfBirth = try store.dateOfBirth();
272
288
  resolve(_dateFormatter.string(from: dateOfBirth));
@@ -274,13 +290,13 @@ class ReactNativeHealthkit: RCTEventEmitter {
274
290
  reject(GENERIC_ERROR, error.localizedDescription, error);
275
291
  }
276
292
  }
277
-
293
+
278
294
  @objc(getBloodType:withRejecter:)
279
295
  func getBloodType(resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
280
296
  guard let store = _store else {
281
297
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
282
298
  }
283
-
299
+
284
300
  do {
285
301
  let bloodType = try store.bloodType();
286
302
  resolve(bloodType.bloodType.rawValue);
@@ -288,13 +304,13 @@ class ReactNativeHealthkit: RCTEventEmitter {
288
304
  reject(GENERIC_ERROR, error.localizedDescription, error);
289
305
  }
290
306
  }
291
-
307
+
292
308
  @objc(getFitzpatrickSkinType:withRejecter:)
293
309
  func getFitzpatrickSkinType(resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
294
310
  guard let store = _store else {
295
311
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
296
312
  }
297
-
313
+
298
314
  do {
299
315
  let fitzpatrickSkinType = try store.fitzpatrickSkinType();
300
316
  resolve(fitzpatrickSkinType.skinType.rawValue);
@@ -302,15 +318,15 @@ class ReactNativeHealthkit: RCTEventEmitter {
302
318
  reject(GENERIC_ERROR, error.localizedDescription, error);
303
319
  }
304
320
  }
305
-
306
-
321
+
322
+
307
323
  @available(iOS 10.0, *)
308
324
  @objc(getWheelchairUse:withRejecter:)
309
325
  func getWheelchairUse(resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
310
326
  guard let store = _store else {
311
327
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
312
328
  }
313
-
329
+
314
330
  do {
315
331
  let wheelchairUse = try store.wheelchairUse();
316
332
  resolve(wheelchairUse.wheelchairUse.rawValue);
@@ -318,29 +334,29 @@ class ReactNativeHealthkit: RCTEventEmitter {
318
334
  reject(GENERIC_ERROR, error.localizedDescription, error);
319
335
  }
320
336
  }
321
-
337
+
322
338
  @objc(authorizationStatusFor:withResolver:withRejecter:)
323
339
  func authorizationStatusFor(typeIdentifier: String, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
324
340
  guard let store = _store else {
325
341
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
326
342
  }
327
-
343
+
328
344
  guard let objectType = objectTypeFromString(typeIdentifier: typeIdentifier) else {
329
345
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
330
346
  }
331
-
347
+
332
348
  let authStatus = store.authorizationStatus(for: objectType);
333
349
  resolve(authStatus.rawValue);
334
350
  }
335
-
351
+
336
352
  @objc(saveQuantitySample:unitString:value:start:end:metadata:resolve:reject:)
337
353
  func saveQuantitySample(typeIdentifier: String, unitString: String, value: Double, start: Date, end: Date, metadata: Dictionary<String, Any>, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock){
338
354
  guard let store = _store else {
339
355
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
340
356
  }
341
-
357
+
342
358
  let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier);
343
-
359
+
344
360
  guard let type = HKObjectType.quantityType(forIdentifier: identifier) else {
345
361
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
346
362
  }
@@ -362,19 +378,19 @@ class ReactNativeHealthkit: RCTEventEmitter {
362
378
  reject(GENERIC_ERROR, err.localizedDescription, error);
363
379
  }
364
380
  }
365
-
381
+
366
382
  @objc(saveCorrelationSample:samples:start:end:metadata:resolve:reject:)
367
383
  func saveCorrelationSample(typeIdentifier: String, samples: Array<Dictionary<String, Any>>, start: Date, end: Date, metadata: Dictionary<String, Any>, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock){
368
384
  guard let store = _store else {
369
385
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
370
386
  }
371
-
387
+
372
388
  let identifier = HKCorrelationTypeIdentifier.init(rawValue: typeIdentifier);
373
-
389
+
374
390
  guard let type = HKObjectType.correlationType(forIdentifier: identifier) else {
375
391
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
376
392
  }
377
-
393
+
378
394
  var initializedSamples = Set<HKSample>();
379
395
  for sample in samples {
380
396
  if(sample.keys.contains("quantityType")){
@@ -383,7 +399,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
383
399
  let unitStr = sample["unit"] as! String
384
400
  let quantityVal = sample["quantity"] as! Double
385
401
  let metadata = sample["metadata"] as? [String: Any]
386
-
402
+
387
403
  let unit = HKUnit.init(from: unitStr)
388
404
  let quantity = HKQuantity.init(unit: unit, doubleValue: quantityVal)
389
405
  let quantitySample = HKQuantitySample.init(type: type, quantity: quantity, start: start, end: end, metadata: metadata)
@@ -394,16 +410,16 @@ class ReactNativeHealthkit: RCTEventEmitter {
394
410
  if let type = HKSampleType.categoryType(forIdentifier: typeId) {
395
411
  let value = sample["value"] as! Int
396
412
  let metadata = sample["metadata"] as? [String: Any]
397
-
413
+
398
414
  let categorySample = HKCategorySample.init(type: type, value: value, start: start, end: end, metadata: metadata)
399
415
  initializedSamples.insert(categorySample);
400
416
  }
401
417
  }
402
-
418
+
403
419
  }
404
-
420
+
405
421
  let correlation = HKCorrelation.init(type: type, start: start, end: end, objects: initializedSamples, metadata: metadata)
406
-
422
+
407
423
  store.save(correlation) { (success: Bool, error: Error?) in
408
424
  guard let err = error else {
409
425
  return resolve(success);
@@ -411,30 +427,30 @@ class ReactNativeHealthkit: RCTEventEmitter {
411
427
  reject(GENERIC_ERROR, err.localizedDescription, error);
412
428
  }
413
429
  }
414
-
430
+
415
431
  @objc(saveWorkoutSample:quantities:start:end:metadata:resolve:reject:)
416
432
  func saveWorkoutSample(typeIdentifier: UInt, quantities: Array<Dictionary<String, Any>>, start: Date, end: Date, metadata: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock){
417
433
  guard let store = _store else {
418
434
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
419
435
  }
420
-
436
+
421
437
  guard let type = HKWorkoutActivityType.init(rawValue: typeIdentifier) else {
422
438
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier.description, nil);
423
439
  }
424
-
440
+
425
441
  var initializedSamples = [HKSample]();
426
442
  var totalEnergyBurned: HKQuantity?
427
443
  var totalDistance: HKQuantity?
428
444
  var totalSwimmingStrokeCount: HKQuantity?
429
445
  var totalFlightsClimbed: HKQuantity?
430
-
446
+
431
447
  for quantity in quantities {
432
448
  let typeId = HKQuantityTypeIdentifier.init(rawValue: quantity["quantityType"] as! String)
433
449
  if let type = HKSampleType.quantityType(forIdentifier: typeId) {
434
450
  let unitStr = quantity["unit"] as! String
435
451
  let quantityVal = quantity["quantity"] as! Double
436
452
  let metadata = quantity["metadata"] as? [String: Any]
437
-
453
+
438
454
  let unit = HKUnit.init(from: unitStr)
439
455
  let quantity = HKQuantity.init(unit: unit, doubleValue: quantityVal)
440
456
  if(quantity.is(compatibleWith: HKUnit.kilocalorie())){
@@ -453,10 +469,10 @@ class ReactNativeHealthkit: RCTEventEmitter {
453
469
  initializedSamples.append(quantitySample)
454
470
  }
455
471
  }
456
-
472
+
457
473
  var workout: HKWorkout?;
458
-
459
-
474
+
475
+
460
476
  if(totalSwimmingStrokeCount != nil){
461
477
  workout = HKWorkout.init(activityType: type, start: start, end: end, workoutEvents: nil, totalEnergyBurned: totalEnergyBurned, totalDistance: totalDistance, totalSwimmingStrokeCount: totalSwimmingStrokeCount, device: nil, metadata: metadata)
462
478
  } else {
@@ -466,11 +482,11 @@ class ReactNativeHealthkit: RCTEventEmitter {
466
482
  }
467
483
  }
468
484
  }
469
-
485
+
470
486
  if(workout == nil){
471
487
  workout = HKWorkout.init(activityType: type, start: start, end: end, workoutEvents: nil, totalEnergyBurned: totalEnergyBurned, totalDistance: totalDistance, metadata: metadata)
472
488
  }
473
-
489
+
474
490
  store.save(workout!) { (success: Bool, error: Error?) in
475
491
  guard let err = error else {
476
492
  if(success){
@@ -486,24 +502,24 @@ class ReactNativeHealthkit: RCTEventEmitter {
486
502
  }
487
503
  reject(GENERIC_ERROR, err.localizedDescription, error);
488
504
  }
489
-
490
-
505
+
506
+
491
507
  }
492
-
508
+
493
509
  @objc(saveCategorySample:value:start:end:metadata:resolve:reject:)
494
- func saveCategorySample(typeIdentifier: String, value: Int, start: Date, end: Date, metadata: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock){
510
+ func saveCategorySample(typeIdentifier: String, value: Double, start: Date, end: Date, metadata: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock){
495
511
  guard let store = _store else {
496
512
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
497
513
  }
498
-
514
+
499
515
  let identifier = HKCategoryTypeIdentifier.init(rawValue: typeIdentifier);
500
-
516
+
501
517
  guard let type = HKObjectType.categoryType(forIdentifier: identifier) else {
502
518
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
503
519
  }
504
520
 
505
- let sample = HKCategorySample.init(type: type, value: value, start: start, end: end, metadata: metadata as? Dictionary<String, Any>)
506
-
521
+ let sample = HKCategorySample.init(type: type, value: Int(value), start: start, end: end, metadata: metadata as? Dictionary<String, Any>)
522
+
507
523
  store.save(sample) { (success: Bool, error: Error?) in
508
524
  guard let err = error else {
509
525
  return resolve(success);
@@ -511,25 +527,25 @@ class ReactNativeHealthkit: RCTEventEmitter {
511
527
  reject(GENERIC_ERROR, err.localizedDescription, error);
512
528
  }
513
529
  }
514
-
530
+
515
531
  override func supportedEvents() -> [String]! {
516
532
  return ["onChange"]
517
533
  }
518
-
534
+
519
535
  @objc(enableBackgroundDelivery:updateFrequency:resolve:reject:)
520
536
  func enableBackgroundDelivery(typeIdentifier: String, updateFrequency: Int, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock){
521
537
  guard let store = _store else {
522
538
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
523
539
  }
524
-
540
+
525
541
  guard let sampleType = objectTypeFromString(typeIdentifier: typeIdentifier) else {
526
542
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
527
543
  }
528
-
544
+
529
545
  guard let frequency = HKUpdateFrequency.init(rawValue: updateFrequency) else {
530
546
  return reject("UpdateFrequency not valid", "UpdateFrequency not valid", nil);
531
547
  }
532
-
548
+
533
549
  store.enableBackgroundDelivery(for: sampleType, frequency:frequency ) { (success, error) in
534
550
  guard let err = error else {
535
551
  return resolve(success);
@@ -537,13 +553,13 @@ class ReactNativeHealthkit: RCTEventEmitter {
537
553
  reject(GENERIC_ERROR, err.localizedDescription, err);
538
554
  }
539
555
  }
540
-
556
+
541
557
  @objc(disableAllBackgroundDelivery:reject:)
542
558
  func disableAllBackgroundDelivery(resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock){
543
559
  guard let store = _store else {
544
560
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
545
561
  }
546
-
562
+
547
563
  store.disableAllBackgroundDelivery(completion: { (success, error) in
548
564
  guard let err = error else {
549
565
  return resolve(success);
@@ -551,18 +567,18 @@ class ReactNativeHealthkit: RCTEventEmitter {
551
567
  reject(GENERIC_ERROR, err.localizedDescription, err);
552
568
  })
553
569
  }
554
-
570
+
555
571
  @objc(disableBackgroundDelivery:resolve:reject:)
556
572
  func disableBackgroundDelivery(typeIdentifier: String, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock){
557
573
  guard let store = _store else {
558
574
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
559
575
  }
560
-
576
+
561
577
  guard let sampleType = objectTypeFromString(typeIdentifier: typeIdentifier) else {
562
578
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
563
579
  }
564
-
565
-
580
+
581
+
566
582
  store.disableBackgroundDelivery(for: sampleType) { (success, error) in
567
583
  guard let err = error else {
568
584
  return resolve(success);
@@ -570,21 +586,21 @@ class ReactNativeHealthkit: RCTEventEmitter {
570
586
  reject(GENERIC_ERROR, err.localizedDescription, err);
571
587
  }
572
588
  }
573
-
589
+
574
590
  @objc(subscribeToObserverQuery:resolve:reject:)
575
591
  func subscribeToObserverQuery(typeIdentifier: String, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock){
576
592
  guard let store = _store else {
577
593
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
578
594
  }
579
-
595
+
580
596
  guard let sampleType = sampleTypeFromString(typeIdentifier: typeIdentifier) else {
581
597
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
582
598
  }
583
-
599
+
584
600
  let predicate = HKQuery.predicateForSamples(withStart: Date.init(), end: nil, options: HKQueryOptions.strictStartDate)
585
-
601
+
586
602
  let queryId = UUID().uuidString
587
-
603
+
588
604
  func responder(query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?) -> Void {
589
605
  if(error == nil){
590
606
  DispatchQueue.main.async {
@@ -593,62 +609,62 @@ class ReactNativeHealthkit: RCTEventEmitter {
593
609
  "typeIdentifier": typeIdentifier,
594
610
  ]);
595
611
  }
596
-
612
+
597
613
  }
598
614
  handler();
599
615
  }
600
616
  }
601
-
617
+
602
618
  let query = HKObserverQuery(sampleType: sampleType, predicate: predicate) { (query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?) in
603
619
  guard let err = error else {
604
620
  return responder(query: query, handler: handler, error: error);
605
621
  }
606
622
  reject(GENERIC_ERROR, err.localizedDescription, err);
607
623
  }
608
-
624
+
609
625
  store.execute(query);
610
-
626
+
611
627
  self._runningQueries.updateValue(query, forKey: queryId);
612
628
  }
613
-
629
+
614
630
  @objc(unsubscribeQuery:resolve:reject:)
615
631
  func unsubscribeQuery(queryId: String, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) {
616
632
  guard let store = _store else {
617
633
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
618
634
  }
619
-
635
+
620
636
  guard let query = self._runningQueries[queryId] else {
621
637
  reject("Error", "Query with id " + queryId + " not found", nil);
622
638
  return;
623
639
  }
624
-
640
+
625
641
  store.stop(query);
626
-
642
+
627
643
  self._runningQueries.removeValue(forKey: queryId);
628
-
644
+
629
645
  resolve(true);
630
646
  }
631
-
647
+
632
648
 
633
649
  static override func requiresMainQueueSetup() -> Bool {
634
650
  return true
635
651
  }
636
-
652
+
637
653
  @objc(queryStatisticsForQuantity:unitString:from:to:options:resolve:reject:)
638
654
  func queryStatisticsForQuantity(typeIdentifier: String, unitString: String, from: Date, to: Date, options: NSArray, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
639
655
  guard let store = _store else {
640
656
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
641
657
  }
642
-
658
+
643
659
  let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier);
644
660
  guard let quantityType = HKObjectType.quantityType(forIdentifier: identifier) else {
645
661
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
646
662
  }
647
-
663
+
648
664
  let predicate = HKQuery.predicateForSamples(withStart: from, end: to, options: HKQueryOptions.strictEndDate)
649
-
665
+
650
666
  var opts = HKStatisticsOptions.init();
651
-
667
+
652
668
  for o in options {
653
669
  let str = o as! String;
654
670
  if(str == "cumulativeSum"){
@@ -675,12 +691,12 @@ class ReactNativeHealthkit: RCTEventEmitter {
675
691
  opts.insert(HKStatisticsOptions.mostRecent)
676
692
  }
677
693
  }
678
-
694
+
679
695
  if(str == "separateBySource"){
680
696
  opts.insert(HKStatisticsOptions.separateBySource)
681
697
  }
682
698
  }
683
-
699
+
684
700
  let query = HKStatisticsQuery.init(quantityType: quantityType, quantitySamplePredicate: predicate, options: opts) { (query, stats: HKStatistics?, error: Error?) in
685
701
  if let gottenStats = stats {
686
702
  var dic = Dictionary<String, Dictionary<String, Any>?>()
@@ -701,7 +717,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
701
717
  if let mostRecent = gottenStats.mostRecentQuantity() {
702
718
  dic.updateValue(self.serializeQuantity(unit: unit, quantity: mostRecent), forKey: "mostRecentQuantity")
703
719
  }
704
-
720
+
705
721
  if let mostRecentDateInterval = gottenStats.mostRecentQuantityDateInterval() {
706
722
  dic.updateValue([
707
723
  "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
@@ -715,38 +731,41 @@ class ReactNativeHealthkit: RCTEventEmitter {
715
731
  dic.updateValue(self.serializeQuantity(unit: durationUnit, quantity: duration), forKey: "duration")
716
732
  }
717
733
  }
718
-
734
+
719
735
  resolve(dic);
720
736
  }
721
737
  }
722
-
738
+
723
739
  store.execute(query);
724
740
  }
725
-
741
+
726
742
  func serializeUnknownQuantity(quantity: HKQuantity) -> Dictionary<String, Any>? {
727
743
  if(quantity.is(compatibleWith: HKUnit.percent())){
728
744
  return self.serializeQuantity(unit: HKUnit.percent(), quantity: quantity);
729
745
  }
730
-
731
-
746
+
732
747
  if(quantity.is(compatibleWith: HKUnit.second())){
733
748
  return self.serializeQuantity(unit: HKUnit.second(), quantity: quantity);
734
749
  }
735
-
750
+
736
751
  if(quantity.is(compatibleWith: HKUnit.kilocalorie())){
737
752
  return self.serializeQuantity(unit: HKUnit.kilocalorie(), quantity: quantity);
738
753
  }
739
-
754
+
740
755
  if(quantity.is(compatibleWith: HKUnit.count())){
741
756
  return self.serializeQuantity(unit: HKUnit.count(), quantity: quantity)
742
757
  }
743
-
758
+
759
+ if(quantity.is(compatibleWith: HKUnit.meter())){
760
+ return self.serializeQuantity(unit: HKUnit.meter(), quantity: quantity)
761
+ }
762
+
744
763
  if #available(iOS 11, *) {
745
764
  if(quantity.is(compatibleWith: HKUnit.internationalUnit())){
746
765
  return self.serializeQuantity(unit: HKUnit.internationalUnit(), quantity: quantity);
747
766
  }
748
767
  }
749
-
768
+
750
769
  if #available(iOS 13, *) {
751
770
  if(quantity.is(compatibleWith: HKUnit.hertz())){
752
771
  return self.serializeQuantity(unit: HKUnit.hertz(), quantity: quantity);
@@ -755,9 +774,18 @@ class ReactNativeHealthkit: RCTEventEmitter {
755
774
  return self.serializeQuantity(unit: HKUnit.decibelHearingLevel(), quantity: quantity);
756
775
  }
757
776
  }
777
+
778
+ if(quantity.is(compatibleWith: SpeedUnit)){
779
+ return self.serializeQuantity(unit: SpeedUnit, quantity: quantity);
780
+ }
781
+
782
+ if(quantity.is(compatibleWith: METUnit)){
783
+ return self.serializeQuantity(unit: METUnit, quantity: quantity);
784
+ }
785
+
758
786
  return nil;
759
787
  }
760
-
788
+
761
789
  func serializeMetadata(metadata: [String: Any]?) -> NSDictionary {
762
790
  let serialized: NSMutableDictionary = [:];
763
791
  if let m = metadata {
@@ -768,6 +796,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
768
796
  if let str = item.value as? String {
769
797
  serialized.setValue(str, forKey: item.key)
770
798
  }
799
+
771
800
  if let double = item.value as? Double {
772
801
  serialized.setValue(double, forKey: item.key)
773
802
  }
@@ -780,7 +809,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
780
809
  }
781
810
  return serialized;
782
811
  }
783
-
812
+
784
813
  func serializeDevice(_device: HKDevice?) -> Dictionary<String, String?>? {
785
814
  guard let device = _device else {
786
815
  return nil;
@@ -795,22 +824,22 @@ class ReactNativeHealthkit: RCTEventEmitter {
795
824
  "softwareVersion": device.softwareVersion,
796
825
  ]
797
826
  }
798
-
827
+
799
828
  func serializeOperatingSystemVersion(_version: OperatingSystemVersion?) -> String? {
800
829
  guard let version = _version else {
801
830
  return nil;
802
831
  }
803
-
832
+
804
833
  let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)";
805
-
834
+
806
835
  return versionString;
807
836
  }
808
-
837
+
809
838
  func serializeSourceRevision(_sourceRevision: HKSourceRevision?) -> Dictionary<String, Any?>? {
810
839
  guard let sourceRevision = _sourceRevision else {
811
840
  return nil;
812
841
  }
813
-
842
+
814
843
  var dict = [
815
844
  "source": [
816
845
  "name": sourceRevision.source.name,
@@ -818,12 +847,12 @@ class ReactNativeHealthkit: RCTEventEmitter {
818
847
  ],
819
848
  "version": sourceRevision.version
820
849
  ] as [String : Any];
821
-
850
+
822
851
  if #available(iOS 11, *) {
823
852
  dict["operatingSystemVersion"] = self.serializeOperatingSystemVersion(_version: sourceRevision.operatingSystemVersion);
824
853
  dict["productType"] = sourceRevision.productType;
825
854
  }
826
-
855
+
827
856
  return dict;
828
857
  }
829
858
 
@@ -832,28 +861,29 @@ class ReactNativeHealthkit: RCTEventEmitter {
832
861
  guard let store = _store else {
833
862
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
834
863
  }
835
-
864
+
836
865
  let from = from.timeIntervalSince1970 > 0 ? from : nil;
837
866
  let to = to.timeIntervalSince1970 > 0 ? to : nil;
838
-
867
+
839
868
  let predicate = from != nil || to != nil ? HKQuery.predicateForSamples(withStart: from, end: to, options: [HKQueryOptions.strictEndDate, HKQueryOptions.strictStartDate]) : nil;
840
-
869
+
841
870
  let limit = limit == 0 ? HKObjectQueryNoLimit : limit;
842
-
871
+
843
872
  let energyUnit = HKUnit.init(from: energyUnitString)
844
873
  let distanceUnit = HKUnit.init(from: distanceUnitString)
845
-
874
+
846
875
  let q = HKSampleQuery(sampleType: .workoutType(), predicate: predicate, limit: limit, sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)]) { (query: HKSampleQuery, sample: [HKSample]?, error: Error?) in
847
876
  guard let err = error else {
848
877
  guard let samples = sample else {
849
878
  return resolve([]);
850
879
  }
851
880
  let arr: NSMutableArray = [];
852
-
881
+
853
882
  for s in samples {
854
883
  if let workout = s as? HKWorkout {
855
884
  let endDate = self._dateFormatter.string(from: workout.endDate)
856
885
  let startDate = self._dateFormatter.string(from: workout.startDate);
886
+
857
887
  let dict: NSMutableDictionary = [
858
888
  "uuid": workout.uuid.uuidString,
859
889
  "device": self.serializeDevice(_device: workout.device) as Any,
@@ -867,87 +897,87 @@ class ReactNativeHealthkit: RCTEventEmitter {
867
897
  "metadata": self.serializeMetadata(metadata: workout.metadata),
868
898
  "sourceRevision": self.serializeSourceRevision(_sourceRevision: workout.sourceRevision) as Any
869
899
  ]
870
-
900
+
871
901
  if #available(iOS 11, *) {
872
902
  dict.setValue(self.serializeQuantity(unit: HKUnit.count(), quantity: workout.totalFlightsClimbed), forKey: "totalFlightsClimbed")
873
903
  }
874
-
904
+
875
905
  arr.add(dict)
876
906
  }
877
907
  }
878
-
908
+
879
909
  return resolve(arr);
880
910
  }
881
911
  reject(GENERIC_ERROR, err.localizedDescription, err);
882
912
  }
883
-
913
+
884
914
  store.execute(q);
885
915
  }
886
-
887
-
916
+
917
+
888
918
  @objc(queryQuantitySamples:unitString:from:to:limit:ascending:resolve:reject:)
889
919
  func queryQuantitySamples(typeIdentifier: String, unitString: String, from: Date, to: Date, limit: Int, ascending: Bool, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
890
920
  guard let store = _store else {
891
921
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
892
922
  }
893
-
923
+
894
924
  let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier);
895
925
  guard let sampleType = HKSampleType.quantityType(forIdentifier: identifier) else {
896
926
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
897
927
  }
898
-
928
+
899
929
  let from = from.timeIntervalSince1970 > 0 ? from : nil;
900
930
  let to = to.timeIntervalSince1970 > 0 ? to : nil;
901
-
931
+
902
932
  let predicate = from != nil || to != nil ? HKQuery.predicateForSamples(withStart: from, end: to, options: [HKQueryOptions.strictEndDate, HKQueryOptions.strictStartDate]) : nil;
903
-
933
+
904
934
  let limit = limit == 0 ? HKObjectQueryNoLimit : limit;
905
-
935
+
906
936
  let q = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: limit, sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)]) { (query: HKSampleQuery, sample: [HKSample]?, error: Error?) in
907
937
  guard let err = error else {
908
938
  guard let samples = sample else {
909
939
  return resolve([]);
910
940
  }
911
941
  let arr: NSMutableArray = [];
912
-
942
+
913
943
  for s in samples {
914
944
  if let sample = s as? HKQuantitySample {
915
945
  let serialized = self.serializeQuantitySample(sample: sample, unit: HKUnit.init(from: unitString))
916
-
946
+
917
947
  arr.add(serialized)
918
948
  }
919
949
  }
920
-
950
+
921
951
  return resolve(arr);
922
952
  }
923
953
  reject(GENERIC_ERROR, err.localizedDescription, err);
924
954
  }
925
-
955
+
926
956
  store.execute(q);
927
957
  }
928
-
958
+
929
959
  @objc(queryCorrelationSamples:from:to:resolve:reject:)
930
960
  func queryCorrelationSamples(typeIdentifier: String, from: Date, to: Date, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
931
961
  guard let store = _store else {
932
962
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
933
963
  }
934
-
964
+
935
965
  let identifier = HKCorrelationTypeIdentifier.init(rawValue: typeIdentifier);
936
966
  guard let sampleType = HKSampleType.correlationType(forIdentifier: identifier) else {
937
967
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
938
968
  }
939
-
969
+
940
970
  let from = from.timeIntervalSince1970 > 0 ? from : nil;
941
971
  let to = to.timeIntervalSince1970 > 0 ? to : nil;
942
-
972
+
943
973
  let predicate = from != nil || to != nil ? HKQuery.predicateForSamples(withStart: from, end: to, options: [HKQueryOptions.strictEndDate, HKQueryOptions.strictStartDate]) : nil;
944
-
974
+
945
975
  let q = HKCorrelationQuery(type: sampleType, predicate: predicate, samplePredicates: nil) { (query: HKCorrelationQuery, _correlations: [HKCorrelation]?, error: Error?) in
946
976
  guard let err = error else {
947
977
  guard let correlations = _correlations else {
948
978
  return resolve([]);
949
979
  }
950
-
980
+
951
981
  var qts = Set<HKQuantityType>();
952
982
  for c in correlations {
953
983
  for object in c.objects {
@@ -969,7 +999,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
969
999
  objects.add(self.serializeCategorySample(sample: categorySample))
970
1000
  }
971
1001
  }
972
-
1002
+
973
1003
  collerationsToReturn.add([
974
1004
  "uuid": c.uuid.uuidString,
975
1005
  "device": self.serializeDevice(_device: c.device) as Any,
@@ -980,7 +1010,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
980
1010
  "endDate": self._dateFormatter.string(from: c.endDate),
981
1011
  ])
982
1012
  }
983
-
1013
+
984
1014
  return resolve(collerationsToReturn);
985
1015
  }
986
1016
  reject(GENERIC_ERROR, e.localizedDescription, e);
@@ -988,60 +1018,60 @@ class ReactNativeHealthkit: RCTEventEmitter {
988
1018
  }
989
1019
  reject(GENERIC_ERROR, err.localizedDescription, err);
990
1020
  }
991
-
1021
+
992
1022
  store.execute(q);
993
1023
  }
994
-
1024
+
995
1025
  @objc(queryCategorySamples:from:to:limit:ascending:resolve:reject:)
996
1026
  func queryCategorySamples(typeIdentifier: String, from: Date, to: Date, limit: Int, ascending: Bool, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
997
1027
  guard let store = _store else {
998
1028
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
999
1029
  }
1000
-
1030
+
1001
1031
  let identifier = HKCategoryTypeIdentifier.init(rawValue: typeIdentifier);
1002
1032
  guard let sampleType = HKSampleType.categoryType(forIdentifier: identifier) else {
1003
1033
  return reject(TYPE_IDENTIFIER_ERROR, typeIdentifier, nil);
1004
1034
  }
1005
-
1035
+
1006
1036
  let from = from.timeIntervalSince1970 > 0 ? from : nil;
1007
1037
  let to = to.timeIntervalSince1970 > 0 ? to : nil;
1008
-
1038
+
1009
1039
  let predicate = from != nil || to != nil ? HKQuery.predicateForSamples(withStart: from, end: to, options: [HKQueryOptions.strictEndDate, HKQueryOptions.strictStartDate]) : nil;
1010
-
1040
+
1011
1041
  let limit = limit == 0 ? HKObjectQueryNoLimit : limit;
1012
-
1042
+
1013
1043
  let q = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: limit, sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)]) { (query: HKSampleQuery, sample: [HKSample]?, error: Error?) in
1014
1044
  guard let err = error else {
1015
1045
  guard let samples = sample else {
1016
1046
  return resolve([]);
1017
1047
  }
1018
1048
  let arr: NSMutableArray = [];
1019
-
1049
+
1020
1050
  for s in samples {
1021
1051
  if let sample = s as? HKCategorySample {
1022
1052
  let serialized = self.serializeCategorySample(sample: sample);
1023
-
1053
+
1024
1054
  arr.add(serialized)
1025
1055
  }
1026
1056
  }
1027
-
1057
+
1028
1058
  return resolve(arr);
1029
1059
  }
1030
1060
  reject(GENERIC_ERROR, err.localizedDescription, err);
1031
1061
  }
1032
-
1062
+
1033
1063
  store.execute(q);
1034
1064
  }
1035
-
1065
+
1036
1066
  @objc(requestAuthorization:read:resolve:withRejecter:)
1037
1067
  func requestAuthorization(toShare: NSDictionary, read: NSDictionary, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
1038
1068
  guard let store = _store else {
1039
1069
  return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil);
1040
1070
  }
1041
-
1071
+
1042
1072
  let share = sampleTypesFromDictionary(typeIdentifiers: toShare);
1043
1073
  let toRead = objectTypesFromDictionary(typeIdentifiers: read);
1044
-
1074
+
1045
1075
  store.requestAuthorization(toShare: share, read: toRead) { (success: Bool, error: Error?) in
1046
1076
  guard let err = error else {
1047
1077
  return resolve(success);
@@ -1049,4 +1079,162 @@ class ReactNativeHealthkit: RCTEventEmitter {
1049
1079
  reject(GENERIC_ERROR, err.localizedDescription, err);
1050
1080
  }
1051
1081
  }
1082
+
1083
+ @available(iOS 13.0.0, *)
1084
+ func getWorkoutByID(store: HKHealthStore,
1085
+ workoutUUID: UUID) async -> HKWorkout? {
1086
+ let workoutPredicate = HKQuery.predicateForObject(with: workoutUUID);
1087
+
1088
+ let samples = try! await withCheckedThrowingContinuation {
1089
+ (continuation: CheckedContinuation<[HKSample], Error>) in
1090
+ let query = HKAnchoredObjectQuery(type: HKSeriesType.workoutType(),
1091
+ predicate: workoutPredicate,
1092
+ anchor: nil,
1093
+ limit: 1) {
1094
+ (query, samples, deletedObjects, anchor, error) in
1095
+
1096
+ if let hasError = error {
1097
+ continuation.resume(throwing: hasError)
1098
+ return
1099
+ }
1100
+
1101
+ guard let samples = samples else {
1102
+ fatalError("Should not fail")
1103
+ }
1104
+
1105
+ continuation.resume(returning: samples)
1106
+ }
1107
+ store.execute(query)
1108
+ }
1109
+
1110
+ guard let workouts = samples as? [HKWorkout] else {
1111
+ return nil
1112
+ }
1113
+
1114
+ return workouts.first ?? nil
1115
+ }
1116
+
1117
+
1118
+ @available(iOS 13.0.0, *)
1119
+ func _getWorkoutRoutes(store: HKHealthStore, workoutUUID: UUID) async -> [HKWorkoutRoute]?{
1120
+ guard let workout = await getWorkoutByID(store: store, workoutUUID: workoutUUID) else {
1121
+ return nil
1122
+ }
1123
+
1124
+ let workoutPredicate = HKQuery.predicateForObjects(from: workout)
1125
+ let samples = try! await withCheckedThrowingContinuation {
1126
+ (continuation: CheckedContinuation<[HKSample], Error>) in
1127
+ let query = HKAnchoredObjectQuery(type: HKSeriesType.workoutRoute(),
1128
+ predicate: workoutPredicate,
1129
+ anchor: nil,
1130
+ limit: HKObjectQueryNoLimit) {
1131
+ (query, samples, deletedObjects, anchor, error) in
1132
+
1133
+ if let hasError = error {
1134
+ continuation.resume(throwing: hasError)
1135
+ return
1136
+ }
1137
+
1138
+ guard let samples = samples else {
1139
+ fatalError("Should not fail")
1140
+ }
1141
+
1142
+ continuation.resume(returning: samples)
1143
+ }
1144
+ store.execute(query)
1145
+ }
1146
+
1147
+ guard let routes = samples as? [HKWorkoutRoute] else {
1148
+ return nil
1149
+ }
1150
+
1151
+ return routes
1152
+ }
1153
+
1154
+ @available(iOS 13.0.0, *)
1155
+ func getRouteLocations(store: HKHealthStore, route: HKWorkoutRoute) async -> [CLLocation] {
1156
+ let locations = try! await withCheckedThrowingContinuation {
1157
+ (continuation: CheckedContinuation<[CLLocation], Error>) in
1158
+ var allLocations: [CLLocation] = []
1159
+
1160
+ let query = HKWorkoutRouteQuery(route: route) {
1161
+ (query, locationsOrNil, done, errorOrNil) in
1162
+
1163
+ if let error = errorOrNil {
1164
+ continuation.resume(throwing: error)
1165
+ return
1166
+ }
1167
+
1168
+ guard let currentLocationBatch = locationsOrNil else {
1169
+ fatalError("Should not fail")
1170
+ }
1171
+
1172
+ allLocations.append(contentsOf: currentLocationBatch)
1173
+
1174
+ if done {
1175
+ continuation.resume(returning: allLocations)
1176
+ }
1177
+ }
1178
+
1179
+ store.execute(query)
1180
+ }
1181
+
1182
+ return locations
1183
+ }
1184
+
1185
+ @available(iOS 13.0.0, *)
1186
+ func getSerializedWorkoutLocations(store: HKHealthStore, workoutUUID: UUID) async -> [Dictionary<String, Any>]? {
1187
+ let routes = await _getWorkoutRoutes(store: store, workoutUUID: workoutUUID)
1188
+
1189
+ var allRoutes: [Dictionary<String, Any>] = []
1190
+ guard let _routes = routes else {
1191
+ return nil
1192
+ }
1193
+ for route in _routes {
1194
+ let routeMetadata = self.serializeMetadata(metadata: route.metadata) as! Dictionary<String, Any>
1195
+ let routeLocations = (await getRouteLocations(store: store, route: route)).map{serializeLocation(location: $0)}
1196
+ let routeInfos: Dictionary<String, Any> = ["locations": routeLocations]
1197
+ allRoutes.append(routeInfos.merging(routeMetadata) { (current, _) in current })
1198
+ }
1199
+ return allRoutes
1200
+ }
1201
+
1202
+ func serializeLocation(location: CLLocation) -> Dictionary<String, Any> {
1203
+ return [
1204
+ "longitude": location.coordinate.longitude,
1205
+ "latitude": location.coordinate.latitude,
1206
+ "altitude": location.altitude,
1207
+ "speed": location.speed,
1208
+ "timestamp": location.timestamp.timeIntervalSince1970,
1209
+ "horizontalAccuracy": location.horizontalAccuracy,
1210
+ "speedAccuracy": location.speedAccuracy,
1211
+ "verticalAccuracy": location.verticalAccuracy,
1212
+ ]
1213
+ }
1214
+
1215
+ @available(iOS 13.0.0, *)
1216
+ @objc(getWorkoutRoutes:resolve:reject:)
1217
+ func getWorkoutRoutes(
1218
+ workoutUUID: String,
1219
+ resolve: @escaping RCTPromiseResolveBlock,
1220
+ reject: @escaping RCTPromiseRejectBlock){
1221
+
1222
+ guard let store = _store else {
1223
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1224
+ }
1225
+
1226
+ guard let _workoutUUID = UUID(uuidString: workoutUUID) else {
1227
+ return reject("INVALID_UUID_ERROR", "Invalid UUID received", nil)
1228
+ }
1229
+
1230
+ async {
1231
+ do {
1232
+ let locations = await getSerializedWorkoutLocations(store: store, workoutUUID: _workoutUUID)
1233
+ resolve(locations)
1234
+ }
1235
+ catch {
1236
+ reject("WORKOUT_LOCATION_ERROR", "Failed to retrieve workout locations", nil)
1237
+ }
1238
+ }
1239
+ }
1052
1240
  }