import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:catcher_2/catcher_2.dart'; import 'package:mom_kitchen/src/l10n/app_localizations.dart'; import 'package:mom_kitchen/src/config/app_routes.dart'; import 'package:mom_kitchen/src/services/core/app_service.dart'; import 'package:mom_kitchen/src/services/api/api_service.dart'; import 'package:mom_kitchen/src/services/system/orientation_service.dart'; import 'package:mom_kitchen/src/services/ui/theme_service.dart'; import 'package:mom_kitchen/src/services/ui/toast_service.dart'; import 'package:mom_kitchen/src/services/data/storage/storage_service.dart'; import 'package:mom_kitchen/src/services/system/crash_guard_service.dart'; import 'package:mom_kitchen/src/app_binding.dart'; import 'package:mom_kitchen/src/utils/app_logger.dart'; // 2026-04-15 | main.dart | 应用入口 | Catcher2最先初始化+串行化启动流程防ANR // 2026-04-15 | 增强防御:所有初始化步骤加超时+try-catch,任何一步失败都不阻断启动 // 2026-04-15 | Trae IDE兼容:增强错误处理,防止调试环境闪退 // 2026-04-19 | 移除flutter_dotenv依赖,不再加载.env文件 void main() { runZonedGuarded( () { final crashGuard = CrashGuardService(); Catcher2( runAppFunction: () async { WidgetsFlutterBinding.ensureInitialized(); PlatformDispatcher.instance.onError = (error, stack) { debugPrint('🚨 PlatformDispatcher Error: $error'); try { Catcher2.reportCheckedError(error, stack); } catch (_) { debugPrint('❌ PlatformDispatcher report failed: $error'); } return true; }; FlutterError.onError = (details) { if (_isLayoutOverflow(details)) { debugPrint('⚠️ [LayoutWarning] ${details.exceptionAsString()}'); if (details.stack != null) { debugPrint('⚠️ [LayoutWarning] Stack: ${details.stack}'); } if (!kReleaseMode) { FlutterError.presentError(details); } try { Catcher2.reportCheckedError(details.exception, details.stack); } catch (_) { debugPrint( '❌ FlutterError.onError catch failed: ${details.exception}', ); } return; } FlutterError.presentError(details); try { Catcher2.reportCheckedError(details.exception, details.stack); } catch (_) { debugPrint( '❌ FlutterError.onError catch failed: ${details.exception}', ); } }; try { await _initApp(); } catch (e, st) { debugPrint('🚨 _initApp failed: $e'); debugPrint('Stack: $st'); } runApp(const MyApp()); }, ensureInitialized: false, debugConfig: crashGuard.buildDebugOptions(), releaseConfig: crashGuard.buildReleaseOptions(), ); }, (error, stack) { debugPrint('🚨 Uncaught async error: $error'); debugPrint('Stack: $stack'); }, ); } /// 启动初始化链 - 每一步都有独立超时和try-catch /// 核心原则:任何一步失败都不阻断后续步骤,保证App至少能启动 Future _initApp() async { // 1. 核心服务初始化(最关键,但不阻断) try { await AppService.instance.init().timeout( const Duration(seconds: 10), onTimeout: () => debugPrint('❌ AppService初始化超时(10s)'), ); } catch (e) { debugPrint('❌ AppService初始化失败: $e'); } // 2. DNS预检(非关键,纯后台) try { await ApiService().preCheckDns().timeout( const Duration(seconds: 5), onTimeout: () => debugPrint('⚠️ DNS预检超时(5s)'), ); } catch (e) { debugPrint('⚠️ DNS预检失败: $e'); } // 3. CrashGuard初始化 try { await CrashGuardService.init().timeout(const Duration(seconds: 3)); } catch (e) { debugPrint('⚠️ CrashGuardService初始化失败或超时: $e'); } if (kDebugMode) { AppRoutes.registerAllPages(); } await OrientationService().unlockOrientation(); } class DesktopScrollBehavior extends MaterialScrollBehavior { @override Set get dragDevices => { PointerDeviceKind.touch, PointerDeviceKind.mouse, PointerDeviceKind.stylus, PointerDeviceKind.trackpad, }; } class MyApp extends StatelessWidget { const MyApp({super.key}); ThemeService _getThemeService() { if (Get.isRegistered()) { return Get.find(); } return Get.put(ThemeService.instance, permanent: true); } @override Widget build(BuildContext context) { final themeService = _getThemeService(); return Obx(() { final textScale = themeService.fontSize.value / 16.0; return GetCupertinoApp( title: 'Mom\'s Kitchen', navigatorKey: Catcher2.navigatorKey, theme: themeService.cupertinoThemeData, locale: Locale(themeService.currentLocale.value), debugShowCheckedModeBanner: false, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: const [ Locale('en'), Locale('zh'), Locale('zh', 'Hant'), ], getPages: AppRoutes.pages, initialBinding: AppBinding(), builder: (context, widget) { return KeyboardListener( focusNode: FocusNode(), onKeyEvent: (event) { if (event is KeyDownEvent) { final isAltLeft = HardwareKeyboard.instance.isAltPressed && event.logicalKey == LogicalKeyboardKey.arrowLeft; final isBackspace = event.logicalKey == LogicalKeyboardKey.backspace; final isBrowserBack = event.logicalKey == LogicalKeyboardKey.browserBack; if (isAltLeft || isBackspace || isBrowserBack) { if (Get.currentRoute != '/' && Get.currentRoute != AppRoutes.main && Get.currentRoute != AppRoutes.guide) { Get.back(); } } } }, child: ScrollConfiguration( behavior: DesktopScrollBehavior(), child: MediaQuery( data: MediaQuery.of( context, ).copyWith(textScaler: TextScaler.linear(textScale)), child: ColoredBox( color: themeService.backgroundColor.value, child: widget ?? const SizedBox.shrink(), ), ), ), ); }, routingCallback: (routing) { if (kDebugMode && routing?.current != null) { AppLogger.d('🔍 路由变化: ${routing?.previous} → ${routing?.current}'); } }, home: const _InitWrapper(), ); }); } } class _InitWrapper extends StatefulWidget { const _InitWrapper(); @override State<_InitWrapper> createState() => _InitWrapperState(); } class _InitWrapperState extends State<_InitWrapper> with WidgetsBindingObserver { bool _navigated = false; int _retryCount = 0; static const int _maxRetries = 5; String? _lastError; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { _navigateToMain(); }); } void _navigateToMain() { if (!mounted || _navigated) return; try { ToastService.init(context); final agreementAccepted = StorageService().getBool('agreement_accepted') ?? false; if (agreementAccepted) { Get.offAllNamed(AppRoutes.main); } else { Get.offAllNamed(AppRoutes.guide); } _navigated = true; debugPrint('✅ 导航到${agreementAccepted ? '主页面' : '引导页'}成功'); } catch (e, st) { _lastError = e.toString(); _retryCount++; debugPrint('❌ 导航失败 (第$_retryCount次): $e'); debugPrint('Stack: $st'); if (_retryCount < _maxRetries) { Future.delayed(Duration(milliseconds: 300 * _retryCount), () { if (mounted && !_navigated) _navigateToMain(); }); } else { debugPrint('🚨 导航重试已达上限,显示错误页面'); if (mounted) setState(() {}); } } } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangePlatformBrightness() { super.didChangePlatformBrightness(); final brightness = WidgetsBinding.instance.platformDispatcher.platformBrightness; try { final themeService = Get.find(); themeService.onSystemBrightnessChanged(brightness); } catch (_) {} } @override Widget build(BuildContext context) { if (_retryCount >= _maxRetries) { return CupertinoPageScaffold( child: SafeArea( child: Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon( CupertinoIcons.exclamationmark_triangle, size: 48, color: CupertinoColors.systemRed, ), const SizedBox(height: 16), const Text( '启动失败,请重试', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600), ), if (_lastError != null) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: CupertinoColors.systemGrey5, borderRadius: BorderRadius.circular(8), ), child: Text( _lastError!.length > 200 ? '${_lastError!.substring(0, 200)}...' : _lastError!, style: const TextStyle( fontSize: 12, fontFamily: 'monospace', ), textAlign: TextAlign.center, ), ), ], const SizedBox(height: 24), Row( mainAxisSize: MainAxisSize.min, children: [ CupertinoButton( onPressed: () { _retryCount = 0; _lastError = null; setState(() {}); _navigateToMain(); }, child: const Text('重试'), ), const SizedBox(width: 16), CupertinoButton.filled( onPressed: () { _retryCount = 0; _lastError = null; _navigated = false; setState(() {}); _navigateToMain(); }, child: const Text('强制重启'), ), ], ), ], ), ), ), ), ); } return const CupertinoPageScaffold( child: Center(child: CupertinoActivityIndicator()), ); } } bool _isLayoutOverflow(FlutterErrorDetails details) { final msg = details.exceptionAsString().toLowerCase(); return msg.contains('overflowed') || msg.contains('renderflex') || msg.contains('renderconstrainedbox') || msg.contains('hasSize') || msg.contains('not positioned'); }