// 暖记 App 根组件 — MaterialApp + BLoC Provider 注入 // // 依赖注入结构: // MultiRepositoryProvider // ├─ ApiClient // ├─ AuthRepository // ├─ JournalRepository (IsarJournalRepository — 离线优先) // ├─ RemoteJournalRepository (供 SyncEngine 使用) // └─ ClassRepository // └─ BlocProvider // └─ MaterialApp.router import 'package:flutter/foundation.dart' show kIsWeb, kDebugMode; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart' show ListenableProvider; import 'package:shared_preferences/shared_preferences.dart'; import 'config/app_config.dart'; import 'core/theme/app_theme.dart'; import 'core/routing/app_router.dart'; import 'data/local/secure_token_store_factory.dart'; import 'data/remote/api_client.dart'; import 'data/repositories/auth_repository.dart'; import 'data/repositories/journal_repository.dart'; import 'data/repositories/isar_journal_repository.dart'; import 'data/repositories/remote_journal_repository.dart'; import 'data/repositories/class_repository.dart'; import 'data/services/sync_engine.dart'; import 'features/auth/bloc/auth_bloc.dart'; import 'features/profile/bloc/settings_bloc.dart'; /// 暖记 App — 根组件 class NuanjiApp extends StatefulWidget { const NuanjiApp({super.key}); @override State createState() => _NuanjiAppState(); } class _NuanjiAppState extends State { late final ApiClient _apiClient; late final AuthRepository _authRepository; late final JournalRepository _journalRepository; late final RemoteJournalRepository _remoteJournalRepository; late final SyncEngine _syncEngine; late final ClassRepository _classRepository; late final SettingsBloc _settingsBloc; late final AuthBloc _authBloc; bool _initialized = false; @override void initState() { super.initState(); _initApp(); } Future _initApp() async { final config = kDebugMode ? AppConfig.dev : AppConfig.fromEnvironment(); _apiClient = ApiClient(baseUrl: config.apiBaseUrl); final tokenStore = createSecureTokenStore(); _authRepository = AuthRepository(apiClient: _apiClient, tokenStore: tokenStore); _journalRepository = kIsWeb ? RemoteJournalRepository(api: _apiClient) : IsarJournalRepository(); _remoteJournalRepository = RemoteJournalRepository(api: _apiClient); _syncEngine = SyncEngine(apiClient: _apiClient); _classRepository = ClassRepository(api: _apiClient); _settingsBloc = SettingsBloc( prefs: await SharedPreferences.getInstance(), ); _authBloc = AuthBloc( authRepository: _authRepository, classRepository: _classRepository, ); // 启动时检查认证状态 _authBloc.add(const AppStarted()); // 异步恢复 SyncEngine 持久化队列 _syncEngine.restorePendingQueue(); _syncEngine.startAutoSync(); // 认证状态监听:登出时清除 token _authBloc.stream.listen((state) { if (state is! Authenticated) { _apiClient.clearToken(); } }); // Token 刷新彻底失败时 → 派发 AuthExpired _apiClient.onAuthFailed = () { _authBloc.add(const AuthExpired()); }; setState(() => _initialized = true); } @override Widget build(BuildContext context) { if (!_initialized) { return const MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold(body: Center(child: CircularProgressIndicator())), ); } return MultiRepositoryProvider( providers: [ RepositoryProvider.value(value: _apiClient), RepositoryProvider.value(value: _authRepository), RepositoryProvider.value(value: _journalRepository), RepositoryProvider.value(value: _remoteJournalRepository), RepositoryProvider.value(value: _syncEngine), RepositoryProvider.value(value: _classRepository), ], child: ListenableProvider.value( value: _settingsBloc, child: Builder( builder: (context) { final settings = context.watch(); return BlocProvider.value( value: _authBloc, child: _AppView( router: createAppRouter(_authBloc), themeMode: settings.state.themeMode, ), ); }, ), ), ); } } /// App 视图 — MaterialApp.router 包装 class _AppView extends StatelessWidget { final GoRouter router; final ThemeMode themeMode; const _AppView({required this.router, this.themeMode = ThemeMode.system}); @override Widget build(BuildContext context) { return MaterialApp.router( title: '暖记', debugShowCheckedModeBanner: false, theme: AppTheme.light(), darkTheme: AppTheme.dark(), themeMode: themeMode, routerConfig: router, ); } }