From b918986fc5b8701379393b66b0e4d88501b9cbbb Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 19 May 2025 00:55:51 +0800 Subject: [PATCH] :recycle: New tab navigation --- lib/main.dart | 17 +++- lib/route.dart | 60 +++++------- lib/route.gr.dart | 131 +++++++++++++++++++++---- lib/screens/account.dart | 33 ++++++- lib/screens/auth/tabs.dart | 190 ++++++++++++++++++++++--------------- lib/screens/chat/chat.dart | 13 ++- 6 files changed, 305 insertions(+), 139 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index bff6cca..02c90d2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,6 +17,7 @@ import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:island/pods/userinfo.dart'; import 'package:island/pods/websocket.dart'; import 'package:island/route.dart'; +import 'package:island/screens/auth/tabs.dart'; import 'package:island/services/notify.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:relative_time/relative_time.dart'; @@ -102,7 +103,16 @@ class IslandApp extends HookConsumerWidget { theme: theme?.light, darkTheme: theme?.dark, themeMode: ThemeMode.system, - routerConfig: _appRouter.config(), + routerConfig: _appRouter.config( + navigatorObservers: + () => [ + TabNavigationObserver( + onChange: (route) { + ref.read(currentRouteProvider.notifier).state = route; + }, + ), + ], + ), supportedLocales: context.supportedLocales, localizationsDelegates: [ ...context.localizationDelegates, @@ -117,7 +127,10 @@ class IslandApp extends HookConsumerWidget { builder: (_) => WindowScaffold( router: _appRouter, - child: child ?? const SizedBox.shrink(), + child: TabsNavigationWidget( + router: _appRouter, + child: child ?? const SizedBox.shrink(), + ), ), ), ], diff --git a/lib/route.dart b/lib/route.dart index b74f616..7fac723 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -8,49 +8,39 @@ class AppRouter extends RootStackRouter { @override List get routes => [ + RedirectRoute(path: '/', redirectTo: '/explore'), + AutoRoute(page: ExploreRoute.page, path: '/explore'), AutoRoute( - page: TabsRoute.page, - path: '/', - initial: true, + page: AccountShellRoute.page, + path: '/account', children: [ - AutoRoute(page: ExploreRoute.page, path: 'explore'), - AutoRoute(page: AccountRoute.page, path: 'account'), - AutoRoute(page: RealmListRoute.page, path: 'realms'), - AutoRoute( - page: ChatShellRoute.page, - path: 'chat', - children: [ - AutoRoute(page: ChatListRoute.page, path: ''), - AutoRoute(page: ChatRoomRoute.page, path: ':id'), - AutoRoute(page: NewChatRoute.page, path: 'new'), - AutoRoute(page: EditChatRoute.page, path: ':id/edit'), - AutoRoute(page: ChatDetailRoute.page, path: ':id/detail'), - ], - ), + AutoRoute(page: WalletRoute.page, path: 'wallet'), + AutoRoute(page: RelationshipRoute.page, path: 'relationships'), + AutoRoute(page: AccountRoute.page, path: ''), + AutoRoute(page: AccountSettingsRoute.page, path: 'settings'), + AutoRoute(page: AccountProfileRoute.page, path: ':name'), + AutoRoute(page: PublisherProfileRoute.page, path: ':name/calendar'), + AutoRoute(page: MyselfEventCalendarRoute.page, path: 'me/calendar'), + AutoRoute(page: UpdateProfileRoute.page, path: 'me/update'), + AutoRoute(page: ManagedPublisherRoute.page, path: 'me/publishers'), + ], + ), + AutoRoute(page: RealmListRoute.page, path: '/realms'), + AutoRoute( + page: ChatShellRoute.page, + path: '/chat', + children: [ + AutoRoute(page: ChatListRoute.page, path: ''), + AutoRoute(page: ChatRoomRoute.page, path: ':id'), + AutoRoute(page: NewChatRoute.page, path: 'new'), + AutoRoute(page: EditChatRoute.page, path: ':id/edit'), + AutoRoute(page: ChatDetailRoute.page, path: ':id/detail'), ], ), - AutoRoute(page: WalletRoute.page, path: '/wallet'), - AutoRoute(page: RelationshipRoute.page, path: '/relationships'), AutoRoute(page: SettingsRoute.page, path: '/settings'), AutoRoute(page: LoginRoute.page, path: '/auth/login'), AutoRoute(page: CreateAccountRoute.page, path: '/auth/create-account'), AutoRoute(page: AccountSettingsRoute.page, path: '/account/settings'), - AutoRoute( - page: MyselfEventCalendarRoute.page, - path: '/account/me/calendar', - ), - AutoRoute(page: UpdateProfileRoute.page, path: '/account/me/update'), - AutoRoute(page: ManagedPublisherRoute.page, path: '/account/me/publishers'), - AutoRoute(page: NewPublisherRoute.page, path: '/account/me/publishers/new'), - AutoRoute( - page: EditPublisherRoute.page, - path: '/account/me/publishers/:id/edit', - ), - AutoRoute(page: AccountProfileRoute.page, path: '/account/:name'), - AutoRoute( - page: PublisherProfileRoute.page, - path: '/account/:name/calendar', - ), AutoRoute(page: PostComposeRoute.page, path: '/posts/compose'), AutoRoute(page: PostDetailRoute.page, path: '/posts/:id'), AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'), diff --git a/lib/route.gr.dart b/lib/route.gr.dart index 12e3db2..ca9c99f 100644 --- a/lib/route.gr.dart +++ b/lib/route.gr.dart @@ -10,8 +10,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_route/auto_route.dart' as _i25; +import 'package:flutter/foundation.dart' as _i27; import 'package:flutter/material.dart' as _i26; -import 'package:island/models/post.dart' as _i27; +import 'package:island/models/post.dart' as _i28; +import 'package:island/route.dart' as _i29; import 'package:island/screens/account.dart' as _i2; import 'package:island/screens/account/me/event_calendar.dart' as _i15; import 'package:island/screens/account/me/publishers.dart' as _i9; @@ -81,20 +83,43 @@ class AccountProfileRouteArgs { /// generated route for /// [_i2.AccountScreen] -class AccountRoute extends _i25.PageRouteInfo { - const AccountRoute({List<_i25.PageRouteInfo>? children}) - : super(AccountRoute.name, initialChildren: children); +class AccountRoute extends _i25.PageRouteInfo { + AccountRoute({ + _i27.Key? key, + bool isAside = false, + List<_i25.PageRouteInfo>? children, + }) : super( + AccountRoute.name, + args: AccountRouteArgs(key: key, isAside: isAside), + initialChildren: children, + ); static const String name = 'AccountRoute'; static _i25.PageInfo page = _i25.PageInfo( name, builder: (data) { - return const _i2.AccountScreen(); + final args = data.argsAs( + orElse: () => const AccountRouteArgs(), + ); + return _i2.AccountScreen(key: args.key, isAside: args.isAside); }, ); } +class AccountRouteArgs { + const AccountRouteArgs({this.key, this.isAside = false}); + + final _i27.Key? key; + + final bool isAside; + + @override + String toString() { + return 'AccountRouteArgs{key: $key, isAside: $isAside}'; + } +} + /// generated route for /// [_i3.AccountSettingsScreen] class AccountSettingsRoute extends _i25.PageRouteInfo { @@ -111,6 +136,22 @@ class AccountSettingsRoute extends _i25.PageRouteInfo { ); } +/// generated route for +/// [_i2.AccountShellScreen] +class AccountShellRoute extends _i25.PageRouteInfo { + const AccountShellRoute({List<_i25.PageRouteInfo>? children}) + : super(AccountShellRoute.name, initialChildren: children); + + static const String name = 'AccountShellRoute'; + + static _i25.PageInfo page = _i25.PageInfo( + name, + builder: (data) { + return const _i2.AccountShellScreen(); + }, + ); +} + /// generated route for /// [_i4.ChatDetailScreen] class ChatDetailRoute extends _i25.PageRouteInfo { @@ -154,20 +195,43 @@ class ChatDetailRouteArgs { /// generated route for /// [_i5.ChatListScreen] -class ChatListRoute extends _i25.PageRouteInfo { - const ChatListRoute({List<_i25.PageRouteInfo>? children}) - : super(ChatListRoute.name, initialChildren: children); +class ChatListRoute extends _i25.PageRouteInfo { + ChatListRoute({ + _i26.Key? key, + bool isAside = false, + List<_i25.PageRouteInfo>? children, + }) : super( + ChatListRoute.name, + args: ChatListRouteArgs(key: key, isAside: isAside), + initialChildren: children, + ); static const String name = 'ChatListRoute'; static _i25.PageInfo page = _i25.PageInfo( name, builder: (data) { - return const _i5.ChatListScreen(); + final args = data.argsAs( + orElse: () => const ChatListRouteArgs(), + ); + return _i5.ChatListScreen(key: args.key, isAside: args.isAside); }, ); } +class ChatListRouteArgs { + const ChatListRouteArgs({this.key, this.isAside = false}); + + final _i26.Key? key; + + final bool isAside; + + @override + String toString() { + return 'ChatListRouteArgs{key: $key, isAside: $isAside}'; + } +} + /// generated route for /// [_i6.ChatRoomScreen] class ChatRoomRoute extends _i25.PageRouteInfo { @@ -697,7 +761,7 @@ class NewStickersRouteArgs { class PostComposeRoute extends _i25.PageRouteInfo { PostComposeRoute({ _i26.Key? key, - _i27.SnPost? originalPost, + _i28.SnPost? originalPost, List<_i25.PageRouteInfo>? children, }) : super( PostComposeRoute.name, @@ -726,7 +790,7 @@ class PostComposeRouteArgs { final _i26.Key? key; - final _i27.SnPost? originalPost; + final _i28.SnPost? originalPost; @override String toString() { @@ -1047,21 +1111,54 @@ class StickersRouteArgs { } /// generated route for -/// [_i22.TabsScreen] -class TabsRoute extends _i25.PageRouteInfo { - const TabsRoute({List<_i25.PageRouteInfo>? children}) - : super(TabsRoute.name, initialChildren: children); +/// [_i22.TabsNavigationWidget] +class TabsNavigationWidget + extends _i25.PageRouteInfo { + TabsNavigationWidget({ + _i26.Key? key, + required _i26.Widget child, + required _i29.AppRouter router, + List<_i25.PageRouteInfo>? children, + }) : super( + TabsNavigationWidget.name, + args: TabsNavigationWidgetArgs(key: key, child: child, router: router), + initialChildren: children, + ); - static const String name = 'TabsRoute'; + static const String name = 'TabsNavigationWidget'; static _i25.PageInfo page = _i25.PageInfo( name, builder: (data) { - return const _i22.TabsScreen(); + final args = data.argsAs(); + return _i22.TabsNavigationWidget( + key: args.key, + child: args.child, + router: args.router, + ); }, ); } +class TabsNavigationWidgetArgs { + const TabsNavigationWidgetArgs({ + this.key, + required this.child, + required this.router, + }); + + final _i26.Key? key; + + final _i26.Widget child; + + final _i29.AppRouter router; + + @override + String toString() { + return 'TabsNavigationWidgetArgs{key: $key, child: $child, router: $router}'; + } +} + /// generated route for /// [_i23.UpdateProfileScreen] class UpdateProfileRoute extends _i25.PageRouteInfo { diff --git a/lib/screens/account.dart b/lib/screens/account.dart index fc3436b..29a743a 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -9,6 +9,7 @@ import 'package:island/pods/message.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/userinfo.dart'; import 'package:island/route.gr.dart'; +import 'package:island/services/responsive.dart'; import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/account/leveling_progress.dart'; import 'package:island/widgets/app_scaffold.dart'; @@ -17,11 +18,39 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @RoutePage() -class AccountScreen extends HookConsumerWidget { - const AccountScreen({super.key}); +class AccountShellScreen extends HookConsumerWidget { + const AccountShellScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final isWide = isWideScreen(context); + + if (isWide) { + return Row( + children: [ + SizedBox(width: 360, child: AccountScreen(isAside: true)), + VerticalDivider(width: 1), + Expanded(child: AutoRouter()), + ], + ); + } + + return AutoRouter(); + } +} + +@RoutePage() +class AccountScreen extends HookConsumerWidget { + final bool isAside; + const AccountScreen({super.key, this.isAside = false}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isWide = isWideScreen(context); + if (isWide && !isAside) { + return Container(color: Theme.of(context).scaffoldBackgroundColor); + } + final user = ref.watch(userInfoProvider); if (!user.hasValue || user.value == null) { diff --git a/lib/screens/auth/tabs.dart b/lib/screens/auth/tabs.dart index 31c5ca0..d700f32 100644 --- a/lib/screens/auth/tabs.dart +++ b/lib/screens/auth/tabs.dart @@ -2,18 +2,53 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/route.dart'; import 'package:island/route.gr.dart'; import 'package:island/services/responsive.dart'; import 'package:material_symbols_icons/symbols.dart'; -@RoutePage() -class TabsScreen extends StatelessWidget { - const TabsScreen({super.key}); +final currentRouteProvider = StateProvider((ref) => null); + +class TabNavigationObserver extends AutoRouterObserver { + Function(String?) onChange; + TabNavigationObserver({required this.onChange}); @override - Widget build(BuildContext context) { + void didPush(Route route, Route? previousRoute) { + Future(() { + print('didPush: ${route.settings.name}'); + onChange(route.settings.name); + }); + } + + @override + void didPop(Route route, Route? previousRoute) { + Future(() { + print('didPop: ${previousRoute?.settings.name}'); + onChange(previousRoute?.settings.name); + }); + } +} + +@RoutePage() +class TabsNavigationWidget extends HookConsumerWidget { + final Widget child; + final AppRouter router; + const TabsNavigationWidget({ + super.key, + required this.child, + required this.router, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { final useHorizontalLayout = isWideScreen(context); final useExpandableLayout = isWidestScreen(context); + final currentRoute = ref.watch(currentRouteProvider); + print('currentRoute: $currentRoute'); + + int activeIndex = 0; final destinations = [ NavigationDestination( @@ -31,90 +66,93 @@ class TabsScreen extends StatelessWidget { ), ]; - final routes = [ + final routes = [ ExploreRoute(), ChatListRoute(), RealmListRoute(), AccountRoute(), ]; + final routeNames = [ + ExploreRoute.name, + ChatListRoute.name, + RealmListRoute.name, + AccountRoute.name, + ChatShellRoute.name, + AccountShellRoute.name, + ]; - return AutoTabsRouter.tabBar( - routes: routes, - scrollDirection: useHorizontalLayout ? Axis.vertical : Axis.horizontal, - physics: const NeverScrollableScrollPhysics(), - builder: (context, child, _) { - final tabsRouter = AutoTabsRouter.of(context); + activeIndex = routes.indexWhere((route) => route.routeName == currentRoute); + if (activeIndex == -1) { + activeIndex = 0; + } - // Check if current route is a tab route - final currentRoute = context.router.topRoute; - final isTabRoute = routes.any( - (route) => route.routeName == currentRoute.name, - ); + final isTabRoute = routeNames.any((route) { + return route == currentRoute; + }); - return Scaffold( - extendBodyBehindAppBar: true, - backgroundColor: Colors.transparent, - body: - useHorizontalLayout - ? Row( + return Scaffold( + extendBodyBehindAppBar: true, + backgroundColor: Colors.transparent, + body: + useHorizontalLayout + ? Row( + children: [ + Column( children: [ - if (isTabRoute) - Column( - children: [ - Gap(MediaQuery.of(context).padding.top + 8), - if (useExpandableLayout) - Expanded( - child: NavigationDrawer( - backgroundColor: Colors.transparent, - children: [ - for (final destination in destinations) - NavigationDrawerDestination( - label: Text(destination.label), - icon: destination.icon, + Gap(MediaQuery.of(context).padding.top + 8), + if (useExpandableLayout) + Expanded( + child: NavigationDrawer( + backgroundColor: Colors.transparent, + children: [ + for (final destination in destinations) + NavigationDrawerDestination( + label: Text(destination.label), + icon: destination.icon, + ), + ], + ), + ) + else + Expanded( + child: NavigationRail( + selectedIndex: activeIndex, + onDestinationSelected: (index) { + router.replace(routes[index]); + }, + labelType: NavigationRailLabelType.all, + destinations: + destinations + .map( + (d) => NavigationRailDestination( + icon: d.icon, + label: Text(d.label), ), - ], - ), - ) - else - Expanded( - child: NavigationRail( - selectedIndex: tabsRouter.activeIndex, - onDestinationSelected: - tabsRouter.setActiveIndex, - labelType: NavigationRailLabelType.all, - destinations: - destinations - .map( - (d) => NavigationRailDestination( - icon: d.icon, - label: Text(d.label), - ), - ) - .toList(), - ), - ), - Gap(MediaQuery.of(context).padding.bottom + 8), - ], + ) + .toList(), + ), ), - if (isTabRoute) - VerticalDivider( - color: Theme.of(context).dividerColor, - width: 1 / MediaQuery.of(context).devicePixelRatio, - ), - Expanded(child: child), + Gap(MediaQuery.of(context).padding.bottom + 8), ], - ) - : child, - bottomNavigationBar: - !useHorizontalLayout && isTabRoute - ? NavigationBar( - selectedIndex: tabsRouter.activeIndex, - onDestinationSelected: tabsRouter.setActiveIndex, - destinations: destinations, - ) - : null, - ); - }, + ), + VerticalDivider( + color: Theme.of(context).dividerColor, + width: 1 / MediaQuery.of(context).devicePixelRatio, + ), + Expanded(child: child), + ], + ) + : child, + bottomNavigationBar: + !useHorizontalLayout && isTabRoute + ? NavigationBar( + selectedIndex: activeIndex, + onDestinationSelected: (index) { + router.replace(routes[index]); + }, + destinations: destinations, + ) + : null, ); } } diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart index db73045..7c4a4fe 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/screens/chat/chat.dart @@ -115,6 +115,11 @@ class ChatListScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final isWide = isWideScreen(context); + if (isWide && !isAside) { + return Container(color: Theme.of(context).scaffoldBackgroundColor); + } + final chats = ref.watch(chatroomsJoinedProvider); final chatInvites = ref.watch(chatroomInvitesProvider); @@ -133,12 +138,6 @@ class ChatListScreen extends HookConsumerWidget { } } - final isWide = isWideScreen(context); - - if (isWide && !isAside) { - return SizedBox.shrink(); - } - return AppScaffold( appBar: AppBar( title: Text('chat').tr(), @@ -222,7 +221,7 @@ class ChatListScreen extends HookConsumerWidget { room: item, isDirect: item.type == 1, onTap: () { - if (isWide) { + if (context.router.topRoute.name == ChatRoomRoute.name) { context.router.replace(ChatRoomRoute(id: item.id)); } else { context.router.push(ChatRoomRoute(id: item.id));