381 lines
15 KiB
Dart
381 lines
15 KiB
Dart
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<NavigatorState>(debugLabel: 'root');
|
|
final _homeBranchKey = GlobalKey<NavigatorState>(debugLabel: 'home');
|
|
final _serversBranchKey = GlobalKey<NavigatorState>(debugLabel: 'servers');
|
|
final _aggregatedBranchKey = GlobalKey<NavigatorState>(
|
|
debugLabel: 'aggregated',
|
|
);
|
|
final _discoverBranchKey = GlobalKey<NavigatorState>(debugLabel: 'discover');
|
|
final _calendarBranchKey = GlobalKey<NavigatorState>(debugLabel: 'calendar');
|
|
final _settingsBranchKey = GlobalKey<NavigatorState>(debugLabel: 'settings');
|
|
final _modulesBranchKey = GlobalKey<NavigatorState>(debugLabel: 'modules');
|
|
final appRouterProvider = Provider<GoRouter>((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<String, String>.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,
|
|
),
|
|
),
|
|
);
|
|
});
|