Skip to content

feat(home): long-press the You tab to open the account switcher - #749

Open
bmc08gt wants to merge 4 commits into
mainfrom
feat/you-tab-long-press-switch-account
Open

feat(home): long-press the You tab to open the account switcher#749
bmc08gt wants to merge 4 commits into
mainfrom
feat/you-tab-long-press-switch-account

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Holding the You tab opens the account switcher as a sheet. It is behind the same gate as the Switch Accounts row in Settings, BetaFlags.canSwitchAccounts, which is accessGranted today, so nobody outside that group sees a difference. The tap still lands: on both bars the press selects the You tab on release and the switcher slides up over it.

Changes:

  • New .switchAccount sheet and stack on AppRouter. The stack is root-only and not tab-hosted, so the existing cross-stack invariants hold. SwitchAccountSheetRoot wraps AccountSelectionScreen in its own NavigationStack with a close button.
  • The dismiss-then-switch closure the Settings push already used moves to AccountSelectionScreen.switchAccountAction(router:sessionAuthenticator:) so the push and the sheet run the same code.
  • Legacy pill (HomeTabBar, iOS 18 to 25): a simultaneousGesture long press on each tab button. A high-priority gesture held the button's tap until the press failed, which made quick taps flaky, so it is simultaneous.
  • iOS 26 TabView: the existing TabBarSelectedIcons.Probe already finds the UITabBar; it now adds one UILongPressGestureRecognizer per bar with cancelsTouchesInView = false. The recognizer also needs a delegate allowing simultaneous recognition — without one the bar's _UIContinuousSelectionGestureRecognizer, which tracks a finger sliding across the items, wins the conflict and the long press never begins.
  • TabBarItemLocator maps the press point back to a HomeTab from the bar's button frames. Two things about the iOS 26 bar shape it has to absorb: every item is drawn twice at the same frame (a plain copy and the one the glass lens reveals), so coincident frames collapse to one before the count is compared against the tabs; and the buttons are laid out wider than their pitch — 114pt buttons every 85pt on a 402pt bar — so neighbours overlap and a press near a boundary falls inside two frames. The nearer centre wins, which is the item the press looks like it is on.
  • Haptics.medium() fires from handleLongPress, the one place both bars converge, and after the canSwitchAccounts gate — a hold that opens nothing stays silent.
  • Tests: SwitchAccountSheetTests pins the sheet and stack mapping, root-sheet presentation, and that dismissing restores the You stack's path. TabBarItemLocatorTests builds a real windowed UITabBarController and checks one frame per tab in display order, that the last frame resolves to You, that a point in an overlap resolves to the nearer item, and that an off-bar point resolves to nil.

Android has no long press on its bottom nav, so this is iOS-only for now.

A root-only `.switchAccount` sheet and stack on AppRouter, wrapping
AccountSelectionScreen in its own NavigationStack with a close button.
The stack is not tab-hosted, so the cross-stack invariants hold.

The dismiss-then-switch closure the Settings push already used moves to
`AccountSelectionScreen.switchAccountAction(router:sessionAuthenticator:)`
so the push and the sheet run the same code.
Behind `BetaFlags.canSwitchAccounts`, the same gate as the Switch
Accounts row in Settings. On both bars the press still selects the You
tab on release.

Legacy pill (iOS 18 to 25): a simultaneous LongPressGesture on each tab
button. A high-priority gesture held the button's tap until the press
failed, which made quick taps flaky.

iOS 26 TabView: the TabBarSelectedIcons probe already finds the
UITabBar, so it adds one UILongPressGestureRecognizer per bar with
cancelsTouchesInView off. TabBarItemLocator maps the press point back
to a HomeTab from the bar's button frames. iOS 26 draws every item
twice at the same frame (a plain copy and the one the glass lens
reveals), so coinciding frames collapse to one before the count is
compared against the tabs. The locator test drives a real windowed
UITabBarController so that layout is what gets checked.
A `UILongPressGestureRecognizer` added to the native `UITabBar` never began:
the bar's own `_UIContinuousSelectionGestureRecognizer` — the one tracking a
finger sliding across the items — won the conflict. Give the recognizer a
delegate that allows simultaneous recognition, so it runs alongside the bar's
own recognizers and holding an item still selects it on release.

The same press also exposed a latent locator bug. iOS 26 lays the items out
wider than their pitch (114pt buttons every 85pt on a 402pt bar), so adjacent
frames overlap and a press near a boundary falls inside two; taking the first
containing frame could resolve to the wrong tab. The nearest centre now wins.
@bmc08gt bmc08gt self-assigned this Sep 10, 2026
`Haptics.medium()` from the one place both bars converge, `handleLongPress`,
placed after the `canSwitchAccounts` gate so a hold that opens nothing stays
silent.
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