Skip to content

feat(wallet): Apple-Wallet card expand + v2 "You" tab - #1252

Merged
bmc08gt merged 8 commits into
code/cashfrom
feat/wallet-card-expand
Aug 19, 2026
Merged

feat(wallet): Apple-Wallet card expand + v2 "You" tab#1252
bmc08gt merged 8 commits into
code/cashfrom
feat/wallet-card-expand

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Ports the iOS #587 Apple-Wallet card-expand to Android, makes the menu the v2 "You" tab, and fixes a handful of v1/v2 issues found while testing.

Card expand

Tapping a wallet card flies it to the currency-info hero while the deck reorganises behind it, driven by a single progress scalar (:apps:flipcash:card-expand).

The deck reorg mirrors iOS TokenCardStack: every non-hero card interpolates linearly from its own current top to a cleared spot and fades — cards above converge onto the opened card's slot, cards below run off the bottom. Reading each card's own bounds makes it correct at any scroll offset, which fixes hidden cards revealing themselves mid-transition when the deck was scrolled.

The expanded detail is hosted inside the wallet nav entry (matching iOS's WalletScreen structure) so a pushed action screen covers it with the right z-order. The controller stays app-root so its fly state survives the entry being torn down on push. The hero's fly target is seeded from — and falls back to — the controller's surviving heroBounds; without that the card came back invisible after popping from a pushed action, since the target was a composition-local remember that reset to null.

Currency-info action tiles are wired up: Give and Withdraw push real screens, Convert opens the buy flow. Give pushes rather than presenting a sheet on v2.

Also: the flying hero ramps to full opacity by HeroPhase (opaque before the detail background covers its deck backing, but still crossfading on close so the hand-off to the natural-z deck card dissolves instead of snapping); token discovery opens token-info as an ordinary push with a back arrow; Convert uses the Figma IconArrowBottomTop glyph.

v2 "You" tab

The menu is now the v2 "You" tab — the viewer's tip card plus "Share as a Link" above the settings list, with the version footer scrolling in the footer slot. v1 keeps the Settings sheet (title, close, Add Money / Withdraw tiles, pinned footer). Tip-code preview rendering moves to :shared:bills so the menu can warm the share preview without depending on the tipping feature. The v2 TipCard step becomes just the post-profile-setup landing.

The list reserves the hoisted tab bar's height as bottom content padding so it scrolls clear of the bar, and enabling New UI now re-homes onto the target shell's landing screen instead of leaving the app on the v1 settings sheet (which v2 rendered as a floating sheet).

Drops the GiveUsdf flag — USDF is giveable via the v2 currency-info tiles.

Bug fixes

NewUi leaked into v1. observe() returns a StateFlow seeded with the flag's default (NewUi defaults to true) until DataStore emits the stored value, so remember { observe(flag).value } captured that default on first composition and froze it — a v1 build rendered the v2 "You" screen with no close button and no money tiles. The app root already collected it properly, which is why only these screens disagreed with the rest of the app. Fixed on the menu, tip card, tips and both token-info screens, which all shared the pattern.

Amount-entry title off-centre. The give/chat amount-entry screens pass a fillMaxWidth() pill as the app bar title but left titleAlignment at its Start default. That only looked centred by accident — the phantom leading slot used to be measured unconditionally, so a Start title got pushed right by roughly a back button's width. Now that reserving it is conditional on the title actually being centred, the pill snapped flush to the inset and sat 64px left of centre. Declaring the centring puts it back at exactly centre with or without a back arrow.

Testing

Unit suite green. Card expand, give flow, the v1/v2 menu split, the app-bar centring and both UI-switch directions were verified on an emulator; the centring and tab-bar inset were checked against measured view bounds rather than by eye.

Ports the iOS currency-info revamp (code-ios-app#585) to Android's new UI (gated on
FeatureFlag.NewUi; legacy screen untouched). Hero card, action tiles (Give/Convert/Withdraw,
single Get when not held, USDF = Convert/Withdraw), per-token Recent, Market Cap + chart,
About, created footer, and a scroll-revealed title pill.

The app bar is an overlay (chat-style): content fills behind it as the haze source, inset by
the measured bar height, with a bg->transparent scrim; back/pill/share are frosted liquid glass
(via the optional hazeState on the app-bar chrome). Adds a bounded per-token recent-activity
query (MessageDao/MessageDataSource/ActivityFeedCoordinator) surfaced as
TokenInfoViewModel.State.transactions. All sizing via grid/type/shape/color tokens.
Replaces the onSizeChanged/measured overlay with a SubcomposeLayout (OverlayTopBarScaffold)
that measures the app bar BEFORE the content in the same layout pass, so the hero card gets the
correct top inset on the very first frame instead of settling on frame 2. Mirrors the chat
screen's ChatInputScaffold (top-bar only). The scrim height is kept off the content inset so it
can't shift the content on a later frame.
…pp bar

The scrim spanned the full app-bar height, washing content out across the whole bar. Scope it to
the status-bar strip plus half the app-bar row, so the hero card dims as it scrolls up behind the
frosted chrome (matching iOS) and stays vibrant below the bar's midline. The app bar is measured on
its own (status bar excluded) so scrim = statusBar + appBar/2. The scrim never grows the content
inset (the scaffold measures the full bar), so there's no jump.
…h over it

Port the iOS #587 card-expand to Android: tapping a wallet card flies it to the
currency-info hero while the deck reorganises behind it, driven by one progress
scalar (:apps:flipcash:card-expand).

Deck reorg mirrors iOS TokenCardStack: every non-hero card interpolates linearly
from its own current top to a cleared spot and fades out — cards above converge
onto the opened card's slot, cards below run off the bottom. Reading each card's
own bounds makes it correct at any scroll offset, which fixes hidden cards
revealing themselves mid-transition when the deck was scrolled/pinned.

Host the expanded detail INSIDE the wallet nav entry (CardExpandHost), matching
iOS's WalletScreen structure, so a pushed action screen covers it with the right
z-order. The controller stays app-root so its fly-state survives the entry's
composition being torn down on push; the hero's fly target is seeded from (and
falls back to) the controller's surviving heroBounds, otherwise the card came
back invisible after popping from a pushed action.

