Files
ReelShort/lib/app.dart
T

279 lines
10 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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/playback_session.dart';
import 'core/data/services/video_player_port.dart';
import 'core/data/services/wallet_controller.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';
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,
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 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 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<PlayerFactory>(create: (_) => widget.playerFactory),
Provider<CatalogRepository>.value(value: _catalogRepository),
Provider<PlaybackRepository>.value(value: _playback),
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,
localeResolutionCallback: (deviceLocale, _) =>
resolveAppLocale(deviceLocale),
debugShowCheckedModeBanner: false,
themeMode: ThemeMode.light,
theme: AppTheme.light(),
darkTheme: AppTheme.light(),
builder: (context, child) => AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.transparent,
systemNavigationBarColor: AppColors.bar,
systemNavigationBarIconBrightness: Brightness.dark,
),
child: child ?? const SizedBox.shrink(),
),
home: const HomePage(),
),
),
);
}
}