Skip to content

feat(journey-planner): alight-alert bell on each result row - #221

Merged
itsjfx merged 1 commit into
masterfrom
feat/journey-row-alight-bell
Jul 14, 2026
Merged

itsjfx merged 1 commit into
masterfrom
feat/journey-row-alight-bell

Conversation

@ai-tiro

@ai-tiro ai-tiro commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Closes #220

What

Adds the alight-alert bell (🔔) to every journey option row on the journey planner results list. Tapping it follows that run and arms the "I'm getting off here" alert (#201) at the journey's destination stop — the one-tap version of opening the run pattern and belling the destination stop. Tapping the armed bell stops tracking. The armed state is derived reactively from FollowedTripRepository, so it stays consistent with arming/disarming done on the run-pattern screen or from the ongoing notification.

How / why it's built this way

  • No pattern fetch at tap time. A FollowedTrip normally wants the run's terminus arrival for completesAtUtc, which only the pattern knows — but most journey options are derived from a departures join without ever fetching a pattern. Instead the trip is built from planner-local data (runRef/route/direction from the JourneyOption, alert stop id/name/coordinates from the destination Stop) and completesAtUtc is seeded with the arrival at the destination. That's safe because AlightAlertService starts the moment an alert is stored and refreshes completesAtUtc from the pattern terminus on its first poll (and every poll after), and the 5-minute completion grace covers the gap. Arming is instant and costs zero network.
  • Replace-confirmation mirrors run-pattern's Follow this trip: pinned "return to your trip" bar with live run pattern #200 rule: arming while a different run is followed raises the same dialog; nothing is written until confirmed.
  • Permission plumbing in JourneyPlannerRoute is the RunPatternRoute recipe verbatim: contextual POST_NOTIFICATIONS request on arm (snackbar on denial), contextual location request when the armed run is schedule-only.
  • Schedule-only detection without a pattern: run-pattern checks the fetched pattern for estimates; the planner has no pattern, so it prompts on routeType == Tram (trams never carry real-time on the pattern endpoint the service polls, even though their departures feeds do — CLAUDE.md quirk) or when the option has no estimate at either end.

Decisions worth reviewing

  • Armed bell tap = unfollow, not just disarm. Run-pattern's bell disarms but keeps the follow, because Follow is its own top-bar action there. On the planner the bell is the whole "track this journey" affordance, so toggling it off returns to the pre-tap state entirely. Edge case: if you followed a run from the pattern screen with an alert at this destination, the planner bell renders armed and tapping it unfollows — I think that's what "stop tracking" means from this surface, but flagging it.
  • completesAtUtc seeding (above) is a deliberate, self-correcting approximation. If every pattern poll failed for the whole trip, the follow would auto-clear 5 min after the destination arrival instead of the terminus — by which point the user has alighted anyway.

Testing

  • 10 new ViewModel tests (arm builds the right trip, upsert preserves followedAtUtc and resets alert latches, disarm/no-op paths, replace confirm/dismiss, tram + schedule-only location prompt, armed-state derivation incl. a trip armed from the run-pattern screen). 41/41 green, detekt/lint clean.
  • Emulator run with screenshots to follow in a comment.

Notes

  • Two detekt suppressions: TooManyFunctions on the ViewModel (public methods are one-per-screen-event by convention) and LargeClass on the test (same trade as NearbyViewModelTest).
  • No one-way doors: no schema/DataStore changes; FollowedTrip shape untouched.

🤖 Generated with Claude Code

Each journey option row gets the run-pattern bell (issue #201): one tap
follows the run and arms the "I'm getting off here" alert at the
journey's destination stop, skipping the tap-through to the pattern
timeline. The trip is built from planner data alone — completesAtUtc is
seeded with the destination arrival and corrected to the terminus by
AlightAlertService's first poll — so arming costs no network fetch.
Replace-confirmation, contextual notification/location prompts, and the
armed-state derivation all mirror the run-pattern screen.

Closes #220

Co-Authored-By: ai-tiro <ai-tiro@jfx.ac>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Debug APK: app-debug-1f6e0928355da8481b3f0d9c6d768265d5a85c25.apk (built from 1f6e092 at 2026-07-13 11:09 UTC)

Requires GitHub login. Artifact expires after 3 days.

@ai-tiro

ai-tiro commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator Author

Results list — bell on every row (unarmed)

Contextual POST_NOTIFICATIONS prompt on first arm

Replace confirmation when a different run is followed

Armed row (full-strength bell) + pinned Return-to-trip bar

Ongoing tracking notification — 8 stops to Flinders Street

Run-pattern screen agrees: Getting off here at Flinders Street

Uploaded via pr-attach.

@ai-tiro
ai-tiro marked this pull request as ready for review July 13, 2026 11:14
@itsjfx
itsjfx merged commit 9aff7d9 into master Jul 14, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Journey planner: alight-alert bell on each journey result row

2 participants