@kingstinct/react-native-healthkit 8.1.1 → 8.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 (42) hide show
  1. package/README.md +18 -4
  2. package/ios/Constants.swift +4 -0
  3. package/ios/Helpers.swift +81 -0
  4. package/ios/ReactNativeHealthkit.m +15 -0
  5. package/ios/ReactNativeHealthkit.swift +1680 -1209
  6. package/ios/Serializers.swift +18 -0
  7. package/lib/commonjs/index.ios.js +18 -0
  8. package/lib/commonjs/index.ios.js.map +1 -1
  9. package/lib/commonjs/index.native.js +15 -1
  10. package/lib/commonjs/index.native.js.map +1 -1
  11. package/lib/commonjs/native-types.js +14 -1
  12. package/lib/commonjs/native-types.js.map +1 -1
  13. package/lib/commonjs/test-setup.js +3 -1
  14. package/lib/commonjs/test-setup.js.map +1 -1
  15. package/lib/commonjs/utils/queryStatisticsCollectionForQuantity.js +16 -0
  16. package/lib/commonjs/utils/queryStatisticsCollectionForQuantity.js.map +1 -0
  17. package/lib/commonjs/utils/startWatchApp.js +11 -0
  18. package/lib/commonjs/utils/startWatchApp.js.map +1 -0
  19. package/lib/module/index.ios.js +6 -2
  20. package/lib/module/index.ios.js.map +1 -1
  21. package/lib/module/index.native.js +13 -1
  22. package/lib/module/index.native.js.map +1 -1
  23. package/lib/module/native-types.js +15 -0
  24. package/lib/module/native-types.js.map +1 -1
  25. package/lib/module/test-setup.js +3 -1
  26. package/lib/module/test-setup.js.map +1 -1
  27. package/lib/module/utils/queryStatisticsCollectionForQuantity.js +9 -0
  28. package/lib/module/utils/queryStatisticsCollectionForQuantity.js.map +1 -0
  29. package/lib/module/utils/startWatchApp.js +4 -0
  30. package/lib/module/utils/startWatchApp.js.map +1 -0
  31. package/lib/typescript/src/index.ios.d.ts +6 -2
  32. package/lib/typescript/src/index.native.d.ts +11 -3
  33. package/lib/typescript/src/native-types.d.ts +29 -1
  34. package/lib/typescript/src/utils/queryStatisticsCollectionForQuantity.d.ts +3 -0
  35. package/lib/typescript/src/utils/startWatchApp.d.ts +3 -0
  36. package/package.json +1 -1
  37. package/src/index.ios.tsx +7 -0
  38. package/src/index.native.tsx +16 -0
  39. package/src/native-types.ts +45 -1
  40. package/src/test-setup.ts +2 -0
  41. package/src/utils/queryStatisticsCollectionForQuantity.ts +38 -0
  42. package/src/utils/startWatchApp.ts +7 -0
@@ -8,747 +8,1043 @@ import WorkoutKit
8
8
  @objc(ReactNativeHealthkit)
9
9
  @available(iOS 10.0, *)
