Linear: https://linear.app/engineereddev/issue/ENG-253
What's in this PR (all under apps/android/):
- Theme:
BrandColors.ktmapped to Material 3 light/dark (dynamic colour off) plusDESIGN.mdtypography; brand launcher and notification icons. - Navigation (
ui/PneumoNavHost.kt): Pair → Confirm → Onboarding → main shell with bottom nav (Home, Devices, Settings). The start destination comes from SecureStore and DataStore state. - Pair: CameraX + ZXing core scanner (no Play Services), camera rationale and denied state, and "Paste link instead".
- Deep link
pneumo://pair→ Confirm. Confirm shows the host (warns on non-HTTPS outside debug), claims, checks keyCheck againstk, and saves MK + token. Mismatch follows §7.5 (delete own device, wipe); 410 → "This code has expired…". - Onboarding: notifications (API 33+) and the battery-optimization exemption, both skippable with a warning, shown once.
- Placeholder Home/Devices/Settings; Settings Sign out (
DELETEown device, wipe SecureStore, back to Pair). AppContainermanual DI;PairingRepository,SessionPreferences,PairingViewModel.- A debug-only network security config allows cleartext to
10.0.2.2/localhost; release keepsusesCleartextTraffic="false". - 36 new Roborazzi baselines, recorded on purpose (light + dark): Pair (idle, camera denied, paste, invalid), Confirm (https, non-https, claiming, retryable error, error), Onboarding (notifications, battery, done, warning), Shell (home, devices, settings, settings busy, sign-out error). The obsolete scaffold screen and its goldens were removed.
Verify (lead, rebased on 555a737):
mise run check:android→ exit 0 (Spotless, detekt, Lint "no errors or warnings", JVM tests, Roborazzi verify)mise run e2e:android→ exit 0; forced rerunpixel6Api34DebugAndroidTest --rerun→ BUILD SUCCESSFUL, "Running tests on devices: pixel6Api34"testDebugUnitTest --rerun→ 108 tests, 0 failures- Implementer's end-to-end check against a local server: deep-link pairing reached Home ("Paired with 10.0.2.2:8080"); a relaunch kept the session; sign-out removed the device on the server.
Deviations:
- A narrowly scoped
@SuppressLint("BatteryLife")on the spec-required battery-exemption helper. Lint flags the Play-policy API; Pneumo is sideloaded with no FCM. No project-wide disable or baseline entry. - The managed (ATD) image has no Settings/SystemUI, so the battery request exercised the "settings unavailable" warning path there. The real dialog and physical-camera scanning need a regular emulator or phone (M5 acceptance).
🤖 Generated with Claude Code