@flomentumsolutions/capacitor-health-extended 0.4.5 → 0.6.1
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
|
@@ -28,12 +28,14 @@ Thanks [@mley](https://github.com/mley) for the ground work. The goal of this fo
|
|
|
28
28
|
- Query aggregated data like steps or calories
|
|
29
29
|
- Retrieve workout sessions with optional route and heart rate data
|
|
30
30
|
- Fetch the latest samples for steps, distance (incl. cycling), calories (active/total/basal), heart‑rate, resting HR, HRV, respiratory rate, blood pressure, oxygen saturation, blood glucose, body temperature (basal + core), body fat, height, weight, flights climbed, sleep, and exercise time.
|
|
31
|
+
- Read profile characteristics on iOS: biological sex, blood type, date of birth, Fitzpatrick skin type, wheelchair use.
|
|
31
32
|
|
|
32
33
|
### Supported data types (parity iOS + Android)
|
|
33
34
|
- Activity: steps, distance, distance‑cycling, exercise time (Apple Exercise Time), workouts (with routes/steps/calories), flights climbed
|
|
34
35
|
- Energy: active calories, total calories, basal calories
|
|
35
36
|
- Vitals: heart rate, resting heart rate, HRV, respiratory rate, blood pressure, oxygen saturation, blood glucose, body temperature, basal body temperature
|
|
36
37
|
- Body: weight, height, body fat
|
|
38
|
+
- Characteristics (iOS): biological sex, blood type, date of birth, Fitzpatrick skin type, wheelchair use
|
|
37
39
|
- Sessions: mindfulness, sleep
|
|
38
40
|
|
|
39
41
|
## Install
|
|
@@ -213,6 +215,7 @@ This setup ensures your WebView will load HTTPS content securely and complies wi
|
|
|
213
215
|
* [`openAppleHealthSettings()`](#openapplehealthsettings)
|
|
214
216
|
* [`openHealthConnectSettings()`](#openhealthconnectsettings)
|
|
215
217
|
* [`showHealthConnectInPlayStore()`](#showhealthconnectinplaystore)
|
|
218
|
+
* [`getCharacteristics()`](#getcharacteristics)
|
|
216
219
|
* [`queryAggregated(...)`](#queryaggregated)
|
|
217
220
|
* [`queryWorkouts(...)`](#queryworkouts)
|
|
218
221
|
* [`queryLatestSample(...)`](#querylatestsample)
|
|
@@ -319,20 +322,27 @@ Opens the Google Health Connect app in PlayStore
|
|
|
319
322
|
--------------------
|
|
320
323
|
|
|
321
324
|
|
|
322
|
-
###
|
|
325
|
+
### getCharacteristics()
|
|
323
326
|
|
|
324
327
|
```typescript
|
|
325
|
-
|
|
328
|
+
getCharacteristics() => Promise<CharacteristicsResponse>
|
|
326
329
|
```
|
|
327
330
|
|
|
328
|
-
|
|
331
|
+
iOS only: Reads user characteristics such as biological sex, blood type, date of birth, Fitzpatrick skin type, and wheelchair use.
|
|
332
|
+
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.
|
|
329
333
|
|
|
330
|
-
|
|
331
|
-
| ------------- | ------------------------------------------------------------------------- |
|
|
332
|
-
| **`request`** | <code><a href="#queryaggregatedrequest">QueryAggregatedRequest</a></code> |
|
|
334
|
+
**Returns:** <code>Promise<<a href="#characteristicsresponse">CharacteristicsResponse</a>></code>
|
|
333
335
|
|
|
334
|
-
|
|
336
|
+
--------------------
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
### queryAggregated(...)
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
queryAggregated(request: QueryAggregatedRequest) => Promise<QueryAggregatedResponse>
|
|
343
|
+
```
|
|
335
344
|
|
|
345
|
+
Query aggregated data
|
|
336
346
|
- Blood-pressure aggregates return the systolic average in `value` plus `systolic`, `diastolic`, and `unit`.
|
|
337
347
|
- `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.
|
|
338
348
|
- Weight/height aggregation returns the latest sample per day (no averaging).
|
|
@@ -340,6 +350,12 @@ Query aggregated data
|
|
|
340
350
|
- Android `distance-cycling` aggregates distance recorded during biking exercise sessions (requires distance + workouts permissions).
|
|
341
351
|
- 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.
|
|
342
352
|
|
|
353
|
+
| Param | Type |
|
|
354
|
+
| ------------- | ------------------------------------------------------------------------- |
|
|
355
|
+
| **`request`** | <code><a href="#queryaggregatedrequest">QueryAggregatedRequest</a></code> |
|
|
356
|
+
|
|
357
|
+
**Returns:** <code>Promise<<a href="#queryaggregatedresponse">QueryAggregatedResponse</a>></code>
|
|
358
|
+
|
|
343
359
|
--------------------
|
|
344
360
|
|
|
345
361
|
|
|
@@ -363,19 +379,18 @@ Query workouts
|
|
|
363
379
|
### queryLatestSample(...)
|
|
364
380
|
|
|
365
381
|
```typescript
|
|
366
|
-
queryLatestSample(request: { dataType:
|
|
382
|
+
queryLatestSample(request: { dataType: LatestDataType; }) => Promise<QueryLatestSampleResponse>
|
|
367
383
|
```
|
|
368
384
|
|
|
369
385
|
Query latest sample for a specific data type
|
|
386
|
+
- 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.
|
|
370
387
|
|
|
371
|
-
| Param | Type
|
|
372
|
-
| ------------- |
|
|
373
|
-
| **`request`** | <code>{ dataType:
|
|
388
|
+
| Param | Type |
|
|
389
|
+
| ------------- | ------------------------------------------------------------------------ |
|
|
390
|
+
| **`request`** | <code>{ dataType: <a href="#latestdatatype">LatestDataType</a>; }</code> |
|
|
374
391
|
|
|
375
392
|
**Returns:** <code>Promise<<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>></code>
|
|
376
393
|
|
|
377
|
-
- 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.
|
|
378
|
-
|
|
379
394
|
--------------------
|
|
380
395
|
|
|
381
396
|
|
|
@@ -436,9 +451,9 @@ Query latest steps sample
|
|
|
436
451
|
|
|
437
452
|
#### PermissionResponse
|
|
438
453
|
|
|
439
|
-
| Prop | Type
|
|
440
|
-
| ----------------- |
|
|
441
|
-
| **`permissions`** | <code>Record
|
|
454
|
+
| Prop | Type |
|
|
455
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
456
|
+
| **`permissions`** | <code><a href="#record">Record</a><<a href="#healthpermission">HealthPermission</a>, boolean></code> |
|
|
442
457
|
|
|
443
458
|
|
|
444
459
|
#### PermissionsRequest
|
|
@@ -448,6 +463,19 @@ Query latest steps sample
|
|
|
448
463
|
| **`permissions`** | <code>HealthPermission[]</code> |
|
|
449
464
|
|
|
450
465
|
|
|
466
|
+
#### CharacteristicsResponse
|
|
467
|
+
|
|
468
|
+
| Prop | Type | Description |
|
|
469
|
+
| ------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
470
|
+
| **`biologicalSex`** | <code><a href="#healthbiologicalsex">HealthBiologicalSex</a> \| null</code> | |
|
|
471
|
+
| **`bloodType`** | <code><a href="#healthbloodtype">HealthBloodType</a> \| null</code> | |
|
|
472
|
+
| **`dateOfBirth`** | <code>string \| null</code> | |
|
|
473
|
+
| **`fitzpatrickSkinType`** | <code><a href="#healthfitzpatrickskintype">HealthFitzpatrickSkinType</a> \| null</code> | |
|
|
474
|
+
| **`wheelchairUse`** | <code><a href="#healthwheelchairuse">HealthWheelchairUse</a> \| null</code> | |
|
|
475
|
+
| **`platformSupported`** | <code>boolean</code> | Indicates whether the platform exposes these characteristics via the plugin (true on iOS, false on Android). |
|
|
476
|
+
| **`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. |
|
|
477
|
+
|
|
478
|
+
|
|
451
479
|
#### QueryAggregatedResponse
|
|
452
480
|
|
|
453
481
|
| Prop | Type |
|
|
@@ -457,14 +485,14 @@ Query latest steps sample
|
|
|
457
485
|
|
|
458
486
|
#### AggregatedSample
|
|
459
487
|
|
|
460
|
-
| Prop
|
|
461
|
-
|
|
|
462
|
-
| **`startDate`**
|
|
463
|
-
| **`endDate`**
|
|
464
|
-
| **`value`**
|
|
465
|
-
| **`systolic`**
|
|
466
|
-
| **`diastolic`**
|
|
467
|
-
| **`unit`**
|
|
488
|
+
| Prop | Type |
|
|
489
|
+
| --------------- | ------------------- |
|
|
490
|
+
| **`startDate`** | <code>string</code> |
|
|
491
|
+
| **`endDate`** | <code>string</code> |
|
|
492
|
+
| **`value`** | <code>number</code> |
|
|
493
|
+
| **`systolic`** | <code>number</code> |
|
|
494
|
+
| **`diastolic`** | <code>number</code> |
|
|
495
|
+
| **`unit`** | <code>string</code> |
|
|
468
496
|
|
|
469
497
|
|
|
470
498
|
#### QueryAggregatedRequest
|
|
@@ -473,7 +501,7 @@ Query latest steps sample
|
|
|
473
501
|
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
474
502
|
| **`startDate`** | <code>string</code> |
|
|
475
503
|
| **`endDate`** | <code>string</code> |
|
|
476
|
-
| **`dataType`** | <code>'steps' \| 'active-calories' \| 'total-calories' \| 'basal-calories' \| 'distance' \| '
|
|
504
|
+
| **`dataType`** | <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' \| 'hrv' \| 'blood-pressure'</code> |
|
|
477
505
|
| **`bucket`** | <code>string</code> |
|
|
478
506
|
|
|
479
507
|
|
|
@@ -533,20 +561,54 @@ Query latest steps sample
|
|
|
533
561
|
|
|
534
562
|
#### QueryLatestSampleResponse
|
|
535
563
|
|
|
536
|
-
| Prop
|
|
537
|
-
|
|
|
538
|
-
| **`value`**
|
|
539
|
-
| **`systolic`**
|
|
540
|
-
| **`diastolic`**
|
|
541
|
-
| **`timestamp`**
|
|
542
|
-
| **`
|
|
564
|
+
| Prop | Type |
|
|
565
|
+
| ------------------ | ---------------------------------------------------------------- |
|
|
566
|
+
| **`value`** | <code>number</code> |
|
|
567
|
+
| **`systolic`** | <code>number</code> |
|
|
568
|
+
| **`diastolic`** | <code>number</code> |
|
|
569
|
+
| **`timestamp`** | <code>number</code> |
|
|
570
|
+
| **`endTimestamp`** | <code>number</code> |
|
|
571
|
+
| **`unit`** | <code>string</code> |
|
|
572
|
+
| **`metadata`** | <code><a href="#record">Record</a><string, unknown></code> |
|
|
543
573
|
|
|
544
574
|
|
|
545
575
|
### Type Aliases
|
|
546
576
|
|
|
547
577
|
|
|
578
|
+
#### Record
|
|
579
|
+
|
|
580
|
+
Construct a type with a set of properties K of type T
|
|
581
|
+
|
|
582
|
+
<code>{
|
|
548
583
|
[P in K]: T;
|
|
549
584
|
}</code>
|
|
585
|
+
|
|
586
|
+
|
|
550
587
|
#### HealthPermission
|
|
551
588
|
|
|
552
|
-
<code>'READ_STEPS' | 'READ_WORKOUTS' | 'READ_ACTIVE_CALORIES' | 'READ_TOTAL_CALORIES' | 'READ_DISTANCE' | 'READ_WEIGHT' | 'READ_HEIGHT' | 'READ_HEART_RATE' | 'READ_ROUTE' | 'READ_MINDFULNESS' | 'READ_HRV' | 'READ_BLOOD_PRESSURE'</code>
|
|
589
|
+
<code>'READ_STEPS' | 'READ_WORKOUTS' | 'READ_ACTIVE_CALORIES' | 'READ_TOTAL_CALORIES' | 'READ_DISTANCE' | 'READ_WEIGHT' | 'READ_HEIGHT' | 'READ_HEART_RATE' | 'READ_RESTING_HEART_RATE' | 'READ_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' | '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>
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
#### HealthBiologicalSex
|
|
593
|
+
|
|
594
|
+
<code>'female' | 'male' | 'other' | 'not_set' | 'unknown'</code>
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
#### HealthBloodType
|
|
598
|
+
|
|
599
|
+
<code>'a-positive' | 'a-negative' | 'b-positive' | 'b-negative' | 'ab-positive' | 'ab-negative' | 'o-positive' | 'o-negative' | 'not_set' | 'unknown'</code>
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
#### HealthFitzpatrickSkinType
|
|
603
|
+
|
|
604
|
+
<code>'type1' | 'type2' | 'type3' | 'type4' | 'type5' | 'type6' | 'not_set' | 'unknown'</code>
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
#### HealthWheelchairUse
|
|
608
|
+
|
|
609
|
+
<code>'wheelchair_user' | 'not_wheelchair_user' | 'not_set' | 'unknown'</code>
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
#### LatestDataType
|
|
613
|
+
|
|
614
|
+
<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' | 'hrv' | 'blood-pressure'</code>
|
|
553
615
|
|
|
554
616
|
</docgen-api>
|
|
@@ -313,6 +313,17 @@ class HealthPlugin : Plugin() {
|
|
|
313
313
|
call.resolve()
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
@PluginMethod
|
|
317
|
+
fun getCharacteristics(call: PluginCall) {
|
|
318
|
+
val result = JSObject()
|
|
319
|
+
result.put("platformSupported", false)
|
|
320
|
+
result.put(
|
|
321
|
+
"platformMessage",
|
|
322
|
+
"Health Connect does not expose characteristics; this section stays empty unless synced from an iOS device."
|
|
323
|
+
)
|
|
324
|
+
call.resolve(result)
|
|
325
|
+
}
|
|
326
|
+
|
|
316
327
|
private fun getMetricAndMapper(dataType: String): MetricAndMapper {
|
|
317
328
|
return when (dataType) {
|
|
318
329
|
"steps" -> metricAndMapper("steps", CapHealthPermission.READ_STEPS, StepsRecord.COUNT_TOTAL) { it?.toDouble() }
|
|
@@ -40,8 +40,19 @@ export interface HealthPlugin {
|
|
|
40
40
|
* Opens the Google Health Connect app in PlayStore
|
|
41
41
|
*/
|
|
42
42
|
showHealthConnectInPlayStore(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* iOS only: Reads user characteristics such as biological sex, blood type, date of birth, Fitzpatrick skin type, and wheelchair use.
|
|
45
|
+
* 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.
|
|
46
|
+
*/
|
|
47
|
+
getCharacteristics(): Promise<CharacteristicsResponse>;
|
|
43
48
|
/**
|
|
44
49
|
* Query aggregated data
|
|
50
|
+
* - Blood-pressure aggregates return the systolic average in `value` plus `systolic`, `diastolic`, and `unit`.
|
|
51
|
+
* - `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.
|
|
52
|
+
* - Weight/height aggregation returns the latest sample per day (no averaging).
|
|
53
|
+
* - Android aggregation currently supports daily buckets; unsupported buckets will be rejected.
|
|
54
|
+
* - Android `distance-cycling` aggregates distance recorded during biking exercise sessions (requires distance + workouts permissions).
|
|
55
|
+
* - 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.
|
|
45
56
|
* @param request
|
|
46
57
|
*/
|
|
47
58
|
queryAggregated(request: QueryAggregatedRequest): Promise<QueryAggregatedResponse>;
|
|
@@ -52,6 +63,7 @@ export interface HealthPlugin {
|
|
|
52
63
|
queryWorkouts(request: QueryWorkoutRequest): Promise<QueryWorkoutResponse>;
|
|
53
64
|
/**
|
|
54
65
|
* Query latest sample for a specific data type
|
|
66
|
+
* - 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.
|
|
55
67
|
* @param request
|
|
56
68
|
*/
|
|
57
69
|
queryLatestSample(request: {
|
|
@@ -74,7 +86,7 @@ export interface HealthPlugin {
|
|
|
74
86
|
*/
|
|
75
87
|
querySteps(): Promise<QueryLatestSampleResponse>;
|
|
76
88
|
}
|
|
77
|
-
export declare type HealthPermission = 'READ_STEPS' | 'READ_WORKOUTS' | 'READ_ACTIVE_CALORIES' | 'READ_TOTAL_CALORIES' | 'READ_DISTANCE' | 'READ_WEIGHT' | 'READ_HEIGHT' | 'READ_HEART_RATE' | 'READ_RESTING_HEART_RATE' | 'READ_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' | 'READ_FLOORS_CLIMBED' | 'READ_SLEEP' | 'READ_EXERCISE_TIME';
|
|
89
|
+
export declare type HealthPermission = 'READ_STEPS' | 'READ_WORKOUTS' | 'READ_ACTIVE_CALORIES' | 'READ_TOTAL_CALORIES' | 'READ_DISTANCE' | 'READ_WEIGHT' | 'READ_HEIGHT' | 'READ_HEART_RATE' | 'READ_RESTING_HEART_RATE' | 'READ_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' | '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';
|
|
78
90
|
export type LatestDataType = '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' | 'hrv' | 'blood-pressure';
|
|
79
91
|
export interface PermissionsRequest {
|
|
80
92
|
permissions: HealthPermission[];
|
|
@@ -142,3 +154,22 @@ export interface QueryLatestSampleResponse {
|
|
|
142
154
|
unit: string;
|
|
143
155
|
metadata?: Record<string, unknown>;
|
|
144
156
|
}
|
|
157
|
+
export interface CharacteristicsResponse {
|
|
158
|
+
biologicalSex?: HealthBiologicalSex | null;
|
|
159
|
+
bloodType?: HealthBloodType | null;
|
|
160
|
+
dateOfBirth?: string | null;
|
|
161
|
+
fitzpatrickSkinType?: HealthFitzpatrickSkinType | null;
|
|
162
|
+
wheelchairUse?: HealthWheelchairUse | null;
|
|
163
|
+
/**
|
|
164
|
+
* Indicates whether the platform exposes these characteristics via the plugin (true on iOS, false on Android).
|
|
165
|
+
*/
|
|
166
|
+
platformSupported?: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Optional platform-specific message; on Android we return a user-facing note explaining that values remain empty unless synced from iOS.
|
|
169
|
+
*/
|
|
170
|
+
platformMessage?: string;
|
|
171
|
+
}
|
|
172
|
+
export type HealthBiologicalSex = 'female' | 'male' | 'other' | 'not_set' | 'unknown';
|
|
173
|
+
export type HealthBloodType = 'a-positive' | 'a-negative' | 'b-positive' | 'b-negative' | 'ab-positive' | 'ab-negative' | 'o-positive' | 'o-negative' | 'not_set' | 'unknown';
|
|
174
|
+
export type HealthFitzpatrickSkinType = 'type1' | 'type2' | 'type3' | 'type4' | 'type5' | 'type6' | 'not_set' | 'unknown';
|
|
175
|
+
export type HealthWheelchairUse = 'wheelchair_user' | 'not_wheelchair_user' | 'not_set' | 'unknown';
|
|
@@ -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 * Query aggregated data\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 * @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\nexport declare type HealthPermission =\n | 'READ_STEPS'\n | 'READ_WORKOUTS'\n | 'READ_ACTIVE_CALORIES'\n | 'READ_TOTAL_CALORIES'\n | 'READ_DISTANCE'\n | 'READ_WEIGHT'\n | 'READ_HEIGHT'\n | 'READ_HEART_RATE'\n | 'READ_RESTING_HEART_RATE'\n | 'READ_ROUTE'\n | 'READ_MINDFULNESS'\n | 'READ_HRV'\n | 'READ_BLOOD_PRESSURE'\n | 'READ_BASAL_CALORIES'\n | 'READ_RESPIRATORY_RATE'\n | 'READ_OXYGEN_SATURATION'\n | 'READ_BLOOD_GLUCOSE'\n | 'READ_BODY_TEMPERATURE'\n | 'READ_BASAL_BODY_TEMPERATURE'\n | 'READ_BODY_FAT'\n | 'READ_FLOORS_CLIMBED'\n | 'READ_SLEEP'\n | 'READ_EXERCISE_TIME';\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 | '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 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"]}
|
|
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 getCharacteristics(): 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 * @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\nexport declare type HealthPermission =\n | 'READ_STEPS'\n | 'READ_WORKOUTS'\n | 'READ_ACTIVE_CALORIES'\n | 'READ_TOTAL_CALORIES'\n | 'READ_DISTANCE'\n | 'READ_WEIGHT'\n | 'READ_HEIGHT'\n | 'READ_HEART_RATE'\n | 'READ_RESTING_HEART_RATE'\n | 'READ_ROUTE'\n | 'READ_MINDFULNESS'\n | 'READ_HRV'\n | 'READ_BLOOD_PRESSURE'\n | 'READ_BASAL_CALORIES'\n | 'READ_RESPIRATORY_RATE'\n | 'READ_OXYGEN_SATURATION'\n | 'READ_BLOOD_GLUCOSE'\n | 'READ_BODY_TEMPERATURE'\n | 'READ_BASAL_BODY_TEMPERATURE'\n | 'READ_BODY_FAT'\n | 'READ_FLOORS_CLIMBED'\n | 'READ_SLEEP'\n | 'READ_EXERCISE_TIME'\n | 'READ_BIOLOGICAL_SEX'\n | 'READ_BLOOD_TYPE'\n | 'READ_DATE_OF_BIRTH'\n | 'READ_FITZPATRICK_SKIN_TYPE'\n | '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 | '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 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 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"]}
|
|
@@ -18,7 +18,8 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
18
18
|
CAPPluginMethod(name: "openAppleHealthSettings", returnType: CAPPluginReturnPromise),
|
|
19
19
|
CAPPluginMethod(name: "queryAggregated", returnType: CAPPluginReturnPromise),
|
|
20
20
|
CAPPluginMethod(name: "queryWorkouts", returnType: CAPPluginReturnPromise),
|
|
21
|
-
CAPPluginMethod(name: "queryLatestSample", returnType: CAPPluginReturnPromise)
|
|
21
|
+
CAPPluginMethod(name: "queryLatestSample", returnType: CAPPluginReturnPromise),
|
|
22
|
+
CAPPluginMethod(name: "getCharacteristics", returnType: CAPPluginReturnPromise)
|
|
22
23
|
]
|
|
23
24
|
|
|
24
25
|
let healthStore = HKHealthStore()
|
|
@@ -96,6 +97,47 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
@objc func getCharacteristics(_ call: CAPPluginCall) {
|
|
101
|
+
var result: [String: Any] = [:]
|
|
102
|
+
|
|
103
|
+
func setValue(_ key: String, _ value: String?) {
|
|
104
|
+
result[key] = value ?? NSNull()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if let biologicalSexObject = try? healthStore.biologicalSex() {
|
|
108
|
+
setValue("biologicalSex", mapBiologicalSex(biologicalSexObject.biologicalSex))
|
|
109
|
+
} else {
|
|
110
|
+
setValue("biologicalSex", nil)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if let bloodTypeObject = try? healthStore.bloodType() {
|
|
114
|
+
setValue("bloodType", mapBloodType(bloodTypeObject.bloodType))
|
|
115
|
+
} else {
|
|
116
|
+
setValue("bloodType", nil)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if let dateComponents = try? healthStore.dateOfBirthComponents() {
|
|
120
|
+
setValue("dateOfBirth", isoBirthDateString(from: dateComponents))
|
|
121
|
+
} else {
|
|
122
|
+
setValue("dateOfBirth", nil)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if let fitzpatrickObject = try? healthStore.fitzpatrickSkinType() {
|
|
126
|
+
setValue("fitzpatrickSkinType", mapFitzpatrickSkinType(fitzpatrickObject.skinType))
|
|
127
|
+
} else {
|
|
128
|
+
setValue("fitzpatrickSkinType", nil)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if let wheelchairUseObject = try? healthStore.wheelchairUse() {
|
|
132
|
+
setValue("wheelchairUse", mapWheelchairUse(wheelchairUseObject.wheelchairUse))
|
|
133
|
+
} else {
|
|
134
|
+
setValue("wheelchairUse", nil)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
result["platformSupported"] = true
|
|
138
|
+
call.resolve(result)
|
|
139
|
+
}
|
|
140
|
+
|
|
99
141
|
@objc func queryLatestSample(_ call: CAPPluginCall) {
|
|
100
142
|
guard let dataTypeString = call.getString("dataType") else {
|
|
101
143
|
call.reject("Missing data type")
|
|
@@ -473,6 +515,16 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
473
515
|
return [HKObjectType.categoryType(forIdentifier: .sleepAnalysis)!].compactMap { $0 }
|
|
474
516
|
case "READ_EXERCISE_TIME":
|
|
475
517
|
return [HKObjectType.quantityType(forIdentifier: .appleExerciseTime)].compactMap { $0 }
|
|
518
|
+
case "READ_BIOLOGICAL_SEX":
|
|
519
|
+
return [HKObjectType.characteristicType(forIdentifier: .biologicalSex)].compactMap { $0 }
|
|
520
|
+
case "READ_BLOOD_TYPE":
|
|
521
|
+
return [HKObjectType.characteristicType(forIdentifier: .bloodType)].compactMap { $0 }
|
|
522
|
+
case "READ_DATE_OF_BIRTH":
|
|
523
|
+
return [HKObjectType.characteristicType(forIdentifier: .dateOfBirth)].compactMap { $0 }
|
|
524
|
+
case "READ_FITZPATRICK_SKIN_TYPE":
|
|
525
|
+
return [HKObjectType.characteristicType(forIdentifier: .fitzpatrickSkinType)].compactMap { $0 }
|
|
526
|
+
case "READ_WHEELCHAIR_USE":
|
|
527
|
+
return [HKObjectType.characteristicType(forIdentifier: .wheelchairUse)].compactMap { $0 }
|
|
476
528
|
// Add common alternative permission names
|
|
477
529
|
case "steps":
|
|
478
530
|
return [HKObjectType.quantityType(forIdentifier: .stepCount)].compactMap{$0}
|
|
@@ -534,6 +586,94 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
534
586
|
return []
|
|
535
587
|
}
|
|
536
588
|
}
|
|
589
|
+
|
|
590
|
+
private func mapBiologicalSex(_ biologicalSex: HKBiologicalSex) -> String {
|
|
591
|
+
switch biologicalSex {
|
|
592
|
+
case .female:
|
|
593
|
+
return "female"
|
|
594
|
+
case .male:
|
|
595
|
+
return "male"
|
|
596
|
+
case .other:
|
|
597
|
+
return "other"
|
|
598
|
+
case .notSet:
|
|
599
|
+
return "not_set"
|
|
600
|
+
@unknown default:
|
|
601
|
+
return "unknown"
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private func mapBloodType(_ bloodType: HKBloodType) -> String {
|
|
606
|
+
switch bloodType {
|
|
607
|
+
case .aPositive:
|
|
608
|
+
return "a-positive"
|
|
609
|
+
case .aNegative:
|
|
610
|
+
return "a-negative"
|
|
611
|
+
case .bPositive:
|
|
612
|
+
return "b-positive"
|
|
613
|
+
case .bNegative:
|
|
614
|
+
return "b-negative"
|
|
615
|
+
case .abPositive:
|
|
616
|
+
return "ab-positive"
|
|
617
|
+
case .abNegative:
|
|
618
|
+
return "ab-negative"
|
|
619
|
+
case .oPositive:
|
|
620
|
+
return "o-positive"
|
|
621
|
+
case .oNegative:
|
|
622
|
+
return "o-negative"
|
|
623
|
+
case .notSet:
|
|
624
|
+
return "not_set"
|
|
625
|
+
@unknown default:
|
|
626
|
+
return "unknown"
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private func mapFitzpatrickSkinType(_ skinType: HKFitzpatrickSkinType) -> String {
|
|
631
|
+
switch skinType {
|
|
632
|
+
case .I:
|
|
633
|
+
return "type1"
|
|
634
|
+
case .II:
|
|
635
|
+
return "type2"
|
|
636
|
+
case .III:
|
|
637
|
+
return "type3"
|
|
638
|
+
case .IV:
|
|
639
|
+
return "type4"
|
|
640
|
+
case .V:
|
|
641
|
+
return "type5"
|
|
642
|
+
case .VI:
|
|
643
|
+
return "type6"
|
|
644
|
+
case .notSet:
|
|
645
|
+
return "not_set"
|
|
646
|
+
@unknown default:
|
|
647
|
+
return "unknown"
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
private func mapWheelchairUse(_ wheelchairUse: HKWheelchairUse) -> String {
|
|
652
|
+
switch wheelchairUse {
|
|
653
|
+
case .yes:
|
|
654
|
+
return "wheelchair_user"
|
|
655
|
+
case .no:
|
|
656
|
+
return "not_wheelchair_user"
|
|
657
|
+
case .notSet:
|
|
658
|
+
return "not_set"
|
|
659
|
+
@unknown default:
|
|
660
|
+
return "unknown"
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private func isoBirthDateString(from components: DateComponents) -> String? {
|
|
665
|
+
var calendar = Calendar(identifier: .gregorian)
|
|
666
|
+
calendar.timeZone = TimeZone(secondsFromGMT: 0) ?? TimeZone.current
|
|
667
|
+
guard let date = calendar.date(from: components) else {
|
|
668
|
+
return nil
|
|
669
|
+
}
|
|
670
|
+
let formatter = DateFormatter()
|
|
671
|
+
formatter.calendar = calendar
|
|
672
|
+
formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
673
|
+
formatter.timeZone = calendar.timeZone
|
|
674
|
+
formatter.dateFormat = "yyyy-MM-dd"
|
|
675
|
+
return formatter.string(from: date)
|
|
676
|
+
}
|
|
537
677
|
|
|
538
678
|
func aggregateTypeToHKQuantityType(_ dataType: String) -> HKQuantityType? {
|
|
539
679
|
switch dataType {
|
package/package.json
CHANGED