// Author: fruit.com.cn import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'core/config/locale_controller.dart'; import 'core/data/remote/api_client.dart'; import 'core/data/remote/auth_api.dart'; import 'core/data/remote/catalog_api.dart'; import 'core/data/remote/category_api.dart'; import 'core/data/remote/comment_api.dart'; import 'core/data/remote/download_api.dart'; import 'core/data/remote/interaction_api.dart'; import 'core/data/remote/library_api.dart'; import 'core/data/remote/playback_api.dart'; import 'core/data/remote/watch_api.dart'; import 'core/data/repositories/catalog_repository.dart'; import 'core/data/repositories/category_repository.dart'; import 'core/data/repositories/comment_repository.dart'; import 'core/data/repositories/download_repository.dart'; import 'core/data/repositories/favorite_repository.dart'; import 'core/data/repositories/interaction_repository.dart'; import 'core/data/repositories/playback_repository.dart'; import 'core/data/repositories/watch_history_repository.dart'; import 'core/data/services/auth_controller.dart'; import 'core/data/services/auth_session_store.dart'; import 'core/data/services/catalog_controller.dart'; import 'core/data/services/category_controller.dart'; import 'core/data/services/download_controller.dart'; import 'core/data/services/favorite_controller.dart'; import 'core/data/services/interaction_controller.dart'; import 'core/data/services/playback_session.dart'; import 'core/data/services/video_player_port.dart'; import 'core/theme/app_theme.dart'; import 'l10n/app_localizations.dart'; import 'pages/home/home_view.dart'; class LumaDramaApp extends StatefulWidget { const LumaDramaApp({ super.key, this.playerFactory = VideoPlayerPort.new, this.catalogRepository, this.categoryRepository, this.downloadRepository, this.watchHistoryRepository, this.favoriteRepository, this.playbackRepository, this.commentRepository, this.interactionRepository, }); final PlayerFactory playerFactory; /// 测试注入用;生产走 HTTP 仓库。 final CatalogRepository? catalogRepository; final CategoryRepository? categoryRepository; final DownloadRepository? downloadRepository; final WatchHistoryRepository? watchHistoryRepository; final FavoriteRepository? favoriteRepository; final PlaybackRepository? playbackRepository; final CommentRepository? commentRepository; final InteractionRepository? interactionRepository; @override State createState() => _LumaDramaAppState(); } class _LumaDramaAppState extends State { late final AuthSessionStore _sessionStore = AuthSessionStore(); /// 全 App 共用一个 ApiClient:这样目录、账号、后续的互动/进度请求 /// 都自动带 `Authorization` 与 `X-Device-Id`,401 的刷新重试也只有一处实现。 late final ApiClient _apiClient = ApiClient( accessTokenReader: () => _auth.accessToken, tokenRefresher: () => _auth.refresh(), onUnauthorized: () => _auth.clear(), deviceIdReader: _sessionStore.deviceId, ); late final AuthController _auth = AuthController( AuthApi(_apiClient), _sessionStore, ); late final CatalogRepository _catalogRepository = widget.catalogRepository ?? HttpCatalogRepository(CatalogApi(_apiClient)); late final CategoryRepository _categoryRepository = widget.categoryRepository ?? HttpCategoryRepository(CategoryApi(_apiClient), _catalogRepository); late final WatchHistoryRepository _watchHistory = widget.watchHistoryRepository ?? HttpWatchHistoryRepository( WatchApi(_apiClient), isSignedIn: () => _auth.isSignedIn, ); late final FavoriteRepository _favorites = widget.favoriteRepository ?? HttpFavoriteRepository( LibraryApi(_apiClient), isSignedIn: () => _auth.isSignedIn, ); late final PlaybackRepository _playback = widget.playbackRepository ?? HttpPlaybackRepository(PlaybackApi(_apiClient)); late final FavoriteController _favoriteController = FavoriteController( _favorites, ); late final DownloadRepository _downloadRepository = widget.downloadRepository ?? HttpDownloadRepository(DownloadApi(_apiClient)); late final DownloadController _downloadController = DownloadController( _downloadRepository, ); late final CommentRepository _commentRepository = widget.commentRepository ?? HttpCommentRepository(CommentApi(_apiClient)); late final InteractionRepository _interactionRepository = widget.interactionRepository ?? HttpInteractionRepository(InteractionApi(_apiClient)); late final InteractionController _interactionController = InteractionController(_interactionRepository); @override void initState() { super.initState(); // 恢复本地令牌与设备号;未登录也能正常浏览,不阻塞首屏 _auth.load(); // 登录/登出时同步收藏状态:收藏属于账号,不能跨账号串联 _auth.addListener(_onAuthChanged); if (_auth.isSignedIn) unawaited(_favoriteController.load()); } bool _wasSignedIn = false; void _onAuthChanged() { if (_auth.isSignedIn == _wasSignedIn) return; _wasSignedIn = _auth.isSignedIn; _interactionController.clear(); if (_auth.isSignedIn) { unawaited(_favoriteController.load()); } else { _favoriteController.clear(); _downloadController.reset(); } } @override void dispose() { _auth.removeListener(_onAuthChanged); _favoriteController.dispose(); _downloadController.dispose(); _interactionController.dispose(); _apiClient.close(); super.dispose(); } @override Widget build(BuildContext context) { return MultiProvider( providers: [ Provider(create: (_) => PlaybackCoordinator()), Provider(create: (_) => widget.playerFactory), Provider.value(value: _playback), Provider.value(value: _watchHistory), Provider.value(value: _favorites), Provider.value(value: _commentRepository), Provider.value(value: _interactionRepository), ChangeNotifierProvider( create: (_) => CatalogController(_catalogRepository)..load(), ), ChangeNotifierProvider( create: (_) => CategoryController(_categoryRepository)..load(), ), ChangeNotifierProvider(create: (_) => LocaleController()..load()), ChangeNotifierProvider.value(value: _favoriteController), ChangeNotifierProvider.value(value: _downloadController), ChangeNotifierProvider.value(value: _interactionController), ChangeNotifierProvider.value(value: _auth), ], child: Consumer( builder: (context, locale, _) => MaterialApp( onGenerateTitle: (context) => AppLocalizations.of(context)!.appName, locale: locale.locale, supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: AppLocalizations.localizationsDelegates, localeResolutionCallback: (deviceLocale, _) => resolveAppLocale(locale.locale, deviceLocale), debugShowCheckedModeBanner: false, themeMode: ThemeMode.system, theme: AppTheme.light(), darkTheme: AppTheme.dark(), home: const HomePage(), ), ), ); } }