更新项目名称及相关引用,包括README、iOS/macOS/Linux配置、文档和代码中的包引用。同时更新版本号至1.3.5并清理无用的HarmonyOS配置文件。 - 修改所有代码中的包引用路径 - 更新各平台配置文件和安装脚本 - 清理HarmonyOS相关无用文件 - 更新应用版本号至1.3.5 - 修正文档中的项目名称引用
400 lines
13 KiB
Dart
400 lines
13 KiB
Dart
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:cute_kitchen/src/l10n/app_localizations.dart';
|
||
|
||
import 'package:cute_kitchen/src/config/app_routes.dart';
|
||
|
||
import 'package:cute_kitchen/src/services/core/app_service.dart';
|
||
import 'package:cute_kitchen/src/services/api/api_service.dart';
|
||
import 'package:cute_kitchen/src/services/system/orientation_service.dart';
|
||
import 'package:cute_kitchen/src/services/ui/theme_service.dart';
|
||
import 'package:cute_kitchen/src/services/ui/toast_service.dart';
|
||
import 'package:cute_kitchen/src/services/data/storage/storage_service.dart';
|
||
import 'package:cute_kitchen/src/services/system/crash_guard_service.dart';
|
||
|
||
import 'package:cute_kitchen/src/app_binding.dart';
|
||
|
||
import 'package:cute_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<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 DesktopScrollBehavior extends MaterialScrollBehavior {
|
||
@override
|
||
Set<PointerDeviceKind> get dragDevices => {
|
||
PointerDeviceKind.touch,
|
||
PointerDeviceKind.mouse,
|
||
PointerDeviceKind.stylus,
|
||
PointerDeviceKind.trackpad,
|
||
};
|
||
}
|
||
|
||
class MyApp extends StatefulWidget {
|
||
const MyApp({super.key});
|
||
|
||
@override
|
||
State<MyApp> createState() => _MyAppState();
|
||
}
|
||
|
||
class _MyAppState extends State<MyApp> {
|
||
final FocusNode _rootFocusNode = FocusNode();
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
// 初始禁止 root focus 请求,避免在首次布局前触发全局焦点遍历
|
||
_rootFocusNode.canRequestFocus = false;
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
if (mounted) {
|
||
_rootFocusNode.canRequestFocus = true;
|
||
}
|
||
});
|
||
}
|
||
|
||
ThemeService _getThemeService() {
|
||
if (Get.isRegistered<ThemeService>()) {
|
||
return Get.find<ThemeService>();
|
||
}
|
||
return Get.put(ThemeService.instance, permanent: true);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_rootFocusNode.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final themeService = _getThemeService();
|
||
|
||
return Obx(() {
|
||
final textScale = themeService.fontSize.value / 16.0;
|
||
return GetCupertinoApp(
|
||
title: '小妈厨房',
|
||
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: _rootFocusNode,
|
||
onKeyEvent: (event) {
|
||
if (event is KeyDownEvent) {
|
||
final isAltLeft =
|
||
HardwareKeyboard.instance.isAltPressed &&
|
||
event.logicalKey == LogicalKeyboardKey.arrowLeft;
|
||
final isEscape = event.logicalKey == LogicalKeyboardKey.escape;
|
||
final isBrowserBack =
|
||
event.logicalKey == LogicalKeyboardKey.browserBack;
|
||
if (isAltLeft || isEscape || 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>();
|
||
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');
|
||
}
|