@tryterra/react-native 0.0.0-stage → 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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -2
  3. package/TerraReactNative.podspec +48 -0
  4. package/android/build.gradle +87 -0
  5. package/android/src/bridgeCore/kotlin/README.md +1 -0
  6. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeBootstrap.kt +133 -0
  7. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeHost.kt +32 -0
  8. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Conversions.kt +557 -0
  9. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/LauncherSlots.kt +41 -0
  10. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/ReadConversions.kt +250 -0
  11. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Rejections.kt +150 -0
  12. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/StreamRegistry.kt +77 -0
  13. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridge.kt +86 -0
  14. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeOpsImpl.kt +300 -0
  15. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeStreamsImpl.kt +50 -0
  16. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TokenBroker.kt +198 -0
  17. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeErrors.kt +217 -0
  18. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeOps.kt +582 -0
  19. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeTypes.kt +1569 -0
  20. package/android/src/healthConnectOnly/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +11 -0
  21. package/android/src/main/AndroidManifest.xml +24 -0
  22. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraBridgeModule.kt +96 -0
  23. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraReactNativePackage.kt +26 -0
  24. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraStartup.kt +46 -0
  25. package/android/src/samsung/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +13 -0
  26. package/docs/availability.md +396 -0
  27. package/frameworks/TerraSDK.xcframework/Info.plist +48 -0
  28. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Info.plist +0 -0
  29. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.abi.json +82672 -0
  30. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +2186 -0
  31. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +2186 -0
  32. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  33. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftinterface +2163 -0
  34. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
  35. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/TerraSDK +0 -0
  36. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
  37. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  38. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6594 -0
  39. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Info.plist +0 -0
  40. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +82672 -0
  41. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +2186 -0
  42. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +2186 -0
  43. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  44. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +2163 -0
  45. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +82672 -0
  46. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +2186 -0
  47. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +2186 -0
  48. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  49. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +2163 -0
  50. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
  51. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/TerraSDK +0 -0
  52. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/_CodeSignature/CodeResources +223 -0
  53. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
  54. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  55. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6595 -0
  56. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/TerraSDK.yml +6513 -0
  57. package/ios/TerraBridgeCore/BridgeBootstrap.swift +89 -0
  58. package/ios/TerraBridgeCore/BridgeHost.swift +13 -0
  59. package/ios/TerraBridgeCore/Conversions+Reads.swift +259 -0
  60. package/ios/TerraBridgeCore/Conversions.swift +594 -0
  61. package/ios/TerraBridgeCore/Generated/BridgeErrors.swift +179 -0
  62. package/ios/TerraBridgeCore/Generated/BridgeOps.swift +967 -0
  63. package/ios/TerraBridgeCore/Generated/BridgeTypes.swift +2050 -0
  64. package/ios/TerraBridgeCore/Locked.swift +15 -0
  65. package/ios/TerraBridgeCore/README.md +1 -0
  66. package/ios/TerraBridgeCore/Rejections.swift +130 -0
  67. package/ios/TerraBridgeCore/RuntimeAttachment.swift +60 -0
  68. package/ios/TerraBridgeCore/StreamRegistry.swift +59 -0
  69. package/ios/TerraBridgeCore/TerraBridge.swift +122 -0
  70. package/ios/TerraBridgeCore/TerraBridgeOpsImpl.swift +318 -0
  71. package/ios/TerraBridgeCore/TerraBridgeStreamsImpl.swift +60 -0
  72. package/ios/TerraBridgeCore/TokenBroker.swift +147 -0
  73. package/ios/TerraBridgeModule.mm +79 -0
  74. package/ios/TerraBridgeModuleImpl.swift +49 -0
  75. package/ios/TerraReactNative.swift +19 -0
  76. package/lib/EndUser.d.ts +12 -0
  77. package/lib/EndUser.js +13 -0
  78. package/lib/NativeTerraBridge.d.ts +23 -0
  79. package/lib/NativeTerraBridge.js +13 -0
  80. package/lib/Terra.d.ts +223 -0
  81. package/lib/Terra.js +336 -0
  82. package/lib/TerraError.d.ts +42 -0
  83. package/lib/TerraError.js +95 -0
  84. package/lib/createTerra.d.ts +8 -0
  85. package/lib/createTerra.js +19 -0
  86. package/lib/generated/client.d.ts +128 -0
  87. package/lib/generated/client.js +256 -0
  88. package/lib/generated/codecs.d.ts +219 -0
  89. package/lib/generated/codecs.js +2066 -0
  90. package/lib/generated/errors.d.ts +13 -0
  91. package/lib/generated/errors.js +68 -0
  92. package/lib/generated/ops.d.ts +13 -0
  93. package/lib/generated/ops.js +161 -0
  94. package/lib/generated/streams.d.ts +23 -0
  95. package/lib/generated/streams.js +58 -0
  96. package/lib/generated/types.d.ts +794 -0
  97. package/lib/generated/types.js +224 -0
  98. package/lib/index.d.ts +29 -0
  99. package/lib/index.js +55 -0
  100. package/package.json +71 -4
  101. package/src/EndUser.ts +15 -0
  102. package/src/NativeTerraBridge.ts +27 -0
  103. package/src/Terra.ts +592 -0
  104. package/src/TerraError.ts +116 -0
  105. package/src/createTerra.ts +18 -0
  106. package/src/generated/client.ts +315 -0
  107. package/src/generated/codecs.ts +1989 -0
  108. package/src/generated/errors.ts +113 -0
  109. package/src/generated/ops.ts +233 -0
  110. package/src/generated/streams.ts +74 -0
  111. package/src/generated/types.ts +1222 -0
  112. package/src/index.ts +54 -0
