@tryterra/react-native 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Conversions.kt +103 -14
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/ReadConversions.kt +250 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeOpsImpl.kt +47 -5
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeStreamsImpl.kt +1 -3
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeOps.kt +89 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeTypes.kt +672 -0
- package/docs/availability.md +154 -4
- package/frameworks/TerraSDK.xcframework/Info.plist +5 -5
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Info.plist +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.abi.json +1385 -226
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +2 -2
- package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6589 -6468
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Info.plist +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +1385 -226
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +1385 -226
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/_CodeSignature/CodeResources +21 -21
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +2 -2
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6590 -6469
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/TerraSDK.yml +6508 -6387
- package/ios/TerraBridgeCore/Conversions+Reads.swift +259 -0
- package/ios/TerraBridgeCore/Conversions.swift +129 -2
- package/ios/TerraBridgeCore/Generated/BridgeOps.swift +172 -0
- package/ios/TerraBridgeCore/Generated/BridgeTypes.swift +734 -1
- package/ios/TerraBridgeCore/TerraBridgeOpsImpl.swift +64 -0
- package/lib/Terra.d.ts +74 -5
- package/lib/Terra.js +69 -13
- package/lib/generated/client.d.ts +37 -1
- package/lib/generated/client.js +60 -0
- package/lib/generated/codecs.d.ts +85 -1
- package/lib/generated/codecs.js +710 -2
- package/lib/generated/ops.d.ts +1 -1
- package/lib/generated/ops.js +24 -0
- package/lib/generated/types.d.ts +314 -1
- package/lib/generated/types.js +133 -1
- package/lib/index.d.ts +1 -1
- package/package.json +3 -3
- package/src/Terra.ts +146 -16
- package/src/generated/client.ts +74 -2
- package/src/generated/codecs.ts +705 -3
- package/src/generated/ops.ts +36 -0
- package/src/generated/types.ts +556 -1
- package/src/index.ts +2 -0
package/README.md
CHANGED
|
@@ -18,6 +18,37 @@ There is no sync call: once connected, the SDK uploads on its own, in the backgr
|
|
|
18
18
|
rejection is a `TerraError` with a stable `code` (see `docs/availability.md` for the full list and
|
|
19
19
|
what exists on which platform).
|
|
20
20
|
|
|
21
|
+
## Local reads
|
|
22
|
+
|
|
23
|
+
Every read goes straight to the device's store and never touches the network:
|
|
24
|
+
|
|
25
|
+
| Member | Answers | Platforms |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `body.heartRate(range)` | `HeartRateSample[]` | both |
|
|
28
|
+
| `body.restingHeartRate(range)` | `RestingHeartRateSample[]`, the store's own daily figure | both; Android grants it under `daily` |
|
|
29
|
+
| `body.readings(range)` | `BodyReading[]`, each `kind` carrying its unit | both; `bmi` and wrist temperature iOS only |
|
|
30
|
+
| `daily.steps(range)` | `StepCount[]` | both |
|
|
31
|
+
| `daily.totals(range)` | `DayTotal[]`, one per metric per whole local day | both; `appleExerciseTime` iOS only |
|
|
32
|
+
| `daily.intraday(range)` | `IntradaySample[]` | both; `basalEnergy` iOS only |
|
|
33
|
+
| `sleep.segments(range)` | `SleepSegment[]` | both |
|
|
34
|
+
| `activity.workouts(range)` | `Workout[]`, with the writer's `title` where it set one | both |
|
|
35
|
+
| `activity.routes(range)` | `WorkoutRoute[]`, each naming its workout | both |
|
|
36
|
+
| `activity.intensity(range)` | `WorkoutIntensitySample[]` | both; no `workoutId` on Android |
|
|
37
|
+
| `nutrition.quantities(range)` | `DietaryQuantity[]`, one per nutrient, not grouped into meals | both |
|
|
38
|
+
| `nutrition.hydration(range)` | `HydrationSample[]`, in millilitres | both |
|
|
39
|
+
| `menstruation.flow(range)` | `MenstruationFlowDay[]`, one per logged day | both |
|
|
40
|
+
| `menstruation.events(range)` | `MenstrualEvent[]`: ovulation tests, cervical mucus, intermenstrual bleeding | both |
|
|
41
|
+
| `athlete?.profile()` | `AthleteProfile`, or `undefined` where the store has none | iOS; `terra.athlete` is `undefined` on Android |
|
|
42
|
+
|
|
43
|
+
Ask for the category first: `permissions.request({ read: ["nutrition", "menstruation"] })`, plus
|
|
44
|
+
`"athlete"` on iOS, without which HealthKit hides the characteristics and `profile()` answers
|
|
45
|
+
`notSet` and nothing else. A read rejects with `not_connected` before `connect`, and with
|
|
46
|
+
`permission_denied` naming the category in `details.category` when the grant was refused.
|
|
47
|
+
|
|
48
|
+
`terra.availability(source?)` answers before any of them, with no connection: `available`, or why
|
|
49
|
+
not - `unsupportedDevice` on iOS, and on Android the reason Health Connect gives, such as
|
|
50
|
+
`providerAppMissing` when it needs installing.
|
|
51
|
+
|
|
21
52
|
## Native setup
|
|
22
53
|
|
|
23
54
|
The client is built natively at launch, before any JavaScript runs, because background delivery
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
package co.tryterra.sdk.bridge
|
|
2
2
|
|
|
3
3
|
import co.tryterra.sdk.DataCategory
|
|
4
|
+
import co.tryterra.sdk.DietaryQuantity
|
|
5
|
+
import co.tryterra.sdk.MealSample
|
|
6
|
+
import co.tryterra.sdk.MenstruationFlowDay
|
|
7
|
+
import co.tryterra.sdk.MenstruationFlowSample
|
|
8
|
+
import co.tryterra.sdk.Nutrient
|
|
4
9
|
import co.tryterra.sdk.EndUser
|
|
5
10
|
import co.tryterra.sdk.BodyEntry
|
|
6
11
|
import co.tryterra.sdk.HeartRateEntry
|
|
@@ -38,6 +43,11 @@ import co.tryterra.sdk.bridge.generated.Connection as WireConnection
|
|
|
38
43
|
import co.tryterra.sdk.bridge.generated.NutritionEntry as WireNutritionEntry
|
|
39
44
|
import co.tryterra.sdk.bridge.generated.Nutrients as WireNutrients
|
|
40
45
|
import co.tryterra.sdk.bridge.generated.DataCategory as WireCategory
|
|
46
|
+
import co.tryterra.sdk.bridge.generated.DietaryQuantity as WireDietaryQuantity
|
|
47
|
+
import co.tryterra.sdk.bridge.generated.MealType as WireMealType
|
|
48
|
+
import co.tryterra.sdk.bridge.generated.MenstruationFlow as WireMenstruationFlow
|
|
49
|
+
import co.tryterra.sdk.bridge.generated.MenstruationFlowDay as WireMenstruationFlowDay
|
|
50
|
+
import co.tryterra.sdk.bridge.generated.Nutrient as WireNutrient
|
|
41
51
|
import co.tryterra.sdk.bridge.generated.DiagnosticsState as WireDiagnosticsState
|
|
42
52
|
import co.tryterra.sdk.bridge.generated.EndUser as WireEndUser
|
|
43
53
|
import co.tryterra.sdk.bridge.generated.HealthAccess as WireHealthAccess
|
|
@@ -88,12 +98,8 @@ internal object Conversions {
|
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* `MENSTRUATION` is excluded in the surface with a reason, so a request naming it cannot arrive
|
|
94
|
-
* - the generated enum has no entry for it - and a value coming *out* of the SDK for it would
|
|
95
|
-
* be a category the wrapper has no type for. Refusing it names the situation instead of
|
|
96
|
-
* substituting a neighbour.
|
|
101
|
+
* `ATHLETE` is HealthKit's characteristics and has no Health Connect counterpart, so a request
|
|
102
|
+
* naming it is refused as this platform lacking it rather than quietly dropped from the sheet.
|
|
97
103
|
*/
|
|
98
104
|
fun category(wire: WireCategory): DataCategory = when (wire) {
|
|
99
105
|
WireCategory.ACTIVITY -> DataCategory.ACTIVITY
|
|
@@ -101,15 +107,17 @@ internal object Conversions {
|
|
|
101
107
|
WireCategory.DAILY -> DataCategory.DAILY
|
|
102
108
|
WireCategory.SLEEP -> DataCategory.SLEEP
|
|
103
109
|
WireCategory.NUTRITION -> DataCategory.NUTRITION
|
|
110
|
+
WireCategory.MENSTRUATION -> DataCategory.MENSTRUATION
|
|
111
|
+
WireCategory.ATHLETE -> throw BridgeError.UnsupportedOnPlatform("athlete")
|
|
104
112
|
}
|
|
105
113
|
|
|
106
|
-
fun category(category: DataCategory): WireCategory
|
|
114
|
+
fun category(category: DataCategory): WireCategory = when (category) {
|
|
107
115
|
DataCategory.ACTIVITY -> WireCategory.ACTIVITY
|
|
108
116
|
DataCategory.BODY -> WireCategory.BODY
|
|
109
117
|
DataCategory.DAILY -> WireCategory.DAILY
|
|
110
118
|
DataCategory.SLEEP -> WireCategory.SLEEP
|
|
111
119
|
DataCategory.NUTRITION -> WireCategory.NUTRITION
|
|
112
|
-
DataCategory.MENSTRUATION ->
|
|
120
|
+
DataCategory.MENSTRUATION -> WireCategory.MENSTRUATION
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
/**
|
|
@@ -255,6 +263,88 @@ internal object Conversions {
|
|
|
255
263
|
timeZone = segment.timeZone?.id,
|
|
256
264
|
)
|
|
257
265
|
|
|
266
|
+
fun nutrient(nutrient: Nutrient): WireNutrient = when (nutrient) {
|
|
267
|
+
Nutrient.ENERGY_CONSUMED -> WireNutrient.ENERGY_CONSUMED
|
|
268
|
+
Nutrient.PROTEIN -> WireNutrient.PROTEIN
|
|
269
|
+
Nutrient.CARBOHYDRATES -> WireNutrient.CARBOHYDRATES
|
|
270
|
+
Nutrient.FAT_TOTAL -> WireNutrient.FAT_TOTAL
|
|
271
|
+
Nutrient.FAT_SATURATED -> WireNutrient.FAT_SATURATED
|
|
272
|
+
Nutrient.FAT_MONOUNSATURATED -> WireNutrient.FAT_MONOUNSATURATED
|
|
273
|
+
Nutrient.FAT_POLYUNSATURATED -> WireNutrient.FAT_POLYUNSATURATED
|
|
274
|
+
Nutrient.SUGAR -> WireNutrient.SUGAR
|
|
275
|
+
Nutrient.FIBER -> WireNutrient.FIBER
|
|
276
|
+
Nutrient.CHOLESTEROL -> WireNutrient.CHOLESTEROL
|
|
277
|
+
Nutrient.SODIUM -> WireNutrient.SODIUM
|
|
278
|
+
Nutrient.POTASSIUM -> WireNutrient.POTASSIUM
|
|
279
|
+
Nutrient.CALCIUM -> WireNutrient.CALCIUM
|
|
280
|
+
Nutrient.IRON -> WireNutrient.IRON
|
|
281
|
+
Nutrient.MAGNESIUM -> WireNutrient.MAGNESIUM
|
|
282
|
+
Nutrient.ZINC -> WireNutrient.ZINC
|
|
283
|
+
Nutrient.PHOSPHORUS -> WireNutrient.PHOSPHORUS
|
|
284
|
+
Nutrient.IODINE -> WireNutrient.IODINE
|
|
285
|
+
Nutrient.MANGANESE -> WireNutrient.MANGANESE
|
|
286
|
+
Nutrient.MOLYBDENUM -> WireNutrient.MOLYBDENUM
|
|
287
|
+
Nutrient.SELENIUM -> WireNutrient.SELENIUM
|
|
288
|
+
Nutrient.COPPER -> WireNutrient.COPPER
|
|
289
|
+
Nutrient.CHROMIUM -> WireNutrient.CHROMIUM
|
|
290
|
+
Nutrient.CHLORIDE -> WireNutrient.CHLORIDE
|
|
291
|
+
Nutrient.CAFFEINE -> WireNutrient.CAFFEINE
|
|
292
|
+
Nutrient.VITAMIN_A -> WireNutrient.VITAMIN_A
|
|
293
|
+
Nutrient.VITAMIN_C -> WireNutrient.VITAMIN_C
|
|
294
|
+
Nutrient.VITAMIN_D -> WireNutrient.VITAMIN_D
|
|
295
|
+
Nutrient.VITAMIN_E -> WireNutrient.VITAMIN_E
|
|
296
|
+
Nutrient.VITAMIN_K -> WireNutrient.VITAMIN_K
|
|
297
|
+
Nutrient.VITAMIN_B6 -> WireNutrient.VITAMIN_B6
|
|
298
|
+
Nutrient.VITAMIN_B12 -> WireNutrient.VITAMIN_B12
|
|
299
|
+
Nutrient.THIAMIN -> WireNutrient.THIAMIN
|
|
300
|
+
Nutrient.RIBOFLAVIN -> WireNutrient.RIBOFLAVIN
|
|
301
|
+
Nutrient.NIACIN -> WireNutrient.NIACIN
|
|
302
|
+
Nutrient.FOLATE -> WireNutrient.FOLATE
|
|
303
|
+
Nutrient.BIOTIN -> WireNutrient.BIOTIN
|
|
304
|
+
Nutrient.PANTOTHENIC_ACID -> WireNutrient.PANTOTHENIC_ACID
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
fun mealType(type: MealSample.MealType): WireMealType = when (type) {
|
|
308
|
+
MealSample.MealType.BREAKFAST -> WireMealType.BREAKFAST
|
|
309
|
+
MealSample.MealType.LUNCH -> WireMealType.LUNCH
|
|
310
|
+
MealSample.MealType.DINNER -> WireMealType.DINNER
|
|
311
|
+
MealSample.MealType.SNACK -> WireMealType.SNACK
|
|
312
|
+
MealSample.MealType.UNKNOWN -> WireMealType.UNKNOWN
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
fun flow(flow: MenstruationFlowSample.Flow): WireMenstruationFlow = when (flow) {
|
|
316
|
+
MenstruationFlowSample.Flow.UNSPECIFIED -> WireMenstruationFlow.UNSPECIFIED
|
|
317
|
+
MenstruationFlowSample.Flow.LIGHT -> WireMenstruationFlow.LIGHT
|
|
318
|
+
MenstruationFlowSample.Flow.MEDIUM -> WireMenstruationFlow.MEDIUM
|
|
319
|
+
MenstruationFlowSample.Flow.HEAVY -> WireMenstruationFlow.HEAVY
|
|
320
|
+
MenstruationFlowSample.Flow.NONE -> WireMenstruationFlow.NO_FLOW
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
fun quantity(quantity: DietaryQuantity): WireDietaryQuantity = WireDietaryQuantity(
|
|
324
|
+
id = quantity.id.value,
|
|
325
|
+
nutrient = nutrient(quantity.nutrient),
|
|
326
|
+
value = quantity.value,
|
|
327
|
+
start = BridgeInstant.format(quantity.interval.start),
|
|
328
|
+
end = BridgeInstant.format(quantity.interval.end),
|
|
329
|
+
mealType = mealType(quantity.mealType),
|
|
330
|
+
name = quantity.name,
|
|
331
|
+
foodCorrelationId = quantity.foodCorrelationId?.value,
|
|
332
|
+
source = sampleSource(quantity.source),
|
|
333
|
+
userEntered = quantity.userEntered,
|
|
334
|
+
timeZone = quantity.timeZone?.id,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
fun flowDay(day: MenstruationFlowDay): WireMenstruationFlowDay = WireMenstruationFlowDay(
|
|
338
|
+
id = day.id.value,
|
|
339
|
+
start = BridgeInstant.format(day.interval.start),
|
|
340
|
+
end = BridgeInstant.format(day.interval.end),
|
|
341
|
+
flow = flow(day.flow),
|
|
342
|
+
cycleStart = day.cycleStart,
|
|
343
|
+
source = sampleSource(day.source),
|
|
344
|
+
userEntered = day.userEntered,
|
|
345
|
+
timeZone = day.timeZone?.id,
|
|
346
|
+
)
|
|
347
|
+
|
|
258
348
|
fun workout(workout: Workout): WireWorkout = WireWorkout(
|
|
259
349
|
id = workout.id.value,
|
|
260
350
|
start = BridgeInstant.format(workout.interval.start),
|
|
@@ -267,6 +357,7 @@ internal object Conversions {
|
|
|
267
357
|
source = sampleSource(workout.source),
|
|
268
358
|
userEntered = workout.userEntered,
|
|
269
359
|
timeZone = workout.timeZone?.id,
|
|
360
|
+
title = workout.title,
|
|
270
361
|
)
|
|
271
362
|
|
|
272
363
|
/**
|
|
@@ -275,10 +366,8 @@ internal object Conversions {
|
|
|
275
366
|
* Sending the instant would make the receiving side re-derive the day in its own zone, and get
|
|
276
367
|
* a different one either side of midnight.
|
|
277
368
|
*/
|
|
278
|
-
fun change(change: HealthChange, zone: ZoneId): WireHealthChange
|
|
279
|
-
category(change.category)
|
|
280
|
-
WireHealthChange(category = it, day = change.day.atZone(zone).toLocalDate().toString())
|
|
281
|
-
}
|
|
369
|
+
fun change(change: HealthChange, zone: ZoneId): WireHealthChange =
|
|
370
|
+
WireHealthChange(category = category(change.category), day = change.day.atZone(zone).toLocalDate().toString())
|
|
282
371
|
|
|
283
372
|
/**
|
|
284
373
|
* An event is the object `DiagnosticsWire` already produces, carried unchanged.
|
|
@@ -312,8 +401,8 @@ internal object Conversions {
|
|
|
312
401
|
* which the vectors would be flaky.
|
|
313
402
|
*/
|
|
314
403
|
fun access(access: HealthAccess): WireHealthAccess = WireHealthAccess(
|
|
315
|
-
read = access.read.
|
|
316
|
-
write = access.write.
|
|
404
|
+
read = access.read.map(::category).sortedBy { it.name },
|
|
405
|
+
write = access.write.map(::category).sortedBy { it.name },
|
|
317
406
|
workouts = access.workouts
|
|
318
407
|
.groupBy { WorkoutActivity.reportName(it.activity) }
|
|
319
408
|
.toSortedMap()
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
package co.tryterra.sdk.bridge
|
|
2
|
+
|
|
3
|
+
import co.tryterra.sdk.BloodGlucoseSample
|
|
4
|
+
import co.tryterra.sdk.BloodGlucoseUnit
|
|
5
|
+
import co.tryterra.sdk.BodyMeasurement
|
|
6
|
+
import co.tryterra.sdk.BodyReading
|
|
7
|
+
import co.tryterra.sdk.BodyTemperatureSample
|
|
8
|
+
import co.tryterra.sdk.DayTotal
|
|
9
|
+
import co.tryterra.sdk.HydrationSample
|
|
10
|
+
import co.tryterra.sdk.IntradaySample
|
|
11
|
+
import co.tryterra.sdk.MenstrualEvent
|
|
12
|
+
import co.tryterra.sdk.MenstruationEventSample
|
|
13
|
+
import co.tryterra.sdk.RestingHeartRateSample
|
|
14
|
+
import co.tryterra.sdk.Vo2MaxSample
|
|
15
|
+
import co.tryterra.sdk.WorkoutIntensitySample
|
|
16
|
+
import co.tryterra.sdk.WorkoutRoute
|
|
17
|
+
import co.tryterra.sdk.bridge.generated.BridgeInstant
|
|
18
|
+
import co.tryterra.sdk.health.StoreAvailability
|
|
19
|
+
import co.tryterra.sdk.bridge.generated.BloodGlucoseUnit as WireGlucoseUnit
|
|
20
|
+
import co.tryterra.sdk.bridge.generated.BodyMetric as WireBodyMetric
|
|
21
|
+
import co.tryterra.sdk.bridge.generated.BodyReading as WireBodyReading
|
|
22
|
+
import co.tryterra.sdk.bridge.generated.BodyReadingKind as WireBodyReadingKind
|
|
23
|
+
import co.tryterra.sdk.bridge.generated.BodyTemperatureMetric as WireTemperatureMetric
|
|
24
|
+
import co.tryterra.sdk.bridge.generated.DayTotal as WireDayTotal
|
|
25
|
+
import co.tryterra.sdk.bridge.generated.DayTotalMetric as WireDayTotalMetric
|
|
26
|
+
import co.tryterra.sdk.bridge.generated.GlucoseMealTime as WireMealTime
|
|
27
|
+
import co.tryterra.sdk.bridge.generated.GlucoseSpecimen as WireSpecimen
|
|
28
|
+
import co.tryterra.sdk.bridge.generated.HeartRateVariabilityMetric as WireVariabilityMetric
|
|
29
|
+
import co.tryterra.sdk.bridge.generated.HydrationSample as WireHydrationSample
|
|
30
|
+
import co.tryterra.sdk.bridge.generated.IntradayMetric as WireIntradayMetric
|
|
31
|
+
import co.tryterra.sdk.bridge.generated.IntradaySample as WireIntradaySample
|
|
32
|
+
import co.tryterra.sdk.bridge.generated.MenstrualEvent as WireMenstrualEvent
|
|
33
|
+
import co.tryterra.sdk.bridge.generated.MenstrualEventKind as WireEventKind
|
|
34
|
+
import co.tryterra.sdk.bridge.generated.RestingHeartRateSample as WireRestingHeartRateSample
|
|
35
|
+
import co.tryterra.sdk.bridge.generated.RoutePoint as WireRoutePoint
|
|
36
|
+
import co.tryterra.sdk.bridge.generated.StoreAvailability as WireStoreAvailability
|
|
37
|
+
import co.tryterra.sdk.bridge.generated.VO2MaxTestType as WireTestType
|
|
38
|
+
import co.tryterra.sdk.bridge.generated.WorkoutIntensityMetric as WireIntensityMetric
|
|
39
|
+
import co.tryterra.sdk.bridge.generated.WorkoutIntensitySample as WireIntensitySample
|
|
40
|
+
import co.tryterra.sdk.bridge.generated.WorkoutRoute as WireWorkoutRoute
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The reads that close the rest of the 1.x local-read gap, between the SDK's types and the wire's.
|
|
44
|
+
*
|
|
45
|
+
* Apart from [Conversions] only because there are many of them, and under its rules: every `when`
|
|
46
|
+
* is exhaustive with no `else`, so a case added on either side stops the build here, and a case the
|
|
47
|
+
* read never returns maps to null and is dropped rather than sent under a neighbour's name.
|
|
48
|
+
*/
|
|
49
|
+
internal object ReadConversions {
|
|
50
|
+
|
|
51
|
+
/** Null for the metrics `daily.totals` leaves out, which the read never hands over. */
|
|
52
|
+
fun dayTotalMetric(metric: DayTotal.Metric): WireDayTotalMetric? = when (metric) {
|
|
53
|
+
DayTotal.Metric.DISTANCE_WALKING_RUNNING -> WireDayTotalMetric.DISTANCE_WALKING_RUNNING
|
|
54
|
+
DayTotal.Metric.DISTANCE_CYCLING -> WireDayTotalMetric.DISTANCE_CYCLING
|
|
55
|
+
DayTotal.Metric.DISTANCE_SWIMMING -> WireDayTotalMetric.DISTANCE_SWIMMING
|
|
56
|
+
DayTotal.Metric.DISTANCE_WHEELCHAIR -> WireDayTotalMetric.DISTANCE_WHEELCHAIR
|
|
57
|
+
DayTotal.Metric.ACTIVE_ENERGY_BURNED -> WireDayTotalMetric.ACTIVE_ENERGY_BURNED
|
|
58
|
+
DayTotal.Metric.BASAL_ENERGY_BURNED -> WireDayTotalMetric.BASAL_ENERGY_BURNED
|
|
59
|
+
DayTotal.Metric.FLIGHTS_CLIMBED -> WireDayTotalMetric.FLIGHTS_CLIMBED
|
|
60
|
+
DayTotal.Metric.STEPS, DayTotal.Metric.SWIMMING_STROKE_COUNT -> null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fun dayTotal(total: DayTotal): WireDayTotal? = dayTotalMetric(total.metric)?.let {
|
|
64
|
+
WireDayTotal(
|
|
65
|
+
metric = it,
|
|
66
|
+
start = BridgeInstant.format(total.interval.start),
|
|
67
|
+
end = BridgeInstant.format(total.interval.end),
|
|
68
|
+
value = total.value,
|
|
69
|
+
unit = total.unit,
|
|
70
|
+
timeZone = total.timeZone?.id,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fun intradayMetric(metric: IntradaySample.Metric): WireIntradayMetric = when (metric) {
|
|
75
|
+
IntradaySample.Metric.ACTIVE_ENERGY -> WireIntradayMetric.ACTIVE_ENERGY
|
|
76
|
+
IntradaySample.Metric.DISTANCE -> WireIntradayMetric.DISTANCE
|
|
77
|
+
IntradaySample.Metric.FLOORS_CLIMBED -> WireIntradayMetric.FLOORS_CLIMBED
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fun intraday(sample: IntradaySample): WireIntradaySample = WireIntradaySample(
|
|
81
|
+
id = sample.id.value,
|
|
82
|
+
metric = intradayMetric(sample.metric),
|
|
83
|
+
start = BridgeInstant.format(sample.interval.start),
|
|
84
|
+
end = BridgeInstant.format(sample.interval.end),
|
|
85
|
+
value = sample.value,
|
|
86
|
+
source = Conversions.sampleSource(sample.source),
|
|
87
|
+
userEntered = sample.userEntered,
|
|
88
|
+
timeZone = sample.timeZone?.id,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
fun restingHeartRate(sample: RestingHeartRateSample): WireRestingHeartRateSample = WireRestingHeartRateSample(
|
|
92
|
+
id = sample.id.value,
|
|
93
|
+
start = BridgeInstant.format(sample.interval.start),
|
|
94
|
+
end = BridgeInstant.format(sample.interval.end),
|
|
95
|
+
beatsPerMinute = sample.beatsPerMinute,
|
|
96
|
+
source = Conversions.sampleSource(sample.source),
|
|
97
|
+
userEntered = sample.userEntered,
|
|
98
|
+
timeZone = sample.timeZone?.id,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
/** Null for the three `body.readings` leaves out. */
|
|
102
|
+
fun bodyMetric(metric: BodyMeasurement.Metric): WireBodyMetric? = when (metric) {
|
|
103
|
+
BodyMeasurement.Metric.WEIGHT -> WireBodyMetric.WEIGHT
|
|
104
|
+
BodyMeasurement.Metric.HEIGHT -> WireBodyMetric.HEIGHT
|
|
105
|
+
BodyMeasurement.Metric.BODY_FAT_PERCENTAGE -> WireBodyMetric.BODY_FAT_PERCENTAGE
|
|
106
|
+
BodyMeasurement.Metric.LEAN_BODY_MASS -> WireBodyMetric.LEAN_BODY_MASS
|
|
107
|
+
BodyMeasurement.Metric.BONE_MASS,
|
|
108
|
+
BodyMeasurement.Metric.BODY_WATER_MASS,
|
|
109
|
+
BodyMeasurement.Metric.BASAL_METABOLIC_RATE,
|
|
110
|
+
-> null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fun glucoseUnit(unit: BloodGlucoseUnit): WireGlucoseUnit = when (unit) {
|
|
114
|
+
BloodGlucoseUnit.MILLIGRAMS_PER_DECILITRE -> WireGlucoseUnit.MILLIGRAMS_PER_DECILITRE
|
|
115
|
+
BloodGlucoseUnit.MILLIMOLES_PER_LITRE -> WireGlucoseUnit.MILLIMOLES_PER_LITRE
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fun mealTime(time: BloodGlucoseSample.MealTime): WireMealTime = when (time) {
|
|
119
|
+
BloodGlucoseSample.MealTime.PREPRANDIAL -> WireMealTime.PREPRANDIAL
|
|
120
|
+
BloodGlucoseSample.MealTime.POSTPRANDIAL -> WireMealTime.POSTPRANDIAL
|
|
121
|
+
BloodGlucoseSample.MealTime.UNKNOWN -> WireMealTime.UNKNOWN
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fun specimen(specimen: BloodGlucoseSample.Specimen): WireSpecimen = when (specimen) {
|
|
125
|
+
BloodGlucoseSample.Specimen.CAPILLARY -> WireSpecimen.CAPILLARY
|
|
126
|
+
BloodGlucoseSample.Specimen.INTERSTITIAL -> WireSpecimen.INTERSTITIAL
|
|
127
|
+
BloodGlucoseSample.Specimen.PLASMA -> WireSpecimen.PLASMA
|
|
128
|
+
BloodGlucoseSample.Specimen.SERUM -> WireSpecimen.SERUM
|
|
129
|
+
BloodGlucoseSample.Specimen.TEARS -> WireSpecimen.TEARS
|
|
130
|
+
BloodGlucoseSample.Specimen.WHOLE_BLOOD -> WireSpecimen.WHOLE_BLOOD
|
|
131
|
+
BloodGlucoseSample.Specimen.UNKNOWN -> WireSpecimen.UNKNOWN
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fun temperatureMetric(metric: BodyTemperatureSample.Metric): WireTemperatureMetric = when (metric) {
|
|
135
|
+
BodyTemperatureSample.Metric.BODY -> WireTemperatureMetric.BODY
|
|
136
|
+
BodyTemperatureSample.Metric.BASAL -> WireTemperatureMetric.BASAL
|
|
137
|
+
BodyTemperatureSample.Metric.SKIN -> WireTemperatureMetric.SKIN
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fun testType(type: Vo2MaxSample.TestType): WireTestType = when (type) {
|
|
141
|
+
Vo2MaxSample.TestType.MAX_EXERCISE -> WireTestType.MAX_EXERCISE
|
|
142
|
+
Vo2MaxSample.TestType.PREDICTED_SUB_MAX_EXERCISE -> WireTestType.PREDICTED_SUB_MAX_EXERCISE
|
|
143
|
+
Vo2MaxSample.TestType.PREDICTED_NON_EXERCISE -> WireTestType.PREDICTED_NON_EXERCISE
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fun bodyReadingKind(kind: BodyReading.Kind): WireBodyReadingKind? = when (kind) {
|
|
147
|
+
is BodyReading.Kind.Measurement -> bodyMetric(kind.metric)?.let { WireBodyReadingKind.Measurement(it, kind.value) }
|
|
148
|
+
is BodyReading.Kind.BloodPressure ->
|
|
149
|
+
WireBodyReadingKind.BloodPressure(kind.systolicMillimetersOfMercury, kind.diastolicMillimetersOfMercury)
|
|
150
|
+
is BodyReading.Kind.BloodGlucose -> WireBodyReadingKind.BloodGlucose(
|
|
151
|
+
value = kind.value,
|
|
152
|
+
unit = glucoseUnit(kind.unit),
|
|
153
|
+
mealTime = kind.mealTime?.let(::mealTime),
|
|
154
|
+
specimen = kind.specimen?.let(::specimen),
|
|
155
|
+
)
|
|
156
|
+
is BodyReading.Kind.OxygenSaturation -> WireBodyReadingKind.OxygenSaturation(kind.fraction)
|
|
157
|
+
is BodyReading.Kind.Temperature -> WireBodyReadingKind.Temperature(temperatureMetric(kind.metric), kind.celsius)
|
|
158
|
+
// RMSSD is the only statistic Health Connect records.
|
|
159
|
+
is BodyReading.Kind.HeartRateVariability ->
|
|
160
|
+
WireBodyReadingKind.HeartRateVariability(WireVariabilityMetric.RMSSD, kind.rmssdMilliseconds)
|
|
161
|
+
is BodyReading.Kind.RespiratoryRate -> WireBodyReadingKind.RespiratoryRate(kind.breathsPerMinute)
|
|
162
|
+
is BodyReading.Kind.Vo2Max ->
|
|
163
|
+
WireBodyReadingKind.Vo2Max(kind.millilitresPerKilogramPerMinute, kind.testType?.let(::testType))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fun bodyReading(reading: BodyReading): WireBodyReading? = bodyReadingKind(reading.kind)?.let {
|
|
167
|
+
WireBodyReading(
|
|
168
|
+
id = reading.id.value,
|
|
169
|
+
kind = it,
|
|
170
|
+
start = BridgeInstant.format(reading.interval.start),
|
|
171
|
+
end = BridgeInstant.format(reading.interval.end),
|
|
172
|
+
source = Conversions.sampleSource(reading.source),
|
|
173
|
+
userEntered = reading.userEntered,
|
|
174
|
+
timeZone = reading.timeZone?.id,
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fun route(route: WorkoutRoute): WireWorkoutRoute = WireWorkoutRoute(
|
|
179
|
+
id = route.id.value,
|
|
180
|
+
workoutId = route.workoutId.value,
|
|
181
|
+
start = BridgeInstant.format(route.interval.start),
|
|
182
|
+
end = BridgeInstant.format(route.interval.end),
|
|
183
|
+
points = route.points.map { WireRoutePoint(BridgeInstant.format(it.time), it.latitude, it.longitude) },
|
|
184
|
+
source = Conversions.sampleSource(route.source),
|
|
185
|
+
timeZone = route.timeZone?.id,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
fun intensityMetric(metric: WorkoutIntensitySample.Metric): WireIntensityMetric = when (metric) {
|
|
189
|
+
WorkoutIntensitySample.Metric.SPEED -> WireIntensityMetric.SPEED
|
|
190
|
+
WorkoutIntensitySample.Metric.POWER -> WireIntensityMetric.POWER
|
|
191
|
+
WorkoutIntensitySample.Metric.CADENCE -> WireIntensityMetric.CADENCE
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** An instant on Health Connect, so both ends are its time, and no workout it could name. */
|
|
195
|
+
fun intensity(sample: WorkoutIntensitySample): WireIntensitySample = WireIntensitySample(
|
|
196
|
+
id = sample.id.value,
|
|
197
|
+
metric = intensityMetric(sample.metric),
|
|
198
|
+
workoutId = null,
|
|
199
|
+
start = BridgeInstant.format(sample.time),
|
|
200
|
+
end = BridgeInstant.format(sample.time),
|
|
201
|
+
value = sample.value,
|
|
202
|
+
source = Conversions.sampleSource(sample.source),
|
|
203
|
+
userEntered = sample.userEntered,
|
|
204
|
+
timeZone = sample.timeZone?.id,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
fun hydration(sample: HydrationSample): WireHydrationSample = WireHydrationSample(
|
|
208
|
+
id = sample.id.value,
|
|
209
|
+
start = BridgeInstant.format(sample.interval.start),
|
|
210
|
+
end = BridgeInstant.format(sample.interval.end),
|
|
211
|
+
millilitres = sample.millilitres,
|
|
212
|
+
source = Conversions.sampleSource(sample.source),
|
|
213
|
+
userEntered = sample.userEntered,
|
|
214
|
+
timeZone = sample.timeZone?.id,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Which types a partner policy refuses, and which feature is missing, stay in the native report:
|
|
219
|
+
* the wire names the reason, and the remedy a wrapper app can offer is the same either way.
|
|
220
|
+
*/
|
|
221
|
+
fun availability(availability: StoreAvailability): WireStoreAvailability = when (availability) {
|
|
222
|
+
StoreAvailability.Available -> WireStoreAvailability.Available
|
|
223
|
+
StoreAvailability.ModuleMissing -> WireStoreAvailability.ModuleMissing
|
|
224
|
+
StoreAvailability.ProviderAppMissing -> WireStoreAvailability.ProviderAppMissing
|
|
225
|
+
is StoreAvailability.ProviderAppTooOld -> WireStoreAvailability.ProviderAppTooOld(availability.installed)
|
|
226
|
+
is StoreAvailability.PartnerPolicyMissing -> WireStoreAvailability.PartnerPolicyMissing
|
|
227
|
+
is StoreAvailability.FeatureUnavailable -> WireStoreAvailability.FeatureUnavailable
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Null for sexual activity, which `menstruation.events` leaves out. */
|
|
231
|
+
fun eventKind(event: MenstruationEventSample.Event): WireEventKind? = when (event) {
|
|
232
|
+
MenstruationEventSample.Event.INTERMENSTRUAL_BLEEDING -> WireEventKind.INTERMENSTRUAL_BLEEDING
|
|
233
|
+
MenstruationEventSample.Event.OVULATION_TEST -> WireEventKind.OVULATION_TEST
|
|
234
|
+
MenstruationEventSample.Event.CERVICAL_MUCUS -> WireEventKind.CERVICAL_MUCUS
|
|
235
|
+
MenstruationEventSample.Event.SEXUAL_ACTIVITY -> null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fun menstrualEvent(event: MenstrualEvent): WireMenstrualEvent? = eventKind(event.event)?.let {
|
|
239
|
+
WireMenstrualEvent(
|
|
240
|
+
id = event.id.value,
|
|
241
|
+
kind = it,
|
|
242
|
+
time = BridgeInstant.format(event.time),
|
|
243
|
+
value = event.value?.wire,
|
|
244
|
+
storeIdentifier = event.storeIdentifier,
|
|
245
|
+
source = Conversions.sampleSource(event.source),
|
|
246
|
+
userEntered = event.userEntered,
|
|
247
|
+
timeZone = event.timeZone?.id,
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -12,11 +12,13 @@ import co.tryterra.sdk.bridge.generated.Connection
|
|
|
12
12
|
import co.tryterra.sdk.bridge.generated.ConnectRequest
|
|
13
13
|
import co.tryterra.sdk.bridge.generated.ConnectionList
|
|
14
14
|
import co.tryterra.sdk.bridge.generated.DiagnosticsState
|
|
15
|
+
import co.tryterra.sdk.bridge.generated.DietaryQuantityList
|
|
15
16
|
import co.tryterra.sdk.bridge.generated.Empty
|
|
16
17
|
import co.tryterra.sdk.bridge.generated.HealthAccess
|
|
17
18
|
import co.tryterra.sdk.bridge.generated.HeartRateEntryList
|
|
18
19
|
import co.tryterra.sdk.bridge.generated.HeartRateList
|
|
19
20
|
import co.tryterra.sdk.bridge.generated.LastDeliveryResult
|
|
21
|
+
import co.tryterra.sdk.bridge.generated.MenstruationFlowDayList
|
|
20
22
|
import co.tryterra.sdk.bridge.generated.NutritionEntryList
|
|
21
23
|
import co.tryterra.sdk.bridge.generated.PermissionGrant
|
|
22
24
|
import co.tryterra.sdk.bridge.generated.PermissionRequest
|
|
@@ -37,7 +39,16 @@ import co.tryterra.sdk.bridge.generated.TokenRejectRequest
|
|
|
37
39
|
import co.tryterra.sdk.bridge.generated.TokenResolveRequest
|
|
38
40
|
import co.tryterra.sdk.bridge.generated.WorkoutEntryList
|
|
39
41
|
import co.tryterra.sdk.bridge.generated.WorkoutList
|
|
42
|
+
import co.tryterra.sdk.bridge.generated.AvailabilityResult
|
|
40
43
|
import co.tryterra.sdk.bridge.generated.BodyEntryList
|
|
44
|
+
import co.tryterra.sdk.bridge.generated.BodyReadingList
|
|
45
|
+
import co.tryterra.sdk.bridge.generated.DayTotalList
|
|
46
|
+
import co.tryterra.sdk.bridge.generated.HydrationList
|
|
47
|
+
import co.tryterra.sdk.bridge.generated.IntradaySampleList
|
|
48
|
+
import co.tryterra.sdk.bridge.generated.MenstrualEventList
|
|
49
|
+
import co.tryterra.sdk.bridge.generated.RestingHeartRateList
|
|
50
|
+
import co.tryterra.sdk.bridge.generated.WorkoutIntensityList
|
|
51
|
+
import co.tryterra.sdk.bridge.generated.WorkoutRouteList
|
|
41
52
|
import co.tryterra.sdk.bridge.generated.Source as WireSource
|
|
42
53
|
import kotlinx.coroutines.CancellationException
|
|
43
54
|
|
|
@@ -78,6 +89,9 @@ internal class TerraBridgeOpsImpl(
|
|
|
78
89
|
override suspend fun clientConnections(request: Empty): ConnectionList =
|
|
79
90
|
ConnectionList(terra.connections().map(Conversions::connection))
|
|
80
91
|
|
|
92
|
+
override suspend fun clientAvailability(request: SourceRequest): AvailabilityResult =
|
|
93
|
+
AvailabilityResult(ReadConversions.availability(terra.availability(sourceOf(request.source))))
|
|
94
|
+
|
|
81
95
|
override suspend fun clientDisconnect(request: SourceRequest): Empty {
|
|
82
96
|
terra.disconnect(sourceOf(request.source))
|
|
83
97
|
return Empty()
|
|
@@ -143,11 +157,7 @@ internal class TerraBridgeOpsImpl(
|
|
|
143
157
|
|
|
144
158
|
override suspend fun permissionsStatuses(request: SourceRequest): StatusesResult {
|
|
145
159
|
val statuses = permissionsOf(request.source).statuses()
|
|
146
|
-
.
|
|
147
|
-
// A category the wrapper does not project has no key to report it under. Dropping
|
|
148
|
-
// it is right: inventing one would put a name in the map that no type admits.
|
|
149
|
-
Conversions.category(category)?.let { it to Conversions.permissionStatus(status) }
|
|
150
|
-
}
|
|
160
|
+
.map { (category, status) -> Conversions.category(category) to Conversions.permissionStatus(status) }
|
|
151
161
|
.toMap()
|
|
152
162
|
return StatusesResult(statuses)
|
|
153
163
|
}
|
|
@@ -187,6 +197,12 @@ internal class TerraBridgeOpsImpl(
|
|
|
187
197
|
override suspend fun bodyHeartRate(request: RangeRequest): HeartRateList =
|
|
188
198
|
HeartRateList(terra.body.heartRate(Conversions.timeRange(request.range)).map(Conversions::heartRate))
|
|
189
199
|
|
|
200
|
+
override suspend fun bodyRestingHeartRate(request: RangeRequest): RestingHeartRateList =
|
|
201
|
+
RestingHeartRateList(terra.body.restingHeartRate(Conversions.timeRange(request.range)).map(ReadConversions::restingHeartRate))
|
|
202
|
+
|
|
203
|
+
override suspend fun bodyReadings(request: RangeRequest): BodyReadingList =
|
|
204
|
+
BodyReadingList(terra.body.readings(Conversions.timeRange(request.range)).mapNotNull(ReadConversions::bodyReading))
|
|
205
|
+
|
|
190
206
|
override suspend fun bodyLogHeartRate(request: HeartRateEntryList): RecordIdList =
|
|
191
207
|
RecordIdList(terra.body.log(request.entries.map(Conversions::entry)).map { it.value })
|
|
192
208
|
|
|
@@ -203,6 +219,12 @@ internal class TerraBridgeOpsImpl(
|
|
|
203
219
|
override suspend fun dailySteps(request: RangeRequest): StepCountList =
|
|
204
220
|
StepCountList(terra.daily.steps(Conversions.timeRange(request.range)).map(Conversions::steps))
|
|
205
221
|
|
|
222
|
+
override suspend fun dailyTotals(request: RangeRequest): DayTotalList =
|
|
223
|
+
DayTotalList(terra.daily.totals(Conversions.timeRange(request.range)).mapNotNull(ReadConversions::dayTotal))
|
|
224
|
+
|
|
225
|
+
override suspend fun dailyIntraday(request: RangeRequest): IntradaySampleList =
|
|
226
|
+
IntradaySampleList(terra.daily.intraday(Conversions.timeRange(request.range)).map(ReadConversions::intraday))
|
|
227
|
+
|
|
206
228
|
override suspend fun dailyLog(request: StepEntryList): RecordIdList =
|
|
207
229
|
RecordIdList(terra.daily.log(request.entries.map(Conversions::entry)).map { it.value })
|
|
208
230
|
|
|
@@ -229,6 +251,12 @@ internal class TerraBridgeOpsImpl(
|
|
|
229
251
|
override suspend fun activityWorkouts(request: RangeRequest): WorkoutList =
|
|
230
252
|
WorkoutList(terra.activity.workouts(Conversions.timeRange(request.range)).map(Conversions::workout))
|
|
231
253
|
|
|
254
|
+
override suspend fun activityRoutes(request: RangeRequest): WorkoutRouteList =
|
|
255
|
+
WorkoutRouteList(terra.activity.routes(Conversions.timeRange(request.range)).map(ReadConversions::route))
|
|
256
|
+
|
|
257
|
+
override suspend fun activityIntensity(request: RangeRequest): WorkoutIntensityList =
|
|
258
|
+
WorkoutIntensityList(terra.activity.intensity(Conversions.timeRange(request.range)).map(ReadConversions::intensity))
|
|
259
|
+
|
|
232
260
|
override suspend fun activityLog(request: WorkoutEntryList): RecordIdList =
|
|
233
261
|
RecordIdList(terra.activity.log(request.entries.map(Conversions::entry)).map { it.value })
|
|
234
262
|
|
|
@@ -239,6 +267,9 @@ internal class TerraBridgeOpsImpl(
|
|
|
239
267
|
|
|
240
268
|
// ---- nutrition --------------------------------------------------------------------------
|
|
241
269
|
|
|
270
|
+
override suspend fun nutritionQuantities(request: RangeRequest): DietaryQuantityList =
|
|
271
|
+
DietaryQuantityList(terra.nutrition.quantities(Conversions.timeRange(request.range)).map(Conversions::quantity))
|
|
272
|
+
|
|
242
273
|
override suspend fun nutritionLog(request: NutritionEntryList): RecordIdList =
|
|
243
274
|
RecordIdList(terra.nutrition.log(request.entries.map(Conversions::entry)).map { it.value })
|
|
244
275
|
|
|
@@ -247,6 +278,17 @@ internal class TerraBridgeOpsImpl(
|
|
|
247
278
|
return Empty()
|
|
248
279
|
}
|
|
249
280
|
|
|
281
|
+
override suspend fun nutritionHydration(request: RangeRequest): HydrationList =
|
|
282
|
+
HydrationList(terra.nutrition.hydration(Conversions.timeRange(request.range)).map(ReadConversions::hydration))
|
|
283
|
+
|
|
284
|
+
// ---- menstruation -----------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
override suspend fun menstruationFlow(request: RangeRequest): MenstruationFlowDayList =
|
|
287
|
+
MenstruationFlowDayList(terra.menstruation.flow(Conversions.timeRange(request.range)).map(Conversions::flowDay))
|
|
288
|
+
|
|
289
|
+
override suspend fun menstruationEvents(request: RangeRequest): MenstrualEventList =
|
|
290
|
+
MenstrualEventList(terra.menstruation.events(Conversions.timeRange(request.range)).mapNotNull(ReadConversions::menstrualEvent))
|
|
291
|
+
|
|
250
292
|
/** Absent means the default source, which is what every other entry point on the SDK assumes. */
|
|
251
293
|
private fun sourceOf(wire: WireSource?): Source = wire?.let(Conversions::source) ?: Source.HEALTH
|
|
252
294
|
|
|
@@ -10,7 +10,6 @@ import co.tryterra.sdk.bridge.generated.TokenRequest
|
|
|
10
10
|
import java.time.ZoneId
|
|
11
11
|
import kotlinx.coroutines.flow.Flow
|
|
12
12
|
import kotlinx.coroutines.flow.map
|
|
13
|
-
import kotlinx.coroutines.flow.mapNotNull
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* The hand-written half of the streams: which SDK flow answers each stream, as typed values.
|
|
@@ -43,10 +42,9 @@ internal class TerraBridgeStreamsImpl(
|
|
|
43
42
|
DataCategory.DAILY -> terra.daily.changes()
|
|
44
43
|
DataCategory.SLEEP -> terra.sleep.changes()
|
|
45
44
|
DataCategory.ACTIVITY -> terra.activity.changes()
|
|
46
|
-
// Nutrition is write-only on both platforms and has no `changes()`.
|
|
47
45
|
DataCategory.NUTRITION, DataCategory.MENSTRUATION ->
|
|
48
46
|
error("no change stream for $category")
|
|
49
47
|
}
|
|
50
|
-
return source.
|
|
48
|
+
return source.map { change -> Conversions.change(change, deviceZone) }
|
|
51
49
|
}
|
|
52
50
|
}
|