utils/sync_settings_ui/README.md
Krzysztof kuhy Rudnicki fff46de4c5 Extract the Flutter sync glue into crdt_sync_flutter, and document it
`crdt_sync` is pure Dart so it can run from CLI tools and headless systemd
jobs, which means it takes closures instead of depending on
flutter_secure_storage and google_sign_in. Every Flutter app then wrote the
same ~390 lines to supply them, and five apps ended up carrying near-identical
copies of firebase_backend.dart.

Those copies drifted. `todo` gained an opt-out flag and a seeded-session path
that home_inventory and wake_alarm never received, so the same bug was fixed
in one copy and left live in the others -- which is the actual cost being paid
here, not the line count.

This package owns that glue once: the keystore adapter, the persisted
per-install device id, the account store and a one-call bootstrap. A new app
needs a SyncApp const and `openSync(kApp)`.

The wrapper routes are a parameter rather than a constant, because todo's copy
hardcoded its own `~/.config/todo/firebase_auth.json`. An extraction only one
app could adopt would be worse than the copies.

Also documents what made this hard to find: crdt_sync_dart's README covered
only the *old* GitHub transport and never mentioned the Firebase surface at
all, and sync_settings_ui -- a complete drop-in settings screen -- had no
README whatsoever.

The five existing apps stay on their copies and on crdt_sync_dart-v0.10.0.
Migration is forward-only and those fallbacks are load-bearing; converging
them is a separate task, and the README says so.

35 tests, 100% line coverage, no suppressions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFSGeWxHPY5rHUGrPqhcmG
2026-08-22 17:26:34 +02:00

81 lines
2.6 KiB
Markdown

# sync_settings_ui
The shared **Sync settings** screen: Firebase sign-in (email/password or
Google one-tap), Disconnect, a live status line, and an optional Backup
section. Reused across every one of kuhy's Flutter apps that syncs, so the
screen behaves identically everywhere and is fixed in one place.
Generalized from the near-identical `_connectFirebase` / `_connectGoogle` /
`_disconnectFirebase` / `_load` logic that had been duplicated into every
app's settings screen.
## Install
```yaml
dependencies:
sync_settings_ui:
git:
url: https://github.com/kuhyx/utils
ref: sync_settings_ui-v0.1.0
path: sync_settings_ui
```
## Usage
This package **never touches `flutter_secure_storage` or `google_sign_in`
itself** — every keystore read/write and every platform sign-in arrives as an
already-built closure. [`crdt_sync_flutter`](../crdt_sync_flutter) provides
all of them, so the two fit together directly:
```dart
import 'package:crdt_sync_flutter/crdt_sync_flutter.dart';
import 'package:sync_settings_ui/sync_settings_ui.dart';
SyncSettingsScreen(
accountLoader: () => loadAccount(),
accountSaver: saveAccount,
accountClearer: clearAccount,
sessionProbe: () => isSyncConfigured(kApp),
firebaseFactory: () => openSync(kApp),
googleFirebaseFactory: () => signInWithGoogle(
kApp,
tokenFetcher: googleIdToken, // the app's own google_sign_in call
),
googleAvailable: googleSignInSupported,
backup: BackupSlot(label: 'notes', export: _export, import: _import),
)
```
Pass `backup: null` to omit the Backup section entirely.
## What it gets right, so you don't re-derive it
**"Connected" means the session probe says so** — never that a factory
returned a non-null client. `FirebaseSyncController` returns a
`FirebaseConnectResult` carrying a six-case outcome:
| Outcome | Meaning |
|---|---|
| `connected` | Signed in, and the probe confirms a live session. |
| `rejected` | The credentials were refused. |
| `cancelled` | The user dismissed the picker. |
| `signedInButNotPersisted` | Sign-in worked; nothing durable was stored. |
| `wrongAccount` | Authenticated as a uid the rules do not allow. |
| `failed` | Anything else. |
`signedInButNotPersisted` and `wrongAccount` exist because both looked
identical to success from the call site and both produced a device that
appeared connected and synced nothing.
**The Google button is double-gated** on `supportsGoogle && googleAvailable`,
so a button that cannot possibly succeed is never rendered — a visible control
that always reports "cancelled" is worse than no control.
## Development
```bash
flutter pub get
flutter analyze
flutter test
```