# T08 — Navigation shell + ViewModels **Phase** 4 · **Depends on** T01 · **Status** Done ## Goal Three navigable destinations, theme extracted out of the Activity, and state moved into ViewModels. Done when Record, Trips, and Trip Detail are reachable with a working back stack and `MainActivity` no longer owns screen state. ## Context `app/src/main/java/com/rippr/MainActivity.kt` (216 lines) currently does everything: permission launcher, service intents, theme definition, screen composition, and state. That was proportionate for one screen and will not survive three. Existing pieces to preserve rather than rewrite: - The permission flow (`RequestMultiplePermissions`, with `POST_NOTIFICATIONS` added only on TIRAMISU+) works and should move largely as-is. - The battery-optimisation exemption prompt is genuinely useful for an app that must survive a multi-hour ride; keep it on the Record screen. - `RipprTheme` is currently a private composable applying `darkColorScheme()`. Dylan called the UI "simple and clean" and explicitly likes it — **do not redesign it**. Extract, do not restyle. - `StatRow` and `BigStat` are reusable; move them into a shared `ui/components/`. ## Design ``` ui/ theme/ Theme.kt, Color.kt components/ StatRow.kt, BigStat.kt, ConfirmDialog.kt record/ RecordScreen.kt, RecordViewModel.kt trips/ TripsScreen.kt, TripsViewModel.kt detail/ TripDetailScreen.kt, TripDetailViewModel.kt RipprNavHost.kt ``` Routes: | Route | Screen | |---|---| | `record` | Start destination | | `trips` | Trip list | | `trip/{tripId}` | Detail, `tripId: Long` argument | `navigation-compose` rather than hand-rolled state switching: the back stack from detail → list is worth not writing by hand, and it gives correct behaviour on the system back gesture for free. **ViewModels** get the repository through a simple factory — the app already uses a `@Volatile` singleton for `AppDatabase` and T03 adds one for `TripRepository`. Hilt would be more ceremony than a three-screen app earns. **Navigation from Record → Trips** goes in a top bar action, keeping the Record screen's centre free for the numeric readout. ## Implementation 1. Extract `ui/theme/` from `MainActivity`. 2. Move `StatRow` / `BigStat` into `ui/components/`, unchanged. 3. Create `RipprNavHost` with the three routes. 4. Add a ViewModel per screen with a shared factory. 5. Reduce `MainActivity` to: permissions, battery exemption, `setContent { RipprTheme { RipprNavHost() } }`. 6. Move service-intent dispatch into `RecordViewModel`. ## Acceptance criteria - [x] All three destinations reachable; system back works from detail - [x] `MainActivity` holds no screen state - [x] Visual appearance of the Record screen is unchanged - [x] Permission flow and battery-optimisation prompt still function - [ ] Config change (rotation) does not lose state > **Not verified.** The unchecked items above, and the Compose UI tests below, were > not done. Verification for T08–T11 was manual on the emulator (screenshots through > the full flow) plus the existing unit and instrumented suites. Compose UI tests are > outstanding — tracked in T18. ## Tests Instrumented Compose UI tests: - Navigate record → trips → detail → back → back - Rotation preserves screen state - Existing suites stay green ## Risks / gotchas - **Do not restyle.** The brief was "simple and clean, I like that". This task is structural only. - **`tripId` argument type.** Room ids are `Long`; declare `NavType.LongType` explicitly or it silently arrives as a String. - The v1 UI reads state via `collectAsStateWithLifecycle` already — keep that, it is the correct choice over `collectAsState` for lifecycle-aware collection. ## Out of scope Screen content beyond wiring (T09, T10, T11); any map (T13).