diff --git a/lib/src/controller/router.dart b/lib/src/controller/router.dart index f5618dcb..40abee39 100644 --- a/lib/src/controller/router.dart +++ b/lib/src/controller/router.dart @@ -10,13 +10,22 @@ class AppRouter extends RootStackRouter { @override List get routes => [ + RedirectRoute(path: '/', redirectTo: '/$defaultInstance'), AutoRoute( page: AppInstanceRoute.page, path: '/:instance', children: [ - AutoRoute(page: AppHome.page, path: ''), - AutoRoute(page: FeedRoute.page, path: 'feed'), - AutoRoute(page: ExploreRoute.page, path: 'explore'), + AutoRoute( + page: AppHome.page, + path: '', + children: [ + AutoRoute(page: FeedRoute.page, path: 'home'), + AutoRoute(page: ExploreTab.page, path: 'explore'), + AutoRoute(page: SelfFeed.page, path: 'account'), + AutoRoute(page: InboxRoute.page, path: 'inbox'), + AutoRoute(page: SettingsRoute.page, path: 'settings'), + ], + ), AutoRoute(page: ThreadRoute.page, path: 'c/:communityName/thread/:id'), AutoRoute( @@ -56,51 +65,60 @@ class AppRouter extends RootStackRouter { AutoRoute(page: EditProfileRoute.page, path: 'profile/edit'), AutoRoute(page: EditFilterListRoute.page, path: 'filter/:filterList'), AutoRoute(page: EditFeedRoute.page, path: 'feed/:feed/edit'), - ], - ), - RedirectRoute(path: '/', redirectTo: '/$defaultInstance'), + AutoRoute(page: ExploreRoute.page, path: 'search'), - //settings - AutoRoute(page: BehaviorSettingsRoute.page, path: '/settings/behavior'), - AutoRoute(page: DisplaySettingsRoute.page, path: '/settings/display'), - AutoRoute(page: FeedSettingsRoute.page, path: '/settings/feeds'), - AutoRoute(page: FeedActionsSettingsRoute.page, path: '/settings/actions'), - AutoRoute(page: FeedDefaultSettingsRoute.page, path: '/settings/defaults'), - AutoRoute( - page: FeedSourceOrderSettingsRoute.page, - path: '/settings/defaults/source', - ), - AutoRoute( - page: FeedViewOrderSettingsRoute.page, - path: '/settings/defaults/view', - ), - AutoRoute( - page: FeedSortOrderSettingsRoute.page, - path: '/settings/defaults/sort', - ), - AutoRoute(page: TagsRoute.page, path: '/settings/tags'), - AutoRoute(page: FilterListsRoute.page, path: '/settings/filters'), - AutoRoute( - page: NotificationSettingsRoute.page, - path: '/settings/notifications', - ), - AutoRoute(page: DataUtilitiesRoute.page, path: '/settings/utilities'), - AutoRoute( - page: AccountMigrationRoute.page, - path: '/settings/utilities/migration', - ), - AutoRoute(page: AccountResetRoute.page, path: '/settings/utilities/reset'), - AutoRoute(page: AboutRoute.page, path: '/settings/about'), - AutoRoute(page: DebugSettingsRoute.page, path: '/settings/about/debug'), - AutoRoute(page: LogConsole.page, path: '/settings/about/debug/log'), - // DriftDbViewer is part of a library so can't use code gen. - NamedRouteDef( - name: 'DriftDbViewer', - builder: (context, data) => DriftDbViewer(database), - path: '/settings/about/debug/database', + //settings + AutoRoute(page: BehaviorSettingsRoute.page, path: 'settings/behavior'), + AutoRoute(page: DisplaySettingsRoute.page, path: 'settings/display'), + AutoRoute(page: FeedSettingsRoute.page, path: 'settings/feeds'), + AutoRoute( + page: FeedActionsSettingsRoute.page, + path: 'settings/actions', + ), + AutoRoute( + page: FeedDefaultSettingsRoute.page, + path: 'settings/defaults', + ), + AutoRoute( + page: FeedSourceOrderSettingsRoute.page, + path: 'settings/defaults/source', + ), + AutoRoute( + page: FeedViewOrderSettingsRoute.page, + path: 'settings/defaults/view', + ), + AutoRoute( + page: FeedSortOrderSettingsRoute.page, + path: 'settings/defaults/sort', + ), + AutoRoute(page: TagsRoute.page, path: 'settings/tags'), + AutoRoute(page: FilterListsRoute.page, path: 'settings/filters'), + AutoRoute( + page: NotificationSettingsRoute.page, + path: 'settings/notifications', + ), + AutoRoute(page: DataUtilitiesRoute.page, path: 'settings/utilities'), + AutoRoute( + page: AccountMigrationRoute.page, + path: 'settings/utilities/migration', + ), + AutoRoute( + page: AccountResetRoute.page, + path: 'settings/utilities/reset', + ), + AutoRoute(page: AboutRoute.page, path: 'settings/about'), + AutoRoute(page: DebugSettingsRoute.page, path: 'settings/about/debug'), + AutoRoute(page: LogConsole.page, path: 'settings/about/debug/log'), + // DriftDbViewer is part of a library so can't use code gen. + NamedRouteDef( + name: 'DriftDbViewer', + builder: (context, data) => DriftDbViewer(database), + path: 'settings/about/debug/database', + ), + AutoRoute(page: LoginSelectRoute.page, path: 'settings/login'), + AutoRoute(page: LoginConfirmRoute.page, path: 'settings/login/confirm'), + AutoRoute(page: WebViewRoute.page, path: 'webview'), + ], ), - AutoRoute(page: LoginSelectRoute.page, path: '/settings/login'), - AutoRoute(page: LoginConfirmRoute.page, path: '/settings/login/confirm'), - AutoRoute(page: WebViewRoute.page, path: '/webview'), ]; } diff --git a/lib/src/screens/account/inbox_screen.dart b/lib/src/screens/account/inbox_screen.dart index a31ca38b..e4de8862 100644 --- a/lib/src/screens/account/inbox_screen.dart +++ b/lib/src/screens/account/inbox_screen.dart @@ -1,3 +1,4 @@ +import 'package:auto_route/annotations.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/screens/account/messages/messages_screen.dart'; @@ -7,21 +8,15 @@ import 'package:interstellar/src/utils/utils.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -class InboxScreen extends StatefulWidget { - const InboxScreen({super.key}); +@RoutePage() +class InboxScreen extends StatelessWidget { + const InboxScreen({super.key, this.placeholder = false}); - @override - State createState() => _InboxScreenState(); -} - -class _InboxScreenState extends State - with AutomaticKeepAliveClientMixin { - @override - bool get wantKeepAlive => true; + // This is so the generated route includes parameters so that a key can be passed. + final bool placeholder; @override Widget build(BuildContext context) { - super.build(context); return whenLoggedIn( context, DefaultTabController( diff --git a/lib/src/screens/account/self_feed.dart b/lib/src/screens/account/self_feed.dart index 393d187d..9759714b 100644 --- a/lib/src/screens/account/self_feed.dart +++ b/lib/src/screens/account/self_feed.dart @@ -1,3 +1,4 @@ +import 'package:auto_route/annotations.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/models/user.dart'; @@ -8,21 +9,21 @@ import 'package:interstellar/src/widgets/loading_template.dart'; import 'package:oauth2/oauth2.dart'; import 'package:provider/provider.dart'; +@RoutePage() class SelfFeed extends StatefulWidget { - const SelfFeed({super.key}); + const SelfFeed({super.key, this.placeholder = false}); + + // This is so the generated route includes parameters so that a key can be passed. + final bool placeholder; @override State createState() => _SelfFeedState(); } -class _SelfFeedState extends State - with AutomaticKeepAliveClientMixin { +class _SelfFeedState extends State { DetailedUserModel? _meUser; AuthorizationException? _authError; - @override - bool get wantKeepAlive => true; - @override void initState() { super.initState(); @@ -52,8 +53,6 @@ class _SelfFeedState extends State @override Widget build(BuildContext context) { - super.build(context); - final ac = context.read(); if (!ac.isLoggedIn) { diff --git a/lib/src/screens/app_home.dart b/lib/src/screens/app_home.dart index 75b10d70..f90ce48c 100644 --- a/lib/src/screens/app_home.dart +++ b/lib/src/screens/app_home.dart @@ -4,16 +4,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/router.dart'; import 'package:interstellar/src/controller/router.gr.dart'; -import 'package:interstellar/src/screens/account/inbox_screen.dart'; import 'package:interstellar/src/screens/account/notification/notification_badge.dart'; -import 'package:interstellar/src/screens/account/self_feed.dart'; -import 'package:interstellar/src/screens/explore/explore_screen.dart'; -import 'package:interstellar/src/screens/feed/feed_screen.dart'; +import 'package:interstellar/src/screens/explore/explore_screen.dart' + show ExploreType; import 'package:interstellar/src/screens/settings/account_selection.dart'; import 'package:interstellar/src/screens/settings/profile_selection.dart'; -import 'package:interstellar/src/screens/settings/settings_screen.dart'; import 'package:interstellar/src/utils/breakpoints.dart'; import 'package:interstellar/src/utils/globals.dart'; import 'package:interstellar/src/utils/utils.dart'; @@ -32,7 +28,7 @@ class AppInstanceScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return const Scaffold(body: AutoRouter()); + return const AutoRouter(); } } @@ -46,7 +42,6 @@ class AppHome extends StatefulWidget { class _AppHomeState extends State { int _navIndex = 0; - final PageController _pageController = PageController(); Key _feedKey = UniqueKey(); Key _exploreKey = UniqueKey(); Key _accountKey = UniqueKey(); @@ -103,7 +98,6 @@ class _AppHomeState extends State { setState(() { _navIndex = newIndex; }); - _pageController.jumpToPage(_navIndex); } Future _handleExit(bool didPop, result) async { @@ -128,10 +122,7 @@ class _AppHomeState extends State { @override Widget build(BuildContext context) { - final ac = context.watch(); - - // AppController should be available for later if needed. - // ignore: cascade_invocations + final ac = context.read(); ac.refreshState = () { setState(() { _feedKey = UniqueKey(); @@ -142,117 +133,150 @@ class _AppHomeState extends State { context.router.root.replace(AppInstanceRoute(instance: ac.instanceHost)); }; - final notCompact = !Breakpoints.isCompact(context); - return PopScope( canPop: false, onPopInvokedWithResult: _handleExit, - child: Scaffold( - bottomNavigationBar: notCompact - ? null - : NavigationBar( - height: 56, - labelBehavior: NavigationDestinationLabelBehavior.alwaysHide, - destinations: [ - NavigationDestination( - label: l(context).feed, - icon: const Icon(Symbols.home_rounded), - selectedIcon: const Icon(Symbols.home_rounded, fill: 1), - ), - NavigationDestination( - label: l(context).explore, - icon: const Icon(Symbols.explore_rounded), - selectedIcon: const Icon(Symbols.explore_rounded, fill: 1), - ), - NavigationDestination( - label: l(context).account, - icon: const Icon(Symbols.person_rounded), - selectedIcon: const Icon(Symbols.person_rounded, fill: 1), - ), - NavigationDestination( - label: l(context).inbox, - icon: Wrapper( - shouldWrap: context.watch().isLoggedIn, - parentBuilder: (child) => NotificationBadge(child: child), - child: const Icon(Symbols.inbox_rounded), - ), - selectedIcon: Wrapper( - shouldWrap: context.watch().isLoggedIn, - parentBuilder: (child) => NotificationBadge(child: child), - child: const Icon(Symbols.inbox_rounded, fill: 1), - ), - ), - NavigationDestination( - label: l(context).settings, - icon: const Icon(Symbols.settings_rounded), - selectedIcon: const Icon(Symbols.settings_rounded, fill: 1), - ), - ], - selectedIndex: _navIndex, - onDestinationSelected: _changeNav, - ), - body: Row( - children: [ - if (notCompact) - NavigationRail( - selectedIndex: _navIndex, - onDestinationSelected: _changeNav, - labelType: NavigationRailLabelType.all, - destinations: [ - NavigationRailDestination( - label: Text(l(context).feed), - icon: const Icon(Symbols.feed_rounded), - selectedIcon: const Icon(Symbols.feed_rounded, fill: 1), - ), - NavigationRailDestination( - label: Text(l(context).explore), - icon: const Icon(Symbols.explore_rounded), - selectedIcon: const Icon(Symbols.explore_rounded, fill: 1), - ), - NavigationRailDestination( - label: Text(l(context).account), - icon: const Icon(Symbols.person_rounded), - selectedIcon: const Icon(Symbols.person_rounded, fill: 1), - ), - NavigationRailDestination( - label: Text(l(context).inbox), - icon: Wrapper( - shouldWrap: context.watch().isLoggedIn, - parentBuilder: (child) => NotificationBadge(child: child), - child: const Icon(Symbols.inbox_rounded), - ), - selectedIcon: Wrapper( - shouldWrap: context.watch().isLoggedIn, - parentBuilder: (child) => NotificationBadge(child: child), - child: const Icon(Symbols.inbox_rounded, fill: 1), - ), - ), - NavigationRailDestination( - label: Text(l(context).settings), - icon: const Icon(Symbols.settings_rounded), - selectedIcon: const Icon(Symbols.settings_rounded, fill: 1), - ), - ], - ), - if (notCompact) const VerticalDivider(thickness: 1, width: 1), - Expanded( - child: PageView( - controller: _pageController, - physics: const NeverScrollableScrollPhysics(), - children: [ - FeedScreen( - key: _feedKey, - scrollController: _feedScrollController, + child: AutoTabsRouter( + routes: [ + FeedRoute(key: _feedKey, scrollController: _feedScrollController), + ExploreTab(key: _exploreKey, focusNode: _exploreFocusNode), + SelfFeed(key: _accountKey), + InboxRoute(key: _inboxKey), + const SettingsRoute(), + ], + builder: (context, child) { + final tabsRouter = AutoTabsRouter.of(context); + final notCompact = !Breakpoints.isCompact(context); + + return Scaffold( + body: notCompact + ? Row( + children: [ + NavigationRail( + destinations: [ + NavigationRailDestination( + icon: const Icon(Symbols.home_rounded), + selectedIcon: const Icon( + Symbols.home_rounded, + fill: 1, + ), + label: Text(l(context).feed), + ), + NavigationRailDestination( + icon: const Icon(Symbols.explore_rounded), + selectedIcon: const Icon( + Symbols.explore_rounded, + fill: 1, + ), + label: Text(l(context).explore), + ), + NavigationRailDestination( + icon: const Icon(Symbols.person_rounded), + selectedIcon: const Icon( + Symbols.person_rounded, + fill: 1, + ), + label: Text(l(context).account), + ), + NavigationRailDestination( + icon: Wrapper( + shouldWrap: context + .watch() + .isLoggedIn, + parentBuilder: (child) => + NotificationBadge(child: child), + child: const Icon(Symbols.inbox_rounded), + ), + selectedIcon: Wrapper( + shouldWrap: context + .watch() + .isLoggedIn, + parentBuilder: (child) => + NotificationBadge(child: child), + child: const Icon(Symbols.inbox_rounded, fill: 1), + ), + label: Text(l(context).inbox), + ), + NavigationRailDestination( + icon: const Icon(Symbols.settings_rounded), + selectedIcon: const Icon( + Symbols.settings_rounded, + fill: 1, + ), + label: Text(l(context).settings), + ), + ], + selectedIndex: tabsRouter.activeIndex, + onDestinationSelected: (index) { + _changeNav(index); + tabsRouter.setActiveIndex(index); + }, + labelType: NavigationRailLabelType.all, + ), + Expanded(child: child), + ], + ) + : child, + bottomNavigationBar: notCompact + ? null + : NavigationBar( + height: 56, + labelBehavior: + NavigationDestinationLabelBehavior.alwaysHide, + selectedIndex: tabsRouter.activeIndex, + onDestinationSelected: (index) { + _changeNav(index); + tabsRouter.setActiveIndex(index); + }, + destinations: [ + NavigationDestination( + icon: const Icon(Symbols.home_rounded), + selectedIcon: const Icon(Symbols.home_rounded, fill: 1), + label: l(context).feed, + ), + NavigationDestination( + icon: const Icon(Symbols.explore_rounded), + selectedIcon: const Icon( + Symbols.explore_rounded, + fill: 1, + ), + label: l(context).explore, + ), + NavigationDestination( + icon: const Icon(Symbols.person_rounded), + selectedIcon: const Icon( + Symbols.person_rounded, + fill: 1, + ), + label: l(context).account, + ), + NavigationDestination( + icon: Wrapper( + shouldWrap: context.watch().isLoggedIn, + parentBuilder: (child) => + NotificationBadge(child: child), + child: const Icon(Symbols.inbox_rounded), + ), + selectedIcon: Wrapper( + shouldWrap: context.watch().isLoggedIn, + parentBuilder: (child) => + NotificationBadge(child: child), + child: const Icon(Symbols.inbox_rounded, fill: 1), + ), + label: l(context).inbox, + ), + NavigationDestination( + icon: const Icon(Symbols.settings_rounded), + selectedIcon: const Icon( + Symbols.settings_rounded, + fill: 1, + ), + label: l(context).settings, + ), + ], ), - ExploreScreen(key: _exploreKey, focusNode: _exploreFocusNode), - SelfFeed(key: _accountKey), - InboxScreen(key: _inboxKey), - const SettingsScreen(), - ], - ), - ), - ], - ), + ); + }, ), ); } diff --git a/lib/src/screens/explore/explore_screen.dart b/lib/src/screens/explore/explore_screen.dart index 8f982c5f..ff5aede6 100644 --- a/lib/src/screens/explore/explore_screen.dart +++ b/lib/src/screens/explore/explore_screen.dart @@ -17,6 +17,11 @@ import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; +@RoutePage() +class ExploreTab extends ExploreScreen { + const ExploreTab({super.key, super.focusNode}); +} + @RoutePage() class ExploreScreen extends StatefulWidget { const ExploreScreen({ @@ -42,8 +47,7 @@ class ExploreScreen extends StatefulWidget { State createState() => _ExploreScreenState(); } -class _ExploreScreenState extends State - with AutomaticKeepAliveClientMixin { +class _ExploreScreenState extends State { late FocusNode _focusNode; final TextEditingController _searchController = TextEditingController(); final searchDebounce = Debouncer(duration: const Duration(milliseconds: 500)); @@ -126,9 +130,6 @@ class _ExploreScreenState extends State ); late final ScrollController _scrollController; - @override - bool get wantKeepAlive => true; - @override void initState() { super.initState(); @@ -148,7 +149,6 @@ class _ExploreScreenState extends State @override Widget build(BuildContext context) { - super.build(context); const chipPadding = EdgeInsets.symmetric(vertical: 6, horizontal: 4); final currentExploreSort = exploreSortSelection(context).getOption(sort); diff --git a/lib/src/screens/feed/feed_screen.dart b/lib/src/screens/feed/feed_screen.dart index 9608573f..91a2418a 100644 --- a/lib/src/screens/feed/feed_screen.dart +++ b/lib/src/screens/feed/feed_screen.dart @@ -50,8 +50,7 @@ class FeedScreen extends StatefulWidget { State createState() => _FeedScreenState(); } -class _FeedScreenState extends State - with AutomaticKeepAliveClientMixin { +class _FeedScreenState extends State { late final ScrollController _scrollController; final _fabKey = GlobalKey(); final List> _feedKeyList = []; @@ -65,9 +64,6 @@ class _FeedScreenState extends State ); NavDrawPersistentState? _navDrawPersistentState; - @override - bool get wantKeepAlive => true; - GlobalKey<_FeedScreenBodyState> _getFeedKey(int index) { while (index >= _feedKeyList.length) { _feedKeyList.add(GlobalKey()); @@ -243,7 +239,6 @@ class _FeedScreenState extends State @override Widget build(BuildContext context) { - super.build(context); final sort = _sort ?? _defaultSortFromMode(_view); final ac = context.watch(); diff --git a/lib/src/screens/settings/login_select.dart b/lib/src/screens/settings/login_select.dart index dbc2ce73..5ce4e406 100644 --- a/lib/src/screens/settings/login_select.dart +++ b/lib/src/screens/settings/login_select.dart @@ -100,11 +100,11 @@ class _LoginSelectScreenState extends State { // Check BuildContext if (!mounted) return; - final shouldPop = await context.router.push( + final shouldPop = await context.router.push( LoginConfirmRoute(software: software, server: host), ); - if (shouldPop == true) { + if (shouldPop ?? false) { // Check BuildContext if (!mounted) return; diff --git a/lib/src/screens/settings/settings_screen.dart b/lib/src/screens/settings/settings_screen.dart index 7b9c1f20..b7d39649 100644 --- a/lib/src/screens/settings/settings_screen.dart +++ b/lib/src/screens/settings/settings_screen.dart @@ -10,6 +10,7 @@ import 'package:interstellar/src/widgets/server_software_indicator.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; +@RoutePage() class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key});