Wire the currency-info action tiles: Give and Withdraw push real screens, Convert
opens the buy flow. Give pushes rather than presenting a sheet on v2.

Also:
- flying hero ramps to full opacity by HeroPhase — opaque before the detail
  background covers its deck backing (no open dim), while still crossfading on
  close so the hand-off to the natural-z deck card dissolves instead of snapping
- token discovery opens token-info as an ordinary push (asPush) with a back arrow
- Convert tile uses the Figma IconArrowBottomTop glyph
The give/chat amount-entry screens pass a fillMaxWidth() token pill as the app
bar title but left titleAlignment at its Start default. That used to look
centred only by accident: the phantom leading slot was measured unconditionally,
so leftIconWidth was never 0 and a Start title got pushed right by roughly a back
button's width.

Reserving that phantom width is now conditional on the title actually being
centred, so with no leading control leftIconWidth is 0 and the title snaps flush
to the inset — on the give screen the pill sat 64px left of centre (content
centred at 476 against a 540 screen centre).

Declare the centring instead of leaning on the leading slot's width. The pill now
centres at exactly 540 with or without a back arrow.
The v2 "You" tab is now the menu surface: the viewer's own tip card (tappable to
present full screen) plus "Share as a Link" above the settings list, with the
version footer scrolling in the footer slot. v1 keeps the Settings sheet — title,
close, Add Money / Withdraw tiles and a pinned footer.

Collect the NewUi flag reactively instead of snapshotting it. observe() is a
StateFlow seeded with the flag's DEFAULT (NewUi defaults to true) until DataStore
emits the stored value, so `remember { observe(flag).value }` captured that
default on first composition and froze it — a v1 build rendered the v2 "You"
screen (no close, no money tiles) because the read never saw the stored false.
The app root already collected it properly, which is why only these screens
disagreed with the rest of the app. Same fix applied to the tip card, tips and
token-info screens, which shared the pattern.

The tip card is now owned by the You tab, so the v2 TipCard step is just the
post-profile-setup landing (full-bleed, no settings hamburger) and the "You" tab
routes to the menu. Tip-code preview rendering moves to :shared:bills so the menu
can warm the share preview without depending on the tipping feature.

Also drops the GiveUsdf flag — USDF is giveable via the v2 currency-info tiles,
and the legacy reserve layout offers Withdraw + Deposit only.
…bling v2

The v2 tab bar is a hoisted overlay drawn above the nav content, so the You tab's
list needs its height as bottom content padding — otherwise the settings rows and
version footer scroll under the bar with no way to clear it. Reserve it via
LocalTabBarPadding, which the tab-bar inset decorator only makes non-zero for tab
homes. No navigationBarsPadding on the v2 footer: the bar measures itself with
that padding already in, so applying it again would double-count. Give the footer
its own top spacing too — in the footer slot it scrolls with the list and was
sitting flush against the last row's divider.

Enabling New UI also used to leave the app on whatever the v1 stack was — the
settings sheet the toggle lives in — so v2 rendered the menu as a floating sheet
instead of its "You" tab. Re-home onto the target shell's landing screen (You tab
for v2, scanner for v1) when the flag is toggled.

Order matters: reset the stack BEFORE flipping the flag. Resetting after races
the shell swap, and an animated sheet dismiss can't survive it at all — the host
driving the dismiss is disposed mid-animation, stranding the sheet on screen.
Resetting first means the new shell composes against a stack that already makes
sense in it.

The reset also has to target the real app-root navigator. Inside a sheet,
LocalCodeNavigator is the sheet's own navigator, and that one is built without a
parent — so rootNavigator resolves back to itself and the reset only cleared the
sheet's inner stack (dropping Lab back to Menu, still in the sheet). The sheet
scene publishes its host as LocalSheetNavigator; use that when present.
…ng a bill

v2 reaches the give screen as a push from currency-info, not as a sheet, so
hide() — which only pops when a Sheet is on the stack — left it up once a bill
was presented. Pop instead so the bill presents over the currency-info beneath
it. v1 still opens give as a sheet and keeps hide().

The pop is untransitioned. The bill overlay and its scrim are drawn PER nav
entry, so an animated pop slides the outgoing give entry's copy of them away
while the incoming currency-info entry composes its own — which reads as a flash
behind the bill. Swapping in a single frame keeps the scrim continuously up.
@bmc08gt
bmc08gt force-pushed the feat/wallet-card-expand branch from 7a63631 to b8979de Compare August 19, 2026 21:41
@github-actions github-actions Bot added type: feature New functionality area: payments Payments, transfers, intents, billing area: ui Compose UI, theme, components, resources area: network gRPC, connectivity, API, exchange rates area: build-system Gradle, convention plugins, build-logic area: tokens Token accounts, balances, token info area: session labels Aug 19, 2026
@bmc08gt
bmc08gt merged commit 5512941 into code/cash Aug 19, 2026
3 checks passed
@bmc08gt
bmc08gt deleted the feat/wallet-card-expand branch August 19, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: build-system Gradle, convention plugins, build-logic area: network gRPC, connectivity, API, exchange rates area: payments Payments, transfers, intents, billing area: session area: tokens Token accounts, balances, token info area: ui Compose UI, theme, components, resources type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant