@kingstinct/react-native-healthkit 8.4.0 → 8.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ios/Helpers.swift +39 -35
- package/ios/ReactNativeHealthkit.swift +321 -255
- package/package.json +1 -1
package/ios/Helpers.swift
CHANGED
|
@@ -18,7 +18,8 @@ func limitOrNilIfZero(limit: Int) -> Int {
|
|
|
18
18
|
|
|
19
19
|
func createPredicate(from: Date?, to: Date?) -> NSPredicate? {
|
|
20
20
|
if from != nil || to != nil {
|
|
21
|
-
return HKQuery.predicateForSamples(
|
|
21
|
+
return HKQuery.predicateForSamples(
|
|
22
|
+
withStart: from, end: to, options: [.strictEndDate, .strictStartDate])
|
|
22
23
|
} else {
|
|
23
24
|
return nil
|
|
24
25
|
}
|
|
@@ -29,23 +30,23 @@ func getSortDescriptors(ascending: Bool) -> [NSSortDescriptor] {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
func base64StringToHKQueryAnchor(base64String: String) -> HKQueryAnchor? {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
33
|
+
// Step 1: Decode the base64 string to a Data object
|
|
34
|
+
guard let data = Data(base64Encoded: base64String) else {
|
|
35
|
+
print("Error: Invalid base64 string")
|
|
36
|
+
return nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Step 2: Use NSKeyedUnarchiver to unarchive the data and create an HKQueryAnchor object
|
|
40
|
+
do {
|
|
41
|
+
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
|
|
42
|
+
unarchiver.requiresSecureCoding = true
|
|
43
|
+
let anchor = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
|
|
44
|
+
|
|
45
|
+
return anchor as? HKQueryAnchor
|
|
46
|
+
} catch {
|
|
47
|
+
print("Error: Unable to unarchive HKQueryAnchor object: \(error)")
|
|
48
|
+
return nil
|
|
49
|
+
}
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
func sampleTypeFromString(typeIdentifier: String) -> HKSampleType? {
|
|
@@ -102,7 +103,7 @@ func sampleTypesFromDictionary(typeIdentifiers: NSDictionary) -> Set<HKSampleTyp
|
|
|
102
103
|
if item.value as! Bool {
|
|
103
104
|
let sampleType = sampleTypeFromString(typeIdentifier: item.key as! String)
|
|
104
105
|
if sampleType != nil {
|
|
105
|
-
|
|
106
|
+
share.insert(sampleType!)
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
}
|
|
@@ -134,11 +135,13 @@ func objectTypeFromString(typeIdentifier: String) -> HKObjectType? {
|
|
|
134
135
|
return HKObjectType.activitySummaryType()
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
if
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
#if compiler(>=6)
|
|
139
|
+
if #available(iOS 18, *) {
|
|
140
|
+
if typeIdentifier == HKStateOfMindTypeIdentifier {
|
|
141
|
+
return HKObjectType.stateOfMindType()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
#endif
|
|
142
145
|
|
|
143
146
|
if #available(iOS 13, *) {
|
|
144
147
|
if typeIdentifier == HKAudiogramTypeIdentifier {
|
|
@@ -223,23 +226,24 @@ func serializeQuantityIfExists(unit: HKUnit, quantity: HKQuantity?) -> [String:
|
|
|
223
226
|
return serializeQuantity(unit: unit, quantity: quantity)
|
|
224
227
|
}
|
|
225
228
|
|
|
226
|
-
func serializeStatisticIfExists(unit: HKUnit, quantity: HKQuantity?, stats: HKStatistics)
|
|
229
|
+
func serializeStatisticIfExists(unit: HKUnit, quantity: HKQuantity?, stats: HKStatistics)
|
|
230
|
+
-> [String: Any]? {
|
|
227
231
|
guard let quantity = quantity else { return nil }
|
|
228
232
|
return serializeStatistic(unit: unit, quantity: quantity, stats: stats)
|
|
229
233
|
}
|
|
230
234
|
|
|
231
235
|
func parseWorkoutConfiguration(_ dict: NSDictionary) -> HKWorkoutConfiguration {
|
|
232
|
-
|
|
236
|
+
let configuration = HKWorkoutConfiguration()
|
|
233
237
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
if let activityTypeRaw = dict[HKWorkoutActivityTypePropertyName] as? UInt,
|
|
239
|
+
let activityType = HKWorkoutActivityType(rawValue: activityTypeRaw) {
|
|
240
|
+
configuration.activityType = activityType
|
|
241
|
+
}
|
|
238
242
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
+
if let locationTypeRaw = dict[HKWorkoutSessionLocationTypePropertyName] as? Int,
|
|
244
|
+
let locationType = HKWorkoutSessionLocationType(rawValue: locationTypeRaw) {
|
|
245
|
+
configuration.locationType = locationType
|
|
246
|
+
}
|
|
243
247
|
|
|
244
|
-
|
|
248
|
+
return configuration
|
|
245
249
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import HealthKit
|
|
2
1
|
import CoreLocation
|
|
2
|
+
import HealthKit
|
|
3
3
|
|
|
4
4
|
#if canImport(WorkoutKit)
|
|
5
|
-
import WorkoutKit
|
|
5
|
+
import WorkoutKit
|
|
6
6
|
#endif
|
|
7
7
|
|
|
8
8
|
@objc(ReactNativeHealthkit)
|
|
@@ -87,10 +87,11 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
87
87
|
let share = sampleTypesFromDictionary(typeIdentifiers: toShare)
|
|
88
88
|
let toRead = objectTypesFromDictionary(typeIdentifiers: read)
|
|
89
89
|
|
|
90
|
-
store.getRequestStatusForAuthorization(toShare: share, read: toRead) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
store.getRequestStatusForAuthorization(toShare: share, read: toRead) {
|
|
91
|
+
(
|
|
92
|
+
status: HKAuthorizationRequestStatus,
|
|
93
|
+
error: Error?
|
|
94
|
+
) in
|
|
94
95
|
guard let err = error else {
|
|
95
96
|
return resolve(status.rawValue)
|
|
96
97
|
}
|
|
@@ -116,7 +117,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
store.preferredUnits(for: quantityTypes) {
|
|
120
|
+
store.preferredUnits(for: quantityTypes) {
|
|
121
|
+
(typePerUnits: [HKQuantityType: HKUnit], _: Error?) in
|
|
120
122
|
let dic: NSMutableDictionary = NSMutableDictionary()
|
|
121
123
|
|
|
122
124
|
for typePerUnit in typePerUnits {
|
|
@@ -291,7 +293,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
291
293
|
|
|
292
294
|
let samplePredicate = HKQuery.predicateForObject(with: sampleUuid)
|
|
293
295
|
|
|
294
|
-
store.deleteObjects(of: sampleType, predicate: samplePredicate) {
|
|
296
|
+
store.deleteObjects(of: sampleType, predicate: samplePredicate) {
|
|
297
|
+
(success: Bool, _: Int, error: Error?) in
|
|
295
298
|
guard let err = error else {
|
|
296
299
|
return resolve(success)
|
|
297
300
|
}
|
|
@@ -323,7 +326,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
323
326
|
options: HKQueryOptions.strictStartDate
|
|
324
327
|
)
|
|
325
328
|
|
|
326
|
-
store.deleteObjects(of: sampleType, predicate: samplePredicate) {
|
|
329
|
+
store.deleteObjects(of: sampleType, predicate: samplePredicate) {
|
|
330
|
+
(success: Bool, _: Int, error: Error?) in
|
|
327
331
|
guard let err = error else {
|
|
328
332
|
return resolve(success)
|
|
329
333
|
}
|
|
@@ -421,7 +425,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
421
425
|
}
|
|
422
426
|
|
|
423
427
|
guard let type = HKWorkoutActivityType.init(rawValue: typeIdentifier) else {
|
|
424
|
-
return reject(
|
|
428
|
+
return reject(
|
|
429
|
+
TYPE_IDENTIFIER_ERROR,
|
|
430
|
+
"Failed to initialize HKWorkoutActivityType " + typeIdentifier.description, nil)
|
|
425
431
|
}
|
|
426
432
|
|
|
427
433
|
// if start and end both exist, ensure that start date is before end date
|
|
@@ -572,13 +578,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
572
578
|
var clLocations: [CLLocation] = []
|
|
573
579
|
for location in locations {
|
|
574
580
|
guard let latitude = location["latitude"] as? CLLocationDegrees,
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
581
|
+
let longitude = location["longitude"] as? CLLocationDegrees,
|
|
582
|
+
let altitude = location["altitude"] as? CLLocationDistance,
|
|
583
|
+
let horizontalAccuracy = location["horizontalAccuracy"] as? CLLocationAccuracy,
|
|
584
|
+
let verticalAccuracy = location["verticalAccuracy"] as? CLLocationAccuracy,
|
|
585
|
+
let course = location["course"] as? CLLocationDirection,
|
|
586
|
+
let speed = location["speed"] as? CLLocationSpeed,
|
|
587
|
+
let timestamp = location["timestamp"] as? String
|
|
588
|
+
else {
|
|
582
589
|
continue
|
|
583
590
|
}
|
|
584
591
|
|
|
@@ -698,7 +705,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
698
705
|
return reject("UpdateFrequency not valid", "UpdateFrequency not valid", nil)
|
|
699
706
|
}
|
|
700
707
|
|
|
701
|
-
store.enableBackgroundDelivery(for: sampleType, frequency: frequency
|
|
708
|
+
store.enableBackgroundDelivery(for: sampleType, frequency: frequency) { (success, error) in
|
|
702
709
|
guard let err = error else {
|
|
703
710
|
return resolve(success)
|
|
704
711
|
}
|
|
@@ -767,13 +774,17 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
767
774
|
|
|
768
775
|
let queryId = UUID().uuidString
|
|
769
776
|
|
|
770
|
-
func responder(
|
|
777
|
+
func responder(
|
|
778
|
+
query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?
|
|
779
|
+
) {
|
|
771
780
|
if error == nil {
|
|
772
781
|
DispatchQueue.main.async {
|
|
773
782
|
if self.bridge != nil && self.bridge.isValid {
|
|
774
|
-
self.sendEvent(
|
|
775
|
-
"
|
|
776
|
-
|
|
783
|
+
self.sendEvent(
|
|
784
|
+
withName: "onChange",
|
|
785
|
+
body: [
|
|
786
|
+
"typeIdentifier": typeIdentifier
|
|
787
|
+
])
|
|
777
788
|
}
|
|
778
789
|
|
|
779
790
|
}
|
|
@@ -784,7 +795,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
784
795
|
let query = HKObserverQuery(
|
|
785
796
|
sampleType: sampleType,
|
|
786
797
|
predicate: predicate
|
|
787
|
-
) {
|
|
798
|
+
) {
|
|
799
|
+
(query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?)
|
|
800
|
+
in
|
|
788
801
|
guard let err = error else {
|
|
789
802
|
return responder(query: query, handler: handler, error: error)
|
|
790
803
|
}
|
|
@@ -894,34 +907,38 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
894
907
|
|
|
895
908
|
let unit = HKUnit.init(from: unitString)
|
|
896
909
|
if let averageQuantity = gottenStats.averageQuantity() {
|
|
897
|
-
dic.updateValue(
|
|
910
|
+
dic.updateValue(
|
|
911
|
+
serializeQuantity(unit: unit, quantity: averageQuantity), forKey: "averageQuantity")
|
|
898
912
|
}
|
|
899
913
|
if let maximumQuantity = gottenStats.maximumQuantity() {
|
|
900
|
-
dic.updateValue(
|
|
914
|
+
dic.updateValue(
|
|
915
|
+
serializeQuantity(unit: unit, quantity: maximumQuantity), forKey: "maximumQuantity")
|
|
901
916
|
}
|
|
902
917
|
if let minimumQuantity = gottenStats.minimumQuantity() {
|
|
903
|
-
dic.updateValue(
|
|
918
|
+
dic.updateValue(
|
|
919
|
+
serializeQuantity(unit: unit, quantity: minimumQuantity), forKey: "minimumQuantity")
|
|
904
920
|
}
|
|
905
921
|
if let sumQuantity = gottenStats.sumQuantity() {
|
|
906
922
|
dic.updateValue(serializeQuantity(unit: unit, quantity: sumQuantity), forKey: "sumQuantity")
|
|
907
923
|
}
|
|
908
924
|
if #available(iOS 12, *) {
|
|
909
925
|
if let mostRecent = gottenStats.mostRecentQuantity() {
|
|
910
|
-
dic.updateValue(
|
|
926
|
+
dic.updateValue(
|
|
927
|
+
serializeQuantity(unit: unit, quantity: mostRecent), forKey: "mostRecentQuantity")
|
|
911
928
|
}
|
|
912
929
|
|
|
913
930
|
if let mostRecentDateInterval = gottenStats.mostRecentQuantityDateInterval() {
|
|
914
|
-
dic.updateValue(
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
931
|
+
dic.updateValue(
|
|
932
|
+
[
|
|
933
|
+
"start": self._dateFormatter.string(from: mostRecentDateInterval.start),
|
|
934
|
+
"end": self._dateFormatter.string(from: mostRecentDateInterval.end)
|
|
935
|
+
], forKey: "mostRecentQuantityDateInterval")
|
|
918
936
|
}
|
|
919
937
|
}
|
|
920
938
|
if #available(iOS 13, *) {
|
|
921
939
|
let durationUnit = HKUnit.second()
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
940
|
+
dic["duration"] = serializeQuantityIfExists(
|
|
941
|
+
unit: durationUnit, quantity: gottenStats.duration())
|
|
925
942
|
}
|
|
926
943
|
|
|
927
944
|
resolve(dic)
|
|
@@ -930,83 +947,97 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
930
947
|
store.execute(query)
|
|
931
948
|
}
|
|
932
949
|
|
|
933
|
-
@objc(
|
|
950
|
+
@objc(
|
|
951
|
+
queryStatisticsCollectionForQuantity:unitString:options:anchorDate:interval:startDate:endDate:
|
|
952
|
+
resolve:reject:
|
|
953
|
+
)
|
|
934
954
|
func queryStatisticsCollectionForQuantity(
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
955
|
+
typeIdentifier: String,
|
|
956
|
+
unitString: String,
|
|
957
|
+
options: NSArray,
|
|
958
|
+
anchorDate: Date,
|
|
959
|
+
interval: NSDictionary,
|
|
960
|
+
startDate: Date,
|
|
961
|
+
endDate: Date,
|
|
962
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
963
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
944
964
|
) {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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
|
-
}
|
|
952
|
-
|
|
953
|
-
let opts = hkStatisticsOptionsFromOptions(options)
|
|
954
|
-
let intervalComponents = componentsFromInterval(interval)
|
|
965
|
+
guard let store = _store else {
|
|
966
|
+
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
|
|
967
|
+
}
|
|
955
968
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
969
|
+
guard
|
|
970
|
+
let quantityType = HKObjectType.quantityType(
|
|
971
|
+
forIdentifier: HKQuantityTypeIdentifier(rawValue: typeIdentifier))
|
|
972
|
+
else {
|
|
973
|
+
return reject(
|
|
974
|
+
TYPE_IDENTIFIER_ERROR,
|
|
975
|
+
"Failed to initialize quantity type for identifier: \(typeIdentifier)", nil)
|
|
976
|
+
}
|
|
963
977
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
return reject(QUERY_ERROR, error.localizedDescription, error)
|
|
967
|
-
}
|
|
978
|
+
let opts = hkStatisticsOptionsFromOptions(options)
|
|
979
|
+
let intervalComponents = componentsFromInterval(interval)
|
|
968
980
|
|
|
969
|
-
|
|
981
|
+
let query = HKStatisticsCollectionQuery(
|
|
982
|
+
quantityType: quantityType,
|
|
983
|
+
quantitySamplePredicate: nil, // We will use enumerateStatistics to filter with date
|
|
984
|
+
options: opts,
|
|
985
|
+
anchorDate: anchorDate,
|
|
986
|
+
intervalComponents: intervalComponents
|
|
987
|
+
)
|
|
970
988
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
989
|
+
query.initialResultsHandler = { _, statsCollection, error in
|
|
990
|
+
if let error = error {
|
|
991
|
+
return reject(QUERY_ERROR, error.localizedDescription, error)
|
|
992
|
+
}
|
|
974
993
|
|
|
975
|
-
|
|
994
|
+
var results = [[String: [String: Any]?]]()
|
|
976
995
|
|
|
977
|
-
|
|
978
|
-
|
|
996
|
+
guard let collection = statsCollection else {
|
|
997
|
+
return resolve(results)
|
|
998
|
+
}
|
|
979
999
|
|
|
980
|
-
|
|
981
|
-
let endDate = self._dateFormatter.string(from: stats.endDate)
|
|
1000
|
+
let unit = HKUnit(from: unitString)
|
|
982
1001
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
dic["minimumQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.minimumQuantity())
|
|
986
|
-
dic["sumQuantity"] = serializeStatisticIfExists(unit: unit, quantity: stats.sumQuantity(), stats: stats)
|
|
1002
|
+
collection.enumerateStatistics(from: startDate, to: endDate) { stats, _ in
|
|
1003
|
+
var dic = [String: [String: Any]?]()
|
|
987
1004
|
|
|
988
|
-
|
|
989
|
-
|
|
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
|
-
}
|
|
1005
|
+
let startDate = self._dateFormatter.string(from: stats.startDate)
|
|
1006
|
+
let endDate = self._dateFormatter.string(from: stats.endDate)
|
|
997
1007
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1008
|
+
dic["averageQuantity"] = serializeQuantityIfExists(
|
|
1009
|
+
unit: unit, quantity: stats.averageQuantity())
|
|
1010
|
+
dic["maximumQuantity"] = serializeQuantityIfExists(
|
|
1011
|
+
unit: unit, quantity: stats.maximumQuantity())
|
|
1012
|
+
dic["minimumQuantity"] = serializeQuantityIfExists(
|
|
1013
|
+
unit: unit, quantity: stats.minimumQuantity())
|
|
1014
|
+
dic["sumQuantity"] = serializeStatisticIfExists(
|
|
1015
|
+
unit: unit, quantity: stats.sumQuantity(), stats: stats)
|
|
1002
1016
|
|
|
1003
|
-
|
|
1017
|
+
if #available(iOS 12, *) {
|
|
1018
|
+
dic["mostRecentQuantity"] = serializeQuantityIfExists(
|
|
1019
|
+
unit: unit, quantity: stats.mostRecentQuantity())
|
|
1020
|
+
if let mostRecentDateInterval = stats.mostRecentQuantityDateInterval() {
|
|
1021
|
+
dic["mostRecentQuantityDateInterval"] = [
|
|
1022
|
+
"start": self._dateFormatter.string(from: mostRecentDateInterval.start),
|
|
1023
|
+
"end": self._dateFormatter.string(from: mostRecentDateInterval.end)
|
|
1024
|
+
]
|
|
1004
1025
|
}
|
|
1026
|
+
}
|
|
1005
1027
|
|
|
1006
|
-
|
|
1028
|
+
if #available(iOS 13, *) {
|
|
1029
|
+
let durationUnit = HKUnit.second()
|
|
1030
|
+
dic["duration"] = serializeQuantityIfExists(
|
|
1031
|
+
unit: durationUnit, quantity: stats.duration())
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
results.append(dic)
|
|
1007
1035
|
}
|
|
1008
1036
|
|
|
1009
|
-
|
|
1037
|
+
resolve(results)
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
store.execute(query)
|
|
1010
1041
|
}
|
|
1011
1042
|
|
|
1012
1043
|
func mapWorkout(
|
|
@@ -1021,9 +1052,12 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1021
1052
|
"uuid": workout.uuid.uuidString,
|
|
1022
1053
|
"device": serializeDevice(_device: workout.device) as Any,
|
|
1023
1054
|
"duration": workout.duration,
|
|
1024
|
-
"totalDistance": serializeQuantity(unit: distanceUnit, quantity: workout.totalDistance)
|
|
1025
|
-
|
|
1026
|
-
"
|
|
1055
|
+
"totalDistance": serializeQuantity(unit: distanceUnit, quantity: workout.totalDistance)
|
|
1056
|
+
as Any,
|
|
1057
|
+
"totalEnergyBurned": serializeQuantity(unit: energyUnit, quantity: workout.totalEnergyBurned)
|
|
1058
|
+
as Any,
|
|
1059
|
+
"totalSwimmingStrokeCount": serializeQuantity(
|
|
1060
|
+
unit: HKUnit.count(), quantity: workout.totalSwimmingStrokeCount) as Any,
|
|
1027
1061
|
"workoutActivityType": workout.workoutActivityType.rawValue,
|
|
1028
1062
|
"startDate": startDate,
|
|
1029
1063
|
"endDate": endDate,
|
|
@@ -1039,7 +1073,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1039
1073
|
let eventStartDate = self._dateFormatter.string(from: event.dateInterval.start)
|
|
1040
1074
|
let eventEndDate = self._dateFormatter.string(from: event.dateInterval.end)
|
|
1041
1075
|
let eventDict: [String: Any] = [
|
|
1042
|
-
"type": event.type.rawValue,
|
|
1076
|
+
"type": event.type.rawValue, // https://developer.apple.com/documentation/healthkit/hkworkouteventtype
|
|
1043
1077
|
"startDate": eventStartDate,
|
|
1044
1078
|
"endDate": eventEndDate
|
|
1045
1079
|
]
|
|
@@ -1078,7 +1112,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1078
1112
|
dict["activities"] = activitiesArray
|
|
1079
1113
|
|
|
1080
1114
|
if #available(iOS 11, *) {
|
|
1081
|
-
dict.setValue(
|
|
1115
|
+
dict.setValue(
|
|
1116
|
+
serializeQuantity(unit: HKUnit.count(), quantity: workout.totalFlightsClimbed),
|
|
1117
|
+
forKey: "totalFlightsClimbed")
|
|
1082
1118
|
}
|
|
1083
1119
|
return dict
|
|
1084
1120
|
}
|
|
@@ -1167,13 +1203,16 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1167
1203
|
|
|
1168
1204
|
let actualAnchor = deserializeHKQueryAnchor(anchor: anchor)
|
|
1169
1205
|
|
|
1170
|
-
let q = HKAnchoredObjectQuery(
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1206
|
+
let q = HKAnchoredObjectQuery(
|
|
1207
|
+
type: .workoutType(), predicate: predicate, anchor: actualAnchor, limit: limit
|
|
1208
|
+
) {
|
|
1209
|
+
(
|
|
1210
|
+
_: HKAnchoredObjectQuery,
|
|
1211
|
+
s: [HKSample]?,
|
|
1212
|
+
deletedSamples: [HKDeletedObject]?,
|
|
1213
|
+
newAnchor: HKQueryAnchor?,
|
|
1214
|
+
error: Error?
|
|
1215
|
+
) in
|
|
1177
1216
|
guard let err = error else {
|
|
1178
1217
|
guard let samples = s else {
|
|
1179
1218
|
return resolve([])
|
|
@@ -1246,7 +1285,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1246
1285
|
|
|
1247
1286
|
for s in samples {
|
|
1248
1287
|
if let sample = s as? HKQuantitySample {
|
|
1249
|
-
let serialized = serializeQuantitySample(
|
|
1288
|
+
let serialized = serializeQuantitySample(
|
|
1289
|
+
sample: sample, unit: HKUnit.init(from: unitString))
|
|
1250
1290
|
|
|
1251
1291
|
arr.add(serialized)
|
|
1252
1292
|
}
|
|
@@ -1365,7 +1405,10 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1365
1405
|
let predicate = createPredicate(from: from, to: to)
|
|
1366
1406
|
let limit = limitOrNilIfZero(limit: limit)
|
|
1367
1407
|
|
|
1368
|
-
let q = HKSampleQuery(
|
|
1408
|
+
let q = HKSampleQuery(
|
|
1409
|
+
sampleType: sampleType, predicate: predicate, limit: limit,
|
|
1410
|
+
sortDescriptors: getSortDescriptors(ascending: ascending)
|
|
1411
|
+
) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
|
|
1369
1412
|
guard let err = error else {
|
|
1370
1413
|
guard let samples = sample else {
|
|
1371
1414
|
return resolve([])
|
|
@@ -1420,13 +1463,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1420
1463
|
predicate: predicate,
|
|
1421
1464
|
anchor: actualAnchor,
|
|
1422
1465
|
limit: limit
|
|
1423
|
-
) {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1466
|
+
) {
|
|
1467
|
+
(
|
|
1468
|
+
_: HKAnchoredObjectQuery,
|
|
1469
|
+
s: [HKSample]?,
|
|
1470
|
+
deletedSamples: [HKDeletedObject]?,
|
|
1471
|
+
newAnchor: HKQueryAnchor?,
|
|
1472
|
+
error: Error?
|
|
1473
|
+
) in
|
|
1430
1474
|
guard let err = error else {
|
|
1431
1475
|
guard let samples = s else {
|
|
1432
1476
|
return resolve([])
|
|
@@ -1482,13 +1526,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1482
1526
|
predicate: predicate,
|
|
1483
1527
|
anchor: anchor != "" ? base64StringToHKQueryAnchor(base64String: anchor) : nil,
|
|
1484
1528
|
limit: limit
|
|
1485
|
-
) {
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1529
|
+
) {
|
|
1530
|
+
(
|
|
1531
|
+
_: HKAnchoredObjectQuery,
|
|
1532
|
+
s: [HKSample]?,
|
|
1533
|
+
deletedSamples: [HKDeletedObject]?,
|
|
1534
|
+
newAnchor: HKQueryAnchor?,
|
|
1535
|
+
error: Error?
|
|
1536
|
+
) in
|
|
1492
1537
|
guard let err = error else {
|
|
1493
1538
|
guard let samples = s else {
|
|
1494
1539
|
return resolve([])
|
|
@@ -1536,11 +1581,12 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1536
1581
|
let query = HKSourceQuery(
|
|
1537
1582
|
sampleType: type as! HKSampleType,
|
|
1538
1583
|
samplePredicate: nil
|
|
1539
|
-
) {
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1584
|
+
) {
|
|
1585
|
+
(
|
|
1586
|
+
_: HKSourceQuery,
|
|
1587
|
+
source: Set<HKSource>?,
|
|
1588
|
+
error: Error?
|
|
1589
|
+
) in
|
|
1544
1590
|
guard let err = error else {
|
|
1545
1591
|
guard let sources = source else {
|
|
1546
1592
|
return resolve([])
|
|
@@ -1585,8 +1631,10 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1585
1631
|
}
|
|
1586
1632
|
|
|
1587
1633
|
@available(iOS 13.0.0, *)
|
|
1588
|
-
func getWorkoutByID(
|
|
1589
|
-
|
|
1634
|
+
func getWorkoutByID(
|
|
1635
|
+
store: HKHealthStore,
|
|
1636
|
+
workoutUUID: UUID
|
|
1637
|
+
) async -> HKWorkout? {
|
|
1590
1638
|
let workoutPredicate = HKQuery.predicateForObject(with: workoutUUID)
|
|
1591
1639
|
|
|
1592
1640
|
let samples = try! await withCheckedThrowingContinuation {
|
|
@@ -1698,28 +1746,30 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1698
1746
|
func getSerializedWorkoutLocations(
|
|
1699
1747
|
store: HKHealthStore,
|
|
1700
1748
|
workoutUUID: UUID
|
|
1701
|
-
) async -> [
|
|
1749
|
+
) async -> [[String: Any]]? {
|
|
1702
1750
|
let routes = await _getWorkoutRoutes(
|
|
1703
1751
|
store: store,
|
|
1704
1752
|
workoutUUID: workoutUUID
|
|
1705
1753
|
)
|
|
1706
1754
|
|
|
1707
|
-
var allRoutes: [
|
|
1755
|
+
var allRoutes: [[String: Any]] = []
|
|
1708
1756
|
guard let _routes = routes else {
|
|
1709
1757
|
return nil
|
|
1710
1758
|
}
|
|
1711
1759
|
for route in _routes {
|
|
1712
|
-
let routeMetadata =
|
|
1713
|
-
|
|
1714
|
-
|
|
1760
|
+
let routeMetadata =
|
|
1761
|
+
serializeMetadata(
|
|
1762
|
+
metadata: route.metadata
|
|
1763
|
+
) as! [String: Any]
|
|
1715
1764
|
let routeCLLocations = await getRouteLocations(
|
|
1716
1765
|
store: store,
|
|
1717
1766
|
route: route
|
|
1718
1767
|
)
|
|
1719
1768
|
let routeLocations = routeCLLocations.enumerated().map {
|
|
1720
|
-
(i, loc) in
|
|
1769
|
+
(i, loc) in
|
|
1770
|
+
serializeLocation(
|
|
1721
1771
|
location: loc,
|
|
1722
|
-
previousLocation: i == 0 ? nil: routeCLLocations[i - 1]
|
|
1772
|
+
previousLocation: i == 0 ? nil : routeCLLocations[i - 1]
|
|
1723
1773
|
)
|
|
1724
1774
|
}
|
|
1725
1775
|
let routeInfos: [String: Any] = ["locations": routeLocations]
|
|
@@ -1731,59 +1781,60 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1731
1781
|
|
|
1732
1782
|
@available(iOS 17.0.0, *)
|
|
1733
1783
|
func getWorkoutPlan(workout: HKWorkout) async -> [String: Any]? {
|
|
1734
|
-
#if canImport(WorkoutKit)
|
|
1735
|
-
|
|
1736
|
-
|
|
1784
|
+
#if canImport(WorkoutKit)
|
|
1785
|
+
do {
|
|
1786
|
+
let workoutPlan = try await workout.workoutPlan
|
|
1737
1787
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1788
|
+
var dict = [String: Any]()
|
|
1789
|
+
if (workoutPlan?.id) != nil {
|
|
1790
|
+
dict["id"] = workoutPlan?.id.uuidString
|
|
1741
1791
|
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1792
|
+
}
|
|
1793
|
+
if (workoutPlan?.workout.activity) != nil {
|
|
1794
|
+
dict["activityType"] = workoutPlan?.workout.activity.rawValue
|
|
1795
|
+
}
|
|
1746
1796
|
|
|
1747
|
-
|
|
1797
|
+
if dict.isEmpty {
|
|
1798
|
+
return nil
|
|
1799
|
+
}
|
|
1800
|
+
return dict
|
|
1801
|
+
} catch {
|
|
1748
1802
|
return nil
|
|
1749
1803
|
}
|
|
1750
|
-
|
|
1751
|
-
} catch {
|
|
1804
|
+
#else
|
|
1752
1805
|
return nil
|
|
1753
|
-
|
|
1754
|
-
#else
|
|
1755
|
-
return nil
|
|
1756
|
-
#endif
|
|
1806
|
+
#endif
|
|
1757
1807
|
}
|
|
1758
1808
|
|
|
1759
1809
|
@objc(getWorkoutPlanById:resolve:reject:)
|
|
1760
1810
|
func getWorkoutPlanById(
|
|
1761
1811
|
workoutUUID: String,
|
|
1762
1812
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
1763
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
1813
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
1814
|
+
) {
|
|
1764
1815
|
if #available(iOS 17.0, *) {
|
|
1765
|
-
#if canImport(WorkoutKit)
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1816
|
+
#if canImport(WorkoutKit)
|
|
1817
|
+
guard let store = _store else {
|
|
1818
|
+
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
|
|
1819
|
+
}
|
|
1769
1820
|
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1821
|
+
Task {
|
|
1822
|
+
if let uuid = UUID(uuidString: workoutUUID) {
|
|
1823
|
+
let workout = await self.getWorkoutByID(store: store, workoutUUID: uuid)
|
|
1824
|
+
if let workout {
|
|
1825
|
+
let workoutPlan = await self.getWorkoutPlan(workout: workout)
|
|
1775
1826
|
|
|
1776
|
-
|
|
1827
|
+
return resolve(workoutPlan)
|
|
1828
|
+
} else {
|
|
1829
|
+
return reject(GENERIC_ERROR, "No workout found", nil)
|
|
1830
|
+
}
|
|
1777
1831
|
} else {
|
|
1778
|
-
return reject(GENERIC_ERROR, "
|
|
1832
|
+
return reject(GENERIC_ERROR, "Invalid UUID", nil)
|
|
1779
1833
|
}
|
|
1780
|
-
} else {
|
|
1781
|
-
return reject(GENERIC_ERROR, "Invalid UUID", nil)
|
|
1782
1834
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
#endif
|
|
1835
|
+
#else
|
|
1836
|
+
return resolve(nil)
|
|
1837
|
+
#endif
|
|
1787
1838
|
} else {
|
|
1788
1839
|
return resolve(nil)
|
|
1789
1840
|
}
|
|
@@ -1814,25 +1865,26 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1814
1865
|
func getWorkoutRoutes(
|
|
1815
1866
|
workoutUUID: String,
|
|
1816
1867
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
1817
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
1868
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
1869
|
+
) {
|
|
1818
1870
|
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1871
|
+
guard let store = _store else {
|
|
1872
|
+
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
|
|
1873
|
+
}
|
|
1822
1874
|
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1875
|
+
guard let _workoutUUID = UUID(uuidString: workoutUUID) else {
|
|
1876
|
+
return reject("INVALID_UUID_ERROR", "Invalid UUID received", nil)
|
|
1877
|
+
}
|
|
1826
1878
|
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
}
|
|
1879
|
+
Task {
|
|
1880
|
+
do {
|
|
1881
|
+
let locations = await getSerializedWorkoutLocations(store: store, workoutUUID: _workoutUUID)
|
|
1882
|
+
resolve(locations)
|
|
1883
|
+
} catch {
|
|
1884
|
+
reject("WORKOUT_LOCATION_ERROR", "Failed to retrieve workout locations", nil)
|
|
1834
1885
|
}
|
|
1835
1886
|
}
|
|
1887
|
+
}
|
|
1836
1888
|
|
|
1837
1889
|
typealias HKAnchoredObjectQueryResult = (
|
|
1838
1890
|
samples: [HKSample],
|
|
@@ -1854,13 +1906,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1854
1906
|
predicate: predicate,
|
|
1855
1907
|
anchor: anchor,
|
|
1856
1908
|
limit: limit
|
|
1857
|
-
) {
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1909
|
+
) {
|
|
1910
|
+
(
|
|
1911
|
+
_: HKAnchoredObjectQuery,
|
|
1912
|
+
s: [HKSample]?,
|
|
1913
|
+
deletedSamples: [HKDeletedObject]?,
|
|
1914
|
+
newAnchor: HKQueryAnchor?,
|
|
1915
|
+
error: Error?
|
|
1916
|
+
) in
|
|
1864
1917
|
if let err = error {
|
|
1865
1918
|
return continuation.resume(throwing: err)
|
|
1866
1919
|
} else {
|
|
@@ -1887,18 +1940,19 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1887
1940
|
func getHeartbeatSeriesHeartbeats(
|
|
1888
1941
|
store: HKHealthStore,
|
|
1889
1942
|
sample: HKHeartbeatSeriesSample
|
|
1890
|
-
) async throws -> [
|
|
1943
|
+
) async throws -> [[String: Any]] {
|
|
1891
1944
|
let beatTimes = try await withCheckedThrowingContinuation {
|
|
1892
|
-
(continuation: CheckedContinuation<[
|
|
1893
|
-
var allBeats: [
|
|
1894
|
-
|
|
1895
|
-
let query = HKHeartbeatSeriesQuery(heartbeatSeries: sample) {
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1945
|
+
(continuation: CheckedContinuation<[[String: Any]], Error>) in
|
|
1946
|
+
var allBeats: [[String: Any]] = []
|
|
1947
|
+
|
|
1948
|
+
let query = HKHeartbeatSeriesQuery(heartbeatSeries: sample) {
|
|
1949
|
+
(
|
|
1950
|
+
_: HKHeartbeatSeriesQuery,
|
|
1951
|
+
timeSinceSeriesStart: TimeInterval,
|
|
1952
|
+
precededByGap: Bool,
|
|
1953
|
+
done: Bool,
|
|
1954
|
+
error: Error?
|
|
1955
|
+
) in
|
|
1902
1956
|
if let err = error {
|
|
1903
1957
|
return continuation.resume(throwing: err)
|
|
1904
1958
|
} else {
|
|
@@ -1975,7 +2029,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
1975
2029
|
anchor: actualAnchor
|
|
1976
2030
|
)
|
|
1977
2031
|
|
|
1978
|
-
var allHeartbeatSamples: [
|
|
2032
|
+
var allHeartbeatSamples: [[String: Any]] = []
|
|
1979
2033
|
for sample in queryResult.samples as! [HKHeartbeatSeriesSample] {
|
|
1980
2034
|
allHeartbeatSamples.append(
|
|
1981
2035
|
try await getSerializedHeartbeatSeriesSample(
|
|
@@ -2012,7 +2066,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
2012
2066
|
sampleType: HKSeriesType.heartbeat(),
|
|
2013
2067
|
predicate: predicate,
|
|
2014
2068
|
limit: limit,
|
|
2015
|
-
sortDescriptors: [
|
|
2069
|
+
sortDescriptors: [
|
|
2070
|
+
NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)
|
|
2071
|
+
]
|
|
2016
2072
|
) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
|
|
2017
2073
|
if let err = error {
|
|
2018
2074
|
continuation.resume(throwing: err)
|
|
@@ -2065,7 +2121,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
2065
2121
|
ascending: ascending
|
|
2066
2122
|
)
|
|
2067
2123
|
|
|
2068
|
-
var allHeartbeatSamples: [
|
|
2124
|
+
var allHeartbeatSamples: [[String: Any]] = []
|
|
2069
2125
|
for sample in samples as! [HKHeartbeatSeriesSample] {
|
|
2070
2126
|
allHeartbeatSamples.append(
|
|
2071
2127
|
try await getSerializedHeartbeatSeriesSample(
|
|
@@ -2105,7 +2161,6 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
2105
2161
|
}
|
|
2106
2162
|
}
|
|
2107
2163
|
|
|
2108
|
-
@available(iOS 18.0, *)
|
|
2109
2164
|
@objc(queryStateOfMindSamples:to:limit:ascending:resolve:reject:)
|
|
2110
2165
|
func queryStateOfMindSamples(
|
|
2111
2166
|
from: Date,
|
|
@@ -2115,56 +2170,60 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
2115
2170
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
2116
2171
|
reject: @escaping RCTPromiseRejectBlock
|
|
2117
2172
|
) {
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
let from = dateOrNilIfZero(date: from)
|
|
2124
|
-
let to = dateOrNilIfZero(date: to)
|
|
2125
|
-
|
|
2126
|
-
let predicate = createPredicate(
|
|
2127
|
-
from: from,
|
|
2128
|
-
to: to
|
|
2129
|
-
)
|
|
2130
|
-
|
|
2131
|
-
let limit = limitOrNilIfZero(
|
|
2132
|
-
limit: limit
|
|
2133
|
-
)
|
|
2173
|
+
#if compiler(>=6)
|
|
2174
|
+
if #available(iOS 18.0, *) {
|
|
2175
|
+
guard let store = _store else {
|
|
2176
|
+
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
|
|
2177
|
+
}
|
|
2134
2178
|
|
|
2135
|
-
|
|
2179
|
+
Task {
|
|
2180
|
+
let from = dateOrNilIfZero(date: from)
|
|
2181
|
+
let to = dateOrNilIfZero(date: to)
|
|
2136
2182
|
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)]
|
|
2142
|
-
) { (_, samples, error) in
|
|
2143
|
-
if let error = error {
|
|
2144
|
-
reject(GENERIC_ERROR, error.localizedDescription, error)
|
|
2145
|
-
return
|
|
2146
|
-
}
|
|
2183
|
+
let predicate = createPredicate(
|
|
2184
|
+
from: from,
|
|
2185
|
+
to: to
|
|
2186
|
+
)
|
|
2147
2187
|
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
}
|
|
2188
|
+
let limit = limitOrNilIfZero(
|
|
2189
|
+
limit: limit
|
|
2190
|
+
)
|
|
2152
2191
|
|
|
2153
|
-
let
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2192
|
+
let type = HKStateOfMindType.stateOfMindType()
|
|
2193
|
+
|
|
2194
|
+
let query = HKSampleQuery(
|
|
2195
|
+
sampleType: type,
|
|
2196
|
+
predicate: predicate,
|
|
2197
|
+
limit: limit,
|
|
2198
|
+
sortDescriptors: [
|
|
2199
|
+
NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)
|
|
2200
|
+
]
|
|
2201
|
+
) { (_, samples, error) in
|
|
2202
|
+
if let error = error {
|
|
2203
|
+
reject(GENERIC_ERROR, error.localizedDescription, error)
|
|
2204
|
+
return
|
|
2159
2205
|
}
|
|
2160
2206
|
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
return label.rawValue
|
|
2165
|
-
})
|
|
2207
|
+
guard let samples = samples as? [HKStateOfMind] else {
|
|
2208
|
+
resolve([])
|
|
2209
|
+
return
|
|
2166
2210
|
}
|
|
2167
2211
|
|
|
2212
|
+
let serializedSamples = samples.map { sample in
|
|
2213
|
+
var associations: [Int] = []
|
|
2214
|
+
if #available(iOS 18.0, *) {
|
|
2215
|
+
associations = sample.associations.map({ association in
|
|
2216
|
+
return association.rawValue
|
|
2217
|
+
})
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
var labels: [Int] = []
|
|
2221
|
+
if #available(iOS 18.0, *) {
|
|
2222
|
+
labels = sample.labels.map({ label in
|
|
2223
|
+
return label.rawValue
|
|
2224
|
+
})
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2168
2227
|
return [
|
|
2169
2228
|
"uuid": sample.uuid.uuidString,
|
|
2170
2229
|
"device": serializeDevice(_device: sample.device) as Any,
|
|
@@ -2176,15 +2235,22 @@ class ReactNativeHealthkit: RCTEventEmitter {
|
|
|
2176
2235
|
"associations": associations,
|
|
2177
2236
|
"labels": labels,
|
|
2178
2237
|
"metadata": serializeMetadata(metadata: sample.metadata),
|
|
2179
|
-
"sourceRevision": serializeSourceRevision(_sourceRevision: sample.sourceRevision)
|
|
2238
|
+
"sourceRevision": serializeSourceRevision(_sourceRevision: sample.sourceRevision)
|
|
2239
|
+
as Any
|
|
2180
2240
|
]
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
resolve(serializedSamples)
|
|
2181
2244
|
}
|
|
2182
2245
|
|
|
2183
|
-
|
|
2246
|
+
store.execute(query)
|
|
2184
2247
|
}
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2248
|
+
} else {
|
|
2249
|
+
reject("STATE_OF_MIND_ERROR", "State of Mind features require iOS 18.0 or later", nil)
|
|
2250
|
+
}
|
|
2251
|
+
#else
|
|
2252
|
+
reject(
|
|
2253
|
+
"STATE_OF_MIND_ERROR", "State of Mind features require Xcode 16 or later to compile", nil)
|
|
2254
|
+
#endif
|
|
2188
2255
|
}
|
|
2189
|
-
|
|
2190
2256
|
}
|