# HC Sync Android SDK

## 0.2.0
- `HcSync.state: StateFlow<SyncState>`: `Idle`, `Syncing(progress)`, `Synced(at, uploaded, rejected, rebackfilled)`, `Error(kind, message)`.
- Error kinds: `Unavailable(availability)` (Health Connect missing or outdated, carries an install/update Intent), `PermissionDenied(missing)`, `BackgroundNotAllowed`, `Network`, `Auth` (401), `PlanLimit` (402), `Server(status)`, `NotConfigured`.
- `uploaded`/`rejected` come from the server's `stored`/`rejected`, not the number submitted.
- `syncNow(context)` now returns the final `SyncState` instead of an `Int` and does not throw for expected failures (breaking change from 0.1).
- The WorkManager worker returns `failure` (not `success`) for permission, availability, auth and plan errors; it retries only network, 5xx/429.
- `HcSync.availability(context)`: `Available`, `NotInstalled(intent)`, `UpdateRequired(intent)`, `Unsupported`.
- `HcSync.syncOnAppStart(application, minIntervalMinutes = 15)`: foreground sync via `ProcessLifecycleOwner`, debounced.
- `HcSync.logout(context)`: waits for a running sync, cancels background work, clears the user token and change tokens.
- `HcSync.missingPermissions(context)`.
- Expired change token: re-reads the last `backfillDays` (max 30) and reports `rebackfilled = true`. Deletions made while the token was expired cannot be detected; those records stay on the server until deleted there.
- A batch the API rejects entirely (400) is counted as rejected and skipped so the change token can advance.
- New `android/sample` app (plain Views). `./gradlew :sample:assembleDebug`.

## 0.2.1
- Fix: `configure()` for another user while a sync runs can no longer receive the previous user's change tokens.
- Fix: cancelling a sync (stop, logout, worker cancel) resets `state` to `Idle` instead of leaving `Syncing`.
- Fix: a 400 on the delete call is reported via `onRejected` and the change token advances (no permanent replay).
- Fix: `syncOnAppStart` registers on the main thread, only once, and debounces after a successful sync only.
- Fix: `missingPermissions` returns an empty set when Health Connect is unavailable; devices below API 28 report `Unsupported`.
- Change tokens are now per type. Revoked types lose their token, so no stale token returns after re-granting.
- A `SecurityException` reports `BackgroundNotAllowed` only when the background permission is the missing one.
- Backup: the user token and change tokens are stored in `hcsync.xml` (SharedPreferences). Exclude it from Auto Backup in both
  `res/xml/data_extraction_rules.xml` and the `fullBackupContent` file: `<exclude domain="sharedpref" path="hcsync.xml"/>`.
  If restored anyway, change tokens are dropped on the first sync (a random install id in `noBackupFilesDir` differs) and the backfill window is re-read.

## 0.3.0
- Drop-in Connect screen: `HcSync.connectContract()` is an `ActivityResultContract<ConnectOptions, ConnectResult>` that opens a built-in Activity (plain Views, no Compose).
  Flow: Health Connect install/update (or an "unsupported device" message) -> what data and why -> permission request (optional background access, explained) -> first sync with live progress -> summary with actionable errors and a retry button.
- `ConnectOptions(appName, userToken, types, labels, privacyUrl, accentColor, requestBackground, backfillDays)`.
- `ConnectResult`: `Connected(uploaded)`, `Cancelled`, `Failed(kind)`. For `Failed(Unavailable)` the Intent is not carried across; call `HcSync.availability(context)`.
- Theming: `accentColor`, or override `hcsync_bg`, `hcsync_text`, `hcsync_text_secondary`, `hcsync_accent` in `res/values` and `res/values-night`. Dark mode follows the system.
- Accessibility: headings and live regions for TalkBack, 48dp targets, text in sp inside a scroll view. Rotation keeps the step and the running sync.
- Text is English only for now.
- The sample app's Connect button uses it.

```kotlin
val connect = registerForActivityResult(HcSync.connectContract()) { r ->
    if (r is ConnectResult.Connected) HcSync.start(this)
}
connect.launch(ConnectOptions(appName = "MyApp", userToken = token, privacyUrl = "https://example.com/privacy"))
```
