Files
samplez/rippr-src/docs/v2/09-record-screen.md
uhryniuk 280fd7f988 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>
2026-08-11 08:30:25 -05:00

4.6 KiB
Raw Blame History

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

  • All five transitions work from the UI
  • Discard is unavailable while actively recording
  • Discard confirms, and states what will be lost
  • Stats reset between trips — no bleed from the previous ride
  • Distance appears and increases during a ride
  • 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.