Files
kitchen/lib/main.dart
Developer b1acdbdf05 feat: 新增公告功能及外卖备注工具
新增公告功能接口及页面,支持查看最新公告信息
添加外卖备注工具,可管理常用备注并一键生成
优化动态筛选接口,支持多分类和标签组合筛选
移除flutter_dotenv依赖,不再使用.env文件
修复布局溢出错误处理逻辑,避免生产环境报错
新增文件选择器插件,替换receive_sharing_intent实现文件导入
2026-04-20 08:21:40 +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: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 (kReleaseMode && _isLayoutOverflow(details)) {
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<void> _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 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()),
);
}
}
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');
}