@capgo/capacitor-health 8.2.17 → 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 +42 -3
- package/android/build.gradle +1 -1
- package/android/src/main/AndroidManifest.xml +20 -0
- package/android/src/main/java/app/capgo/plugin/health/HealthDataType.kt +24 -1
- package/android/src/main/java/app/capgo/plugin/health/HealthManager.kt +264 -14
- package/android/src/main/java/app/capgo/plugin/health/HealthPlugin.kt +4 -1
- package/android/src/main/java/app/capgo/plugin/health/WorkoutType.kt +117 -1
- package/dist/docs.json +336 -0
- package/dist/esm/definitions.d.ts +11 -3
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/HealthPlugin/Health.swift +582 -2
- package/ios/Sources/HealthPlugin/HealthPlugin.swift +7 -2
- package/package.json +1 -1
|
@@ -27,6 +27,19 @@ enum HealthManagerError: LocalizedError {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/// WorkoutType enum that maps TypeScript workout types to iOS HealthKit HKWorkoutActivityType values.
|
|
31
|
+
///
|
|
32
|
+
/// This enum provides bidirectional mapping between the plugin's TypeScript workout types and
|
|
33
|
+
/// native iOS HealthKit workout activity types. The mapping is designed to provide maximum
|
|
34
|
+
/// compatibility across all iOS versions.
|
|
35
|
+
///
|
|
36
|
+
/// iOS Version Compatibility:
|
|
37
|
+
/// - Most workout types are available on all supported iOS versions
|
|
38
|
+
/// - cardioDance and socialDance require iOS 14.0+ and fallback to .dance on older versions
|
|
39
|
+
/// - transition requires iOS 16.0+ and falls back to .other on older versions
|
|
40
|
+
/// - underwaterDiving requires iOS 17.0+ and falls back to .swimming on older versions
|
|
41
|
+
///
|
|
42
|
+
/// Note: The enum case names match the TypeScript WorkoutType union type for consistency.
|
|
30
43
|
enum WorkoutType: String, CaseIterable {
|
|
31
44
|
case running
|
|
32
45
|
case cycling
|
|
@@ -49,6 +62,66 @@ enum WorkoutType: String, CaseIterable {
|
|
|
49
62
|
case waterPolo
|
|
50
63
|
case waterSports
|
|
51
64
|
case wrestling
|
|
65
|
+
case archery
|
|
66
|
+
case australianFootball
|
|
67
|
+
case badminton
|
|
68
|
+
case barre
|
|
69
|
+
case bowling
|
|
70
|
+
case boxing
|
|
71
|
+
case climbing
|
|
72
|
+
case cooldown
|
|
73
|
+
case coreTraining
|
|
74
|
+
case cricket
|
|
75
|
+
case crossCountrySkiing
|
|
76
|
+
case curling
|
|
77
|
+
case dance
|
|
78
|
+
case discSports
|
|
79
|
+
case downhillSkiing
|
|
80
|
+
case equestrianSports
|
|
81
|
+
case fencing
|
|
82
|
+
case fishing
|
|
83
|
+
case fitnessGaming
|
|
84
|
+
case flexibility
|
|
85
|
+
case functionalStrengthTraining
|
|
86
|
+
case golf
|
|
87
|
+
case gymnastics
|
|
88
|
+
case handball
|
|
89
|
+
case handCycling
|
|
90
|
+
case highIntensityIntervalTraining
|
|
91
|
+
case hockey
|
|
92
|
+
case hunting
|
|
93
|
+
case jumpRope
|
|
94
|
+
case kickboxing
|
|
95
|
+
case lacrosse
|
|
96
|
+
case martialArts
|
|
97
|
+
case mindAndBody
|
|
98
|
+
case mixedCardio
|
|
99
|
+
case paddleSports
|
|
100
|
+
case pickleball
|
|
101
|
+
case pilates
|
|
102
|
+
case play
|
|
103
|
+
case preparationAndRecovery
|
|
104
|
+
case racquetball
|
|
105
|
+
case rugby
|
|
106
|
+
case sailing
|
|
107
|
+
case skatingSports
|
|
108
|
+
case snowboarding
|
|
109
|
+
case snowSports
|
|
110
|
+
case softball
|
|
111
|
+
case squash
|
|
112
|
+
case stairs
|
|
113
|
+
case stepTraining
|
|
114
|
+
case surfingSports
|
|
115
|
+
case tableTennis
|
|
116
|
+
case taiChi
|
|
117
|
+
case trackAndField
|
|
118
|
+
case transition
|
|
119
|
+
case underwaterDiving
|
|
120
|
+
case volleyball
|
|
121
|
+
case wheelchairRunPace
|
|
122
|
+
case wheelchairWalkPace
|
|
123
|
+
case cardioDance
|
|
124
|
+
case socialDance
|
|
52
125
|
case other
|
|
53
126
|
|
|
54
127
|
func hkWorkoutActivityType() -> HKWorkoutActivityType {
|
|
@@ -95,6 +168,142 @@ enum WorkoutType: String, CaseIterable {
|
|
|
95
168
|
return .waterSports
|
|
96
169
|
case .wrestling:
|
|
97
170
|
return .wrestling
|
|
171
|
+
case .archery:
|
|
172
|
+
return .archery
|
|
173
|
+
case .australianFootball:
|
|
174
|
+
return .australianFootball
|
|
175
|
+
case .badminton:
|
|
176
|
+
return .badminton
|
|
177
|
+
case .barre:
|
|
178
|
+
return .barre
|
|
179
|
+
case .bowling:
|
|
180
|
+
return .bowling
|
|
181
|
+
case .boxing:
|
|
182
|
+
return .boxing
|
|
183
|
+
case .climbing:
|
|
184
|
+
return .climbing
|
|
185
|
+
case .cooldown:
|
|
186
|
+
return .cooldown
|
|
187
|
+
case .coreTraining:
|
|
188
|
+
return .coreTraining
|
|
189
|
+
case .cricket:
|
|
190
|
+
return .cricket
|
|
191
|
+
case .crossCountrySkiing:
|
|
192
|
+
return .crossCountrySkiing
|
|
193
|
+
case .curling:
|
|
194
|
+
return .curling
|
|
195
|
+
case .dance:
|
|
196
|
+
return .dance
|
|
197
|
+
case .discSports:
|
|
198
|
+
return .discSports
|
|
199
|
+
case .downhillSkiing:
|
|
200
|
+
return .downhillSkiing
|
|
201
|
+
case .equestrianSports:
|
|
202
|
+
return .equestrianSports
|
|
203
|
+
case .fencing:
|
|
204
|
+
return .fencing
|
|
205
|
+
case .fishing:
|
|
206
|
+
return .fishing
|
|
207
|
+
case .fitnessGaming:
|
|
208
|
+
return .fitnessGaming
|
|
209
|
+
case .flexibility:
|
|
210
|
+
return .flexibility
|
|
211
|
+
case .functionalStrengthTraining:
|
|
212
|
+
return .functionalStrengthTraining
|
|
213
|
+
case .golf:
|
|
214
|
+
return .golf
|
|
215
|
+
case .gymnastics:
|
|
216
|
+
return .gymnastics
|
|
217
|
+
case .handball:
|
|
218
|
+
return .handball
|
|
219
|
+
case .handCycling:
|
|
220
|
+
return .handCycling
|
|
221
|
+
case .highIntensityIntervalTraining:
|
|
222
|
+
return .highIntensityIntervalTraining
|
|
223
|
+
case .hockey:
|
|
224
|
+
return .hockey
|
|
225
|
+
case .hunting:
|
|
226
|
+
return .hunting
|
|
227
|
+
case .jumpRope:
|
|
228
|
+
return .jumpRope
|
|
229
|
+
case .kickboxing:
|
|
230
|
+
return .kickboxing
|
|
231
|
+
case .lacrosse:
|
|
232
|
+
return .lacrosse
|
|
233
|
+
case .martialArts:
|
|
234
|
+
return .martialArts
|
|
235
|
+
case .mindAndBody:
|
|
236
|
+
return .mindAndBody
|
|
237
|
+
case .mixedCardio:
|
|
238
|
+
return .mixedCardio
|
|
239
|
+
case .paddleSports:
|
|
240
|
+
return .paddleSports
|
|
241
|
+
case .pickleball:
|
|
242
|
+
return .pickleball
|
|
243
|
+
case .pilates:
|
|
244
|
+
return .pilates
|
|
245
|
+
case .play:
|
|
246
|
+
return .play
|
|
247
|
+
case .preparationAndRecovery:
|
|
248
|
+
return .preparationAndRecovery
|
|
249
|
+
case .racquetball:
|
|
250
|
+
return .racquetball
|
|
251
|
+
case .rugby:
|
|
252
|
+
return .rugby
|
|
253
|
+
case .sailing:
|
|
254
|
+
return .sailing
|
|
255
|
+
case .skatingSports:
|
|
256
|
+
return .skatingSports
|
|
257
|
+
case .snowboarding:
|
|
258
|
+
return .snowboarding
|
|
259
|
+
case .snowSports:
|
|
260
|
+
return .snowSports
|
|
261
|
+
case .softball:
|
|
262
|
+
return .softball
|
|
263
|
+
case .squash:
|
|
264
|
+
return .squash
|
|
265
|
+
case .stairs:
|
|
266
|
+
return .stairs
|
|
267
|
+
case .stepTraining:
|
|
268
|
+
return .stepTraining
|
|
269
|
+
case .surfingSports:
|
|
270
|
+
return .surfingSports
|
|
271
|
+
case .tableTennis:
|
|
272
|
+
return .tableTennis
|
|
273
|
+
case .taiChi:
|
|
274
|
+
return .taiChi
|
|
275
|
+
case .trackAndField:
|
|
276
|
+
return .trackAndField
|
|
277
|
+
case .transition:
|
|
278
|
+
// transition requires iOS 16.0+, fallback to other for older versions
|
|
279
|
+
if #available(iOS 16.0, *) {
|
|
280
|
+
return .transition
|
|
281
|
+
}
|
|
282
|
+
return .other
|
|
283
|
+
case .underwaterDiving:
|
|
284
|
+
// underwaterDiving requires iOS 17.0+, fallback to swimming for older versions
|
|
285
|
+
if #available(iOS 17.0, *) {
|
|
286
|
+
return .underwaterDiving
|
|
287
|
+
}
|
|
288
|
+
return .swimming
|
|
289
|
+
case .volleyball:
|
|
290
|
+
return .volleyball
|
|
291
|
+
case .wheelchairRunPace:
|
|
292
|
+
return .wheelchairRunPace
|
|
293
|
+
case .wheelchairWalkPace:
|
|
294
|
+
return .wheelchairWalkPace
|
|
295
|
+
case .cardioDance:
|
|
296
|
+
// cardioDance requires iOS 14.0+, fallback to dance for older versions
|
|
297
|
+
if #available(iOS 14.0, *) {
|
|
298
|
+
return .cardioDance
|
|
299
|
+
}
|
|
300
|
+
return .dance
|
|
301
|
+
case .socialDance:
|
|
302
|
+
// socialDance requires iOS 14.0+, fallback to dance for older versions
|
|
303
|
+
if #available(iOS 14.0, *) {
|
|
304
|
+
return .socialDance
|
|
305
|
+
}
|
|
306
|
+
return .dance
|
|
98
307
|
case .other:
|
|
99
308
|
return .other
|
|
100
309
|
}
|
|
@@ -112,8 +321,11 @@ enum WorkoutType: String, CaseIterable {
|
|
|
112
321
|
return .swimming
|
|
113
322
|
case .yoga:
|
|
114
323
|
return .yoga
|
|
324
|
+
// Note: Both plugin workout types "strengthTraining" and "traditionalStrengthTraining"
|
|
325
|
+
// are written to HealthKit as HKWorkoutActivityType.traditionalStrengthTraining.
|
|
326
|
+
// When reading from HealthKit we always normalize back to .strengthTraining to keep
|
|
327
|
+
// a single canonical plugin type and maintain backward compatibility.
|
|
115
328
|
case .traditionalStrengthTraining:
|
|
116
|
-
// Map back to strengthTraining for consistency (both map to the same HK type)
|
|
117
329
|
return .strengthTraining
|
|
118
330
|
case .hiking:
|
|
119
331
|
return .hiking
|
|
@@ -143,7 +355,140 @@ enum WorkoutType: String, CaseIterable {
|
|
|
143
355
|
return .waterSports
|
|
144
356
|
case .wrestling:
|
|
145
357
|
return .wrestling
|
|
358
|
+
case .archery:
|
|
359
|
+
return .archery
|
|
360
|
+
case .australianFootball:
|
|
361
|
+
return .australianFootball
|
|
362
|
+
case .badminton:
|
|
363
|
+
return .badminton
|
|
364
|
+
case .barre:
|
|
365
|
+
return .barre
|
|
366
|
+
case .bowling:
|
|
367
|
+
return .bowling
|
|
368
|
+
case .boxing:
|
|
369
|
+
return .boxing
|
|
370
|
+
case .climbing:
|
|
371
|
+
return .climbing
|
|
372
|
+
case .cooldown:
|
|
373
|
+
return .cooldown
|
|
374
|
+
case .coreTraining:
|
|
375
|
+
return .coreTraining
|
|
376
|
+
case .cricket:
|
|
377
|
+
return .cricket
|
|
378
|
+
case .crossCountrySkiing:
|
|
379
|
+
return .crossCountrySkiing
|
|
380
|
+
case .curling:
|
|
381
|
+
return .curling
|
|
382
|
+
case .dance:
|
|
383
|
+
return .dance
|
|
384
|
+
case .discSports:
|
|
385
|
+
return .discSports
|
|
386
|
+
case .downhillSkiing:
|
|
387
|
+
return .downhillSkiing
|
|
388
|
+
case .equestrianSports:
|
|
389
|
+
return .equestrianSports
|
|
390
|
+
case .fencing:
|
|
391
|
+
return .fencing
|
|
392
|
+
case .fishing:
|
|
393
|
+
return .fishing
|
|
394
|
+
case .fitnessGaming:
|
|
395
|
+
return .fitnessGaming
|
|
396
|
+
case .flexibility:
|
|
397
|
+
return .flexibility
|
|
398
|
+
case .functionalStrengthTraining:
|
|
399
|
+
return .functionalStrengthTraining
|
|
400
|
+
case .golf:
|
|
401
|
+
return .golf
|
|
402
|
+
case .gymnastics:
|
|
403
|
+
return .gymnastics
|
|
404
|
+
case .handball:
|
|
405
|
+
return .handball
|
|
406
|
+
case .handCycling:
|
|
407
|
+
return .handCycling
|
|
408
|
+
case .highIntensityIntervalTraining:
|
|
409
|
+
return .highIntensityIntervalTraining
|
|
410
|
+
case .hockey:
|
|
411
|
+
return .hockey
|
|
412
|
+
case .hunting:
|
|
413
|
+
return .hunting
|
|
414
|
+
case .jumpRope:
|
|
415
|
+
return .jumpRope
|
|
416
|
+
case .kickboxing:
|
|
417
|
+
return .kickboxing
|
|
418
|
+
case .lacrosse:
|
|
419
|
+
return .lacrosse
|
|
420
|
+
case .martialArts:
|
|
421
|
+
return .martialArts
|
|
422
|
+
case .mindAndBody:
|
|
423
|
+
return .mindAndBody
|
|
424
|
+
case .mixedCardio:
|
|
425
|
+
return .mixedCardio
|
|
426
|
+
case .paddleSports:
|
|
427
|
+
return .paddleSports
|
|
428
|
+
case .pickleball:
|
|
429
|
+
return .pickleball
|
|
430
|
+
case .pilates:
|
|
431
|
+
return .pilates
|
|
432
|
+
case .play:
|
|
433
|
+
return .play
|
|
434
|
+
case .preparationAndRecovery:
|
|
435
|
+
return .preparationAndRecovery
|
|
436
|
+
case .racquetball:
|
|
437
|
+
return .racquetball
|
|
438
|
+
case .rugby:
|
|
439
|
+
return .rugby
|
|
440
|
+
case .sailing:
|
|
441
|
+
return .sailing
|
|
442
|
+
case .skatingSports:
|
|
443
|
+
return .skatingSports
|
|
444
|
+
case .snowboarding:
|
|
445
|
+
return .snowboarding
|
|
446
|
+
case .snowSports:
|
|
447
|
+
return .snowSports
|
|
448
|
+
case .softball:
|
|
449
|
+
return .softball
|
|
450
|
+
case .squash:
|
|
451
|
+
return .squash
|
|
452
|
+
case .stairs:
|
|
453
|
+
return .stairs
|
|
454
|
+
case .stepTraining:
|
|
455
|
+
return .stepTraining
|
|
456
|
+
case .surfingSports:
|
|
457
|
+
return .surfingSports
|
|
458
|
+
case .tableTennis:
|
|
459
|
+
return .tableTennis
|
|
460
|
+
case .taiChi:
|
|
461
|
+
return .taiChi
|
|
462
|
+
case .trackAndField:
|
|
463
|
+
return .trackAndField
|
|
464
|
+
case .volleyball:
|
|
465
|
+
return .volleyball
|
|
466
|
+
case .wheelchairRunPace:
|
|
467
|
+
return .wheelchairRunPace
|
|
468
|
+
case .wheelchairWalkPace:
|
|
469
|
+
return .wheelchairWalkPace
|
|
146
470
|
default:
|
|
471
|
+
// Handle iOS 14+ types
|
|
472
|
+
if #available(iOS 14.0, *) {
|
|
473
|
+
if hkType == .cardioDance {
|
|
474
|
+
return .cardioDance
|
|
475
|
+
}
|
|
476
|
+
if hkType == .socialDance {
|
|
477
|
+
return .socialDance
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Handle iOS 16+ types
|
|
481
|
+
if #available(iOS 16.0, *) {
|
|
482
|
+
if hkType == .transition {
|
|
483
|
+
return .transition
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// Handle iOS 17+ types
|
|
487
|
+
if #available(iOS 17.0, *) {
|
|
488
|
+
if hkType == .underwaterDiving {
|
|
489
|
+
return .underwaterDiving
|
|
490
|
+
}
|
|
491
|
+
}
|
|
147
492
|
return .other
|
|
148
493
|
}
|
|
149
494
|
}
|
|
@@ -160,6 +505,18 @@ enum HealthDataType: String, CaseIterable {
|
|
|
160
505
|
case oxygenSaturation
|
|
161
506
|
case restingHeartRate
|
|
162
507
|
case heartRateVariability
|
|
508
|
+
case bloodPressure
|
|
509
|
+
case bloodGlucose
|
|
510
|
+
case bodyTemperature
|
|
511
|
+
case height
|
|
512
|
+
case flightsClimbed
|
|
513
|
+
case exerciseTime
|
|
514
|
+
case distanceCycling
|
|
515
|
+
case bodyFat
|
|
516
|
+
case basalBodyTemperature
|
|
517
|
+
case basalCalories
|
|
518
|
+
case totalCalories
|
|
519
|
+
case mindfulness
|
|
163
520
|
|
|
164
521
|
func sampleType() throws -> HKSampleType {
|
|
165
522
|
switch self {
|
|
@@ -168,6 +525,16 @@ enum HealthDataType: String, CaseIterable {
|
|
|
168
525
|
throw HealthManagerError.dataTypeUnavailable(rawValue)
|
|
169
526
|
}
|
|
170
527
|
return type
|
|
528
|
+
case .bloodPressure:
|
|
529
|
+
guard let type = HKObjectType.correlationType(forIdentifier: .bloodPressure) else {
|
|
530
|
+
throw HealthManagerError.dataTypeUnavailable(rawValue)
|
|
531
|
+
}
|
|
532
|
+
return type
|
|
533
|
+
case .mindfulness:
|
|
534
|
+
guard let type = HKObjectType.categoryType(forIdentifier: .mindfulSession) else {
|
|
535
|
+
throw HealthManagerError.dataTypeUnavailable(rawValue)
|
|
536
|
+
}
|
|
537
|
+
return type
|
|
171
538
|
default:
|
|
172
539
|
return try quantityType()
|
|
173
540
|
}
|
|
@@ -194,8 +561,32 @@ enum HealthDataType: String, CaseIterable {
|
|
|
194
561
|
identifier = .restingHeartRate
|
|
195
562
|
case .heartRateVariability:
|
|
196
563
|
identifier = .heartRateVariabilitySDNN
|
|
564
|
+
case .bloodGlucose:
|
|
565
|
+
identifier = .bloodGlucose
|
|
566
|
+
case .bodyTemperature:
|
|
567
|
+
identifier = .bodyTemperature
|
|
568
|
+
case .height:
|
|
569
|
+
identifier = .height
|
|
570
|
+
case .flightsClimbed:
|
|
571
|
+
identifier = .flightsClimbed
|
|
572
|
+
case .exerciseTime:
|
|
573
|
+
identifier = .appleExerciseTime
|
|
574
|
+
case .distanceCycling:
|
|
575
|
+
identifier = .distanceCycling
|
|
576
|
+
case .bodyFat:
|
|
577
|
+
identifier = .bodyFatPercentage
|
|
578
|
+
case .basalBodyTemperature:
|
|
579
|
+
identifier = .basalBodyTemperature
|
|
580
|
+
case .basalCalories:
|
|
581
|
+
identifier = .basalEnergyBurned
|
|
582
|
+
case .totalCalories:
|
|
583
|
+
identifier = .activeEnergyBurned
|
|
197
584
|
case .sleep:
|
|
198
585
|
throw HealthManagerError.invalidDataType("Sleep is a category type, not a quantity type")
|
|
586
|
+
case .bloodPressure:
|
|
587
|
+
throw HealthManagerError.invalidDataType("Blood pressure is a correlation type, not a quantity type")
|
|
588
|
+
case .mindfulness:
|
|
589
|
+
throw HealthManagerError.invalidDataType("Mindfulness is a category type, not a quantity type")
|
|
199
590
|
}
|
|
200
591
|
|
|
201
592
|
guard let type = HKObjectType.quantityType(forIdentifier: identifier) else {
|
|
@@ -224,6 +615,28 @@ enum HealthDataType: String, CaseIterable {
|
|
|
224
615
|
return HKUnit.secondUnit(with: .milli)
|
|
225
616
|
case .sleep:
|
|
226
617
|
return HKUnit.minute()
|
|
618
|
+
case .bloodPressure:
|
|
619
|
+
return HKUnit.millimeterOfMercury()
|
|
620
|
+
case .bloodGlucose:
|
|
621
|
+
return HKUnit.gramUnit(with: .milli).unitDivided(by: HKUnit.literUnit(with: .deci))
|
|
622
|
+
case .bodyTemperature, .basalBodyTemperature:
|
|
623
|
+
return HKUnit.degreeCelsius()
|
|
624
|
+
case .height:
|
|
625
|
+
return HKUnit.meterUnit(with: .centi)
|
|
626
|
+
case .flightsClimbed:
|
|
627
|
+
return HKUnit.count()
|
|
628
|
+
case .exerciseTime:
|
|
629
|
+
return HKUnit.minute()
|
|
630
|
+
case .distanceCycling:
|
|
631
|
+
return HKUnit.meter()
|
|
632
|
+
case .bodyFat:
|
|
633
|
+
return HKUnit.percent()
|
|
634
|
+
case .basalCalories:
|
|
635
|
+
return HKUnit.kilocalorie()
|
|
636
|
+
case .totalCalories:
|
|
637
|
+
return HKUnit.kilocalorie()
|
|
638
|
+
case .mindfulness:
|
|
639
|
+
return HKUnit.minute()
|
|
227
640
|
}
|
|
228
641
|
}
|
|
229
642
|
|
|
@@ -245,6 +658,28 @@ enum HealthDataType: String, CaseIterable {
|
|
|
245
658
|
return "millisecond"
|
|
246
659
|
case .sleep:
|
|
247
660
|
return "minute"
|
|
661
|
+
case .bloodPressure:
|
|
662
|
+
return "mmHg"
|
|
663
|
+
case .bloodGlucose:
|
|
664
|
+
return "mg/dL"
|
|
665
|
+
case .bodyTemperature, .basalBodyTemperature:
|
|
666
|
+
return "celsius"
|
|
667
|
+
case .height:
|
|
668
|
+
return "centimeter"
|
|
669
|
+
case .flightsClimbed:
|
|
670
|
+
return "count"
|
|
671
|
+
case .exerciseTime:
|
|
672
|
+
return "minute"
|
|
673
|
+
case .distanceCycling:
|
|
674
|
+
return "meter"
|
|
675
|
+
case .bodyFat:
|
|
676
|
+
return "percent"
|
|
677
|
+
case .basalCalories:
|
|
678
|
+
return "kilocalorie"
|
|
679
|
+
case .totalCalories:
|
|
680
|
+
return "kilocalorie"
|
|
681
|
+
case .mindfulness:
|
|
682
|
+
return "minute"
|
|
248
683
|
}
|
|
249
684
|
}
|
|
250
685
|
|
|
@@ -415,6 +850,94 @@ final class Health {
|
|
|
415
850
|
healthStore.execute(query)
|
|
416
851
|
return
|
|
417
852
|
}
|
|
853
|
+
|
|
854
|
+
// Handle mindfulness as a category sample
|
|
855
|
+
if dataType == .mindfulness {
|
|
856
|
+
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: queryLimit, sortDescriptors: [sortDescriptor]) { [weak self] _, samples, error in
|
|
857
|
+
guard let self = self else { return }
|
|
858
|
+
|
|
859
|
+
if let error = error {
|
|
860
|
+
completion(.failure(error))
|
|
861
|
+
return
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
guard let categorySamples = samples as? [HKCategorySample] else {
|
|
865
|
+
completion(.success([]))
|
|
866
|
+
return
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
let results = categorySamples.map { sample -> [String: Any] in
|
|
870
|
+
let durationMinutes = sample.endDate.timeIntervalSince(sample.startDate) / 60.0
|
|
871
|
+
|
|
872
|
+
var payload: [String: Any] = [
|
|
873
|
+
"dataType": dataType.rawValue,
|
|
874
|
+
"value": durationMinutes,
|
|
875
|
+
"unit": dataType.unitIdentifier,
|
|
876
|
+
"startDate": self.isoFormatter.string(from: sample.startDate),
|
|
877
|
+
"endDate": self.isoFormatter.string(from: sample.endDate)
|
|
878
|
+
]
|
|
879
|
+
|
|
880
|
+
let source = sample.sourceRevision.source
|
|
881
|
+
payload["sourceName"] = source.name
|
|
882
|
+
payload["sourceId"] = source.bundleIdentifier
|
|
883
|
+
|
|
884
|
+
return payload
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
completion(.success(results))
|
|
888
|
+
}
|
|
889
|
+
healthStore.execute(query)
|
|
890
|
+
return
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Handle blood pressure as a correlation sample
|
|
894
|
+
if dataType == .bloodPressure {
|
|
895
|
+
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: queryLimit, sortDescriptors: [sortDescriptor]) { [weak self] _, samples, error in
|
|
896
|
+
guard let self = self else { return }
|
|
897
|
+
|
|
898
|
+
if let error = error {
|
|
899
|
+
completion(.failure(error))
|
|
900
|
+
return
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
guard let correlations = samples as? [HKCorrelation] else {
|
|
904
|
+
completion(.success([]))
|
|
905
|
+
return
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
let results = correlations.compactMap { correlation -> [String: Any]? in
|
|
909
|
+
guard let systolicType = HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic),
|
|
910
|
+
let diastolicType = HKObjectType.quantityType(forIdentifier: .bloodPressureDiastolic),
|
|
911
|
+
let systolicSample = correlation.objects(for: systolicType).first as? HKQuantitySample,
|
|
912
|
+
let diastolicSample = correlation.objects(for: diastolicType).first as? HKQuantitySample else {
|
|
913
|
+
return nil
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
let systolicValue = systolicSample.quantity.doubleValue(for: HKUnit.millimeterOfMercury())
|
|
917
|
+
let diastolicValue = diastolicSample.quantity.doubleValue(for: HKUnit.millimeterOfMercury())
|
|
918
|
+
|
|
919
|
+
var payload: [String: Any] = [
|
|
920
|
+
"dataType": dataType.rawValue,
|
|
921
|
+
"value": systolicValue,
|
|
922
|
+
"unit": dataType.unitIdentifier,
|
|
923
|
+
"startDate": self.isoFormatter.string(from: correlation.startDate),
|
|
924
|
+
"endDate": self.isoFormatter.string(from: correlation.endDate),
|
|
925
|
+
"systolic": systolicValue,
|
|
926
|
+
"diastolic": diastolicValue
|
|
927
|
+
]
|
|
928
|
+
|
|
929
|
+
let source = correlation.sourceRevision.source
|
|
930
|
+
payload["sourceName"] = source.name
|
|
931
|
+
payload["sourceId"] = source.bundleIdentifier
|
|
932
|
+
|
|
933
|
+
return payload
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
completion(.success(results))
|
|
937
|
+
}
|
|
938
|
+
healthStore.execute(query)
|
|
939
|
+
return
|
|
940
|
+
}
|
|
418
941
|
|
|
419
942
|
// Handle quantity samples
|
|
420
943
|
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: queryLimit, sortDescriptors: [sortDescriptor]) { [weak self] _, samples, error in
|
|
@@ -481,7 +1004,7 @@ final class Health {
|
|
|
481
1004
|
}
|
|
482
1005
|
}
|
|
483
1006
|
|
|
484
|
-
func saveSample(dataTypeIdentifier: String, value: Double, unitIdentifier: String?, startDateString: String?, endDateString: String?, metadata: [String: String]?, completion: @escaping (Result<Void, Error>) -> Void) throws {
|
|
1007
|
+
func saveSample(dataTypeIdentifier: String, value: Double, unitIdentifier: String?, startDateString: String?, endDateString: String?, metadata: [String: String]?, systolic: Double?, diastolic: Double?, completion: @escaping (Result<Void, Error>) -> Void) throws {
|
|
485
1008
|
guard HKHealthStore.isHealthDataAvailable() else {
|
|
486
1009
|
throw HealthManagerError.healthDataUnavailable
|
|
487
1010
|
}
|
|
@@ -526,6 +1049,63 @@ final class Health {
|
|
|
526
1049
|
}
|
|
527
1050
|
return
|
|
528
1051
|
}
|
|
1052
|
+
|
|
1053
|
+
// Handle mindfulness as a category sample
|
|
1054
|
+
if dataType == .mindfulness {
|
|
1055
|
+
guard let categoryType = HKObjectType.categoryType(forIdentifier: .mindfulSession) else {
|
|
1056
|
+
throw HealthManagerError.dataTypeUnavailable(dataTypeIdentifier)
|
|
1057
|
+
}
|
|
1058
|
+
let sample = HKCategorySample(type: categoryType, value: 0, start: startDate, end: endDate, metadata: metadataDictionary)
|
|
1059
|
+
|
|
1060
|
+
healthStore.save(sample) { success, error in
|
|
1061
|
+
if let error = error {
|
|
1062
|
+
completion(.failure(error))
|
|
1063
|
+
return
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if success {
|
|
1067
|
+
completion(.success(()))
|
|
1068
|
+
} else {
|
|
1069
|
+
completion(.failure(HealthManagerError.operationFailed("Failed to save the sample.")))
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Handle blood pressure as a correlation sample
|
|
1076
|
+
if dataType == .bloodPressure {
|
|
1077
|
+
guard let systolicValue = systolic, let diastolicValue = diastolic else {
|
|
1078
|
+
throw HealthManagerError.operationFailed("Blood pressure requires both systolic and diastolic values")
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
guard let systolicType = HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic),
|
|
1082
|
+
let diastolicType = HKObjectType.quantityType(forIdentifier: .bloodPressureDiastolic),
|
|
1083
|
+
let correlationType = HKObjectType.correlationType(forIdentifier: .bloodPressure) else {
|
|
1084
|
+
throw HealthManagerError.dataTypeUnavailable(dataTypeIdentifier)
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
let systolicQuantity = HKQuantity(unit: HKUnit.millimeterOfMercury(), doubleValue: systolicValue)
|
|
1088
|
+
let diastolicQuantity = HKQuantity(unit: HKUnit.millimeterOfMercury(), doubleValue: diastolicValue)
|
|
1089
|
+
|
|
1090
|
+
let systolicSample = HKQuantitySample(type: systolicType, quantity: systolicQuantity, start: startDate, end: endDate)
|
|
1091
|
+
let diastolicSample = HKQuantitySample(type: diastolicType, quantity: diastolicQuantity, start: startDate, end: endDate)
|
|
1092
|
+
|
|
1093
|
+
let correlation = HKCorrelation(type: correlationType, start: startDate, end: endDate, objects: [systolicSample, diastolicSample], metadata: metadataDictionary)
|
|
1094
|
+
|
|
1095
|
+
healthStore.save(correlation) { success, error in
|
|
1096
|
+
if let error = error {
|
|
1097
|
+
completion(.failure(error))
|
|
1098
|
+
return
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
if success {
|
|
1102
|
+
completion(.success(()))
|
|
1103
|
+
} else {
|
|
1104
|
+
completion(.failure(HealthManagerError.operationFailed("Failed to save the sample.")))
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return
|
|
1108
|
+
}
|
|
529
1109
|
|
|
530
1110
|
// Handle quantity samples
|
|
531
1111
|
let sampleType = try dataType.quantityType()
|
|
@@ -3,7 +3,7 @@ import Capacitor
|
|
|
3
3
|
|
|
4
4
|
@objc(HealthPlugin)
|
|
5
5
|
public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
6
|
-
private let pluginVersion: String = "8.
|
|
6
|
+
private let pluginVersion: String = "8.3.0"
|
|
7
7
|
public let identifier = "HealthPlugin"
|
|
8
8
|
public let jsName = "Health"
|
|
9
9
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -110,6 +110,9 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
110
110
|
result[entry.key] = stringValue
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
let systolic = call.getDouble("systolic")
|
|
115
|
+
let diastolic = call.getDouble("diastolic")
|
|
113
116
|
|
|
114
117
|
do {
|
|
115
118
|
try implementation.saveSample(
|
|
@@ -118,7 +121,9 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
118
121
|
unitIdentifier: unit,
|
|
119
122
|
startDateString: startDate,
|
|
120
123
|
endDateString: endDate,
|
|
121
|
-
metadata: metadata
|
|
124
|
+
metadata: metadata,
|
|
125
|
+
systolic: systolic,
|
|
126
|
+
diastolic: diastolic
|
|
122
127
|
) { result in
|
|
123
128
|
DispatchQueue.main.async {
|
|
124
129
|
switch result {
|
package/package.json
CHANGED