@capgo/capacitor-health 8.2.17 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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>&lt;string, string&gt;</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
@@ -636,7 +675,7 @@ Construct a type with a set of properties K of type T
636
675
 
637
676
  #### WorkoutType
638
677
 
639
- <code>'running' | 'cycling' | 'walking' | 'swimming' | 'yoga' | 'strengthTraining' | 'hiking' | 'tennis' | 'basketball' | 'soccer' | 'americanFootball' | 'baseball' | 'crossTraining' | 'elliptical' | 'rowing' | 'stairClimbing' | 'traditionalStrengthTraining' | 'waterFitness' | 'waterPolo' | 'waterSports' | 'wrestling' | 'other'</code>
678
+ <code>'running' | 'cycling' | 'walking' | 'swimming' | 'yoga' | 'strengthTraining' | 'hiking' | 'tennis' | 'basketball' | 'soccer' | 'americanFootball' | 'baseball' | 'crossTraining' | 'elliptical' | 'rowing' | 'stairClimbing' | 'traditionalStrengthTraining' | 'waterFitness' | 'waterPolo' | 'waterSports' | 'wrestling' | 'archery' | 'australianFootball' | 'badminton' | 'barre' | 'bowling' | 'boxing' | 'climbing' | 'cooldown' | 'coreTraining' | 'cricket' | 'crossCountrySkiing' | 'curling' | 'dance' | 'discSports' | 'downhillSkiing' | 'equestrianSports' | 'fencing' | 'fishing' | 'fitnessGaming' | 'flexibility' | 'functionalStrengthTraining' | 'golf' | 'gymnastics' | 'handball' | 'handCycling' | 'highIntensityIntervalTraining' | 'hockey' | 'hunting' | 'jumpRope' | 'kickboxing' | 'lacrosse' | 'martialArts' | 'mindAndBody' | 'mixedCardio' | 'paddleSports' | 'pickleball' | 'pilates' | 'play' | 'preparationAndRecovery' | 'racquetball' | 'rugby' | 'sailing' | 'skatingSports' | 'snowboarding' | 'snowSports' | 'softball' | 'squash' | 'stairs' | 'stepTraining' | 'surfingSports' | 'tableTennis' | 'taiChi' | 'trackAndField' | 'transition' | 'underwaterDiving' | 'volleyball' | 'wheelchairRunPace' | 'wheelchairWalkPace' | 'cardioDance' | 'socialDance' | 'other'</code>
640
679
 
641
680
 
642
681
  #### BucketType
@@ -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-alpha10'
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)