import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../features/account/account_auth_page.dart'; import '../features/account/account_center_page.dart'; import '../features/aggregated/aggregated_view_page.dart'; import '../features/calendar/trakt_calendar_page.dart'; import '../features/detail/detail_entry.dart'; import '../features/detail/emby_detail_view.dart'; import '../features/detail/tmdb_detail_view.dart'; import '../features/discover/discover_page.dart'; import '../features/home/home_page.dart'; import '../features/library/library_list_page.dart'; import '../features/modules/module_all_page.dart'; import '../features/modules/module_browse_page.dart'; import '../features/modules/module_detail_page.dart'; import '../features/person_detail/person_detail_page.dart'; import '../features/player/player_launcher.dart'; import '../features/player/player_page.dart'; import '../features/profile/profile_page.dart'; import '../features/search/global_search_page.dart'; import '../features/servers/servers_page.dart'; import '../features/settings/settings_full_page.dart'; import '../shared/constants/breakpoints.dart'; import '../core/contracts/library.dart'; import '../core/contracts/script_widget.dart'; import '../core/contracts/tmdb.dart'; import '../providers/appearance_provider.dart'; import '../shared/theme/app_theme.dart'; import '../shared/widgets/app_error_state.dart'; import '../shared/widgets/app_shell.dart'; import '../shared/widgets/window_chrome_overlay.dart'; import 'page_transitions.dart'; final rootNavigatorKey = GlobalKey(debugLabel: 'root'); final _homeBranchKey = GlobalKey(debugLabel: 'home'); final _serversBranchKey = GlobalKey(debugLabel: 'servers'); final _aggregatedBranchKey = GlobalKey( debugLabel: 'aggregated', ); final _discoverBranchKey = GlobalKey(debugLabel: 'discover'); final _calendarBranchKey = GlobalKey(debugLabel: 'calendar'); final _settingsBranchKey = GlobalKey(debugLabel: 'settings'); final _modulesBranchKey = GlobalKey(debugLabel: 'modules'); final appRouterProvider = Provider((ref) { return GoRouter( navigatorKey: rootNavigatorKey, initialLocation: ref.watch(initialLocationProvider), routes: [ StatefulShellRoute.indexedStack( builder: (ctx, state, navigationShell) { final parentTheme = Theme.of(ctx); final materialTheme = parentTheme.brightness == Brightness.dark ? AppTheme.dark() : AppTheme.light(); return Theme( data: materialTheme.copyWith(platform: parentTheme.platform), child: AppShell( navigationShell: navigationShell, location: state.matchedLocation, ), ); }, branches: [ StatefulShellBranch( navigatorKey: _homeBranchKey, routes: [ GoRoute( path: '/', pageBuilder: (_, _) => const CustomTransitionPage( child: HomePage(), transitionsBuilder: PageTransitions.fade, transitionDuration: PageTransitions.shellFade, reverseTransitionDuration: PageTransitions.shellFade, ), ), GoRoute( path: '/library/:libraryId', pageBuilder: (_, state) => CustomTransitionPage( child: LibraryListPage( libraryId: state.pathParameters['libraryId']!, title: state.uri.queryParameters['title'], isCollection: state.uri.queryParameters['collection'] == '1', ), transitionsBuilder: PageTransitions.fade, transitionDuration: PageTransitions.shellFade, reverseTransitionDuration: PageTransitions.shellFade, ), ), ], ), StatefulShellBranch( navigatorKey: _serversBranchKey, routes: [ GoRoute( path: '/servers', pageBuilder: (_, _) => const CustomTransitionPage( child: ServersPage(), transitionsBuilder: PageTransitions.fade, transitionDuration: PageTransitions.shellFade, reverseTransitionDuration: PageTransitions.shellFade, ), ), ], ), StatefulShellBranch( navigatorKey: _aggregatedBranchKey, routes: [ GoRoute( path: '/aggregated', pageBuilder: (_, state) { final tab = state.uri.queryParameters['tab']; return CustomTransitionPage( child: LayoutBuilder( builder: (_, constraints) => AggregatedViewPage( initialTab: tab, isCompactMode: Breakpoints.isCompact( constraints.maxWidth, ), ), ), transitionsBuilder: PageTransitions.fade, transitionDuration: PageTransitions.shellFade, reverseTransitionDuration: PageTransitions.shellFade, ); }, ), GoRoute( path: '/history', redirect: (_, _) => '/aggregated?tab=history', ), GoRoute( path: '/favorites-all', redirect: (_, _) => '/aggregated?tab=favorites', ), GoRoute( path: '/recent', redirect: (_, _) => '/aggregated?tab=recent', ), ], ), StatefulShellBranch( navigatorKey: _discoverBranchKey, routes: [ GoRoute( path: '/discover', pageBuilder: (_, _) => const CustomTransitionPage( child: DiscoverPage(), transitionsBuilder: PageTransitions.fade, transitionDuration: PageTransitions.shellFade, reverseTransitionDuration: PageTransitions.shellFade, ), ), ], ), StatefulShellBranch( navigatorKey: _calendarBranchKey, routes: [ GoRoute( path: '/calendar', pageBuilder: (_, _) => const CustomTransitionPage( child: TraktCalendarPage(), transitionsBuilder: PageTransitions.fade, transitionDuration: PageTransitions.shellFade, reverseTransitionDuration: PageTransitions.shellFade, ), ), ], ), StatefulShellBranch( navigatorKey: _settingsBranchKey, routes: [ GoRoute( path: '/settings', pageBuilder: (_, _) => CustomTransitionPage( child: LayoutBuilder( builder: (_, constraints) => Breakpoints.isCompact(constraints.maxWidth) ? const SettingsFullPage() : const ProfilePage(), ), transitionsBuilder: PageTransitions.fade, transitionDuration: PageTransitions.shellFade, reverseTransitionDuration: PageTransitions.shellFade, ), ), GoRoute(path: '/profile', redirect: (_, _) => '/settings'), ], ), StatefulShellBranch( navigatorKey: _modulesBranchKey, routes: [ GoRoute(path: '/modules', redirect: (_, _) => '/'), GoRoute( path: '/modules/:widgetId', pageBuilder: (_, state) => CustomTransitionPage( key: state.pageKey, child: ModuleBrowsePage( widgetId: state.pathParameters['widgetId'] ?? '', ), transitionsBuilder: PageTransitions.fade, transitionDuration: PageTransitions.shellFade, reverseTransitionDuration: PageTransitions.shellFade, ), ), GoRoute( path: '/modules/:widgetId/module/:moduleKey', pageBuilder: (_, state) => CustomTransitionPage( key: state.pageKey, child: ModuleAllPage( widgetId: state.pathParameters['widgetId'] ?? '', moduleKey: state.pathParameters['moduleKey'] ?? '', initialParams: Map.from( state.uri.queryParameters, ), ), transitionsBuilder: PageTransitions.fade, transitionDuration: PageTransitions.shellFade, reverseTransitionDuration: PageTransitions.shellFade, ), ), ], ), ], ), GoRoute( path: '/global-search', parentNavigatorKey: rootNavigatorKey, pageBuilder: (_, state) { final currentOnly = state.uri.queryParameters['currentServer'] == 'true'; return CustomTransitionPage( child: WindowChromeHost( child: GlobalSearchPage(currentServerOnly: currentOnly), ), transitionsBuilder: PageTransitions.fade, transitionDuration: PageTransitions.shellFade, reverseTransitionDuration: PageTransitions.shellFade, ); }, ), GoRoute( path: '/media/:itemId', parentNavigatorKey: rootNavigatorKey, pageBuilder: (_, state) => CustomTransitionPage( key: state.pageKey, child: EmbyDetailView( itemId: state.pathParameters['itemId']!, initialSeriesId: state.uri.queryParameters['seriesId'], initialSeasonId: state.uri.queryParameters['seasonId'], seedItem: state.extra is EmbyRawItem ? state.extra as EmbyRawItem : null, ), transitionsBuilder: PageTransitions.slideRight, transitionDuration: PageTransitions.detailForward, reverseTransitionDuration: PageTransitions.detailReverse, ), ), GoRoute( path: '/tmdb/:mediaType/:id', parentNavigatorKey: rootNavigatorKey, pageBuilder: (_, state) { final extra = state.extra; return CustomTransitionPage( key: state.pageKey, child: TmdbDetailView( entry: TmdbDetailEntry( mediaType: state.pathParameters['mediaType']!, tmdbId: state.pathParameters['id']!, seed: extra is TmdbRecommendation ? extra : null, ), ), transitionsBuilder: PageTransitions.slideRight, transitionDuration: PageTransitions.detailForward, reverseTransitionDuration: PageTransitions.detailReverse, ); }, ), GoRoute( path: '/person/:embyPersonId', parentNavigatorKey: rootNavigatorKey, pageBuilder: (_, state) { final embyPersonId = state.pathParameters['embyPersonId'] ?? ''; return CustomTransitionPage( child: WindowChromeHost( child: PersonDetailPage(embyPersonId: embyPersonId), ), transitionsBuilder: PageTransitions.slideRight, transitionDuration: PageTransitions.detailForward, reverseTransitionDuration: PageTransitions.detailReverse, ); }, ), GoRoute( path: '/account/auth', parentNavigatorKey: rootNavigatorKey, pageBuilder: (_, _) => const CustomTransitionPage( child: WindowChromeHost(child: AccountAuthPage()), transitionsBuilder: PageTransitions.slideRight, transitionDuration: PageTransitions.detailForward, reverseTransitionDuration: PageTransitions.detailReverse, ), ), GoRoute( path: '/account-center', parentNavigatorKey: rootNavigatorKey, pageBuilder: (_, _) => const CustomTransitionPage( child: WindowChromeHost(child: AccountCenterPage()), transitionsBuilder: PageTransitions.slideRight, transitionDuration: PageTransitions.detailForward, reverseTransitionDuration: PageTransitions.detailReverse, ), ), GoRoute( path: '/modules/:widgetId/detail', parentNavigatorKey: rootNavigatorKey, pageBuilder: (_, state) { final seedItem = state.extra; return CustomTransitionPage( key: state.pageKey, child: seedItem is ScriptVideoItem ? ModuleDetailPage( widgetId: state.pathParameters['widgetId'] ?? '', seedItem: seedItem, ) : const Scaffold( body: AppErrorState( title: '缺少媒体信息', message: '请从模块内容列表重新打开该条目。', icon: Icons.link_off_rounded, ), ), transitionsBuilder: PageTransitions.slideRight, transitionDuration: PageTransitions.detailForward, reverseTransitionDuration: PageTransitions.detailReverse, ); }, ), GoRoute( path: '/player/:itemId', parentNavigatorKey: rootNavigatorKey, pageBuilder: (_, state) { final q = state.uri.queryParameters; return CustomTransitionPage( transitionsBuilder: PageTransitions.player, transitionDuration: PageTransitions.playerForward, reverseTransitionDuration: PageTransitions.playerReverse, child: PlayerPage( itemId: state.pathParameters['itemId']!, serverId: q['serverId'], mediaSourceId: q['mediaSourceId'], backdropUrl: q['backdropUrl'], preferredSubtitleStreamIndex: int.tryParse( q['subtitleIndex'] ?? '', ), preferredAudioStreamIndex: int.tryParse(q['audioIndex'] ?? ''), startPositionTicks: int.tryParse(q['startPositionTicks'] ?? ''), fromStart: q['fromStart'] == '1', directStream: state.extra is DirectStreamLaunchData ? state.extra as DirectStreamLaunchData : null, ), ); }, ), ], errorBuilder: (_, state) => Scaffold( body: AppErrorState( title: '页面不存在', message: state.matchedLocation, icon: Icons.search_off_outlined, ), ), ); });