diff --git a/lib/mixins/tab_navigation_mixin.dart b/lib/mixins/tab_navigation_mixin.dart index d91063fe..69345c81 100644 --- a/lib/mixins/tab_navigation_mixin.dart +++ b/lib/mixins/tab_navigation_mixin.dart @@ -84,7 +84,7 @@ mixin TabNavigationMixin on State, TickerProviderSt } void onTabBarBack() { - MainScreenFocusScope.of(context, listen: false)?.focusSidebar(); + MainScreenFocusScope.focusSidebarOf(context); } /// Shared tab chip builder — eliminates duplication between screens. diff --git a/lib/navigation/main_screen_scope.dart b/lib/navigation/main_screen_scope.dart index eeeb87b6..bdde1f90 100644 --- a/lib/navigation/main_screen_scope.dart +++ b/lib/navigation/main_screen_scope.dart @@ -56,6 +56,11 @@ class MainScreenFocusScope extends InheritedModel { return context.getElementForInheritedWidgetOfExactType()?.widget as MainScreenFocusScope?; } + /// Focuses the sidebar without registering an inherited dependency. + static void focusSidebarOf(BuildContext context) { + of(context, listen: false)?.focusSidebar(); + } + static MainScreenFocusScope? _dependOn(BuildContext context, MainScreenScopeAspect aspect) { return InheritedModel.inheritFrom(context, aspect: aspect); } diff --git a/lib/screens/discover_screen.dart b/lib/screens/discover_screen.dart index 914bd284..db192e15 100644 --- a/lib/screens/discover_screen.dart +++ b/lib/screens/discover_screen.dart @@ -362,7 +362,7 @@ class _DiscoverScreenState extends State /// Navigate focus to the sidebar void _navigateToSidebar() { - MainScreenFocusScope.of(context, listen: false)?.focusSidebar(); + MainScreenFocusScope.focusSidebarOf(context); } @override diff --git a/lib/screens/downloads/downloads_screen.dart b/lib/screens/downloads/downloads_screen.dart index ef57ff6b..ee22db2d 100644 --- a/lib/screens/downloads/downloads_screen.dart +++ b/lib/screens/downloads/downloads_screen.dart @@ -220,7 +220,7 @@ class DownloadsScreenState extends State }, onCancel: downloadProvider.cancelDownload, onDelete: downloadProvider.deleteDownload, - onNavigateLeft: () => MainScreenFocusScope.of(context, listen: false)?.focusSidebar(), + onNavigateLeft: () => MainScreenFocusScope.focusSidebarOf(context), onBack: focusTabBar, suppressAutoFocus: suppressAutoFocus, ); @@ -287,7 +287,7 @@ class _DownloadsGridContentState extends State<_DownloadsGridContent> { /// Navigate focus to the sidebar void _navigateToSidebar() { - MainScreenFocusScope.of(context, listen: false)?.focusSidebar(); + MainScreenFocusScope.focusSidebarOf(context); } @override diff --git a/lib/screens/explore_screen.dart b/lib/screens/explore_screen.dart index 72d637a1..eba37639 100644 --- a/lib/screens/explore_screen.dart +++ b/lib/screens/explore_screen.dart @@ -133,7 +133,7 @@ class ExploreScreenState extends State } void _navigateToSidebar() { - MainScreenFocusScope.of(context, listen: false)?.focusSidebar(); + MainScreenFocusScope.focusSidebarOf(context); } static IconData _rowIcon(CatalogRowId row) => switch (row) { diff --git a/lib/screens/libraries/tabs/library_browse_tab.dart b/lib/screens/libraries/tabs/library_browse_tab.dart index a287e78b..367f4690 100644 --- a/lib/screens/libraries/tabs/library_browse_tab.dart +++ b/lib/screens/libraries/tabs/library_browse_tab.dart @@ -1198,7 +1198,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState /// Navigate focus to the sidebar void _navigateToSidebar() { - MainScreenFocusScope.of(context, listen: false)?.focusSidebar(); + MainScreenFocusScope.focusSidebarOf(context); } Widget _buildResultsList(BuildContext context) { diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index d177d1aa..32b54f6a 100644 --- a/lib/screens/settings/settings_screen.dart +++ b/lib/screens/settings/settings_screen.dart @@ -117,7 +117,7 @@ class _SettingsScreenState extends State with FocusableTab, Moun } void _navigateToSidebar() { - MainScreenFocusScope.of(context, listen: false)?.focusSidebar(); + MainScreenFocusScope.focusSidebarOf(context); } KeyEventResult _handleKeyEvent(FocusNode _, KeyEvent event) { diff --git a/test/navigation/main_screen_scope_test.dart b/test/navigation/main_screen_scope_test.dart new file mode 100644 index 00000000..5556405e --- /dev/null +++ b/test/navigation/main_screen_scope_test.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/navigation/main_screen_scope.dart'; + +void main() { + testWidgets('focusSidebarOf invokes the scope callback', (tester) async { + var focusCalls = 0; + late BuildContext childContext; + + await tester.pumpWidget( + MaterialApp( + home: MainScreenFocusScope( + focusSidebar: () => focusCalls++, + focusContent: () {}, + isSidebarFocused: false, + sideNavigationWidth: 0, + child: Builder( + builder: (context) { + childContext = context; + return const SizedBox.shrink(); + }, + ), + ), + ), + ); + + MainScreenFocusScope.focusSidebarOf(childContext); + + expect(focusCalls, 1); + }); + + testWidgets('focusSidebarOf is a no-op without a scope', (tester) async { + late BuildContext childContext; + + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (context) { + childContext = context; + return const SizedBox.shrink(); + }, + ), + ), + ); + + expect(() => MainScreenFocusScope.focusSidebarOf(childContext), returnsNormally); + }); + + testWidgets('focusSidebarOf does not register an inherited dependency', (tester) async { + var isSidebarFocused = false; + var childBuilds = 0; + var focusCalls = 0; + late StateSetter rebuildScope; + final child = Builder( + builder: (context) { + childBuilds++; + MainScreenFocusScope.focusSidebarOf(context); + return const SizedBox.shrink(); + }, + ); + + await tester.pumpWidget( + MaterialApp( + home: StatefulBuilder( + builder: (context, setState) { + rebuildScope = setState; + return MainScreenFocusScope( + focusSidebar: () => focusCalls++, + focusContent: () {}, + isSidebarFocused: isSidebarFocused, + sideNavigationWidth: 0, + child: child, + ); + }, + ), + ), + ); + + expect(childBuilds, 1); + expect(focusCalls, 1); + + rebuildScope(() => isSidebarFocused = true); + await tester.pump(); + + expect(childBuilds, 1); + expect(focusCalls, 1); + }); +}