10
10
  class ReactNativeHealthkit: RCTEventEmitter {
11
- var _store: HKHealthStore?
12
- var _runningQueries: [String: HKQuery]
13
- var _dateFormatter: ISO8601DateFormatter
14
- var _hasListeners = false
15
-
16
- override init() {
17
- self._runningQueries = [String: HKQuery]()
18
- self._dateFormatter = ISO8601DateFormatter()
19
- self._dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
20
- if HKHealthStore.isHealthDataAvailable() {
21
- self._store = HKHealthStore.init()
22
- }
23
- super.init()
11
+ var _store: HKHealthStore?
12
+ var _runningQueries: [String: HKQuery]
13
+ var _dateFormatter: ISO8601DateFormatter
14
+ var _hasListeners = false
15
+
16
+ override init() {
17
+ self._runningQueries = [String: HKQuery]()
18
+ self._dateFormatter = ISO8601DateFormatter()
19
+ self._dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
20
+ if HKHealthStore.isHealthDataAvailable() {
21
+ self._store = HKHealthStore.init()
24
22
  }
23
+ super.init()
24
+ }
25
25
 
26
- deinit {
27
- if let store = _store {
28
- for query in self._runningQueries {
29
- store.stop(query.value)
30
- }
31
- }
26
+ deinit {
27
+ if let store = _store {
28
+ for query in self._runningQueries {
29
+ store.stop(query.value)
30
+ }
32
31
  }
32
+ }
33
33
 
34
- override func stopObserving() {
35
- self._hasListeners = false
36
- if let store = _store {
37
- for query in self._runningQueries {
38
- store.stop(query.value)
39
- }
40
- }
34
+ override func stopObserving() {
35
+ self._hasListeners = false
36
+ if let store = _store {
37
+ for query in self._runningQueries {
38
+ store.stop(query.value)
39
+ }
41
40
  }
41
+ }
42
42
 
43
- override func startObserving() {
44
- self._hasListeners = true
45
- }
43
+ override func startObserving() {
44
+ self._hasListeners = true
45
+ }
46
46
 
47
- @objc(isProtectedDataAvailable:withRejecter:)
48
- func isProtectedDataAvailable(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
49
- resolve(UIApplication.shared.isProtectedDataAvailable)
47
+ @objc(isProtectedDataAvailable:withRejecter:)
48
+ func isProtectedDataAvailable(
49
+ resolve: RCTPromiseResolveBlock,
50
+ reject: RCTPromiseRejectBlock
51
+ ) {
52
+ resolve(UIApplication.shared.isProtectedDataAvailable)
53
+ }
54
+
55
+ @objc(isHealthDataAvailable:withRejecter:)
56
+ func isHealthDataAvailable(
57
+ resolve: RCTPromiseResolveBlock,
58
+ reject: RCTPromiseRejectBlock
59
+ ) {
60
+ resolve(HKHealthStore.isHealthDataAvailable())
61
+ }
62
+
63
+ @available(iOS 12.0, *)
64
+ @objc(supportsHealthRecords:withRejecter:)
65
+ func supportsHealthRecords(
66
+ resolve: RCTPromiseResolveBlock,
67
+ reject: RCTPromiseRejectBlock
68
+ ) {
69
+ guard let store = _store else {
70
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
50
71
  }
72
+ resolve(store.supportsHealthRecords())
73
+ }
51
74
 
52
- @objc(isHealthDataAvailable:withRejecter:)
53
- func isHealthDataAvailable(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
54
- resolve(HKHealthStore.isHealthDataAvailable())
75
+ @available(iOS 12.0, *)
76
+ @objc(getRequestStatusForAuthorization:read:resolve:withRejecter:)
77
+ func getRequestStatusForAuthorization(
78
+ toShare: NSDictionary,
79
+ read: NSDictionary,
80
+ resolve: @escaping RCTPromiseResolveBlock,
81
+ reject: @escaping RCTPromiseRejectBlock
82
+ ) {
83
+ guard let store = _store else {
84
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
55
85
  }
56
86
 
57
- @available(iOS 12.0, *)
58
- @objc(supportsHealthRecords:withRejecter:)
59
- func supportsHealthRecords(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
60
- guard let store = _store else {
61
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
62
- }
63
- resolve(store.supportsHealthRecords())
87
+ let share = sampleTypesFromDictionary(typeIdentifiers: toShare)
88
+ let toRead = objectTypesFromDictionary(typeIdentifiers: read)
89
+
90
+ store.getRequestStatusForAuthorization(toShare: share, read: toRead) { (
91
+ status: HKAuthorizationRequestStatus,
92
+ error: Error?
93
+ ) in
94
+ guard let err = error else {
95
+ return resolve(status.rawValue)
96
+ }
97
+ reject(GENERIC_ERROR, err.localizedDescription, err)
64
98
  }
99
+ }
65
100
 
66
- @available(iOS 12.0, *)
67
- @objc(getRequestStatusForAuthorization:read:resolve:withRejecter:)
68
- func getRequestStatusForAuthorization(toShare: NSDictionary, read: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
69
- guard let store = _store else {
70
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
71
- }
72
- let share = sampleTypesFromDictionary(typeIdentifiers: toShare)
73
- let toRead = objectTypesFromDictionary(typeIdentifiers: read)
74
- store.getRequestStatusForAuthorization(toShare: share, read: toRead) { (status: HKAuthorizationRequestStatus, error: Error?) in
75
- guard let err = error else {
76
- return resolve(status.rawValue)
77
- }
78
- reject(GENERIC_ERROR, err.localizedDescription, err)
79
- }
101
+ @objc(getPreferredUnits:resolve:reject:)
102
+ func getPreferredUnits(
103
+ forIdentifiers: NSArray,
104
+ resolve: @escaping RCTPromiseResolveBlock,
105
+ reject: @escaping RCTPromiseRejectBlock
106
+ ) {
107
+ guard let store = _store else {
108
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
109
+ }
110
+ var quantityTypes = Set<HKQuantityType>()
111
+ for identifierString in forIdentifiers {
112
+ let identifier = HKQuantityTypeIdentifier.init(rawValue: identifierString as! String)
113
+ let type = HKSampleType.quantityType(forIdentifier: identifier)
114
+ if type != nil {
115
+ quantityTypes.insert(type!)
116
+ }
80
117
  }
81
118
 
82
- @objc(getPreferredUnits:resolve:reject:)
83
- func getPreferredUnits(forIdentifiers: NSArray, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
84
- guard let store = _store else {
85
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
86
- }
87
- var quantityTypes = Set<HKQuantityType>()
88
- for identifierString in forIdentifiers {
89
- let identifier = HKQuantityTypeIdentifier.init(rawValue: identifierString as! String)
90
- let type = HKSampleType.quantityType(forIdentifier: identifier)
91
- if type != nil {
92
- quantityTypes.insert(type!)
93
- }
94
- }
119
+ store.preferredUnits(for: quantityTypes) { (typePerUnits: [HKQuantityType: HKUnit], _: Error?) in
120
+ let dic: NSMutableDictionary = NSMutableDictionary()
95
121
 
96
- store.preferredUnits(for: quantityTypes) { (typePerUnits: [HKQuantityType: HKUnit], _: Error?) in
97
- let dic: NSMutableDictionary = NSMutableDictionary()
122
+ for typePerUnit in typePerUnits {
123
+ dic.setObject(typePerUnit.value.unitString, forKey: typePerUnit.key.identifier as NSCopying)
124
+ }
98
125
 
99
- for typePerUnit in typePerUnits {
100
- dic.setObject(typePerUnit.value.unitString, forKey: typePerUnit.key.identifier as NSCopying)
101
- }
126
+ resolve(dic)
127
+ }
128
+ }
102
129
 
103
- resolve(dic)
104
- }
130
+ @objc(getBiologicalSex:withRejecter:)
131
+ func getBiologicalSex(
132
+ resolve: @escaping RCTPromiseResolveBlock,
133
+ reject: @escaping RCTPromiseRejectBlock
134
+ ) {
135
+ guard let store = _store else {
136
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
105
137
  }
106
138
 
107
- @objc(getBiologicalSex:withRejecter:)
108
- func getBiologicalSex(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
109
- guard let store = _store else {
110
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
111
- }
139
+ do {
140
+ let bioSex = try store.biologicalSex()
141
+ resolve(bioSex.biologicalSex.rawValue)
142
+ } catch {
143
+ reject(GENERIC_ERROR, error.localizedDescription, error)
144
+ }
145
+ }
112
146
 
113
- do {
114
- let bioSex = try store.biologicalSex()
115
- resolve(bioSex.biologicalSex.rawValue)
116
- } catch {
117
- reject(GENERIC_ERROR, error.localizedDescription, error)
118
- }
147
+ @objc(getDateOfBirth:withRejecter:)
148
+ func getDateOfBirth(
149
+ resolve: @escaping RCTPromiseResolveBlock,
150
+ reject: @escaping RCTPromiseRejectBlock
151
+ ) {
152
+ guard let store = _store else {
153
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
119
154
  }
120
155
 
121
- @objc(getDateOfBirth:withRejecter:)
122
- func getDateOfBirth(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
123
- guard let store = _store else {
124
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
125
- }
156
+ do {
157
+ let dateOfBirth = try store.dateOfBirthComponents()
126
158
 
127
- do {
128
- let dateOfBirth = try store.dateOfBirthComponents()
159
+ resolve(_dateFormatter.string(from: dateOfBirth.date!))
160
+ } catch {
161
+ reject(GENERIC_ERROR, error.localizedDescription, error)
162
+ }
163
+ }
129
164
 
130
- resolve(_dateFormatter.string(from: dateOfBirth.date!))
131
- } catch {
132
- reject(GENERIC_ERROR, error.localizedDescription, error)
133
- }
165
+ @objc(getBloodType:withRejecter:)
166
+ func getBloodType(
167
+ resolve: @escaping RCTPromiseResolveBlock,
168
+ reject: @escaping RCTPromiseRejectBlock
169
+ ) {
170
+ guard let store = _store else {
171
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
134
172
  }
135
173
 
136
- @objc(getBloodType:withRejecter:)
137
- func getBloodType(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
138
- guard let store = _store else {
139
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
140
- }
174
+ do {
175
+ let bloodType = try store.bloodType()
176
+ resolve(bloodType.bloodType.rawValue)
177
+ } catch {
178
+ reject(GENERIC_ERROR, error.localizedDescription, error)
179
+ }
180
+ }
141
181
 
142
- do {
143
- let bloodType = try store.bloodType()
144
- resolve(bloodType.bloodType.rawValue)
145
- } catch {
146
- reject(GENERIC_ERROR, error.localizedDescription, error)
147
- }
182
+ @objc(getFitzpatrickSkinType:withRejecter:)
183
+ func getFitzpatrickSkinType(
184
+ resolve: @escaping RCTPromiseResolveBlock,
185
+ reject: @escaping RCTPromiseRejectBlock
186
+ ) {
187
+ guard let store = _store else {
188
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
148
189
  }
149
190
 
150
- @objc(getFitzpatrickSkinType:withRejecter:)
151
- func getFitzpatrickSkinType(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
152
- guard let store = _store else {
153
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
154
- }
191
+ do {
192
+ let fitzpatrickSkinType = try store.fitzpatrickSkinType()
193
+ resolve(fitzpatrickSkinType.skinType.rawValue)
194
+ } catch {
195
+ reject(GENERIC_ERROR, error.localizedDescription, error)
196
+ }
197
+ }
155
198
 
156
- do {
157
- let fitzpatrickSkinType = try store.fitzpatrickSkinType()
158
- resolve(fitzpatrickSkinType.skinType.rawValue)
159
- } catch {
160
- reject(GENERIC_ERROR, error.localizedDescription, error)
161
- }
199
+ @available(iOS 10.0, *)
200
+ @objc(getWheelchairUse:withRejecter:)
201
+ func getWheelchairUse(
202
+ resolve: @escaping RCTPromiseResolveBlock,
203
+ reject: @escaping RCTPromiseRejectBlock
204
+ ) {
205
+ guard let store = _store else {
206
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
162
207
  }
163
208
 
164
- @available(iOS 10.0, *)
165
- @objc(getWheelchairUse:withRejecter:)
166
- func getWheelchairUse(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
167
- guard let store = _store else {
168
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
169
- }
209
+ do {
210
+ let wheelchairUse = try store.wheelchairUse()
211
+ resolve(wheelchairUse.wheelchairUse.rawValue)
212
+ } catch {
213
+ reject(GENERIC_ERROR, error.localizedDescription, error)
214
+ }
215
+ }
170
216
 
171
- do {
172
- let wheelchairUse = try store.wheelchairUse()
173
- resolve(wheelchairUse.wheelchairUse.rawValue)
174
- } catch {
175
- reject(GENERIC_ERROR, error.localizedDescription, error)
176
- }
217
+ @objc(authorizationStatusFor:withResolver:withRejecter:)
218
+ func authorizationStatusFor(
219
+ typeIdentifier: String,
220
+ resolve: @escaping RCTPromiseResolveBlock,
221
+ reject: @escaping RCTPromiseRejectBlock
222
+ ) {
223
+ guard let store = _store else {
224
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
177
225
  }
178
226
 
179
- @objc(authorizationStatusFor:withResolver:withRejecter:)
180
- func authorizationStatusFor(typeIdentifier: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
181
- guard let store = _store else {
182
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
183
- }
227
+ guard let objectType = objectTypeFromString(typeIdentifier: typeIdentifier) else {
228
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
229
+ }
184
230
 
185
- guard let objectType = objectTypeFromString(typeIdentifier: typeIdentifier) else {
186
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
187
- }
231
+ let authStatus = store.authorizationStatus(for: objectType)
232
+ resolve(authStatus.rawValue)
233
+ }
188
234
 
189
- let authStatus = store.authorizationStatus(for: objectType)
190
- resolve(authStatus.rawValue)
235
+ @objc(saveQuantitySample:unitString:value:start:end:metadata:resolve:reject:)
236
+ func saveQuantitySample(
237
+ typeIdentifier: String,
238
+ unitString: String,
239
+ value: Double,
240
+ start: Date,
241
+ end: Date,
242
+ metadata: [String: Any],
243
+ resolve: @escaping RCTPromiseResolveBlock,
244
+ reject: @escaping RCTPromiseRejectBlock
245
+ ) {
246
+ guard let store = _store else {
247
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
191
248
  }
192
249
 
193
- @objc(saveQuantitySample:unitString:value:start:end:metadata:resolve:reject:)
194
- func saveQuantitySample(typeIdentifier: String, unitString: String, value: Double, start: Date, end: Date, metadata: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
195
- guard let store = _store else {
196
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
197
- }
250
+ let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
198
251
 
199
- let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
252
+ guard let type = HKObjectType.quantityType(forIdentifier: identifier) else {
253
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
254
+ }
200
255
 
201
- guard let type = HKObjectType.quantityType(forIdentifier: identifier) else {
202
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
203
- }
256
+ let unit = HKUnit.init(from: unitString)
257
+ let quantity = HKQuantity.init(unit: unit, doubleValue: value)
258
+ let sample = HKQuantitySample.init(
259
+ type: type,
260
+ quantity: quantity,
261
+ start: start,
262
+ end: end,
263
+ metadata: metadata
264
+ )
265
+
266
+ store.save(sample) { (success: Bool, error: Error?) in
267
+ guard let err = error else {
268
+ return resolve(success)
269
+ }
270
+ reject(GENERIC_ERROR, err.localizedDescription, error)
271
+ }
272
+ }
204
273
 
205
- let unit = HKUnit.init(from: unitString)
206
- let quantity = HKQuantity.init(unit: unit, doubleValue: value)
207
- let sample = HKQuantitySample.init(
208
- type: type,
209
- quantity: quantity,
210
- start: start,
211
- end: end,
212
- metadata: metadata
213
- )
274
+ @objc(deleteQuantitySample:uuid:resolve:reject:)
275
+ func deleteQuantitySample(
276
+ typeIdentifier: String,
277
+ uuid: String,
278
+ resolve: @escaping RCTPromiseResolveBlock,
279
+ reject: @escaping RCTPromiseRejectBlock
280
+ ) {
281
+ guard let store = _store else {
282
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
283
+ }
214
284
 
215
- store.save(sample) { (success: Bool, error: Error?) in
216
- guard let err = error else {
217
- return resolve(success)
218
- }
219
- reject(GENERIC_ERROR, err.localizedDescription, error)
220
- }
285
+ let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
286
+
287
+ guard let sampleType = HKObjectType.quantityType(forIdentifier: identifier) else {
288
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
221
289
  }
222
290
 
223
- @objc(deleteQuantitySample:uuid:resolve:reject:)
224
- func deleteQuantitySample(typeIdentifier: String, uuid: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
225
- guard let store = _store else {
226
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
227
- }
291
+ // https://developer.apple.com/documentation/healthkit/hkquery/1614783-predicateforobjectwithuuid
292
+ let samplePredicate: NSPredicate = NSPredicate(format: "%K == %@", HKPredicateKeyPathUUID, uuid)
228
293
 
229
- let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
230
- let sampleUuid = UUID.init(uuidString: uuid)!
294
+ store.deleteObjects(of: sampleType, predicate: samplePredicate) { (success: Bool, _: Int, error: Error?) in
295
+ guard let err = error else {
296
+ return resolve(success)
297
+ }
298
+ reject(GENERIC_ERROR, err.localizedDescription, error)
299
+ }
300
+ }
231
301
 
232
- guard let sampleType = HKObjectType.quantityType(forIdentifier: identifier) else {
233
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
234
- }
302
+ @objc(deleteSamples:start:end:resolve:reject:)
303
+ func deleteSamples(
304
+ typeIdentifier: String,
305
+ start: Date,
306
+ end: Date,
307
+ resolve: @escaping RCTPromiseResolveBlock,
308
+ reject: @escaping RCTPromiseRejectBlock
309
+ ) {
310
+ guard let store = _store else {
311
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
312
+ }
235
313
 
236
- let samplePredicate = HKQuery.predicateForObject(with: sampleUuid)
314
+ let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
237
315
 
238
- store.deleteObjects(of: sampleType, predicate: samplePredicate) { (success: Bool, _: Int, error: Error?) in
239
- guard let err = error else {
240
- return resolve(success)
241
- }
242
- reject(GENERIC_ERROR, err.localizedDescription, error)
243
- }
316
+ guard let sampleType = HKObjectType.quantityType(forIdentifier: identifier) else {
317
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
244
318
  }
245
319
 
246
- @objc(deleteSamples:start:end:resolve:reject:)
247
- func deleteSamples(typeIdentifier: String, start: Date, end: Date, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
248
- guard let store = _store else {
249
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
250
- }
320
+ let samplePredicate = HKQuery.predicateForSamples(
321
+ withStart: start,
322
+ end: end,
323
+ options: HKQueryOptions.strictStartDate
324
+ )
251
325
 
252
- let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
326
+ store.deleteObjects(of: sampleType, predicate: samplePredicate) { (success: Bool, _: Int, error: Error?) in
327
+ guard let err = error else {
328
+ return resolve(success)
329
+ }
330
+ reject(GENERIC_ERROR, err.localizedDescription, error)
331
+ }
332
+ }
253
333
 
254
- guard let sampleType = HKObjectType.quantityType(forIdentifier: identifier) else {
255
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
256
- }
334
+ @objc(saveCorrelationSample:samples:start:end:metadata:resolve:reject:)
335
+ func saveCorrelationSample(
336
+ typeIdentifier: String,
337
+ samples: [[String: Any]],
338
+ start: Date,
339
+ end: Date,
340
+ metadata: [String: Any],
341
+ resolve: @escaping RCTPromiseResolveBlock,
342
+ reject: @escaping RCTPromiseRejectBlock
343
+ ) {
344
+ guard let store = _store else {
345
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
346
+ }
257
347
 
258
- let samplePredicate = HKQuery.predicateForSamples(withStart: start, end: end, options: HKQueryOptions.strictStartDate)
348
+ let identifier = HKCorrelationTypeIdentifier.init(rawValue: typeIdentifier)
259
349
 
260
- store.deleteObjects(of: sampleType, predicate: samplePredicate) { (success: Bool, _: Int, error: Error?) in
261
- guard let err = error else {
262
- return resolve(success)
263
- }
264
- reject(GENERIC_ERROR, err.localizedDescription, error)
265
- }
350
+ guard let type = HKObjectType.correlationType(forIdentifier: identifier) else {
351
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
266
352
  }
267
353
 
268
- @objc(saveCorrelationSample:samples:start:end:metadata:resolve:reject:)
269
- func saveCorrelationSample(typeIdentifier: String, samples: [[String: Any]], start: Date, end: Date, metadata: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
270
- guard let store = _store else {
271
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
354
+ var initializedSamples = Set<HKSample>()
355
+ for sample in samples {
356
+ if sample.keys.contains("quantityType") {
357
+ let typeId = HKQuantityTypeIdentifier.init(rawValue: sample["quantityType"] as! String)
358
+ if let type = HKSampleType.quantityType(forIdentifier: typeId) {
359
+ let unitStr = sample["unit"] as! String
360
+ let quantityVal = sample["quantity"] as! Double
361
+ let metadata = sample["metadata"] as? [String: Any]
362
+
363
+ let unit = HKUnit.init(from: unitStr)
364
+ let quantity = HKQuantity.init(unit: unit, doubleValue: quantityVal)
365
+ let quantitySample = HKQuantitySample.init(
366
+ type: type,
367
+ quantity: quantity,
368
+ start: start,
369
+ end: end,
370
+ metadata: metadata
371
+ )
372
+ initializedSamples.insert(quantitySample)
373
+ }
374
+ } else if sample.keys.contains("categoryType") {
375
+ let typeId = HKCategoryTypeIdentifier.init(rawValue: sample["categoryType"] as! String)
376
+ if let type = HKSampleType.categoryType(forIdentifier: typeId) {
377
+ let value = sample["value"] as! Int
378
+ let metadata = sample["metadata"] as? [String: Any]
379
+ let categorySample = HKCategorySample.init(
380
+ type: type,
381
+ value: value,
382
+ start: start,
383
+ end: end,
384
+ metadata: metadata
385
+ )
386
+ initializedSamples.insert(categorySample)
272
387
  }
388
+ }
273
389
 
274
- let identifier = HKCorrelationTypeIdentifier.init(rawValue: typeIdentifier)
390
+ }
275
391
 
276
- guard let type = HKObjectType.correlationType(forIdentifier: identifier) else {
277
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
278
- }
392
+ let correlation = HKCorrelation.init(
393
+ type: type,
394
+ start: start,
395
+ end: end,
396
+ objects: initializedSamples,
397
+ metadata: metadata
398
+ )
279
399
 
280
- var initializedSamples = Set<HKSample>()
281
- for sample in samples {
282
- if sample.keys.contains("quantityType") {
283
- let typeId = HKQuantityTypeIdentifier.init(rawValue: sample["quantityType"] as! String)
284
- if let type = HKSampleType.quantityType(forIdentifier: typeId) {
285
- let unitStr = sample["unit"] as! String
286
- let quantityVal = sample["quantity"] as! Double
287
- let metadata = sample["metadata"] as? [String: Any]
288
-
289
- let unit = HKUnit.init(from: unitStr)
290
- let quantity = HKQuantity.init(unit: unit, doubleValue: quantityVal)
291
- let quantitySample = HKQuantitySample.init(type: type, quantity: quantity, start: start, end: end, metadata: metadata)
292
- initializedSamples.insert(quantitySample)
293
- }
294
- } else if sample.keys.contains("categoryType") {
295
- let typeId = HKCategoryTypeIdentifier.init(rawValue: sample["categoryType"] as! String)
296
- if let type = HKSampleType.categoryType(forIdentifier: typeId) {
297
- let value = sample["value"] as! Int
298
- let metadata = sample["metadata"] as? [String: Any]
299
- let categorySample = HKCategorySample.init(type: type, value: value, start: start, end: end, metadata: metadata)
300
- initializedSamples.insert(categorySample)
301
- }
302
- }
400
+ store.save(correlation) { (success: Bool, error: Error?) in
401
+ guard let err = error else {
402
+ return resolve(success)
403
+ }
404
+ reject(GENERIC_ERROR, err.localizedDescription, error)
405
+ }
406
+ }
303
407
 
304
- }
408
+ @objc(saveWorkoutSample:quantities:start:end:totals:metadata:resolve:reject:)
409
+ func saveWorkoutSample(
410
+ typeIdentifier: UInt,
411
+ quantities: [[String: Any]],
412
+ start: Date,
413
+ end: Date,
414
+ totals: [String: Any],
415
+ metadata: [String: Any],
416
+ resolve: @escaping RCTPromiseResolveBlock,
417
+ reject: @escaping RCTPromiseRejectBlock
418
+ ) {
419
+ guard let store = _store else {
420
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
421
+ }
305
422
 
306
- let correlation = HKCorrelation.init(type: type, start: start, end: end, objects: initializedSamples, metadata: metadata)
423
+ guard let type = HKWorkoutActivityType.init(rawValue: typeIdentifier) else {
424
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize HKWorkoutActivityType " + typeIdentifier.description, nil)
425
+ }
307
426
 
308
- store.save(correlation) { (success: Bool, error: Error?) in
309
- guard let err = error else {
310
- return resolve(success)
311
- }
312
- reject(GENERIC_ERROR, err.localizedDescription, error)
313
- }
427
+ // if start and end both exist, ensure that start date is before end date
428
+ if let startDate = start as Date?, let endDate = end as Date? {
429
+ if startDate > endDate {
430
+ return reject(GENERIC_ERROR, "Start date must be before end date", nil)
431
+ }
314
432
  }
315
433
 
316
- @objc(saveWorkoutSample:quantities:start:end:totals:metadata:resolve:reject:)
317
- func saveWorkoutSample(typeIdentifier: UInt, quantities: [[String: Any]], start: Date, end: Date, totals: [String: Any], metadata: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
318
- guard let store = _store else {
319
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
434
+ var initializedSamples = [HKSample]()
435
+ var totalEnergyBurned: HKQuantity?
436
+ var totalDistance: HKQuantity?
437
+ var totalSwimmingStrokeCount: HKQuantity?
438
+ var totalFlightsClimbed: HKQuantity?
439
+ // generating quantity samples
440
+ for quantity in quantities {
441
+ let typeId = HKQuantityTypeIdentifier.init(rawValue: quantity["quantityType"] as! String)
442
+ if let type = HKSampleType.quantityType(forIdentifier: typeId) {
443
+ let unitStr = quantity["unit"] as! String
444
+ let quantityVal = quantity["quantity"] as! Double
445
+ let metadata = quantity["metadata"] as? [String: Any]
446
+ let quantityStart = quantity["startDate"] as? String
447
+ let quantityEnd = quantity["endDate"] as? String
448
+ let unit = HKUnit.init(from: unitStr)
449
+ let quantity = HKQuantity.init(unit: unit, doubleValue: quantityVal)
450
+
451
+ if quantity.is(compatibleWith: HKUnit.kilocalorie()) {
452
+ totalEnergyBurned = quantity
453
+ }
454
+ if quantity.is(compatibleWith: HKUnit.meter()) {
455
+ totalDistance = quantity
456
+ }
457
+ if typeId == HKQuantityTypeIdentifier.swimmingStrokeCount {
458
+ totalSwimmingStrokeCount = quantity
459
+ }
460
+ if typeId == HKQuantityTypeIdentifier.flightsClimbed {
461
+ totalFlightsClimbed = quantity
462
+ }
463
+ if let quantityStart, let quantityEnd {
464
+ let quantityStartDate = self._dateFormatter.date(from: quantityStart) ?? start
465
+ let quantityEndDate = self._dateFormatter.date(from: quantityEnd) ?? end
466
+ let quantitySample = HKQuantitySample.init(
467
+ type: type,
468
+ quantity: quantity,
469
+ start: quantityStartDate,
470
+ end: quantityEndDate,
471
+ metadata: metadata
472
+ )
473
+ initializedSamples.append(quantitySample)
474
+ } else {
475
+ // Handle the case where either startDate or endDate is nil
476
+ let quantitySample = HKQuantitySample.init(
477
+ type: type,
478
+ quantity: quantity,
479
+ start: start,
480
+ end: end,
481
+ metadata: metadata
482
+ )
483
+ initializedSamples.append(quantitySample)
320
484
  }
485
+ }
486
+ }
321
487
 
322
- guard let type = HKWorkoutActivityType.init(rawValue: typeIdentifier) else {
323
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize HKWorkoutActivityType " + typeIdentifier.description, nil)
324
- }
488
+ // if totals are provided override samples
489
+ let rawTotalDistance = totals["distance"] as? Double ?? 0.0
490
+ let rawTotalEnergy = totals["energyBurned"] as? Double ?? 0.0
325
491
 
326
- // if start and end both exist, ensure that start date is before end date
327
- if let startDate = start as Date?, let endDate = end as Date? {
328
- if startDate > endDate {
329
- return reject(GENERIC_ERROR, "Start date must be before end date", nil)
330
- }
331
- }
492
+ if rawTotalDistance != 0.0 {
493
+ totalDistance = HKQuantity(unit: .meter(), doubleValue: rawTotalDistance)
494
+ }
495
+ if rawTotalEnergy != 0.0 {
496
+ totalEnergyBurned = HKQuantity(unit: .kilocalorie(), doubleValue: rawTotalEnergy)
497
+ }
332
498
 
333
- var initializedSamples = [HKSample]()
334
- var totalEnergyBurned: HKQuantity?
335
- var totalDistance: HKQuantity?
336
- var totalSwimmingStrokeCount: HKQuantity?
337
- var totalFlightsClimbed: HKQuantity?
338
- // generating quantity samples
339
- for quantity in quantities {
340
- let typeId = HKQuantityTypeIdentifier.init(rawValue: quantity["quantityType"] as! String)
341
- if let type = HKSampleType.quantityType(forIdentifier: typeId) {
342
- let unitStr = quantity["unit"] as! String
343
- let quantityVal = quantity["quantity"] as! Double
344
- let metadata = quantity["metadata"] as? [String: Any]
345
- let quantityStart = quantity["startDate"] as? String
346
- let quantityEnd = quantity["endDate"] as? String
347
- let unit = HKUnit.init(from: unitStr)
348
- let quantity = HKQuantity.init(unit: unit, doubleValue: quantityVal)
349
-
350
- if quantity.is(compatibleWith: HKUnit.kilocalorie()) {
351
- totalEnergyBurned = quantity
352
- }
353
- if quantity.is(compatibleWith: HKUnit.meter()) {
354
- totalDistance = quantity
355
- }
356
- if typeId == HKQuantityTypeIdentifier.swimmingStrokeCount {
357
- totalSwimmingStrokeCount = quantity
358
- }
359
- if typeId == HKQuantityTypeIdentifier.flightsClimbed {
360
- totalFlightsClimbed = quantity
361
- }
362
- if let quantityStart, let quantityEnd {
363
- let quantityStartDate = self._dateFormatter.date(from: quantityStart) ?? start
364
- let quantityEndDate = self._dateFormatter.date(from: quantityEnd) ?? end
365
- let quantitySample = HKQuantitySample.init(type: type, quantity: quantity, start: quantityStartDate, end: quantityEndDate, metadata: metadata)
366
- initializedSamples.append(quantitySample)
367
- } else {
368
- // Handle the case where either startDate or endDate is nil
369
- let quantitySample = HKQuantitySample.init(type: type, quantity: quantity, start: start, end: end, metadata: metadata)
370
- initializedSamples.append(quantitySample)
371
- }
372
- }
499
+ // creating workout
500
+ var workout: HKWorkout?
501
+
502
+ if totalSwimmingStrokeCount != nil {
503
+ workout = HKWorkout.init(
504
+ activityType: type,
505
+ start: start,
506
+ end: end,
507
+ workoutEvents: nil,
508
+ totalEnergyBurned: totalEnergyBurned,
509
+ totalDistance: totalDistance,
510
+ totalSwimmingStrokeCount: totalSwimmingStrokeCount,
511
+ device: nil,
512
+ metadata: metadata
513
+ )
514
+ } else {
515
+ if #available(iOS 11, *) {
516
+ if totalFlightsClimbed != nil {
517
+ workout = HKWorkout.init(
518
+ activityType: type,
519
+ start: start,
520
+ end: end,
521
+ workoutEvents: nil,
522
+ totalEnergyBurned: totalEnergyBurned,
523
+ totalDistance: totalDistance,
524
+ totalFlightsClimbed: totalFlightsClimbed,
525
+ device: nil,
526
+ metadata: metadata
527
+ )
373
528
  }
529
+ }
530
+ }
374
531
 
375
- // if totals are provided override samples
376
- let rawTotalDistance = totals["distance"] as? Double ?? 0.0
377
- let rawTotalEnergy = totals["energyBurned"] as? Double ?? 0.0
532
+ if workout == nil {
533
+ workout = HKWorkout.init(
534
+ activityType: type,
535
+ start: start,
536
+ end: end,
537
+ workoutEvents: nil,
538
+ totalEnergyBurned: totalEnergyBurned,
539
+ totalDistance: totalDistance,
540
+ metadata: metadata
541
+ )
542
+ }
378
543
 
379
- if rawTotalDistance != 0.0 {
380
- totalDistance = HKQuantity(unit: .meter(), doubleValue: rawTotalDistance)
381
- }
382
- if rawTotalEnergy != 0.0 {
383
- totalEnergyBurned = HKQuantity(unit: .kilocalorie(), doubleValue: rawTotalEnergy)
384
- }
544
+ guard let workout = workout else {
545
+ reject(GENERIC_ERROR, "Could not create workout", nil)
546
+ return
547
+ }
385
548
 
386
- // creating workout
387
- var workout: HKWorkout?
549
+ // saving workout, samples and route
550
+ store.save(workout) { (_: Bool, error: Error?) in
551
+ guard error == nil else {
552
+ reject(GENERIC_ERROR, error!.localizedDescription, error)
553
+ return
554
+ }
388
555
 
389
- if totalSwimmingStrokeCount != nil {
390
- workout = HKWorkout.init(activityType: type, start: start, end: end, workoutEvents: nil, totalEnergyBurned: totalEnergyBurned, totalDistance: totalDistance, totalSwimmingStrokeCount: totalSwimmingStrokeCount, device: nil, metadata: metadata)
391
- } else {
392
- if #available(iOS 11, *) {
393
- if totalFlightsClimbed != nil {
394
- workout = HKWorkout.init(activityType: type, start: start, end: end, workoutEvents: nil, totalEnergyBurned: totalEnergyBurned, totalDistance: totalDistance, totalFlightsClimbed: totalFlightsClimbed, device: nil, metadata: metadata)
395
- }
396
- }
397
- }
556
+ if initializedSamples.isEmpty {
557
+ return resolve(workout.uuid.uuidString)
558
+ }
398
559
 
399
- if workout == nil {
400
- workout = HKWorkout.init(activityType: type, start: start, end: end, workoutEvents: nil, totalEnergyBurned: totalEnergyBurned, totalDistance: totalDistance, metadata: metadata)
560
+ store.add(initializedSamples, to: workout) { (_, error: Error?) in
561
+ guard error == nil else {
562
+ reject(GENERIC_ERROR, error!.localizedDescription, error)
563
+ return
401
564
  }
565
+ return resolve(workout.uuid.uuidString)
566
+ }
567
+ }
568
+ }
402
569
 
403
- guard let workout = workout else {
404
- reject(GENERIC_ERROR, "Could not create workout", nil)
405
- return
406
- }
570
+ // function which will take an array of location in string format and create an array of CLLocations
571
+ func _createCLLocations(from locations: [[String: Any]]) -> [CLLocation] {
572
+ var clLocations: [CLLocation] = []
573
+ for location in locations {
574
+ guard let latitude = location["latitude"] as? CLLocationDegrees,
575
+ let longitude = location["longitude"] as? CLLocationDegrees,
576
+ let altitude = location["altitude"] as? CLLocationDistance,
577
+ let horizontalAccuracy = location["horizontalAccuracy"] as? CLLocationAccuracy,
578
+ let verticalAccuracy = location["verticalAccuracy"] as? CLLocationAccuracy,
579
+ let course = location["course"] as? CLLocationDirection,
580
+ let speed = location["speed"] as? CLLocationSpeed,
581
+ let timestamp = location["timestamp"] as? String else {
582
+ continue
583
+ }
407
584
 
408
- // saving workout, samples and route
409
- store.save(workout) { (_: Bool, error: Error?) in
410
- guard error == nil else {
411
- reject(GENERIC_ERROR, error!.localizedDescription, error)
412
- return
413
- }
585
+ let date = self._dateFormatter.date(from: timestamp) ?? Date()
586
+ let clLocation = CLLocation(
587
+ coordinate: CLLocationCoordinate2D(
588
+ latitude: latitude,
589
+ longitude: longitude
590
+ ), altitude: altitude,
591
+ horizontalAccuracy: horizontalAccuracy,
592
+ verticalAccuracy: verticalAccuracy,
593
+ course: course,
594
+ speed: speed,
595
+ timestamp: date
596
+ )
597
+ clLocations.append(clLocation)
598
+ }
599
+ return clLocations
600
+ }
414
601
 
415
- if initializedSamples.isEmpty {
416
- return resolve(workout.uuid.uuidString)
417
- }
602
+ @available(iOS 13.0.0, *)
603
+ @objc(saveWorkoutRoute:locations:resolve:reject:)
604
+ func saveWorkoutRoute(
605
+ workoutUUID: String,
606
+ locations: [[String: Any]],
607
+ resolve: @escaping RCTPromiseResolveBlock,
608
+ reject: @escaping RCTPromiseRejectBlock
609
+ ) {
610
+ guard let store = _store else {
611
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
612
+ }
418
613
 
419
- store.add(initializedSamples, to: workout) { (_, error: Error?) in
420
- guard error == nil else {
421
- reject(GENERIC_ERROR, error!.localizedDescription, error)
422
- return
423
- }
424
- return resolve(workout.uuid.uuidString)
614
+ Task {
615
+ if let uuid = UUID(uuidString: workoutUUID) {
616
+ do {
617
+ let workout = await self.getWorkoutByID(store: store, workoutUUID: uuid)
618
+ if let workout {
619
+ // create CLLocations and return if locations are empty
620
+ let clLocations = self._createCLLocations(from: locations)
621
+ if clLocations.isEmpty {
622
+ return reject(GENERIC_ERROR, "No locations provided", nil)
425
623
  }
624
+ // create route
625
+ let routeBuilder = HKWorkoutRouteBuilder(healthStore: store, device: nil)
626
+ try await routeBuilder.insertRouteData(clLocations)
627
+ try await routeBuilder.finishRoute(with: workout, metadata: nil)
628
+
629
+ return resolve(true)
630
+ } else {
631
+ return reject(GENERIC_ERROR, "No workout found", nil)
632
+ }
633
+ } catch {
634
+ return reject(GENERIC_ERROR, error.localizedDescription, error)
426
635
  }
636
+ } else {
637
+ return reject(GENERIC_ERROR, "Invalid UUID", nil)
638
+ }
427
639
  }
640
+ }
428
641
 
429
- // function which will take an array of location in string format and create an array of CLLocations
430
- func _createCLLocations(from locations: [[String: Any]]) -> [CLLocation] {
431
- var clLocations: [CLLocation] = []
432
- for location in locations {
433
- guard let latitude = location["latitude"] as? CLLocationDegrees,
434
- let longitude = location["longitude"] as? CLLocationDegrees,
435
- let altitude = location["altitude"] as? CLLocationDistance,
436
- let horizontalAccuracy = location["horizontalAccuracy"] as? CLLocationAccuracy,
437
- let verticalAccuracy = location["verticalAccuracy"] as? CLLocationAccuracy,
438
- let course = location["course"] as? CLLocationDirection,
439
- let speed = location["speed"] as? CLLocationSpeed,
440
- let timestamp = location["timestamp"] as? String else {
441
- continue
442
- }
642
+ @objc(saveCategorySample:value:start:end:metadata:resolve:reject:)
643
+ func saveCategorySample(
644
+ typeIdentifier: String,
645
+ value: Double,
646
+ start: Date,
647
+ end: Date,
648
+ metadata: NSDictionary,
649
+ resolve: @escaping RCTPromiseResolveBlock,
650
+ reject: @escaping RCTPromiseRejectBlock
651
+ ) {
652
+ guard let store = _store else {
653
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
654
+ }
443
655
 
444
- let date = self._dateFormatter.date(from: timestamp) ?? Date()
445
- let clLocation = CLLocation(coordinate: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), altitude: altitude, horizontalAccuracy: horizontalAccuracy, verticalAccuracy: verticalAccuracy, course: course, speed: speed, timestamp: date)
446
- clLocations.append(clLocation)
447
- }
448
- return clLocations
656
+ let identifier = HKCategoryTypeIdentifier.init(rawValue: typeIdentifier)
657
+
658
+ guard let type = HKObjectType.categoryType(forIdentifier: identifier) else {
659
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
449
660
  }
450
661
 
451
- @available(iOS 13.0.0, *)
452
- @objc(saveWorkoutRoute:locations:resolve:reject:)
453
- func saveWorkoutRoute(workoutUUID: String, locations: [[String: Any]], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
454
- guard let store = _store else {
455
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
456
- }
662
+ let sample = HKCategorySample.init(
663
+ type: type,
664
+ value: Int(value),
665
+ start: start,
666
+ end: end,
667
+ metadata: metadata as? [String: Any]
668
+ )
457
669
 
458
- Task {
459
- if let uuid = UUID(uuidString: workoutUUID) {
460
- do {
461
- let workout = await self.getWorkoutByID(store: store, workoutUUID: uuid)
462
- if let workout {
463
- // create CLLocations and return if locations are empty
464
- let clLocations = self._createCLLocations(from: locations)
465
- if clLocations.isEmpty {
466
- return reject(GENERIC_ERROR, "No locations provided", nil)
467
- }
468
- // create route
469
- let routeBuilder = HKWorkoutRouteBuilder(healthStore: store, device: nil)
470
- try await routeBuilder.insertRouteData(clLocations)
471
- try await routeBuilder.finishRoute(with: workout, metadata: nil)
472
-
473
- return resolve(true)
474
- } else {
475
- return reject(GENERIC_ERROR, "No workout found", nil)
476
- }
477
- } catch {
478
- return reject(GENERIC_ERROR, error.localizedDescription, error)
479
- }
480
- } else {
481
- return reject(GENERIC_ERROR, "Invalid UUID", nil)
482
- }
483
- }
670
+ store.save(sample) { (success: Bool, error: Error?) in
671
+ guard let err = error else {
672
+ return resolve(success)
673
+ }
674
+ reject(GENERIC_ERROR, err.localizedDescription, error)
484
675
  }
676
+ }
485
677
 
486
- @objc(saveCategorySample:value:start:end:metadata:resolve:reject:)
487
- func saveCategorySample(typeIdentifier: String, value: Double, start: Date, end: Date, metadata: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
488
- guard let store = _store else {
489
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
490
- }
678
+ override func supportedEvents() -> [String]! {
679
+ return ["onChange"]
680
+ }
491
681
 
492
- let identifier = HKCategoryTypeIdentifier.init(rawValue: typeIdentifier)
682
+ @objc(enableBackgroundDelivery:updateFrequency:resolve:reject:)
683
+ func enableBackgroundDelivery(
684
+ typeIdentifier: String,
685
+ updateFrequency: Int,
686
+ resolve: @escaping RCTPromiseResolveBlock,
687
+ reject: @escaping RCTPromiseRejectBlock
688
+ ) {
689
+ guard let store = _store else {
690
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
691
+ }
493
692
 
494
- guard let type = HKObjectType.categoryType(forIdentifier: identifier) else {
495
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
496
- }
693
+ guard let sampleType = objectTypeFromString(typeIdentifier: typeIdentifier) else {
694
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
695
+ }
497
696
 
498
- let sample = HKCategorySample.init(type: type, value: Int(value), start: start, end: end, metadata: metadata as? [String: Any])
697
+ guard let frequency = HKUpdateFrequency.init(rawValue: updateFrequency) else {
698
+ return reject("UpdateFrequency not valid", "UpdateFrequency not valid", nil)
699
+ }
499
700
 
500
- store.save(sample) { (success: Bool, error: Error?) in
501
- guard let err = error else {
502
- return resolve(success)
503
- }
504
- reject(GENERIC_ERROR, err.localizedDescription, error)
505
- }
701
+ store.enableBackgroundDelivery(for: sampleType, frequency: frequency ) { (success, error) in
702
+ guard let err = error else {
703
+ return resolve(success)
704
+ }
705
+ reject(GENERIC_ERROR, err.localizedDescription, err)
506
706
  }
707
+ }
507
708
 
508
- override func supportedEvents() -> [String]! {
509
- return ["onChange"]
709
+ @objc(disableAllBackgroundDelivery:reject:)
710
+ func disableAllBackgroundDelivery(
711
+ resolve: @escaping RCTPromiseResolveBlock,
712
+ reject: @escaping RCTPromiseRejectBlock
713
+ ) {
714
+ guard let store = _store else {
715
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
510
716
  }
511
717
 
512
- @objc(enableBackgroundDelivery:updateFrequency:resolve:reject:)
513
- func enableBackgroundDelivery(typeIdentifier: String, updateFrequency: Int, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
514
- guard let store = _store else {
515
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
516
- }
718
+ store.disableAllBackgroundDelivery(completion: { (success, error) in
719
+ guard let err = error else {
720
+ return resolve(success)
721
+ }
722
+ reject(GENERIC_ERROR, err.localizedDescription, err)
723
+ })
724
+ }
517
725
 
518
- guard let sampleType = objectTypeFromString(typeIdentifier: typeIdentifier) else {
519
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
520
- }
726
+ @objc(disableBackgroundDelivery:resolve:reject:)
727
+ func disableBackgroundDelivery(
728
+ typeIdentifier: String,
729
+ resolve: @escaping RCTPromiseResolveBlock,
730
+ reject: @escaping RCTPromiseRejectBlock
731
+ ) {
732
+ guard let store = _store else {
733
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
734
+ }
521
735
 
522
- guard let frequency = HKUpdateFrequency.init(rawValue: updateFrequency) else {
523
- return reject("UpdateFrequency not valid", "UpdateFrequency not valid", nil)
524
- }
736
+ guard let sampleType = objectTypeFromString(typeIdentifier: typeIdentifier) else {
737
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
738
+ }
525
739
 
526
- store.enableBackgroundDelivery(for: sampleType, frequency: frequency ) { (success, error) in
527
- guard let err = error else {
528
- return resolve(success)
529
- }
530
- reject(GENERIC_ERROR, err.localizedDescription, err)
531
- }
740
+ store.disableBackgroundDelivery(for: sampleType) { (success, error) in
741
+ guard let err = error else {
742
+ return resolve(success)
743
+ }
744
+ reject(GENERIC_ERROR, err.localizedDescription, err)
532
745
  }
746
+ }
533
747
 
534
- @objc(disableAllBackgroundDelivery:reject:)
535
- func disableAllBackgroundDelivery(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
536
- guard let store = _store else {
537
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
538
- }
748
+ @objc(subscribeToObserverQuery:resolve:reject:)
749
+ func subscribeToObserverQuery(
750
+ typeIdentifier: String,
751
+ resolve: @escaping RCTPromiseResolveBlock,
752
+ reject: @escaping RCTPromiseRejectBlock
753
+ ) {
754
+ guard let store = _store else {
755
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
756
+ }
539
757
 
540
- store.disableAllBackgroundDelivery(completion: { (success, error) in
541
- guard let err = error else {
542
- return resolve(success)
543
- }
544
- reject(GENERIC_ERROR, err.localizedDescription, err)
545
- })
758
+ guard let sampleType = sampleTypeFromString(typeIdentifier: typeIdentifier) else {
759
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
546
760
  }
547
761
 
548
- @objc(disableBackgroundDelivery:resolve:reject:)
549
- func disableBackgroundDelivery(typeIdentifier: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
550
- guard let store = _store else {
551
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
552
- }
762
+ let predicate = HKQuery.predicateForSamples(
763
+ withStart: Date.init(),
764
+ end: nil,
765
+ options: HKQueryOptions.strictStartDate
766
+ )
553
767
 
554
- guard let sampleType = objectTypeFromString(typeIdentifier: typeIdentifier) else {
555
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
556
- }
768
+ let queryId = UUID().uuidString
769
+
770
+ func responder(query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?) {
771
+ if error == nil {
772
+ DispatchQueue.main.async {
773
+ if self.bridge != nil && self.bridge.isValid {
774
+ self.sendEvent(withName: "onChange", body: [
775
+ "typeIdentifier": typeIdentifier
776
+ ])
777
+ }
557
778
 
558
- store.disableBackgroundDelivery(for: sampleType) { (success, error) in
559
- guard let err = error else {
560
- return resolve(success)
561
- }
562
- reject(GENERIC_ERROR, err.localizedDescription, err)
563
779
  }
780
+ handler()
781
+ }
564
782
  }
565
783
 
566
- @objc(subscribeToObserverQuery:resolve:reject:)
567
- func subscribeToObserverQuery(typeIdentifier: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
568
- guard let store = _store else {
569
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
570
- }
784
+ let query = HKObserverQuery(
785
+ sampleType: sampleType,
786
+ predicate: predicate
787
+ ) { (query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?) in
788
+ guard let err = error else {
789
+ return responder(query: query, handler: handler, error: error)
790
+ }
791
+ reject(GENERIC_ERROR, err.localizedDescription, err)
792
+ }
571
793
 
572
- guard let sampleType = sampleTypeFromString(typeIdentifier: typeIdentifier) else {
573
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
574
- }
794
+ store.execute(query)
575
795
 
576
- let predicate = HKQuery.predicateForSamples(withStart: Date.init(), end: nil, options: HKQueryOptions.strictStartDate)
796
+ self._runningQueries.updateValue(query, forKey: queryId)
577
797
 
578
- let queryId = UUID().uuidString
798
+ resolve(queryId)
799
+ }
579
800
 
580
- func responder(query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?) {
581
- if error == nil {
582
- DispatchQueue.main.async {
583
- if self.bridge != nil && self.bridge.isValid {
584
- self.sendEvent(withName: "onChange", body: [
585
- "typeIdentifier": typeIdentifier
586
- ])
587
- }
801
+ @objc(unsubscribeQuery:resolve:reject:)
802
+ func unsubscribeQuery(
803
+ queryId: String,
804
+ resolve: @escaping RCTPromiseResolveBlock,
805
+ reject: @escaping RCTPromiseRejectBlock
806
+ ) {
807
+ guard let store = _store else {
808
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
809
+ }
588
810
 
589
- }
590
- handler()
591
- }
592
- }
811
+ guard let query = self._runningQueries[queryId] else {
812
+ reject("Error", "Query with id " + queryId + " not found", nil)
813
+ return
814
+ }
593
815
 
594
- let query = HKObserverQuery(sampleType: sampleType, predicate: predicate) { (query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?) in
595
- guard let err = error else {
596
- return responder(query: query, handler: handler, error: error)
597
- }
598
- reject(GENERIC_ERROR, err.localizedDescription, err)
816
+ store.stop(query)
817
+
818
+ self._runningQueries.removeValue(forKey: queryId)
819
+
820
+ resolve(true)
821
+ }
822
+
823
+ static override func requiresMainQueueSetup() -> Bool {
824
+ return true
825
+ }
826
+
827
+ @objc(queryStatisticsForQuantity:unitString:from:to:options:resolve:reject:)
828
+ func queryStatisticsForQuantity(
829
+ typeIdentifier: String,
830
+ unitString: String,
831
+ from: Date,
832
+ to: Date,
833
+ options: NSArray,
834
+ resolve: @escaping RCTPromiseResolveBlock,
835
+ reject: @escaping RCTPromiseRejectBlock
836
+ ) {
837
+ guard let store = _store else {
838
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
839
+ }
840
+
841
+ let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
842
+ guard let quantityType = HKObjectType.quantityType(forIdentifier: identifier) else {
843
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
844
+ }
845
+
846
+ let predicate = HKQuery.predicateForSamples(
847
+ withStart: from,
848
+ end: to,
849
+ options: HKQueryOptions.strictEndDate
850
+ )
851
+
852
+ var opts = HKStatisticsOptions.init()
853
+
854
+ for o in options {
855
+ let str = o as! String
856
+ if str == "cumulativeSum" {
857
+ opts.insert(HKStatisticsOptions.cumulativeSum)
858
+ } else if str == "discreteAverage" {
859
+ opts.insert(HKStatisticsOptions.discreteAverage)
860
+ } else if str == "discreteMax" {
861
+ opts.insert(HKStatisticsOptions.discreteMax)
862
+ } else if str == "discreteMin" {
863
+ opts.insert(HKStatisticsOptions.discreteMin)
864
+ }
865
+ if #available(iOS 12, *) {
866
+ if str == "discreteMostRecent" {
867
+ opts.insert(HKStatisticsOptions.discreteMostRecent)
868
+ }
869
+ }
870
+ if #available(iOS 13, *) {
871
+ if str == "duration" {
872
+ opts.insert(HKStatisticsOptions.duration)
873
+ }
874
+ if str == "mostRecent" {
875
+ opts.insert(HKStatisticsOptions.mostRecent)
599
876
  }
877
+ }
600
878
 
601
- store.execute(query)
879
+ if str == "separateBySource" {
880
+ opts.insert(HKStatisticsOptions.separateBySource)
881
+ }
882
+ }
602
883
 
603
- self._runningQueries.updateValue(query, forKey: queryId)
884
+ let query = HKStatisticsQuery.init(
885
+ quantityType: quantityType,
886
+ quantitySamplePredicate: predicate,
887
+ options: opts
888
+ ) { (_, stats: HKStatistics?, _: Error?) in
889
+ var dic = [String: [String: Any]?]()
604
890
 
605
- resolve(queryId)
606
- }
891
+ guard let gottenStats = stats else {
892
+ return resolve(dic)
893
+ }
607
894
 
608
- @objc(unsubscribeQuery:resolve:reject:)
609
- func unsubscribeQuery(queryId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
610
- guard let store = _store else {
611
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
895
+ let unit = HKUnit.init(from: unitString)
896
+ if let averageQuantity = gottenStats.averageQuantity() {
897
+ dic.updateValue(serializeQuantity(unit: unit, quantity: averageQuantity), forKey: "averageQuantity")
898
+ }
899
+ if let maximumQuantity = gottenStats.maximumQuantity() {
900
+ dic.updateValue(serializeQuantity(unit: unit, quantity: maximumQuantity), forKey: "maximumQuantity")
901
+ }
902
+ if let minimumQuantity = gottenStats.minimumQuantity() {
903
+ dic.updateValue(serializeQuantity(unit: unit, quantity: minimumQuantity), forKey: "minimumQuantity")
904
+ }
905
+ if let sumQuantity = gottenStats.sumQuantity() {
906
+ dic.updateValue(serializeQuantity(unit: unit, quantity: sumQuantity), forKey: "sumQuantity")
907
+ }
908
+ if #available(iOS 12, *) {
909
+ if let mostRecent = gottenStats.mostRecentQuantity() {
910
+ dic.updateValue(serializeQuantity(unit: unit, quantity: mostRecent), forKey: "mostRecentQuantity")
612
911
  }
613
912
 
614
- guard let query = self._runningQueries[queryId] else {
615
- reject("Error", "Query with id " + queryId + " not found", nil)
616
- return
913
+ if let mostRecentDateInterval = gottenStats.mostRecentQuantityDateInterval() {
914
+ dic.updateValue([
915
+ "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
916
+ "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
917
+ ], forKey: "mostRecentQuantityDateInterval")
918
+ }
919
+ }
920
+ if #available(iOS 13, *) {
921
+ let durationUnit = HKUnit.second()
922
+ if let duration = gottenStats.duration() {
923
+ dic.updateValue(serializeQuantity(unit: durationUnit, quantity: duration), forKey: "duration")
617
924
  }
925
+ }
618
926
 
619
- store.stop(query)
927
+ resolve(dic)
928
+ }
620
929
 
621
- self._runningQueries.removeValue(forKey: queryId)
930
+ store.execute(query)
931
+ }
622
932
 
623
- resolve(true)
624
- }
933
+ @objc(queryStatisticsCollectionForQuantity:unitString:options:anchorDate:interval:startDate:endDate:resolve:reject:)
934
+ func queryStatisticsCollectionForQuantity(
935
+ typeIdentifier: String,
936
+ unitString: String,
937
+ options: NSArray,
938
+ anchorDate: Date,
939
+ interval: NSDictionary,
940
+ startDate: Date,
941
+ endDate: Date,
942
+ resolve: @escaping RCTPromiseResolveBlock,
943
+ reject: @escaping RCTPromiseRejectBlock
944
+ ) {
945
+ guard let store = _store else {
946
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
947
+ }
625
948
 
626
- static override func requiresMainQueueSetup() -> Bool {
627
- return true
628
- }
949
+ guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier(rawValue: typeIdentifier)) else {
950
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize quantity type for identifier: \(typeIdentifier)", nil)
951
+ }
629
952
 
630
- @objc(queryStatisticsForQuantity:unitString:from:to:options:resolve:reject:)
631
- func queryStatisticsForQuantity(typeIdentifier: String, unitString: String, from: Date, to: Date, options: NSArray, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
632
- guard let store = _store else {
633
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
634
- }
953
+ let opts = hkStatisticsOptionsFromOptions(options)
954
+ let intervalComponents = componentsFromInterval(interval)
635
955
 
636
- let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
637
- guard let quantityType = HKObjectType.quantityType(forIdentifier: identifier) else {
638
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
639
- }
956
+ let query = HKStatisticsCollectionQuery(
957
+ quantityType: quantityType,
958
+ quantitySamplePredicate: nil, // We will use enumerateStatistics to filter with date
959
+ options: opts,
960
+ anchorDate: anchorDate,
961
+ intervalComponents: intervalComponents
962
+ )
640
963
 
641
- let predicate = HKQuery.predicateForSamples(withStart: from, end: to, options: HKQueryOptions.strictEndDate)
964
+ query.initialResultsHandler = { _, statsCollection, error in
965
+ if let error = error {
966
+ return reject(QUERY_ERROR, error.localizedDescription, error)
967
+ }
642
968
 
643
- var opts = HKStatisticsOptions.init()
969
+ var results = [[String: [String: Any]?]]()
644
970
 
645
- for o in options {
646
- let str = o as! String
647
- if str == "cumulativeSum" {
648
- opts.insert(HKStatisticsOptions.cumulativeSum)
649
- } else if str == "discreteAverage" {
650
- opts.insert(HKStatisticsOptions.discreteAverage)
651
- } else if str == "discreteMax" {
652
- opts.insert(HKStatisticsOptions.discreteMax)
653
- } else if str == "discreteMin" {
654
- opts.insert(HKStatisticsOptions.discreteMin)
655
- }
656
- if #available(iOS 12, *) {
657
- if str == "discreteMostRecent" {
658
- opts.insert(HKStatisticsOptions.discreteMostRecent)
659
- }
660
- }
661
- if #available(iOS 13, *) {
662
- if str == "duration" {
663
- opts.insert(HKStatisticsOptions.duration)
664
- }
665
- if str == "mostRecent" {
666
- opts.insert(HKStatisticsOptions.mostRecent)
667
- }
668
- }
971
+ guard let collection = statsCollection else {
972
+ return resolve(results)
973
+ }
669
974
 
670
- if str == "separateBySource" {
671
- opts.insert(HKStatisticsOptions.separateBySource)
672
- }
673
- }
975
+ let unit = HKUnit(from: unitString)
674
976
 
675
- let query = HKStatisticsQuery.init(quantityType: quantityType, quantitySamplePredicate: predicate, options: opts) { (_, stats: HKStatistics?, _: Error?) in
676
- var dic = [String: [String: Any]?]()
977
+ collection.enumerateStatistics(from: startDate, to: endDate) { stats, _ in
978
+ var dic = [String: [String: Any]?]()
677
979
 
678
- guard let gottenStats = stats else {
679
- return resolve(dic)
680
- }
980
+ let startDate = self._dateFormatter.string(from: stats.startDate)
981
+ let endDate = self._dateFormatter.string(from: stats.endDate)
681
982
 
682
- let unit = HKUnit.init(from: unitString)
683
- if let averageQuantity = gottenStats.averageQuantity() {
684
- dic.updateValue(serializeQuantity(unit: unit, quantity: averageQuantity), forKey: "averageQuantity")
685
- }
686
- if let maximumQuantity = gottenStats.maximumQuantity() {
687
- dic.updateValue(serializeQuantity(unit: unit, quantity: maximumQuantity), forKey: "maximumQuantity")
688
- }
689
- if let minimumQuantity = gottenStats.minimumQuantity() {
690
- dic.updateValue(serializeQuantity(unit: unit, quantity: minimumQuantity), forKey: "minimumQuantity")
691
- }
692
- if let sumQuantity = gottenStats.sumQuantity() {
693
- dic.updateValue(serializeQuantity(unit: unit, quantity: sumQuantity), forKey: "sumQuantity")
694
- }
695
- if #available(iOS 12, *) {
696
- if let mostRecent = gottenStats.mostRecentQuantity() {
697
- dic.updateValue(serializeQuantity(unit: unit, quantity: mostRecent), forKey: "mostRecentQuantity")
698
- }
983
+ dic["averageQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.averageQuantity())
984
+ dic["maximumQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.maximumQuantity())
985
+ dic["minimumQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.minimumQuantity())
986
+ dic["sumQuantity"] = serializeStatisticIfExists(unit: unit, quantity: stats.sumQuantity(), stats: stats)
699
987
 
700
- if let mostRecentDateInterval = gottenStats.mostRecentQuantityDateInterval() {
701
- dic.updateValue([
702
- "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
703
- "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
704
- ], forKey: "mostRecentQuantityDateInterval")
705
- }
706
- }
707
- if #available(iOS 13, *) {
708
- let durationUnit = HKUnit.second()
709
- if let duration = gottenStats.duration() {
710
- dic.updateValue(serializeQuantity(unit: durationUnit, quantity: duration), forKey: "duration")
711
- }
712
- }
988
+ if #available(iOS 12, *) {
989
+ dic["mostRecentQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.mostRecentQuantity())
990
+ if let mostRecentDateInterval = stats.mostRecentQuantityDateInterval() {
991
+ dic["mostRecentQuantityDateInterval"] = [
992
+ "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
993
+ "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
994
+ ]
995
+ }
996
+ }
713
997
 
714
- resolve(dic)
715
- }
998
+ if #available(iOS 13, *) {
999
+ let durationUnit = HKUnit.second()
1000
+ dic["duration"] = serializeQuantityIfExists(unit: durationUnit, quantity: stats.duration())
1001
+ }
716
1002
 
717
- store.execute(query)
718
- }
1003
+ results.append(dic)
1004
+ }
1005
+
1006
+ resolve(results)
1007
+ }
719
1008
 
720
- func mapWorkout(workout: HKWorkout, distanceUnit: HKUnit, energyUnit: HKUnit) -> NSMutableDictionary {
1009
+ store.execute(query)
1010
+ }
1011
+
1012
+ func mapWorkout(
1013
+ workout: HKWorkout,
1014
+ distanceUnit: HKUnit,
1015
+ energyUnit: HKUnit
1016
+ ) -> NSMutableDictionary {
721
1017
  let endDate = self._dateFormatter.string(from: workout.endDate)
722
1018
  let startDate = self._dateFormatter.string(from: workout.startDate)
723
1019
 
724
1020
  let dict: NSMutableDictionary = [
725
- "uuid": workout.uuid.uuidString,
726
- "device": serializeDevice(_device: workout.device) as Any,
727
- "duration": workout.duration,
728
- "totalDistance": serializeQuantity(unit: distanceUnit, quantity: workout.totalDistance) as Any,
729
- "totalEnergyBurned": serializeQuantity(unit: energyUnit, quantity: workout.totalEnergyBurned) as Any,
730
- "totalSwimmingStrokeCount": serializeQuantity(unit: HKUnit.count(), quantity: workout.totalSwimmingStrokeCount) as Any,
731
- "workoutActivityType": workout.workoutActivityType.rawValue,
732
- "startDate": startDate,
733
- "endDate": endDate,
734
- "metadata": serializeMetadata(metadata: workout.metadata),
735
- "sourceRevision": serializeSourceRevision(_sourceRevision: workout.sourceRevision) as Any
1021
+ "uuid": workout.uuid.uuidString,
1022
+ "device": serializeDevice(_device: workout.device) as Any,
1023
+ "duration": workout.duration,
1024
+ "totalDistance": serializeQuantity(unit: distanceUnit, quantity: workout.totalDistance) as Any,
1025
+ "totalEnergyBurned": serializeQuantity(unit: energyUnit, quantity: workout.totalEnergyBurned) as Any,
1026
+ "totalSwimmingStrokeCount": serializeQuantity(unit: HKUnit.count(), quantity: workout.totalSwimmingStrokeCount) as Any,
1027
+ "workoutActivityType": workout.workoutActivityType.rawValue,
1028
+ "startDate": startDate,
1029
+ "endDate": endDate,
1030
+ "metadata": serializeMetadata(metadata: workout.metadata),
1031
+ "sourceRevision": serializeSourceRevision(_sourceRevision: workout.sourceRevision) as Any
736
1032
  ]
737
1033
 
738
1034
  // this is used for our laps functionality to get markers
739
1035
  // https://developer.apple.com/documentation/healthkit/hkworkoutevent
740
1036
  var eventArray: [[String: Any]] = []
741
1037
  if let events = workout.workoutEvents {
742
- for event in events {
743
- let eventStartDate = self._dateFormatter.string(from: event.dateInterval.start)
744
- let eventEndDate = self._dateFormatter.string(from: event.dateInterval.end)
745
- let eventDict: [String: Any] = [
746
- "type": event.type.rawValue, // https://developer.apple.com/documentation/healthkit/hkworkouteventtype
747
- "startDate": eventStartDate,
748
- "endDate": eventEndDate
749
- ]
750
- eventArray.append(eventDict)
751
- }
1038
+ for event in events {
1039
+ let eventStartDate = self._dateFormatter.string(from: event.dateInterval.start)
1040
+ let eventEndDate = self._dateFormatter.string(from: event.dateInterval.end)
1041
+ let eventDict: [String: Any] = [
1042
+ "type": event.type.rawValue, // https://developer.apple.com/documentation/healthkit/hkworkouteventtype
1043
+ "startDate": eventStartDate,
1044
+ "endDate": eventEndDate
1045
+ ]
1046
+ eventArray.append(eventDict)
1047
+ }
752
1048
  }
753
1049
  dict["events"] = eventArray
754
1050
 
@@ -757,83 +1053,106 @@ class ReactNativeHealthkit: RCTEventEmitter {
757
1053
  // it seems this might be depricated in the latest beta so this might need updating!
758
1054
  var activitiesArray: [[String: Any]] = []
759
1055
  if #available(iOS 16.0, *) {
760
- let activities: [HKWorkoutActivity] = workout.workoutActivities
761
-
762
- if !activities.isEmpty {
763
- for activity in activities {
764
- var activityStartDate = ""
765
- var activityEndDate = ""
766
- if let start = activity.startDate as Date? {
767
- activityStartDate = self._dateFormatter.string(from: start)
768
- }
769
- if let end = activity.endDate as Date? {
770
- activityEndDate = self._dateFormatter.string(from: end)
771
- }
772
- let activityDict: [String: Any] = [
773
- "startDate": activityStartDate,
774
- "endDate": activityEndDate,
775
- "uuid": activity.uuid.uuidString,
776
- "duration": activity.duration
777
- ]
778
- activitiesArray.append(activityDict)
779
- }
1056
+ let activities: [HKWorkoutActivity] = workout.workoutActivities
1057
+
1058
+ if !activities.isEmpty {
1059
+ for activity in activities {
1060
+ var activityStartDate = ""
1061
+ var activityEndDate = ""
1062
+ if let start = activity.startDate as Date? {
1063
+ activityStartDate = self._dateFormatter.string(from: start)
1064
+ }
1065
+ if let end = activity.endDate as Date? {
1066
+ activityEndDate = self._dateFormatter.string(from: end)
1067
+ }
1068
+ let activityDict: [String: Any] = [
1069
+ "startDate": activityStartDate,
1070
+ "endDate": activityEndDate,
1071
+ "uuid": activity.uuid.uuidString,
1072
+ "duration": activity.duration
1073
+ ]
1074
+ activitiesArray.append(activityDict)
780
1075
  }
1076
+ }
781
1077
  }
782
1078
  dict["activities"] = activitiesArray
783
1079
 
784
1080
  if #available(iOS 11, *) {
785
- dict.setValue(serializeQuantity(unit: HKUnit.count(), quantity: workout.totalFlightsClimbed), forKey: "totalFlightsClimbed")
1081
+ dict.setValue(serializeQuantity(unit: HKUnit.count(), quantity: workout.totalFlightsClimbed), forKey: "totalFlightsClimbed")
786
1082
  }
787
1083
  return dict
788
1084
  }
789
1085
 
790
- @objc(queryWorkoutSamples:distanceUnitString:from:to:limit:ascending:resolve:reject:)
791
- func queryWorkoutSamples(energyUnitString: String, distanceUnitString: String, from: Date, to: Date, limit: Int, ascending: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
792
- guard let store = _store else {
793
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
794
- }
1086
+ @objc(queryWorkoutSamples:distanceUnitString:from:to:limit:ascending:resolve:reject:)
1087
+ func queryWorkoutSamples(
1088
+ energyUnitString: String,
1089
+ distanceUnitString: String,
1090
+ from: Date,
1091
+ to: Date,
1092
+ limit: Int,
1093
+ ascending: Bool,
1094
+ resolve: @escaping RCTPromiseResolveBlock,
1095
+ reject: @escaping RCTPromiseRejectBlock
1096
+ ) {
1097
+ guard let store = _store else {
1098
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1099
+ }
795
1100
 
796
- let from = dateOrNilIfZero(date: from)
797
- let to = dateOrNilIfZero(date: to)
1101
+ let from = dateOrNilIfZero(date: from)
1102
+ let to = dateOrNilIfZero(date: to)
798
1103
 
799
- let predicate = createPredicate(from: from, to: to)
1104
+ let predicate = createPredicate(from: from, to: to)
800
1105
 
801
- let limit = limitOrNilIfZero(limit: limit)
1106
+ let limit = limitOrNilIfZero(limit: limit)
802
1107
 
803
- let energyUnit = HKUnit.init(from: energyUnitString)
804
- let distanceUnit = HKUnit.init(from: distanceUnitString)
1108
+ let energyUnit = HKUnit.init(from: energyUnitString)
1109
+ let distanceUnit = HKUnit.init(from: distanceUnitString)
805
1110
 
806
- let q = HKSampleQuery(sampleType: .workoutType(), predicate: predicate, limit: limit, sortDescriptors: getSortDescriptors(ascending: ascending)) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
807
- guard let err = error else {
808
- guard let samples = sample else {
809
- return resolve([])
810
- }
811
- let arr: NSMutableArray = []
812
-
813
- for s in samples {
814
- if let workout = s as? HKWorkout {
815
- let dict = self.mapWorkout(
816
- workout: workout,
817
- distanceUnit: distanceUnit,
818
- energyUnit: energyUnit
819
- )
1111
+ let q = HKSampleQuery(
1112
+ sampleType: .workoutType(),
1113
+ predicate: predicate,
1114
+ limit: limit,
1115
+ sortDescriptors: getSortDescriptors(ascending: ascending)
1116
+ ) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
1117
+ guard let err = error else {
1118
+ guard let samples = sample else {
1119
+ return resolve([])
1120
+ }
1121
+ let arr: NSMutableArray = []
820
1122
 
821
- arr.add(dict)
822
- }
823
- }
1123
+ for s in samples {
1124
+ if let workout = s as? HKWorkout {
1125
+ let dict = self.mapWorkout(
1126
+ workout: workout,
1127
+ distanceUnit: distanceUnit,
1128
+ energyUnit: energyUnit
1129
+ )
824
1130
 
825
- return resolve(arr)
826
- }
827
- reject(GENERIC_ERROR, err.localizedDescription, err)
1131
+ arr.add(dict)
1132
+ }
828
1133
  }
829
1134
 
830
- store.execute(q)
1135
+ return resolve(arr)
1136
+ }
1137
+ reject(GENERIC_ERROR, err.localizedDescription, err)
831
1138
  }
832
1139
 
1140
+ store.execute(q)
1141
+ }
1142
+
833
1143
  @objc(queryWorkoutSamplesWithAnchor:distanceUnitString:from:to:limit:anchor:resolve:reject:)
834
- func queryWorkoutSamplesWithAnchor(energyUnitString: String, distanceUnitString: String, from: Date, to: Date, limit: Int, anchor: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
1144
+ func queryWorkoutSamplesWithAnchor(
1145
+ energyUnitString: String,
1146
+ distanceUnitString: String,
1147
+ from: Date,
1148
+ to: Date,
1149
+ limit: Int,
1150
+ anchor: String,
1151
+ resolve: @escaping RCTPromiseResolveBlock,
1152
+ reject: @escaping RCTPromiseRejectBlock
1153
+ ) {
835
1154
  guard let store = _store else {
836
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1155
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
837
1156
  }
838
1157
 
839
1158
  let from = dateOrNilIfZero(date: from)
@@ -857,7 +1176,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
857
1176
  ) in
858
1177
  guard let err = error else {
859
1178
  guard let samples = s else {
860
- return resolve([])
1179
+ return resolve([])
861
1180
  }
862
1181
 
863
1182
  let arr: NSMutableArray = []
@@ -899,148 +1218,176 @@ class ReactNativeHealthkit: RCTEventEmitter {
899
1218
  resolve: @escaping RCTPromiseResolveBlock,
900
1219
  reject: @escaping RCTPromiseRejectBlock
901
1220
  ) {
902
- guard let store = _store else {
903
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
904
- }
905
-
906
- let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
907
- guard let sampleType = HKSampleType.quantityType(forIdentifier: identifier) else {
908
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
909
- }
1221
+ guard let store = _store else {
1222
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1223
+ }
910
1224
 
911
- let from = dateOrNilIfZero(date: from)
912
- let to = dateOrNilIfZero(date: to)
913
- let predicate = createPredicate(from: from, to: to)
914
- let limit = limitOrNilIfZero(limit: limit)
1225
+ let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
1226
+ guard let sampleType = HKSampleType.quantityType(forIdentifier: identifier) else {
1227
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
1228
+ }
915
1229
 
916
- let q = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: limit, sortDescriptors: getSortDescriptors(ascending: ascending)) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
917
- guard let err = error else {
918
- guard let samples = sample else {
919
- return resolve([])
920
- }
921
- let arr: NSMutableArray = []
1230
+ let from = dateOrNilIfZero(date: from)
1231
+ let to = dateOrNilIfZero(date: to)
1232
+ let predicate = createPredicate(from: from, to: to)
1233
+ let limit = limitOrNilIfZero(limit: limit)
922
1234
 
923
- for s in samples {
924
- if let sample = s as? HKQuantitySample {
925
- let serialized = serializeQuantitySample(sample: sample, unit: HKUnit.init(from: unitString))
1235
+ let q = HKSampleQuery(
1236
+ sampleType: sampleType,
1237
+ predicate: predicate,
1238
+ limit: limit,
1239
+ sortDescriptors: getSortDescriptors(ascending: ascending)
1240
+ ) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
1241
+ guard let err = error else {
1242
+ guard let samples = sample else {
1243
+ return resolve([])
1244
+ }
1245
+ let arr: NSMutableArray = []
926
1246
 
927
- arr.add(serialized)
928
- }
929
- }
1247
+ for s in samples {
1248
+ if let sample = s as? HKQuantitySample {
1249
+ let serialized = serializeQuantitySample(sample: sample, unit: HKUnit.init(from: unitString))
930
1250
 
931
- return resolve(arr)
932
- }
933
- reject(GENERIC_ERROR, err.localizedDescription, err)
1251
+ arr.add(serialized)
1252
+ }
934
1253
  }
935
1254
 
936
- store.execute(q)
1255
+ return resolve(arr)
1256
+ }
1257
+ reject(GENERIC_ERROR, err.localizedDescription, err)
937
1258
  }
938
1259
 
939
- @objc(queryCorrelationSamples:from:to:resolve:reject:)
940
- func queryCorrelationSamples(typeIdentifier: String, from: Date, to: Date, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
941
- guard let store = _store else {
942
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
943
- }
1260
+ store.execute(q)
1261
+ }
944
1262
 
945
- let identifier = HKCorrelationTypeIdentifier.init(rawValue: typeIdentifier)
946
- guard let sampleType = HKSampleType.correlationType(forIdentifier: identifier) else {
947
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
948
- }
1263
+ @objc(queryCorrelationSamples:from:to:resolve:reject:)
1264
+ func queryCorrelationSamples(
1265
+ typeIdentifier: String,
1266
+ from: Date,
1267
+ to: Date,
1268
+ resolve: @escaping RCTPromiseResolveBlock,
1269
+ reject: @escaping RCTPromiseRejectBlock
1270
+ ) {
1271
+ guard let store = _store else {
1272
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1273
+ }
949
1274
 
950
- let from = from.timeIntervalSince1970 >= 0 ? from : nil
951
- let to = to.timeIntervalSince1970 >= 0 ? to : nil
1275
+ let identifier = HKCorrelationTypeIdentifier.init(rawValue: typeIdentifier)
1276
+ guard let sampleType = HKSampleType.correlationType(forIdentifier: identifier) else {
1277
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
1278
+ }
952
1279
 
953
- let predicate = createPredicate(from: from, to: to)
1280
+ let from = from.timeIntervalSince1970 >= 0 ? from : nil
1281
+ let to = to.timeIntervalSince1970 >= 0 ? to : nil
954
1282
 
955
- let q = HKCorrelationQuery(type: sampleType, predicate: predicate, samplePredicates: nil) { (_: HKCorrelationQuery, _correlations: [HKCorrelation]?, error: Error?) in
956
- guard let err = error else {
957
- guard let correlations = _correlations else {
958
- return resolve([])
959
- }
1283
+ let predicate = createPredicate(from: from, to: to)
1284
+
1285
+ let q = HKCorrelationQuery(
1286
+ type: sampleType,
1287
+ predicate: predicate,
1288
+ samplePredicates: nil
1289
+ ) { (_: HKCorrelationQuery, _correlations: [HKCorrelation]?, error: Error?) in
1290
+ guard let err = error else {
1291
+ guard let correlations = _correlations else {
1292
+ return resolve([])
1293
+ }
960
1294
 
961
- var qts = Set<HKQuantityType>()
962
- for c in correlations {
963
- for object in c.objects {
964
- if let quantitySample = object as? HKQuantitySample {
965
- qts.insert(quantitySample.quantityType)
966
- }
967
- }
1295
+ var qts = Set<HKQuantityType>()
1296
+ for c in correlations {
1297
+ for object in c.objects {
1298
+ if let quantitySample = object as? HKQuantitySample {
1299
+ qts.insert(quantitySample.quantityType)
1300
+ }
1301
+ }
1302
+ }
1303
+ return store.preferredUnits(for: qts) { (map: [HKQuantityType: HKUnit], error: Error?) in
1304
+ guard let e = error else {
1305
+ let collerationsToReturn: NSMutableArray = []
1306
+ for c in correlations {
1307
+ let objects = NSMutableArray()
1308
+ for o in c.objects {
1309
+ if let quantitySample = o as? HKQuantitySample {
1310
+ objects.add(
1311
+ serializeQuantitySample(
1312
+ sample: quantitySample,
1313
+ unit: map[quantitySample.quantityType]!
1314
+ )
1315
+ )
968
1316
  }
969
- return store.preferredUnits(for: qts) { (map: [HKQuantityType: HKUnit], error: Error?) in
970
- guard let e = error else {
971
- let collerationsToReturn: NSMutableArray = []
972
- for c in correlations {
973
- let objects = NSMutableArray()
974
- for o in c.objects {
975
- if let quantitySample = o as? HKQuantitySample {
976
- objects.add(serializeQuantitySample(sample: quantitySample, unit: map[quantitySample.quantityType]!))
977
- }
978
- if let categorySample = o as? HKCategorySample {
979
- objects.add(serializeCategorySample(sample: categorySample))
980
- }
981
- }
982
-
983
- collerationsToReturn.add([
984
- "uuid": c.uuid.uuidString,
985
- "device": serializeDevice(_device: c.device) as Any,
986
- "correlationType": c.correlationType.identifier,
987
- "objects": objects,
988
- "metadata": serializeMetadata(metadata: c.metadata),
989
- "startDate": self._dateFormatter.string(from: c.startDate),
990
- "endDate": self._dateFormatter.string(from: c.endDate)
991
- ])
992
- }
993
-
994
- return resolve(collerationsToReturn)
995
- }
996
- reject(GENERIC_ERROR, e.localizedDescription, e)
1317
+ if let categorySample = o as? HKCategorySample {
1318
+ objects.add(serializeCategorySample(sample: categorySample))
997
1319
  }
1320
+ }
1321
+
1322
+ collerationsToReturn.add([
1323
+ "uuid": c.uuid.uuidString,
1324
+ "device": serializeDevice(_device: c.device) as Any,
1325
+ "correlationType": c.correlationType.identifier,
1326
+ "objects": objects,
1327
+ "metadata": serializeMetadata(metadata: c.metadata),
1328
+ "startDate": self._dateFormatter.string(from: c.startDate),
1329
+ "endDate": self._dateFormatter.string(from: c.endDate)
1330
+ ])
998
1331
  }
999
- reject(GENERIC_ERROR, err.localizedDescription, err)
1000
- }
1001
1332
 
1002
- store.execute(q)
1333
+ return resolve(collerationsToReturn)
1334
+ }
1335
+ reject(GENERIC_ERROR, e.localizedDescription, e)
1336
+ }
1337
+ }
1338
+ reject(GENERIC_ERROR, err.localizedDescription, err)
1003
1339
  }
1004
1340
 
1005
- @objc(queryCategorySamples:from:to:limit:ascending:resolve:reject:)
1006
- func queryCategorySamples(typeIdentifier: String, from: Date, to: Date, limit: Int, ascending: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
1007
- guard let store = _store else {
1008
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1009
- }
1341
+ store.execute(q)
1342
+ }
1010
1343
 
1011
- let identifier = HKCategoryTypeIdentifier.init(rawValue: typeIdentifier)
1012
- guard let sampleType = HKSampleType.categoryType(forIdentifier: identifier) else {
1013
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
1014
- }
1344
+ @objc(queryCategorySamples:from:to:limit:ascending:resolve:reject:)
1345
+ func queryCategorySamples(
1346
+ typeIdentifier: String,
1347
+ from: Date,
1348
+ to: Date,
1349
+ limit: Int,
1350
+ ascending: Bool,
1351
+ resolve: @escaping RCTPromiseResolveBlock,
1352
+ reject: @escaping RCTPromiseRejectBlock
1353
+ ) {
1354
+ guard let store = _store else {
1355
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1356
+ }
1015
1357
 
1016
- let from = dateOrNilIfZero(date: from)
1017
- let to = dateOrNilIfZero(date: to)
1018
- let predicate = createPredicate(from: from, to: to)
1019
- let limit = limitOrNilIfZero(limit: limit)
1358
+ let identifier = HKCategoryTypeIdentifier.init(rawValue: typeIdentifier)
1359
+ guard let sampleType = HKSampleType.categoryType(forIdentifier: identifier) else {
1360
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
1361
+ }
1020
1362
 
1021
- let q = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: limit, sortDescriptors: getSortDescriptors(ascending: ascending)) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
1022
- guard let err = error else {
1023
- guard let samples = sample else {
1024
- return resolve([])
1025
- }
1026
- let arr: NSMutableArray = []
1363
+ let from = dateOrNilIfZero(date: from)
1364
+ let to = dateOrNilIfZero(date: to)
1365
+ let predicate = createPredicate(from: from, to: to)
1366
+ let limit = limitOrNilIfZero(limit: limit)
1027
1367
 
1028
- for s in samples {
1029
- if let sample = s as? HKCategorySample {
1030
- let serialized = serializeCategorySample(sample: sample)
1368
+ let q = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: limit, sortDescriptors: getSortDescriptors(ascending: ascending)) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
1369
+ guard let err = error else {
1370
+ guard let samples = sample else {
1371
+ return resolve([])
1372
+ }
1373
+ let arr: NSMutableArray = []
1031
1374
 
1032
- arr.add(serialized)
1033
- }
1034
- }
1035
- return resolve(arr)
1036
- }
1375
+ for s in samples {
1376
+ if let sample = s as? HKCategorySample {
1377
+ let serialized = serializeCategorySample(sample: sample)
1037
1378
 
1038
- reject(GENERIC_ERROR, err.localizedDescription, err)
1379
+ arr.add(serialized)
1380
+ }
1039
1381
  }
1382
+ return resolve(arr)
1383
+ }
1040
1384
 
1041
- store.execute(q)
1385
+ reject(GENERIC_ERROR, err.localizedDescription, err)
1042
1386
  }
1043
1387
 
1388
+ store.execute(q)
1389
+ }
1390
+
1044
1391
  @objc(queryQuantitySamplesWithAnchor:unitString:from:to:limit:anchor:resolve:reject:)
1045
1392
  func queryQuantitySamplesWithAnchor(
1046
1393
  typeIdentifier: String,
@@ -1052,586 +1399,710 @@ class ReactNativeHealthkit: RCTEventEmitter {
1052
1399
  resolve: @escaping RCTPromiseResolveBlock,
1053
1400
  reject: @escaping RCTPromiseRejectBlock
1054
1401
  ) {
1055
- guard let store = _store else {
1056
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1057
- }
1402
+ guard let store = _store else {
1403
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1404
+ }
1058
1405
 
1059
- let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
1060
- guard let sampleType = HKSampleType.quantityType(forIdentifier: identifier) else {
1061
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
1062
- }
1406
+ let identifier = HKQuantityTypeIdentifier.init(rawValue: typeIdentifier)
1407
+ guard let sampleType = HKSampleType.quantityType(forIdentifier: identifier) else {
1408
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
1409
+ }
1063
1410
 
1064
- let from = dateOrNilIfZero(date: from)
1065
- let to = dateOrNilIfZero(date: to)
1066
- let predicate = createPredicate(from: from, to: to)
1067
- let limit = limitOrNilIfZero(limit: limit)
1411
+ let from = dateOrNilIfZero(date: from)
1412
+ let to = dateOrNilIfZero(date: to)
1413
+ let predicate = createPredicate(from: from, to: to)
1414
+ let limit = limitOrNilIfZero(limit: limit)
1068
1415
 
1069
- let actualAnchor = deserializeHKQueryAnchor(anchor: anchor)
1416
+ let actualAnchor = deserializeHKQueryAnchor(anchor: anchor)
1070
1417
 
1071
- let q = HKAnchoredObjectQuery(
1072
- type: sampleType,
1073
- predicate: predicate,
1074
- anchor: actualAnchor,
1075
- limit: limit
1076
- ) { (
1077
- _: HKAnchoredObjectQuery,
1078
- s: [HKSample]?,
1079
- deletedSamples: [HKDeletedObject]?,
1080
- newAnchor: HKQueryAnchor?,
1081
- error: Error?
1082
- ) in
1083
- guard let err = error else {
1084
- guard let samples = s else {
1085
- return resolve([])
1086
- }
1418
+ let q = HKAnchoredObjectQuery(
1419
+ type: sampleType,
1420
+ predicate: predicate,
1421
+ anchor: actualAnchor,
1422
+ limit: limit
1423
+ ) { (
1424
+ _: HKAnchoredObjectQuery,
1425
+ s: [HKSample]?,
1426
+ deletedSamples: [HKDeletedObject]?,
1427
+ newAnchor: HKQueryAnchor?,
1428
+ error: Error?
1429
+ ) in
1430
+ guard let err = error else {
1431
+ guard let samples = s else {
1432
+ return resolve([])
1433
+ }
1087
1434
 
1088
- return resolve([
1089
- "samples": samples.map({ sample in
1090
- let serialized = serializeQuantitySample(sample: sample as! HKQuantitySample, unit: HKUnit.init(from: unitString))
1435
+ return resolve([
1436
+ "samples": samples.map({ sample in
1437
+ let serialized = serializeQuantitySample(
1438
+ sample: sample as! HKQuantitySample,
1439
+ unit: HKUnit.init(from: unitString)
1440
+ )
1091
1441
 
1092
- return serialized
1093
- }) as Any,
1094
- "deletedSamples": deletedSamples?.map({ sample in
1095
- return serializeDeletedSample(sample: sample)
1096
- }) as Any,
1097
- "newAnchor": serializeAnchor(anchor: newAnchor) as Any
1098
- ])
1099
- }
1100
- reject(GENERIC_ERROR, err.localizedDescription, err)
1101
- }
1442
+ return serialized
1443
+ }) as Any,
1444
+ "deletedSamples": deletedSamples?.map({ sample in
1445
+ return serializeDeletedSample(sample: sample)
1446
+ }) as Any,
1447
+ "newAnchor": serializeAnchor(anchor: newAnchor) as Any
1448
+ ])
1449
+ }
1450
+ reject(GENERIC_ERROR, err.localizedDescription, err)
1451
+ }
1452
+
1453
+ store.execute(q)
1454
+ }
1455
+
1456
+ @objc(queryCategorySamplesWithAnchor:from:to:limit:anchor:resolve:reject:)
1457
+ func queryCategorySamplesWithAnchor(
1458
+ typeIdentifier: String,
1459
+ from: Date,
1460
+ to: Date,
1461
+ limit: Int,
1462
+ anchor: String,
1463
+ resolve: @escaping RCTPromiseResolveBlock,
1464
+ reject: @escaping RCTPromiseRejectBlock
1465
+ ) {
1466
+ guard let store = _store else {
1467
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1468
+ }
1102
1469
 
1103
- store.execute(q)
1470
+ let identifier = HKCategoryTypeIdentifier.init(rawValue: typeIdentifier)
1471
+ guard let sampleType = HKSampleType.categoryType(forIdentifier: identifier) else {
1472
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
1104
1473
  }
1105
1474
 
1106
- @objc(queryCategorySamplesWithAnchor:from:to:limit:anchor:resolve:reject:)
1107
- func queryCategorySamplesWithAnchor(typeIdentifier: String, from: Date, to: Date, limit: Int, anchor: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
1108
- guard let store = _store else {
1109
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1110
- }
1475
+ let from = dateOrNilIfZero(date: from)
1476
+ let to = dateOrNilIfZero(date: to)
1477
+ let predicate = createPredicate(from: from, to: to)
1478
+ let limit = limitOrNilIfZero(limit: limit)
1111
1479
 
1112
- let identifier = HKCategoryTypeIdentifier.init(rawValue: typeIdentifier)
1113
- guard let sampleType = HKSampleType.categoryType(forIdentifier: identifier) else {
1114
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
1480
+ let q = HKAnchoredObjectQuery(
1481
+ type: sampleType,
1482
+ predicate: predicate,
1483
+ anchor: anchor != "" ? base64StringToHKQueryAnchor(base64String: anchor) : nil,
1484
+ limit: limit
1485
+ ) { (
1486
+ _: HKAnchoredObjectQuery,
1487
+ s: [HKSample]?,
1488
+ deletedSamples: [HKDeletedObject]?,
1489
+ newAnchor: HKQueryAnchor?,
1490
+ error: Error?
1491
+ ) in
1492
+ guard let err = error else {
1493
+ guard let samples = s else {
1494
+ return resolve([])
1115
1495
  }
1116
1496
 
1117
- let from = dateOrNilIfZero(date: from)
1118
- let to = dateOrNilIfZero(date: to)
1119
- let predicate = createPredicate(from: from, to: to)
1120
- let limit = limitOrNilIfZero(limit: limit)
1121
-
1122
- let q = HKAnchoredObjectQuery(
1123
- type: sampleType,
1124
- predicate: predicate,
1125
- anchor: anchor != "" ? base64StringToHKQueryAnchor(base64String: anchor) : nil,
1126
- limit: limit
1127
- ) { (
1128
- _: HKAnchoredObjectQuery,
1129
- s: [HKSample]?,
1130
- deletedSamples: [HKDeletedObject]?,
1131
- newAnchor: HKQueryAnchor?,
1132
- error: Error?
1133
- ) in
1134
- guard let err = error else {
1135
- guard let samples = s else {
1136
- return resolve([])
1137
- }
1138
-
1139
- let arr: NSMutableArray = []
1140
-
1141
- for s in samples {
1142
- if let sample = s as? HKCategorySample {
1143
- let serialized = serializeCategorySample(sample: sample)
1497
+ let arr: NSMutableArray = []
1144
1498
 
1145
- arr.add(serialized)
1146
- }
1147
- }
1499
+ for s in samples {
1500
+ if let sample = s as? HKCategorySample {
1501
+ let serialized = serializeCategorySample(sample: sample)
1148
1502
 
1149
- return resolve([
1150
- "samples": arr,
1151
- "deletedSamples": deletedSamples?.map({ sample in
1152
- return serializeDeletedSample(sample: sample)
1153
- }) as Any,
1154
- "newAnchor": serializeAnchor(anchor: newAnchor) as Any
1155
- ])
1503
+ arr.add(serialized)
1156
1504
  }
1157
- reject(GENERIC_ERROR, err.localizedDescription, err)
1158
1505
  }
1159
1506
 
1160
- store.execute(q)
1507
+ return resolve([
1508
+ "samples": arr,
1509
+ "deletedSamples": deletedSamples?.map({ sample in
1510
+ return serializeDeletedSample(sample: sample)
1511
+ }) as Any,
1512
+ "newAnchor": serializeAnchor(anchor: newAnchor) as Any
1513
+ ])
1514
+ }
1515
+ reject(GENERIC_ERROR, err.localizedDescription, err)
1161
1516
  }
1162
1517
 
1163
- @objc(querySources:resolve:reject:)
1164
- func querySources(typeIdentifier: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
1165
-
1166
- guard let store = _store else {
1167
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1168
- }
1518
+ store.execute(q)
1519
+ }
1169
1520
 
1170
- guard let type = objectTypeFromString(typeIdentifier: typeIdentifier) else {
1171
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
1172
- }
1521
+ @objc(querySources:resolve:reject:)
1522
+ func querySources(
1523
+ typeIdentifier: String,
1524
+ resolve: @escaping RCTPromiseResolveBlock,
1525
+ reject: @escaping RCTPromiseRejectBlock
1526
+ ) {
1173
1527
 
1174
- let query = HKSourceQuery(sampleType: type as! HKSampleType, samplePredicate: nil) { (_: HKSourceQuery, source: Set<HKSource>?, error: Error?) in
1175
- guard let err = error else {
1176
- guard let sources = source else {
1177
- return resolve([])
1178
- }
1179
- let arr: NSMutableArray = []
1528
+ guard let store = _store else {
1529
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1530
+ }
1180
1531
 
1181
- for s in sources {
1182
- if let source = s as? HKSource {
1183
- let serialized = serializeSource(source: source)
1532
+ guard let type = objectTypeFromString(typeIdentifier: typeIdentifier) else {
1533
+ return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize " + typeIdentifier, nil)
1534
+ }
1184
1535
 
1185
- arr.add(serialized)
1186
- }
1187
- }
1536
+ let query = HKSourceQuery(
1537
+ sampleType: type as! HKSampleType,
1538
+ samplePredicate: nil
1539
+ ) { (
1540
+ _: HKSourceQuery,
1541
+ source: Set<HKSource>?,
1542
+ error: Error?
1543
+ ) in
1544
+ guard let err = error else {
1545
+ guard let sources = source else {
1546
+ return resolve([])
1547
+ }
1548
+ let arr: NSMutableArray = []
1188
1549
 
1189
- return resolve(arr)
1190
- }
1191
- reject(GENERIC_ERROR, err.localizedDescription, err)
1550
+ for s in sources {
1551
+ let serialized = serializeSource(source: s)
1192
1552
 
1553
+ arr.add(serialized)
1193
1554
  }
1194
1555
 
1195
- store.execute(query)
1196
- }
1556
+ return resolve(arr)
1557
+ }
1558
+ reject(GENERIC_ERROR, err.localizedDescription, err)
1197
1559
 
1198
- @objc(requestAuthorization:read:resolve:withRejecter:)
1199
- func requestAuthorization(toShare: NSDictionary, read: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
1200
- guard let store = _store else {
1201
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1202
- }
1560
+ }
1203
1561
 
1204
- let share = sampleTypesFromDictionary(typeIdentifiers: toShare)
1205
- let toRead = objectTypesFromDictionary(typeIdentifiers: read)
1562
+ store.execute(query)
1563
+ }
1206
1564
 
1207
- store.requestAuthorization(toShare: share, read: toRead) { (success: Bool, error: Error?) in
1208
- guard let err = error else {
1209
- return resolve(success)
1210
- }
1211
- reject(GENERIC_ERROR, err.localizedDescription, err)
1212
- }
1565
+ @objc(requestAuthorization:read:resolve:withRejecter:)
1566
+ func requestAuthorization(
1567
+ toShare: NSDictionary,
1568
+ read: NSDictionary,
1569
+ resolve: @escaping RCTPromiseResolveBlock,
1570
+ reject: @escaping RCTPromiseRejectBlock
1571
+ ) {
1572
+ guard let store = _store else {
1573
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1213
1574
  }
1214
1575
 
1215
- @available(iOS 13.0.0, *)
1216
- func getWorkoutByID(store: HKHealthStore,
1217
- workoutUUID: UUID) async -> HKWorkout? {
1218
- let workoutPredicate = HKQuery.predicateForObject(with: workoutUUID)
1576
+ let share = sampleTypesFromDictionary(typeIdentifiers: toShare)
1577
+ let toRead = objectTypesFromDictionary(typeIdentifiers: read)
1219
1578
 
1220
- let samples = try! await withCheckedThrowingContinuation {
1221
- (continuation: CheckedContinuation<[HKSample], Error>) in
1222
- let query = HKSampleQuery(sampleType: HKObjectType.workoutType(), predicate: workoutPredicate, limit: 1, sortDescriptors: nil) { (_, results, error) in
1579
+ store.requestAuthorization(toShare: share, read: toRead) { (success: Bool, error: Error?) in
1580
+ guard let err = error else {
1581
+ return resolve(success)
1582
+ }
1583
+ reject(GENERIC_ERROR, err.localizedDescription, err)
1584
+ }
1585
+ }
1223
1586
 
1224
- if let hasError = error {
1225
- continuation.resume(throwing: hasError)
1226
- return
1227
- }
1587
+ @available(iOS 13.0.0, *)
1588
+ func getWorkoutByID(store: HKHealthStore,
1589
+ workoutUUID: UUID) async -> HKWorkout? {
1590
+ let workoutPredicate = HKQuery.predicateForObject(with: workoutUUID)
1228
1591
 
1229
- guard let samples = results else {
1230
- fatalError("Should not fail")
1231
- }
1592
+ let samples = try! await withCheckedThrowingContinuation {
1593
+ (continuation: CheckedContinuation<[HKSample], Error>) in
1594
+ let query = HKSampleQuery(
1595
+ sampleType: HKObjectType.workoutType(),
1596
+ predicate: workoutPredicate,
1597
+ limit: 1,
1598
+ sortDescriptors: nil
1599
+ ) { (_, results, error) in
1232
1600
 
1233
- continuation.resume(returning: samples)
1234
- }
1235
- store.execute(query)
1601
+ if let hasError = error {
1602
+ continuation.resume(throwing: hasError)
1603
+ return
1236
1604
  }
1237
1605
 
1238
- guard let workouts = samples as? [HKWorkout] else {
1239
- return nil
1606
+ guard let samples = results else {
1607
+ fatalError("workout samples unexpectedly nil")
1240
1608
  }
1241
1609
 
1242
- return workouts.first ?? nil
1610
+ continuation.resume(returning: samples)
1611
+ }
1612
+ store.execute(query)
1243
1613
  }
1244
1614
 
1245
- @available(iOS 13.0.0, *)
1246
- func _getWorkoutRoutes(store: HKHealthStore, workoutUUID: UUID) async -> [HKWorkoutRoute]? {
1247
- guard let workout = await getWorkoutByID(store: store, workoutUUID: workoutUUID) else {
1248
- return nil
1249
- }
1615
+ guard let workouts = samples as? [HKWorkout] else {
1616
+ return nil
1617
+ }
1250
1618
 
1251
- let workoutPredicate = HKQuery.predicateForObjects(from: workout)
1252
- let samples = try! await withCheckedThrowingContinuation {
1253
- (continuation: CheckedContinuation<[HKSample], Error>) in
1254
- let query = HKAnchoredObjectQuery(type: HKSeriesType.workoutRoute(),
1255
- predicate: workoutPredicate,
1256
- anchor: nil,
1257
- limit: HKObjectQueryNoLimit) {
1258
- (_, samples, _, _, error) in
1259
-
1260
- if let hasError = error {
1261
- continuation.resume(throwing: hasError)
1262
- return
1263
- }
1619
+ return workouts.first ?? nil
1620
+ }
1264
1621
 
1265
- guard let samples = samples else {
1266
- fatalError("Should not fail")
1267
- }
1622
+ @available(iOS 13.0.0, *)
1623
+ func _getWorkoutRoutes(
1624
+ store: HKHealthStore,
1625
+ workoutUUID: UUID
1626
+ ) async -> [HKWorkoutRoute]? {
1627
+ guard let workout = await getWorkoutByID(store: store, workoutUUID: workoutUUID) else {
1628
+ return nil
1629
+ }
1268
1630
 
1269
- continuation.resume(returning: samples)
1270
- }
1271
- store.execute(query)
1631
+ let workoutPredicate = HKQuery.predicateForObjects(from: workout)
1632
+ let samples = try! await withCheckedThrowingContinuation {
1633
+ (continuation: CheckedContinuation<[HKSample], Error>) in
1634
+ let query = HKAnchoredObjectQuery(
1635
+ type: HKSeriesType.workoutRoute(),
1636
+ predicate: workoutPredicate,
1637
+ anchor: nil,
1638
+ limit: HKObjectQueryNoLimit
1639
+ ) {
1640
+ (_, samples, _, _, error) in
1641
+
1642
+ if let hasError = error {
1643
+ continuation.resume(throwing: hasError)
1644
+ return
1272
1645
  }
1273
1646
 
1274
- guard let routes = samples as? [HKWorkoutRoute] else {
1275
- return nil
1647
+ guard let samples = samples else {
1648
+ fatalError("workoutRoute samples unexpectedly nil")
1276
1649
  }
1277
1650
 
1278
- return routes
1651
+ continuation.resume(returning: samples)
1652
+ }
1653
+ store.execute(query)
1654
+ }
1655
+
1656
+ guard let routes = samples as? [HKWorkoutRoute] else {
1657
+ return nil
1279
1658
  }
1280
1659
 
1281
- @available(iOS 13.0.0, *)
1282
- func getRouteLocations(store: HKHealthStore, route: HKWorkoutRoute) async -> [CLLocation] {
1283
- let locations = try! await withCheckedThrowingContinuation {
1284
- (continuation: CheckedContinuation<[CLLocation], Error>) in
1285
- var allLocations: [CLLocation] = []
1660
+ return routes
1661
+ }
1286
1662
 
1287
- let query = HKWorkoutRouteQuery(route: route) {
1288
- (_, locationsOrNil, done, errorOrNil) in
1663
+ @available(iOS 13.0.0, *)
1664
+ func getRouteLocations(
1665
+ store: HKHealthStore,
1666
+ route: HKWorkoutRoute
1667
+ ) async -> [CLLocation] {
1668
+ let locations = try! await withCheckedThrowingContinuation {
1669
+ (continuation: CheckedContinuation<[CLLocation], Error>) in
1670
+ var allLocations: [CLLocation] = []
1289
1671
 
1290
- if let error = errorOrNil {
1291
- continuation.resume(throwing: error)
1292
- return
1293
- }
1672
+ let query = HKWorkoutRouteQuery(route: route) {
1673
+ (_, locationsOrNil, done, errorOrNil) in
1294
1674
 
1295
- guard let currentLocationBatch = locationsOrNil else {
1296
- fatalError("Should not fail")
1297
- }
1675
+ if let error = errorOrNil {
1676
+ continuation.resume(throwing: error)
1677
+ return
1678
+ }
1298
1679
 
1299
- allLocations.append(contentsOf: currentLocationBatch)
1680
+ guard let currentLocationBatch = locationsOrNil else {
1681
+ fatalError("routeLocations unexpectedly nil")
1682
+ }
1300
1683
 
1301
- if done {
1302
- continuation.resume(returning: allLocations)
1303
- }
1304
- }
1684
+ allLocations.append(contentsOf: currentLocationBatch)
1305
1685
 
1306
- store.execute(query)
1686
+ if done {
1687
+ continuation.resume(returning: allLocations)
1307
1688
  }
1689
+ }
1308
1690
 
1309
- return locations
1691
+ store.execute(query)
1310
1692
  }
1311
1693
 
1312
- @available(iOS 13.0.0, *)
1313
- func getSerializedWorkoutLocations(store: HKHealthStore, workoutUUID: UUID) async -> [Dictionary<String, Any>]? {
1314
- let routes = await _getWorkoutRoutes(store: store, workoutUUID: workoutUUID)
1694
+ return locations
1695
+ }
1315
1696
 
1316
- var allRoutes: [Dictionary<String, Any>] = []
1317
- guard let _routes = routes else {
1318
- return nil
1319
- }
1320
- for route in _routes {
1321
- let routeMetadata = serializeMetadata(metadata: route.metadata) as! [String: Any]
1322
- let routeCLLocations = (await getRouteLocations(store: store, route: route))
1323
- let routeLocations = routeCLLocations.enumerated().map {(i, loc) in serializeLocation(location: loc, previousLocation: i == 0 ? nil: routeCLLocations[i - 1])}
1324
- let routeInfos: [String: Any] = ["locations": routeLocations]
1325
- allRoutes.append(routeInfos.merging(routeMetadata) { (current, _) in current })
1326
- }
1327
- return allRoutes
1697
+ @available(iOS 13.0.0, *)
1698
+ func getSerializedWorkoutLocations(
1699
+ store: HKHealthStore,
1700
+ workoutUUID: UUID
1701
+ ) async -> [Dictionary<String, Any>]? {
1702
+ let routes = await _getWorkoutRoutes(
1703
+ store: store,
1704
+ workoutUUID: workoutUUID
1705
+ )
1706
+
1707
+ var allRoutes: [Dictionary<String, Any>] = []
1708
+ guard let _routes = routes else {
1709
+ return nil
1328
1710
  }
1711
+ for route in _routes {
1712
+ let routeMetadata = serializeMetadata(
1713
+ metadata: route.metadata
1714
+ ) as! [String: Any]
1715
+ let routeCLLocations = await getRouteLocations(
1716
+ store: store,
1717
+ route: route
1718
+ )
1719
+ let routeLocations = routeCLLocations.enumerated().map {
1720
+ (i, loc) in serializeLocation(
1721
+ location: loc,
1722
+ previousLocation: i == 0 ? nil: routeCLLocations[i - 1]
1723
+ )
1724
+ }
1725
+ let routeInfos: [String: Any] = ["locations": routeLocations]
1329
1726
 
1330
- @available(iOS 17.0.0, *)
1331
- func getWorkoutPlan(workout: HKWorkout) async -> [String: Any]? {
1332
- #if canImport(WorkoutKit)
1333
- do {
1334
- let workoutPlan = try await workout.workoutPlan
1727
+ allRoutes.append(routeInfos.merging(routeMetadata) { (current, _) in current })
1728
+ }
1729
+ return allRoutes
1730
+ }
1335
1731
 
1336
- var dict = [String: Any]()
1337
- if (workoutPlan?.id) != nil {
1338
- dict["id"] = workoutPlan?.id.uuidString
1732
+ @available(iOS 17.0.0, *)
1733
+ func getWorkoutPlan(workout: HKWorkout) async -> [String: Any]? {
1734
+ #if canImport(WorkoutKit)
1735
+ do {
1736
+ let workoutPlan = try await workout.workoutPlan
1339
1737
 
1340
- }
1341
- if (workoutPlan?.workout.activity) != nil {
1342
- dict["activityType"] = workoutPlan?.workout.activity.rawValue
1343
- }
1738
+ var dict = [String: Any]()
1739
+ if (workoutPlan?.id) != nil {
1740
+ dict["id"] = workoutPlan?.id.uuidString
1344
1741
 
1345
- if dict.isEmpty {
1346
- return nil
1347
- }
1348
- return dict
1349
- } catch {
1350
- return nil
1351
- }
1352
- #else
1742
+ }
1743
+ if (workoutPlan?.workout.activity) != nil {
1744
+ dict["activityType"] = workoutPlan?.workout.activity.rawValue
1745
+ }
1746
+
1747
+ if dict.isEmpty {
1353
1748
  return nil
1354
- #endif
1749
+ }
1750
+ return dict
1751
+ } catch {
1752
+ return nil
1355
1753
  }
1754
+ #else
1755
+ return nil
1756
+ #endif
1757
+ }
1356
1758
 
1357
- @objc(getWorkoutPlanById:resolve:reject:)
1358
- func getWorkoutPlanById(workoutUUID: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
1359
- if #available(iOS 17.0, *) {
1360
- #if canImport(WorkoutKit)
1361
- guard let store = _store else {
1362
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1363
- }
1759
+ @objc(getWorkoutPlanById:resolve:reject:)
1760
+ func getWorkoutPlanById(
1761
+ workoutUUID: String,
1762
+ resolve: @escaping RCTPromiseResolveBlock,
1763
+ reject: @escaping RCTPromiseRejectBlock) {
1764
+ if #available(iOS 17.0, *) {
1765
+ #if canImport(WorkoutKit)
1766
+ guard let store = _store else {
1767
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1768
+ }
1364
1769
 
1365
- Task {
1366
- if let uuid = UUID(uuidString: workoutUUID) {
1367
- let workout = await self.getWorkoutByID(store: store, workoutUUID: uuid)
1368
- if let workout {
1369
- let workoutPlan = await self.getWorkoutPlan(workout: workout)
1370
-
1371
- return resolve(workoutPlan)
1372
- } else {
1373
- return reject(GENERIC_ERROR, "No workout found", nil)
1374
- }
1375
- } else {
1376
- return reject(GENERIC_ERROR, "Invalid UUID", nil)
1377
- }
1378
- }
1379
- #else
1380
- return resolve(nil)
1381
- #endif
1770
+ Task {
1771
+ if let uuid = UUID(uuidString: workoutUUID) {
1772
+ let workout = await self.getWorkoutByID(store: store, workoutUUID: uuid)
1773
+ if let workout {
1774
+ let workoutPlan = await self.getWorkoutPlan(workout: workout)
1775
+
1776
+ return resolve(workoutPlan)
1777
+ } else {
1778
+ return reject(GENERIC_ERROR, "No workout found", nil)
1779
+ }
1382
1780
  } else {
1383
- return resolve(nil)
1781
+ return reject(GENERIC_ERROR, "Invalid UUID", nil)
1384
1782
  }
1783
+ }
1784
+ #else
1785
+ return resolve(nil)
1786
+ #endif
1787
+ } else {
1788
+ return resolve(nil)
1385
1789
  }
1790
+ }
1386
1791
 
1387
- func serializeLocation(location: CLLocation, previousLocation: CLLocation?) -> [String: Any] {
1388
- var distance: CLLocationDistance?
1389
- if let previousLocation = previousLocation {
1390
- distance = location.distance(from: previousLocation)
1391
- } else {
1392
- distance = nil
1393
- }
1394
- return [
1395
- "longitude": location.coordinate.longitude,
1396
- "latitude": location.coordinate.latitude,
1397
- "altitude": location.altitude,
1398
- "speed": location.speed,
1399
- "timestamp": location.timestamp.timeIntervalSince1970,
1400
- "horizontalAccuracy": location.horizontalAccuracy,
1401
- "speedAccuracy": location.speedAccuracy,
1402
- "verticalAccuracy": location.verticalAccuracy,
1403
- "distance": distance as Any
1404
- ]
1792
+ func serializeLocation(location: CLLocation, previousLocation: CLLocation?) -> [String: Any] {
1793
+ var distance: CLLocationDistance?
1794
+ if let previousLocation = previousLocation {
1795
+ distance = location.distance(from: previousLocation)
1796
+ } else {
1797
+ distance = nil
1405
1798
  }
1799
+ return [
1800
+ "longitude": location.coordinate.longitude,
1801
+ "latitude": location.coordinate.latitude,
1802
+ "altitude": location.altitude,
1803
+ "speed": location.speed,
1804
+ "timestamp": location.timestamp.timeIntervalSince1970,
1805
+ "horizontalAccuracy": location.horizontalAccuracy,
1806
+ "speedAccuracy": location.speedAccuracy,
1807
+ "verticalAccuracy": location.verticalAccuracy,
1808
+ "distance": distance as Any
1809
+ ]
1810
+ }
1406
1811
 
1407
- @available(iOS 13.0.0, *)
1408
- @objc(getWorkoutRoutes:resolve:reject:)
1409
- func getWorkoutRoutes(
1410
- workoutUUID: String,
1411
- resolve: @escaping RCTPromiseResolveBlock,
1412
- reject: @escaping RCTPromiseRejectBlock) {
1812
+ @available(iOS 13.0.0, *)
1813
+ @objc(getWorkoutRoutes:resolve:reject:)
1814
+ func getWorkoutRoutes(
1815
+ workoutUUID: String,
1816
+ resolve: @escaping RCTPromiseResolveBlock,
1817
+ reject: @escaping RCTPromiseRejectBlock) {
1413
1818
 
1414
- guard let store = _store else {
1415
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1416
- }
1819
+ guard let store = _store else {
1820
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1821
+ }
1417
1822
 
1418
- guard let _workoutUUID = UUID(uuidString: workoutUUID) else {
1419
- return reject("INVALID_UUID_ERROR", "Invalid UUID received", nil)
1420
- }
1823
+ guard let _workoutUUID = UUID(uuidString: workoutUUID) else {
1824
+ return reject("INVALID_UUID_ERROR", "Invalid UUID received", nil)
1825
+ }
1421
1826
 
1422
- Task {
1423
- do {
1424
- let locations = await getSerializedWorkoutLocations(store: store, workoutUUID: _workoutUUID)
1425
- resolve(locations)
1426
- } catch {
1427
- reject("WORKOUT_LOCATION_ERROR", "Failed to retrieve workout locations", nil)
1428
- }
1827
+ Task {
1828
+ do {
1829
+ let locations = await getSerializedWorkoutLocations(store: store, workoutUUID: _workoutUUID)
1830
+ resolve(locations)
1831
+ } catch {
1832
+ reject("WORKOUT_LOCATION_ERROR", "Failed to retrieve workout locations", nil)
1429
1833
  }
1834
+ }
1430
1835
  }
1431
1836
 
1432
- typealias HKAnchoredObjectQueryResult = (samples: [HKSample], deletedSamples: [HKDeletedObject]?, newAnchor: HKQueryAnchor?)
1433
-
1434
- @available(iOS 13.0.0, *)
1435
- func _queryHeartbeatSeriesSamplesWithAnchor(
1436
- store: HKHealthStore,
1437
- predicate: NSPredicate?,
1438
- limit: Int,
1439
- anchor: HKQueryAnchor?
1440
- ) async throws -> HKAnchoredObjectQueryResult {
1441
- let queryResult = try await withCheckedThrowingContinuation {
1442
- (continuation: CheckedContinuation<HKAnchoredObjectQueryResult, Error>) in
1443
- let query = HKAnchoredObjectQuery(
1444
- type: HKSeriesType.heartbeat(),
1445
- predicate: predicate,
1446
- anchor: anchor,
1447
- limit: limit
1448
- ) { (
1449
- _: HKAnchoredObjectQuery,
1450
- s: [HKSample]?,
1451
- deletedSamples: [HKDeletedObject]?,
1452
- newAnchor: HKQueryAnchor?,
1453
- error: Error?
1454
- ) in
1455
- if let err = error {
1456
- continuation.resume(throwing: err)
1457
- }
1458
-
1459
- guard let samples = s else {
1460
- fatalError("Should not fail")
1461
- }
1837
+ typealias HKAnchoredObjectQueryResult = (
1838
+ samples: [HKSample],
1839
+ deletedSamples: [HKDeletedObject]?,
1840
+ newAnchor: HKQueryAnchor?
1841
+ )
1462
1842
 
1463
- continuation.resume(returning: HKAnchoredObjectQueryResult(samples: samples, deletedSamples: deletedSamples, newAnchor: newAnchor))
1464
- }
1843
+ @available(iOS 13.0.0, *)
1844
+ func _queryHeartbeatSeriesSamplesWithAnchor(
1845
+ store: HKHealthStore,
1846
+ predicate: NSPredicate?,
1847
+ limit: Int,
1848
+ anchor: HKQueryAnchor?
1849
+ ) async throws -> HKAnchoredObjectQueryResult {
1850
+ let queryResult = try await withCheckedThrowingContinuation {
1851
+ (continuation: CheckedContinuation<HKAnchoredObjectQueryResult, Error>) in
1852
+ let query = HKAnchoredObjectQuery(
1853
+ type: HKSeriesType.heartbeat(),
1854
+ predicate: predicate,
1855
+ anchor: anchor,
1856
+ limit: limit
1857
+ ) { (
1858
+ _: HKAnchoredObjectQuery,
1859
+ s: [HKSample]?,
1860
+ deletedSamples: [HKDeletedObject]?,
1861
+ newAnchor: HKQueryAnchor?,
1862
+ error: Error?
1863
+ ) in
1864
+ if let err = error {
1865
+ return continuation.resume(throwing: err)
1866
+ } else {
1867
+ guard let samples = s else {
1868
+ fatalError("heartbeatSeries unexpectedly nil")
1869
+ }
1465
1870
 
1466
- store.execute(query)
1871
+ continuation.resume(
1872
+ returning: HKAnchoredObjectQueryResult(
1873
+ samples: samples,
1874
+ deletedSamples: deletedSamples,
1875
+ newAnchor: newAnchor)
1876
+ )
1467
1877
  }
1878
+ }
1468
1879
 
1469
- return queryResult
1880
+ store.execute(query)
1470
1881
  }
1471
1882
 
1472
- @available(iOS 13.0.0, *)
1473
- func getHeartbeatSeriesHeartbeats(store: HKHealthStore, sample: HKHeartbeatSeriesSample) async throws -> [Dictionary<String, Any>] {
1474
- let beatTimes = try await withCheckedThrowingContinuation {
1475
- (continuation: CheckedContinuation<[Dictionary<String, Any>], Error>) in
1476
- var allBeats: [Dictionary<String, Any>] = []
1477
-
1478
- let query = HKHeartbeatSeriesQuery(heartbeatSeries: sample) { (
1479
- _: HKHeartbeatSeriesQuery,
1480
- timeSinceSeriesStart: TimeInterval,
1481
- precededByGap: Bool,
1482
- done: Bool,
1483
- error: Error?
1484
- ) in
1485
- if let err = error {
1486
- continuation.resume(throwing: err)
1487
- }
1488
-
1489
- let timeDict: [String: Any] = [
1490
- "timeSinceSeriesStart": timeSinceSeriesStart,
1491
- "precededByGap": precededByGap
1492
- ]
1883
+ return queryResult
1884
+ }
1493
1885
 
1494
- allBeats.append(timeDict)
1886
+ @available(iOS 13.0.0, *)
1887
+ func getHeartbeatSeriesHeartbeats(
1888
+ store: HKHealthStore,
1889
+ sample: HKHeartbeatSeriesSample
1890
+ ) async throws -> [Dictionary<String, Any>] {
1891
+ let beatTimes = try await withCheckedThrowingContinuation {
1892
+ (continuation: CheckedContinuation<[Dictionary<String, Any>], Error>) in
1893
+ var allBeats: [Dictionary<String, Any>] = []
1894
+
1895
+ let query = HKHeartbeatSeriesQuery(heartbeatSeries: sample) { (
1896
+ _: HKHeartbeatSeriesQuery,
1897
+ timeSinceSeriesStart: TimeInterval,
1898
+ precededByGap: Bool,
1899
+ done: Bool,
1900
+ error: Error?
1901
+ ) in
1902
+ if let err = error {
1903
+ return continuation.resume(throwing: err)
1904
+ } else {
1905
+ let timeDict: [String: Any] = [
1906
+ "timeSinceSeriesStart": timeSinceSeriesStart,
1907
+ "precededByGap": precededByGap
1908
+ ]
1495
1909
 
1496
- if done {
1497
- continuation.resume(returning: allBeats)
1498
- }
1499
- }
1910
+ allBeats.append(timeDict)
1500
1911
 
1501
- store.execute(query)
1912
+ if done {
1913
+ continuation.resume(returning: allBeats)
1914
+ }
1502
1915
  }
1916
+ }
1503
1917
 
1504
- return beatTimes
1918
+ store.execute(query)
1505
1919
  }
1506
1920
 
1507
- @available(iOS 13.0.0, *)
1508
- func getSerializedHeartbeatSeriesSample(store: HKHealthStore, sample: HKHeartbeatSeriesSample) async throws -> [String: Any] {
1509
- let sampleMetadata = serializeMetadata(metadata: sample.metadata) as! [String: Any]
1510
- let sampleHeartbeats = try await getHeartbeatSeriesHeartbeats(store: store, sample: sample)
1511
-
1512
- return [
1513
- "uuid": sample.uuid.uuidString,
1514
- "device": serializeDevice(_device: sample.device) as Any,
1515
- "startDate": self._dateFormatter.string(from: sample.startDate),
1516
- "endDate": self._dateFormatter.string(from: sample.endDate),
1517
- "heartbeats": sampleHeartbeats as Any,
1518
- "metadata": serializeMetadata(metadata: sample.metadata),
1519
- "sourceRevision": serializeSourceRevision(_sourceRevision: sample.sourceRevision) as Any
1520
- ]
1521
- }
1921
+ return beatTimes
1922
+ }
1522
1923
 
1523
- @available(iOS 13.0.0, *)
1524
- @objc(queryHeartbeatSeriesSamplesWithAnchor:to:limit:anchor:resolve:reject:)
1525
- func queryHeartbeatSeriesSamplesWithAnchor(
1526
- from: Date,
1527
- to: Date,
1528
- limit: Int,
1529
- anchor: String,
1530
- resolve: @escaping RCTPromiseResolveBlock,
1531
- reject: @escaping RCTPromiseRejectBlock
1532
- ) {
1533
- guard let store = _store else {
1534
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1535
- }
1924
+ @available(iOS 13.0.0, *)
1925
+ func getSerializedHeartbeatSeriesSample(
1926
+ store: HKHealthStore,
1927
+ sample: HKHeartbeatSeriesSample
1928
+ ) async throws -> [String: Any] {
1929
+ let sampleMetadata = serializeMetadata(metadata: sample.metadata) as! [String: Any]
1930
+ let sampleHeartbeats = try await getHeartbeatSeriesHeartbeats(
1931
+ store: store,
1932
+ sample: sample
1933
+ )
1934
+
1935
+ return [
1936
+ "uuid": sample.uuid.uuidString,
1937
+ "device": serializeDevice(_device: sample.device) as Any,
1938
+ "startDate": self._dateFormatter.string(from: sample.startDate),
1939
+ "endDate": self._dateFormatter.string(from: sample.endDate),
1940
+ "heartbeats": sampleHeartbeats as Any,
1941
+ "metadata": serializeMetadata(metadata: sample.metadata),
1942
+ "sourceRevision": serializeSourceRevision(_sourceRevision: sample.sourceRevision) as Any
1943
+ ]
1944
+ }
1536
1945
 
1537
- Task {
1538
- do {
1539
- let from = dateOrNilIfZero(date: from)
1540
- let to = dateOrNilIfZero(date: to)
1946
+ @available(iOS 13.0.0, *)
1947
+ @objc(queryHeartbeatSeriesSamplesWithAnchor:to:limit:anchor:resolve:reject:)
1948
+ func queryHeartbeatSeriesSamplesWithAnchor(
1949
+ from: Date,
1950
+ to: Date,
1951
+ limit: Int,
1952
+ anchor: String,
1953
+ resolve: @escaping RCTPromiseResolveBlock,
1954
+ reject: @escaping RCTPromiseRejectBlock
1955
+ ) {
1956
+ guard let store = _store else {
1957
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1958
+ }
1541
1959
 
1542
- let predicate = createPredicate(from: from, to: to)
1960
+ Task {
1961
+ do {
1962
+ let from = dateOrNilIfZero(date: from)
1963
+ let to = dateOrNilIfZero(date: to)
1543
1964
 
1544
- let limit = limitOrNilIfZero(limit: limit)
1965
+ let predicate = createPredicate(from: from, to: to)
1545
1966
 
1546
- let actualAnchor = deserializeHKQueryAnchor(anchor: anchor)
1967
+ let limit = limitOrNilIfZero(limit: limit)
1547
1968
 
1548
- let queryResult = try await _queryHeartbeatSeriesSamplesWithAnchor(store: store, predicate: predicate, limit: limit, anchor: actualAnchor)
1969
+ let actualAnchor = deserializeHKQueryAnchor(anchor: anchor)
1549
1970
 
1550
- var allHeartbeatSamples: [Dictionary<String, Any>] = []
1551
- for sample in queryResult.samples as! [HKHeartbeatSeriesSample] {
1552
- allHeartbeatSamples.append(try await getSerializedHeartbeatSeriesSample(store: store, sample: sample))
1553
- }
1971
+ let queryResult = try await _queryHeartbeatSeriesSamplesWithAnchor(
1972
+ store: store,
1973
+ predicate: predicate,
1974
+ limit: limit,
1975
+ anchor: actualAnchor
1976
+ )
1554
1977
 
1555
- resolve([
1556
- "samples": allHeartbeatSamples as Any,
1557
- "deletedSamples": queryResult.deletedSamples?.map({ sample in
1558
- return serializeDeletedSample(sample: sample)
1559
- }) as Any,
1560
- "newAnchor": serializeAnchor(anchor: queryResult.newAnchor) as Any
1561
- ])
1562
- } catch {
1563
- reject(GENERIC_ERROR, error.localizedDescription, error)
1564
- }
1978
+ var allHeartbeatSamples: [Dictionary<String, Any>] = []
1979
+ for sample in queryResult.samples as! [HKHeartbeatSeriesSample] {
1980
+ allHeartbeatSamples.append(
1981
+ try await getSerializedHeartbeatSeriesSample(
1982
+ store: store,
1983
+ sample: sample
1984
+ )
1985
+ )
1565
1986
  }
1987
+
1988
+ resolve([
1989
+ "samples": allHeartbeatSamples as Any,
1990
+ "deletedSamples": queryResult.deletedSamples?.map({ sample in
1991
+ return serializeDeletedSample(sample: sample)
1992
+ }) as Any,
1993
+ "newAnchor": serializeAnchor(anchor: queryResult.newAnchor) as Any
1994
+ ])
1995
+ } catch {
1996
+ reject(GENERIC_ERROR, error.localizedDescription, error)
1997
+ }
1566
1998
  }
1999
+ }
1567
2000
 
1568
2001
  @available(iOS 13.0.0, *)
1569
2002
  func _queryHeartbeatSeriesSamples(
1570
- store: HKHealthStore,
1571
- predicate: NSPredicate?,
1572
- limit: Int,
1573
- ascending: Bool
2003
+ store: HKHealthStore,
2004
+ predicate: NSPredicate?,
2005
+ limit: Int,
2006
+ ascending: Bool
1574
2007
  ) async throws -> [HKSample] {
1575
- let samples = try await withCheckedThrowingContinuation {
1576
- (continuation: CheckedContinuation<[HKSample], Error>) in
1577
-
1578
- let query = HKSampleQuery(
1579
- sampleType: HKSeriesType.heartbeat(),
1580
- predicate: predicate,
1581
- limit: limit,
1582
- sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)]
1583
- ) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
1584
- if let err = error {
1585
- continuation.resume(throwing: err)
1586
- } else {
1587
- guard let actualSamples = sample else {
1588
- fatalError("Should not fail")
1589
- }
1590
- continuation.resume(returning: actualSamples)
1591
- }
2008
+ let samples = try await withCheckedThrowingContinuation {
2009
+ (continuation: CheckedContinuation<[HKSample], Error>) in
2010
+
2011
+ let query = HKSampleQuery(
2012
+ sampleType: HKSeriesType.heartbeat(),
2013
+ predicate: predicate,
2014
+ limit: limit,
2015
+ sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)]
2016
+ ) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
2017
+ if let err = error {
2018
+ continuation.resume(throwing: err)
2019
+ } else {
2020
+ guard let actualSamples = sample else {
2021
+ fatalError("heartbeatSeries samples unexpectedly nil")
1592
2022
  }
1593
-
1594
- store.execute(query)
2023
+ continuation.resume(returning: actualSamples)
2024
+ }
1595
2025
  }
1596
2026
 
1597
- return samples
2027
+ store.execute(query)
2028
+ }
2029
+
2030
+ return samples
1598
2031
  }
1599
2032
 
1600
2033
  @available(iOS 13.0.0, *)
1601
2034
  @objc(queryHeartbeatSeriesSamples:to:limit:ascending:resolve:reject:)
1602
2035
  func queryHeartbeatSeriesSamples(
1603
- from: Date,
1604
- to: Date,
1605
- limit: Int,
1606
- ascending: Bool,
1607
- resolve: @escaping RCTPromiseResolveBlock,
1608
- reject: @escaping RCTPromiseRejectBlock
2036
+ from: Date,
2037
+ to: Date,
2038
+ limit: Int,
2039
+ ascending: Bool,
2040
+ resolve: @escaping RCTPromiseResolveBlock,
2041
+ reject: @escaping RCTPromiseRejectBlock
1609
2042
  ) {
1610
- guard let store = _store else {
1611
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1612
- }
2043
+ guard let store = _store else {
2044
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
2045
+ }
1613
2046
 
1614
- Task {
1615
- do {
1616
- let from = dateOrNilIfZero(date: from)
1617
- let to = dateOrNilIfZero(date: to)
2047
+ Task {
2048
+ do {
2049
+ let from = dateOrNilIfZero(date: from)
2050
+ let to = dateOrNilIfZero(date: to)
1618
2051
 
1619
- let predicate = createPredicate(from: from, to: to)
2052
+ let predicate = createPredicate(
2053
+ from: from,
2054
+ to: to
2055
+ )
1620
2056
 
1621
- let limit = limitOrNilIfZero(limit: limit)
2057
+ let limit = limitOrNilIfZero(
2058
+ limit: limit
2059
+ )
1622
2060
 
1623
- let samples = try await _queryHeartbeatSeriesSamples(store: store, predicate: predicate, limit: limit, ascending: ascending)
2061
+ let samples = try await _queryHeartbeatSeriesSamples(
2062
+ store: store,
2063
+ predicate: predicate,
2064
+ limit: limit,
2065
+ ascending: ascending
2066
+ )
1624
2067
 
1625
- var allHeartbeatSamples: [Dictionary<String, Any>] = []
1626
- for sample in samples as! [HKHeartbeatSeriesSample] {
1627
- allHeartbeatSamples.append(try await getSerializedHeartbeatSeriesSample(store: store, sample: sample))
1628
- }
2068
+ var allHeartbeatSamples: [Dictionary<String, Any>] = []
2069
+ for sample in samples as! [HKHeartbeatSeriesSample] {
2070
+ allHeartbeatSamples.append(
2071
+ try await getSerializedHeartbeatSeriesSample(
2072
+ store: store,
2073
+ sample: sample
2074
+ )
2075
+ )
2076
+ }
1629
2077
 
1630
- resolve(allHeartbeatSamples as Any)
1631
- } catch {
1632
- reject(GENERIC_ERROR, error.localizedDescription, error)
1633
- }
2078
+ resolve(allHeartbeatSamples as Any)
2079
+ } catch {
2080
+ reject(GENERIC_ERROR, error.localizedDescription, error)
2081
+ }
2082
+ }
2083
+ }
2084
+
2085
+ @available(iOS 17.0.0, *)
2086
+ @objc(startWatchAppWithWorkoutConfiguration:resolve:reject:)
2087
+ func startWatchAppWithWorkoutConfiguration(
2088
+ _ workoutConfiguration: NSDictionary,
2089
+ resolve: @escaping RCTPromiseResolveBlock,
2090
+ reject: @escaping RCTPromiseRejectBlock
2091
+ ) {
2092
+ guard let store = _store else {
2093
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
2094
+ }
2095
+
2096
+ let configuration = parseWorkoutConfiguration(workoutConfiguration)
2097
+
2098
+ store.startWatchApp(with: configuration) { success, error in
2099
+ if let error {
2100
+ reject(INIT_ERROR, INIT_ERROR_MESSAGE, error)
2101
+ return
1634
2102
  }
2103
+
2104
+ resolve(success)
2105
+ }
1635
2106
  }
1636
2107
 
1637
2108
  }