@flomentumsolutions/capacitor-health-extended 0.7.6 → 0.8.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
CHANGED
|
@@ -227,6 +227,38 @@ class PermissionsRationaleActivity : AppCompatActivity() {
|
|
|
227
227
|
|
|
228
228
|
This setup ensures your WebView will load HTTPS content securely and complies with Android's default network security policy.
|
|
229
229
|
|
|
230
|
+
## iOS read-permission UX hint
|
|
231
|
+
|
|
232
|
+
On iOS, `checkHealthPermissions` is optimistic for read sample permissions because HealthKit does not distinguish
|
|
233
|
+
denied access from empty data. If a query returns no results, prompt the user to verify Apple Health settings.
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { Health, type HealthPermission } from '@flomentumsolutions/capacitor-health-extended';
|
|
237
|
+
|
|
238
|
+
const NO_DATA_HINT =
|
|
239
|
+
'No data found. If you believe this is an error, please verify your settings in Apple Health.';
|
|
240
|
+
|
|
241
|
+
export async function queryWithHealthEmptyHint<T>(
|
|
242
|
+
permission: HealthPermission,
|
|
243
|
+
query: () => Promise<T>,
|
|
244
|
+
isEmpty: (result: T) => boolean,
|
|
245
|
+
showHint: (message: string) => void
|
|
246
|
+
): Promise<T | null> {
|
|
247
|
+
const { permissions } = await Health.checkHealthPermissions({ permissions: [permission] });
|
|
248
|
+
if (!permissions[permission]) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const result = await query();
|
|
253
|
+
|
|
254
|
+
if (isEmpty(result)) {
|
|
255
|
+
showHint(NO_DATA_HINT);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
230
262
|
## API
|
|
231
263
|
```
|
|
232
264
|
<docgen-index>
|
|
@@ -237,7 +269,7 @@ This setup ensures your WebView will load HTTPS content securely and complies wi
|
|
|
237
269
|
* [`openAppleHealthSettings()`](#openapplehealthsettings)
|
|
238
270
|
* [`openHealthConnectSettings()`](#openhealthconnectsettings)
|
|
239
271
|
* [`showHealthConnectInPlayStore()`](#showhealthconnectinplaystore)
|
|
240
|
-
* [`getCharacteristics()`](#getcharacteristics)
|
|
272
|
+
* [`getCharacteristics(...)`](#getcharacteristics)
|
|
241
273
|
* [`queryAggregated(...)`](#queryaggregated)
|
|
242
274
|
* [`queryWorkouts(...)`](#queryworkouts)
|
|
243
275
|
* [`queryLatestSample(...)`](#querylatestsample)
|
|
@@ -276,7 +308,12 @@ See showHealthConnectInPlayStore()
|
|
|
276
308
|
checkHealthPermissions(permissions: PermissionsRequest) => Promise<PermissionResponse>
|
|
277
309
|
```
|
|
278
310
|
|
|
279
|
-
|
|
311
|
+
Returns whether each permission is granted.
|
|
312
|
+
Android: Uses Health Connect grant state.
|
|
313
|
+
iOS: Write permissions are strict. Read permissions for sample types are optimistic because HealthKit does not
|
|
314
|
+
distinguish denied vs no data; this returns false only when the read permission is not determined. For
|
|
315
|
+
characteristics, this probes access and returns false when denied.
|
|
316
|
+
UX tip: If this returns true but a query yields no results, show a hint to check Apple Health settings.
|
|
280
317
|
|
|
281
318
|
| Param | Type | Description |
|
|
282
319
|
| ----------------- | ----------------------------------------------------------------- | -------------------- |
|
|
@@ -331,6 +368,7 @@ openHealthConnectSettings() => Promise<void>
|
|
|
331
368
|
```
|
|
332
369
|
|
|
333
370
|
Opens the Google Health Connect app
|
|
371
|
+
iOS: Aliases openAppleHealthSettings().
|
|
334
372
|
|
|
335
373
|
--------------------
|
|
336
374
|
|
|
@@ -342,19 +380,22 @@ showHealthConnectInPlayStore() => Promise<void>
|
|
|
342
380
|
```
|
|
343
381
|
|
|
344
382
|
Opens the Google Health Connect app in PlayStore
|
|
383
|
+
iOS: Resolves without action.
|
|
345
384
|
|
|
346
385
|
--------------------
|
|
347
386
|
|
|
348
387
|
|
|
349
|
-
### getCharacteristics()
|
|
388
|
+
### getCharacteristics(...)
|
|
350
389
|
|
|
351
390
|
```typescript
|
|
352
|
-
getCharacteristics(request?: CharacteristicsRequest) => Promise<CharacteristicsResponse>
|
|
391
|
+
getCharacteristics(request?: CharacteristicsRequest | undefined) => Promise<CharacteristicsResponse>
|
|
353
392
|
```
|
|
354
393
|
|
|
355
|
-
iOS only: Reads user characteristics such as biological sex, blood type, date of birth, Fitzpatrick skin type, and wheelchair use.
|
|
394
|
+
iOS only: Reads user characteristics such as biological sex, blood type, date of birth, Fitzpatrick skin type, and wheelchair use.
|
|
356
395
|
Values are null when unavailable or permission was not granted. Android does not expose these characteristics; it returns `platformSupported: false` and a `platformMessage` for UI hints without emitting null values.
|
|
357
396
|
|
|
397
|
+
Passing `fields` lets you request only specific characteristics (e.g., date of birth) to keep permissions scoped narrowly. Defaults to all characteristics when omitted.
|
|
398
|
+
|
|
358
399
|
| Param | Type |
|
|
359
400
|
| ------------- | ------------------------------------------------------------------------- |
|
|
360
401
|
| **`request`** | <code><a href="#characteristicsrequest">CharacteristicsRequest</a></code> |
|
|
@@ -430,6 +471,7 @@ queryWeight() => Promise<QueryLatestSampleResponse>
|
|
|
430
471
|
```
|
|
431
472
|
|
|
432
473
|
Query latest weight sample
|
|
474
|
+
Convenience wrapper around queryLatestSample({ dataType: 'weight' }).
|
|
433
475
|
|
|
434
476
|
**Returns:** <code>Promise<<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>></code>
|
|
435
477
|
|
|
@@ -443,6 +485,7 @@ queryHeight() => Promise<QueryLatestSampleResponse>
|
|
|
443
485
|
```
|
|
444
486
|
|
|
445
487
|
Query latest height sample
|
|
488
|
+
Convenience wrapper around queryLatestSample({ dataType: 'height' }).
|
|
446
489
|
|
|
447
490
|
**Returns:** <code>Promise<<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>></code>
|
|
448
491
|
|
|
@@ -456,6 +499,7 @@ queryHeartRate() => Promise<QueryLatestSampleResponse>
|
|
|
456
499
|
```
|
|
457
500
|
|
|
458
501
|
Query latest heart rate sample
|
|
502
|
+
Convenience wrapper around queryLatestSample({ dataType: 'heart-rate' }).
|
|
459
503
|
|
|
460
504
|
**Returns:** <code>Promise<<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>></code>
|
|
461
505
|
|
|
@@ -469,6 +513,7 @@ querySteps() => Promise<QueryLatestSampleResponse>
|
|
|
469
513
|
```
|
|
470
514
|
|
|
471
515
|
Query latest steps sample
|
|
516
|
+
Convenience wrapper around queryLatestSample({ dataType: 'steps' }).
|
|
472
517
|
|
|
473
518
|
**Returns:** <code>Promise<<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>></code>
|
|
474
519
|
|
|
@@ -529,13 +574,6 @@ Save user-provided body metrics to the health platform.
|
|
|
529
574
|
| **`permissions`** | <code>HealthPermission[]</code> |
|
|
530
575
|
|
|
531
576
|
|
|
532
|
-
#### CharacteristicsRequest
|
|
533
|
-
|
|
534
|
-
| Prop | Type | Description |
|
|
535
|
-
| ------------ | -------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
536
|
-
| **`fields`** | <code><a href="#characteristicfield">CharacteristicField</a>[]</code> | Characteristics to query; defaults to all when unspecified |
|
|
537
|
-
|
|
538
|
-
|
|
539
577
|
#### CharacteristicsResponse
|
|
540
578
|
|
|
541
579
|
| Prop | Type | Description |
|
|
@@ -549,6 +587,13 @@ Save user-provided body metrics to the health platform.
|
|
|
549
587
|
| **`platformMessage`** | <code>string</code> | Optional platform-specific message; on Android we return a user-facing note explaining that values remain empty unless synced from iOS. |
|
|
550
588
|
|
|
551
589
|
|
|
590
|
+
#### CharacteristicsRequest
|
|
591
|
+
|
|
592
|
+
| Prop | Type | Description |
|
|
593
|
+
| ------------ | ---------------------------------- | ----------------------------------------------------------------------- |
|
|
594
|
+
| **`fields`** | <code>CharacteristicField[]</code> | Characteristics to query. Defaults to all characteristics when omitted. |
|
|
595
|
+
|
|
596
|
+
|
|
552
597
|
#### QueryAggregatedResponse
|
|
553
598
|
|
|
554
599
|
| Prop | Type |
|
|
@@ -695,11 +740,6 @@ Construct a type with a set of properties K of type T
|
|
|
695
740
|
<code>{
|
|
696
741
|
[P in K]: T;
|
|
697
742
|
}</code>
|
|
698
743
|
|
|
699
744
|
|
|
700
|
-
#### CharacteristicField
|
|
701
|
-
|
|
702
|
-
<code>'biologicalSex' | 'bloodType' | 'dateOfBirth' | 'fitzpatrickSkinType' | 'wheelchairUse'</code>
|
|
703
|
-
|
|
704
|
-
|
|
705
745
|
#### HealthPermission
|
|
706
746
|
|
|
707
747
|
<code>'READ_STEPS' | 'READ_WORKOUTS' | 'WRITE_WORKOUTS' | 'READ_ACTIVE_CALORIES' | 'WRITE_ACTIVE_CALORIES' | 'READ_TOTAL_CALORIES' | 'WRITE_TOTAL_CALORIES' | 'READ_DISTANCE' | 'WRITE_DISTANCE' | 'READ_WEIGHT' | 'WRITE_WEIGHT' | 'READ_HEIGHT' | 'WRITE_HEIGHT' | 'READ_HEART_RATE' | 'WRITE_HEART_RATE' | 'READ_RESTING_HEART_RATE' | 'WRITE_RESTING_HEART_RATE' | 'READ_ROUTE' | 'WRITE_ROUTE' | 'READ_MINDFULNESS' | 'READ_HRV' | 'READ_BLOOD_PRESSURE' | 'READ_BASAL_CALORIES' | 'READ_RESPIRATORY_RATE' | 'READ_OXYGEN_SATURATION' | 'READ_BLOOD_GLUCOSE' | 'READ_BODY_TEMPERATURE' | 'READ_BASAL_BODY_TEMPERATURE' | 'READ_BODY_FAT' | 'WRITE_BODY_FAT' | 'READ_FLOORS_CLIMBED' | 'READ_SLEEP' | 'READ_EXERCISE_TIME' | 'READ_BIOLOGICAL_SEX' | 'READ_BLOOD_TYPE' | 'READ_DATE_OF_BIRTH' | 'READ_FITZPATRICK_SKIN_TYPE' | 'READ_WHEELCHAIR_USE'</code>
|
|
@@ -725,6 +765,11 @@ Construct a type with a set of properties K of type T
|
|
|
725
765
|
<code>'wheelchair_user' | 'not_wheelchair_user' | 'not_set' | 'unknown'</code>
|
|
726
766
|
|
|
727
767
|
|
|
768
|
+
#### CharacteristicField
|
|
769
|
+
|
|
770
|
+
<code>'biologicalSex' | 'bloodType' | 'dateOfBirth' | 'fitzpatrickSkinType' | 'wheelchairUse'</code>
|
|
771
|
+
|
|
772
|
+
|
|
728
773
|
#### LatestDataType
|
|
729
774
|
|
|
730
775
|
<code>'steps' | 'active-calories' | 'total-calories' | 'basal-calories' | 'distance' | 'weight' | 'height' | 'heart-rate' | 'resting-heart-rate' | 'respiratory-rate' | 'oxygen-saturation' | 'blood-glucose' | 'body-temperature' | 'basal-body-temperature' | 'body-fat' | 'flights-climbed' | 'exercise-time' | 'distance-cycling' | 'mindfulness' | 'sleep' | 'sleep-rem' | 'hrv' | 'blood-pressure'</code>
|
|
@@ -9,7 +9,12 @@ export interface HealthPlugin {
|
|
|
9
9
|
available: boolean;
|
|
10
10
|
}>;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Returns whether each permission is granted.
|
|
13
|
+
* Android: Uses Health Connect grant state.
|
|
14
|
+
* iOS: Write permissions are strict. Read permissions for sample types are optimistic because HealthKit does not
|
|
15
|
+
* distinguish denied vs no data; this returns false only when the read permission is not determined. For
|
|
16
|
+
* characteristics, this probes access and returns false when denied.
|
|
17
|
+
* UX tip: If this returns true but a query yields no results, show a hint to check Apple Health settings.
|
|
13
18
|
* @param permissions permissions to query
|
|
14
19
|
*/
|
|
15
20
|
checkHealthPermissions(permissions: PermissionsRequest): Promise<PermissionResponse>;
|
|
@@ -34,10 +39,12 @@ export interface HealthPlugin {
|
|
|
34
39
|
openAppleHealthSettings(): Promise<void>;
|
|
35
40
|
/**
|
|
36
41
|
* Opens the Google Health Connect app
|
|
42
|
+
* iOS: Aliases openAppleHealthSettings().
|
|
37
43
|
*/
|
|
38
44
|
openHealthConnectSettings(): Promise<void>;
|
|
39
45
|
/**
|
|
40
46
|
* Opens the Google Health Connect app in PlayStore
|
|
47
|
+
* iOS: Resolves without action.
|
|
41
48
|
*/
|
|
42
49
|
showHealthConnectInPlayStore(): Promise<void>;
|
|
43
50
|
/**
|
|
@@ -74,18 +81,22 @@ export interface HealthPlugin {
|
|
|
74
81
|
}): Promise<QueryLatestSampleResponse>;
|
|
75
82
|
/**
|
|
76
83
|
* Query latest weight sample
|
|
84
|
+
* Convenience wrapper around queryLatestSample({ dataType: 'weight' }).
|
|
77
85
|
*/
|
|
78
86
|
queryWeight(): Promise<QueryLatestSampleResponse>;
|
|
79
87
|
/**
|
|
80
88
|
* Query latest height sample
|
|
89
|
+
* Convenience wrapper around queryLatestSample({ dataType: 'height' }).
|
|
81
90
|
*/
|
|
82
91
|
queryHeight(): Promise<QueryLatestSampleResponse>;
|
|
83
92
|
/**
|
|
84
93
|
* Query latest heart rate sample
|
|
94
|
+
* Convenience wrapper around queryLatestSample({ dataType: 'heart-rate' }).
|
|
85
95
|
*/
|
|
86
96
|
queryHeartRate(): Promise<QueryLatestSampleResponse>;
|
|
87
97
|
/**
|
|
88
98
|
* Query latest steps sample
|
|
99
|
+
* Convenience wrapper around queryLatestSample({ dataType: 'steps' }).
|
|
89
100
|
*/
|
|
90
101
|
querySteps(): Promise<QueryLatestSampleResponse>;
|
|
91
102
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface HealthPlugin {\n /**\n * Checks if health API is available.\n * Android: If false is returned, the Google Health Connect app is probably not installed.\n * See showHealthConnectInPlayStore()\n *\n */\n isHealthAvailable(): Promise<{ available: boolean }>;\n\n /**\n * Android only: Returns for each given permission, if it was granted by the underlying health API\n * @param permissions permissions to query\n */\n checkHealthPermissions(permissions: PermissionsRequest): Promise<PermissionResponse>;\n\n /**\n * Requests the permissions from the user.\n *\n * Android: Apps can ask only a few times for permissions, after that the user has to grant them manually in\n * the Health Connect app. See openHealthConnectSettings()\n *\n * iOS: If the permissions are already granted or denied, this method will just return without asking the user. In iOS\n * we can't really detect if a user granted or denied a permission. The return value reflects the assumption that all\n * permissions were granted.\n *\n * @param permissions permissions to request\n */\n requestHealthPermissions(permissions: PermissionsRequest): Promise<PermissionResponse>;\n\n /**\n * Opens the apps settings, which is kind of wrong, because health permissions are configured under:\n * Settings > Apps > (Apple) Health > Access and Devices > [app-name]\n * But we can't go there directly.\n */\n openAppleHealthSettings(): Promise<void>;\n\n /**\n * Opens the Google Health Connect app\n */\n openHealthConnectSettings(): Promise<void>;\n\n /**\n * Opens the Google Health Connect app in PlayStore\n */\n showHealthConnectInPlayStore(): Promise<void>;\n\n /**\n * iOS only: Reads user characteristics such as biological sex, blood type, date of birth, Fitzpatrick skin type, and wheelchair use.\n * Values are null when unavailable or permission was not granted. Android does not expose these characteristics; it returns `platformSupported: false` and a `platformMessage` for UI hints without emitting null values.\n *\n * Passing `fields` lets you request only specific characteristics (e.g., date of birth) to keep permissions scoped narrowly. Defaults to all characteristics when omitted.\n */\n getCharacteristics(request?: CharacteristicsRequest): Promise<CharacteristicsResponse>;\n\n /**\n * Query aggregated data\n * - Blood-pressure aggregates return the systolic average in `value` plus `systolic`, `diastolic`, and `unit`.\n * - `total-calories` is derived as active + basal energy on both iOS and Android for latest samples, aggregated queries, and workouts. We fall back to the platform's total‑calories metric (or active calories) when basal data isn't available or permission is missing. Request both `READ_ACTIVE_CALORIES` and `READ_BASAL_CALORIES` for full totals.\n * - Weight/height aggregation returns the latest sample per day (no averaging).\n * - Android aggregation currently supports daily buckets; unsupported buckets will be rejected.\n * - Android `distance-cycling` aggregates distance recorded during biking exercise sessions (requires distance + workouts permissions).\n * - Daily `bucket: \"day\"` queries use calendar-day boundaries in the device time zone (start-of-day through the next start-of-day) instead of a trailing 24-hour window. For “today,” send `startDate` at today’s start-of-day and `endDate` at now or tomorrow’s start-of-day.\n * @param request\n */\n queryAggregated(request: QueryAggregatedRequest): Promise<QueryAggregatedResponse>;\n\n /**\n * Query workouts\n * @param request\n */\n queryWorkouts(request: QueryWorkoutRequest): Promise<QueryWorkoutResponse>;\n\n /**\n * Query latest sample for a specific data type\n * - Latest sleep sample returns the most recent complete sleep session (asleep states only) from the last ~36 hours; if a longer overnight session exists, shorter naps are ignored.\n * - `sleep-rem` returns REM duration (minutes) for the latest sleep session; requires iOS 16+ sleep stages and Health Connect REM data on Android.\n * @param request\n */\n queryLatestSample(request: { dataType: LatestDataType }): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest weight sample\n */\n queryWeight(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest height sample\n */\n queryHeight(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest heart rate sample\n */\n queryHeartRate(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest steps sample\n */\n querySteps(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Create a workout session with optional totals and route/heart-rate samples.\n * - iOS stores an `HKWorkout` (activityType mapped from `activityType`) with total energy/distance and optional metadata/route/heart-rate samples.\n * - Android stores an `ExerciseSessionRecord` plus `ActiveCaloriesBurnedRecord`, `DistanceRecord`, and `HeartRateRecord` when provided. Routes are attached via `ExerciseRoute`.\n * - Requires matching WRITE_* permissions for the values you include (e.g., WRITE_WORKOUTS + WRITE_ACTIVE_CALORIES + WRITE_DISTANCE + WRITE_HEART_RATE + WRITE_ROUTE).\n */\n saveWorkout(request: SaveWorkoutRequest): Promise<SaveWorkoutResponse>;\n\n /**\n * Save user-provided body metrics to the health platform.\n */\n saveMetrics(request: SaveMetricsRequest): Promise<SaveMetricsResponse>;\n}\n\nexport declare type HealthPermission = 'READ_STEPS' | 'READ_WORKOUTS' | 'WRITE_WORKOUTS' | 'READ_ACTIVE_CALORIES' | 'WRITE_ACTIVE_CALORIES' | 'READ_TOTAL_CALORIES' | 'WRITE_TOTAL_CALORIES' | 'READ_DISTANCE' | 'WRITE_DISTANCE' | 'READ_WEIGHT' | 'WRITE_WEIGHT' | 'READ_HEIGHT' | 'WRITE_HEIGHT' | 'READ_HEART_RATE' | 'WRITE_HEART_RATE' | 'READ_RESTING_HEART_RATE' | 'WRITE_RESTING_HEART_RATE' | 'READ_ROUTE' | 'WRITE_ROUTE' | 'READ_MINDFULNESS' | 'READ_HRV' | 'READ_BLOOD_PRESSURE' | 'READ_BASAL_CALORIES' | 'READ_RESPIRATORY_RATE' | 'READ_OXYGEN_SATURATION' | 'READ_BLOOD_GLUCOSE' | 'READ_BODY_TEMPERATURE' | 'READ_BASAL_BODY_TEMPERATURE' | 'READ_BODY_FAT' | 'WRITE_BODY_FAT' | 'READ_FLOORS_CLIMBED' | 'READ_SLEEP' | 'READ_EXERCISE_TIME' | 'READ_BIOLOGICAL_SEX' | 'READ_BLOOD_TYPE' | 'READ_DATE_OF_BIRTH' | 'READ_FITZPATRICK_SKIN_TYPE' | 'READ_WHEELCHAIR_USE';\n\nexport type LatestDataType =\n | 'steps'\n | 'active-calories'\n | 'total-calories'\n | 'basal-calories'\n | 'distance'\n | 'weight'\n | 'height'\n | 'heart-rate'\n | 'resting-heart-rate'\n | 'respiratory-rate'\n | 'oxygen-saturation'\n | 'blood-glucose'\n | 'body-temperature'\n | 'basal-body-temperature'\n | 'body-fat'\n | 'flights-climbed'\n | 'exercise-time'\n | 'distance-cycling'\n | 'mindfulness'\n | 'sleep'\n | 'sleep-rem'\n | 'hrv'\n | 'blood-pressure';\n\nexport interface PermissionsRequest {\n permissions: HealthPermission[];\n}\n\nexport interface PermissionResponse {\n permissions: Record<HealthPermission, boolean>;\n}\n\nexport interface QueryWorkoutRequest {\n startDate: string;\n endDate: string;\n includeHeartRate: boolean;\n includeRoute: boolean;\n includeSteps: boolean;\n}\n\nexport interface HeartRateSample {\n timestamp: string;\n bpm: number;\n}\n\nexport interface RouteSample {\n timestamp: string;\n lat: number;\n lng: number;\n alt?: number;\n}\n\nexport interface QueryWorkoutResponse {\n workouts: Workout[];\n}\n\nexport type WorkoutActivityType =\n | 'rock-climbing'\n | 'climbing'\n | 'hiking'\n | 'running'\n | 'walking'\n | 'cycling'\n | 'biking'\n | 'strength-training'\n | 'yoga'\n | 'other';\n\nexport interface SaveWorkoutRequest {\n activityType: WorkoutActivityType;\n startDate: string;\n endDate: string;\n calories?: number;\n distance?: number;\n metadata?: Record<string, any>;\n route?: RouteSample[];\n heartRateSamples?: HeartRateSample[];\n}\n\nexport interface SaveWorkoutResponse {\n success: boolean;\n id?: string;\n}\n\nexport interface SaveMetricsRequest {\n weightKg?: number;\n heightCm?: number;\n bodyFatPercent?: number;\n restingHeartRate?: number;\n}\n\nexport interface SaveMetricsResponse {\n success: boolean;\n inserted?: number;\n}\n\nexport interface Workout {\n startDate: string;\n endDate: string;\n workoutType: string;\n sourceName: string;\n id?: string;\n duration: number;\n distance?: number;\n steps?: number;\n calories: number;\n sourceBundleId: string;\n route?: RouteSample[];\n heartRate?: HeartRateSample[];\n}\n\nexport interface QueryAggregatedRequest {\n startDate: string;\n endDate: string;\n dataType:\n | 'steps'\n | 'active-calories'\n | 'total-calories'\n | 'basal-calories'\n | 'distance'\n | 'weight'\n | 'height'\n | 'heart-rate'\n | 'resting-heart-rate'\n | 'respiratory-rate'\n | 'oxygen-saturation'\n | 'blood-glucose'\n | 'body-temperature'\n | 'basal-body-temperature'\n | 'body-fat'\n | 'flights-climbed'\n | 'exercise-time'\n | 'distance-cycling'\n | 'sleep'\n | 'mindfulness'\n | 'hrv'\n | 'blood-pressure';\n bucket: string;\n}\n\nexport interface QueryAggregatedResponse {\n aggregatedData: AggregatedSample[];\n}\n\nexport interface AggregatedSample {\n startDate: string;\n endDate: string;\n value: number;\n systolic?: number;\n diastolic?: number;\n unit?: string;\n}\n\nexport interface QueryLatestSampleResponse {\n value?: number;\n systolic?: number;\n diastolic?: number;\n timestamp: number;\n endTimestamp?: number;\n unit: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface CharacteristicsResponse {\n biologicalSex?: HealthBiologicalSex | null;\n bloodType?: HealthBloodType | null;\n dateOfBirth?: string | null;\n fitzpatrickSkinType?: HealthFitzpatrickSkinType | null;\n wheelchairUse?: HealthWheelchairUse | null;\n /**\n * Indicates whether the platform exposes these characteristics via the plugin (true on iOS, false on Android).\n */\n platformSupported?: boolean;\n /**\n * Optional platform-specific message; on Android we return a user-facing note explaining that values remain empty unless synced from iOS.\n */\n platformMessage?: string;\n}\n\nexport type CharacteristicField =\n | 'biologicalSex'\n | 'bloodType'\n | 'dateOfBirth'\n | 'fitzpatrickSkinType'\n | 'wheelchairUse';\n\nexport interface CharacteristicsRequest {\n /**\n * Characteristics to query. Defaults to all characteristics when omitted.\n */\n fields?: CharacteristicField[];\n}\n\nexport type HealthBiologicalSex = 'female' | 'male' | 'other' | 'not_set' | 'unknown';\n\nexport type HealthBloodType =\n | 'a-positive'\n | 'a-negative'\n | 'b-positive'\n | 'b-negative'\n | 'ab-positive'\n | 'ab-negative'\n | 'o-positive'\n | 'o-negative'\n | 'not_set'\n | 'unknown';\n\nexport type HealthFitzpatrickSkinType = 'type1' | 'type2' | 'type3' | 'type4' | 'type5' | 'type6' | 'not_set' | 'unknown';\n\nexport type HealthWheelchairUse = 'wheelchair_user' | 'not_wheelchair_user' | 'not_set' | 'unknown';\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface HealthPlugin {\n /**\n * Checks if health API is available.\n * Android: If false is returned, the Google Health Connect app is probably not installed.\n * See showHealthConnectInPlayStore()\n *\n */\n isHealthAvailable(): Promise<{ available: boolean }>;\n\n /**\n * Returns whether each permission is granted.\n * Android: Uses Health Connect grant state.\n * iOS: Write permissions are strict. Read permissions for sample types are optimistic because HealthKit does not\n * distinguish denied vs no data; this returns false only when the read permission is not determined. For\n * characteristics, this probes access and returns false when denied.\n * UX tip: If this returns true but a query yields no results, show a hint to check Apple Health settings.\n * @param permissions permissions to query\n */\n checkHealthPermissions(permissions: PermissionsRequest): Promise<PermissionResponse>;\n\n /**\n * Requests the permissions from the user.\n *\n * Android: Apps can ask only a few times for permissions, after that the user has to grant them manually in\n * the Health Connect app. See openHealthConnectSettings()\n *\n * iOS: If the permissions are already granted or denied, this method will just return without asking the user. In iOS\n * we can't really detect if a user granted or denied a permission. The return value reflects the assumption that all\n * permissions were granted.\n *\n * @param permissions permissions to request\n */\n requestHealthPermissions(permissions: PermissionsRequest): Promise<PermissionResponse>;\n\n /**\n * Opens the apps settings, which is kind of wrong, because health permissions are configured under:\n * Settings > Apps > (Apple) Health > Access and Devices > [app-name]\n * But we can't go there directly.\n */\n openAppleHealthSettings(): Promise<void>;\n\n /**\n * Opens the Google Health Connect app\n * iOS: Aliases openAppleHealthSettings().\n */\n openHealthConnectSettings(): Promise<void>;\n\n /**\n * Opens the Google Health Connect app in PlayStore\n * iOS: Resolves without action.\n */\n showHealthConnectInPlayStore(): Promise<void>;\n\n /**\n * iOS only: Reads user characteristics such as biological sex, blood type, date of birth, Fitzpatrick skin type, and wheelchair use.\n * Values are null when unavailable or permission was not granted. Android does not expose these characteristics; it returns `platformSupported: false` and a `platformMessage` for UI hints without emitting null values.\n *\n * Passing `fields` lets you request only specific characteristics (e.g., date of birth) to keep permissions scoped narrowly. Defaults to all characteristics when omitted.\n */\n getCharacteristics(request?: CharacteristicsRequest): Promise<CharacteristicsResponse>;\n\n /**\n * Query aggregated data\n * - Blood-pressure aggregates return the systolic average in `value` plus `systolic`, `diastolic`, and `unit`.\n * - `total-calories` is derived as active + basal energy on both iOS and Android for latest samples, aggregated queries, and workouts. We fall back to the platform's total‑calories metric (or active calories) when basal data isn't available or permission is missing. Request both `READ_ACTIVE_CALORIES` and `READ_BASAL_CALORIES` for full totals.\n * - Weight/height aggregation returns the latest sample per day (no averaging).\n * - Android aggregation currently supports daily buckets; unsupported buckets will be rejected.\n * - Android `distance-cycling` aggregates distance recorded during biking exercise sessions (requires distance + workouts permissions).\n * - Daily `bucket: \"day\"` queries use calendar-day boundaries in the device time zone (start-of-day through the next start-of-day) instead of a trailing 24-hour window. For “today,” send `startDate` at today’s start-of-day and `endDate` at now or tomorrow’s start-of-day.\n * @param request\n */\n queryAggregated(request: QueryAggregatedRequest): Promise<QueryAggregatedResponse>;\n\n /**\n * Query workouts\n * @param request\n */\n queryWorkouts(request: QueryWorkoutRequest): Promise<QueryWorkoutResponse>;\n\n /**\n * Query latest sample for a specific data type\n * - Latest sleep sample returns the most recent complete sleep session (asleep states only) from the last ~36 hours; if a longer overnight session exists, shorter naps are ignored.\n * - `sleep-rem` returns REM duration (minutes) for the latest sleep session; requires iOS 16+ sleep stages and Health Connect REM data on Android.\n * @param request\n */\n queryLatestSample(request: { dataType: LatestDataType }): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest weight sample\n * Convenience wrapper around queryLatestSample({ dataType: 'weight' }).\n */\n queryWeight(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest height sample\n * Convenience wrapper around queryLatestSample({ dataType: 'height' }).\n */\n queryHeight(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest heart rate sample\n * Convenience wrapper around queryLatestSample({ dataType: 'heart-rate' }).\n */\n queryHeartRate(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest steps sample\n * Convenience wrapper around queryLatestSample({ dataType: 'steps' }).\n */\n querySteps(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Create a workout session with optional totals and route/heart-rate samples.\n * - iOS stores an `HKWorkout` (activityType mapped from `activityType`) with total energy/distance and optional metadata/route/heart-rate samples.\n * - Android stores an `ExerciseSessionRecord` plus `ActiveCaloriesBurnedRecord`, `DistanceRecord`, and `HeartRateRecord` when provided. Routes are attached via `ExerciseRoute`.\n * - Requires matching WRITE_* permissions for the values you include (e.g., WRITE_WORKOUTS + WRITE_ACTIVE_CALORIES + WRITE_DISTANCE + WRITE_HEART_RATE + WRITE_ROUTE).\n */\n saveWorkout(request: SaveWorkoutRequest): Promise<SaveWorkoutResponse>;\n\n /**\n * Save user-provided body metrics to the health platform.\n */\n saveMetrics(request: SaveMetricsRequest): Promise<SaveMetricsResponse>;\n}\n\nexport declare type HealthPermission = 'READ_STEPS' | 'READ_WORKOUTS' | 'WRITE_WORKOUTS' | 'READ_ACTIVE_CALORIES' | 'WRITE_ACTIVE_CALORIES' | 'READ_TOTAL_CALORIES' | 'WRITE_TOTAL_CALORIES' | 'READ_DISTANCE' | 'WRITE_DISTANCE' | 'READ_WEIGHT' | 'WRITE_WEIGHT' | 'READ_HEIGHT' | 'WRITE_HEIGHT' | 'READ_HEART_RATE' | 'WRITE_HEART_RATE' | 'READ_RESTING_HEART_RATE' | 'WRITE_RESTING_HEART_RATE' | 'READ_ROUTE' | 'WRITE_ROUTE' | 'READ_MINDFULNESS' | 'READ_HRV' | 'READ_BLOOD_PRESSURE' | 'READ_BASAL_CALORIES' | 'READ_RESPIRATORY_RATE' | 'READ_OXYGEN_SATURATION' | 'READ_BLOOD_GLUCOSE' | 'READ_BODY_TEMPERATURE' | 'READ_BASAL_BODY_TEMPERATURE' | 'READ_BODY_FAT' | 'WRITE_BODY_FAT' | 'READ_FLOORS_CLIMBED' | 'READ_SLEEP' | 'READ_EXERCISE_TIME' | 'READ_BIOLOGICAL_SEX' | 'READ_BLOOD_TYPE' | 'READ_DATE_OF_BIRTH' | 'READ_FITZPATRICK_SKIN_TYPE' | 'READ_WHEELCHAIR_USE';\n\nexport type LatestDataType =\n | 'steps'\n | 'active-calories'\n | 'total-calories'\n | 'basal-calories'\n | 'distance'\n | 'weight'\n | 'height'\n | 'heart-rate'\n | 'resting-heart-rate'\n | 'respiratory-rate'\n | 'oxygen-saturation'\n | 'blood-glucose'\n | 'body-temperature'\n | 'basal-body-temperature'\n | 'body-fat'\n | 'flights-climbed'\n | 'exercise-time'\n | 'distance-cycling'\n | 'mindfulness'\n | 'sleep'\n | 'sleep-rem'\n | 'hrv'\n | 'blood-pressure';\n\nexport interface PermissionsRequest {\n permissions: HealthPermission[];\n}\n\nexport interface PermissionResponse {\n permissions: Record<HealthPermission, boolean>;\n}\n\nexport interface QueryWorkoutRequest {\n startDate: string;\n endDate: string;\n includeHeartRate: boolean;\n includeRoute: boolean;\n includeSteps: boolean;\n}\n\nexport interface HeartRateSample {\n timestamp: string;\n bpm: number;\n}\n\nexport interface RouteSample {\n timestamp: string;\n lat: number;\n lng: number;\n alt?: number;\n}\n\nexport interface QueryWorkoutResponse {\n workouts: Workout[];\n}\n\nexport type WorkoutActivityType =\n | 'rock-climbing'\n | 'climbing'\n | 'hiking'\n | 'running'\n | 'walking'\n | 'cycling'\n | 'biking'\n | 'strength-training'\n | 'yoga'\n | 'other';\n\nexport interface SaveWorkoutRequest {\n activityType: WorkoutActivityType;\n startDate: string;\n endDate: string;\n calories?: number;\n distance?: number;\n metadata?: Record<string, any>;\n route?: RouteSample[];\n heartRateSamples?: HeartRateSample[];\n}\n\nexport interface SaveWorkoutResponse {\n success: boolean;\n id?: string;\n}\n\nexport interface SaveMetricsRequest {\n weightKg?: number;\n heightCm?: number;\n bodyFatPercent?: number;\n restingHeartRate?: number;\n}\n\nexport interface SaveMetricsResponse {\n success: boolean;\n inserted?: number;\n}\n\nexport interface Workout {\n startDate: string;\n endDate: string;\n workoutType: string;\n sourceName: string;\n id?: string;\n duration: number;\n distance?: number;\n steps?: number;\n calories: number;\n sourceBundleId: string;\n route?: RouteSample[];\n heartRate?: HeartRateSample[];\n}\n\nexport interface QueryAggregatedRequest {\n startDate: string;\n endDate: string;\n dataType:\n | 'steps'\n | 'active-calories'\n | 'total-calories'\n | 'basal-calories'\n | 'distance'\n | 'weight'\n | 'height'\n | 'heart-rate'\n | 'resting-heart-rate'\n | 'respiratory-rate'\n | 'oxygen-saturation'\n | 'blood-glucose'\n | 'body-temperature'\n | 'basal-body-temperature'\n | 'body-fat'\n | 'flights-climbed'\n | 'exercise-time'\n | 'distance-cycling'\n | 'sleep'\n | 'mindfulness'\n | 'hrv'\n | 'blood-pressure';\n bucket: string;\n}\n\nexport interface QueryAggregatedResponse {\n aggregatedData: AggregatedSample[];\n}\n\nexport interface AggregatedSample {\n startDate: string;\n endDate: string;\n value: number;\n systolic?: number;\n diastolic?: number;\n unit?: string;\n}\n\nexport interface QueryLatestSampleResponse {\n value?: number;\n systolic?: number;\n diastolic?: number;\n timestamp: number;\n endTimestamp?: number;\n unit: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface CharacteristicsResponse {\n biologicalSex?: HealthBiologicalSex | null;\n bloodType?: HealthBloodType | null;\n dateOfBirth?: string | null;\n fitzpatrickSkinType?: HealthFitzpatrickSkinType | null;\n wheelchairUse?: HealthWheelchairUse | null;\n /**\n * Indicates whether the platform exposes these characteristics via the plugin (true on iOS, false on Android).\n */\n platformSupported?: boolean;\n /**\n * Optional platform-specific message; on Android we return a user-facing note explaining that values remain empty unless synced from iOS.\n */\n platformMessage?: string;\n}\n\nexport type CharacteristicField =\n | 'biologicalSex'\n | 'bloodType'\n | 'dateOfBirth'\n | 'fitzpatrickSkinType'\n | 'wheelchairUse';\n\nexport interface CharacteristicsRequest {\n /**\n * Characteristics to query. Defaults to all characteristics when omitted.\n */\n fields?: CharacteristicField[];\n}\n\nexport type HealthBiologicalSex = 'female' | 'male' | 'other' | 'not_set' | 'unknown';\n\nexport type HealthBloodType =\n | 'a-positive'\n | 'a-negative'\n | 'b-positive'\n | 'b-negative'\n | 'ab-positive'\n | 'ab-negative'\n | 'o-positive'\n | 'o-negative'\n | 'not_set'\n | 'unknown';\n\nexport type HealthFitzpatrickSkinType = 'type1' | 'type2' | 'type3' | 'type4' | 'type5' | 'type6' | 'not_set' | 'unknown';\n\nexport type HealthWheelchairUse = 'wheelchair_user' | 'not_wheelchair_user' | 'not_set' | 'unknown';\n"]}
|
|
@@ -17,9 +17,15 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
17
17
|
CAPPluginMethod(name: "checkHealthPermissions", returnType: CAPPluginReturnPromise),
|
|
18
18
|
CAPPluginMethod(name: "requestHealthPermissions", returnType: CAPPluginReturnPromise),
|
|
19
19
|
CAPPluginMethod(name: "openAppleHealthSettings", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "openHealthConnectSettings", returnType: CAPPluginReturnPromise),
|
|
21
|
+
CAPPluginMethod(name: "showHealthConnectInPlayStore", returnType: CAPPluginReturnPromise),
|
|
20
22
|
CAPPluginMethod(name: "queryAggregated", returnType: CAPPluginReturnPromise),
|
|
21
23
|
CAPPluginMethod(name: "queryWorkouts", returnType: CAPPluginReturnPromise),
|
|
22
24
|
CAPPluginMethod(name: "queryLatestSample", returnType: CAPPluginReturnPromise),
|
|
25
|
+
CAPPluginMethod(name: "queryWeight", returnType: CAPPluginReturnPromise),
|
|
26
|
+
CAPPluginMethod(name: "queryHeight", returnType: CAPPluginReturnPromise),
|
|
27
|
+
CAPPluginMethod(name: "queryHeartRate", returnType: CAPPluginReturnPromise),
|
|
28
|
+
CAPPluginMethod(name: "querySteps", returnType: CAPPluginReturnPromise),
|
|
23
29
|
CAPPluginMethod(name: "getCharacteristics", returnType: CAPPluginReturnPromise),
|
|
24
30
|
CAPPluginMethod(name: "saveMetrics", returnType: CAPPluginReturnPromise),
|
|
25
31
|
CAPPluginMethod(name: "saveWorkout", returnType: CAPPluginReturnPromise)
|
|
@@ -64,28 +70,94 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
64
70
|
return
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
var result: [String:
|
|
73
|
+
var result: [String: Bool] = [:]
|
|
68
74
|
|
|
69
75
|
for permission in permissions {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
// Resolve Mappings
|
|
77
|
+
let readTypes = permissionToHKObjectType(permission)
|
|
78
|
+
let shareTypes = permissionToHKSampleType(permission)
|
|
79
|
+
|
|
80
|
+
// Prevent invalid/typo permissions from defaulting to "true"
|
|
81
|
+
if readTypes.isEmpty && shareTypes.isEmpty {
|
|
82
|
+
print("⚡️ [HealthPlugin] Warning: Permission '\(permission)' is not mapped to any HealthKit types.")
|
|
83
|
+
result[permission] = false
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
// Determine Intent (Write vs Read)
|
|
88
|
+
// If the permission maps to any shareable types, treat it as a Write request.
|
|
89
|
+
let isWriteRequest = !shareTypes.isEmpty
|
|
90
|
+
|
|
91
|
+
var isGranted = true
|
|
92
|
+
|
|
93
|
+
if isWriteRequest {
|
|
94
|
+
// WRITE LOGIC: Strict
|
|
95
|
+
// Multi-type permissions must be All-or-Nothing for data integrity.
|
|
96
|
+
for type in shareTypes {
|
|
97
|
+
if healthStore.authorizationStatus(for: type) != .sharingAuthorized {
|
|
98
|
+
isGranted = false
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// READ LOGIC: Hybrid (Strict Characteristic / Optimistic Sample)
|
|
104
|
+
for type in readTypes {
|
|
105
|
+
if let charType = type as? HKCharacteristicType {
|
|
106
|
+
// Characteristics: Strict Probe
|
|
107
|
+
// We strictly check these because they throw explicit errors on denial.
|
|
108
|
+
if !isCharacteristicAuthorized(charType) {
|
|
109
|
+
isGranted = false
|
|
110
|
+
break
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
// Sample Types: Optimistic Check
|
|
114
|
+
// Strategy:
|
|
115
|
+
// - If .notDetermined: We definitely need to ask. Return false.
|
|
116
|
+
// - If .sharingDenied: User explicitly denied OR it's a read-only masking.
|
|
117
|
+
// We MUST return 'true' here. If we return 'false', the app will loop
|
|
118
|
+
// requests forever because we cannot distinguish Denied from "No Data".
|
|
119
|
+
if healthStore.authorizationStatus(for: type) == .notDetermined {
|
|
120
|
+
isGranted = false
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
}
|
|
83
124
|
}
|
|
84
125
|
}
|
|
126
|
+
|
|
127
|
+
result[permission] = isGranted
|
|
85
128
|
}
|
|
86
129
|
|
|
87
130
|
call.resolve(["permissions": result])
|
|
88
131
|
}
|
|
132
|
+
|
|
133
|
+
// Helper: Accurately checks Characteristic read access by attempting a fetch.
|
|
134
|
+
private func isCharacteristicAuthorized(_ type: HKCharacteristicType) -> Bool {
|
|
135
|
+
do {
|
|
136
|
+
switch type.identifier {
|
|
137
|
+
case HKCharacteristicTypeIdentifier.biologicalSex.rawValue:
|
|
138
|
+
_ = try healthStore.biologicalSex()
|
|
139
|
+
case HKCharacteristicTypeIdentifier.bloodType.rawValue:
|
|
140
|
+
_ = try healthStore.bloodType()
|
|
141
|
+
case HKCharacteristicTypeIdentifier.dateOfBirth.rawValue:
|
|
142
|
+
_ = try healthStore.dateOfBirthComponents()
|
|
143
|
+
case HKCharacteristicTypeIdentifier.fitzpatrickSkinType.rawValue:
|
|
144
|
+
_ = try healthStore.fitzpatrickSkinType()
|
|
145
|
+
case HKCharacteristicTypeIdentifier.wheelchairUse.rawValue:
|
|
146
|
+
_ = try healthStore.wheelchairUse()
|
|
147
|
+
default:
|
|
148
|
+
// Unknown characteristic identifiers should default to false to surface errors.
|
|
149
|
+
print("⚡️ [HealthPlugin] Warning: Unknown characteristic type probed: \(type.identifier)")
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
return true
|
|
153
|
+
} catch let error as HKError {
|
|
154
|
+
// If the system explicitly says "Authorization Denied", we know for sure.
|
|
155
|
+
return error.code != .errorAuthorizationDenied
|
|
156
|
+
} catch {
|
|
157
|
+
// Other errors (device not supported, etc) implies we can't read it.
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
}
|
|
89
161
|
|
|
90
162
|
@objc func requestHealthPermissions(_ call: CAPPluginCall) {
|
|
91
163
|
guard let permissions = call.getArray("permissions") as? [String] else {
|
|
@@ -264,7 +336,27 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
264
336
|
call.reject("Missing data type")
|
|
265
337
|
return
|
|
266
338
|
}
|
|
267
|
-
|
|
339
|
+
|
|
340
|
+
queryLatestSample(for: dataTypeString, call: call)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
@objc func queryWeight(_ call: CAPPluginCall) {
|
|
344
|
+
queryLatestSample(for: "weight", call: call)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
@objc func queryHeight(_ call: CAPPluginCall) {
|
|
348
|
+
queryLatestSample(for: "height", call: call)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@objc func queryHeartRate(_ call: CAPPluginCall) {
|
|
352
|
+
queryLatestSample(for: "heart-rate", call: call)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
@objc func querySteps(_ call: CAPPluginCall) {
|
|
356
|
+
queryLatestSample(for: "steps", call: call)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private func queryLatestSample(for dataTypeString: String, call: CAPPluginCall) {
|
|
268
360
|
print("⚡️ [HealthPlugin] Querying latest sample for data type: \(dataTypeString)")
|
|
269
361
|
// ---- Special handling for blood‑pressure correlation ----
|
|
270
362
|
if dataTypeString == "blood-pressure" {
|
|
@@ -670,6 +762,14 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
670
762
|
healthStore.execute(query)
|
|
671
763
|
}
|
|
672
764
|
|
|
765
|
+
@objc func openHealthConnectSettings(_ call: CAPPluginCall) {
|
|
766
|
+
openAppleHealthSettings(call)
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
@objc func showHealthConnectInPlayStore(_ call: CAPPluginCall) {
|
|
770
|
+
call.resolve()
|
|
771
|
+
}
|
|
772
|
+
|
|
673
773
|
@objc func openAppleHealthSettings(_ call: CAPPluginCall) {
|
|
674
774
|
if let url = URL(string: UIApplication.openSettingsURLString) {
|
|
675
775
|
DispatchQueue.main.async {
|
package/package.json
CHANGED