@@ -0,0 +1,557 @@
1
+ package co.tryterra.sdk.bridge
2
+
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
9
+ import co.tryterra.sdk.EndUser
10
+ import co.tryterra.sdk.BodyEntry
11
+ import co.tryterra.sdk.HeartRateEntry
12
+ import co.tryterra.sdk.NutritionEntry
13
+ import co.tryterra.sdk.HeartRateSample
14
+ import co.tryterra.sdk.PermissionStatus
15
+ import co.tryterra.sdk.RecordId
16
+ import co.tryterra.sdk.SampleInterval
17
+ import co.tryterra.sdk.SampleSource
18
+ import co.tryterra.sdk.SleepEntry
19
+ import co.tryterra.sdk.SleepSegment
20
+ import co.tryterra.sdk.SleepStage
21
+ import co.tryterra.sdk.Source
22
+ import co.tryterra.sdk.StepCount
23
+ import co.tryterra.sdk.StepEntry
24
+ import co.tryterra.sdk.TimeRange
25
+ import co.tryterra.sdk.Workout
26
+ import co.tryterra.sdk.WorkoutActivity
27
+ import co.tryterra.sdk.WorkoutEntry
28
+ import co.tryterra.sdk.WorkoutScope
29
+ import co.tryterra.sdk.bridge.generated.BridgeInstant
30
+ import co.tryterra.sdk.bridge.generated.BridgeError
31
+ import co.tryterra.sdk.diagnostics.BackgroundRestrictionReason
32
+ import co.tryterra.sdk.diagnostics.DiagnosticsState
33
+ import co.tryterra.sdk.diagnostics.DiagnosticsWire
34
+ import co.tryterra.sdk.diagnostics.SupportReport
35
+ import co.tryterra.sdk.diagnostics.TerraEvent
36
+ import co.tryterra.sdk.health.HealthAccess
37
+ import co.tryterra.sdk.HealthChange
38
+ import co.tryterra.sdk.store.StoredConnection
39
+ import java.time.ZoneId
40
+ import co.tryterra.sdk.bridge.generated.BackgroundRestrictionReason as WireRestriction
41
+ import co.tryterra.sdk.bridge.generated.BodyEntry as WireBodyEntry
42
+ import co.tryterra.sdk.bridge.generated.Connection as WireConnection
43
+ import co.tryterra.sdk.bridge.generated.NutritionEntry as WireNutritionEntry
44
+ import co.tryterra.sdk.bridge.generated.Nutrients as WireNutrients
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
51
+ import co.tryterra.sdk.bridge.generated.DiagnosticsState as WireDiagnosticsState
52
+ import co.tryterra.sdk.bridge.generated.EndUser as WireEndUser
53
+ import co.tryterra.sdk.bridge.generated.HealthAccess as WireHealthAccess
54
+ import co.tryterra.sdk.bridge.generated.HealthChange as WireHealthChange
55
+ import co.tryterra.sdk.bridge.generated.HeartRateEntry as WireHeartRateEntry
56
+ import co.tryterra.sdk.bridge.generated.HeartRateSample as WireHeartRateSample
57
+ import co.tryterra.sdk.bridge.generated.MotionContext as WireMotionContext
58
+ import co.tryterra.sdk.bridge.generated.PermissionStatus as WirePermissionStatus
59
+ import co.tryterra.sdk.bridge.generated.SampleSource as WireSampleSource
60
+ import co.tryterra.sdk.bridge.generated.SleepEntry as WireSleepEntry
61
+ import co.tryterra.sdk.bridge.generated.SleepSegment as WireSleepSegment
62
+ import co.tryterra.sdk.bridge.generated.SleepStage as WireSleepStage
63
+ import co.tryterra.sdk.bridge.generated.Source as WireSource
64
+ import co.tryterra.sdk.bridge.generated.StepCount as WireStepCount
65
+ import co.tryterra.sdk.bridge.generated.StepEntry as WireStepEntry
66
+ import co.tryterra.sdk.bridge.generated.SupportReport as WireSupportReport
67
+ import co.tryterra.sdk.bridge.generated.TerraEvent as WireTerraEvent
68
+ import co.tryterra.sdk.bridge.generated.TimeRange as WireTimeRange
69
+ import co.tryterra.sdk.bridge.generated.Workout as WireWorkout
70
+ import co.tryterra.sdk.bridge.generated.WorkoutActivity as WireWorkoutActivity
71
+ import co.tryterra.sdk.bridge.generated.WorkoutEntry as WireWorkoutEntry
72
+ import co.tryterra.sdk.bridge.generated.WorkoutScope as WireWorkoutScope
73
+
74
+ /**
75
+ * Between the SDK's types and the generated wire DTOs, in one place.
76
+ *
77
+ * Every `when` here is exhaustive over a sealed type or an enum with no `else`, which is what makes
78
+ * a case added to the SDK a compile error in this file rather than a value that quietly maps to
79
+ * something plausible. That is lock 1 of the four, and it is the lock the old wrapper did not have:
80
+ * `TerraReactModule.java` and `TerraReact.swift` each carried a `switch` with a fall-through, and a
81
+ * data type stopped travelling without anything going red.
82
+ *
83
+ * The SDK's models are deliberately not `@Serializable`. Keeping the wire shape out of the SDK's
84
+ * public types is the better boundary, and the price of it is this file.
85
+ */
86
+ internal object Conversions {
87
+
88
+ // ---- enums ------------------------------------------------------------------------------
89
+
90
+ fun source(wire: WireSource): Source = when (wire) {
91
+ WireSource.HEALTH -> Source.HEALTH
92
+ WireSource.SAMSUNG_HEALTH -> Source.SAMSUNG_HEALTH
93
+ }
94
+
95
+ fun source(source: Source): WireSource = when (source) {
96
+ Source.HEALTH -> WireSource.HEALTH
97
+ Source.SAMSUNG_HEALTH -> WireSource.SAMSUNG_HEALTH
98
+ }
99
+
100
+ /**
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.
103
+ */
104
+ fun category(wire: WireCategory): DataCategory = when (wire) {
105
+ WireCategory.ACTIVITY -> DataCategory.ACTIVITY
106
+ WireCategory.BODY -> DataCategory.BODY
107
+ WireCategory.DAILY -> DataCategory.DAILY
108
+ WireCategory.SLEEP -> DataCategory.SLEEP
109
+ WireCategory.NUTRITION -> DataCategory.NUTRITION
110
+ WireCategory.MENSTRUATION -> DataCategory.MENSTRUATION
111
+ WireCategory.ATHLETE -> throw BridgeError.UnsupportedOnPlatform("athlete")
112
+ }
113
+
114
+ fun category(category: DataCategory): WireCategory = when (category) {
115
+ DataCategory.ACTIVITY -> WireCategory.ACTIVITY
116
+ DataCategory.BODY -> WireCategory.BODY
117
+ DataCategory.DAILY -> WireCategory.DAILY
118
+ DataCategory.SLEEP -> WireCategory.SLEEP
119
+ DataCategory.NUTRITION -> WireCategory.NUTRITION
120
+ DataCategory.MENSTRUATION -> WireCategory.MENSTRUATION
121
+ }
122
+
123
+ /**
124
+ * `UNAVAILABLE` is Apple's and has no Android counterpart, so nothing here can produce it.
125
+ *
126
+ * The surface types it as iOS-only for exactly that reason: a caller narrowing on it is writing
127
+ * iOS-specific code, and the type says so rather than letting them discover it.
128
+ */
129
+ fun permissionStatus(status: PermissionStatus): WirePermissionStatus = when (status) {
130
+ PermissionStatus.GRANTED -> WirePermissionStatus.GRANTED
131
+ PermissionStatus.DENIED -> WirePermissionStatus.DENIED
132
+ PermissionStatus.NOT_DETERMINED -> WirePermissionStatus.NOT_DETERMINED
133
+ PermissionStatus.NOT_DISCLOSED -> WirePermissionStatus.NOT_DISCLOSED
134
+ PermissionStatus.UNSUPPORTED -> WirePermissionStatus.UNSUPPORTED
135
+ }
136
+
137
+ fun restriction(reason: BackgroundRestrictionReason): WireRestriction = when (reason) {
138
+ BackgroundRestrictionReason.OEM_RESTRICTED -> WireRestriction.OEM_RESTRICTED
139
+ BackgroundRestrictionReason.BACKGROUND_READ_DENIED -> WireRestriction.BACKGROUND_READ_DENIED
140
+ BackgroundRestrictionReason.HEALTH_CONNECT_FEATURE_UNAVAILABLE ->
141
+ WireRestriction.HEALTH_CONNECT_FEATURE_UNAVAILABLE
142
+ BackgroundRestrictionReason.STORE_FEATURE_UNAVAILABLE -> WireRestriction.STORE_FEATURE_UNAVAILABLE
143
+ BackgroundRestrictionReason.TICKS_NOT_RUNNING -> WireRestriction.TICKS_NOT_RUNNING
144
+ }
145
+
146
+ // ---- unions -----------------------------------------------------------------------------
147
+
148
+ fun endUser(wire: WireEndUser): EndUser = when (wire) {
149
+ is WireEndUser.Reference -> EndUser.Reference(wire.id)
150
+ WireEndUser.Anonymous -> EndUser.Anonymous
151
+ }
152
+
153
+ fun timeRange(wire: WireTimeRange): TimeRange = when (wire) {
154
+ is WireTimeRange.LastHours -> TimeRange.LastHours(wire.hours)
155
+ is WireTimeRange.LastDays -> TimeRange.LastDays(wire.days)
156
+ WireTimeRange.Today -> TimeRange.Today
157
+ is WireTimeRange.Between -> TimeRange.Between(
158
+ start = BridgeInstant.parse(wire.start),
159
+ end = BridgeInstant.parse(wire.end),
160
+ )
161
+ }
162
+
163
+ fun sleepStage(stage: SleepStage): WireSleepStage = when (stage) {
164
+ SleepStage.InBed -> WireSleepStage.InBed
165
+ SleepStage.Awake -> WireSleepStage.Awake
166
+ SleepStage.Asleep -> WireSleepStage.Asleep
167
+ SleepStage.Light -> WireSleepStage.Light
168
+ SleepStage.Deep -> WireSleepStage.Deep
169
+ SleepStage.Rem -> WireSleepStage.Rem
170
+ is SleepStage.Unknown -> WireSleepStage.Unknown(stage.raw)
171
+ }
172
+
173
+ fun sleepStage(wire: WireSleepStage): SleepStage = when (wire) {
174
+ WireSleepStage.InBed -> SleepStage.InBed
175
+ WireSleepStage.Awake -> SleepStage.Awake
176
+ WireSleepStage.Asleep -> SleepStage.Asleep
177
+ WireSleepStage.Light -> SleepStage.Light
178
+ WireSleepStage.Deep -> SleepStage.Deep
179
+ WireSleepStage.Rem -> SleepStage.Rem
180
+ is WireSleepStage.Unknown -> SleepStage.Unknown(wire.raw)
181
+ }
182
+
183
+ fun activity(activity: WorkoutActivity): WireWorkoutActivity = when (activity) {
184
+ WorkoutActivity.Running -> WireWorkoutActivity.Running
185
+ WorkoutActivity.Walking -> WireWorkoutActivity.Walking
186
+ WorkoutActivity.Cycling -> WireWorkoutActivity.Cycling
187
+ WorkoutActivity.Swimming -> WireWorkoutActivity.Swimming
188
+ WorkoutActivity.StrengthTraining -> WireWorkoutActivity.StrengthTraining
189
+ is WorkoutActivity.Other -> WireWorkoutActivity.Other(activity.label)
190
+ }
191
+
192
+ fun activity(wire: WireWorkoutActivity): WorkoutActivity = when (wire) {
193
+ WireWorkoutActivity.Running -> WorkoutActivity.Running
194
+ WireWorkoutActivity.Walking -> WorkoutActivity.Walking
195
+ WireWorkoutActivity.Cycling -> WorkoutActivity.Cycling
196
+ WireWorkoutActivity.Swimming -> WorkoutActivity.Swimming
197
+ WireWorkoutActivity.StrengthTraining -> WorkoutActivity.StrengthTraining
198
+ is WireWorkoutActivity.Other -> WorkoutActivity.Other(wire.label)
199
+ }
200
+
201
+ /**
202
+ * Android has no `devIDMismatch` state - it raises `dev_id_mismatch` instead - so nothing here
203
+ * produces that variant. The surface types it as iOS-only.
204
+ */
205
+ fun diagnosticsState(state: DiagnosticsState): WireDiagnosticsState = when (state) {
206
+ DiagnosticsState.Healthy -> WireDiagnosticsState.Healthy
207
+ is DiagnosticsState.Quiet -> WireDiagnosticsState.Quiet(BridgeInstant.format(state.since))
208
+ is DiagnosticsState.BackgroundRestricted ->
209
+ WireDiagnosticsState.BackgroundRestricted(restriction(state.reason))
210
+ DiagnosticsState.BackgroundRefreshUnscheduled -> WireDiagnosticsState.BackgroundRefreshUnscheduled
211
+ DiagnosticsState.PendingRetry -> WireDiagnosticsState.PendingRetry
212
+ DiagnosticsState.LinkRevoked -> WireDiagnosticsState.LinkRevoked
213
+ }
214
+
215
+ // ---- records ----------------------------------------------------------------------------
216
+
217
+ fun sampleSource(source: SampleSource): WireSampleSource = WireSampleSource(
218
+ name = source.name,
219
+ bundleId = source.bundleId,
220
+ productType = source.productType,
221
+ deviceType = source.deviceType,
222
+ )
223
+
224
+ fun connection(stored: StoredConnection): WireConnection = WireConnection(
225
+ source = source(stored.source),
226
+ userId = stored.userId,
227
+ providerUserId = stored.providerUserId,
228
+ )
229
+
230
+ fun heartRate(sample: HeartRateSample): WireHeartRateSample = WireHeartRateSample(
231
+ id = sample.id.value,
232
+ time = BridgeInstant.format(sample.time),
233
+ beatsPerMinute = sample.beatsPerMinute,
234
+ source = sampleSource(sample.source),
235
+ motionContext = sample.motionContext?.let {
236
+ when (it) {
237
+ co.tryterra.sdk.MotionContext.UNKNOWN -> WireMotionContext.UNKNOWN
238
+ co.tryterra.sdk.MotionContext.SEDENTARY -> WireMotionContext.SEDENTARY
239
+ co.tryterra.sdk.MotionContext.ACTIVE -> WireMotionContext.ACTIVE
240
+ }
241
+ },
242
+ userEntered = sample.userEntered,
243
+ timeZone = sample.timeZone?.id,
244
+ )
245
+
246
+ fun steps(sample: StepCount): WireStepCount = WireStepCount(
247
+ id = sample.id?.value,
248
+ start = BridgeInstant.format(sample.interval.start),
249
+ end = BridgeInstant.format(sample.interval.end),
250
+ steps = sample.steps,
251
+ source = sampleSource(sample.source),
252
+ userEntered = sample.userEntered,
253
+ timeZone = sample.timeZone?.id,
254
+ )
255
+
256
+ fun segment(segment: SleepSegment): WireSleepSegment = WireSleepSegment(
257
+ id = segment.id.value,
258
+ start = BridgeInstant.format(segment.interval.start),
259
+ end = BridgeInstant.format(segment.interval.end),
260
+ stage = sleepStage(segment.stage),
261
+ source = sampleSource(segment.source),
262
+ userEntered = segment.userEntered,
263
+ timeZone = segment.timeZone?.id,
264
+ )
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
+
348
+ fun workout(workout: Workout): WireWorkout = WireWorkout(
349
+ id = workout.id.value,
350
+ start = BridgeInstant.format(workout.interval.start),
351
+ end = BridgeInstant.format(workout.interval.end),
352
+ activity = activity(workout.activity),
353
+ activityTypeCode = workout.activityTypeCode,
354
+ indoor = workout.indoor,
355
+ activeEnergyKilocalories = workout.activeEnergyKilocalories,
356
+ distanceMeters = workout.distanceMeters,
357
+ source = sampleSource(workout.source),
358
+ userEntered = workout.userEntered,
359
+ timeZone = workout.timeZone?.id,
360
+ title = workout.title,
361
+ )
362
+
363
+ /**
364
+ * A change carries a day, not an instant.
365
+ *
366
+ * Sending the instant would make the receiving side re-derive the day in its own zone, and get
367
+ * a different one either side of midnight.
368
+ */
369
+ fun change(change: HealthChange, zone: ZoneId): WireHealthChange =
370
+ WireHealthChange(category = category(change.category), day = change.day.atZone(zone).toLocalDate().toString())
371
+
372
+ /**
373
+ * An event is the object `DiagnosticsWire` already produces, carried unchanged.
374
+ *
375
+ * Not reshaped into a `{kind, payload}` of the wrapper's own: that would be a hand-written
376
+ * second contract here and another in the Swift half, sitting between the SDK and the generated
377
+ * codec where no vector can reach either of them. `EventPrivacyTest` guards this object against
378
+ * carrying a health value, and passing it through is what keeps that guarantee the only one.
379
+ */
380
+ fun event(event: TerraEvent): WireTerraEvent = DiagnosticsWire.event(event)
381
+
382
+ fun report(report: SupportReport): WireSupportReport = WireSupportReport(
383
+ sdkVersion = report.sdkVersion,
384
+ generatedAt = BridgeInstant.format(report.generatedAt),
385
+ state = diagnosticsState(report.state),
386
+ lastDelivery = report.lastDelivery?.let { BridgeInstant.format(it) },
387
+ isConnected = report.isConnected,
388
+ requestedCategories = report.requestedCategories,
389
+ requestedWrites = report.requestedWrites,
390
+ requestedWorkouts = report.requestedWorkouts,
391
+ recentEvents = report.recentEvents.map(::event),
392
+ )
393
+
394
+ /**
395
+ * Sorted and merged, because a `Set` has no order and JSON has no set.
396
+ *
397
+ * Two scopes for one activity are one grant with both flags, not two entries: `HealthAccess`
398
+ * can hold `Running+energy` and `Running+distance` separately, and sending both would let a
399
+ * caller read a grant that contradicts itself depending on which it looked at first. Sorting is
400
+ * what makes two grants differing only in iteration order compare equal on the wire, without
401
+ * which the vectors would be flaky.
402
+ */
403
+ fun access(access: HealthAccess): WireHealthAccess = WireHealthAccess(
404
+ read = access.read.map(::category).sortedBy { it.name },
405
+ write = access.write.map(::category).sortedBy { it.name },
406
+ workouts = access.workouts
407
+ .groupBy { WorkoutActivity.reportName(it.activity) }
408
+ .toSortedMap()
409
+ .map { (_, scopes) ->
410
+ WireWorkoutScope(
411
+ activity = activity(scopes.first().activity),
412
+ energy = scopes.any { it.energy },
413
+ distance = scopes.any { it.distance },
414
+ )
415
+ },
416
+ )
417
+
418
+ fun scope(scope: WorkoutScope): WireWorkoutScope = WireWorkoutScope(
419
+ activity = activity(scope.activity),
420
+ energy = scope.energy,
421
+ distance = scope.distance,
422
+ )
423
+
424
+ fun scope(wire: WireWorkoutScope): WorkoutScope = WorkoutScope(
425
+ activity = activity(wire.activity),
426
+ energy = wire.energy,
427
+ distance = wire.distance,
428
+ )
429
+
430
+ // ---- write entries ----------------------------------------------------------------------
431
+
432
+ /**
433
+ * A caller-supplied record id is honoured; an absent one is minted by the SDK's own default.
434
+ *
435
+ * Minting one here would use a different scheme from `RecordHandles.mint`, and `delete` only
436
+ * accepts handles that scheme produced.
437
+ */
438
+ fun entry(wire: WireHeartRateEntry): HeartRateEntry =
439
+ wire.recordId?.let { HeartRateEntry(BridgeInstant.parse(wire.time), wire.beatsPerMinute, RecordId(it)) }
440
+ ?: HeartRateEntry(BridgeInstant.parse(wire.time), wire.beatsPerMinute)
441
+
442
+ fun entry(wire: WireStepEntry): StepEntry {
443
+ val interval = SampleInterval(BridgeInstant.parse(wire.start), BridgeInstant.parse(wire.end))
444
+ return wire.recordId?.let { StepEntry(interval, wire.steps, RecordId(it)) }
445
+ ?: StepEntry(interval, wire.steps)
446
+ }
447
+
448
+ fun entry(wire: WireSleepEntry): SleepEntry {
449
+ val interval = SampleInterval(BridgeInstant.parse(wire.start), BridgeInstant.parse(wire.end))
450
+ val stage = sleepStage(wire.stage)
451
+ return wire.recordId?.let { SleepEntry(interval, stage, RecordId(it)) } ?: SleepEntry(interval, stage)
452
+ }
453
+
454
+ fun entry(wire: WireWorkoutEntry): WorkoutEntry {
455
+ val interval = SampleInterval(BridgeInstant.parse(wire.start), BridgeInstant.parse(wire.end))
456
+ val activity = activity(wire.activity)
457
+ return wire.recordId?.let {
458
+ WorkoutEntry(interval, activity, wire.totalEnergyKilocalories, wire.totalDistanceMeters, RecordId(it))
459
+ } ?: WorkoutEntry(interval, activity, wire.totalEnergyKilocalories, wire.totalDistanceMeters)
460
+ }
461
+
462
+ /**
463
+ * Ten optional measurements and a time, one for one with `BodyEntry`.
464
+ *
465
+ * Spelled out rather than reflected: a field added to `BodyEntry` should be a decision here,
466
+ * and reflection would carry it silently with whatever name the SDK happened to give it.
467
+ */
468
+ fun entry(wire: WireBodyEntry): BodyEntry {
469
+ val time = BridgeInstant.parse(wire.time)
470
+ return wire.recordId?.let {
471
+ BodyEntry(
472
+ time = time,
473
+ bodyMassKilograms = wire.bodyMassKilograms,
474
+ bodyFatPercent = wire.bodyFatPercent,
475
+ bodyMassIndex = wire.bodyMassIndex,
476
+ heightMeters = wire.heightMeters,
477
+ leanBodyMassKilograms = wire.leanBodyMassKilograms,
478
+ bodyTemperatureCelsius = wire.bodyTemperatureCelsius,
479
+ oxygenSaturationPercent = wire.oxygenSaturationPercent,
480
+ bloodGlucoseMilligramsPerDeciliter = wire.bloodGlucoseMilligramsPerDeciliter,
481
+ bloodPressureSystolicMillimetersOfMercury = wire.bloodPressureSystolicMillimetersOfMercury,
482
+ bloodPressureDiastolicMillimetersOfMercury = wire.bloodPressureDiastolicMillimetersOfMercury,
483
+ recordId = RecordId(it),
484
+ )
485
+ } ?: BodyEntry(
486
+ time = time,
487
+ bodyMassKilograms = wire.bodyMassKilograms,
488
+ bodyFatPercent = wire.bodyFatPercent,
489
+ bodyMassIndex = wire.bodyMassIndex,
490
+ heightMeters = wire.heightMeters,
491
+ leanBodyMassKilograms = wire.leanBodyMassKilograms,
492
+ bodyTemperatureCelsius = wire.bodyTemperatureCelsius,
493
+ oxygenSaturationPercent = wire.oxygenSaturationPercent,
494
+ bloodGlucoseMilligramsPerDeciliter = wire.bloodGlucoseMilligramsPerDeciliter,
495
+ bloodPressureSystolicMillimetersOfMercury = wire.bloodPressureSystolicMillimetersOfMercury,
496
+ bloodPressureDiastolicMillimetersOfMercury = wire.bloodPressureDiastolicMillimetersOfMercury,
497
+ )
498
+ }
499
+
500
+ fun entry(wire: WireNutritionEntry): NutritionEntry {
501
+ val interval = SampleInterval(BridgeInstant.parse(wire.start), BridgeInstant.parse(wire.end))
502
+ val nutrients = nutrients(wire.nutrients)
503
+ return wire.recordId?.let { NutritionEntry(interval, wire.name, nutrients, RecordId(it)) }
504
+ ?: NutritionEntry(interval, wire.name, nutrients)
505
+ }
506
+
507
+ /**
508
+ * All thirty-nine, by name.
509
+ *
510
+ * The unit is in every name on both sides, so a mismatched pair is a compile error rather than
511
+ * a value that arrives in the wrong unit - which is the class of bug that would be invisible in
512
+ * every test that did not know what the number should be.
513
+ */
514
+ private fun nutrients(wire: WireNutrients): NutritionEntry.Nutrients = NutritionEntry.Nutrients(
515
+ energyKilocalories = wire.energyKilocalories,
516
+ proteinGrams = wire.proteinGrams,
517
+ carbohydratesGrams = wire.carbohydratesGrams,
518
+ fiberGrams = wire.fiberGrams,
519
+ sugarGrams = wire.sugarGrams,
520
+ fatTotalGrams = wire.fatTotalGrams,
521
+ fatSaturatedGrams = wire.fatSaturatedGrams,
522
+ fatMonounsaturatedGrams = wire.fatMonounsaturatedGrams,
523
+ fatPolyunsaturatedGrams = wire.fatPolyunsaturatedGrams,
524
+ cholesterolMilligrams = wire.cholesterolMilligrams,
525
+ sodiumMilligrams = wire.sodiumMilligrams,
526
+ potassiumMilligrams = wire.potassiumMilligrams,
527
+ calciumMilligrams = wire.calciumMilligrams,
528
+ ironMilligrams = wire.ironMilligrams,
529
+ magnesiumMilligrams = wire.magnesiumMilligrams,
530
+ zincMilligrams = wire.zincMilligrams,
531
+ waterLiters = wire.waterLiters,
532
+ caffeineMilligrams = wire.caffeineMilligrams,
533
+ vitaminAMicrograms = wire.vitaminAMicrograms,
534
+ vitaminCMilligrams = wire.vitaminCMilligrams,
535
+ vitaminDMicrograms = wire.vitaminDMicrograms,
536
+ vitaminEMilligrams = wire.vitaminEMilligrams,
537
+ vitaminKMicrograms = wire.vitaminKMicrograms,
538
+ vitaminB6Milligrams = wire.vitaminB6Milligrams,
539
+ vitaminB12Micrograms = wire.vitaminB12Micrograms,
540
+ folateMicrograms = wire.folateMicrograms,
541
+ thiaminMilligrams = wire.thiaminMilligrams,
542
+ riboflavinMilligrams = wire.riboflavinMilligrams,
543
+ niacinMilligrams = wire.niacinMilligrams,
544
+ biotinMicrograms = wire.biotinMicrograms,
545
+ pantothenicAcidMilligrams = wire.pantothenicAcidMilligrams,
546
+ phosphorusMilligrams = wire.phosphorusMilligrams,
547
+ iodineMicrograms = wire.iodineMicrograms,
548
+ chromiumMicrograms = wire.chromiumMicrograms,
549
+ copperMilligrams = wire.copperMilligrams,
550
+ manganeseMilligrams = wire.manganeseMilligrams,
551
+ molybdenumMicrograms = wire.molybdenumMicrograms,
552
+ seleniumMicrograms = wire.seleniumMicrograms,
553
+ chlorideMilligrams = wire.chlorideMilligrams,
554
+ )
555
+
556
+
557
+ }
@@ -0,0 +1,41 @@
1
+ package co.tryterra.sdk.bridge
2
+
3
+ import co.tryterra.sdk.Source
4
+
5
+ /**
6
+ * Which activity's launcher presents a permission sheet: the most recently started one that is
7
+ * still started.
8
+ *
9
+ * Creation order is not that. An app with two tasks, or one that returns to an older activity,
10
+ * keeps a newer activity alive but stopped, and a sheet launched through it would not be seen.
11
+ */
12
+ internal class LauncherSlots<A : Any, L : Any> {
13
+ private val made = HashMap<A, Map<Source, L>>()
14
+ private val started = LinkedHashSet<A>()
15
+
16
+ @Synchronized
17
+ fun created(activity: A, launchers: Map<Source, L>) {
18
+ made[activity] = launchers
19
+ }
20
+
21
+ @Synchronized
22
+ fun started(activity: A) {
23
+ started.remove(activity)
24
+ started.add(activity)
25
+ }
26
+
27
+ @Synchronized
28
+ fun stopped(activity: A) {
29
+ started.remove(activity)
30
+ }
31
+
32
+ /** Forgets [activity], answering the launchers it held so the caller can unregister them. */
33
+ @Synchronized
34
+ fun destroyed(activity: A): Map<Source, L>? {
35
+ started.remove(activity)
36
+ return made.remove(activity)
37
+ }
38
+
39
+ @Synchronized
40
+ fun current(source: Source): L? = started.toList().asReversed().firstNotNullOfOrNull { made[it]?.get(source) }
41
+ }