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