296 lines
11 KiB
Dart
296 lines
11 KiB
Dart
// Author: fruit.com.cn
|
||
|
||
import 'dart:async';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:provider/provider.dart';
|
||
|
||
import 'core/config/app_locale.dart';
|
||
import 'core/data/remote/account_api.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/commerce_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/notice_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/account_repository.dart';
|
||
import 'core/data/repositories/category_repository.dart';
|
||
import 'core/data/repositories/comment_repository.dart';
|
||
import 'core/data/repositories/commerce_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/notice_repository.dart';
|
||
import 'core/data/repositories/playback_repository.dart';
|
||
import 'core/data/repositories/search_repository.dart';
|
||
import 'core/data/repositories/store_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/locale_controller.dart';
|
||
import 'core/data/services/event_tracker.dart';
|
||
import 'core/data/services/playback_position_cache.dart';
|
||
import 'core/data/services/playback_session.dart';
|
||
import 'core/data/services/video_player_port.dart';
|
||
import 'core/data/services/wallet_controller.dart';
|
||
import 'core/navigation/app_route_observer.dart';
|
||
import 'core/platform/haptics.dart';
|
||
import 'core/theme/app_theme.dart';
|
||
import 'core/theme/app_colors.dart';
|
||
import 'l10n/app_localizations.dart';
|
||
import 'pages/home/home_view.dart';
|
||
|
||
/// 全局系统栏样式:浅暖导航栏、关闭 Android 对比遮罩。
|
||
final SystemUiOverlayStyle appSystemOverlayStyle = SystemUiOverlayStyle.dark
|
||
.copyWith(
|
||
statusBarColor: Colors.transparent,
|
||
systemNavigationBarColor: AppColors.ink,
|
||
systemNavigationBarDividerColor: Colors.transparent,
|
||
systemNavigationBarIconBrightness: Brightness.dark,
|
||
systemNavigationBarContrastEnforced: false,
|
||
);
|
||
|
||
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.playbackPositionCache,
|
||
this.commentRepository,
|
||
this.interactionRepository,
|
||
this.noticeRepository,
|
||
this.accountRepository,
|
||
this.commerceRepository,
|
||
this.storeRepository,
|
||
this.searchRepository,
|
||
this.haptics = const FlutterHaptics(),
|
||
});
|
||
|
||
final PlayerFactory playerFactory;
|
||
|
||
/// 测试注入用;生产走 HTTP 仓库。
|
||
final CatalogRepository? catalogRepository;
|
||
final CategoryRepository? categoryRepository;
|
||
final DownloadRepository? downloadRepository;
|
||
final WatchHistoryRepository? watchHistoryRepository;
|
||
final FavoriteRepository? favoriteRepository;
|
||
final PlaybackRepository? playbackRepository;
|
||
final PlaybackPositionCache? playbackPositionCache;
|
||
final CommentRepository? commentRepository;
|
||
final InteractionRepository? interactionRepository;
|
||
final NoticeRepository? noticeRepository;
|
||
final AccountRepository? accountRepository;
|
||
final CommerceRepository? commerceRepository;
|
||
final StoreRepository? storeRepository;
|
||
final SearchRepository? searchRepository;
|
||
final Haptics haptics;
|
||
|
||
@override
|
||
State<LumaDramaApp> createState() => _LumaDramaAppState();
|
||
}
|
||
|
||
class _LumaDramaAppState extends State<LumaDramaApp> {
|
||
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 PlaybackPositionCache _playbackPositionCache =
|
||
widget.playbackPositionCache ?? PlaybackPositionCache();
|
||
|
||
late final FavoriteController _favoriteController = FavoriteController(
|
||
_favorites,
|
||
);
|
||
|
||
late final DownloadRepository _downloadRepository =
|
||
widget.downloadRepository ??
|
||
HttpDownloadRepository(DownloadApi(_apiClient));
|
||
|
||
late final DownloadController _downloadController = DownloadController(
|
||
_downloadRepository,
|
||
);
|
||
|
||
late final CommerceRepository _commerceRepository =
|
||
widget.commerceRepository ??
|
||
HttpCommerceRepository(CommerceApi(_apiClient));
|
||
|
||
late final WalletController _walletController = WalletController(
|
||
_commerceRepository,
|
||
);
|
||
|
||
late final SearchRepository _searchRepository =
|
||
widget.searchRepository ?? HttpSearchRepository(CatalogApi(_apiClient));
|
||
|
||
late final StoreRepository _storeRepository =
|
||
widget.storeRepository ?? HttpStoreRepository(CommerceApi(_apiClient));
|
||
|
||
late final CommentRepository _commentRepository =
|
||
widget.commentRepository ?? HttpCommentRepository(CommentApi(_apiClient));
|
||
|
||
late final InteractionRepository _interactionRepository =
|
||
widget.interactionRepository ??
|
||
HttpInteractionRepository(InteractionApi(_apiClient));
|
||
|
||
late final NoticeRepository _noticeRepository =
|
||
widget.noticeRepository ?? HttpNoticeRepository(NoticeApi(_apiClient));
|
||
|
||
late final AccountRepository _accountRepository =
|
||
widget.accountRepository ?? HttpAccountRepository(AccountApi(_apiClient));
|
||
|
||
late final InteractionController _interactionController =
|
||
InteractionController(_interactionRepository);
|
||
|
||
late final LocaleController _localeController = LocaleController();
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
// 恢复本地令牌与设备号;未登录也能正常浏览,不阻塞首屏
|
||
_auth.load();
|
||
unawaited(_localeController.load());
|
||
// 登录/登出时同步收藏状态:收藏属于账号,不能跨账号串联
|
||
_auth.addListener(_onAuthChanged);
|
||
if (_auth.isSignedIn) unawaited(_favoriteController.load());
|
||
if (_auth.isSignedIn) unawaited(_walletController.load());
|
||
unawaited(_downloadController.restore());
|
||
}
|
||
|
||
bool _wasSignedIn = false;
|
||
|
||
void _onAuthChanged() {
|
||
if (_auth.isSignedIn == _wasSignedIn) return;
|
||
_wasSignedIn = _auth.isSignedIn;
|
||
_interactionController.clear();
|
||
if (_auth.isSignedIn) {
|
||
unawaited(_favoriteController.load());
|
||
unawaited(_walletController.load());
|
||
} else {
|
||
_favoriteController.clear();
|
||
_downloadController.reset();
|
||
_walletController.reset();
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_auth.removeListener(_onAuthChanged);
|
||
_favoriteController.dispose();
|
||
_downloadController.dispose();
|
||
_walletController.dispose();
|
||
_interactionController.dispose();
|
||
_localeController.dispose();
|
||
_apiClient.close();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return MultiProvider(
|
||
providers: [
|
||
Provider(create: (_) => PlaybackCoordinator()),
|
||
Provider<EventTracker>(create: (_) => EventTracker(_apiClient)),
|
||
Provider<PlayerFactory>(create: (_) => widget.playerFactory),
|
||
Provider<CatalogRepository>.value(value: _catalogRepository),
|
||
Provider<PlaybackRepository>.value(value: _playback),
|
||
Provider<PlaybackPositionCache>.value(value: _playbackPositionCache),
|
||
Provider<WatchHistoryRepository>.value(value: _watchHistory),
|
||
Provider<FavoriteRepository>.value(value: _favorites),
|
||
Provider<CommentRepository>.value(value: _commentRepository),
|
||
Provider<InteractionRepository>.value(value: _interactionRepository),
|
||
Provider<NoticeRepository>.value(value: _noticeRepository),
|
||
Provider<AccountRepository>.value(value: _accountRepository),
|
||
Provider<CommerceRepository>.value(value: _commerceRepository),
|
||
Provider<StoreRepository>.value(value: _storeRepository),
|
||
Provider<SearchRepository>.value(value: _searchRepository),
|
||
Provider<Haptics>.value(value: widget.haptics),
|
||
ChangeNotifierProvider(
|
||
create: (_) => CatalogController(_catalogRepository)..load(),
|
||
),
|
||
ChangeNotifierProvider(
|
||
create: (_) => CategoryController(_categoryRepository)..load(),
|
||
),
|
||
ChangeNotifierProvider.value(value: _favoriteController),
|
||
ChangeNotifierProvider.value(value: _downloadController),
|
||
ChangeNotifierProvider.value(value: _walletController),
|
||
ChangeNotifierProvider.value(value: _interactionController),
|
||
ChangeNotifierProvider.value(value: _localeController),
|
||
ChangeNotifierProvider.value(value: _auth),
|
||
],
|
||
child: Consumer<LocaleController>(
|
||
builder: (context, localeController, _) => MaterialApp(
|
||
locale: localeController.locale,
|
||
onGenerateTitle: (context) => AppLocalizations.of(context)!.appName,
|
||
supportedLocales: AppLocalizations.supportedLocales,
|
||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||
navigatorObservers: [appRouteObserver],
|
||
localeResolutionCallback: (deviceLocale, _) =>
|
||
resolveAppLocale(deviceLocale),
|
||
debugShowCheckedModeBanner: false,
|
||
themeMode: ThemeMode.light,
|
||
theme: AppTheme.light(),
|
||
darkTheme: AppTheme.light(),
|
||
builder: (context, child) => AnnotatedRegion<SystemUiOverlayStyle>(
|
||
value: appSystemOverlayStyle,
|
||
child: child ?? const SizedBox.shrink(),
|
||
),
|
||
home: const HomePage(),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|