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