import 'dart:convert'; import 'dart:io'; import 'dart:ui'; import 'package:cloudreve4_flutter/core/utils/app_logger.dart'; import 'package:logger/logger.dart' show Level; import 'package:cloudreve4_flutter/presentation/widgets/desktop_title_bar.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_single_instance/flutter_single_instance.dart'; import 'package:provider/provider.dart'; import 'package:media_kit/media_kit.dart'; import 'package:oktoast/oktoast.dart'; import 'package:window_manager/window_manager.dart'; import 'config/app_config.dart'; import 'core/constants/storage_keys.dart'; import 'presentation/providers/auth_provider.dart'; import 'presentation/providers/file_manager_provider.dart'; import 'presentation/providers/navigation_provider.dart'; import 'presentation/providers/upload_manager_provider.dart'; import 'presentation/providers/download_manager_provider.dart'; import 'presentation/providers/user_setting_provider.dart'; import 'presentation/providers/admin_provider.dart'; import 'presentation/providers/quick_access_provider.dart'; import 'presentation/providers/sync_provider.dart'; import 'presentation/providers/theme_provider.dart'; import 'services/upload_service.dart'; import 'services/api_service.dart'; import 'services/storage_service.dart'; import 'services/server_service.dart'; import 'services/cache_manager_service.dart'; import 'services/avatar_cache_service.dart'; import 'core/utils/video_fullscreen.dart'; import 'services/desktop_service.dart'; import 'router/app_router.dart'; import 'presentation/widgets/toast_helper.dart'; import 'presentation/widgets/update_prompt.dart'; import 'src/rust/frb_generated.dart' show RustSyncApi; final RouteObserver routeObserver = RouteObserver(); Level _parseLogLevel(String level) { return switch (level) { 'error' => Level.error, 'warning' => Level.warning, 'info' => Level.info, 'debug' => Level.debug, 'trace' => Level.trace, _ => Level.info, }; } void main() async { WidgetsFlutterBinding.ensureInitialized(); await AppLogger.init(); final savedLevel = await StorageService.instance.getString( StorageKeys.logLevel, ); if (savedLevel != null) { final level = _parseLogLevel(savedLevel); AppLogger.setLevel(level); } AppLogger.i("应用启动,日志系统就绪"); try { await RustSyncApi.init(); AppLogger.i("RustSyncApi 初始化成功"); } catch (e) { AppLogger.e("RustSyncApi 初始化失败: $e"); } // 捕获 flutter_cache_manager 在 Windows 上删除缓存文件时的文件占用异常 // 该异常是后台异步抛出的,无法通过 try-catch 拦截,需绑定错误处理器静默忽略 FlutterError.onError = (details) { final msg = details.exceptionAsString(); if (msg.contains('PathAccessException') || msg.contains('errno = 32')) { return; // Windows 文件占用,忽略 } FlutterError.presentError(details); }; PlatformDispatcher.instance.onError = (error, stack) { if (error is PathAccessException) { return true; // 已处理,不传播 } return false; }; // 桌面端初始化窗口管理和系统托盘 if (Platform.isWindows || Platform.isLinux) { // 实例化 FlutterSingleInstance 获取单实例句柄 final singleInstance = FlutterSingleInstance(); final addr = FlutterSingleInstance.address as InternetAddress; // 检查是否是第一个实例 final bool isFirstInstance = await singleInstance.isFirstInstance(); if (!isFirstInstance) { AppLogger.i("程序已经在运行, 尝试唤醒..."); // 如果已经有实例在运行,通过focus回调发送消息给"第一个已启动的实例" // 第一个实例的 listen 会收到一个字符串 await singleInstance.focus({"action": "bring_to_front_showWindow"}); // 退出当前新启动的进程 exit(0); } final String processName = await singleInstance.getProcessName(pid) ?? "Unknown"; final File? pidFile = await singleInstance.getPidFile(processName); int port = FlutterSingleInstance.port; if (await pidFile!.exists()) { try { final content = await pidFile.readAsString(); final Map data = jsonDecode(content); port = data['port'] ?? 0; } catch (e) { AppLogger.e( "Get FlutterSingleInstance port has error: ${e.toString()}", ); } } AppLogger.i( "processName: $processName \npid: $pid \npidFile: ${pidFile.path.toString()} \nSingleInstance RPC address:port: ${addr.address}:$port", ); FlutterSingleInstance.onFocus = (metadata) async { AppLogger.i("收到唤醒信号: $metadata"); await DesktopService.instance.showWindow(); }; await DesktopService.instance.initialize(); } // 初始化MediaKit MediaKit.ensureInitialized(); // 初始化服务器服务 await ServerService.instance.init(); // 初始化API服务 await ApiService.instance.init(); // 初始化缓存管理器 await CacheManagerService.instance.initialize(); // 初始化头像缓存服务 await AvatarCacheService.instance.init(); // 设置横竖屏方向(仅移动端) if (Platform.isAndroid || Platform.isIOS) { await SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); } // 设置状态栏样式 SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, systemNavigationBarColor: Colors.transparent, ), ); runApp(const CloudreveApp()); } class CloudreveApp extends StatelessWidget { const CloudreveApp({super.key}); @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => ThemeProvider()..init()), ChangeNotifierProvider(create: (_) => AuthProvider()..init()), ChangeNotifierProvider(create: (_) => FileManagerProvider()), ChangeNotifierProvider(create: (_) => NavigationProvider()), ChangeNotifierProvider(create: (_) => UploadService()), ChangeNotifierProvider( create: (_) => UploadManagerProvider()..initialize(), ), ChangeNotifierProvider( create: (_) => DownloadManagerProvider()..initialize(), ), ChangeNotifierProvider(create: (_) => UserSettingProvider()), ChangeNotifierProvider(create: (_) => AdminProvider()), ChangeNotifierProvider(create: (_) => QuickAccessProvider()..load()), ChangeNotifierProvider(create: (_) => SyncProvider()), ], child: const AppView(), ); } } class AppView extends StatelessWidget { const AppView({super.key}); @override Widget build(BuildContext context) { final themeProvider = context.watch(); final flutterThemeMode = switch (themeProvider.themeMode) { AppThemeMode.light => ThemeMode.light, AppThemeMode.dark => ThemeMode.dark, AppThemeMode.system => ThemeMode.system, }; return OKToast( child: MaterialApp( title: AppConfig.appName, debugShowCheckedModeBanner: false, theme: themeProvider.buildLightTheme(), darkTheme: themeProvider.buildDarkTheme(), themeMode: flutterThemeMode, onGenerateRoute: AppRouter.generateRoute, initialRoute: RouteNames.splash, navigatorObservers: [routeObserver], builder: (context, child) { if (child == null) return const SizedBox.shrink(); Widget currentWidget = child; if (Platform.isWindows || Platform.isLinux) { currentWidget = Material( color: themeProvider.isDark ? Colors.black.withValues(alpha: 0.92) : Colors.white.withValues(alpha: 0.92), child: ValueListenableBuilder( valueListenable: videoFullscreenNotifier, builder: (context, isVideoFullscreen, child) { if (isVideoFullscreen) { return child!; } return Column( children: [ const SizedBox( height: 32, child: DragToMoveArea(child: DesktopTitleBar()), ), Expanded(child: child!), ], ); }, child: currentWidget, ), ); // 添加全局错误处理 currentWidget = FilterQualityWidget(child: currentWidget); } // 添加全局错误处理 return UpdatePrompt(child: ErrorHandler(child: currentWidget)); }, ), ); } } // 定义一个简单的包装类,确保子组件在绘制时使用高画质滤镜走抗锯齿逻辑 // ImageFiltered 会强制渲染引擎进行重绘计算,解决 Windows 上部分 UI 边缘生硬的问题 class FilterQualityWidget extends StatelessWidget { final Widget child; const FilterQualityWidget({super.key, required this.child}); @override Widget build(BuildContext context) { return ImageFiltered( // 在 Windows 上,此滤镜能强制 Skia 引擎重新计算像素边缘 imageFilter: ColorFilter.mode(Colors.transparent, BlendMode.multiply), child: child, ); } } /// 全局错误处理器 class ErrorHandler extends StatelessWidget { final Widget child; const ErrorHandler({super.key, required this.child}); @override Widget build(BuildContext context) { return Consumer( builder: (context, authProvider, child) { // 检查是否有待处理的登录过期错误 if (authProvider.hasRefreshTokenExpired) { WidgetsBinding.instance.addPostFrameCallback((_) async { final navigator = Navigator.of(context); ToastHelper.failure('登录已过期,请重新登录'); navigator.pushNamedAndRemoveUntil( RouteNames.login, (route) => false, ); authProvider.clearRefreshTokenExpired(); }); } return child!; }, child: child, ); } }