Files
kitchen/lib/main.dart
Developer 236bffb1bc 重构4
2026-04-19 04:26:55 +08:00

325 lines
10 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: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/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兼容增强错误处理防止调试环境闪退
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()),
);
}
}