diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 4134f34..1b97b1c 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -94,11 +94,13 @@ class AppRouter { final GlobalKey _rootNavigatorKey = GlobalKey(debugLabel: 'root'); final GlobalKey _homeNavigatorKey = GlobalKey(debugLabel: 'home'); final GlobalKey _searchNavigatorKey = GlobalKey(debugLabel: 'search'); + final GlobalKey _atExplorerNavigatorKey = GlobalKey(debugLabel: 'at-explorer'); final GlobalKey _notificationsNavigatorKey = GlobalKey(debugLabel: 'notifications'); final GlobalKey _profileNavigatorKey = GlobalKey(debugLabel: 'profile'); List> get _branchNavigatorKeys => [ _homeNavigatorKey, _searchNavigatorKey, + _atExplorerNavigatorKey, _notificationsNavigatorKey, _profileNavigatorKey, ]; @@ -537,6 +539,15 @@ class AppRouter { GoRoute(path: '/search', pageBuilder: (context, state) => _page(context, state, const SearchScreen())), ], ), + StatefulShellBranch( + navigatorKey: _atExplorerNavigatorKey, + routes: [ + GoRoute( + path: '/at-explorer', + pageBuilder: (context, state) => _page(context, state, _buildDevToolsRoute(context, state)), + ), + ], + ), StatefulShellBranch( navigatorKey: _notificationsNavigatorKey, routes: [ @@ -687,7 +698,8 @@ class AppRouter { NotificationDomainService? _readNotificationDomainService(BuildContext context) { try { return context.read(); - } catch (_) { + } catch (error, stackTrace) { + log.d('NotificationDomainService not found for route provider setup', error: error, stackTrace: stackTrace); return null; } } diff --git a/lib/core/router/app_shell.dart b/lib/core/router/app_shell.dart index b8ea1b8..2d3f035 100644 --- a/lib/core/router/app_shell.dart +++ b/lib/core/router/app_shell.dart @@ -155,6 +155,11 @@ class _AppShellState extends State { selectedIcon: const _AnimatedNavIcon(selected: true, outlined: Icons.search_outlined, filled: Icons.search), label: l10n.labelSearchNav, ), + NavigationDestination( + icon: const _AnimatedNavIcon(selected: false, outlined: Icons.explore_outlined, filled: Icons.explore), + selectedIcon: const _AnimatedNavIcon(selected: true, outlined: Icons.explore_outlined, filled: Icons.explore), + label: l10n.labelAtExplorer, + ), NavigationDestination( icon: const _AnimatedNotificationNavIcon(selected: false), selectedIcon: const _AnimatedNotificationNavIcon(selected: true), @@ -265,7 +270,7 @@ class _AppMenu extends StatelessWidget { final isFeedsRoute = currentPath == '/feeds'; final isProfileRoute = currentPath.startsWith('/profile/'); final isSettingsRoute = currentPath == '/settings' || currentPath.startsWith('/settings/'); - final isDevToolsRoute = currentPath == '/settings/devtools'; + final isDevToolsRoute = currentPath == '/at-explorer' || currentPath == '/settings/devtools'; final isCleanFollowsRoute = currentPath == '/settings/clean-follows'; final isMessagesRoute = currentPath.startsWith('/alerts/messages') || currentPath.startsWith('/alerts/requests'); final isNotificationsRoute = currentPath.startsWith('/alerts') && !isMessagesRoute; @@ -367,7 +372,7 @@ class _AppMenu extends StatelessWidget { selectedIcon: Icons.person, label: l10n.labelProfile, isSelected: isProfileRoute, - onTap: () => _selectBranch(context, 3), + onTap: () => _selectBranch(context, 4), ), const Divider(height: 24), _MenuSectionLabel(label: l10n.labelAdvanced), @@ -376,7 +381,7 @@ class _AppMenu extends StatelessWidget { selectedIcon: Icons.explore, label: l10n.labelAtExplorer, isSelected: isDevToolsRoute, - onTap: () => _pushRoute(context, '/settings/devtools'), + onTap: () => _selectBranch(context, 2), ), _MenuTile( icon: Icons.cleaning_services_outlined, @@ -607,11 +612,7 @@ class _MenuTile extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final color = isDestructive - ? theme.colorScheme.error - : isSelected - ? theme.colorScheme.primary - : theme.colorScheme.onSurface; + final color = _color(theme); Widget tile = ListTile( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), @@ -632,4 +633,13 @@ class _MenuTile extends StatelessWidget { return tile; } + + Color _color(ThemeData theme) { + if (isDestructive) { + return theme.colorScheme.error; + } else if (isSelected) { + return theme.colorScheme.primary; + } + return theme.colorScheme.onSurface; + } } diff --git a/test/core/router/app_router_test.dart b/test/core/router/app_router_test.dart index 2e156ac..45acff9 100644 --- a/test/core/router/app_router_test.dart +++ b/test/core/router/app_router_test.dart @@ -254,6 +254,11 @@ void main() { await tester.pumpAndSettle(); } + Future tapAtExplorerBottomTab(WidgetTester tester) async { + await tester.tap(find.byIcon(Icons.explore_outlined).last); + await tester.pumpAndSettle(); + } + testWidgets('opens the side menu and switches authenticated branches', (tester) async { await tester.binding.setSurfaceSize(const Size(430, 932)); addTearDown(() => tester.binding.setSurfaceSize(null)); @@ -286,7 +291,7 @@ void main() { expect(find.text('APPEARANCE'), findsOneWidget); }); - testWidgets('bottom navigation bar shows 4 tabs with hidden labels', (tester) async { + testWidgets('bottom navigation bar shows 5 tabs with hidden labels', (tester) async { await tester.binding.setSurfaceSize(const Size(430, 932)); addTearDown(() => tester.binding.setSurfaceSize(null)); @@ -294,11 +299,11 @@ void main() { await tester.pumpAndSettle(); final navBar = tester.widget(find.byType(NavigationBar)); - expect(navBar.destinations.length, 4); + expect(navBar.destinations.length, 5); expect(navBar.labelBehavior, NavigationDestinationLabelBehavior.alwaysHide); final destinations = navBar.destinations.cast(); - expect(destinations.map((d) => d.label), containsAll(['HOME', 'SEARCH', 'ALERTS', 'PROFILE'])); + expect(destinations.map((d) => d.label), ['HOME', 'SEARCH', 'AT Explorer', 'ALERTS', 'PROFILE']); expect(destinations.any((d) => d.label == 'MESSAGES'), isFalse); expect(destinations.any((d) => d.label == 'SETTINGS'), isFalse); }); @@ -469,10 +474,24 @@ void main() { await tapProfileBottomTab(tester); final navBarAfter = tester.widget(find.byType(NavigationBar)); - expect(navBarAfter.selectedIndex, 3); + expect(navBarAfter.selectedIndex, 4); expect(find.text('RIVER TAM'), findsOneWidget); }); + testWidgets('tapping AT Explorer bottom tab opens the explorer branch', (tester) async { + await tester.binding.setSurfaceSize(const Size(430, 932)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + + await tester.pumpWidget(buildSubject()); + await tester.pumpAndSettle(); + + await tapAtExplorerBottomTab(tester); + + final navBar = tester.widget(find.byType(NavigationBar)); + expect(navBar.selectedIndex, 2); + expect(find.text('PDS Explorer'), findsWidgets); + }); + testWidgets('LazuriteAppBar shows section label and hamburger on home screen', (tester) async { await tester.binding.setSurfaceSize(const Size(430, 932)); addTearDown(() => tester.binding.setSurfaceSize(null));