393 lines
14 KiB
Dart
Executable File
393 lines
14 KiB
Dart
Executable File
import 'dart:io';
|
||
import 'dart:async';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
// import 'package:flutter_easyloading/flutter_easyloading.dart'; // 已替换为自定义组件
|
||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||
import 'package:flutter/services.dart';
|
||
|
||
// import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
|
||
|
||
import 'package:get/get.dart';
|
||
// import 'package:flutter_stripe/flutter_stripe.dart';
|
||
|
||
import 'package:kaer_with_panels/app/themes/kr_theme_service.dart';
|
||
import 'package:kaer_with_panels/app/localization/getx_translations.dart';
|
||
import 'package:kaer_with_panels/app/localization/kr_language_utils.dart';
|
||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
|
||
|
||
import 'package:kaer_with_panels/app/utils/kr_window_manager.dart';
|
||
import 'app/services/singbox_imp/kr_sing_box_imp.dart';
|
||
import 'app/common/app_run_data.dart';
|
||
import 'app/utils/kr_secure_storage.dart';
|
||
import 'app/common/app_config.dart';
|
||
import 'app/services/kr_site_config_service.dart';
|
||
import 'app/services/global_overlay_service.dart';
|
||
import 'app/utils/kr_log_util.dart';
|
||
import 'app/utils/kr_secure_storage.dart';
|
||
import 'app/utils/kr_init_log_collector.dart';
|
||
import 'app/utils/kr_file_logger.dart';
|
||
|
||
import 'package:kaer_with_panels/app/routes/transitions/slide_transparent_transition.dart';
|
||
import 'package:kaer_with_panels/app/routes/transitions/transition_config.dart';
|
||
|
||
// 全局导航键
|
||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||
|
||
void main() async {
|
||
// 🔧 全局异常捕获:捕获所有未处理的异常
|
||
runZonedGuarded(() async {
|
||
WidgetsFlutterBinding.ensureInitialized();
|
||
|
||
// 🔧 初始化日志收集器(必须最先初始化)
|
||
final logCollector = KRInitLogCollector();
|
||
// 🔧 P6修复: 必须await完成初始化,否则日志会丢失
|
||
await logCollector.initialize();
|
||
logCollector.log('🚀 应用启动', tag: 'MAIN');
|
||
|
||
// 🔧 诊断信息: 记录平台和设备信息
|
||
logCollector.log('平台: ${Platform.operatingSystem}', tag: 'DEVICE');
|
||
logCollector.log('系统版本: ${Platform.operatingSystemVersion}', tag: 'DEVICE');
|
||
logCollector.log('Dart版本: ${Platform.version}', tag: 'DEVICE');
|
||
|
||
// 🔧 Flutter框架异常捕获
|
||
FlutterError.onError = (FlutterErrorDetails details) {
|
||
logCollector.logError(
|
||
'Flutter框架异常: ${details.exception}',
|
||
tag: 'FLUTTER_ERROR',
|
||
error: details.exception,
|
||
stackTrace: details.stack,
|
||
);
|
||
FlutterError.presentError(details);
|
||
};
|
||
|
||
// 🔧 异步异常捕获(PlatformDispatcher)
|
||
PlatformDispatcher.instance.onError = (error, stack) {
|
||
logCollector.logError(
|
||
'平台异步异常: $error',
|
||
tag: 'PLATFORM_ERROR',
|
||
error: error,
|
||
stackTrace: stack,
|
||
);
|
||
return true;
|
||
};
|
||
|
||
await _initializeApp(logCollector);
|
||
}, (error, stack) {
|
||
// 🔧 Zone捕获的未处理异常
|
||
final logCollector = KRInitLogCollector();
|
||
logCollector.logError(
|
||
'Zone未处理异常: $error',
|
||
tag: 'ZONE_ERROR',
|
||
error: error,
|
||
stackTrace: stack,
|
||
);
|
||
});
|
||
}
|
||
|
||
Future<void> _initializeApp(KRInitLogCollector logCollector) async {
|
||
try {
|
||
logCollector.logPhaseStart('初始化核心服务');
|
||
|
||
// 🔧 初始化文件日志系统(用于诊断 UI 卡死问题)
|
||
logCollector.log('初始化文件日志系统', tag: 'FILE_LOGGER');
|
||
await KRFileLogger.initialize();
|
||
await KRFileLogger.log('📝 文件日志系统已启动');
|
||
logCollector.logSuccess('文件日志系统初始化完成', tag: 'FILE_LOGGER');
|
||
|
||
// 为所有 HttpClient 请求统一注入代理策略
|
||
logCollector.log('配置 HTTP 代理策略', tag: 'HTTP');
|
||
HttpOverrides.global = KRProxyHttpOverrides();
|
||
|
||
// 初始化 Hive
|
||
logCollector.log('初始化 Hive 数据库', tag: 'HIVE');
|
||
await KRSecureStorage().kr_initHive();
|
||
logCollector.logSuccess('Hive 初始化完成', tag: 'HIVE');
|
||
|
||
// 初始化主题
|
||
logCollector.log('初始化主题服务', tag: 'THEME');
|
||
await KRThemeService().init();
|
||
logCollector.logSuccess('主题初始化完成', tag: 'THEME');
|
||
|
||
// 初始化翻译
|
||
logCollector.log('加载多语言翻译', tag: 'I18N');
|
||
final translations = GetxTranslations();
|
||
await translations.loadAllTranslations();
|
||
logCollector.logSuccess('翻译加载完成', tag: 'I18N');
|
||
|
||
// 获取最后保存的语言
|
||
final initialLocale = await KRLanguageUtils.getLastSavedLocale();
|
||
logCollector.log('使用语言: ${initialLocale.toString()}', tag: 'I18N');
|
||
|
||
logCollector.logPhaseEnd('初始化核心服务', success: true);
|
||
|
||
// 启动域名预检测(异步,不阻塞应用启动)
|
||
logCollector.log('启动域名预检测', tag: 'DOMAIN');
|
||
KRDomain.kr_preCheckDomains();
|
||
|
||
logCollector.log('✅ 核心服务初始化完成,启动应用', tag: 'MAIN');
|
||
// 🔧 P9修复: 不要立即finalize,让日志文件保持打开以收集后续信息
|
||
// finalize会在应用关闭或_AppLifecycleWrapper的dispose时调用
|
||
|
||
// 初始化全局 Overlay 服务
|
||
Get.put(GlobalOverlayService());
|
||
|
||
// 🔧 关键修复:必须先 runApp(),让 Flutter 引擎启动
|
||
// 窗口管理器初始化移到 runApp 之后,通过 WidgetsBinding.addPostFrameCallback 延迟执行
|
||
// 这样可以确保窗口显示时 Flutter UI 已经渲染完成
|
||
runApp(_myApp(translations, initialLocale));
|
||
|
||
// 🔧 在 Flutter 引擎启动后再初始化窗口管理器
|
||
if (Platform.isMacOS || Platform.isWindows) {
|
||
// 使用 addPostFrameCallback 确保在首帧渲染后再显示窗口
|
||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||
logCollector.logPhaseStart('初始化窗口管理器');
|
||
await KRWindowManager().kr_initWindowManager();
|
||
logCollector.logPhaseEnd('初始化窗口管理器', success: true);
|
||
});
|
||
}
|
||
} catch (error, stackTrace) {
|
||
logCollector.logError(
|
||
'应用初始化失败',
|
||
tag: 'INIT_FATAL',
|
||
error: error,
|
||
stackTrace: stackTrace,
|
||
);
|
||
await logCollector.finalize();
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
Widget _myApp(GetxTranslations translations, Locale initialLocale) {
|
||
return _AppLifecycleWrapper(
|
||
child: GetMaterialApp(
|
||
navigatorKey: navigatorKey, // 使用全局导航键
|
||
title: "Hi快VPN",
|
||
initialRoute: Routes.KR_SPLASH,
|
||
getPages: AppPages.routes,
|
||
builder: (context, child) {
|
||
// 🔧 P2修复: 将ScreenUtil初始化移到StatefulWidget中,避免重复初始化
|
||
return _ScreenUtilInitializer(
|
||
child: child ?? Container(),
|
||
);
|
||
},
|
||
theme: KRThemeService().kr_lightTheme(),
|
||
darkTheme: KRThemeService().kr_darkTheme(),
|
||
themeMode: KRThemeService().kr_Theme,
|
||
translations: translations,
|
||
locale: initialLocale,
|
||
fallbackLocale: const Locale('zh', 'CN'),
|
||
debugShowCheckedModeBanner: false,
|
||
transitionDuration: TransitionConfig.defaultDuration, // 设置动画持续时间
|
||
customTransition: TransitionConfig.createDefaultTransition(),
|
||
routingCallback: (routing) {
|
||
if (routing == null) return;
|
||
if(Routes.KR_PURCHASE_MEMBERSHIP.contains(routing.current)) return;
|
||
// 需要显示订阅按钮的路由列表
|
||
const showButtonRoutes = [
|
||
Routes.MR_LOGIN,
|
||
Routes.HI_MENU,
|
||
Routes.KR_HOME,
|
||
Routes.HI_USER_INFO,
|
||
Routes.KR_ORDER_STATUS,
|
||
];
|
||
print('routing.current${routing.current}');
|
||
GlobalOverlayService.instance.updateSubscriptionButtonColor(null);
|
||
if (showButtonRoutes.contains(routing.current)) {
|
||
GlobalOverlayService.instance.safeShowSubscriptionButton();
|
||
} else {
|
||
GlobalOverlayService.instance.hideSubscriptionButton();
|
||
}
|
||
},
|
||
)
|
||
);
|
||
}
|
||
|
||
/// 🔧 P2修复: ScreenUtil初始化包装器,避免重复初始化
|
||
/// 🔧 P0修复: 使用静态变量确保全局只初始化一次,防止Widget重建导致的状态丢失
|
||
class _ScreenUtilInitializer extends StatefulWidget {
|
||
final Widget child;
|
||
|
||
const _ScreenUtilInitializer({required this.child});
|
||
|
||
@override
|
||
State<_ScreenUtilInitializer> createState() => _ScreenUtilInitializerState();
|
||
}
|
||
|
||
class _ScreenUtilInitializerState extends State<_ScreenUtilInitializer> {
|
||
// 🔧 P0修复: 使用静态变量,确保即使Widget重建也不会重复初始化
|
||
static bool _isGlobalInitialized = false;
|
||
// 🔧 P0修复: 记录初始化时的屏幕尺寸,用于检测屏幕变化
|
||
static Size? _lastScreenSize;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final mediaQuery = MediaQuery.of(context);
|
||
final currentSize = mediaQuery.size;
|
||
|
||
// 🔧 P0修复: 只在以下情况初始化:
|
||
// 1. 从未初始化过
|
||
// 2. 屏幕尺寸发生显著变化(如旋转屏幕)
|
||
final needsInit = !_isGlobalInitialized ||
|
||
(_lastScreenSize != null &&
|
||
((_lastScreenSize!.width - currentSize.width).abs() > 50 ||
|
||
(_lastScreenSize!.height - currentSize.height).abs() > 50));
|
||
|
||
if (needsInit) {
|
||
// 🔧 P9修复: 记录屏幕信息(延迟记录,确保能写入文件)
|
||
if (kDebugMode || AppConfig.enableInitLogCollection) {
|
||
// 延迟记录,确保在UI线程空闲时执行
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
final logCollector = KRInitLogCollector();
|
||
logCollector.log('━━━ 屏幕信息诊断 ━━━', tag: 'SCREEN');
|
||
logCollector.log('屏幕尺寸: ${mediaQuery.size.width} x ${mediaQuery.size.height}', tag: 'SCREEN');
|
||
logCollector.log('设备像素比: ${mediaQuery.devicePixelRatio}', tag: 'SCREEN');
|
||
logCollector.log('文本缩放: ${mediaQuery.textScaler.scale(1.0)}', tag: 'SCREEN');
|
||
logCollector.log('安全区域: top=${mediaQuery.padding.top}, bottom=${mediaQuery.padding.bottom}', tag: 'SCREEN');
|
||
});
|
||
}
|
||
|
||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||
// 🔧 修复:设计尺寸改为实际窗口尺寸 800x668,确保字体和UI元素正确缩放
|
||
ScreenUtil.init(context,
|
||
designSize: const Size(800, 668), minTextAdapt: true);
|
||
if (kDebugMode || AppConfig.enableInitLogCollection) {
|
||
KRInitLogCollector().log('ScreenUtil初始化: 桌面模式 800x668', tag: 'SCREEN');
|
||
}
|
||
} else {
|
||
ScreenUtil.init(context,
|
||
designSize: const Size(375, 667), minTextAdapt: true);
|
||
if (kDebugMode || AppConfig.enableInitLogCollection) {
|
||
KRInitLogCollector().log('ScreenUtil初始化: 移动模式 375x667', tag: 'SCREEN');
|
||
}
|
||
}
|
||
_isGlobalInitialized = true;
|
||
_lastScreenSize = currentSize;
|
||
}
|
||
|
||
// 如果是 Mac 平台,添加顶部安全区域
|
||
Widget wrappedChild = widget.child;
|
||
if (Platform.isMacOS) {
|
||
wrappedChild = MediaQuery(
|
||
data: MediaQuery.of(context).copyWith(
|
||
padding: MediaQuery.of(context).padding.copyWith(
|
||
top: 10.w, // Mac 平台顶部安全区域
|
||
),
|
||
),
|
||
child: wrappedChild,
|
||
);
|
||
}
|
||
|
||
return Stack(
|
||
fit: StackFit.expand,
|
||
children: [
|
||
// 背景层:使用您的自定义组件
|
||
const KrLocalImage(
|
||
imageName: 'global-bg',
|
||
imageType: ImageType.jpg,
|
||
fit: BoxFit.cover, // 确保背景覆盖整个屏幕
|
||
),
|
||
// 内容层
|
||
wrappedChild,
|
||
Overlay(
|
||
initialEntries: [
|
||
OverlayEntry(builder: (_) => const SizedBox.shrink()), // 初始化空 Overlay
|
||
],
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 🔧 P3修复: 应用生命周期监听包装器
|
||
class _AppLifecycleWrapper extends StatefulWidget {
|
||
final Widget child;
|
||
|
||
const _AppLifecycleWrapper({required this.child});
|
||
|
||
@override
|
||
State<_AppLifecycleWrapper> createState() => _AppLifecycleWrapperState();
|
||
}
|
||
|
||
class _AppLifecycleWrapperState extends State<_AppLifecycleWrapper>
|
||
with WidgetsBindingObserver {
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
WidgetsBinding.instance.addObserver(this);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
// 🔧 P9修复: 应用关闭时finalize日志文件
|
||
KRInitLogCollector().finalize();
|
||
WidgetsBinding.instance.removeObserver(this);
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||
super.didChangeAppLifecycleState(state);
|
||
|
||
if (kDebugMode) {
|
||
print('🔄 应用生命周期变化: $state');
|
||
}
|
||
|
||
// 🔧 P3修复: 当应用从后台恢复时,重置关键服务
|
||
if (state == AppLifecycleState.resumed) {
|
||
if (kDebugMode) {
|
||
print('♻️ 应用恢复,开始重置关键服务...');
|
||
}
|
||
_resetCriticalServices();
|
||
}
|
||
}
|
||
|
||
/// 🔧 P3修复: 重置关键服务,防止状态污染
|
||
Future<void> _resetCriticalServices() async {
|
||
try {
|
||
// 🔧 P1修复: 重置主题服务
|
||
await KRThemeService().reset();
|
||
if (kDebugMode) {
|
||
print('✅ 主题服务已重置');
|
||
}
|
||
|
||
// 🔧 P1修复: 重置应用运行时数据
|
||
await KRAppRunData.getInstance().kr_resetRuntimeState();
|
||
if (kDebugMode) {
|
||
print('✅ 应用运行时数据已重置');
|
||
}
|
||
|
||
// 🔧 P1修复: 清理域名检测状态
|
||
KRDomain.kr_resetDomainState();
|
||
if (kDebugMode) {
|
||
print('✅ 域名状态已重置');
|
||
}
|
||
|
||
// 刷新UI
|
||
if (mounted) {
|
||
setState(() {});
|
||
}
|
||
} catch (e) {
|
||
if (kDebugMode) {
|
||
print('⚠️ 服务重置失败: $e');
|
||
}
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return widget.child;
|
||
}
|
||
}
|
||
|
||
/// 全局 HttpOverrides,确保所有 dart:io 网络请求遵循 sing-box 代理策略
|
||
class KRProxyHttpOverrides extends HttpOverrides {
|
||
@override
|
||
HttpClient createHttpClient(SecurityContext? context) {
|
||
final client = super.createHttpClient(context);
|
||
client.findProxy = (uri) => KRSingBoxImp.instance.kr_buildProxyRule();
|
||
return client;
|
||
}
|
||
} |