refactor(navigation): centralize sidebar focus
This commit is contained in:
@@ -84,7 +84,7 @@ mixin TabNavigationMixin<T extends StatefulWidget> on State<T>, TickerProviderSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onTabBarBack() {
|
void onTabBarBack() {
|
||||||
MainScreenFocusScope.of(context, listen: false)?.focusSidebar();
|
MainScreenFocusScope.focusSidebarOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shared tab chip builder — eliminates duplication between screens.
|
/// Shared tab chip builder — eliminates duplication between screens.
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ class MainScreenFocusScope extends InheritedModel<MainScreenScopeAspect> {
|
|||||||
return context.getElementForInheritedWidgetOfExactType<MainScreenFocusScope>()?.widget as MainScreenFocusScope?;
|
return context.getElementForInheritedWidgetOfExactType<MainScreenFocusScope>()?.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) {
|
static MainScreenFocusScope? _dependOn(BuildContext context, MainScreenScopeAspect aspect) {
|
||||||
return InheritedModel.inheritFrom<MainScreenFocusScope>(context, aspect: aspect);
|
return InheritedModel.inheritFrom<MainScreenFocusScope>(context, aspect: aspect);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
|||||||
|
|
||||||
/// Navigate focus to the sidebar
|
/// Navigate focus to the sidebar
|
||||||
void _navigateToSidebar() {
|
void _navigateToSidebar() {
|
||||||
MainScreenFocusScope.of(context, listen: false)?.focusSidebar();
|
MainScreenFocusScope.focusSidebarOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ class DownloadsScreenState extends State<DownloadsScreen>
|
|||||||
},
|
},
|
||||||
onCancel: downloadProvider.cancelDownload,
|
onCancel: downloadProvider.cancelDownload,
|
||||||
onDelete: downloadProvider.deleteDownload,
|
onDelete: downloadProvider.deleteDownload,
|
||||||
onNavigateLeft: () => MainScreenFocusScope.of(context, listen: false)?.focusSidebar(),
|
onNavigateLeft: () => MainScreenFocusScope.focusSidebarOf(context),
|
||||||
onBack: focusTabBar,
|
onBack: focusTabBar,
|
||||||
suppressAutoFocus: suppressAutoFocus,
|
suppressAutoFocus: suppressAutoFocus,
|
||||||
);
|
);
|
||||||
@@ -287,7 +287,7 @@ class _DownloadsGridContentState extends State<_DownloadsGridContent> {
|
|||||||
|
|
||||||
/// Navigate focus to the sidebar
|
/// Navigate focus to the sidebar
|
||||||
void _navigateToSidebar() {
|
void _navigateToSidebar() {
|
||||||
MainScreenFocusScope.of(context, listen: false)?.focusSidebar();
|
MainScreenFocusScope.focusSidebarOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class ExploreScreenState extends State<ExploreScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToSidebar() {
|
void _navigateToSidebar() {
|
||||||
MainScreenFocusScope.of(context, listen: false)?.focusSidebar();
|
MainScreenFocusScope.focusSidebarOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static IconData _rowIcon(CatalogRowId row) => switch (row) {
|
static IconData _rowIcon(CatalogRowId row) => switch (row) {
|
||||||
|
|||||||
@@ -1198,7 +1198,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
|||||||
|
|
||||||
/// Navigate focus to the sidebar
|
/// Navigate focus to the sidebar
|
||||||
void _navigateToSidebar() {
|
void _navigateToSidebar() {
|
||||||
MainScreenFocusScope.of(context, listen: false)?.focusSidebar();
|
MainScreenFocusScope.focusSidebarOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Navigate focus to the alpha jump bar
|
/// Navigate focus to the alpha jump bar
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ class _LibraryCollectionsTabState extends BaseLibraryTabState<MediaItem, Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToSidebar() {
|
void _navigateToSidebar() {
|
||||||
MainScreenFocusScope.of(context, listen: false)?.focusSidebar();
|
MainScreenFocusScope.focusSidebarOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ class _LibraryPlaylistsTabState extends BaseLibraryTabState<MediaPlaylist, Libra
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToSidebar() {
|
void _navigateToSidebar() {
|
||||||
MainScreenFocusScope.of(context, listen: false)?.focusSidebar();
|
MainScreenFocusScope.focusSidebarOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState<MediaHub, LibraryR
|
|||||||
|
|
||||||
/// Navigate focus to the sidebar
|
/// Navigate focus to the sidebar
|
||||||
void _navigateToSidebar() {
|
void _navigateToSidebar() {
|
||||||
MainScreenFocusScope.of(context, listen: false)?.focusSidebar();
|
MainScreenFocusScope.focusSidebarOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra top padding for focus decoration (scale + border extends beyond item bounds)
|
// Extra top padding for focus decoration (scale + border extends beyond item bounds)
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class _SearchScreenState extends State<SearchScreen>
|
|||||||
|
|
||||||
/// Navigate focus to the sidebar
|
/// Navigate focus to the sidebar
|
||||||
void _navigateToSidebar() {
|
void _navigateToSidebar() {
|
||||||
MainScreenFocusScope.of(context, listen: false)?.focusSidebar();
|
MainScreenFocusScope.focusSidebarOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildResultsList(BuildContext context) {
|
Widget _buildResultsList(BuildContext context) {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab, Moun
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToSidebar() {
|
void _navigateToSidebar() {
|
||||||
MainScreenFocusScope.of(context, listen: false)?.focusSidebar();
|
MainScreenFocusScope.focusSidebarOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEventResult _handleKeyEvent(FocusNode _, KeyEvent event) {
|
KeyEventResult _handleKeyEvent(FocusNode _, KeyEvent event) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user