import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:kaer_with_panels/app/localization/kr_language_utils.dart'; import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; import 'package:kaer_with_panels/singbox/model/singbox_status.dart'; import 'package:kaer_with_panels/app/localization/app_translations.dart'; import 'package:window_manager/window_manager.dart'; import 'package:tray_manager/tray_manager.dart'; import 'dart:io' show Platform; import '../services/singbox_imp/kr_sing_box_imp.dart'; class KRWindowManager with WindowListener, TrayListener { static final KRWindowManager _instance = KRWindowManager._internal(); factory KRWindowManager() => _instance; KRWindowManager._internal(); /// 初始化窗口管理器 Future kr_initWindowManager() async { KRLogUtil.kr_i('kr_initWindowManager: 开始初始化窗口管理器'); await windowManager.ensureInitialized(); KRLogUtil.kr_i('kr_initWindowManager: 窗口管理器已初始化'); const WindowOptions windowOptions = WindowOptions( size: Size(375, 800), minimumSize: Size(375, 600), maximumSize: Size(420, 900), center: true, backgroundColor: Colors.white, skipTaskbar: false, title: 'Hi快VPN', titleBarStyle: TitleBarStyle.normal, windowButtonVisibility: true, ); await windowManager.waitUntilReadyToShow(windowOptions); KRLogUtil.kr_i('kr_initWindowManager: 窗口准备就绪'); // 先添加监听器 windowManager.addListener(this); KRLogUtil.kr_i('kr_initWindowManager: 已添加窗口监听器'); // 确保在 Windows 下正确设置窗口属性 if (Platform.isWindows) { await windowManager.setTitleBarStyle(TitleBarStyle.normal); await windowManager.setTitle('HiFastVPN'); await windowManager.setSize(const Size(375, 800)); await windowManager.setMinimumSize(const Size(375, 600)); await windowManager.setMaximumSize(const Size(420, 900)); await windowManager.setResizable(true); await windowManager.center(); await windowManager.show(); // 阻止窗口关闭 await windowManager.setPreventClose(true); } else { await windowManager.setTitle('HiFastVPN'); await windowManager.setSize(const Size(375, 800)); await windowManager.setMinimumSize(const Size(375, 600)); await windowManager.setMaximumSize(const Size(420, 900)); await windowManager.setResizable(true); await windowManager.center(); await windowManager.show(); // macOS 也需要显式显示窗口 } // 初始化托盘 await _initTray(); // 初始化平台通道 _initPlatformChannel(); KRLogUtil.kr_i('kr_initWindowManager: 初始化完成'); ever(KRLanguageUtils.kr_language, (_) { final Menu menu = Menu( items: [ MenuItem( label: AppTranslations.kr_tray.openDashboard, onClick: (_) => _showWindow(), ), if (Platform.isMacOS) MenuItem.separator(), if (Platform.isMacOS) MenuItem( label: AppTranslations.kr_tray.copyToTerminal, onClick: (_) => _copyToTerminal(), ), MenuItem.separator(), MenuItem( label: AppTranslations.kr_tray.exitApp, onClick: (_) => _exitApp(), ), ], ); trayManager.setContextMenu(menu); }); } /// 初始化平台通道 void _initPlatformChannel() { if (Platform.isMacOS) { const platform = MethodChannel('hifast_vpn/terminate'); platform.setMethodCallHandler((call) async { if (call.method == 'onTerminate') { KRLogUtil.kr_i('收到应用终止通知'); await _handleTerminate(); } }); } } /// 初始化托盘 Future _initTray() async { KRLogUtil.kr_i('_initTray: 开始初始化托盘'); trayManager.addListener(this); final String iconPath = Platform.isMacOS ? 'assets/images/tray_icon.png' : 'assets/images/tray_icon.ico'; await trayManager.setIcon(iconPath); // 初始化托盘 Future.delayed(const Duration(seconds: 1), () async { final Menu menu = Menu( items: [ MenuItem( label: AppTranslations.kr_tray.openDashboard, onClick: (_) => _showWindow(), ), if (Platform.isMacOS) MenuItem.separator(), if (Platform.isMacOS) MenuItem( label: AppTranslations.kr_tray.copyToTerminal, onClick: (_) => _copyToTerminal(), ), MenuItem.separator(), MenuItem( label: AppTranslations.kr_tray.exitApp, onClick: (_) => _exitApp(), ), ], ); await trayManager.setContextMenu(menu); }); } /// 复制到终端 Future _copyToTerminal() async { final String kr_port = KRSingBoxImp.instance.kr_port.toString(); final String proxyText = 'export https_proxy=http://127.0.0.1:$kr_port http_proxy=http://127.0.0.1:$kr_port all_proxy=socks5://127.0.0.1:$kr_port'; await Clipboard.setData(ClipboardData(text: proxyText)); } /// 退出应用 /// ✅ 改进:先恢复窗口(如果最小化),再显示对话框 Future _exitApp() async { KRLogUtil.kr_i('_exitApp: 退出应用'); // ✅ 关键修复:先恢复窗口(从最小化状态) // 这样可以确保对话框可见 try { await windowManager.show(); await windowManager.focus(); await windowManager.setAlwaysOnTop(true); KRLogUtil.kr_i('✅ 窗口已恢复,准备显示对话框', tag: 'WindowManager'); } catch (e) { KRLogUtil.kr_w('⚠️ 恢复窗口失败(可能已显示): $e', tag: 'WindowManager'); } // 🔧 修复:检查 VPN 是否在运行,如果运行则弹窗提醒用户 if (KRSingBoxImp.instance.kr_status.value is! SingboxStopped) { KRLogUtil.kr_w('⚠️ VPN 正在运行,询问用户是否关闭', tag: 'WindowManager'); // 显示确认对话框 final shouldExit = await Get.dialog( AlertDialog( title: Text('关闭 VPN'), content: Text("VPN 代理正在运行。\n\n是否现在关闭 VPN 并退出应用?\n\n(应用将等待 VPN 优雅关闭,预计 3-5 秒)"), actions: [ TextButton( onPressed: () => Get.back(result: false), child: Text('取消'), ), TextButton( onPressed: () => Get.back(result: true), child: Text('关闭并退出', style: const TextStyle(color: Colors.red)), ), ], ), barrierDismissible: false, ) ?? false; // ✅ 关键修复:对话框关闭后,恢复窗口的 AlwaysOnTop 状态 try { await windowManager.setAlwaysOnTop(false); } catch (e) { KRLogUtil.kr_w('⚠️ 恢复 AlwaysOnTop 失败: $e', tag: 'WindowManager'); } if (!shouldExit) { KRLogUtil.kr_i('_exitApp: 用户取消退出'); return; } KRLogUtil.kr_i('_exitApp: 用户确认关闭 VPN 并退出'); } else { // ✅ VPN 未运行,也要恢复 AlwaysOnTop 状态 try { await windowManager.setAlwaysOnTop(false); } catch (e) { KRLogUtil.kr_w('⚠️ 恢复 AlwaysOnTop 失败: $e', tag: 'WindowManager'); } } await _handleTerminate(); await windowManager.destroy(); } /// 显示窗口 Future _showWindow() async { KRLogUtil.kr_i('_showWindow: 开始显示窗口'); try { await windowManager.setSkipTaskbar(false); await windowManager.show(); await windowManager.focus(); await windowManager.setAlwaysOnTop(true); await Future.delayed(const Duration(milliseconds: 100)); await windowManager.setAlwaysOnTop(false); KRLogUtil.kr_i('_showWindow: 窗口显示成功'); } catch (e) { KRLogUtil.kr_e('_showWindow: 显示窗口失败 - $e'); } } @override void onWindowEvent(String eventName) { KRLogUtil.kr_i('onWindowEvent: 收到窗口事件 - $eventName'); // 移除 Windows 下自动显示窗口的逻辑 } @override void onWindowClose() async { if (Platform.isWindows) { await windowManager.setSkipTaskbar(true); await windowManager.hide(); } else if (Platform.isMacOS) { await windowManager.hide(); } } @override void onTrayIconMouseDown() { // 左键点击只显示菜单 trayManager.popUpContextMenu(); } @override void onTrayIconRightMouseDown() { // 右键点击只显示菜单 trayManager.popUpContextMenu(); } @override void onWindowFocus() { // 移除自动显示窗口的逻辑 } @override void onWindowBlur() { // 当窗口失去焦点时,保持窗口可见 } /// 设置窗口背景颜色 Future kr_setBackgroundColor(Color color) async { await windowManager.setBackgroundColor(color); } /// 处理应用终止 Future _handleTerminate() async { KRLogUtil.kr_i('_handleTerminate: 处理应用终止'); // 🔧 修复 BUG:正确检查 VPN 状态而不是直接比较 Rx 对象 // 之前的代码:if (KRSingBoxImp.instance.kr_status == SingboxStatus.started()) // 问题:kr_status 是 Rx 对象,不能直接与 SingboxStatus.started() 比较 // 结果:该条件总是 false,导致 kr_stop() 从不被调用,VPN 不会关闭 if (KRSingBoxImp.instance.kr_status.value is SingboxStarted) { KRLogUtil.kr_i('🛑 VPN 正在运行,开始关闭...', tag: 'WindowManager'); try { await KRSingBoxImp.instance.kr_stop(); KRLogUtil.kr_i('✅ VPN 已关闭', tag: 'WindowManager'); } catch (e) { KRLogUtil.kr_e('❌ VPN 关闭出错: $e', tag: 'WindowManager'); } } else { KRLogUtil.kr_i('✅ VPN 未运行,无需关闭', tag: 'WindowManager'); } // 销毁托盘 try { await trayManager.destroy(); KRLogUtil.kr_i('✅ 托盘已销毁', tag: 'WindowManager'); } catch (e) { KRLogUtil.kr_w('⚠️ 销毁托盘出错: $e', tag: 'WindowManager'); } } }