@capgo/capacitor-health 8.2.17 → 8.2.18
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 +41 -2
- 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/dist/docs.json +96 -0
- package/dist/esm/definitions.d.ts +10 -2
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/HealthPlugin/Health.swift +236 -1
- package/ios/Sources/HealthPlugin/HealthPlugin.swift +7 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -174,10 +174,45 @@ await Health.saveSample({
|
|
|
174
174
|
| `oxygenSaturation` | `percent` | Blood oxygen saturation (SpO2) |
|
|
175
175
|
| `restingHeartRate` | `bpm` | Resting heart rate |
|
|
176
176
|
| `heartRateVariability` | `millisecond` | Heart rate variability (HRV) |
|
|
177
|
+
| `bloodPressure` | `mmHg` | Blood pressure (requires systolic/diastolic values) |
|
|
178
|
+
| `bloodGlucose` | `mg/dL` | Blood glucose level |
|
|
179
|
+
| `bodyTemperature` | `celsius` | Body temperature |
|
|
180
|
+
| `height` | `centimeter` | Body height |
|
|
181
|
+
| `flightsClimbed` | `count` | Floors / flights of stairs climbed |
|
|
182
|
+
| `exerciseTime` | `minute` | Apple Exercise Time (iOS only) |
|
|
183
|
+
| `distanceCycling` | `meter` | Cycling distance |
|
|
184
|
+
| `bodyFat` | `percent` | Body fat percentage |
|
|
185
|
+
| `basalBodyTemperature` | `celsius` | Basal body temperature |
|
|
186
|
+
| `basalCalories` | `kilocalorie` | Basal metabolic rate / resting energy |
|
|
187
|
+
| `totalCalories` | `kilocalorie` | Total energy burned (active + basal) |
|
|
188
|
+
| `mindfulness` | `minute` | Mindfulness / meditation sessions |
|
|
177
189
|
| `workouts` | N/A | Workout sessions (read-only, use with `queryWorkouts()`) |
|
|
178
190
|
|
|
179
191
|
All write operations expect the default unit shown above. On Android the `metadata` option is currently ignored by Health Connect.
|
|
180
192
|
|
|
193
|
+
**Blood Pressure:** Blood pressure requires both systolic and diastolic values:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
await Health.saveSample({
|
|
197
|
+
dataType: 'bloodPressure',
|
|
198
|
+
value: 120, // systolic value (used as main value)
|
|
199
|
+
systolic: 120,
|
|
200
|
+
diastolic: 80,
|
|
201
|
+
startDate: new Date().toISOString(),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Reading blood pressure returns samples with systolic/diastolic fields
|
|
205
|
+
const { samples } = await Health.readSamples({
|
|
206
|
+
dataType: 'bloodPressure',
|
|
207
|
+
startDate: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
|
|
208
|
+
endDate: new Date().toISOString(),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
samples.forEach((sample) => {
|
|
212
|
+
console.log(`BP: ${sample.systolic}/${sample.diastolic} mmHg`);
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
181
216
|
**Note about workouts:** To query workout data using `queryWorkouts()`, you need to explicitly request `workouts` permission:
|
|
182
217
|
|
|
183
218
|
```ts
|
|
@@ -521,6 +556,8 @@ Supported on iOS (HealthKit) and Android (Health Connect).
|
|
|
521
556
|
| **`sourceName`** | <code>string</code> | |
|
|
522
557
|
| **`sourceId`** | <code>string</code> | |
|
|
523
558
|
| **`sleepState`** | <code><a href="#sleepstate">SleepState</a></code> | For sleep data, indicates the sleep state (e.g., 'asleep', 'awake', 'rem', 'deep', 'light'). |
|
|
559
|
+
| **`systolic`** | <code>number</code> | For blood pressure data, the systolic value in mmHg. |
|
|
560
|
+
| **`diastolic`** | <code>number</code> | For blood pressure data, the diastolic value in mmHg. |
|
|
524
561
|
|
|
525
562
|
|
|
526
563
|
#### QueryOptions
|
|
@@ -544,6 +581,8 @@ Supported on iOS (HealthKit) and Android (Health Connect).
|
|
|
544
581
|
| **`startDate`** | <code>string</code> | ISO 8601 start date for the sample. Defaults to now. |
|
|
545
582
|
| **`endDate`** | <code>string</code> | ISO 8601 end date for the sample. Defaults to startDate. |
|
|
546
583
|
| **`metadata`** | <code><a href="#record">Record</a><string, string></code> | Metadata key-value pairs forwarded to the native APIs where supported. |
|
|
584
|
+
| **`systolic`** | <code>number</code> | For blood pressure data, the systolic value in mmHg. Required when dataType is 'bloodPressure'. |
|
|
585
|
+
| **`diastolic`** | <code>number</code> | For blood pressure data, the diastolic value in mmHg. Required when dataType is 'bloodPressure'. |
|
|
547
586
|
|
|
548
587
|
|
|
549
588
|
#### QueryWorkoutsResult
|
|
@@ -614,12 +653,12 @@ Supported on iOS (HealthKit) and Android (Health Connect).
|
|
|
614
653
|
|
|
615
654
|
#### HealthDataType
|
|
616
655
|
|
|
617
|
-
<code>'steps' | 'distance' | 'calories' | 'heartRate' | 'weight' | 'sleep' | 'respiratoryRate' | 'oxygenSaturation' | 'restingHeartRate' | 'heartRateVariability'</code>
|
|
656
|
+
<code>'steps' | 'distance' | 'calories' | 'heartRate' | 'weight' | 'sleep' | 'respiratoryRate' | 'oxygenSaturation' | 'restingHeartRate' | 'heartRateVariability' | 'bloodPressure' | 'bloodGlucose' | 'bodyTemperature' | 'height' | 'flightsClimbed' | 'exerciseTime' | 'distanceCycling' | 'bodyFat' | 'basalBodyTemperature' | 'basalCalories' | 'totalCalories' | 'mindfulness'</code>
|
|
618
657
|
|
|
619
658
|
|
|
620
659
|
#### HealthUnit
|
|
621
660
|
|
|
622
|
-
<code>'count' | 'meter' | 'kilocalorie' | 'bpm' | 'kilogram' | 'minute' | 'percent' | 'millisecond'</code>
|
|
661
|
+
<code>'count' | 'meter' | 'kilocalorie' | 'bpm' | 'kilogram' | 'minute' | 'percent' | 'millisecond' | 'mmHg' | 'mg/dL' | 'celsius' | 'fahrenheit' | 'centimeter'</code>
|
|
623
662
|
|
|
624
663
|
|
|
625
664
|
#### SleepState
|
package/android/build.gradle
CHANGED
|
@@ -62,7 +62,7 @@ dependencies {
|
|
|
62
62
|
implementation project(':capacitor-android')
|
|
63
63
|
implementation "androidx.appcompat:appcompat:${androidxAppCompatVersion}"
|
|
64
64
|
implementation ("org.jetbrains.kotlin:kotlin-stdlib:" + project.ext.kotlinVersion)
|
|
65
|
-
implementation 'androidx.health.connect:connect-client:1.1.0
|
|
65
|
+
implementation 'androidx.health.connect:connect-client:1.1.0'
|
|
66
66
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
|
|
67
67
|
testImplementation "junit:junit:${junitVersion}"
|
|
68
68
|
androidTestImplementation "androidx.test.ext:junit:${androidxJunitVersion}"
|
|
@@ -19,6 +19,26 @@
|
|
|
19
19
|
<uses-permission android:name="android.permission.health.WRITE_RESTING_HEART_RATE" />
|
|
20
20
|
<uses-permission android:name="android.permission.health.READ_HEART_RATE_VARIABILITY" />
|
|
21
21
|
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE_VARIABILITY" />
|
|
22
|
+
<uses-permission android:name="android.permission.health.READ_BLOOD_PRESSURE" />
|
|
23
|
+
<uses-permission android:name="android.permission.health.WRITE_BLOOD_PRESSURE" />
|
|
24
|
+
<uses-permission android:name="android.permission.health.READ_BLOOD_GLUCOSE" />
|
|
25
|
+
<uses-permission android:name="android.permission.health.WRITE_BLOOD_GLUCOSE" />
|
|
26
|
+
<uses-permission android:name="android.permission.health.READ_BODY_TEMPERATURE" />
|
|
27
|
+
<uses-permission android:name="android.permission.health.WRITE_BODY_TEMPERATURE" />
|
|
28
|
+
<uses-permission android:name="android.permission.health.READ_HEIGHT" />
|
|
29
|
+
<uses-permission android:name="android.permission.health.WRITE_HEIGHT" />
|
|
30
|
+
<uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED" />
|
|
31
|
+
<uses-permission android:name="android.permission.health.WRITE_FLOORS_CLIMBED" />
|
|
32
|
+
<uses-permission android:name="android.permission.health.READ_BODY_FAT" />
|
|
33
|
+
<uses-permission android:name="android.permission.health.WRITE_BODY_FAT" />
|
|
34
|
+
<uses-permission android:name="android.permission.health.READ_BASAL_BODY_TEMPERATURE" />
|
|
35
|
+
<uses-permission android:name="android.permission.health.WRITE_BASAL_BODY_TEMPERATURE" />
|
|
36
|
+
<uses-permission android:name="android.permission.health.READ_BASAL_METABOLIC_RATE" />
|
|
37
|
+
<uses-permission android:name="android.permission.health.WRITE_BASAL_METABOLIC_RATE" />
|
|
38
|
+
<uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
|
|
39
|
+
<uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED" />
|
|
40
|
+
<uses-permission android:name="android.permission.health.READ_MINDFULNESS" />
|
|
41
|
+
<uses-permission android:name="android.permission.health.WRITE_MINDFULNESS" />
|
|
22
42
|
<uses-permission android:name="android.permission.health.READ_EXERCISE" />
|
|
23
43
|
|
|
24
44
|
<!-- Query for Health Connect availability -->
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
package app.capgo.plugin.health
|
|
2
2
|
|
|
3
|
+
import androidx.health.connect.client.feature.ExperimentalMindfulnessSessionApi
|
|
3
4
|
import androidx.health.connect.client.permission.HealthPermission
|
|
4
5
|
import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord
|
|
6
|
+
import androidx.health.connect.client.records.BasalBodyTemperatureRecord
|
|
7
|
+
import androidx.health.connect.client.records.BasalMetabolicRateRecord
|
|
8
|
+
import androidx.health.connect.client.records.BloodGlucoseRecord
|
|
9
|
+
import androidx.health.connect.client.records.BloodPressureRecord
|
|
10
|
+
import androidx.health.connect.client.records.BodyFatRecord
|
|
11
|
+
import androidx.health.connect.client.records.BodyTemperatureRecord
|
|
5
12
|
import androidx.health.connect.client.records.DistanceRecord
|
|
13
|
+
import androidx.health.connect.client.records.FloorsClimbedRecord
|
|
6
14
|
import androidx.health.connect.client.records.HeartRateRecord
|
|
7
15
|
import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
|
|
16
|
+
import androidx.health.connect.client.records.HeightRecord
|
|
17
|
+
import androidx.health.connect.client.records.MindfulnessSessionRecord
|
|
8
18
|
import androidx.health.connect.client.records.OxygenSaturationRecord
|
|
9
19
|
import androidx.health.connect.client.records.Record
|
|
10
20
|
import androidx.health.connect.client.records.RespiratoryRateRecord
|
|
11
21
|
import androidx.health.connect.client.records.RestingHeartRateRecord
|
|
12
22
|
import androidx.health.connect.client.records.SleepSessionRecord
|
|
13
23
|
import androidx.health.connect.client.records.StepsRecord
|
|
24
|
+
import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
|
|
14
25
|
import androidx.health.connect.client.records.WeightRecord
|
|
15
26
|
import kotlin.reflect.KClass
|
|
16
27
|
|
|
28
|
+
@OptIn(ExperimentalMindfulnessSessionApi::class)
|
|
17
29
|
enum class HealthDataType(
|
|
18
30
|
val identifier: String,
|
|
19
31
|
val recordClass: KClass<out Record>,
|
|
@@ -28,7 +40,18 @@ enum class HealthDataType(
|
|
|
28
40
|
RESPIRATORY_RATE("respiratoryRate", RespiratoryRateRecord::class, "bpm"),
|
|
29
41
|
OXYGEN_SATURATION("oxygenSaturation", OxygenSaturationRecord::class, "percent"),
|
|
30
42
|
RESTING_HEART_RATE("restingHeartRate", RestingHeartRateRecord::class, "bpm"),
|
|
31
|
-
HEART_RATE_VARIABILITY("heartRateVariability", HeartRateVariabilityRmssdRecord::class, "millisecond")
|
|
43
|
+
HEART_RATE_VARIABILITY("heartRateVariability", HeartRateVariabilityRmssdRecord::class, "millisecond"),
|
|
44
|
+
BLOOD_PRESSURE("bloodPressure", BloodPressureRecord::class, "mmHg"),
|
|
45
|
+
BLOOD_GLUCOSE("bloodGlucose", BloodGlucoseRecord::class, "mg/dL"),
|
|
46
|
+
BODY_TEMPERATURE("bodyTemperature", BodyTemperatureRecord::class, "celsius"),
|
|
47
|
+
HEIGHT("height", HeightRecord::class, "centimeter"),
|
|
48
|
+
FLIGHTS_CLIMBED("flightsClimbed", FloorsClimbedRecord::class, "count"),
|
|
49
|
+
DISTANCE_CYCLING("distanceCycling", DistanceRecord::class, "meter"),
|
|
50
|
+
BODY_FAT("bodyFat", BodyFatRecord::class, "percent"),
|
|
51
|
+
BASAL_BODY_TEMPERATURE("basalBodyTemperature", BasalBodyTemperatureRecord::class, "celsius"),
|
|
52
|
+
BASAL_CALORIES("basalCalories", BasalMetabolicRateRecord::class, "kilocalorie"),
|
|
53
|
+
TOTAL_CALORIES("totalCalories", TotalCaloriesBurnedRecord::class, "kilocalorie"),
|
|
54
|
+
MINDFULNESS("mindfulness", MindfulnessSessionRecord::class, "minute");
|
|
32
55
|
|
|
33
56
|
val readPermission: String
|
|
34
57
|
get() = HealthPermission.getReadPermission(recordClass)
|
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
package app.capgo.plugin.health
|
|
2
2
|
|
|
3
3
|
import androidx.health.connect.client.HealthConnectClient
|
|
4
|
+
import androidx.health.connect.client.feature.ExperimentalMindfulnessSessionApi
|
|
4
5
|
import androidx.health.connect.client.permission.HealthPermission
|
|
5
6
|
import androidx.health.connect.client.request.AggregateRequest
|
|
6
7
|
import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord
|
|
8
|
+
import androidx.health.connect.client.records.BasalBodyTemperatureRecord
|
|
9
|
+
import androidx.health.connect.client.records.BasalMetabolicRateRecord
|
|
10
|
+
import androidx.health.connect.client.records.BloodGlucoseRecord
|
|
11
|
+
import androidx.health.connect.client.records.BloodPressureRecord
|
|
12
|
+
import androidx.health.connect.client.records.BodyFatRecord
|
|
13
|
+
import androidx.health.connect.client.records.BodyTemperatureRecord
|
|
7
14
|
import androidx.health.connect.client.records.DistanceRecord
|
|
8
15
|
import androidx.health.connect.client.records.ExerciseSessionRecord
|
|
16
|
+
import androidx.health.connect.client.records.FloorsClimbedRecord
|
|
9
17
|
import androidx.health.connect.client.records.HeartRateRecord
|
|
10
18
|
import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
|
|
19
|
+
import androidx.health.connect.client.records.HeightRecord
|
|
20
|
+
import androidx.health.connect.client.records.MindfulnessSessionRecord
|
|
11
21
|
import androidx.health.connect.client.records.OxygenSaturationRecord
|
|
12
22
|
import androidx.health.connect.client.records.Record
|
|
13
23
|
import androidx.health.connect.client.records.RespiratoryRateRecord
|
|
14
24
|
import androidx.health.connect.client.records.RestingHeartRateRecord
|
|
15
25
|
import androidx.health.connect.client.records.SleepSessionRecord
|
|
16
26
|
import androidx.health.connect.client.records.StepsRecord
|
|
27
|
+
import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
|
|
17
28
|
import androidx.health.connect.client.records.WeightRecord
|
|
29
|
+
import androidx.health.connect.client.records.metadata.Metadata
|
|
18
30
|
import androidx.health.connect.client.request.ReadRecordsRequest
|
|
19
31
|
import androidx.health.connect.client.time.TimeRangeFilter
|
|
20
32
|
import androidx.health.connect.client.units.Energy
|
|
@@ -22,18 +34,18 @@ import androidx.health.connect.client.units.Length
|
|
|
22
34
|
import androidx.health.connect.client.units.Mass
|
|
23
35
|
import androidx.health.connect.client.units.Percentage
|
|
24
36
|
import androidx.health.connect.client.units.Power
|
|
25
|
-
import androidx.health.connect.client.records.metadata.Metadata
|
|
26
|
-
import java.time.Duration
|
|
27
37
|
import com.getcapacitor.JSArray
|
|
28
38
|
import com.getcapacitor.JSObject
|
|
39
|
+
import java.time.Duration
|
|
29
40
|
import java.time.Instant
|
|
30
41
|
import java.time.ZoneId
|
|
31
42
|
import java.time.ZoneOffset
|
|
32
43
|
import java.time.format.DateTimeFormatter
|
|
33
|
-
import kotlin.math.min
|
|
34
44
|
import kotlin.collections.buildSet
|
|
45
|
+
import kotlin.math.min
|
|
35
46
|
import kotlinx.coroutines.CancellationException
|
|
36
47
|
|
|
48
|
+
@OptIn(ExperimentalMindfulnessSessionApi::class)
|
|
37
49
|
class HealthManager {
|
|
38
50
|
|
|
39
51
|
private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_INSTANT
|
|
@@ -211,6 +223,119 @@ class HealthManager {
|
|
|
211
223
|
)
|
|
212
224
|
samples.add(record.time to payload)
|
|
213
225
|
}
|
|
226
|
+
HealthDataType.BLOOD_PRESSURE -> readRecords(client, BloodPressureRecord::class, startTime, endTime, limit) { record ->
|
|
227
|
+
val payload = createSamplePayload(
|
|
228
|
+
dataType,
|
|
229
|
+
record.time,
|
|
230
|
+
record.time,
|
|
231
|
+
record.systolic.inMillimetersOfMercury,
|
|
232
|
+
record.metadata
|
|
233
|
+
)
|
|
234
|
+
payload.put("systolic", record.systolic.inMillimetersOfMercury)
|
|
235
|
+
payload.put("diastolic", record.diastolic.inMillimetersOfMercury)
|
|
236
|
+
samples.add(record.time to payload)
|
|
237
|
+
}
|
|
238
|
+
HealthDataType.BLOOD_GLUCOSE -> readRecords(client, BloodGlucoseRecord::class, startTime, endTime, limit) { record ->
|
|
239
|
+
val payload = createSamplePayload(
|
|
240
|
+
dataType,
|
|
241
|
+
record.time,
|
|
242
|
+
record.time,
|
|
243
|
+
record.level.inMilligramsPerDeciliter,
|
|
244
|
+
record.metadata
|
|
245
|
+
)
|
|
246
|
+
samples.add(record.time to payload)
|
|
247
|
+
}
|
|
248
|
+
HealthDataType.BODY_TEMPERATURE -> readRecords(client, BodyTemperatureRecord::class, startTime, endTime, limit) { record ->
|
|
249
|
+
val payload = createSamplePayload(
|
|
250
|
+
dataType,
|
|
251
|
+
record.time,
|
|
252
|
+
record.time,
|
|
253
|
+
record.temperature.inCelsius,
|
|
254
|
+
record.metadata
|
|
255
|
+
)
|
|
256
|
+
samples.add(record.time to payload)
|
|
257
|
+
}
|
|
258
|
+
HealthDataType.HEIGHT -> readRecords(client, HeightRecord::class, startTime, endTime, limit) { record ->
|
|
259
|
+
val payload = createSamplePayload(
|
|
260
|
+
dataType,
|
|
261
|
+
record.time,
|
|
262
|
+
record.time,
|
|
263
|
+
record.height.inMeters * 100.0, // Convert to centimeters
|
|
264
|
+
record.metadata
|
|
265
|
+
)
|
|
266
|
+
samples.add(record.time to payload)
|
|
267
|
+
}
|
|
268
|
+
HealthDataType.FLIGHTS_CLIMBED -> readRecords(client, FloorsClimbedRecord::class, startTime, endTime, limit) { record ->
|
|
269
|
+
val payload = createSamplePayload(
|
|
270
|
+
dataType,
|
|
271
|
+
record.startTime,
|
|
272
|
+
record.endTime,
|
|
273
|
+
record.floors,
|
|
274
|
+
record.metadata
|
|
275
|
+
)
|
|
276
|
+
samples.add(record.startTime to payload)
|
|
277
|
+
}
|
|
278
|
+
HealthDataType.DISTANCE_CYCLING -> readRecords(client, DistanceRecord::class, startTime, endTime, limit) { record ->
|
|
279
|
+
val payload = createSamplePayload(
|
|
280
|
+
dataType,
|
|
281
|
+
record.startTime,
|
|
282
|
+
record.endTime,
|
|
283
|
+
record.distance.inMeters,
|
|
284
|
+
record.metadata
|
|
285
|
+
)
|
|
286
|
+
samples.add(record.startTime to payload)
|
|
287
|
+
}
|
|
288
|
+
HealthDataType.BODY_FAT -> readRecords(client, BodyFatRecord::class, startTime, endTime, limit) { record ->
|
|
289
|
+
val payload = createSamplePayload(
|
|
290
|
+
dataType,
|
|
291
|
+
record.time,
|
|
292
|
+
record.time,
|
|
293
|
+
record.percentage.value,
|
|
294
|
+
record.metadata
|
|
295
|
+
)
|
|
296
|
+
samples.add(record.time to payload)
|
|
297
|
+
}
|
|
298
|
+
HealthDataType.BASAL_BODY_TEMPERATURE -> readRecords(client, BasalBodyTemperatureRecord::class, startTime, endTime, limit) { record ->
|
|
299
|
+
val payload = createSamplePayload(
|
|
300
|
+
dataType,
|
|
301
|
+
record.time,
|
|
302
|
+
record.time,
|
|
303
|
+
record.temperature.inCelsius,
|
|
304
|
+
record.metadata
|
|
305
|
+
)
|
|
306
|
+
samples.add(record.time to payload)
|
|
307
|
+
}
|
|
308
|
+
HealthDataType.BASAL_CALORIES -> readRecords(client, BasalMetabolicRateRecord::class, startTime, endTime, limit) { record ->
|
|
309
|
+
val payload = createSamplePayload(
|
|
310
|
+
dataType,
|
|
311
|
+
record.time,
|
|
312
|
+
record.time,
|
|
313
|
+
record.basalMetabolicRate.inKilocaloriesPerDay,
|
|
314
|
+
record.metadata
|
|
315
|
+
)
|
|
316
|
+
samples.add(record.time to payload)
|
|
317
|
+
}
|
|
318
|
+
HealthDataType.TOTAL_CALORIES -> readRecords(client, TotalCaloriesBurnedRecord::class, startTime, endTime, limit) { record ->
|
|
319
|
+
val payload = createSamplePayload(
|
|
320
|
+
dataType,
|
|
321
|
+
record.startTime,
|
|
322
|
+
record.endTime,
|
|
323
|
+
record.energy.inKilocalories,
|
|
324
|
+
record.metadata
|
|
325
|
+
)
|
|
326
|
+
samples.add(record.startTime to payload)
|
|
327
|
+
}
|
|
328
|
+
HealthDataType.MINDFULNESS -> readRecords(client, MindfulnessSessionRecord::class, startTime, endTime, limit) { record ->
|
|
329
|
+
val durationMinutes = Duration.between(record.startTime, record.endTime).toMinutes().toDouble()
|
|
330
|
+
val payload = createSamplePayload(
|
|
331
|
+
dataType,
|
|
332
|
+
record.startTime,
|
|
333
|
+
record.endTime,
|
|
334
|
+
durationMinutes,
|
|
335
|
+
record.metadata
|
|
336
|
+
)
|
|
337
|
+
samples.add(record.startTime to payload)
|
|
338
|
+
}
|
|
214
339
|
}
|
|
215
340
|
|
|
216
341
|
val sorted = samples.sortedBy { it.first }
|
|
@@ -257,8 +382,12 @@ class HealthManager {
|
|
|
257
382
|
value: Double,
|
|
258
383
|
startTime: Instant,
|
|
259
384
|
endTime: Instant,
|
|
260
|
-
metadata: Map<String, String
|
|
385
|
+
metadata: Map<String, String>?,
|
|
386
|
+
systolic: Double?,
|
|
387
|
+
diastolic: Double?
|
|
261
388
|
) {
|
|
389
|
+
val recordMetadata = Metadata.manualEntry()
|
|
390
|
+
|
|
262
391
|
when (dataType) {
|
|
263
392
|
HealthDataType.STEPS -> {
|
|
264
393
|
val record = StepsRecord(
|
|
@@ -266,7 +395,8 @@ class HealthManager {
|
|
|
266
395
|
startZoneOffset = zoneOffset(startTime),
|
|
267
396
|
endTime = endTime,
|
|
268
397
|
endZoneOffset = zoneOffset(endTime),
|
|
269
|
-
count = value.toLong().coerceAtLeast(0)
|
|
398
|
+
count = value.toLong().coerceAtLeast(0),
|
|
399
|
+
metadata = recordMetadata
|
|
270
400
|
)
|
|
271
401
|
client.insertRecords(listOf(record))
|
|
272
402
|
}
|
|
@@ -276,7 +406,8 @@ class HealthManager {
|
|
|
276
406
|
startZoneOffset = zoneOffset(startTime),
|
|
277
407
|
endTime = endTime,
|
|
278
408
|
endZoneOffset = zoneOffset(endTime),
|
|
279
|
-
distance = Length.meters(value)
|
|
409
|
+
distance = Length.meters(value),
|
|
410
|
+
metadata = recordMetadata
|
|
280
411
|
)
|
|
281
412
|
client.insertRecords(listOf(record))
|
|
282
413
|
}
|
|
@@ -286,7 +417,8 @@ class HealthManager {
|
|
|
286
417
|
startZoneOffset = zoneOffset(startTime),
|
|
287
418
|
endTime = endTime,
|
|
288
419
|
endZoneOffset = zoneOffset(endTime),
|
|
289
|
-
energy = Energy.kilocalories(value)
|
|
420
|
+
energy = Energy.kilocalories(value),
|
|
421
|
+
metadata = recordMetadata
|
|
290
422
|
)
|
|
291
423
|
client.insertRecords(listOf(record))
|
|
292
424
|
}
|
|
@@ -294,7 +426,8 @@ class HealthManager {
|
|
|
294
426
|
val record = WeightRecord(
|
|
295
427
|
time = startTime,
|
|
296
428
|
zoneOffset = zoneOffset(startTime),
|
|
297
|
-
weight = Mass.kilograms(value)
|
|
429
|
+
weight = Mass.kilograms(value),
|
|
430
|
+
metadata = recordMetadata
|
|
298
431
|
)
|
|
299
432
|
client.insertRecords(listOf(record))
|
|
300
433
|
}
|
|
@@ -305,7 +438,8 @@ class HealthManager {
|
|
|
305
438
|
startZoneOffset = zoneOffset(startTime),
|
|
306
439
|
endTime = endTime,
|
|
307
440
|
endZoneOffset = zoneOffset(endTime),
|
|
308
|
-
samples = samples
|
|
441
|
+
samples = samples,
|
|
442
|
+
metadata = recordMetadata
|
|
309
443
|
)
|
|
310
444
|
client.insertRecords(listOf(record))
|
|
311
445
|
}
|
|
@@ -314,7 +448,8 @@ class HealthManager {
|
|
|
314
448
|
startTime = startTime,
|
|
315
449
|
startZoneOffset = zoneOffset(startTime),
|
|
316
450
|
endTime = endTime,
|
|
317
|
-
endZoneOffset = zoneOffset(endTime)
|
|
451
|
+
endZoneOffset = zoneOffset(endTime),
|
|
452
|
+
metadata = recordMetadata
|
|
318
453
|
)
|
|
319
454
|
client.insertRecords(listOf(record))
|
|
320
455
|
}
|
|
@@ -322,7 +457,8 @@ class HealthManager {
|
|
|
322
457
|
val record = RespiratoryRateRecord(
|
|
323
458
|
time = startTime,
|
|
324
459
|
zoneOffset = zoneOffset(startTime),
|
|
325
|
-
rate = value
|
|
460
|
+
rate = value,
|
|
461
|
+
metadata = recordMetadata
|
|
326
462
|
)
|
|
327
463
|
client.insertRecords(listOf(record))
|
|
328
464
|
}
|
|
@@ -330,7 +466,8 @@ class HealthManager {
|
|
|
330
466
|
val record = OxygenSaturationRecord(
|
|
331
467
|
time = startTime,
|
|
332
468
|
zoneOffset = zoneOffset(startTime),
|
|
333
|
-
percentage = Percentage(value)
|
|
469
|
+
percentage = Percentage(value),
|
|
470
|
+
metadata = recordMetadata
|
|
334
471
|
)
|
|
335
472
|
client.insertRecords(listOf(record))
|
|
336
473
|
}
|
|
@@ -338,7 +475,8 @@ class HealthManager {
|
|
|
338
475
|
val record = RestingHeartRateRecord(
|
|
339
476
|
time = startTime,
|
|
340
477
|
zoneOffset = zoneOffset(startTime),
|
|
341
|
-
beatsPerMinute = value.toBpmLong()
|
|
478
|
+
beatsPerMinute = value.toBpmLong(),
|
|
479
|
+
metadata = recordMetadata
|
|
342
480
|
)
|
|
343
481
|
client.insertRecords(listOf(record))
|
|
344
482
|
}
|
|
@@ -346,7 +484,119 @@ class HealthManager {
|
|
|
346
484
|
val record = HeartRateVariabilityRmssdRecord(
|
|
347
485
|
time = startTime,
|
|
348
486
|
zoneOffset = zoneOffset(startTime),
|
|
349
|
-
heartRateVariabilityMillis = value
|
|
487
|
+
heartRateVariabilityMillis = value,
|
|
488
|
+
metadata = recordMetadata
|
|
489
|
+
)
|
|
490
|
+
client.insertRecords(listOf(record))
|
|
491
|
+
}
|
|
492
|
+
HealthDataType.BLOOD_PRESSURE -> {
|
|
493
|
+
if (systolic == null || diastolic == null) {
|
|
494
|
+
throw IllegalArgumentException("Blood pressure requires both systolic and diastolic values")
|
|
495
|
+
}
|
|
496
|
+
val record = BloodPressureRecord(
|
|
497
|
+
time = startTime,
|
|
498
|
+
zoneOffset = zoneOffset(startTime),
|
|
499
|
+
metadata = recordMetadata,
|
|
500
|
+
systolic = androidx.health.connect.client.units.Pressure.millimetersOfMercury(systolic),
|
|
501
|
+
diastolic = androidx.health.connect.client.units.Pressure.millimetersOfMercury(diastolic)
|
|
502
|
+
)
|
|
503
|
+
client.insertRecords(listOf(record))
|
|
504
|
+
}
|
|
505
|
+
HealthDataType.BLOOD_GLUCOSE -> {
|
|
506
|
+
val record = BloodGlucoseRecord(
|
|
507
|
+
time = startTime,
|
|
508
|
+
zoneOffset = zoneOffset(startTime),
|
|
509
|
+
metadata = recordMetadata,
|
|
510
|
+
level = androidx.health.connect.client.units.BloodGlucose.milligramsPerDeciliter(value)
|
|
511
|
+
)
|
|
512
|
+
client.insertRecords(listOf(record))
|
|
513
|
+
}
|
|
514
|
+
HealthDataType.BODY_TEMPERATURE -> {
|
|
515
|
+
val record = BodyTemperatureRecord(
|
|
516
|
+
time = startTime,
|
|
517
|
+
zoneOffset = zoneOffset(startTime),
|
|
518
|
+
metadata = recordMetadata,
|
|
519
|
+
temperature = androidx.health.connect.client.units.Temperature.celsius(value)
|
|
520
|
+
)
|
|
521
|
+
client.insertRecords(listOf(record))
|
|
522
|
+
}
|
|
523
|
+
HealthDataType.HEIGHT -> {
|
|
524
|
+
val record = HeightRecord(
|
|
525
|
+
time = startTime,
|
|
526
|
+
zoneOffset = zoneOffset(startTime),
|
|
527
|
+
height = Length.meters(value / 100.0), // Convert from centimeters to meters
|
|
528
|
+
metadata = recordMetadata
|
|
529
|
+
)
|
|
530
|
+
client.insertRecords(listOf(record))
|
|
531
|
+
}
|
|
532
|
+
HealthDataType.FLIGHTS_CLIMBED -> {
|
|
533
|
+
val record = FloorsClimbedRecord(
|
|
534
|
+
startTime = startTime,
|
|
535
|
+
startZoneOffset = zoneOffset(startTime),
|
|
536
|
+
endTime = endTime,
|
|
537
|
+
endZoneOffset = zoneOffset(endTime),
|
|
538
|
+
floors = value,
|
|
539
|
+
metadata = recordMetadata
|
|
540
|
+
)
|
|
541
|
+
client.insertRecords(listOf(record))
|
|
542
|
+
}
|
|
543
|
+
HealthDataType.DISTANCE_CYCLING -> {
|
|
544
|
+
val record = DistanceRecord(
|
|
545
|
+
startTime = startTime,
|
|
546
|
+
startZoneOffset = zoneOffset(startTime),
|
|
547
|
+
endTime = endTime,
|
|
548
|
+
endZoneOffset = zoneOffset(endTime),
|
|
549
|
+
distance = Length.meters(value),
|
|
550
|
+
metadata = recordMetadata
|
|
551
|
+
)
|
|
552
|
+
client.insertRecords(listOf(record))
|
|
553
|
+
}
|
|
554
|
+
HealthDataType.BODY_FAT -> {
|
|
555
|
+
val record = BodyFatRecord(
|
|
556
|
+
time = startTime,
|
|
557
|
+
zoneOffset = zoneOffset(startTime),
|
|
558
|
+
percentage = Percentage(value),
|
|
559
|
+
metadata = recordMetadata
|
|
560
|
+
)
|
|
561
|
+
client.insertRecords(listOf(record))
|
|
562
|
+
}
|
|
563
|
+
HealthDataType.BASAL_BODY_TEMPERATURE -> {
|
|
564
|
+
val record = BasalBodyTemperatureRecord(
|
|
565
|
+
time = startTime,
|
|
566
|
+
zoneOffset = zoneOffset(startTime),
|
|
567
|
+
metadata = recordMetadata,
|
|
568
|
+
temperature = androidx.health.connect.client.units.Temperature.celsius(value)
|
|
569
|
+
)
|
|
570
|
+
client.insertRecords(listOf(record))
|
|
571
|
+
}
|
|
572
|
+
HealthDataType.BASAL_CALORIES -> {
|
|
573
|
+
val record = BasalMetabolicRateRecord(
|
|
574
|
+
time = startTime,
|
|
575
|
+
zoneOffset = zoneOffset(startTime),
|
|
576
|
+
basalMetabolicRate = Power.kilocaloriesPerDay(value),
|
|
577
|
+
metadata = recordMetadata
|
|
578
|
+
)
|
|
579
|
+
client.insertRecords(listOf(record))
|
|
580
|
+
}
|
|
581
|
+
HealthDataType.TOTAL_CALORIES -> {
|
|
582
|
+
val record = TotalCaloriesBurnedRecord(
|
|
583
|
+
startTime = startTime,
|
|
584
|
+
startZoneOffset = zoneOffset(startTime),
|
|
585
|
+
endTime = endTime,
|
|
586
|
+
endZoneOffset = zoneOffset(endTime),
|
|
587
|
+
energy = Energy.kilocalories(value),
|
|
588
|
+
metadata = recordMetadata
|
|
589
|
+
)
|
|
590
|
+
client.insertRecords(listOf(record))
|
|
591
|
+
}
|
|
592
|
+
HealthDataType.MINDFULNESS -> {
|
|
593
|
+
val record = MindfulnessSessionRecord(
|
|
594
|
+
startTime = startTime,
|
|
595
|
+
startZoneOffset = zoneOffset(startTime),
|
|
596
|
+
endTime = endTime,
|
|
597
|
+
endZoneOffset = zoneOffset(endTime),
|
|
598
|
+
metadata = recordMetadata,
|
|
599
|
+
mindfulnessSessionType = MindfulnessSessionRecord.MINDFULNESS_SESSION_TYPE_UNKNOWN
|
|
350
600
|
)
|
|
351
601
|
client.insertRecords(listOf(record))
|
|
352
602
|
}
|
|
@@ -245,11 +245,14 @@ class HealthPlugin : Plugin() {
|
|
|
245
245
|
}
|
|
246
246
|
map.takeIf { it.isNotEmpty() }
|
|
247
247
|
}
|
|
248
|
+
|
|
249
|
+
val systolic = call.getDouble("systolic")
|
|
250
|
+
val diastolic = call.getDouble("diastolic")
|
|
248
251
|
|
|
249
252
|
pluginScope.launch {
|
|
250
253
|
val client = getClientOrReject(call) ?: return@launch
|
|
251
254
|
try {
|
|
252
|
-
manager.saveSample(client, dataType, value, startInstant, endInstant, metadata)
|
|
255
|
+
manager.saveSample(client, dataType, value, startInstant, endInstant, metadata, systolic, diastolic)
|
|
253
256
|
call.resolve()
|
|
254
257
|
} catch (e: Exception) {
|
|
255
258
|
call.reject(e.message ?: "Failed to save sample.", null, e)
|
package/dist/docs.json
CHANGED
|
@@ -397,6 +397,20 @@
|
|
|
397
397
|
"SleepState"
|
|
398
398
|
],
|
|
399
399
|
"type": "SleepState"
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"name": "systolic",
|
|
403
|
+
"tags": [],
|
|
404
|
+
"docs": "For blood pressure data, the systolic value in mmHg.",
|
|
405
|
+
"complexTypes": [],
|
|
406
|
+
"type": "number | undefined"
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
"name": "diastolic",
|
|
410
|
+
"tags": [],
|
|
411
|
+
"docs": "For blood pressure data, the diastolic value in mmHg.",
|
|
412
|
+
"complexTypes": [],
|
|
413
|
+
"type": "number | undefined"
|
|
400
414
|
}
|
|
401
415
|
]
|
|
402
416
|
},
|
|
@@ -500,6 +514,20 @@
|
|
|
500
514
|
"Record"
|
|
501
515
|
],
|
|
502
516
|
"type": "Record<string, string>"
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
"name": "systolic",
|
|
520
|
+
"tags": [],
|
|
521
|
+
"docs": "For blood pressure data, the systolic value in mmHg. Required when dataType is 'bloodPressure'.",
|
|
522
|
+
"complexTypes": [],
|
|
523
|
+
"type": "number | undefined"
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
"name": "diastolic",
|
|
527
|
+
"tags": [],
|
|
528
|
+
"docs": "For blood pressure data, the diastolic value in mmHg. Required when dataType is 'bloodPressure'.",
|
|
529
|
+
"complexTypes": [],
|
|
530
|
+
"type": "number | undefined"
|
|
503
531
|
}
|
|
504
532
|
]
|
|
505
533
|
},
|
|
@@ -811,6 +839,54 @@
|
|
|
811
839
|
{
|
|
812
840
|
"text": "'heartRateVariability'",
|
|
813
841
|
"complexTypes": []
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
"text": "'bloodPressure'",
|
|
845
|
+
"complexTypes": []
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
"text": "'bloodGlucose'",
|
|
849
|
+
"complexTypes": []
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
"text": "'bodyTemperature'",
|
|
853
|
+
"complexTypes": []
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
"text": "'height'",
|
|
857
|
+
"complexTypes": []
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
"text": "'flightsClimbed'",
|
|
861
|
+
"complexTypes": []
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
"text": "'exerciseTime'",
|
|
865
|
+
"complexTypes": []
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
"text": "'distanceCycling'",
|
|
869
|
+
"complexTypes": []
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
"text": "'bodyFat'",
|
|
873
|
+
"complexTypes": []
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
"text": "'basalBodyTemperature'",
|
|
877
|
+
"complexTypes": []
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
"text": "'basalCalories'",
|
|
881
|
+
"complexTypes": []
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
"text": "'totalCalories'",
|
|
885
|
+
"complexTypes": []
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
"text": "'mindfulness'",
|
|
889
|
+
"complexTypes": []
|
|
814
890
|
}
|
|
815
891
|
]
|
|
816
892
|
},
|
|
@@ -850,6 +926,26 @@
|
|
|
850
926
|
{
|
|
851
927
|
"text": "'millisecond'",
|
|
852
928
|
"complexTypes": []
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
"text": "'mmHg'",
|
|
932
|
+
"complexTypes": []
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
"text": "'mg/dL'",
|
|
936
|
+
"complexTypes": []
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
"text": "'celsius'",
|
|
940
|
+
"complexTypes": []
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
"text": "'fahrenheit'",
|
|
944
|
+
"complexTypes": []
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
"text": "'centimeter'",
|
|
948
|
+
"complexTypes": []
|
|
853
949
|
}
|
|
854
950
|
]
|
|
855
951
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type HealthDataType = 'steps' | 'distance' | 'calories' | 'heartRate' | 'weight' | 'sleep' | 'respiratoryRate' | 'oxygenSaturation' | 'restingHeartRate' | 'heartRateVariability';
|
|
2
|
-
export type HealthUnit = 'count' | 'meter' | 'kilocalorie' | 'bpm' | 'kilogram' | 'minute' | 'percent' | 'millisecond';
|
|
1
|
+
export type HealthDataType = 'steps' | 'distance' | 'calories' | 'heartRate' | 'weight' | 'sleep' | 'respiratoryRate' | 'oxygenSaturation' | 'restingHeartRate' | 'heartRateVariability' | 'bloodPressure' | 'bloodGlucose' | 'bodyTemperature' | 'height' | 'flightsClimbed' | 'exerciseTime' | 'distanceCycling' | 'bodyFat' | 'basalBodyTemperature' | 'basalCalories' | 'totalCalories' | 'mindfulness';
|
|
2
|
+
export type HealthUnit = 'count' | 'meter' | 'kilocalorie' | 'bpm' | 'kilogram' | 'minute' | 'percent' | 'millisecond' | 'mmHg' | 'mg/dL' | 'celsius' | 'fahrenheit' | 'centimeter';
|
|
3
3
|
export interface AuthorizationOptions {
|
|
4
4
|
/** Data types that should be readable after authorization. */
|
|
5
5
|
read?: HealthDataType[];
|
|
@@ -41,6 +41,10 @@ export interface HealthSample {
|
|
|
41
41
|
sourceId?: string;
|
|
42
42
|
/** For sleep data, indicates the sleep state (e.g., 'asleep', 'awake', 'rem', 'deep', 'light'). */
|
|
43
43
|
sleepState?: SleepState;
|
|
44
|
+
/** For blood pressure data, the systolic value in mmHg. */
|
|
45
|
+
systolic?: number;
|
|
46
|
+
/** For blood pressure data, the diastolic value in mmHg. */
|
|
47
|
+
diastolic?: number;
|
|
44
48
|
}
|
|
45
49
|
export interface ReadSamplesResult {
|
|
46
50
|
samples: HealthSample[];
|
|
@@ -106,6 +110,10 @@ export interface WriteSampleOptions {
|
|
|
106
110
|
endDate?: string;
|
|
107
111
|
/** Metadata key-value pairs forwarded to the native APIs where supported. */
|
|
108
112
|
metadata?: Record<string, string>;
|
|
113
|
+
/** For blood pressure data, the systolic value in mmHg. Required when dataType is 'bloodPressure'. */
|
|
114
|
+
systolic?: number;
|
|
115
|
+
/** For blood pressure data, the diastolic value in mmHg. Required when dataType is 'bloodPressure'. */
|
|
116
|
+
diastolic?: number;
|
|
109
117
|
}
|
|
110
118
|
export type BucketType = 'hour' | 'day' | 'week' | 'month';
|
|
111
119
|
export type AggregationType = 'sum' | 'average' | 'min' | 'max';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export type HealthDataType =\n | 'steps'\n | 'distance'\n | 'calories'\n | 'heartRate'\n | 'weight'\n | 'sleep'\n | 'respiratoryRate'\n | 'oxygenSaturation'\n | 'restingHeartRate'\n | 'heartRateVariability';\n\nexport type HealthUnit
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export type HealthDataType =\n | 'steps'\n | 'distance'\n | 'calories'\n | 'heartRate'\n | 'weight'\n | 'sleep'\n | 'respiratoryRate'\n | 'oxygenSaturation'\n | 'restingHeartRate'\n | 'heartRateVariability'\n | 'bloodPressure'\n | 'bloodGlucose'\n | 'bodyTemperature'\n | 'height'\n | 'flightsClimbed'\n | 'exerciseTime'\n | 'distanceCycling'\n | 'bodyFat'\n | 'basalBodyTemperature'\n | 'basalCalories'\n | 'totalCalories'\n | 'mindfulness';\n\nexport type HealthUnit =\n | 'count'\n | 'meter'\n | 'kilocalorie'\n | 'bpm'\n | 'kilogram'\n | 'minute'\n | 'percent'\n | 'millisecond'\n | 'mmHg'\n | 'mg/dL'\n | 'celsius'\n | 'fahrenheit'\n | 'centimeter';\n\nexport interface AuthorizationOptions {\n /** Data types that should be readable after authorization. */\n read?: HealthDataType[];\n /** Data types that should be writable after authorization. */\n write?: HealthDataType[];\n}\n\nexport interface AuthorizationStatus {\n readAuthorized: HealthDataType[];\n readDenied: HealthDataType[];\n writeAuthorized: HealthDataType[];\n writeDenied: HealthDataType[];\n}\n\nexport interface AvailabilityResult {\n available: boolean;\n /** Platform specific details (for debugging/diagnostics). */\n platform?: 'ios' | 'android' | 'web';\n reason?: string;\n}\n\nexport interface QueryOptions {\n /** The type of data to retrieve from the health store. */\n dataType: HealthDataType;\n /** Inclusive ISO 8601 start date (defaults to now - 1 day). */\n startDate?: string;\n /** Exclusive ISO 8601 end date (defaults to now). */\n endDate?: string;\n /** Maximum number of samples to return (defaults to 100). */\n limit?: number;\n /** Return results sorted ascending by start date (defaults to false). */\n ascending?: boolean;\n}\n\nexport type SleepState = 'inBed' | 'asleep' | 'awake' | 'rem' | 'deep' | 'light';\n\nexport interface HealthSample {\n dataType: HealthDataType;\n value: number;\n unit: HealthUnit;\n startDate: string;\n endDate: string;\n sourceName?: string;\n sourceId?: string;\n /** For sleep data, indicates the sleep state (e.g., 'asleep', 'awake', 'rem', 'deep', 'light'). */\n sleepState?: SleepState;\n /** For blood pressure data, the systolic value in mmHg. */\n systolic?: number;\n /** For blood pressure data, the diastolic value in mmHg. */\n diastolic?: number;\n}\n\nexport interface ReadSamplesResult {\n samples: HealthSample[];\n}\n\nexport type WorkoutType =\n | 'running'\n | 'cycling'\n | 'walking'\n | 'swimming'\n | 'yoga'\n | 'strengthTraining'\n | 'hiking'\n | 'tennis'\n | 'basketball'\n | 'soccer'\n | 'americanFootball'\n | 'baseball'\n | 'crossTraining'\n | 'elliptical'\n | 'rowing'\n | 'stairClimbing'\n | 'traditionalStrengthTraining'\n | 'waterFitness'\n | 'waterPolo'\n | 'waterSports'\n | 'wrestling'\n | 'other';\n\nexport interface QueryWorkoutsOptions {\n /** Optional workout type filter. If omitted, all workout types are returned. */\n workoutType?: WorkoutType;\n /** Inclusive ISO 8601 start date (defaults to now - 1 day). */\n startDate?: string;\n /** Exclusive ISO 8601 end date (defaults to now). */\n endDate?: string;\n /** Maximum number of workouts to return (defaults to 100). */\n limit?: number;\n /** Return results sorted ascending by start date (defaults to false). */\n ascending?: boolean;\n /**\n * Anchor for pagination. Use the anchor returned from a previous query to continue from that point.\n * On iOS, this uses HKQueryAnchor. On Android, this uses Health Connect's pageToken.\n * Omit this parameter to start from the beginning.\n */\n anchor?: string;\n}\n\nexport interface Workout {\n /** The type of workout. */\n workoutType: WorkoutType;\n /** Duration of the workout in seconds. */\n duration: number;\n /** Total energy burned in kilocalories (if available). */\n totalEnergyBurned?: number;\n /** Total distance in meters (if available). */\n totalDistance?: number;\n /** ISO 8601 start date of the workout. */\n startDate: string;\n /** ISO 8601 end date of the workout. */\n endDate: string;\n /** Source name that recorded the workout. */\n sourceName?: string;\n /** Source bundle identifier. */\n sourceId?: string;\n /** Additional metadata (if available). */\n metadata?: Record<string, string>;\n}\n\nexport interface QueryWorkoutsResult {\n workouts: Workout[];\n /**\n * Anchor for the next page of results. Pass this value as the anchor parameter in the next query\n * to continue pagination. If undefined or null, there are no more results.\n */\n anchor?: string;\n}\n\nexport interface WriteSampleOptions {\n dataType: HealthDataType;\n value: number;\n /**\n * Optional unit override. If omitted, the default unit for the data type is used\n * (count for `steps`, meter for `distance`, kilocalorie for `calories`, bpm for `heartRate`, kilogram for `weight`).\n */\n unit?: HealthUnit;\n /** ISO 8601 start date for the sample. Defaults to now. */\n startDate?: string;\n /** ISO 8601 end date for the sample. Defaults to startDate. */\n endDate?: string;\n /** Metadata key-value pairs forwarded to the native APIs where supported. */\n metadata?: Record<string, string>;\n /** For blood pressure data, the systolic value in mmHg. Required when dataType is 'bloodPressure'. */\n systolic?: number;\n /** For blood pressure data, the diastolic value in mmHg. Required when dataType is 'bloodPressure'. */\n diastolic?: number;\n}\n\nexport type BucketType = 'hour' | 'day' | 'week' | 'month';\n\nexport type AggregationType = 'sum' | 'average' | 'min' | 'max';\n\nexport interface QueryAggregatedOptions {\n /** The type of data to aggregate from the health store. */\n dataType: HealthDataType;\n /** Inclusive ISO 8601 start date (defaults to now - 1 day). */\n startDate?: string;\n /** Exclusive ISO 8601 end date (defaults to now). */\n endDate?: string;\n /** Time bucket for aggregation (defaults to 'day'). */\n bucket?: BucketType;\n /** Aggregation operation to perform (defaults to 'sum'). */\n aggregation?: AggregationType;\n}\n\nexport interface AggregatedSample {\n /** ISO 8601 start date of the bucket. */\n startDate: string;\n /** ISO 8601 end date of the bucket. */\n endDate: string;\n /** Aggregated value for the bucket. */\n value: number;\n /** Unit of the aggregated value. */\n unit: HealthUnit;\n}\n\nexport interface QueryAggregatedResult {\n samples: AggregatedSample[];\n}\n\nexport interface HealthPlugin {\n /** Returns whether the current platform supports the native health SDK. */\n isAvailable(): Promise<AvailabilityResult>;\n /** Requests read/write access to the provided data types. */\n requestAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>;\n /** Checks authorization status for the provided data types without prompting the user. */\n checkAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>;\n /** Reads samples for the given data type within the specified time frame. */\n readSamples(options: QueryOptions): Promise<ReadSamplesResult>;\n /** Writes a single sample to the native health store. */\n saveSample(options: WriteSampleOptions): Promise<void>;\n\n /**\n * Get the native Capacitor plugin version\n *\n * @returns {Promise<{ version: string }>} a Promise with version for this device\n * @throws An error if something went wrong\n */\n getPluginVersion(): Promise<{ version: string }>;\n\n /**\n * Opens the Health Connect settings screen (Android only).\n * On iOS, this method does nothing.\n *\n * Use this to direct users to manage their Health Connect permissions\n * or to install Health Connect if not available.\n *\n * @throws An error if Health Connect settings cannot be opened\n */\n openHealthConnectSettings(): Promise<void>;\n\n /**\n * Shows the app's privacy policy for Health Connect (Android only).\n * On iOS, this method does nothing.\n *\n * This displays the same privacy policy screen that Health Connect shows\n * when the user taps \"Privacy policy\" in the permissions dialog.\n *\n * The privacy policy URL can be configured by adding a string resource\n * named \"health_connect_privacy_policy_url\" in your app's strings.xml,\n * or by placing an HTML file at www/privacypolicy.html in your assets.\n *\n * @throws An error if the privacy policy cannot be displayed\n */\n showPrivacyPolicy(): Promise<void>;\n\n /**\n * Queries workout sessions from the native health store.\n * Supported on iOS (HealthKit) and Android (Health Connect).\n *\n * @param options Query options including optional workout type filter, date range, limit, and sort order\n * @returns A promise that resolves with the workout sessions\n * @throws An error if something went wrong\n */\n queryWorkouts(options: QueryWorkoutsOptions): Promise<QueryWorkoutsResult>;\n\n /**\n * Queries aggregated health data from the native health store.\n * Aggregates data into time buckets (hour, day, week, month) with operations like sum, average, min, or max.\n * This is more efficient than fetching individual samples for large date ranges.\n *\n * Supported on iOS (HealthKit) and Android (Health Connect).\n *\n * @param options Query options including data type, date range, bucket size, and aggregation type\n * @returns A promise that resolves with the aggregated samples\n * @throws An error if something went wrong\n */\n queryAggregated(options: QueryAggregatedOptions): Promise<QueryAggregatedResult>;\n}\n"]}
|
|
@@ -160,6 +160,18 @@ enum HealthDataType: String, CaseIterable {
|
|
|
160
160
|
case oxygenSaturation
|
|
161
161
|
case restingHeartRate
|
|
162
162
|
case heartRateVariability
|
|
163
|
+
case bloodPressure
|
|
164
|
+
case bloodGlucose
|
|
165
|
+
case bodyTemperature
|
|
166
|
+
case height
|
|
167
|
+
case flightsClimbed
|
|
168
|
+
case exerciseTime
|
|
169
|
+
case distanceCycling
|
|
170
|
+
case bodyFat
|
|
171
|
+
case basalBodyTemperature
|
|
172
|
+
case basalCalories
|
|
173
|
+
case totalCalories
|
|
174
|
+
case mindfulness
|
|
163
175
|
|
|
164
176
|
func sampleType() throws -> HKSampleType {
|
|
165
177
|
switch self {
|
|
@@ -168,6 +180,16 @@ enum HealthDataType: String, CaseIterable {
|
|
|
168
180
|
throw HealthManagerError.dataTypeUnavailable(rawValue)
|
|
169
181
|
}
|
|
170
182
|
return type
|
|
183
|
+
case .bloodPressure:
|
|
184
|
+
guard let type = HKObjectType.correlationType(forIdentifier: .bloodPressure) else {
|
|
185
|
+
throw HealthManagerError.dataTypeUnavailable(rawValue)
|
|
186
|
+
}
|
|
187
|
+
return type
|
|
188
|
+
case .mindfulness:
|
|
189
|
+
guard let type = HKObjectType.categoryType(forIdentifier: .mindfulSession) else {
|
|
190
|
+
throw HealthManagerError.dataTypeUnavailable(rawValue)
|
|
191
|
+
}
|
|
192
|
+
return type
|
|
171
193
|
default:
|
|
172
194
|
return try quantityType()
|
|
173
195
|
}
|
|
@@ -194,8 +216,32 @@ enum HealthDataType: String, CaseIterable {
|
|
|
194
216
|
identifier = .restingHeartRate
|
|
195
217
|
case .heartRateVariability:
|
|
196
218
|
identifier = .heartRateVariabilitySDNN
|
|
219
|
+
case .bloodGlucose:
|
|
220
|
+
identifier = .bloodGlucose
|
|
221
|
+
case .bodyTemperature:
|
|
222
|
+
identifier = .bodyTemperature
|
|
223
|
+
case .height:
|
|
224
|
+
identifier = .height
|
|
225
|
+
case .flightsClimbed:
|
|
226
|
+
identifier = .flightsClimbed
|
|
227
|
+
case .exerciseTime:
|
|
228
|
+
identifier = .appleExerciseTime
|
|
229
|
+
case .distanceCycling:
|
|
230
|
+
identifier = .distanceCycling
|
|
231
|
+
case .bodyFat:
|
|
232
|
+
identifier = .bodyFatPercentage
|
|
233
|
+
case .basalBodyTemperature:
|
|
234
|
+
identifier = .basalBodyTemperature
|
|
235
|
+
case .basalCalories:
|
|
236
|
+
identifier = .basalEnergyBurned
|
|
237
|
+
case .totalCalories:
|
|
238
|
+
identifier = .activeEnergyBurned
|
|
197
239
|
case .sleep:
|
|
198
240
|
throw HealthManagerError.invalidDataType("Sleep is a category type, not a quantity type")
|
|
241
|
+
case .bloodPressure:
|
|
242
|
+
throw HealthManagerError.invalidDataType("Blood pressure is a correlation type, not a quantity type")
|
|
243
|
+
case .mindfulness:
|
|
244
|
+
throw HealthManagerError.invalidDataType("Mindfulness is a category type, not a quantity type")
|
|
199
245
|
}
|
|
200
246
|
|
|
201
247
|
guard let type = HKObjectType.quantityType(forIdentifier: identifier) else {
|
|
@@ -224,6 +270,28 @@ enum HealthDataType: String, CaseIterable {
|
|
|
224
270
|
return HKUnit.secondUnit(with: .milli)
|
|
225
271
|
case .sleep:
|
|
226
272
|
return HKUnit.minute()
|
|
273
|
+
case .bloodPressure:
|
|
274
|
+
return HKUnit.millimeterOfMercury()
|
|
275
|
+
case .bloodGlucose:
|
|
276
|
+
return HKUnit.gramUnit(with: .milli).unitDivided(by: HKUnit.literUnit(with: .deci))
|
|
277
|
+
case .bodyTemperature, .basalBodyTemperature:
|
|
278
|
+
return HKUnit.degreeCelsius()
|
|
279
|
+
case .height:
|
|
280
|
+
return HKUnit.meterUnit(with: .centi)
|
|
281
|
+
case .flightsClimbed:
|
|
282
|
+
return HKUnit.count()
|
|
283
|
+
case .exerciseTime:
|
|
284
|
+
return HKUnit.minute()
|
|
285
|
+
case .distanceCycling:
|
|
286
|
+
return HKUnit.meter()
|
|
287
|
+
case .bodyFat:
|
|
288
|
+
return HKUnit.percent()
|
|
289
|
+
case .basalCalories:
|
|
290
|
+
return HKUnit.kilocalorie()
|
|
291
|
+
case .totalCalories:
|
|
292
|
+
return HKUnit.kilocalorie()
|
|
293
|
+
case .mindfulness:
|
|
294
|
+
return HKUnit.minute()
|
|
227
295
|
}
|
|
228
296
|
}
|
|
229
297
|
|
|
@@ -245,6 +313,28 @@ enum HealthDataType: String, CaseIterable {
|
|
|
245
313
|
return "millisecond"
|
|
246
314
|
case .sleep:
|
|
247
315
|
return "minute"
|
|
316
|
+
case .bloodPressure:
|
|
317
|
+
return "mmHg"
|
|
318
|
+
case .bloodGlucose:
|
|
319
|
+
return "mg/dL"
|
|
320
|
+
case .bodyTemperature, .basalBodyTemperature:
|
|
321
|
+
return "celsius"
|
|
322
|
+
case .height:
|
|
323
|
+
return "centimeter"
|
|
324
|
+
case .flightsClimbed:
|
|
325
|
+
return "count"
|
|
326
|
+
case .exerciseTime:
|
|
327
|
+
return "minute"
|
|
328
|
+
case .distanceCycling:
|
|
329
|
+
return "meter"
|
|
330
|
+
case .bodyFat:
|
|
331
|
+
return "percent"
|
|
332
|
+
case .basalCalories:
|
|
333
|
+
return "kilocalorie"
|
|
334
|
+
case .totalCalories:
|
|
335
|
+
return "kilocalorie"
|
|
336
|
+
case .mindfulness:
|
|
337
|
+
return "minute"
|
|
248
338
|
}
|
|
249
339
|
}
|
|
250
340
|
|
|
@@ -415,6 +505,94 @@ final class Health {
|
|
|
415
505
|
healthStore.execute(query)
|
|
416
506
|
return
|
|
417
507
|
}
|
|
508
|
+
|
|
509
|
+
// Handle mindfulness as a category sample
|
|
510
|
+
if dataType == .mindfulness {
|
|
511
|
+
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: queryLimit, sortDescriptors: [sortDescriptor]) { [weak self] _, samples, error in
|
|
512
|
+
guard let self = self else { return }
|
|
513
|
+
|
|
514
|
+
if let error = error {
|
|
515
|
+
completion(.failure(error))
|
|
516
|
+
return
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
guard let categorySamples = samples as? [HKCategorySample] else {
|
|
520
|
+
completion(.success([]))
|
|
521
|
+
return
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
let results = categorySamples.map { sample -> [String: Any] in
|
|
525
|
+
let durationMinutes = sample.endDate.timeIntervalSince(sample.startDate) / 60.0
|
|
526
|
+
|
|
527
|
+
var payload: [String: Any] = [
|
|
528
|
+
"dataType": dataType.rawValue,
|
|
529
|
+
"value": durationMinutes,
|
|
530
|
+
"unit": dataType.unitIdentifier,
|
|
531
|
+
"startDate": self.isoFormatter.string(from: sample.startDate),
|
|
532
|
+
"endDate": self.isoFormatter.string(from: sample.endDate)
|
|
533
|
+
]
|
|
534
|
+
|
|
535
|
+
let source = sample.sourceRevision.source
|
|
536
|
+
payload["sourceName"] = source.name
|
|
537
|
+
payload["sourceId"] = source.bundleIdentifier
|
|
538
|
+
|
|
539
|
+
return payload
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
completion(.success(results))
|
|
543
|
+
}
|
|
544
|
+
healthStore.execute(query)
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Handle blood pressure as a correlation sample
|
|
549
|
+
if dataType == .bloodPressure {
|
|
550
|
+
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: queryLimit, sortDescriptors: [sortDescriptor]) { [weak self] _, samples, error in
|
|
551
|
+
guard let self = self else { return }
|
|
552
|
+
|
|
553
|
+
if let error = error {
|
|
554
|
+
completion(.failure(error))
|
|
555
|
+
return
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
guard let correlations = samples as? [HKCorrelation] else {
|
|
559
|
+
completion(.success([]))
|
|
560
|
+
return
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
let results = correlations.compactMap { correlation -> [String: Any]? in
|
|
564
|
+
guard let systolicType = HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic),
|
|
565
|
+
let diastolicType = HKObjectType.quantityType(forIdentifier: .bloodPressureDiastolic),
|
|
566
|
+
let systolicSample = correlation.objects(for: systolicType).first as? HKQuantitySample,
|
|
567
|
+
let diastolicSample = correlation.objects(for: diastolicType).first as? HKQuantitySample else {
|
|
568
|
+
return nil
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
let systolicValue = systolicSample.quantity.doubleValue(for: HKUnit.millimeterOfMercury())
|
|
572
|
+
let diastolicValue = diastolicSample.quantity.doubleValue(for: HKUnit.millimeterOfMercury())
|
|
573
|
+
|
|
574
|
+
var payload: [String: Any] = [
|
|
575
|
+
"dataType": dataType.rawValue,
|
|
576
|
+
"value": systolicValue,
|
|
577
|
+
"unit": dataType.unitIdentifier,
|
|
578
|
+
"startDate": self.isoFormatter.string(from: correlation.startDate),
|
|
579
|
+
"endDate": self.isoFormatter.string(from: correlation.endDate),
|
|
580
|
+
"systolic": systolicValue,
|
|
581
|
+
"diastolic": diastolicValue
|
|
582
|
+
]
|
|
583
|
+
|
|
584
|
+
let source = correlation.sourceRevision.source
|
|
585
|
+
payload["sourceName"] = source.name
|
|
586
|
+
payload["sourceId"] = source.bundleIdentifier
|
|
587
|
+
|
|
588
|
+
return payload
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
completion(.success(results))
|
|
592
|
+
}
|
|
593
|
+
healthStore.execute(query)
|
|
594
|
+
return
|
|
595
|
+
}
|
|
418
596
|
|
|
419
597
|
// Handle quantity samples
|
|
420
598
|
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: queryLimit, sortDescriptors: [sortDescriptor]) { [weak self] _, samples, error in
|
|
@@ -481,7 +659,7 @@ final class Health {
|
|
|
481
659
|
}
|
|
482
660
|
}
|
|
483
661
|
|
|
484
|
-
func saveSample(dataTypeIdentifier: String, value: Double, unitIdentifier: String?, startDateString: String?, endDateString: String?, metadata: [String: String]?, completion: @escaping (Result<Void, Error>) -> Void) throws {
|
|
662
|
+
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
663
|
guard HKHealthStore.isHealthDataAvailable() else {
|
|
486
664
|
throw HealthManagerError.healthDataUnavailable
|
|
487
665
|
}
|
|
@@ -526,6 +704,63 @@ final class Health {
|
|
|
526
704
|
}
|
|
527
705
|
return
|
|
528
706
|
}
|
|
707
|
+
|
|
708
|
+
// Handle mindfulness as a category sample
|
|
709
|
+
if dataType == .mindfulness {
|
|
710
|
+
guard let categoryType = HKObjectType.categoryType(forIdentifier: .mindfulSession) else {
|
|
711
|
+
throw HealthManagerError.dataTypeUnavailable(dataTypeIdentifier)
|
|
712
|
+
}
|
|
713
|
+
let sample = HKCategorySample(type: categoryType, value: 0, start: startDate, end: endDate, metadata: metadataDictionary)
|
|
714
|
+
|
|
715
|
+
healthStore.save(sample) { success, error in
|
|
716
|
+
if let error = error {
|
|
717
|
+
completion(.failure(error))
|
|
718
|
+
return
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if success {
|
|
722
|
+
completion(.success(()))
|
|
723
|
+
} else {
|
|
724
|
+
completion(.failure(HealthManagerError.operationFailed("Failed to save the sample.")))
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Handle blood pressure as a correlation sample
|
|
731
|
+
if dataType == .bloodPressure {
|
|
732
|
+
guard let systolicValue = systolic, let diastolicValue = diastolic else {
|
|
733
|
+
throw HealthManagerError.operationFailed("Blood pressure requires both systolic and diastolic values")
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
guard let systolicType = HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic),
|
|
737
|
+
let diastolicType = HKObjectType.quantityType(forIdentifier: .bloodPressureDiastolic),
|
|
738
|
+
let correlationType = HKObjectType.correlationType(forIdentifier: .bloodPressure) else {
|
|
739
|
+
throw HealthManagerError.dataTypeUnavailable(dataTypeIdentifier)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
let systolicQuantity = HKQuantity(unit: HKUnit.millimeterOfMercury(), doubleValue: systolicValue)
|
|
743
|
+
let diastolicQuantity = HKQuantity(unit: HKUnit.millimeterOfMercury(), doubleValue: diastolicValue)
|
|
744
|
+
|
|
745
|
+
let systolicSample = HKQuantitySample(type: systolicType, quantity: systolicQuantity, start: startDate, end: endDate)
|
|
746
|
+
let diastolicSample = HKQuantitySample(type: diastolicType, quantity: diastolicQuantity, start: startDate, end: endDate)
|
|
747
|
+
|
|
748
|
+
let correlation = HKCorrelation(type: correlationType, start: startDate, end: endDate, objects: [systolicSample, diastolicSample], metadata: metadataDictionary)
|
|
749
|
+
|
|
750
|
+
healthStore.save(correlation) { success, error in
|
|
751
|
+
if let error = error {
|
|
752
|
+
completion(.failure(error))
|
|
753
|
+
return
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if success {
|
|
757
|
+
completion(.success(()))
|
|
758
|
+
} else {
|
|
759
|
+
completion(.failure(HealthManagerError.operationFailed("Failed to save the sample.")))
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return
|
|
763
|
+
}
|
|
529
764
|
|
|
530
765
|
// Handle quantity samples
|
|
531
766
|
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.2.
|
|
6
|
+
private let pluginVersion: String = "8.2.18"
|
|
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