325 lines
10 KiB
Dart
325 lines
10 KiB
Dart
import 'dart:async';
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:catcher_2/catcher_2.dart';
|
||
import 'package:flutter_dotenv/flutter_dotenv.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/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_service.dart';
|
||
import 'package:mom_kitchen/src/services/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兼容:增强错误处理,防止调试环境闪退
|
||
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) {
|
||
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<void> _initApp() async {
|
||
// 0. 加载环境变量(含SMTP等敏感配置,不阻断启动)
|
||
try {
|
||
await dotenv
|
||
.load(fileName: '.env')
|
||
.timeout(
|
||
const Duration(seconds: 3),
|
||
onTimeout: () => debugPrint('⚠️ .env加载超时(3s)'),
|
||
);
|
||
} catch (e) {
|
||
debugPrint('⚠️ .env加载失败(使用默认空值): $e');
|
||
}
|
||
|
||
// 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 MyApp extends StatelessWidget {
|
||
const MyApp({super.key});
|
||
|
||
ThemeService _getThemeService() {
|
||
if (Get.isRegistered<ThemeService>()) {
|
||
return Get.find<ThemeService>();
|
||
}
|
||
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 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>();
|
||
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()),
|
||
);
|
||
}
|
||
}
|