@cariva-dev/exercise-sdk 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CarivaExerciseSdk.podspec +20 -0
- package/README.md +374 -0
- package/android/build.gradle +66 -0
- package/android/src/main/AndroidManifest.xml +40 -0
- package/android/src/main/java/com/carivaexercisesdk/CarivaExerciseSdkPackage.kt +30 -0
- package/android/src/main/java/com/carivaexercisesdk/HealthConnectModule.kt +222 -0
- package/android/src/main/java/com/carivaexercisesdk/HealthConnectPermissionUsageActivity.kt +11 -0
- package/android/src/main/java/com/carivaexercisesdk/HealthConnectPermissionsRationaleActivity.kt +11 -0
- package/android/src/main/java/com/carivaexercisesdk/Pagination.kt +23 -0
- package/android/src/main/java/com/carivaexercisesdk/application/connection/command/ConnectCommand.kt +12 -0
- package/android/src/main/java/com/carivaexercisesdk/application/connection/dto/ConnectResultDto.kt +13 -0
- package/android/src/main/java/com/carivaexercisesdk/application/connection/handler/ConnectHandler.kt +79 -0
- package/android/src/main/java/com/carivaexercisesdk/application/connection/port/HealthConnectConnectionPort.kt +16 -0
- package/android/src/main/java/com/carivaexercisesdk/application/connection/port/PermissionRequestPort.kt +5 -0
- package/android/src/main/java/com/carivaexercisesdk/application/datasource/command/SetDatasourcePolicyCommand.kt +6 -0
- package/android/src/main/java/com/carivaexercisesdk/application/datasource/handler/GetDatasourcePolicyHandler.kt +10 -0
- package/android/src/main/java/com/carivaexercisesdk/application/datasource/handler/SetDatasourcePolicyHandler.kt +22 -0
- package/android/src/main/java/com/carivaexercisesdk/application/datasource/port/DatasourcePolicyRepository.kt +9 -0
- package/android/src/main/java/com/carivaexercisesdk/application/exercise/dto/BasalReadResultDto.kt +11 -0
- package/android/src/main/java/com/carivaexercisesdk/application/exercise/dto/ExerciseDataDto.kt +41 -0
- package/android/src/main/java/com/carivaexercisesdk/application/exercise/handler/GetExerciseDataHandler.kt +346 -0
- package/android/src/main/java/com/carivaexercisesdk/application/exercise/port/ExerciseRecordPort.kt +45 -0
- package/android/src/main/java/com/carivaexercisesdk/application/exercise/query/GetExerciseDataQuery.kt +9 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/connection/entity/ConnectionAggregate.kt +13 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/connection/service/ConnectionDecisionService.kt +37 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/connection/valueobject/ConnectionNextAction.kt +8 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/connection/valueobject/HealthConnectStatus.kt +8 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/connection/valueobject/PermissionScope.kt +36 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/datasource/entity/DatasourcePolicy.kt +8 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/datasource/valueobject/DatasourceType.kt +14 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/exercise/services/ExerciseDomainService.kt +89 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/exercise/services/HealthConnectCaloriesFallbackMerger.kt +351 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/exercise/services/HealthConnectInternalsConstants.kt +55 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/exercise/services/HealthConnectModuleInternals.kt +316 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/exercise/services/HealthConnectOverlapNormalizer.kt +400 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/exercise/services/HealthConnectSourceTrust.kt +249 -0
- package/android/src/main/java/com/carivaexercisesdk/domain/exercise/services/HealthConnectUnifiedRecordBuilder.kt +316 -0
- package/android/src/main/java/com/carivaexercisesdk/infrastructure/healthconnect/HealthConnectConnectionAdapter.kt +156 -0
- package/android/src/main/java/com/carivaexercisesdk/infrastructure/healthconnect/HealthConnectExerciseRecordAdapter.kt +464 -0
- package/android/src/main/java/com/carivaexercisesdk/infrastructure/persistence/InMemoryDatasourcePolicyRepository.kt +21 -0
- package/android/src/main/java/com/carivaexercisesdk/infrastructure/reactnative/ConnectResultMapper.kt +23 -0
- package/android/src/main/java/com/carivaexercisesdk/infrastructure/reactnative/DatasourcePolicyJsonParser.kt +51 -0
- package/android/src/main/java/com/carivaexercisesdk/infrastructure/reactnative/DatasourcePolicyMapper.kt +46 -0
- package/android/src/main/java/com/carivaexercisesdk/infrastructure/reactnative/ExerciseDataMapper.kt +99 -0
- package/android/src/test/java/com/carivaexercisesdk/ArchitectureDependencyRuleTest.kt +60 -0
- package/android/src/test/java/com/carivaexercisesdk/HealthConnectModuleDatasourceParserTest.kt +69 -0
- package/android/src/test/java/com/carivaexercisesdk/HealthConnectModuleInternalsTest.kt +406 -0
- package/android/src/test/java/com/carivaexercisesdk/application/connection/handler/ConnectHandlerTest.kt +153 -0
- package/android/src/test/java/com/carivaexercisesdk/application/datasource/handler/DatasourcePolicyRoundTripTest.kt +63 -0
- package/android/src/test/java/com/carivaexercisesdk/application/datasource/handler/SetDatasourcePolicyHandlerTest.kt +42 -0
- package/android/src/test/java/com/carivaexercisesdk/domain/connection/service/ConnectionDecisionServiceTest.kt +68 -0
- package/android/src/test/java/com/carivaexercisesdk/infrastructure/reactnative/DatasourcePolicyMapperTest.kt +22 -0
- package/ios/CarivaExerciseSdk.h +5 -0
- package/ios/CarivaExerciseSdk.mm +7 -0
- package/lib/module/connect/index.js +7 -0
- package/lib/module/connect/index.js.map +1 -0
- package/lib/module/datasource/index.js +10 -0
- package/lib/module/datasource/index.js.map +1 -0
- package/lib/module/exercise/index.js +7 -0
- package/lib/module/exercise/index.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native/module.js +13 -0
- package/lib/module/native/module.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/connect/index.d.ts +16 -0
- package/lib/typescript/src/connect/index.d.ts.map +1 -0
- package/lib/typescript/src/datasource/index.d.ts +12 -0
- package/lib/typescript/src/datasource/index.d.ts.map +1 -0
- package/lib/typescript/src/exercise/index.d.ts +64 -0
- package/lib/typescript/src/exercise/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/native/module.d.ts +14 -0
- package/lib/typescript/src/native/module.d.ts.map +1 -0
- package/package.json +127 -0
- package/src/connect/index.ts +34 -0
- package/src/datasource/index.ts +20 -0
- package/src/exercise/index.ts +75 -0
- package/src/index.tsx +22 -0
- package/src/native/module.ts +23 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "CarivaExerciseSdk"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/cariva/cariva-exercise-sdk.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
install_modules_dependencies(s)
|
|
20
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# @cariva/exercise-sdk
|
|
2
|
+
|
|
3
|
+
React Native SDK for reading exercise and activity data from **Android Health Connect**.
|
|
4
|
+
|
|
5
|
+
## Platform Support
|
|
6
|
+
|
|
7
|
+
| Platform | Status |
|
|
8
|
+
|----------|--------|
|
|
9
|
+
| Android | Supported (Health Connect) |
|
|
10
|
+
| iOS | Out of scope for this release |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- React Native **0.68+** (New Architecture / Turbo Module compatible)
|
|
17
|
+
- Android **API level 26+**
|
|
18
|
+
- [Health Connect](https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata) installed on the device (or available through Play Services on Android 14+)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### 1. Install the package
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
# npm
|
|
28
|
+
npm install @cariva/exercise-sdk
|
|
29
|
+
|
|
30
|
+
# yarn
|
|
31
|
+
yarn add @cariva/exercise-sdk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Android — AndroidManifest.xml
|
|
35
|
+
|
|
36
|
+
The SDK ships its own manifest with Health Connect permissions, but you must also declare **two additional entries** in your app's `AndroidManifest.xml`:
|
|
37
|
+
|
|
38
|
+
#### 2a. Add the `<queries>` block (package visibility)
|
|
39
|
+
|
|
40
|
+
Inside the root `<manifest>` element:
|
|
41
|
+
|
|
42
|
+
```xml
|
|
43
|
+
<queries>
|
|
44
|
+
<package android:name="com.google.android.apps.healthdata" />
|
|
45
|
+
<package android:name="com.google.android.healthconnect.controller" />
|
|
46
|
+
</queries>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### 2b. Add the Health Connect permission rationale activities
|
|
50
|
+
|
|
51
|
+
Inside the `<application>` element:
|
|
52
|
+
|
|
53
|
+
```xml
|
|
54
|
+
<!-- Required by Health Connect: shown when user taps "Privacy policy" in permissions dialog -->
|
|
55
|
+
<activity
|
|
56
|
+
android:name="com.carivaexercisesdk.HealthConnectPermissionsRationaleActivity"
|
|
57
|
+
android:exported="true">
|
|
58
|
+
<intent-filter>
|
|
59
|
+
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
|
|
60
|
+
</intent-filter>
|
|
61
|
+
</activity>
|
|
62
|
+
|
|
63
|
+
<!-- Required by Health Connect: shown from Android Settings > App permissions -->
|
|
64
|
+
<activity
|
|
65
|
+
android:name="com.carivaexercisesdk.HealthConnectPermissionUsageActivity"
|
|
66
|
+
android:exported="true"
|
|
67
|
+
android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
|
|
68
|
+
<intent-filter>
|
|
69
|
+
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
|
|
70
|
+
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
|
|
71
|
+
</intent-filter>
|
|
72
|
+
</activity>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
> **Note:** These activities are part of the Health Connect compliance requirement. The app will still build without them, but Health Connect permission management will not work correctly.
|
|
76
|
+
|
|
77
|
+
### 3. iOS
|
|
78
|
+
|
|
79
|
+
No additional setup is required. The SDK is a no-op on iOS.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import {
|
|
87
|
+
connect,
|
|
88
|
+
getExerciseData,
|
|
89
|
+
setAllowDatasource,
|
|
90
|
+
getAllowDatasource,
|
|
91
|
+
} from '@cariva/exercise-sdk';
|
|
92
|
+
|
|
93
|
+
// 1. Connect and request permissions
|
|
94
|
+
const status = await connect({
|
|
95
|
+
onMissingApp: 'status',
|
|
96
|
+
requestPermission: true,
|
|
97
|
+
permissionScope: ['steps', 'calories'],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!status.ready) {
|
|
101
|
+
console.log('Next action required:', status.nextAction);
|
|
102
|
+
// e.g. 'INSTALL_HEALTH_CONNECT' | 'REQUEST_PERMISSION' | ...
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 2. Fetch exercise data
|
|
107
|
+
const data = await getExerciseData({
|
|
108
|
+
sinceMillis: Date.now() - 7 * 24 * 60 * 60 * 1000, // last 7 days
|
|
109
|
+
untilMillis: Date.now(),
|
|
110
|
+
type: 'all',
|
|
111
|
+
bucketPeriod: 'daily',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log('Records:', data.records);
|
|
115
|
+
console.log('Total steps:', data.totalSteps);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## API Reference
|
|
121
|
+
|
|
122
|
+
### Migration Notes (v2.0.0)
|
|
123
|
+
|
|
124
|
+
- `connect()` no longer checks Google Fit installation.
|
|
125
|
+
- `ConnectResult.googleFitInstalled` was removed.
|
|
126
|
+
- `ConnectResult.nextAction` no longer returns `INSTALL_GOOGLE_FIT`.
|
|
127
|
+
- Readiness now depends only on Health Connect:
|
|
128
|
+
`ready = healthConnectInstalled && healthConnectStatus === 'AVAILABLE' && hasPermissions`.
|
|
129
|
+
|
|
130
|
+
### `connect(options?)`
|
|
131
|
+
|
|
132
|
+
Checks Health Connect availability and optionally requests permissions.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
type ConnectOptions = {
|
|
136
|
+
onMissingApp?: 'status' | 'open'; // 'open' navigates to Health Connect in Play Store
|
|
137
|
+
requestPermission?: boolean; // default: false
|
|
138
|
+
permissionScope?: Array<'steps' | 'activetimes' | 'calories' | 'distances'>;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
type ConnectResult = {
|
|
142
|
+
ready: boolean;
|
|
143
|
+
healthConnectInstalled: boolean;
|
|
144
|
+
healthConnectStatus: 'AVAILABLE' | 'PROVIDER_UPDATE_REQUIRED' | 'UNAVAILABLE' | 'UNKNOWN';
|
|
145
|
+
hasPermissions: boolean;
|
|
146
|
+
nextAction:
|
|
147
|
+
| 'INSTALL_HEALTH_CONNECT'
|
|
148
|
+
| 'UPDATE_HEALTH_CONNECT'
|
|
149
|
+
| 'REQUEST_PERMISSION'
|
|
150
|
+
| 'NONE';
|
|
151
|
+
requestedPermissions?: string[];
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Option | Type | Default | Description |
|
|
156
|
+
|--------|------|---------|-------------|
|
|
157
|
+
| `onMissingApp` | `'status' \| 'open'` | `'status'` | `'open'` opens Play Store for Health Connect only |
|
|
158
|
+
| `requestPermission` | `boolean` | `false` | Trigger the Health Connect permission dialog |
|
|
159
|
+
| `permissionScope` | `PermissionScope[]` | all scopes | Limit which permissions are requested |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### `getExerciseData(options)`
|
|
164
|
+
|
|
165
|
+
Reads aggregated exercise data from Health Connect.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
type GetExerciseDataOptions = {
|
|
169
|
+
sinceMillis: number;
|
|
170
|
+
untilMillis: number;
|
|
171
|
+
type?: 'steps' | 'activetimes' | 'calories' | 'distances' | 'all'; // default: 'all'
|
|
172
|
+
bucketPeriod?: 'hourly' | 'daily'; // default: 'hourly'
|
|
173
|
+
debug?: boolean; // default: false
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
type ExerciseData = {
|
|
177
|
+
totalSteps: number;
|
|
178
|
+
totalKcal: number;
|
|
179
|
+
totalDistanceMeters: number;
|
|
180
|
+
totalActiveTimeMillis: number;
|
|
181
|
+
totalActiveTime?: number; // deprecated alias for backward compatibility
|
|
182
|
+
timeZone: string;
|
|
183
|
+
records: UnifiedIntervalRecord[];
|
|
184
|
+
warnings?: string[];
|
|
185
|
+
integrity?: {
|
|
186
|
+
projectionPreserved: boolean;
|
|
187
|
+
roundingScale: number;
|
|
188
|
+
};
|
|
189
|
+
diagnostics?: {
|
|
190
|
+
fallbackUsedSegments: number;
|
|
191
|
+
fallbackSuppressedSegments: number;
|
|
192
|
+
idleGateSuppressedSegments: number;
|
|
193
|
+
basalSeedApplied: boolean;
|
|
194
|
+
seededFromBeforeSince: boolean;
|
|
195
|
+
basalSeedAgeHours?: number;
|
|
196
|
+
};
|
|
197
|
+
debug?: {
|
|
198
|
+
directActiveCaloriesKcal: number;
|
|
199
|
+
totalCaloriesKcal: number;
|
|
200
|
+
basalCaloriesKcal: number;
|
|
201
|
+
mergedActiveCaloriesKcal: number;
|
|
202
|
+
fallbackContributionKcal: number;
|
|
203
|
+
fallbackUsedSegments: number;
|
|
204
|
+
fallbackSuppressedSegments: number;
|
|
205
|
+
idleGateSuppressedSegments: number;
|
|
206
|
+
basalSeedApplied: boolean;
|
|
207
|
+
seededFromBeforeSince: boolean;
|
|
208
|
+
basalSeedAgeHours?: number;
|
|
209
|
+
basalIntervalsCount: number;
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
> Periods longer than **90 days** reject with error code `E_INVALID_PERIOD`.
|
|
215
|
+
> Invalid `permissionScope` values reject with error code `E_INVALID_PERMISSION_SCOPE`.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### `setAllowDatasource(config)`
|
|
220
|
+
|
|
221
|
+
Configures which data sources to exclude from results.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
type DatasourceType = 'manual_input' | 'device' | 'app' | 'unknown';
|
|
225
|
+
|
|
226
|
+
type DatasourceConfig = {
|
|
227
|
+
blacklist: DatasourceType[];
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Example: exclude manually-entered data
|
|
231
|
+
await setAllowDatasource({ blacklist: ['manual_input'] });
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
### `getAllowDatasource()`
|
|
237
|
+
|
|
238
|
+
Returns the current datasource blacklist configuration.
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const config = await getAllowDatasource();
|
|
242
|
+
// => { blacklist: ['manual_input'] }
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Unified Record Schema
|
|
248
|
+
|
|
249
|
+
Every record in `data.records` follows this shape regardless of the `type` queried:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
type UnifiedIntervalRecord = {
|
|
253
|
+
steps: number;
|
|
254
|
+
activeCalories: number;
|
|
255
|
+
activeCaloriesUnit: 'kcal';
|
|
256
|
+
distance: number;
|
|
257
|
+
distanceUnit: 'km';
|
|
258
|
+
activeTime: number;
|
|
259
|
+
activeTimeUnit: 'milliseconds';
|
|
260
|
+
startDate: string; // ISO offset string, e.g. "2026-02-16T08:00:00+07:00"
|
|
261
|
+
endDate: string;
|
|
262
|
+
meta: {
|
|
263
|
+
source: string;
|
|
264
|
+
device: string;
|
|
265
|
+
deviceModel: string;
|
|
266
|
+
trustLevel: 'trusted' | 'low_confidence';
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Example record:
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"steps": 1300,
|
|
276
|
+
"activeCalories": 60.2,
|
|
277
|
+
"activeCaloriesUnit": "kcal",
|
|
278
|
+
"distance": 1.0,
|
|
279
|
+
"distanceUnit": "km",
|
|
280
|
+
"activeTime": 1200000,
|
|
281
|
+
"activeTimeUnit": "milliseconds",
|
|
282
|
+
"startDate": "2026-02-16T08:30:00+07:00",
|
|
283
|
+
"endDate": "2026-02-16T09:00:00+07:00",
|
|
284
|
+
"meta": {
|
|
285
|
+
"source": "device",
|
|
286
|
+
"device": "com.google.android.apps.healthdata",
|
|
287
|
+
"deviceModel": "Pixel Watch 2",
|
|
288
|
+
"trustLevel": "trusted"
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Overlap Handling
|
|
296
|
+
|
|
297
|
+
The SDK automatically de-duplicates overlapping Health Connect records:
|
|
298
|
+
|
|
299
|
+
| Metric | Strategy |
|
|
300
|
+
|--------|----------|
|
|
301
|
+
| `steps` | Source priority: `device > app > unknown > manual_input`, then deterministic single-winner tie-break for same-priority overlaps |
|
|
302
|
+
| `activetimes`, `calories`, `distances` | Proportional-by-time allocation |
|
|
303
|
+
|
|
304
|
+
Bucketized results preserve requested `hourly`/`daily` boundaries and do not merge across bucket edges.
|
|
305
|
+
|
|
306
|
+
### Trust & Fallback Notes
|
|
307
|
+
|
|
308
|
+
- `manual_input` with identifiable device metadata is accepted as `low_confidence`.
|
|
309
|
+
- `manual_input` remains the lowest source priority in overlap resolution.
|
|
310
|
+
- `total - basal` fallback is applied only to uncovered windows (gap-fill only); direct active-calorie windows are preserved.
|
|
311
|
+
- Manual-derived movement evidence is excluded from fallback gate checks.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Integrity & Warnings
|
|
316
|
+
|
|
317
|
+
`data.warnings` is an optional string array that appears when non-fatal anomalies are detected (e.g. value sanitization or boundary guard signals).
|
|
318
|
+
|
|
319
|
+
`data.integrity` is always present and describes the projection audit:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
{
|
|
323
|
+
projectionPreserved: boolean; // true if totals were preserved after overlap correction
|
|
324
|
+
roundingScale: number; // rounding precision applied
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
React Native UI pattern (non-blocking):
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
const hasWarnings = !!data.warnings?.length;
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
<View>
|
|
335
|
+
{hasWarnings && <Text>Data warnings available</Text>}
|
|
336
|
+
<Text>{`Projection preserved: ${data.integrity?.projectionPreserved}`}</Text>
|
|
337
|
+
</View>
|
|
338
|
+
);
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Full Example
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
import { connect, getExerciseData, setAllowDatasource } from '@cariva/exercise-sdk';
|
|
347
|
+
|
|
348
|
+
async function loadHealthData() {
|
|
349
|
+
// Exclude manual entries
|
|
350
|
+
await setAllowDatasource({ blacklist: ['manual_input'] });
|
|
351
|
+
|
|
352
|
+
// Connect with minimal permission scope
|
|
353
|
+
const status = await connect({
|
|
354
|
+
onMissingApp: 'status',
|
|
355
|
+
requestPermission: true,
|
|
356
|
+
permissionScope: ['steps', 'calories'],
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
if (!status.ready) {
|
|
360
|
+
console.warn('Health Connect not ready. Next action:', status.nextAction);
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Query last 24 hours in hourly buckets
|
|
365
|
+
const data = await getExerciseData({
|
|
366
|
+
sinceMillis: Date.now() - 24 * 60 * 60 * 1000,
|
|
367
|
+
untilMillis: Date.now(),
|
|
368
|
+
type: 'all',
|
|
369
|
+
bucketPeriod: 'hourly',
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
return data;
|
|
373
|
+
}
|
|
374
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.CarivaExerciseSdk = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
targetSdkVersion: 36
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
ext.getExtOrDefault = { prop ->
|
|
10
|
+
if (rootProject.ext.has(prop)) {
|
|
11
|
+
return rootProject.ext.get(prop)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return CarivaExerciseSdk[prop]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
repositories {
|
|
18
|
+
google()
|
|
19
|
+
mavenCentral()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
dependencies {
|
|
23
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
24
|
+
// noinspection DifferentKotlinGradleVersion
|
|
25
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
apply plugin: "com.android.library"
|
|
31
|
+
apply plugin: "kotlin-android"
|
|
32
|
+
|
|
33
|
+
apply plugin: "com.facebook.react"
|
|
34
|
+
|
|
35
|
+
android {
|
|
36
|
+
namespace "com.carivaexercisesdk"
|
|
37
|
+
|
|
38
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
39
|
+
|
|
40
|
+
defaultConfig {
|
|
41
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
42
|
+
targetSdkVersion getExtOrDefault("targetSdkVersion")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildTypes {
|
|
46
|
+
release {
|
|
47
|
+
minifyEnabled false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
lint {
|
|
52
|
+
disable "GradleCompatible"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
compileOptions {
|
|
56
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
57
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
dependencies {
|
|
62
|
+
implementation "com.facebook.react:react-android"
|
|
63
|
+
implementation "androidx.health.connect:connect-client:1.1.0-alpha10"
|
|
64
|
+
implementation "androidx.activity:activity-ktx:1.9.3"
|
|
65
|
+
testImplementation "junit:junit:4.13.2"
|
|
66
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
|
|
3
|
+
<uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
|
|
4
|
+
<uses-permission android:name="android.permission.health.READ_BASAL_METABOLIC_RATE" />
|
|
5
|
+
<uses-permission android:name="android.permission.health.READ_STEPS" />
|
|
6
|
+
<uses-permission android:name="android.permission.health.READ_DISTANCE" />
|
|
7
|
+
<uses-permission android:name="android.permission.health.READ_EXERCISE" />
|
|
8
|
+
|
|
9
|
+
<uses-permission android:name="androidx.health.permission.ACTIVE_CALORIES_BURNED_READ" />
|
|
10
|
+
<uses-permission android:name="androidx.health.permission.TOTAL_CALORIES_BURNED_READ" />
|
|
11
|
+
<uses-permission android:name="androidx.health.permission.BASAL_METABOLIC_RATE_READ" />
|
|
12
|
+
<uses-permission android:name="androidx.health.permission.STEPS_READ" />
|
|
13
|
+
<uses-permission android:name="androidx.health.permission.DISTANCE_READ" />
|
|
14
|
+
<uses-permission android:name="androidx.health.permission.EXERCISE_READ" />
|
|
15
|
+
|
|
16
|
+
<queries>
|
|
17
|
+
<package android:name="com.google.android.apps.healthdata" />
|
|
18
|
+
<package android:name="com.google.android.healthconnect.controller" />
|
|
19
|
+
</queries>
|
|
20
|
+
|
|
21
|
+
<application>
|
|
22
|
+
<activity
|
|
23
|
+
android:name=".HealthConnectPermissionsRationaleActivity"
|
|
24
|
+
android:exported="true">
|
|
25
|
+
<intent-filter>
|
|
26
|
+
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
|
|
27
|
+
</intent-filter>
|
|
28
|
+
</activity>
|
|
29
|
+
|
|
30
|
+
<activity
|
|
31
|
+
android:name=".HealthConnectPermissionUsageActivity"
|
|
32
|
+
android:exported="true"
|
|
33
|
+
android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
|
|
34
|
+
<intent-filter>
|
|
35
|
+
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
|
|
36
|
+
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
|
|
37
|
+
</intent-filter>
|
|
38
|
+
</activity>
|
|
39
|
+
</application>
|
|
40
|
+
</manifest>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package com.carivaexercisesdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
|
|
9
|
+
class CarivaExerciseSdkPackage : BaseReactPackage() {
|
|
10
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
11
|
+
return when (name) {
|
|
12
|
+
HealthConnectModule.NAME -> HealthConnectModule(reactContext)
|
|
13
|
+
else -> null
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
|
18
|
+
mapOf(
|
|
19
|
+
HealthConnectModule.NAME to
|
|
20
|
+
ReactModuleInfo(
|
|
21
|
+
name = HealthConnectModule.NAME,
|
|
22
|
+
className = HealthConnectModule.NAME,
|
|
23
|
+
canOverrideExistingModule = false,
|
|
24
|
+
needsEagerInit = false,
|
|
25
|
+
isCxxModule = false,
|
|
26
|
+
isTurboModule = false
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|