Skip to content

refactor(nav): split AppRoute by how a route arrives - #1418

Merged
bmc08gt merged 2 commits into
feat/transaction-detailsfrom
refactor/app-route-presentation
Sep 5, 2026
Merged

refactor(nav): split AppRoute by how a route arrives#1418
bmc08gt merged 2 commits into
feat/transaction-detailsfrom
refactor/app-route-presentation

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #1417 — review that first.

AppRoute.Sheets held three unrelated populations: the four nav-bar tab homes, screens that are only ever pushed (ActivityHistory, TransactionDetails, Give), and actual modals. resolveRoutes wrapped every member in Main.Sheet, so the tab homes needed an asNavBarTab() carve-out to escape it and the pushed screens had no escape at all — a deeplink naming one landed in a modal instead of on the screen the rest of the app pushes.

Three groupings now say how a route arrives:

  • Tabs — the four nav-bar homes (Scanner, Wallet, Tips, Menu). navBarButton() is exhaustive over the type, so a new tab home fails to compile until it is given a button rather than silently behaving as an ordinary push.
  • Sheets — modals that host an inner backstack (TokenSelection, TipAmountEntry, ShareApp). That Main.Sheet packing is the only thing membership buys, so a modal needing no nested navigation implements com.getcode.navigation.Sheet directly instead — that marker, not this grouping, is what ModalBottomSheetSceneStrategy reads to present a route as a sheet.
  • Main — ordinary pushes onto whatever stack the user is on.

resolveRoutes drops the carve-out and wraps on membership alone; leadsWithTab becomes routes.firstOrNull() is AppRoute.Tabs.

Behaviour

Unchanged in-app. Every entry to the three moved screens already goes through navigator.push (WalletScreen.kt:53, TokenInfoScreen.kt:195, CurrencyInfoExpansion.kt:123, ActivityHistoryScreen.kt:69), and AppRouter only ever feeds tab homes into navigateAll. The one change is the fix: a deeplink naming one of the three now lands on the pushed screen.

Route class names change, so a back stack saved by an older build will not restore across the update — inherent to any route rename. Maestro tags derive from simpleName, all of which are preserved, so the flows are untouched.

Three scanner destinations the new type made visible

Second commit. Once the tab homes are their own type, code that treats one as an ordinary push reads as a mistake rather than as a convention.

  • The wallet's new-user tutorial dispatched "scan a tip card" as OpenScreen, which WalletScreen handles with navigator.push — the scanner arrived stacked on the wallet, and Back returned to the wallet instead of behaving like the nav bar. It now dispatches a SwitchTab event handled with replaceAll, the same call AppNavigationBar makes.
  • PurchaseAccountScreen released to the scanner on account creation rather than to homeRoute, which every other onboarding exit uses.
  • NotificationPermissionScreen.kt was a second copy of the permission screens left behind by the FlowHost migration, navigating to the scanner from both of its composables. Nothing calls them and AppRoute.Onboarding.NotificationPermission has no annotatedEntry to reach them by; the live path is OnboardingFlowScreen's NotificationPermissionStepContent, which releases to homeRoute. Deleted rather than repointed.

AppRoute.Sheets held three unrelated populations: the four nav-bar tab homes,
screens that are only ever pushed (ActivityHistory, TransactionDetails, Give),
and actual modals. resolveRoutes wrapped every member in Main.Sheet, so the tab
homes needed an asNavBarTab() carve-out to escape and the pushed screens had no
escape at all — a deeplink naming one landed in a modal instead of on the screen
the rest of the app pushes.

Three groupings now say how a route arrives:

- Tabs — the four nav-bar homes. navBarButton() is exhaustive over the type, so
  a new tab home fails to compile until it is given a button.
- Sheets — modals that host an inner backstack, which is the only thing the
  Main.Sheet wrapping buys. A modal needing no nested navigation implements
  com.getcode.navigation.Sheet directly; that marker, not this grouping, is what
  ModalBottomSheetSceneStrategy reads.
- Main — ordinary pushes.

resolveRoutes loses the carve-out and wraps on membership alone. In-app
behaviour is unchanged: every entry to the three moved screens already went
through navigator.push, and AppRouter only feeds tab homes into navigateAll.
A deeplink to one of them now lands on the pushed screen.

