@kingstinct/react-native-healthkit 15.1.0 → 16.1.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 +22 -3
- package/ReactNativeHealthkit.podspec +17 -6
- package/app.plugin.js +18 -34
- package/app.plugin.ts +25 -50
- package/ios/CategoryTypeModule.swift +1 -0
- package/ios/CharacteristicTypeModule.swift +7 -6
- package/ios/Constants.swift +9 -34
- package/ios/CoreModule.swift +72 -41
- package/ios/CorrelationTypeModule.swift +3 -2
- package/ios/ElectrocardiogramModule.swift +4 -3
- package/ios/HeartbeatSeriesModule.swift +4 -3
- package/ios/Helpers.swift +10 -243
- package/ios/MedicationModule.swift +9 -8
- package/ios/MemoryEstimation.swift +1 -0
- package/ios/PredicateHelpers.swift +16 -86
- package/ios/QuantityTypeModule.swift +3 -2
- package/ios/Serializers.swift +16 -67
- package/ios/SourceProxy.swift +1 -0
- package/ios/StateOfMindModule.swift +3 -2
- package/ios/WorkoutProxy.swift +3 -2
- package/ios/WorkoutsModule.swift +4 -3
- package/lib/commonjs/generated/healthkit.generated.js +3 -0
- package/lib/commonjs/healthkit.ios.js +4 -2
- package/lib/commonjs/healthkit.js +21 -78
- package/lib/commonjs/types/Auth.js +3 -18
- package/lib/commonjs/types/Background.js +2 -10
- package/lib/commonjs/types/QueryOptions.js +2 -17
- package/lib/module/generated/healthkit.generated.js +3 -0
- package/lib/module/healthkit.ios.js +2 -0
- package/lib/module/healthkit.js +20 -77
- package/lib/module/types/Auth.js +1 -18
- package/lib/module/types/Background.js +1 -10
- package/lib/module/types/QueryOptions.js +1 -17
- package/lib/typescript/generated/healthkit.generated.d.ts +6 -2
- package/lib/typescript/healthkit.d.ts +3 -2
- package/lib/typescript/healthkit.ios.d.ts +11 -9
- package/lib/typescript/specs/CoreModule.nitro.d.ts +16 -0
- package/lib/typescript/specs/SourceProxy.nitro.d.ts +2 -4
- package/lib/typescript/types/Auth.d.ts +1 -16
- package/lib/typescript/types/Background.d.ts +1 -9
- package/lib/typescript/types/Device.d.ts +1 -13
- package/lib/typescript/types/QuantityType.d.ts +1 -1
- package/lib/typescript/types/QueryOptions.d.ts +3 -37
- package/lib/typescript/types/Shared.d.ts +2 -25
- package/lib/typescript/types/Source.d.ts +1 -10
- package/lib/typescript/types/Subscriptions.d.ts +2 -3
- package/nitrogen/generated/ios/ReactNativeHealthkit-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/ReactNativeHealthkit-Swift-Cxx-Bridge.hpp +68 -0
- package/nitrogen/generated/ios/c++/HybridCategoryTypeModuleSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/c++/HybridCoreModuleSpecSwift.hpp +8 -0
- package/nitrogen/generated/ios/c++/HybridCorrelationTypeModuleSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/c++/HybridElectrocardiogramModuleSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/c++/HybridHeartbeatSeriesModuleSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/c++/HybridMedicationModuleSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/c++/HybridQuantityTypeModuleSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/c++/HybridStateOfMindModuleSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/swift/CategorySample.swift +8 -8
- package/nitrogen/generated/ios/swift/CorrelationSample.swift +8 -8
- package/nitrogen/generated/ios/swift/ElectrocardiogramSample.swift +8 -8
- package/nitrogen/generated/ios/swift/Func_void_std__unordered_map_std__string__std__chrono__system_clock__time_point_.swift +54 -0
- package/nitrogen/generated/ios/swift/HeartbeatSeriesSample.swift +8 -8
- package/nitrogen/generated/ios/swift/HybridCoreModuleSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridCoreModuleSpec_cxx.swift +25 -0
- package/nitrogen/generated/ios/swift/MedicationDoseEvent.swift +8 -8
- package/nitrogen/generated/ios/swift/ObjectTypeIdentifier.swift +4 -0
- package/nitrogen/generated/ios/swift/QuantitySample.swift +8 -8
- package/nitrogen/generated/ios/swift/QuantityTypeIdentifier.swift +4 -0
- package/nitrogen/generated/ios/swift/QuantityTypeIdentifierWriteable.swift +4 -0
- package/nitrogen/generated/ios/swift/SampleTypeIdentifier.swift +4 -0
- package/nitrogen/generated/ios/swift/SampleTypeIdentifierWriteable.swift +4 -0
- package/nitrogen/generated/ios/swift/StateOfMindSample.swift +8 -8
- package/nitrogen/generated/shared/c++/CategorySample.hpp +7 -7
- package/nitrogen/generated/shared/c++/CorrelationSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/ElectrocardiogramSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/HeartbeatSeriesSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/HybridCoreModuleSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridCoreModuleSpec.hpp +3 -1
- package/nitrogen/generated/shared/c++/MedicationDoseEvent.hpp +7 -7
- package/nitrogen/generated/shared/c++/ObjectTypeIdentifier.hpp +49 -45
- package/nitrogen/generated/shared/c++/QuantitySample.hpp +7 -7
- package/nitrogen/generated/shared/c++/QuantityTypeIdentifier.hpp +42 -38
- package/nitrogen/generated/shared/c++/QuantityTypeIdentifierWriteable.hpp +42 -38
- package/nitrogen/generated/shared/c++/SampleTypeIdentifier.hpp +48 -44
- package/nitrogen/generated/shared/c++/SampleTypeIdentifierWriteable.hpp +47 -43
- package/nitrogen/generated/shared/c++/StateOfMindSample.hpp +7 -7
- package/package.json +4 -1
- package/src/generated/healthkit-schema.json +13 -0
- package/src/generated/healthkit.generated.ts +5 -0
- package/src/healthkit.ios.ts +3 -0
- package/src/healthkit.ts +27 -84
- package/src/specs/CoreModule.nitro.ts +19 -0
- package/src/specs/SourceProxy.nitro.ts +2 -4
- package/src/test-setup.ts +1 -0
- package/src/types/Auth.ts +4 -17
- package/src/types/Background.ts +1 -9
- package/src/types/Device.ts +1 -13
- package/src/types/QuantityType.ts +1 -1
- package/src/types/QueryOptions.ts +7 -65
- package/src/types/Shared.ts +7 -28
- package/src/types/Source.ts +1 -11
- package/src/types/Subscriptions.ts +2 -3
- package/ios/BackgroundDeliveryManager.swift +0 -284
- package/ios/BackgroundLaunchHook.mm +0 -41
- package/ios/Bridge.h +0 -10
- package/ios/ExceptionCatcher.h +0 -5
- package/ios/ExceptionCatcher.mm +0 -32
- package/ios/module.modulemap +0 -5
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import HealthKit
|
|
2
2
|
import NitroModules
|
|
3
|
+
import ReactNativeHealthkitCore
|
|
3
4
|
|
|
4
5
|
// MARK: - Helpers
|
|
5
6
|
|
|
@@ -38,10 +39,10 @@ private func serializeECGSample(sample: HKElectrocardiogram, includeVoltages: Bo
|
|
|
38
39
|
startDate: sample.startDate,
|
|
39
40
|
endDate: sample.endDate,
|
|
40
41
|
hasUndeterminedDuration: sample.hasUndeterminedDuration,
|
|
41
|
-
metadata: serializeMetadata(sample.metadata),
|
|
42
42
|
uuid: sample.uuid.uuidString,
|
|
43
43
|
sourceRevision: serializeSourceRevision(sample.sourceRevision),
|
|
44
|
-
device: serializeDevice(hkDevice: sample.device)
|
|
44
|
+
device: serializeDevice(hkDevice: sample.device),
|
|
45
|
+
metadata: serializeMetadata(sample.metadata)
|
|
45
46
|
)
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -76,7 +77,7 @@ func getECGVoltages(sample: HKElectrocardiogram) async throws -> [Electrocardiog
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
+
healthStore.execute(q)
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import HealthKit
|
|
2
2
|
import NitroModules
|
|
3
|
+
import ReactNativeHealthkitCore
|
|
3
4
|
|
|
4
5
|
func serializeHeartbeatSeriesSample(sample: HKHeartbeatSeriesSample) async throws
|
|
5
6
|
-> HeartbeatSeriesSample {
|
|
@@ -11,10 +12,10 @@ func serializeHeartbeatSeriesSample(sample: HKHeartbeatSeriesSample) async throw
|
|
|
11
12
|
startDate: sample.startDate,
|
|
12
13
|
endDate: sample.endDate,
|
|
13
14
|
hasUndeterminedDuration: sample.hasUndeterminedDuration,
|
|
14
|
-
metadata: serializeMetadata(sample.metadata),
|
|
15
15
|
uuid: sample.uuid.uuidString,
|
|
16
16
|
sourceRevision: serializeSourceRevision(sample.sourceRevision),
|
|
17
|
-
device: serializeDevice(hkDevice: sample.device)
|
|
17
|
+
device: serializeDevice(hkDevice: sample.device),
|
|
18
|
+
metadata: serializeMetadata(sample.metadata)
|
|
18
19
|
)
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -47,7 +48,7 @@ func getHeartbeatSeriesHeartbeats(sample: HKHeartbeatSeriesSample) async throws
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
healthStore.execute(query)
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
package/ios/Helpers.swift
CHANGED
|
@@ -8,167 +8,14 @@
|
|
|
8
8
|
import Foundation
|
|
9
9
|
import HealthKit
|
|
10
10
|
import NitroModules
|
|
11
|
+
import ReactNativeHealthkitCore
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return hkUnit
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
throw runtimeErrorWithPrefix("Supplied invalid '\(unitString)' as HKUnit")
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
func getQueryLimit(_ limit: Double) -> Int {
|
|
24
|
-
if limit == .infinity || limit <= 0 || limit == .nan || limit == .signalingNaN {
|
|
25
|
-
return HKObjectQueryNoLimit
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return Int(limit)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
struct AnchoredQueryResponse {
|
|
32
|
-
var samples: [HKSample]
|
|
33
|
-
var deletedSamples: [DeletedSample]
|
|
34
|
-
var newAnchor: String
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
func sampleAnchoredQueryAsync(
|
|
38
|
-
sampleType: HKSampleType,
|
|
39
|
-
limit: Double,
|
|
40
|
-
queryAnchor: String?,
|
|
41
|
-
predicate: NSPredicate?
|
|
42
|
-
) async throws -> AnchoredQueryResponse {
|
|
43
|
-
let queryAnchor = try deserializeHKQueryAnchor(base64String: queryAnchor)
|
|
44
|
-
|
|
45
|
-
return try await withCheckedThrowingContinuation { continuation in
|
|
46
|
-
let query = HKAnchoredObjectQuery(
|
|
47
|
-
type: sampleType,
|
|
48
|
-
predicate: predicate,
|
|
49
|
-
anchor: queryAnchor,
|
|
50
|
-
limit: getQueryLimit(limit)
|
|
51
|
-
) {
|
|
52
|
-
(
|
|
53
|
-
_: HKAnchoredObjectQuery, samples: [HKSample]?, deletedSamples: [HKDeletedObject]?,
|
|
54
|
-
newAnchor:
|
|
55
|
-
HKQueryAnchor?, error: Error?
|
|
56
|
-
) in
|
|
57
|
-
DispatchQueue.main.async {
|
|
58
|
-
if let error = error {
|
|
59
|
-
return continuation.resume(throwing: error)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if let samples = samples, let deletedSamples = deletedSamples,
|
|
63
|
-
let newAnchor = serializeAnchor(anchor: newAnchor) {
|
|
64
|
-
return continuation.resume(
|
|
65
|
-
returning: AnchoredQueryResponse(
|
|
66
|
-
samples: samples,
|
|
67
|
-
deletedSamples: deletedSamples.map({ deletedSample in
|
|
68
|
-
return serializeDeletedSample(sample: deletedSample)
|
|
69
|
-
}),
|
|
70
|
-
newAnchor: newAnchor
|
|
71
|
-
)
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return continuation.resume(
|
|
76
|
-
throwing: runtimeErrorWithPrefix("Unexpected empty response"))
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
store.execute(query)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
func serializeAnchor(anchor: HKQueryAnchor?) -> String? {
|
|
85
|
-
return toBase64(anchor)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
func toBase64(_ data: Any?) -> String? {
|
|
89
|
-
guard let archivedData = try? NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true) else {
|
|
90
|
-
return nil
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return archivedData.base64EncodedString()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
func sampleQueryAsync(
|
|
97
|
-
sampleType: HKSampleType,
|
|
98
|
-
limit: Double,
|
|
99
|
-
predicate: NSPredicate?,
|
|
100
|
-
sortDescriptors: [NSSortDescriptor]?
|
|
101
|
-
) async throws -> [HKSample] {
|
|
102
|
-
let limit = getQueryLimit(limit)
|
|
103
|
-
return try await withCheckedThrowingContinuation { continuation in
|
|
104
|
-
let q = HKSampleQuery(
|
|
105
|
-
sampleType: sampleType,
|
|
106
|
-
predicate: predicate,
|
|
107
|
-
limit: limit,
|
|
108
|
-
sortDescriptors: sortDescriptors,
|
|
109
|
-
) { (_: HKSampleQuery, samples: [HKSample]?, error: Error?) in
|
|
110
|
-
DispatchQueue.main.async {
|
|
111
|
-
if let error = error {
|
|
112
|
-
return continuation.resume(throwing: error)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if let samples = samples {
|
|
116
|
-
return continuation.resume(returning: samples)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return continuation.resume(
|
|
120
|
-
throwing: runtimeErrorWithPrefix("Unexpected empty response"))
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
store.execute(q)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
func saveAsync(sample: HKObject) async throws -> Bool {
|
|
129
|
-
return try await withCheckedThrowingContinuation { continuation in
|
|
130
|
-
store.save(sample) { (success: Bool, error: Error?) in
|
|
131
|
-
DispatchQueue.main.async {
|
|
132
|
-
if let error = error {
|
|
133
|
-
continuation.resume(throwing: error)
|
|
134
|
-
} else {
|
|
135
|
-
continuation.resume(returning: success)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
func getSortDescriptors(ascending: Bool?) -> [NSSortDescriptor] {
|
|
143
|
-
return [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending ?? false)]
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
func fromBase64(base64String: String?) throws -> Any? {
|
|
147
|
-
if let base64String = base64String {
|
|
148
|
-
if base64String.isEmpty {
|
|
149
|
-
return nil
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Step 1: Decode the base64 string to a Data object
|
|
153
|
-
guard let data = Data(base64Encoded: base64String) else {
|
|
154
|
-
throw runtimeErrorWithPrefix("Invalid base64 string: \(base64String)")
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Step 2: Use NSKeyedUnarchiver to unarchive the data and create an HKQueryAnchor object
|
|
158
|
-
do {
|
|
159
|
-
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
|
|
160
|
-
unarchiver.requiresSecureCoding = true
|
|
161
|
-
return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
|
|
162
|
-
} catch {
|
|
163
|
-
throw runtimeErrorWithPrefix(
|
|
164
|
-
"Error recreating HKQueryAnchor object: \(error.localizedDescription)")
|
|
165
|
-
}
|
|
13
|
+
/// Anchored query results come back with raw HKDeletedObjects; this package
|
|
14
|
+
/// serializes them into its own DeletedSample struct.
|
|
15
|
+
extension AnchoredQueryResponse {
|
|
16
|
+
var deletedSamples: [DeletedSample] {
|
|
17
|
+
return deletedObjects.map { serializeDeletedSample(sample: $0) }
|
|
166
18
|
}
|
|
167
|
-
return nil
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
func deserializeHKQueryAnchor(base64String: String?) throws -> HKQueryAnchor? {
|
|
171
|
-
return try fromBase64(base64String: base64String) as? HKQueryAnchor
|
|
172
19
|
}
|
|
173
20
|
|
|
174
21
|
func initializeCategoryType(_ identifier: String) throws -> HKCategoryType {
|
|
@@ -245,51 +92,7 @@ func sampleTypeFrom(sampleTypeIdentifierWriteable: SampleTypeIdentifierWriteable
|
|
|
245
92
|
}
|
|
246
93
|
|
|
247
94
|
private func sampleTypeFromStringNullable(typeIdentifier: String) throws -> HKSampleType? {
|
|
248
|
-
|
|
249
|
-
return try initializeQuantityType(typeIdentifier)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if typeIdentifier.starts(with: HKCategoryTypeIdentifier_PREFIX) {
|
|
253
|
-
return try initializeCategoryType(typeIdentifier)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if typeIdentifier.starts(with: HKCorrelationTypeIdentifier_PREFIX) {
|
|
257
|
-
return try initializeCorrelationType(typeIdentifier)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if typeIdentifier == HKWorkoutTypeIdentifier {
|
|
261
|
-
return HKSampleType.workoutType()
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if typeIdentifier == HKWorkoutRouteTypeIdentifier {
|
|
265
|
-
return try initializeSeriesType(typeIdentifier)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if typeIdentifier == HKAudiogramTypeIdentifier {
|
|
269
|
-
return HKObjectType.audiogramSampleType()
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if typeIdentifier == HKDataTypeIdentifierHeartbeatSeries {
|
|
273
|
-
return try initializeSeriesType(typeIdentifier)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if typeIdentifier == HKAudiogramTypeIdentifier {
|
|
277
|
-
return HKSampleType.audiogramSampleType()
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if typeIdentifier == HKElectrocardiogramType {
|
|
281
|
-
return HKSampleType.electrocardiogramType()
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
#if compiler(>=6)
|
|
285
|
-
if #available(iOS 18, *) {
|
|
286
|
-
if typeIdentifier == HKStateOfMindTypeIdentifier {
|
|
287
|
-
return HKObjectType.stateOfMindType()
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
#endif
|
|
291
|
-
|
|
292
|
-
return nil
|
|
95
|
+
return sampleType(fromIdentifier: typeIdentifier)
|
|
293
96
|
}
|
|
294
97
|
|
|
295
98
|
func objectTypesFromArray(typeIdentifiers: [ObjectTypeIdentifier]) -> Set<HKObjectType> {
|
|
@@ -305,14 +108,6 @@ func objectTypesFromArray(typeIdentifiers: [ObjectTypeIdentifier]) -> Set<HKObje
|
|
|
305
108
|
return share
|
|
306
109
|
}
|
|
307
110
|
|
|
308
|
-
func initializeUUID(_ uuidString: String) throws -> UUID {
|
|
309
|
-
if let uuid = UUID(uuidString: uuidString) {
|
|
310
|
-
return uuid
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
throw runtimeErrorWithPrefix("Got invalid UUID: \(uuidString)")
|
|
314
|
-
}
|
|
315
|
-
|
|
316
111
|
func sampleTypesFromArray(typeIdentifiers: [SampleTypeIdentifier]) -> Set<HKSampleType> {
|
|
317
112
|
return Set(
|
|
318
113
|
typeIdentifiers.compactMap { typeIdentifier in
|
|
@@ -348,7 +143,7 @@ func objectTypeFrom(objectTypeIdentifier: ObjectTypeIdentifier) throws -> HKObje
|
|
|
348
143
|
return sampleType
|
|
349
144
|
}
|
|
350
145
|
|
|
351
|
-
if typeIdentifier.starts(with:
|
|
146
|
+
if typeIdentifier.starts(with: characteristicTypeIdentifierPrefix) {
|
|
352
147
|
let identifier = HKCharacteristicTypeIdentifier.init(rawValue: typeIdentifier)
|
|
353
148
|
if let type = HKObjectType.characteristicType(forIdentifier: identifier) as HKObjectType? {
|
|
354
149
|
return type
|
|
@@ -434,11 +229,11 @@ func anyMapToDictionary(_ anyMap: AnyMap) -> [String: Any] {
|
|
|
434
229
|
}
|
|
435
230
|
|
|
436
231
|
func runtimeErrorWithPrefix(_ withMessage: String) -> Error {
|
|
437
|
-
return
|
|
232
|
+
return makeRuntimeError(withMessage, prefix: logPrefix)
|
|
438
233
|
}
|
|
439
234
|
|
|
440
235
|
func warnWithPrefix(_ withMessage: String) {
|
|
441
|
-
|
|
236
|
+
logWarning(withMessage, prefix: logPrefix)
|
|
442
237
|
}
|
|
443
238
|
|
|
444
239
|
func buildStatisticsOptions(statistics: [StatisticsOptions], quantityType: HKQuantityType) -> HKStatisticsOptions {
|
|
@@ -483,31 +278,3 @@ func buildStatisticsOptions(statistics: [StatisticsOptions], quantityType: HKQua
|
|
|
483
278
|
}
|
|
484
279
|
return opts
|
|
485
280
|
}
|
|
486
|
-
|
|
487
|
-
private let storeReopenRetryDelay: UInt64 = 1_000_000_000
|
|
488
|
-
|
|
489
|
-
/// HealthKit seals its store the moment the device locks and re-opens it a beat
|
|
490
|
-
/// after it unlocks, and iOS can resume the app inside that beat. A query that
|
|
491
|
-
/// hits the gap fails with `errorDatabaseInaccessible` although protected data is
|
|
492
|
-
/// reported available — the store is re-opening, not sealed. Runs `operation`
|
|
493
|
-
/// and, on exactly that refusal, gives it one more attempt after a short delay.
|
|
494
|
-
/// A refusal while protected data is unavailable is the sealed store itself and
|
|
495
|
-
/// is thrown as-is, as is every other error.
|
|
496
|
-
func retryingWhileStoreReopens<T>(
|
|
497
|
-
_ operation: () async throws -> T
|
|
498
|
-
) async throws -> T {
|
|
499
|
-
do {
|
|
500
|
-
return try await operation()
|
|
501
|
-
} catch {
|
|
502
|
-
let nsError = error as NSError
|
|
503
|
-
guard nsError.domain == HKError.errorDomain,
|
|
504
|
-
nsError.code == HKError.Code.errorDatabaseInaccessible.rawValue
|
|
505
|
-
else { throw error }
|
|
506
|
-
let protectedDataAvailable = await MainActor.run {
|
|
507
|
-
UIApplication.shared.isProtectedDataAvailable
|
|
508
|
-
}
|
|
509
|
-
guard protectedDataAvailable else { throw error }
|
|
510
|
-
try await Task.sleep(nanoseconds: storeReopenRetryDelay)
|
|
511
|
-
return try await operation()
|
|
512
|
-
}
|
|
513
|
-
}
|
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
import HealthKit
|
|
9
9
|
import NitroModules
|
|
10
|
+
import ReactNativeHealthkitCore
|
|
10
11
|
|
|
11
12
|
#if compiler(>=6.2)
|
|
12
13
|
@available(iOS 26.0, *)
|
|
13
14
|
func deserializeHKMedicationConceptIdentifier(base64String: String?) throws
|
|
14
15
|
-> HKHealthConceptIdentifier? {
|
|
15
|
-
return try fromBase64(base64String: base64String
|
|
16
|
+
return try fromBase64(base64String: base64String, as: HKHealthConceptIdentifier.self)
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
@available(iOS 26.0, *)
|
|
@@ -50,7 +51,7 @@ import NitroModules
|
|
|
50
51
|
var lookup: [String: (name: String, nickname: String?)] = [:]
|
|
51
52
|
do {
|
|
52
53
|
let q = HKUserAnnotatedMedicationQueryDescriptor()
|
|
53
|
-
let medications = try await q.result(for:
|
|
54
|
+
let medications = try await q.result(for: healthStore)
|
|
54
55
|
for med in medications {
|
|
55
56
|
let medPredicate = HKQuery.predicateForMedicationDoseEvent(
|
|
56
57
|
medicationConceptIdentifier: med.medication.identifier)
|
|
@@ -97,16 +98,16 @@ import NitroModules
|
|
|
97
98
|
startDate: sample.startDate,
|
|
98
99
|
endDate: sample.endDate,
|
|
99
100
|
hasUndeterminedDuration: sample.hasUndeterminedDuration,
|
|
101
|
+
uuid: sample.uuid.uuidString,
|
|
102
|
+
sourceRevision: serializeSourceRevision(sample.sourceRevision),
|
|
103
|
+
device: serializeDevice(hkDevice: sample.device),
|
|
100
104
|
metadata: {
|
|
101
105
|
var meta = serializeMetadata(sample.metadata)
|
|
102
106
|
if let nick = info?.nickname {
|
|
103
107
|
meta.setString(key: "HKMedicationNickname", value: nick)
|
|
104
108
|
}
|
|
105
109
|
return meta
|
|
106
|
-
}()
|
|
107
|
-
uuid: sample.uuid.uuidString,
|
|
108
|
-
sourceRevision: serializeSourceRevision(sample.sourceRevision),
|
|
109
|
-
device: serializeDevice(hkDevice: sample.device)
|
|
110
|
+
}()
|
|
110
111
|
)
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -152,7 +153,7 @@ import NitroModules
|
|
|
152
153
|
if #available(iOS 26.0, *) {
|
|
153
154
|
let q = HKUserAnnotatedMedicationQueryDescriptor()
|
|
154
155
|
|
|
155
|
-
let medications = try await q.result(for:
|
|
156
|
+
let medications = try await q.result(for: healthStore)
|
|
156
157
|
|
|
157
158
|
return try medications.compactMap({ medication in
|
|
158
159
|
return try serializeMedication(medication: medication)
|
|
@@ -193,7 +194,7 @@ import NitroModules
|
|
|
193
194
|
func requestMedicationsAuthorization() -> Promise<Bool> {
|
|
194
195
|
return Promise.async {
|
|
195
196
|
if #available(iOS 26.0, *) {
|
|
196
|
-
try await
|
|
197
|
+
try await healthStore.requestPerObjectReadAuthorization(
|
|
197
198
|
for: .userAnnotatedMedicationType(), predicate: nil)
|
|
198
199
|
return true
|
|
199
200
|
} else {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
import HealthKit
|
|
9
|
+
import ReactNativeHealthkitCore
|
|
9
10
|
|
|
10
11
|
func createSourcePredicate(_ sources: [HybridSourceProxySpec]?) -> NSPredicate? {
|
|
11
12
|
if let sources = sources {
|
|
@@ -147,103 +148,32 @@ func createPredicateForWorkout(_ filter: FilterForWorkouts?) -> NSPredicate? {
|
|
|
147
148
|
return nil
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let strictEndDate = dateFilter.strictEndDate ?? false
|
|
154
|
-
|
|
155
|
-
let options: HKQueryOptions =
|
|
156
|
-
strictStartDate && strictEndDate
|
|
157
|
-
? [.strictStartDate, .strictEndDate]
|
|
158
|
-
: strictEndDate
|
|
159
|
-
? .strictEndDate
|
|
160
|
-
: strictStartDate
|
|
161
|
-
? .strictStartDate
|
|
162
|
-
: []
|
|
163
|
-
|
|
164
|
-
return HKQuery.predicateForSamples(
|
|
165
|
-
withStart: dateFilter.startDate,
|
|
166
|
-
end: dateFilter.endDate,
|
|
167
|
-
options: options
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
return nil
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
func createUUIDsPredicate(uuids: [String]?) -> NSPredicate? {
|
|
174
|
-
if let uuids = uuids {
|
|
175
|
-
let uuids = uuids.compactMap { uuidStr -> UUID? in
|
|
176
|
-
do {
|
|
177
|
-
let uuid = try initializeUUID(uuidStr)
|
|
178
|
-
return uuid
|
|
179
|
-
} catch {
|
|
180
|
-
warnWithPrefix(error.localizedDescription)
|
|
181
|
-
return nil
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return HKQuery.predicateForObjects(with: Set(uuids))
|
|
185
|
-
}
|
|
186
|
-
return nil
|
|
187
|
-
}
|
|
151
|
+
// The generated filter structs adopt the core protocols so the shared
|
|
152
|
+
// predicate builders in ReactNativeHealthkitCore can read them.
|
|
153
|
+
extension DateFilter: DateFilterConvertible {}
|
|
188
154
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
let uuid = try initializeUUID(uuidStr)
|
|
193
|
-
return HKQuery.predicateForObject(with: uuid)
|
|
194
|
-
} catch {
|
|
195
|
-
warnWithPrefix("createUUIDPredicate: \(error.localizedDescription)")
|
|
155
|
+
extension PredicateWithMetadataKey: MetadataPredicateConvertible {
|
|
156
|
+
public var operatorRawValue: Int? {
|
|
157
|
+
guard let operatorType = operatorType else {
|
|
196
158
|
return nil
|
|
197
159
|
}
|
|
160
|
+
return Int(operatorType.rawValue)
|
|
198
161
|
}
|
|
199
|
-
return nil
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
func getComparisonPredicateOperator(_ op: ComparisonPredicateOperator?) -> NSComparisonPredicate
|
|
203
|
-
.Operator? {
|
|
204
|
-
if let rawValue = op?.rawValue {
|
|
205
|
-
if let op = NSComparisonPredicate.Operator.init(rawValue: UInt(rawValue)) {
|
|
206
|
-
return op
|
|
207
|
-
} else {
|
|
208
|
-
warnWithPrefix(
|
|
209
|
-
"getComparisonPredicateOperator: Unsupported operator in metadata filter: \(rawValue)")
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return nil
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
func createMetadataPredicate(_ metadata: PredicateWithMetadataKey?) -> NSPredicate? {
|
|
216
|
-
if let metadata = metadata {
|
|
217
|
-
|
|
218
|
-
guard let valueVariant = metadata.value else {
|
|
219
|
-
return HKQuery.predicateForObjects(withMetadataKey: metadata.withMetadataKey)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
let actualValue: Any
|
|
223
162
|
|
|
224
|
-
|
|
163
|
+
public var metadataValue: Any? {
|
|
164
|
+
switch value {
|
|
225
165
|
case .first(let boolValue):
|
|
226
|
-
|
|
166
|
+
return NSNumber(value: boolValue ? 1 : 0)
|
|
227
167
|
case .second(let stringValue):
|
|
228
|
-
|
|
168
|
+
return stringValue
|
|
229
169
|
case .third(let doubleValue):
|
|
230
|
-
|
|
170
|
+
return NSNumber(value: doubleValue)
|
|
231
171
|
case .fourth(let dateValue):
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if let operatorType = metadata.operatorType != nil
|
|
236
|
-
? getComparisonPredicateOperator(metadata.operatorType)
|
|
237
|
-
: .equalTo {
|
|
238
|
-
return HKQuery.predicateForObjects(
|
|
239
|
-
withMetadataKey: metadata.withMetadataKey,
|
|
240
|
-
operatorType: operatorType,
|
|
241
|
-
value: actualValue
|
|
242
|
-
)
|
|
172
|
+
return dateValue
|
|
173
|
+
case nil:
|
|
174
|
+
return nil
|
|
243
175
|
}
|
|
244
|
-
|
|
245
176
|
}
|
|
246
|
-
return nil
|
|
247
177
|
}
|
|
248
178
|
|
|
249
179
|
func createPredicateForSamplesBase(_ filter: FilterForSamplesBase?) -> NSPredicate? {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import HealthKit
|
|
2
2
|
import NitroModules
|
|
3
|
+
import ReactNativeHealthkitCore
|
|
3
4
|
|
|
4
5
|
func emptyStatisticsResponse(from: Date?, to: Date?) -> QueryStatisticsResponse {
|
|
5
6
|
return QueryStatisticsResponse(
|
|
@@ -47,7 +48,7 @@ func queryStatisticsForQuantityInternal(
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
healthStore.execute(query)
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -178,7 +179,7 @@ func queryStatisticsCollectionForQuantityInternal(
|
|
|
178
179
|
}
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
|
|
182
|
+
healthStore.execute(query)
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
|
package/ios/Serializers.swift
CHANGED
|
@@ -8,12 +8,7 @@
|
|
|
8
8
|
import Foundation
|
|
9
9
|
import HealthKit
|
|
10
10
|
import NitroModules
|
|
11
|
-
|
|
12
|
-
private let metadataDateFormatter: ISO8601DateFormatter = {
|
|
13
|
-
let formatter = ISO8601DateFormatter()
|
|
14
|
-
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
15
|
-
return formatter
|
|
16
|
-
}()
|
|
11
|
+
import ReactNativeHealthkitCore
|
|
17
12
|
|
|
18
13
|
func serializeQuantityTyped(unit: HKUnit, quantityNullable: HKQuantity?) -> Quantity? {
|
|
19
14
|
guard let q = quantityNullable else {
|
|
@@ -43,10 +38,10 @@ func serializeQuantitySample(sample: HKQuantitySample, unit: HKUnit) throws -> Q
|
|
|
43
38
|
startDate: sample.startDate,
|
|
44
39
|
endDate: sample.endDate,
|
|
45
40
|
hasUndeterminedDuration: sample.hasUndeterminedDuration,
|
|
46
|
-
metadata: serializeMetadata(sample.metadata),
|
|
47
41
|
uuid: sample.uuid.uuidString,
|
|
48
42
|
sourceRevision: serializeSourceRevision(sample.sourceRevision),
|
|
49
|
-
device: serializeDevice(hkDevice: sample.device)
|
|
43
|
+
device: serializeDevice(hkDevice: sample.device),
|
|
44
|
+
metadata: serializeMetadata(sample.metadata)
|
|
50
45
|
)
|
|
51
46
|
}
|
|
52
47
|
throw runtimeErrorWithPrefix(
|
|
@@ -70,10 +65,10 @@ func serializeCategorySample(sample: HKCategorySample) -> CategorySample {
|
|
|
70
65
|
startDate: sample.startDate,
|
|
71
66
|
endDate: sample.endDate,
|
|
72
67
|
hasUndeterminedDuration: sample.hasUndeterminedDuration,
|
|
73
|
-
metadata: serializeMetadata(sample.metadata),
|
|
74
68
|
uuid: sample.uuid.uuidString,
|
|
75
69
|
sourceRevision: serializeSourceRevision(sample.sourceRevision),
|
|
76
|
-
device: serializeDevice(hkDevice: sample.device)
|
|
70
|
+
device: serializeDevice(hkDevice: sample.device),
|
|
71
|
+
metadata: serializeMetadata(sample.metadata)
|
|
77
72
|
)
|
|
78
73
|
}
|
|
79
74
|
|
|
@@ -223,59 +218,19 @@ func serializeUnknownQuantity(quantity: HKQuantity) -> [String: AnyValue]? {
|
|
|
223
218
|
return nil
|
|
224
219
|
}
|
|
225
220
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
serialized.setBoolean(key: item.key, value: number.boolValue)
|
|
233
|
-
} else if isKnownNumericMetadataKey(item.key) {
|
|
234
|
-
serialized.setDouble(key: item.key, value: number.doubleValue)
|
|
235
|
-
} else if CFGetTypeID(number) == CFBooleanGetTypeID() {
|
|
236
|
-
serialized.setBoolean(key: item.key, value: number.boolValue)
|
|
237
|
-
} else {
|
|
238
|
-
serialized.setDouble(key: item.key, value: number.doubleValue)
|
|
239
|
-
}
|
|
240
|
-
continue
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if let bool = item.value as? Bool {
|
|
244
|
-
serialized.setBoolean(key: item.key, value: bool)
|
|
245
|
-
continue
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if let str = item.value as? String {
|
|
249
|
-
serialized.setString(key: item.key, value: str)
|
|
250
|
-
continue
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if let double = item.value as? Double {
|
|
254
|
-
serialized.setDouble(key: item.key, value: double)
|
|
255
|
-
continue
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if let date = item.value as? Date {
|
|
259
|
-
serialized.setString(
|
|
260
|
-
key: item.key,
|
|
261
|
-
value: metadataDateFormatter.string(from: date)
|
|
262
|
-
)
|
|
263
|
-
continue
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if let quantity = item.value as? HKQuantity {
|
|
267
|
-
if let s = serializeUnknownQuantity(quantity: quantity) {
|
|
268
|
-
serialized.setObject(key: item.key, value: s)
|
|
269
|
-
}
|
|
270
|
-
continue
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if let dict = item.value as? [String: AnyValue] {
|
|
274
|
-
serialized.setObject(key: item.key, value: dict)
|
|
275
|
-
}
|
|
221
|
+
private let metadataSerializationOptions = MetadataSerializationOptions(
|
|
222
|
+
isBooleanKey: isKnownBooleanMetadataKey,
|
|
223
|
+
isNumericKey: isKnownNumericMetadataKey,
|
|
224
|
+
serializeQuantity: { quantity in
|
|
225
|
+
if let serialized = serializeUnknownQuantity(quantity: quantity) {
|
|
226
|
+
return .object(serialized)
|
|
276
227
|
}
|
|
228
|
+
return nil
|
|
277
229
|
}
|
|
278
|
-
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
func serializeMetadata(_ metadata: [String: Any]?) -> AnyMap {
|
|
233
|
+
return ReactNativeHealthkitCore.serializeMetadata(metadata, options: metadataSerializationOptions)
|
|
279
234
|
}
|
|
280
235
|
|
|
281
236
|
func serializeDevice(hkDevice: HKDevice?) -> Device? {
|
|
@@ -295,12 +250,6 @@ func serializeDevice(hkDevice: HKDevice?) -> Device? {
|
|
|
295
250
|
)
|
|
296
251
|
}
|
|
297
252
|
|
|
298
|
-
func serializeOperatingSystemVersion(_ version: OperatingSystemVersion) -> String {
|
|
299
|
-
let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
|
|
300
|
-
|
|
301
|
-
return versionString
|
|
302
|
-
}
|
|
303
|
-
|
|
304
253
|
func serializeSourceRevision(_ hkSourceRevision: HKSourceRevision) -> SourceRevision {
|
|
305
254
|
return SourceRevision(
|
|
306
255
|
source: serializeSourceStruct(hkSourceRevision.source),
|