Add Rippr source snapshot and full-history bundle

Two copies for two jobs. rippr-src/ is a browsable git archive export of
the tracked tree at 2f76983 - no build outputs, no local.properties, no
nested .git - which is convenient to read in gitea but carries no history
and will drift.

rippr-full-history.bundle is the real backup: all 18 commits, verified as
"records a complete history" and test-cloned before committing. This
matters because ~/dojo/rippr has no git remote and otherwise exists only
on one machine.

rippr-src/SNAPSHOT.md explains the difference and how to restore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 08:30:25 -05:00
parent 4debdfa518
commit 280fd7f988
106 changed files with 10377 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
# T09 — Record screen v2
**Phase** 4 · **Depends on** T06, T08 · **Status** Done
## Goal
Add Pause/Resume, Stop, and Discard to the recording screen while keeping the clean
numeric readout intact, and show per-trip rather than lifetime stats. Done when a ride
can be fully controlled from the screen and the numbers reflect the current trip only.
## Context
Direct feedback: *"app is simple and clean. I like that but it's missing stuff."* The
missing stuff is controls, not decoration. **Resist adding visual weight.**
v1's screen shows `MAX SPEED` as a large monospace figure, then a card with points
captured, avg speed, duration, and pending upload, then one full-width button. That
layout stays; it gains controls and loses its lifetime scope.
The current single button toggles start/stop via `RecordingState.isRecording`. v2 has
three states and needs a different control arrangement.
## Design
### Control layout by state
| State | Primary | Secondary |
|---|---|---|
| Idle | **START RECORDING** (full width) | — |
Service actions to dispatch: `TrackingService.ACTION_START` / `ACTION_PAUSE` /
`ACTION_RESUME` / `ACTION_STOP` / `ACTION_DISCARD`. Only START uses
`startForegroundService`; the rest are plain `startService` on the running service.
| Recording | **PAUSE** | STOP |
| Paused | **RESUME** | STOP · DISCARD |
Discard appears **only when paused** — deliberately. Offering a destructive action next
to Pause during an active ride invites a gloved mis-tap at 100 km/h. Pausing first is a
natural speed bump.
Discard shows a confirmation dialog stating what is lost ("Delete this ride? N points
recorded over X km will be permanently deleted."). Stop and Pause need no confirmation.
### Replacing T02 scaffolding
`MainActivity` derives stats with `flatMapLatest` over `TripRepository.observeActiveTrip()`
into `observeTripStats(tripId)`, and `isRecording` from `observeActiveTrip().map { it != null }`.
Upload errors come from the `UploadStatus` object. All of that moves into `RecordViewModel`,
and once T07 lands the screen reads persisted aggregates off the `Trip` row rather than
recomputing in SQL.
`onToggleClicked` is now a `lifecycleScope.launch` that queries `trips.activeTrip()` — there
is no synchronous recording flag any more. The ViewModel should expose state instead so the
screen never has to suspend to decide what a button does.
### Stats become per-trip
Driven by `observeActiveTrip()` from T03, showing the live aggregates from T07:
- Max speed (the big figure — it is the number Dylan cares about)
- Distance — **new in v2**, read straight off `Trip.distanceM` (no SQL recomputation)
- Duration, moving time
- Points captured
- Pending upload (only when non-zero, and only if an endpoint is configured)
When idle, the screen shows a resting state rather than stale numbers from the last ride.
## Implementation
1. `RecordViewModel` exposes `RecordUiState` derived from `observeActiveTrip()`.
2. Map `TripState` to the control layout above.
3. Dispatch service intents for each action.
4. Add `ConfirmDialog` for Discard.
5. Reuse `Telemetry.formatDuration` for both duration fields.
6. Keep the battery-optimisation prompt.
7. Add a top-bar action to reach the Trips list.
## Acceptance criteria
- [x] All five transitions work from the UI
- [x] Discard is unavailable while actively recording
- [x] Discard confirms, and states what will be lost
- [ ] Stats reset between trips — no bleed from the previous ride
- [x] Distance appears and increases during a ride
- [x] Idle state is visibly distinct from a zeroed ride
- [ ] Screen still reads at a glance; no added clutter
> **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
Compose UI tests:
- Each state renders the right controls
- Discard dialog appears, cancel is non-destructive
- State survives rotation mid-ride
Emulator end-to-end: drive the full lifecycle through taps, then verify the database
matches what the screen claimed.
## Risks / gotchas
- **Tap targets.** These get used in gloves. Keep the 64–72dp button height v1 uses.
- **Wait for the UI before tapping in tests.** v1's cold start took 8.7 s; a fixed sleep
produced a silently-missed tap and a false "service didn't start" conclusion. Poll
`uiautomator dump` for the button text instead.
- **Do not show "Pending upload" when no endpoint is configured** — it reads as an error
when it is just an unused feature.
## Out of scope
Trips list (T10), any map, live path preview.