Route class names change, so a back stack saved by an older build will not
restore across the update. Maestro tags derive from simpleName, all of which
are preserved.
@bmc08gt bmc08gt self-assigned this Sep 4, 2026
@github-actions github-actions Bot added type: refactor Code restructuring, no behavior change area: payments Payments, transfers, intents, billing area: onramp Deposit, purchase, Coinbase, fiat on-ramp area: scanner QR/Kikcode scanning, camera area: tokens Token accounts, balances, token info area: deeplinks Deep link handling, URL routing, and link parsing area: onboarding labels Sep 4, 2026
Three scanner destinations the new AppRoute.Tabs type makes visible as errors:

The wallet's new-user tutorial dispatched "scan a tip card" as OpenScreen, which
WalletScreen handles with navigator.push — so the scanner arrived stacked on the
wallet, and Back returned to the wallet instead of behaving like the nav bar. It
now dispatches SwitchTab, handled with replaceAll, the same call AppNavigationBar
makes.

PurchaseAccountScreen released to the scanner on account creation rather than to
homeRoute, which every other onboarding exit uses.

NotificationPermissionScreen and NotificationPermissionRationaleScreen were a
second copy of the permission screens left behind by the FlowHost migration:
nothing calls them, and AppRoute.Onboarding.NotificationPermission has no
annotatedEntry to reach them by. The live path is OnboardingFlowScreen's
NotificationPermissionStepContent, which releases to homeRoute. Deleted rather
than repointed — the file's only effect was to make the wrong destination look
supported.
@bmc08gt
bmc08gt merged commit dacddef into feat/transaction-details Sep 5, 2026
3 checks passed
@bmc08gt
bmc08gt deleted the refactor/app-route-presentation branch September 5, 2026 19:12
bmc08gt added a commit to code-payments/code-ios-app that referenced this pull request Sep 8, 2026
* feat(activity): open a tapped activity row's details

An activity row was a label everywhere it was drawn, so the only way to
read an entry's rate, token quantity or id was not to have one, and the
only way to cancel a pending cash link was to find it again in the
per-token history. Android closed this in
code-payments/code-android-app#1417 and
code-payments/code-android-app#1418; this is the same screen on iOS.

`TransactionDetails` maps an `Activity` to what the screen shows: which
of the 13 kinds it was, its heading and subtitle, whether it can be
cancelled, and whether it has a conversation to open. The three cash
kinds stay distinct because `MessageMetadata` already distinguishes
them, and there is no received-cash-link case because a claimed link
arrives as a plain receive. `TransactionDetailsTests` mirrors Android's
`TransactionDetailsMapperTest` case for case.

`ActivityRow` now pushes `transactionDetails` itself rather than each of
its three call sites wiring a tap, so a row opens the same screen in the
wallet's Recent section, the currency info screen, the cross-token
history and a token's own history. The avatar the row draws moves to
`ActivityAvatar`, which the details header draws at 80pt from the same
`ActivityResolution`, so the screen opens on exactly the avatar that was
tapped. `TransactionHistoryScreen` drops its own cancel dialog; cancel
now lives in the details screen's toolbar, as it does on Android.

Layout follows Figma node 9708:105260, and the kind headings node
9708:118186, including the four places Android's implementation is still
waiting on design: amount type size, line-item value size, gutter inset,
and the header avatar's ring.

* fix(activity): push the chat from transaction details instead of switching tabs

"View in Chat" called `navigate(to:)`, which reaches the Chat tab's stack by
bringing the tab forward. That swaps the tab out from under the transition: the
tab bar and the conversation list are both visible for a beat before the chat
pushes in over them.

Push it onto the stack the details screen is already on. The chat slides in from
the entry it belongs to, back returns to the entry, and nothing changes
underneath. `ConversationScreen` names no stack of its own, and the pushes it
makes — a profile, a currency — resolve against whichever stack is topmost, so
it carries over unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: deeplinks Deep link handling, URL routing, and link parsing area: onboarding area: onramp Deposit, purchase, Coinbase, fiat on-ramp area: payments Payments, transfers, intents, billing area: scanner QR/Kikcode scanning, camera area: tokens Token accounts, balances, token info type: refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant