Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
+380
View File
@@ -0,0 +1,380 @@
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,
),
),
);
});