Files
MaliangAINovalWriter/AINoval/lib/main.dart
2025-09-10 00:07:52 +08:00

570 lines
24 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.

import 'dart:io';
import 'dart:async';
// <<< 导入 AiConfigBloc >>>
import 'package:ainoval/blocs/ai_config/ai_config_bloc.dart';
// 导入聊天相关的类
import 'package:ainoval/blocs/auth/auth_bloc.dart';
import 'package:ainoval/blocs/chat/chat_bloc.dart';
import 'package:ainoval/blocs/credit/credit_bloc.dart';
import 'package:ainoval/blocs/editor_version_bloc.dart';
import 'package:ainoval/blocs/novel_list/novel_list_bloc.dart';
import 'package:ainoval/blocs/public_models/public_models_bloc.dart';
import 'package:ainoval/blocs/setting_generation/setting_generation_bloc.dart';
import 'package:ainoval/config/app_config.dart'; // 引入 AppConfig
import 'package:ainoval/l10n/l10n.dart';
import 'package:ainoval/models/app_registration_config.dart';
// import 'package:ainoval/screens/novel_list/novel_list_screen.dart'; // 已删除,使用新页面
import 'package:ainoval/screens/novel_list/novel_list_real_data_screen.dart' deferred as novel_list;
import 'package:ainoval/services/api_service/base/api_client.dart';
import 'package:ainoval/services/api_service/base/sse_client.dart';
// <<< 移除未使用的 Codex Impl 引用 >>>
// import 'package:ainoval/services/api_service/repositories/impl/codex_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/chat_repository.dart'; // <<< 导入接口
// ApiService import might not be needed directly in main unless provided
// import 'package:ainoval/services/api_service.dart';
import 'package:ainoval/services/api_service/repositories/impl/chat_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/credit_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/novel_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/novel_setting_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/public_model_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/storage_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/user_ai_model_config_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/setting_generation_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/universal_ai_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/preset_aggregation_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/ai_preset_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/impl/novel_snippet_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/novel_repository.dart'; // <<< 导入接口
import 'package:ainoval/services/image_cache_service.dart';
// import 'package:ainoval/services/api_service/repositories/novel_setting_repository.dart';
import 'package:ainoval/services/api_service/repositories/credit_repository.dart';
import 'package:ainoval/services/api_service/repositories/public_model_repository.dart';
import 'package:ainoval/services/api_service/repositories/storage_repository.dart';
// <<< 导入 AI Config 仓库 >>>
import 'package:ainoval/services/api_service/repositories/user_ai_model_config_repository.dart';
import 'package:ainoval/services/api_service/repositories/setting_generation_repository.dart';
import 'package:ainoval/services/api_service/repositories/universal_ai_repository.dart';
import 'package:ainoval/services/api_service/repositories/preset_aggregation_repository.dart';
import 'package:ainoval/services/api_service/repositories/ai_preset_repository.dart';
import 'package:ainoval/services/api_service/repositories/novel_snippet_repository.dart';
import 'package:ainoval/services/auth_service.dart' as auth_service;
import 'package:ainoval/services/local_storage_service.dart';
import 'package:ainoval/services/novel_file_service.dart'; // 导入小说文件服务
// import 'package:ainoval/services/websocket_service.dart';
import 'package:ainoval/utils/web_theme.dart';
import 'package:ainoval/utils/logger.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ainoval/l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:ainoval/services/api_service/repositories/prompt_repository.dart';
import 'package:ainoval/services/api_service/repositories/impl/prompt_repository_impl.dart';
// 重复导入清理(下方已存在这些导入)
import 'package:ainoval/blocs/universal_ai/universal_ai_bloc.dart';
import 'package:ainoval/utils/navigation_logger.dart';
import 'package:ainoval/blocs/prompt_new/prompt_new_bloc.dart';
import 'package:ainoval/blocs/prompt_new/prompt_new_event.dart';
import 'package:ainoval/blocs/theme/theme_bloc.dart';
import 'package:ainoval/blocs/theme/theme_event.dart';
import 'package:ainoval/blocs/theme/theme_state.dart';
// 导入预设管理BLoC
import 'package:ainoval/blocs/preset/preset_bloc.dart';
import 'package:ainoval/blocs/preset/preset_event.dart';
// 导入预设聚合仓储
import 'package:ainoval/screens/unified_management/unified_management_screen.dart' deferred as unified_mgmt;
void main() {
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
// Web 平台下:覆盖 Flutter 全局错误处理,避免 Inspector 在处理 JS 对象时报错
FlutterError.onError = (FlutterErrorDetails details) {
if (kIsWeb) {
// 直接输出字符串化的异常信息,避免 DiagnosticsNode 转换
debugPrint('FlutterError: ${details.exceptionAsString()}');
if (details.stack != null) {
debugPrint(details.stack.toString());
}
} else {
FlutterError.presentError(details);
}
};
// 初始化日志系统
AppLogger.init();
// 初始化Hive本地存储
await Hive.initFlutter();
// 初始化注册配置
await _initializeRegistrationConfig();
// 创建必要的资源文件夹 - 仅在非Web平台执行
if (!kIsWeb) {
await _createResourceDirectories();
}
// 初始化LocalStorageService
final localStorageService = LocalStorageService();
await localStorageService.init();
// 创建AuthService
final authServiceInstance = auth_service.AuthService();
await authServiceInstance.init();
// 创建 ApiClient 实例并传入 AuthService
final apiClient = ApiClient(authService: authServiceInstance);
// 创建 SseClient 实例 (单例模式)
final sseClient = SseClient();
/*
// 创建ApiService (如果 ApiService 需要 ApiClient, 则传入)
// 假设 ApiService 构造函数接受 apiClient (如果不需要则忽略)
final apiService = ApiService(/* apiClient: apiClient */);
// 创建WebSocketService
final webSocketService = WebSocketService(); */
// 创建NovelRepository (它不再需要MockDataService)
final novelRepository = NovelRepositoryImpl(/* apiClient: apiClient */);
// 创建ChatRepository并传入 ApiClient
final chatRepository = ChatRepositoryImpl(
apiClient: apiClient, // 使用直接创建的 apiClient
);
// 创建StorageRepository实例
final storageRepository = StorageRepositoryImpl(apiClient);
// 创建UserAIModelConfigRepository
final userAIModelConfigRepository =
UserAIModelConfigRepositoryImpl(apiClient: apiClient);
// 创建PublicModelRepository
final publicModelRepository = PublicModelRepositoryImpl(apiClient: apiClient);
// 创建CreditRepository
final creditRepository = CreditRepositoryImpl(apiClient: apiClient);
// 创建NovelSettingRepository
final novelSettingRepository = NovelSettingRepositoryImpl(apiClient: apiClient);
// 创建PromptRepository
final promptRepository = PromptRepositoryImpl(apiClient);
// 创建NovelFileService
final novelFileService = NovelFileService(
novelRepository: novelRepository,
// editorRepository 暂时为空,可以后续添加
);
// 创建NovelSnippetRepository
final novelSnippetRepository = NovelSnippetRepositoryImpl(apiClient);
// 创建UniversalAIRepository
final universalAIRepository = UniversalAIRepositoryImpl(apiClient: apiClient);
// 创建PresetAggregationRepository
final presetAggregationRepository = PresetAggregationRepositoryImpl(apiClient);
// 创建AIPresetRepository
final aiPresetRepository = AIPresetRepositoryImpl(apiClient: apiClient);
// 创建SettingGenerationRepository
final settingGenerationRepository = SettingGenerationRepositoryImpl(
apiClient: apiClient,
sseClient: sseClient,
);
// 初始化图片缓存服务(如需预热可在此调用)
// ImageCacheService().prewarm();
AppLogger.i('Main', '应用程序初始化完成,准备启动界面');
runApp(MultiRepositoryProvider(
providers: [
RepositoryProvider<auth_service.AuthService>.value(
value: authServiceInstance),
RepositoryProvider<ApiClient>.value(value: apiClient),
RepositoryProvider<NovelRepository>.value(value: novelRepository),
RepositoryProvider<ChatRepository>.value(value: chatRepository),
RepositoryProvider<StorageRepository>.value(value: storageRepository),
RepositoryProvider<UserAIModelConfigRepository>.value(
value: userAIModelConfigRepository),
RepositoryProvider<PublicModelRepository>.value(
value: publicModelRepository),
RepositoryProvider<CreditRepository>.value(
value: creditRepository),
RepositoryProvider<LocalStorageService>.value(
value: localStorageService),
RepositoryProvider<PromptRepository>(
create: (context) => promptRepository,
),
RepositoryProvider<NovelFileService>.value(
value: novelFileService,
),
RepositoryProvider<NovelSnippetRepository>.value(
value: novelSnippetRepository,
),
RepositoryProvider<UniversalAIRepository>.value(
value: universalAIRepository,
),
RepositoryProvider<PresetAggregationRepository>.value(
value: presetAggregationRepository,
),
RepositoryProvider<AIPresetRepository>.value(
value: aiPresetRepository,
),
RepositoryProvider<SettingGenerationRepository>.value(
value: settingGenerationRepository,
),
],
child: MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>(
create: (context) => AuthBloc(
authService: context.read<auth_service.AuthService>(),
)..add(AuthInitialize()),
),
BlocProvider<NovelListBloc>(
create: (context) => NovelListBloc(
repository: context.read<NovelRepository>(),
),
),
BlocProvider<AiConfigBloc>(
create: (context) => AiConfigBloc(
repository: context.read<UserAIModelConfigRepository>(),
),
),
BlocProvider<PublicModelsBloc>(
create: (context) => PublicModelsBloc(
repository: context.read<PublicModelRepository>(),
),
),
BlocProvider<CreditBloc>(
create: (context) => CreditBloc(
repository: context.read<CreditRepository>(),
),
),
BlocProvider<SettingGenerationBloc>(
create: (context) => SettingGenerationBloc(
repository: context.read<SettingGenerationRepository>(),
),
),
/*
BlocProvider<ReaderBloc>(
create: (context) => ReaderBloc(
repository: context.read<NovelRepository>(),
),
),
*/
BlocProvider<ChatBloc>(
create: (context) => ChatBloc(
repository: context.read<ChatRepository>(),
authService: context.read<auth_service.AuthService>(),
aiConfigBloc: context.read<AiConfigBloc>(),
publicModelsBloc: context.read<PublicModelsBloc>(),
settingRepository: novelSettingRepository,
snippetRepository: novelSnippetRepository,
),
),
BlocProvider<EditorVersionBloc>(
create: (context) => EditorVersionBloc(
novelRepository: context.read<NovelRepository>(),
),
),
BlocProvider<UniversalAIBloc>(
create: (context) => UniversalAIBloc(
repository: context.read<UniversalAIRepository>(),
),
),
BlocProvider<PromptNewBloc>(
create: (context) => PromptNewBloc(
promptRepository: context.read<PromptRepository>(),
),
),
BlocProvider<ThemeBloc>(
create: (context) => ThemeBloc()..add(ThemeInitialize()),
),
BlocProvider<PresetBloc>(
create: (context) => PresetBloc(
aggregationRepository: context.read<PresetAggregationRepository>(),
presetRepository: context.read<AIPresetRepository>(),
),
),
],
child: const MyApp(),
),
));
}, (error, stack) {
// 兜底:捕获所有未处理异常并记录,避免在 Web 上出现 LegacyJavaScriptObject -> DiagnosticsNode 的崩溃
AppLogger.e('Uncaught', '未捕获异常: $error', error, stack);
});
}
// 初始化注册配置
Future<void> _initializeRegistrationConfig() async {
try {
// 确保注册配置已初始化,设置默认值
// 默认开启邮箱注册和手机注册,需要验证码验证
final phoneEnabled = await AppRegistrationConfig.isPhoneRegistrationEnabled();
final emailEnabled = await AppRegistrationConfig.isEmailRegistrationEnabled();
final verificationRequired = await AppRegistrationConfig.isVerificationRequired();
AppLogger.i('Registration',
'📝 注册配置已加载 - 邮箱注册: $emailEnabled, 手机注册: $phoneEnabled, 验证码验证: $verificationRequired');
// 如果没有任何注册方式可用,启用默认的邮箱注册
if (!phoneEnabled && !emailEnabled) {
await AppRegistrationConfig.setEmailRegistrationEnabled(true);
AppLogger.i('Registration', '🔧 已自动启用邮箱注册功能');
}
} catch (e) {
AppLogger.e('Registration', '初始化注册配置失败', e);
}
}
// 创建资源文件夹
Future<void> _createResourceDirectories() async {
try {
final appDir = await getApplicationDocumentsDirectory();
final assetsDir = Directory('${appDir.path}/assets');
final imagesDir = Directory('${assetsDir.path}/images');
final iconsDir = Directory('${assetsDir.path}/icons');
// 创建资源目录
if (!await assetsDir.exists()) {
await assetsDir.create(recursive: true);
}
// 创建图像目录
if (!await imagesDir.exists()) {
await imagesDir.create(recursive: true);
}
// 创建图标目录
if (!await iconsDir.exists()) {
await iconsDir.create(recursive: true);
}
AppLogger.i('ResourceDir', '资源文件夹创建成功');
} catch (e) {
AppLogger.e('ResourceDir', '创建资源文件夹失败', e);
}
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
bool _postLoginBootstrapped = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
ImageCacheService().clearCache();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.paused:
case AppLifecycleState.detached:
// 应用进入后台或被关闭时清理图片缓存
ImageCacheService().clearCache();
break;
case AppLifecycleState.resumed:
// 应用恢复时可以预加载一些图片
break;
default:
break;
}
}
@override
Widget build(BuildContext context) {
return BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, themeState) {
return ValueListenableBuilder<String>(
valueListenable: WebTheme.variantListenable,
builder: (context, variant, _) {
// 根据当前变体重建主题
return MaterialApp(
navigatorObservers: [NavigationLogger()],
title: 'AINoval',
theme: WebTheme.buildLightTheme(),
darkTheme: WebTheme.buildDarkTheme(),
themeMode: themeState.themeMode,
initialRoute: '/',
routes: {
'/': (context) => BlocConsumer<AuthBloc, AuthState>(
listenWhen: (prev, curr) =>
curr is AuthAuthenticated || curr is AuthUnauthenticated,
listener: (context, state) {
AppLogger.i('MyApp', '🔔 AuthBloc状态变化: ${state.runtimeType}');
if (state is AuthAuthenticated) {
if (_postLoginBootstrapped) {
AppLogger.i('MyApp', '🔁 已完成登录后的初始化,跳过重复触发');
}
final userId = AppConfig.userId;
if (userId != null) {
AppLogger.i('MyApp',
'User authenticated, loading AiConfigs, PublicModels, Credits, Novels, Presets and PromptPackages for user $userId');
// 并行加载用户AI配置、公共模型和用户积分
if (!_postLoginBootstrapped) {
context.read<AiConfigBloc>().add(LoadAiConfigs(userId: userId));
context.read<PublicModelsBloc>().add(const LoadPublicModels());
// 每次登录都强制重新加载积分,避免复用上个账号缓存
context.read<CreditBloc>().add(const LoadUserCredits());
// 用户登录成功后,加载一次小说列表数据(仅在未加载时)
final novelState = context.read<NovelListBloc>().state;
if (novelState is! NovelListLoaded) {
context.read<NovelListBloc>().add(LoadNovels());
}
// 预设与提示词包
context.read<PresetBloc>().add(const LoadAllPresetData());
context.read<PromptNewBloc>().add(const LoadAllPromptPackages());
_postLoginBootstrapped = true;
}
} else {
AppLogger.e('MyApp',
'User authenticated but userId is null in AppConfig!');
}
} else if (state is AuthUnauthenticated) {
AppLogger.i('MyApp', '✅ 用户已退出登录清理所有BLoC状态');
_postLoginBootstrapped = false;
// 清理所有BLoC状态停止进行中的请求
try {
// 重置 AI 配置,避免跨用户复用本地缓存/内存状态
context.read<AiConfigBloc>().add(const ResetAiConfigs());
} catch (e) {
AppLogger.w('MyApp', '重置AiConfigBloc状态失败', e);
}
try {
// 清理小说列表状态
context.read<NovelListBloc>().add(ClearNovels());
AppLogger.i('MyApp', '✅ NovelListBloc状态已清理');
} catch (e) {
AppLogger.w('MyApp', '清理NovelListBloc状态失败', e);
}
// 清空积分显示为游客0
try {
context.read<CreditBloc>().add(const ClearCredits());
AppLogger.i('MyApp', '✅ CreditBloc状态已清空');
} catch (e) {
AppLogger.w('MyApp', '清空CreditBloc状态失败', e);
}
// 清除用户显示名称为游客
AppConfig.setUsername(null);
AppConfig.setUserId(null);
AppConfig.setAuthToken(null);
// 可以根据需要添加其他BLoC的清理逻辑
// 但暂时先清理最关键的小说列表避免404请求
} else if (state is AuthLoading) {
AppLogger.i('MyApp', '⏳ 认证状态加载中...');
} else if (state is AuthError) {
AppLogger.w('MyApp', '❌ 认证错误: ${state.message}');
}
},
buildWhen: (prev, curr) =>
curr is AuthAuthenticated || curr is AuthUnauthenticated,
builder: (context, state) {
AppLogger.i('MyApp', '🏗️ 构建UI当前状态: ${state.runtimeType}');
if (state is AuthAuthenticated) {
AppLogger.i(
'MyApp', '📚 显示小说列表界面');
// 🚀 登录成功后异步加载并应用用户的主题变体,确保全局组件使用保存的主题色
final userId = AppConfig.userId;
if (userId != null) {
() async {
try {
final settings = await NovelRepositoryImpl.getInstance().getUserEditorSettings(userId);
WebTheme.applyVariant(settings.themeVariant);
AppLogger.i('MyApp', '🎨 已应用用户主题变体: ${settings.themeVariant}');
} catch (e) {
AppLogger.w('MyApp', '无法应用用户主题变体: $e');
}
}();
}
// 异步加载小说列表页面,实现代码分割
return FutureBuilder(
future: novel_list.loadLibrary(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return novel_list.NovelListRealDataScreen();
}
return const Center(
child: CircularProgressIndicator(),
);
},
);
}
// 未登录:默认展示小说列表的“游客模式”界面,受控于页面内的鉴权弹窗
return FutureBuilder(
future: novel_list.loadLibrary(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return novel_list.NovelListRealDataScreen();
}
return const Center(
child: CircularProgressIndicator(),
);
},
);
},
),
'/unified-management': (context) => FutureBuilder(
future: unified_mgmt.loadLibrary(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return unified_mgmt.UnifiedManagementScreen();
}
return const Center(child: CircularProgressIndicator());
},
),
},
debugShowCheckedModeBanner: false,
// 添加完整的本地化支持
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: L10n.all,
locale: const Locale('zh', 'CN'), // 设置默认语言为中文
);
},
);
},
);
}
}