@asiriindatissa/capacitor-health 8.4.2 → 8.4.4
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 +159 -1
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/app/capgo/plugin/health/HealthManager.kt +217 -2
- package/android/src/main/java/app/capgo/plugin/health/HealthPlugin.kt +138 -19
- package/dist/docs.json +376 -1
- package/dist/esm/definitions.d.ts +86 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +3 -1
- package/dist/esm/web.js +6 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +6 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +6 -0
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,6 +162,52 @@ const { workouts } = await Health.queryWorkouts({
|
|
|
162
162
|
- Workout energy and distance data are aggregated from separate Health Connect records during the workout time period. If you don't request permissions for `calories`/`totalCalories` and `distance`, these fields will be missing from workout results.
|
|
163
163
|
- If `totalEnergyBurned` or `totalDistance` are missing despite having permissions, it means no calorie or distance data was recorded during that workout period in Health Connect.
|
|
164
164
|
|
|
165
|
+
### Sleep
|
|
166
|
+
|
|
167
|
+
To query sleep sessions, you need to request read permission for `'sleep'`:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// Request permission to read sleep data
|
|
171
|
+
await Health.requestAuthorization({
|
|
172
|
+
read: ['sleep'],
|
|
173
|
+
write: [],
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Query recent sleep sessions
|
|
177
|
+
const { sleepSessions } = await Health.querySleep({
|
|
178
|
+
startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
179
|
+
endDate: new Date().toISOString(),
|
|
180
|
+
limit: 10,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Each sleep session includes:
|
|
184
|
+
// - duration (in seconds), startDate, endDate (always present)
|
|
185
|
+
// - title (optional, if provided by the source app)
|
|
186
|
+
// - stages (optional array of sleep stage records if available)
|
|
187
|
+
// - sourceName, sourceId (source app information)
|
|
188
|
+
|
|
189
|
+
// Sleep stages can be: 'unknown', 'awake', 'sleeping', 'outOfBed',
|
|
190
|
+
// 'awakeInBed', 'light', 'deep', 'rem'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Supported Sleep Stages:**
|
|
194
|
+
|
|
195
|
+
| Stage | Description |
|
|
196
|
+
| ------------ | ---------------------------------------------- |
|
|
197
|
+
| `unknown` | Unspecified or unknown if the user is sleeping |
|
|
198
|
+
| `awake` | The user is awake within a sleep cycle |
|
|
199
|
+
| `sleeping` | Generic or non-granular sleep description |
|
|
200
|
+
| `outOfBed` | The user gets out of bed during sleep session |
|
|
201
|
+
| `awakeInBed` | The user is awake in bed |
|
|
202
|
+
| `light` | Light sleep cycle |
|
|
203
|
+
| `deep` | Deep sleep cycle |
|
|
204
|
+
| `rem` | REM sleep cycle |
|
|
205
|
+
|
|
206
|
+
**Note:**
|
|
207
|
+
|
|
208
|
+
- `'sleep'` is a special read-only permission type. You cannot write sleep data with this plugin.
|
|
209
|
+
- Not all sleep sessions include detailed sleep stages. Some apps may only record the overall sleep duration.
|
|
210
|
+
|
|
165
211
|
## API
|
|
166
212
|
|
|
167
213
|
<docgen-index>
|
|
@@ -175,6 +221,8 @@ const { workouts } = await Health.queryWorkouts({
|
|
|
175
221
|
* [`openHealthConnectSettings()`](#openhealthconnectsettings)
|
|
176
222
|
* [`showPrivacyPolicy()`](#showprivacypolicy)
|
|
177
223
|
* [`queryWorkouts(...)`](#queryworkouts)
|
|
224
|
+
* [`querySleep(...)`](#querysleep)
|
|
225
|
+
* [`queryHydration(...)`](#queryhydration)
|
|
178
226
|
* [Interfaces](#interfaces)
|
|
179
227
|
* [Type Aliases](#type-aliases)
|
|
180
228
|
|
|
@@ -324,6 +372,40 @@ Queries workout sessions from the native health store on Android (Health Connect
|
|
|
324
372
|
--------------------
|
|
325
373
|
|
|
326
374
|
|
|
375
|
+
### querySleep(...)
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
querySleep(options: QuerySleepOptions) => Promise<QuerySleepResult>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Queries sleep sessions from the native health store on Android (Health Connect).
|
|
382
|
+
|
|
383
|
+
| Param | Type | Description |
|
|
384
|
+
| ------------- | --------------------------------------------------------------- | --------------------------------------------------------- |
|
|
385
|
+
| **`options`** | <code><a href="#querysleepoptions">QuerySleepOptions</a></code> | Query options including date range, limit, and sort order |
|
|
386
|
+
|
|
387
|
+
**Returns:** <code>Promise<<a href="#querysleepresult">QuerySleepResult</a>></code>
|
|
388
|
+
|
|
389
|
+
--------------------
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
### queryHydration(...)
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
queryHydration(options: QueryHydrationOptions) => Promise<QueryHydrationResult>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Queries hydration records from the native health store on Android (Health Connect).
|
|
399
|
+
|
|
400
|
+
| Param | Type | Description |
|
|
401
|
+
| ------------- | ----------------------------------------------------------------------- | --------------------------------------------------------- |
|
|
402
|
+
| **`options`** | <code><a href="#queryhydrationoptions">QueryHydrationOptions</a></code> | Query options including date range, limit, and sort order |
|
|
403
|
+
|
|
404
|
+
**Returns:** <code>Promise<<a href="#queryhydrationresult">QueryHydrationResult</a>></code>
|
|
405
|
+
|
|
406
|
+
--------------------
|
|
407
|
+
|
|
408
|
+
|
|
327
409
|
### Interfaces
|
|
328
410
|
|
|
329
411
|
|
|
@@ -430,6 +512,75 @@ Queries workout sessions from the native health store on Android (Health Connect
|
|
|
430
512
|
| **`ascending`** | <code>boolean</code> | Return results sorted ascending by start date (defaults to false). |
|
|
431
513
|
|
|
432
514
|
|
|
515
|
+
#### QuerySleepResult
|
|
516
|
+
|
|
517
|
+
| Prop | Type |
|
|
518
|
+
| ------------------- | --------------------------- |
|
|
519
|
+
| **`sleepSessions`** | <code>SleepSession[]</code> |
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
#### SleepSession
|
|
523
|
+
|
|
524
|
+
| Prop | Type | Description |
|
|
525
|
+
| ---------------- | --------------------------------------------------------------- | -------------------------------------------------------- |
|
|
526
|
+
| **`title`** | <code>string</code> | Title/name of the sleep session (if available). |
|
|
527
|
+
| **`duration`** | <code>number</code> | Duration of the sleep session in seconds. |
|
|
528
|
+
| **`startDate`** | <code>string</code> | ISO 8601 start date of the sleep session. |
|
|
529
|
+
| **`endDate`** | <code>string</code> | ISO 8601 end date of the sleep session. |
|
|
530
|
+
| **`stages`** | <code>SleepStageRecord[]</code> | Array of sleep stages during the session (if available). |
|
|
531
|
+
| **`sourceName`** | <code>string</code> | Source name that recorded the sleep session. |
|
|
532
|
+
| **`sourceId`** | <code>string</code> | Source bundle identifier. |
|
|
533
|
+
| **`metadata`** | <code><a href="#record">Record</a><string, string></code> | Additional metadata (if available). |
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
#### SleepStageRecord
|
|
537
|
+
|
|
538
|
+
| Prop | Type | Description |
|
|
539
|
+
| --------------- | ------------------------------------------------- | ---------------------------------------- |
|
|
540
|
+
| **`stage`** | <code><a href="#sleepstage">SleepStage</a></code> | The sleep stage type. |
|
|
541
|
+
| **`startDate`** | <code>string</code> | ISO 8601 start date of this sleep stage. |
|
|
542
|
+
| **`endDate`** | <code>string</code> | ISO 8601 end date of this sleep stage. |
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
#### QuerySleepOptions
|
|
546
|
+
|
|
547
|
+
| Prop | Type | Description |
|
|
548
|
+
| --------------- | -------------------- | ------------------------------------------------------------------ |
|
|
549
|
+
| **`startDate`** | <code>string</code> | Inclusive ISO 8601 start date (defaults to now - 1 day). |
|
|
550
|
+
| **`endDate`** | <code>string</code> | Exclusive ISO 8601 end date (defaults to now). |
|
|
551
|
+
| **`limit`** | <code>number</code> | Maximum number of sleep sessions to return (defaults to 100). |
|
|
552
|
+
| **`ascending`** | <code>boolean</code> | Return results sorted ascending by start date (defaults to false). |
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
#### QueryHydrationResult
|
|
556
|
+
|
|
557
|
+
| Prop | Type |
|
|
558
|
+
| ---------------------- | ------------------------------ |
|
|
559
|
+
| **`hydrationRecords`** | <code>HydrationRecord[]</code> |
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
#### HydrationRecord
|
|
563
|
+
|
|
564
|
+
| Prop | Type | Description |
|
|
565
|
+
| ---------------- | --------------------------------------------------------------- | -------------------------------------------- |
|
|
566
|
+
| **`volume`** | <code>number</code> | Volume of water consumed in liters. |
|
|
567
|
+
| **`startDate`** | <code>string</code> | ISO 8601 start date of the hydration record. |
|
|
568
|
+
| **`endDate`** | <code>string</code> | ISO 8601 end date of the hydration record. |
|
|
569
|
+
| **`sourceName`** | <code>string</code> | Source name that recorded the hydration. |
|
|
570
|
+
| **`sourceId`** | <code>string</code> | Source bundle identifier. |
|
|
571
|
+
| **`metadata`** | <code><a href="#record">Record</a><string, string></code> | Additional metadata (if available). |
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
#### QueryHydrationOptions
|
|
575
|
+
|
|
576
|
+
| Prop | Type | Description |
|
|
577
|
+
| --------------- | -------------------- | ------------------------------------------------------------------ |
|
|
578
|
+
| **`startDate`** | <code>string</code> | Inclusive ISO 8601 start date (defaults to now - 1 day). |
|
|
579
|
+
| **`endDate`** | <code>string</code> | Exclusive ISO 8601 end date (defaults to now). |
|
|
580
|
+
| **`limit`** | <code>number</code> | Maximum number of hydration records to return (defaults to 100). |
|
|
581
|
+
| **`ascending`** | <code>boolean</code> | Return results sorted ascending by start date (defaults to false). |
|
|
582
|
+
|
|
583
|
+
|
|
433
584
|
### Type Aliases
|
|
434
585
|
|
|
435
586
|
|
|
@@ -437,8 +588,10 @@ Queries workout sessions from the native health store on Android (Health Connect
|
|
|
437
588
|
|
|
438
589
|
Data types that can be requested for read authorization.
|
|
439
590
|
Includes 'workouts' for querying workout sessions via queryWorkouts().
|
|
591
|
+
Includes 'sleep' for querying sleep sessions via querySleep().
|
|
592
|
+
Includes 'hydration' for querying hydration records via queryHydration().
|
|
440
593
|
|
|
441
|
-
<code><a href="#healthdatatype">HealthDataType</a> | 'workouts'</code>
|
|
594
|
+
<code><a href="#healthdatatype">HealthDataType</a> | 'workouts' | 'sleep' | 'hydration'</code>
|
|
442
595
|
|
|
443
596
|
|
|
444
597
|
#### HealthDataType
|
|
@@ -462,6 +615,11 @@ Construct a type with a set of properties K of type T
|
|
|
462
615
|
|
|
463
616
|
<code>'running' | 'cycling' | 'walking' | 'swimming' | 'yoga' | 'strengthTraining' | 'hiking' | 'tennis' | 'basketball' | 'soccer' | 'americanFootball' | 'baseball' | 'crossTraining' | 'elliptical' | 'rowing' | 'stairClimbing' | 'traditionalStrengthTraining' | 'waterFitness' | 'waterPolo' | 'waterSports' | 'wrestling' | 'other'</code>
|
|
464
617
|
|
|
618
|
+
|
|
619
|
+
#### SleepStage
|
|
620
|
+
|
|
621
|
+
<code>'unknown' | 'awake' | 'sleeping' | 'outOfBed' | 'awakeInBed' | 'light' | 'deep' | 'rem'</code>
|
|
622
|
+
|
|
465
623
|
</docgen-api>
|
|
466
624
|
|
|
467
625
|
### Credits:
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
<uses-permission android:name="android.permission.health.READ_HEIGHT" />
|
|
14
14
|
<uses-permission android:name="android.permission.health.WRITE_HEIGHT" />
|
|
15
15
|
<uses-permission android:name="android.permission.health.READ_EXERCISE" />
|
|
16
|
+
<uses-permission android:name="android.permission.health.READ_SLEEP" />
|
|
17
|
+
<uses-permission android:name="android.permission.health.WRITE_SLEEP" />
|
|
18
|
+
<uses-permission android:name="android.permission.health.READ_HYDRATION" />
|
|
19
|
+
<uses-permission android:name="android.permission.health.WRITE_HYDRATION" />
|
|
16
20
|
|
|
17
21
|
<!-- Query for Health Connect availability -->
|
|
18
22
|
<queries>
|
|
@@ -8,7 +8,9 @@ import androidx.health.connect.client.records.DistanceRecord
|
|
|
8
8
|
import androidx.health.connect.client.records.ExerciseSessionRecord
|
|
9
9
|
import androidx.health.connect.client.records.HeartRateRecord
|
|
10
10
|
import androidx.health.connect.client.records.HeightRecord
|
|
11
|
+
import androidx.health.connect.client.records.HydrationRecord
|
|
11
12
|
import androidx.health.connect.client.records.Record
|
|
13
|
+
import androidx.health.connect.client.records.SleepSessionRecord
|
|
12
14
|
import androidx.health.connect.client.records.StepsRecord
|
|
13
15
|
import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
|
|
14
16
|
import androidx.health.connect.client.records.WeightRecord
|
|
@@ -17,6 +19,7 @@ import androidx.health.connect.client.time.TimeRangeFilter
|
|
|
17
19
|
import androidx.health.connect.client.units.Energy
|
|
18
20
|
import androidx.health.connect.client.units.Length
|
|
19
21
|
import androidx.health.connect.client.units.Mass
|
|
22
|
+
import androidx.health.connect.client.units.Volume
|
|
20
23
|
import androidx.health.connect.client.records.metadata.Metadata
|
|
21
24
|
import java.time.Duration
|
|
22
25
|
import com.getcapacitor.JSArray
|
|
@@ -32,20 +35,30 @@ class HealthManager {
|
|
|
32
35
|
|
|
33
36
|
private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_INSTANT
|
|
34
37
|
|
|
35
|
-
fun permissionsFor(readTypes: Collection<HealthDataType>, writeTypes: Collection<HealthDataType>, includeWorkouts: Boolean = false): Set<String> = buildSet {
|
|
38
|
+
fun permissionsFor(readTypes: Collection<HealthDataType>, writeTypes: Collection<HealthDataType>, includeWorkouts: Boolean = false, includeSleep: Boolean = false, includeHydration: Boolean = false): Set<String> = buildSet {
|
|
36
39
|
readTypes.forEach { add(it.readPermission) }
|
|
37
40
|
writeTypes.forEach { add(it.writePermission) }
|
|
38
41
|
// Include workout read permission if explicitly requested
|
|
39
42
|
if (includeWorkouts) {
|
|
40
43
|
add(HealthPermission.getReadPermission(ExerciseSessionRecord::class))
|
|
41
44
|
}
|
|
45
|
+
// Include sleep read permission if explicitly requested
|
|
46
|
+
if (includeSleep) {
|
|
47
|
+
add(HealthPermission.getReadPermission(SleepSessionRecord::class))
|
|
48
|
+
}
|
|
49
|
+
// Include hydration read permission if explicitly requested
|
|
50
|
+
if (includeHydration) {
|
|
51
|
+
add(HealthPermission.getReadPermission(HydrationRecord::class))
|
|
52
|
+
}
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
suspend fun authorizationStatus(
|
|
45
56
|
client: HealthConnectClient,
|
|
46
57
|
readTypes: Collection<HealthDataType>,
|
|
47
58
|
writeTypes: Collection<HealthDataType>,
|
|
48
|
-
includeWorkouts: Boolean = false
|
|
59
|
+
includeWorkouts: Boolean = false,
|
|
60
|
+
includeSleep: Boolean = false,
|
|
61
|
+
includeHydration: Boolean = false
|
|
49
62
|
): JSObject {
|
|
50
63
|
val granted = client.permissionController.getGrantedPermissions()
|
|
51
64
|
|
|
@@ -69,6 +82,26 @@ class HealthManager {
|
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
84
|
|
|
85
|
+
// Check sleep permission if requested
|
|
86
|
+
if (includeSleep) {
|
|
87
|
+
val sleepPermission = HealthPermission.getReadPermission(SleepSessionRecord::class)
|
|
88
|
+
if (granted.contains(sleepPermission)) {
|
|
89
|
+
readAuthorized.put("sleep")
|
|
90
|
+
} else {
|
|
91
|
+
readDenied.put("sleep")
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check hydration permission if requested
|
|
96
|
+
if (includeHydration) {
|
|
97
|
+
val hydrationPermission = HealthPermission.getReadPermission(HydrationRecord::class)
|
|
98
|
+
if (granted.contains(hydrationPermission)) {
|
|
99
|
+
readAuthorized.put("hydration")
|
|
100
|
+
} else {
|
|
101
|
+
readDenied.put("hydration")
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
72
105
|
val writeAuthorized = JSArray()
|
|
73
106
|
val writeDenied = JSArray()
|
|
74
107
|
writeTypes.forEach { type ->
|
|
@@ -501,6 +534,188 @@ class HealthManager {
|
|
|
501
534
|
return payload
|
|
502
535
|
}
|
|
503
536
|
|
|
537
|
+
suspend fun querySleep(
|
|
538
|
+
client: HealthConnectClient,
|
|
539
|
+
startTime: Instant,
|
|
540
|
+
endTime: Instant,
|
|
541
|
+
limit: Int,
|
|
542
|
+
ascending: Boolean
|
|
543
|
+
): JSArray {
|
|
544
|
+
val sleepSessions = mutableListOf<Pair<Instant, JSObject>>()
|
|
545
|
+
|
|
546
|
+
var pageToken: String? = null
|
|
547
|
+
val pageSize = if (limit > 0) min(limit, MAX_PAGE_SIZE) else DEFAULT_PAGE_SIZE
|
|
548
|
+
var fetched = 0
|
|
549
|
+
|
|
550
|
+
do {
|
|
551
|
+
val request = ReadRecordsRequest(
|
|
552
|
+
recordType = SleepSessionRecord::class,
|
|
553
|
+
timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
|
|
554
|
+
pageSize = pageSize,
|
|
555
|
+
pageToken = pageToken
|
|
556
|
+
)
|
|
557
|
+
val response = client.readRecords(request)
|
|
558
|
+
|
|
559
|
+
response.records.forEach { record ->
|
|
560
|
+
val session = record as SleepSessionRecord
|
|
561
|
+
val payload = createSleepPayload(session)
|
|
562
|
+
sleepSessions.add(session.startTime to payload)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
fetched += response.records.size
|
|
566
|
+
pageToken = response.pageToken
|
|
567
|
+
} while (pageToken != null && (limit <= 0 || fetched < limit))
|
|
568
|
+
|
|
569
|
+
val sorted = sleepSessions.sortedBy { it.first }
|
|
570
|
+
val ordered = if (ascending) sorted else sorted.asReversed()
|
|
571
|
+
val limited = if (limit > 0) ordered.take(limit) else ordered
|
|
572
|
+
|
|
573
|
+
val array = JSArray()
|
|
574
|
+
limited.forEach { array.put(it.second) }
|
|
575
|
+
return array
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private fun createSleepPayload(session: SleepSessionRecord): JSObject {
|
|
579
|
+
val payload = JSObject()
|
|
580
|
+
|
|
581
|
+
// Title if available
|
|
582
|
+
session.title?.let { title ->
|
|
583
|
+
if (title.isNotBlank()) {
|
|
584
|
+
payload.put("title", title)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Duration in seconds
|
|
589
|
+
val durationSeconds = Duration.between(session.startTime, session.endTime).seconds.toInt()
|
|
590
|
+
payload.put("duration", durationSeconds)
|
|
591
|
+
|
|
592
|
+
// Start and end dates
|
|
593
|
+
payload.put("startDate", formatter.format(session.startTime))
|
|
594
|
+
payload.put("endDate", formatter.format(session.endTime))
|
|
595
|
+
|
|
596
|
+
// Sleep stages if available
|
|
597
|
+
if (session.stages.isNotEmpty()) {
|
|
598
|
+
val stagesArray = JSArray()
|
|
599
|
+
session.stages.forEach { stage ->
|
|
600
|
+
val stageObject = JSObject()
|
|
601
|
+
stageObject.put("stage", sleepStageToString(stage.stage))
|
|
602
|
+
stageObject.put("startDate", formatter.format(stage.startTime))
|
|
603
|
+
stageObject.put("endDate", formatter.format(stage.endTime))
|
|
604
|
+
stagesArray.put(stageObject)
|
|
605
|
+
}
|
|
606
|
+
payload.put("stages", stagesArray)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Source information
|
|
610
|
+
val dataOrigin = session.metadata.dataOrigin
|
|
611
|
+
payload.put("sourceId", dataOrigin.packageName)
|
|
612
|
+
payload.put("sourceName", dataOrigin.packageName)
|
|
613
|
+
session.metadata.device?.let { device ->
|
|
614
|
+
val manufacturer = device.manufacturer?.takeIf { it.isNotBlank() }
|
|
615
|
+
val model = device.model?.takeIf { it.isNotBlank() }
|
|
616
|
+
val label = listOfNotNull(manufacturer, model).joinToString(" ").trim()
|
|
617
|
+
if (label.isNotEmpty()) {
|
|
618
|
+
payload.put("sourceName", label)
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return payload
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
private fun sleepStageToString(stage: Int): String {
|
|
626
|
+
return when (stage) {
|
|
627
|
+
SleepSessionRecord.STAGE_TYPE_UNKNOWN -> "unknown"
|
|
628
|
+
SleepSessionRecord.STAGE_TYPE_AWAKE -> "awake"
|
|
629
|
+
SleepSessionRecord.STAGE_TYPE_SLEEPING -> "sleeping"
|
|
630
|
+
SleepSessionRecord.STAGE_TYPE_OUT_OF_BED -> "outOfBed"
|
|
631
|
+
SleepSessionRecord.STAGE_TYPE_LIGHT -> "light"
|
|
632
|
+
SleepSessionRecord.STAGE_TYPE_DEEP -> "deep"
|
|
633
|
+
SleepSessionRecord.STAGE_TYPE_REM -> "rem"
|
|
634
|
+
SleepSessionRecord.STAGE_TYPE_AWAKE_IN_BED -> "awakeInBed"
|
|
635
|
+
else -> "unknown"
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
suspend fun queryHydration(
|
|
640
|
+
client: HealthConnectClient,
|
|
641
|
+
startTime: Instant,
|
|
642
|
+
endTime: Instant,
|
|
643
|
+
limit: Int,
|
|
644
|
+
ascending: Boolean
|
|
645
|
+
): JSArray {
|
|
646
|
+
val hydrationRecords = mutableListOf<Pair<Instant, JSObject>>()
|
|
647
|
+
|
|
648
|
+
var pageToken: String? = null
|
|
649
|
+
val pageSize = if (limit > 0) min(limit, MAX_PAGE_SIZE) else DEFAULT_PAGE_SIZE
|
|
650
|
+
var fetched = 0
|
|
651
|
+
|
|
652
|
+
do {
|
|
653
|
+
val request = ReadRecordsRequest(
|
|
654
|
+
recordType = HydrationRecord::class,
|
|
655
|
+
timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
|
|
656
|
+
pageSize = pageSize,
|
|
657
|
+
pageToken = pageToken
|
|
658
|
+
)
|
|
659
|
+
val response = client.readRecords(request)
|
|
660
|
+
|
|
661
|
+
response.records.forEach { record ->
|
|
662
|
+
val hydration = record as HydrationRecord
|
|
663
|
+
val payload = createHydrationPayload(hydration)
|
|
664
|
+
hydrationRecords.add(hydration.startTime to payload)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
fetched += response.records.size
|
|
668
|
+
pageToken = response.pageToken
|
|
669
|
+
} while (pageToken != null && (limit <= 0 || fetched < limit))
|
|
670
|
+
|
|
671
|
+
val sorted = hydrationRecords.sortedBy { it.first }
|
|
672
|
+
val ordered = if (ascending) sorted else sorted.asReversed()
|
|
673
|
+
val limited = if (limit > 0) ordered.take(limit) else ordered
|
|
674
|
+
|
|
675
|
+
val array = JSArray()
|
|
676
|
+
limited.forEach { array.put(it.second) }
|
|
677
|
+
return array
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private fun createHydrationPayload(hydration: HydrationRecord): JSObject {
|
|
681
|
+
val payload = JSObject()
|
|
682
|
+
|
|
683
|
+
// Volume in liters
|
|
684
|
+
val volumeLiters = hydration.volume.inLiters
|
|
685
|
+
payload.put("volume", volumeLiters)
|
|
686
|
+
|
|
687
|
+
// Start and end dates
|
|
688
|
+
payload.put("startDate", formatter.format(hydration.startTime))
|
|
689
|
+
payload.put("endDate", formatter.format(hydration.endTime))
|
|
690
|
+
|
|
691
|
+
// Source information
|
|
692
|
+
val dataOrigin = hydration.metadata.dataOrigin
|
|
693
|
+
payload.put("sourceId", dataOrigin.packageName)
|
|
694
|
+
payload.put("sourceName", dataOrigin.packageName)
|
|
695
|
+
hydration.metadata.device?.let { device ->
|
|
696
|
+
val manufacturer = device.manufacturer?.takeIf { it.isNotBlank() }
|
|
697
|
+
val model = device.model?.takeIf { it.isNotBlank() }
|
|
698
|
+
val label = listOfNotNull(manufacturer, model).joinToString(" ").trim()
|
|
699
|
+
if (label.isNotEmpty()) {
|
|
700
|
+
payload.put("sourceName", label)
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Metadata if available
|
|
705
|
+
if (hydration.metadata.clientRecordId != null || hydration.metadata.clientRecordVersion > 0) {
|
|
706
|
+
val metadataObj = JSObject()
|
|
707
|
+
hydration.metadata.clientRecordId?.let { metadataObj.put("clientRecordId", it) }
|
|
708
|
+
if (hydration.metadata.clientRecordVersion > 0) {
|
|
709
|
+
metadataObj.put("clientRecordVersion", hydration.metadata.clientRecordVersion)
|
|
710
|
+
}
|
|
711
|
+
if (metadataObj.length() > 0) {
|
|
712
|
+
payload.put("metadata", metadataObj)
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return payload
|
|
717
|
+
}
|
|
718
|
+
|
|
504
719
|
companion object {
|
|
505
720
|
private const val DEFAULT_PAGE_SIZE = 100
|
|
506
721
|
private const val MAX_PAGE_SIZE = 500
|