@flomentumsolutions/capacitor-health-extended 0.0.9 → 0.0.11
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.
|
@@ -6,7 +6,6 @@ import HealthKit
|
|
|
6
6
|
* Please read the Capacitor iOS Plugin Development Guide
|
|
7
7
|
* here: https://capacitorjs.com/docs/plugins/ios
|
|
8
8
|
*/
|
|
9
|
-
@MainActor
|
|
10
9
|
@objc(HealthPlugin)
|
|
11
10
|
public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
12
11
|
public let identifier = "HealthPlugin"
|
|
@@ -66,8 +65,19 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
66
65
|
return
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
print("⚡️ [HealthPlugin] Requesting permissions: \(permissions)")
|
|
69
|
+
|
|
69
70
|
let types: [HKObjectType] = permissions.flatMap { permissionToHKObjectType($0) }
|
|
70
71
|
|
|
72
|
+
print("⚡️ [HealthPlugin] Mapped to \(types.count) HKObjectTypes")
|
|
73
|
+
|
|
74
|
+
// Validate that we have at least one valid permission type
|
|
75
|
+
guard !types.isEmpty else {
|
|
76
|
+
let invalidPermissions = permissions.filter { permissionToHKObjectType($0).isEmpty }
|
|
77
|
+
call.reject("No valid permission types found. Invalid permissions: \(invalidPermissions)")
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
71
81
|
healthStore.requestAuthorization(toShare: nil, read: Set(types)) { success, error in
|
|
72
82
|
if success {
|
|
73
83
|
//we don't know which actual permissions were granted, so we assume all
|
|
@@ -264,7 +274,44 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
264
274
|
HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic),
|
|
265
275
|
HKObjectType.quantityType(forIdentifier: .bloodPressureDiastolic)
|
|
266
276
|
].compactMap { $0 }
|
|
277
|
+
// Add common alternative permission names
|
|
278
|
+
case "steps":
|
|
279
|
+
return [HKObjectType.quantityType(forIdentifier: .stepCount)].compactMap{$0}
|
|
280
|
+
case "weight":
|
|
281
|
+
return [HKObjectType.quantityType(forIdentifier: .bodyMass)].compactMap{$0}
|
|
282
|
+
case "height":
|
|
283
|
+
return [HKObjectType.quantityType(forIdentifier: .height)].compactMap { $0 }
|
|
284
|
+
case "calories", "total-calories":
|
|
285
|
+
return [
|
|
286
|
+
HKObjectType.quantityType(forIdentifier: .activeEnergyBurned),
|
|
287
|
+
HKObjectType.quantityType(forIdentifier: .basalEnergyBurned) // iOS 16+
|
|
288
|
+
].compactMap { $0 }
|
|
289
|
+
case "active-calories":
|
|
290
|
+
return [HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)].compactMap{$0}
|
|
291
|
+
case "workouts":
|
|
292
|
+
return [HKObjectType.workoutType()].compactMap{$0}
|
|
293
|
+
case "heart-rate", "heartrate":
|
|
294
|
+
return [HKObjectType.quantityType(forIdentifier: .heartRate)].compactMap{$0}
|
|
295
|
+
case "route":
|
|
296
|
+
return [HKSeriesType.workoutRoute()].compactMap{$0}
|
|
297
|
+
case "distance":
|
|
298
|
+
return [
|
|
299
|
+
HKObjectType.quantityType(forIdentifier: .distanceCycling),
|
|
300
|
+
HKObjectType.quantityType(forIdentifier: .distanceSwimming),
|
|
301
|
+
HKObjectType.quantityType(forIdentifier: .distanceWalkingRunning),
|
|
302
|
+
HKObjectType.quantityType(forIdentifier: .distanceDownhillSnowSports)
|
|
303
|
+
].compactMap{$0}
|
|
304
|
+
case "mindfulness":
|
|
305
|
+
return [HKObjectType.categoryType(forIdentifier: .mindfulSession)!].compactMap{$0}
|
|
306
|
+
case "hrv":
|
|
307
|
+
return [HKObjectType.quantityType(forIdentifier: .heartRateVariabilitySDNN)].compactMap { $0 }
|
|
308
|
+
case "blood-pressure", "bloodpressure":
|
|
309
|
+
return [
|
|
310
|
+
HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic),
|
|
311
|
+
HKObjectType.quantityType(forIdentifier: .bloodPressureDiastolic)
|
|
312
|
+
].compactMap { $0 }
|
|
267
313
|
default:
|
|
314
|
+
print("⚡️ [HealthPlugin] Unknown permission: \(permission)")
|
|
268
315
|
return []
|
|
269
316
|
}
|
|
270
317
|
}
|
|
@@ -333,17 +380,6 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
333
380
|
switch dataType.aggregationStyle {
|
|
334
381
|
case .cumulative:
|
|
335
382
|
return .cumulativeSum
|
|
336
|
-
case .discreteAverage:
|
|
337
|
-
return .discreteAverage
|
|
338
|
-
@available(iOS 17.0, *)
|
|
339
|
-
case .discreteTemporallyWeighted:
|
|
340
|
-
return .discreteAverage
|
|
341
|
-
@available(iOS 17.0, *)
|
|
342
|
-
case .discreteEquivalentContinuousLevel:
|
|
343
|
-
return .discreteAverage
|
|
344
|
-
@available(iOS 17.0, *)
|
|
345
|
-
case .discreteArithmetic:
|
|
346
|
-
return .discreteAverage
|
|
347
383
|
case .discrete:
|
|
348
384
|
return .discreteAverage
|
|
349
385
|
@unknown default:
|
|
@@ -512,8 +548,10 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
512
548
|
return
|
|
513
549
|
}
|
|
514
550
|
let outerGroup = DispatchGroup()
|
|
551
|
+
let resultsQueue = DispatchQueue(label: "com.flomentum.healthplugin.workoutResults")
|
|
515
552
|
var workoutResults: [[String: Any]] = []
|
|
516
553
|
var errors: [String: String] = [:]
|
|
554
|
+
|
|
517
555
|
for workout in workouts {
|
|
518
556
|
outerGroup.enter()
|
|
519
557
|
var localDict: [String: Any] = [
|
|
@@ -530,11 +568,16 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
530
568
|
let innerGroup = DispatchGroup()
|
|
531
569
|
var localHeartRates: [[String: Any]] = []
|
|
532
570
|
var localRoutes: [[String: Any]] = []
|
|
571
|
+
|
|
533
572
|
if includeHeartRate {
|
|
534
573
|
innerGroup.enter()
|
|
535
574
|
self.queryHeartRate(for: workout) { rates, error in
|
|
536
575
|
localHeartRates = rates
|
|
537
|
-
if let error = error {
|
|
576
|
+
if let error = error {
|
|
577
|
+
resultsQueue.async {
|
|
578
|
+
errors["heart-rate"] = error
|
|
579
|
+
}
|
|
580
|
+
}
|
|
538
581
|
innerGroup.leave()
|
|
539
582
|
}
|
|
540
583
|
}
|
|
@@ -542,7 +585,11 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
542
585
|
innerGroup.enter()
|
|
543
586
|
self.queryRoute(for: workout) { routes, error in
|
|
544
587
|
localRoutes = routes
|
|
545
|
-
if let error = error {
|
|
588
|
+
if let error = error {
|
|
589
|
+
resultsQueue.async {
|
|
590
|
+
errors["route"] = error
|
|
591
|
+
}
|
|
592
|
+
}
|
|
546
593
|
innerGroup.leave()
|
|
547
594
|
}
|
|
548
595
|
}
|
|
@@ -558,7 +605,9 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
558
605
|
innerGroup.notify(queue: .main) {
|
|
559
606
|
localDict["heartRate"] = localHeartRates
|
|
560
607
|
localDict["route"] = localRoutes
|
|
561
|
-
|
|
608
|
+
resultsQueue.async {
|
|
609
|
+
workoutResults.append(localDict)
|
|
610
|
+
}
|
|
562
611
|
outerGroup.leave()
|
|
563
612
|
}
|
|
564
613
|
}
|
|
@@ -611,11 +660,15 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
611
660
|
guard let self = self else { return }
|
|
612
661
|
if let routes = samples as? [HKWorkoutRoute], error == nil {
|
|
613
662
|
let routeDispatchGroup = DispatchGroup()
|
|
663
|
+
let allLocationsQueue = DispatchQueue(label: "com.flomentum.healthplugin.allLocations")
|
|
614
664
|
var allLocations: [[String: Any]] = []
|
|
665
|
+
|
|
615
666
|
for route in routes {
|
|
616
667
|
routeDispatchGroup.enter()
|
|
617
668
|
self.queryLocations(for: route) { locations in
|
|
618
|
-
|
|
669
|
+
allLocationsQueue.async {
|
|
670
|
+
allLocations.append(contentsOf: locations)
|
|
671
|
+
}
|
|
619
672
|
routeDispatchGroup.leave()
|
|
620
673
|
}
|
|
621
674
|
}
|
|
@@ -634,8 +687,6 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
634
687
|
|
|
635
688
|
// MARK: - Query Route Locations
|
|
636
689
|
private func queryLocations(for route: HKWorkoutRoute, completion: @escaping @Sendable ([[String: Any]]) -> Void) {
|
|
637
|
-
var routeLocations: [[String: Any]] = []
|
|
638
|
-
|
|
639
690
|
let locationQuery = HKWorkoutRouteQuery(route: route) { [weak self] _, locations, done, error in
|
|
640
691
|
guard let self = self else { return }
|
|
641
692
|
guard let locations = locations, error == nil else {
|
|
@@ -645,8 +696,10 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
645
696
|
return
|
|
646
697
|
}
|
|
647
698
|
|
|
648
|
-
//
|
|
699
|
+
// Process locations on the serial queue to avoid race conditions
|
|
649
700
|
self.routeSyncQueue.async {
|
|
701
|
+
var routeLocations: [[String: Any]] = []
|
|
702
|
+
|
|
650
703
|
for location in locations {
|
|
651
704
|
let locationDict: [String: Any] = [
|
|
652
705
|
"timestamp": location.timestamp,
|
package/package.json
CHANGED