fix(tv): keep sidebar focused when Back returns to the home tab
Backing out to home selected the home tab but let focusActiveTabIfReady steal real focus into the content while _isSidebarFocused stayed true, leaving the rail stuck expanded over a shifted content box that clipped the end of every hub row. Skip the content-focus grab while the sidebar owns focus, move the sidebar focus ring to the Home item on back-to-home, and self-heal the flag whenever the content scope actually gains focus. close #1411
This commit is contained in:
@@ -280,6 +280,7 @@ class _MainScreenState extends State<MainScreen>
|
||||
_offlineUntilConnected = widget.isOfflineMode;
|
||||
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_contentFocusScope.addListener(_syncSidebarFocusWithContent);
|
||||
|
||||
if (PlatformDetector.isDesktopOS()) {
|
||||
windowManager.addListener(this);
|
||||
@@ -823,6 +824,7 @@ class _MainScreenState extends State<MainScreen>
|
||||
_startupSettleTimeout?.cancel();
|
||||
_startupSettleTimeout = null;
|
||||
_sidebarFocusScope.dispose();
|
||||
_contentFocusScope.removeListener(_syncSidebarFocusWithContent);
|
||||
_contentFocusScope.dispose();
|
||||
_setTvosMenuPassthrough(false);
|
||||
|
||||
@@ -1089,6 +1091,15 @@ class _MainScreenState extends State<MainScreen>
|
||||
});
|
||||
}
|
||||
|
||||
/// _isSidebarFocused is hand-toggled; if anything moves real focus into the
|
||||
/// content scope without going through _focusContent (e.g. a deferred
|
||||
/// focusActiveTabIfReady), collapse the rail to match reality (#1411).
|
||||
void _syncSidebarFocusWithContent() {
|
||||
if (!mounted || !_isSidebarFocused || !_contentFocusScope.hasFocus) return;
|
||||
setState(() => _isSidebarFocused = false);
|
||||
_updateTvosMenuPassthrough();
|
||||
}
|
||||
|
||||
void _handleSidebarInteractionExpandedChanged(bool expanded) {
|
||||
if (_isSidebarInteractionExpanded == expanded) return;
|
||||
setState(() => _isSidebarInteractionExpanded = expanded);
|
||||
@@ -1142,6 +1153,9 @@ class _MainScreenState extends State<MainScreen>
|
||||
final homeTab = tabs.first.id;
|
||||
if (_currentTab != homeTab) {
|
||||
_selectTab(homeTab);
|
||||
// Keep the focus ring in step with the new selection; the sidebar scope
|
||||
// already has focus, so no post-frame deferral is needed.
|
||||
_sideNavKey.currentState?.focusHomeItem();
|
||||
_lastBackPressAt = null;
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
@@ -1390,8 +1404,13 @@ class _MainScreenState extends State<MainScreen>
|
||||
if (newState case final TabVisibilityAware aware) {
|
||||
aware.onTabShown();
|
||||
}
|
||||
if (newState case final FocusableTab focusable) {
|
||||
focusable.focusActiveTabIfReady();
|
||||
// Back-to-home keeps the sidebar focused (chain: content → sidebar →
|
||||
// home → exit); stealing focus here left _isSidebarFocused stuck true
|
||||
// while real focus sat on a content card (#1411).
|
||||
if (!_isSidebarFocused) {
|
||||
if (newState case final FocusableTab focusable) {
|
||||
focusable.focusActiveTabIfReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -341,6 +341,13 @@ class SideNavigationRailState extends State<SideNavigationRail> with MountedSetS
|
||||
_requestFocusAndReveal(node);
|
||||
}
|
||||
|
||||
/// Focus the Home nav item (Back returning to the home tab).
|
||||
void focusHomeItem() {
|
||||
final node = _mountedFocusNodeFor(_kHome);
|
||||
if (node == null) return;
|
||||
_requestFocusAndReveal(node);
|
||||
}
|
||||
|
||||
/// Resolve the best mounted focus node in priority order:
|
||||
/// 1. Explicit [targetKey] (captured before scope switch)
|
||||
/// 2. Last focused key still in the tracker
|
||||
|
||||
Reference in New Issue
Block a user