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