Skip to content

feat(ui): v2 tab-bar nav shell + full-screen wallet (behind BetaFlags.newUI) - #559

Merged
bmc08gt merged 8 commits into
mainfrom
feat/v2-ui-nav-shell
Aug 12, 2026
Merged

bmc08gt merged 8 commits into
mainfrom
feat/v2-ui-nav-shell

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

What

First slice of porting the Android v2 tab-bar UI (code-android-app#1209) to iOS, per the Figma (nav / card stack). This PR is the navigation shell + full-screen wallet, gated behind a new dev flag so v1 is untouched by default.

Moves the logged-in root from the scanner-first, sheet-centric model to a tab-bar-centric one: a floating Liquid Glass pill tab bar (Scan · Wallet · Chat · Tip Card) with a sliding selection indicator, wallet-first launch, and the Wallet screen promoted from a sheet to a full-screen tab.

How it's wired

  • Gate — new developer BetaFlags.newUI (default off). ContainerScreen branches the logged-in root between HomeTabView (v2) and ScanScreen (v1).
  • HomeTabView — hosts the four tabs behind HomeTabBar, launches on Wallet, and owns the app-level router.rootSheet host so router.present(_:) works from any tab. Only the selected tab is mounted (a switch, not TabView — the iOS 15 target predates the tab-bar-hiding APIs), so the Scan camera stops via its own onDisappear.
  • Router made tab-aware — the topmost-stack mutators (push/pop/…) no-op'd with no sheet presented; a tab is the active surface without being a sheet, so an activeTabStack fallback (set by HomeTabView per tab) routes in-tab pushes. No v1 behaviour change (activeTabStack is nil there).
  • Reused surfacesScanScreen(isEmbedded:) suppresses its own sheet host when embedded; BalanceScreen(isEmbedded:) drops its sheet close button to serve as the full-screen Wallet tab (same $router[.balance] stack, all push destinations intact). Chat embeds TipsScreen ($router[.tips]); Tip Card shows TipcardScreen, prompting over to Chat before the profile is tippable.
  • RootSheetHostModifier — the app-level rootSheet host (RoutedSheet swap + tip-resume + dismiss-on-bill) extracted so exactly one live view owns it: ScanScreen in v1, HomeTabView in v2.
  • Tab bar — Liquid Glass capsule (glassEffect on iOS 26, .ultraThinMaterial below), white-20% sliding indicator; icons are the Figma nav glyphs as tintable template imagesets (converted from the same vectors as Android's ic_nav_*).

Not in this PR (follow-ups, per the phased plan)

  • Token cards + collapsing TokenCardStack (populated wallet)
  • Onboarding funnel + v2 wallet header (currency selector + big balance + appreciation)
  • The bundled Android auth / verification-intro fixes (need to confirm iOS has the same bugs first)

Testing

  • xcodebuild build (device + simulator): SUCCEEDED.
  • Launch smoke test on simulator: app starts cleanly, no regression on the v1 (logged-out / default-flag) path.
  • Nav icons rasterized and verified against the Figma glyphs.
  • Not interactively verified in the logged-in v2 UI — the flow sits behind login + the dev flag, which I couldn't drive headlessly without an account. To try it: enable newUI (Settings ▸ 9-tap ▸ Beta, or launch with --beta-flags=newUI) on a logged-in build.

The topmost-stack mutators (push, pushAny, popTopmost, replaceTopmostAny,
popToRoot) targeted presentedSheet?.stack and no-op'd when no sheet was up. In
the v2 tab UI a tab is the active surface without being a sheet, so those pushes
would silently drop. Add an activeTabStack fallback (set by HomeTabView per
selected tab) via a topmostStack helper. No behaviour change in v1, where
activeTabStack is nil and a sheet is always the target.
Extract the app-level router.rootSheet presentation (RoutedSheet swap +
tip-resume + dismiss-on-bill hooks) into RootSheetHostModifier so exactly one
live view can own it. Add ScanScreen(isEmbedded:) — default false keeps the v1
root owning the host unchanged; when embedded as a v2 tab, HomeTabView owns it
and ScanScreen suppresses its own to avoid double-presentation.
Add BalanceScreen(isEmbedded:) which suppresses the sheet-only close button so
the screen can serve as the full-screen v2 Wallet tab, keeping its own
NavigationStack(path: $router[.balance]) and every existing push destination.
Defaults to false — the v1 sheet presentation is unchanged.
Add the v2 tab-bar UI, gated by the new developer BetaFlags.newUI (default off;
v1 stays the default). ContainerScreen branches the logged-in root between
HomeTabView (v2) and ScanScreen (v1).

HomeTabView hosts four tabs — Scan, Wallet, Chat, Tip Card — behind a floating
pill HomeTabBar (sliding indicator, ported from Android's NavigationBarV2),
launches on Wallet, and owns the app-level rootSheet host so present() works
from any tab. Only the selected tab is mounted (a switch, not TabView — the
deployment target predates the tab-bar-hiding APIs) so the Scan camera stops via
onDisappear. Tab icons are SF Symbols for now — close stand-ins for the Figma
nav glyphs, swappable for exported assets.

First slice of the Android v2 UI port (PR #1209); token cards, onboarding
funnel, and header polish follow.
Replace the SF Symbol placeholders with the Figma tab-bar glyphs (Nav* template
imagesets — scan/wallet/chat/tipcard — converted from the same vectors as
Android's ic_nav_*, tinted white at the call site), and swap the flat black
capsule for the app's Liquid Glass surface (glassEffect on iOS 26, ultraThinMaterial
below), matching the design.
Hide the scanner's v1 chrome when embedded in the tab-bar UI — the Flipcash
brand + settings top bar and the bottom navigation are replaced by the app-level
tab bar, so the Scan tab is just the camera. v1 (non-embedded) is unchanged.
When embedded in the tab-bar UI, the permission prompt reads 'Start your camera
to scan a Tip Card' instead of the v1 cash-grab wording. v1 (non-embedded) copy
is unchanged.
Extend the tip-card framing to the other two prompts when embedded in the v2
tab-bar UI: denied → 'Turn on Camera in Settings to scan a Tip Card'; granted but
paused → 'Start your camera to scan a Tip Card'. v1 copy unchanged.
@bmc08gt
bmc08gt merged commit 7811312 into main Aug 12, 2026
@bmc08gt
bmc08gt deleted the feat/v2-ui-nav-shell branch August 27, 2026 15:43
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.

1 participant