From b059d0155622f1ddf3ec86287a8be48907649acf Mon Sep 17 00:00:00 2001 From: speakeloudest Date: Wed, 26 Nov 2025 19:23:15 -0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=8A=82=E7=82=B9=E6=B5=8B=E9=80=9F?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/app/model/business/kr_outbound_item.dart | 373 +++++++------- .../controllers/hi_node_list_controller.dart | 50 +- .../hi_node_list/views/hi_node_list_view.dart | 364 +++++++------- .../controllers/kr_home_controller.dart | 461 +++++------------- .../views/hi_animated_connect_button.dart | 2 +- macos/Runner.xcodeproj/project.pbxproj | 3 + 6 files changed, 513 insertions(+), 740 deletions(-) diff --git a/lib/app/model/business/kr_outbound_item.dart b/lib/app/model/business/kr_outbound_item.dart index 325f2c5..bfc190f 100755 --- a/lib/app/model/business/kr_outbound_item.dart +++ b/lib/app/model/business/kr_outbound_item.dart @@ -28,66 +28,11 @@ class KROutboundItem { /// URL String url = ""; - // ✅ 1. 将传入的 nodeListItem 保存为类的 final 成员变量 - final KrNodeListItem nodeListItem; + /// 服务器类型 /// 构造函数,接受 KrItem 对象并初始化 KROutboundItem - KROutboundItem(this.nodeListItem) { - _initFromNodeListItem(); - } - - /// 静态工厂:构造虚拟 urltest 节点(用于 ${country}-auto) - factory KROutboundItem.fromVirtual(String tag, String country, Map config) { - // 构造一个虚拟 KrNodeListItem,仅填充必要字段 - final virtualNode = KrNodeListItem( - id: 0, - name: tag, - protocol: 'urltest', - serverAddr: '', - port: 0, - uuid: '', - config: jsonEncode(config), - city: '', - country: country, - tags: [], - latitude: 0, - longitude: 0, - latitudeCountry: 0, - longitudeCountry: 0, - relayNode: '', - relayMode: 'none', - protocols: '', - method: '', - speedLimit: 0, - traffic: 0, - trafficRatio: 0, - upload: 0, - download: 0, - startTime: '', - expireTime: '', - ); - return KROutboundItem._virtual(virtualNode); - } - - /// 私有构造:用于虚拟节点,避免重复解析 - KROutboundItem._virtual(this.nodeListItem) { - // 直接填充虚拟节点所需字段 - id = nodeListItem.id.toString(); - protocol = nodeListItem.protocol; - tag = nodeListItem.name; - serverAddr = nodeListItem.serverAddr; - city = nodeListItem.city; - country = nodeListItem.country; - latitude = nodeListItem.latitude; - latitudeCountry = nodeListItem.latitudeCountry; - longitude = nodeListItem.longitude; - longitudeCountry = nodeListItem.longitudeCountry; - config = jsonDecode(nodeListItem.config); // 已知 config 有效 - } - - /// 初始化逻辑提取,供主构造调用 - void _initFromNodeListItem() { + KROutboundItem(KrNodeListItem nodeListItem) { id = nodeListItem.id.toString(); protocol = nodeListItem.protocol; latitude = nodeListItem.latitude; @@ -110,163 +55,160 @@ class KROutboundItem { return; } - // 兜底:尝试从 config 字段解析(旧API格式) - if (nodeListItem.config.isNotEmpty) { - try { - final json = jsonDecode(nodeListItem.config) as Map; - if (kDebugMode) { - print('📄 解析到 config JSON: $json'); + // 兜底:尝试解析 config 字段(旧API格式) + if (nodeListItem.config.isEmpty) { + if (kDebugMode) { + print('❌ 节点 ${nodeListItem.name} 缺少配置信息(无port或config)'); + } + config = {}; + return; + } + + late Map json; + try { + json = jsonDecode(nodeListItem.config) as Map; + } catch (e) { + if (kDebugMode) { + print('❌ 节点 ${nodeListItem.name} 的 config 解析失败: $e,尝试使用直接字段'); + } + if (kDebugMode) { + print('📄 Config 内容: ${nodeListItem.config}'); + } + _buildConfigFromFields(nodeListItem); + return; + } + switch (nodeListItem.protocol) { + case "vless": + final securityConfig = + json["security_config"] as Map? ?? {}; + + // 智能设置 server_name + String serverName = securityConfig["sni"] ?? ""; + if (serverName.isEmpty) { + serverName = nodeListItem.serverAddr; } - // 提取 transport 配置 - Map? transportConfig; - if (json['transport'] != null && json['transport'] != 'tcp') { - transportConfig = _buildTransport(json); - if (kDebugMode) { - print('✅ 找到 transport 配置: $transportConfig'); - } - } - - // 提取 security_config - Map? securityConfig; - if (json['security_config'] != null) { - securityConfig = json['security_config'] as Map; - if (kDebugMode) { - print('✅ 找到 security_config: $securityConfig'); - } - } - - // 根据协议类型构建配置 - switch (nodeListItem.protocol) { - case "shadowsocks": - config = { - "type": "shadowsocks", - "tag": nodeListItem.name, - "server": nodeListItem.serverAddr, - "server_port": json["port"], - "method": json["method"], - "password": nodeListItem.uuid - }; - break; - case "vless": - final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') - .hasMatch(nodeListItem.serverAddr); - - config = { - "type": "vless", - "tag": nodeListItem.name, - "server": nodeListItem.serverAddr, - "server_port": json["port"], - "uuid": nodeListItem.uuid, - if (transportConfig != null) "transport": transportConfig, - if (json["security"] == "tls") "tls": { - "enabled": true, - if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr, - "insecure": securityConfig?["allow_insecure"] ?? true, - "utls": { - "enabled": true, - "fingerprint": securityConfig?["fingerprint"] ?? "chrome" - } - } - }; - break; - case "vmess": - final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') - .hasMatch(nodeListItem.serverAddr); - - config = { - "type": "vmess", - "tag": nodeListItem.name, - "server": nodeListItem.serverAddr, - "server_port": json["port"], - "uuid": nodeListItem.uuid, - "alter_id": 0, - "security": "auto", - if (transportConfig != null) "transport": transportConfig, - if (json["security"] == "tls") "tls": { - "enabled": true, - if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr, - "insecure": securityConfig?["allow_insecure"] ?? true, - "utls": { - "enabled": true, - "fingerprint": securityConfig?["fingerprint"] ?? "chrome" - } - } - }; - break; - case "trojan": - final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') - .hasMatch(nodeListItem.serverAddr); - - config = { - "type": "trojan", - "tag": nodeListItem.name, - "server": nodeListItem.serverAddr, - "server_port": json["port"], - "password": nodeListItem.uuid, - if (transportConfig != null) "transport": transportConfig, - "tls": { - "enabled": json["security"] == "tls", - if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr, - "insecure": securityConfig?["allow_insecure"] ?? true, - "utls": { - "enabled": true, - "fingerprint": securityConfig?["fingerprint"] ?? "chrome" - } - } - }; - break; - case "hysteria": - case "hysteria2": - final securityConfig = json["security_config"] as Map? ?? {}; - config = { - "type": "hysteria2", - "tag": nodeListItem.name, - "server": nodeListItem.serverAddr, - "server_port": json["port"], - "password": nodeListItem.uuid, - "up_mbps": 100, - "down_mbps": 100, - "obfs": { - "type": "salamander", - "password": json["obfs_password"] ?? nodeListItem.uuid - }, - "tls": { - "enabled": true, - "server_name": securityConfig["sni"] ?? "", - "insecure": securityConfig["allow_insecure"] ?? true - } - }; - break; - default: - if (kDebugMode) { - print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}'); + config = { + "type": "vless", + "tag": nodeListItem.name, + "server": nodeListItem.serverAddr, + "server_port": json["port"], + "uuid": nodeListItem.uuid, + if (json["flow"] != null && json["flow"] != "none") + "flow": json["flow"], + if (json["transport"] != null && json["transport"] != "tcp") + "transport": _buildTransport(json), + "tls": { + "enabled": json["security"] == "tls", + "server_name": serverName, + "insecure": securityConfig["allow_insecure"] ?? true, + "utls": { + "enabled": true, + "fingerprint": securityConfig["fingerprint"] ?? "chrome" } - config = {}; + } + }; + break; + case "vmess": + final securityConfig = + json["security_config"] as Map? ?? {}; + + // 智能设置 server_name + String serverName = securityConfig["sni"] ?? ""; + if (serverName.isEmpty) { + serverName = nodeListItem.serverAddr; } - // 检查 relayNode 是否为 JSON 字符串并解析 - if (nodeListItem.relayNode.isNotEmpty && nodeListItem.relayMode != "none") { - final relayNodeJson = jsonDecode(nodeListItem.relayNode); - if (relayNodeJson is List && nodeListItem.relayMode != "none") { - // 随机选择一个元素 - final randomNode = (relayNodeJson..shuffle()).first; - config["server"] = randomNode["host"]; // 提取 host - config["server_port"] = randomNode["port"]; // 提取 port + config = { + "type": "vmess", + "tag": nodeListItem.name, + "server": nodeListItem.serverAddr, + "server_port": json["port"], + "uuid": nodeListItem.uuid, + "alter_id": 0, + "security": "auto", + if (json["transport"] != null && json["transport"] != "tcp") + "transport": _buildTransport(json), + "tls": { + "enabled": json["security"] == "tls", + "server_name": serverName, + "insecure": securityConfig["allow_insecure"] ?? true, + "utls": {"enabled": true, "fingerprint": "chrome"} } + }; + break; + case "shadowsocks": + config = { + "type": "shadowsocks", + "tag": nodeListItem.name, + "server": nodeListItem.serverAddr, + "server_port": json["port"], + "method": json["method"], + "password": nodeListItem.uuid + }; + break; + case "hysteria": + case "hysteria2": + // 后端的 "hysteria" 实际上是 Hysteria2 协议 + final securityConfig = + json["security_config"] as Map? ?? {}; + config = { + "type": "hysteria2", + "tag": nodeListItem.name, + "server": nodeListItem.serverAddr, + "server_port": json["port"], + "password": nodeListItem.uuid, + "up_mbps": 100, + "down_mbps": 100, + "obfs": { + "type": "salamander", + "password": json["obfs_password"] ?? nodeListItem.uuid + }, + "tls": { + "enabled": true, + "server_name": securityConfig["sni"] ?? "", + "insecure": securityConfig["allow_insecure"] ?? true + } + }; + break; + case "trojan": + final securityConfig = + json["security_config"] as Map? ?? {}; + + // 智能设置 server_name + String serverName = securityConfig["sni"] ?? ""; + if (serverName.isEmpty) { + // 如果没有配置 SNI,使用服务器地址 + serverName = nodeListItem.serverAddr; } - } catch (e) { - if (kDebugMode) { - print('⚠️ 解析 config 字段失败: $e'); - } - config = {}; + + config = { + "type": "trojan", + "tag": nodeListItem.name, + "server": nodeListItem.serverAddr, + "server_port": json["port"], + "password": nodeListItem.uuid, + "tls": { + "enabled": json["security"] == "tls", + "server_name": serverName, + "insecure": securityConfig["allow_insecure"] ?? true, + "utls": {"enabled": true, "fingerprint": "chrome"} + } + }; + break; + } + + // 检查 relayNode 是否为 JSON 字符串并解析 + if (nodeListItem.relayNode.isNotEmpty && nodeListItem.relayMode != "none") { + final relayNodeJson = jsonDecode(nodeListItem.relayNode); + if (relayNodeJson is List && nodeListItem.relayMode != "none") { + // 随机选择一个元素 + final randomNode = (relayNodeJson..shuffle()).first; + config["server"] = randomNode["host"]; // 提取 host + config["server_port"] = randomNode["port"]; // 提取 port } } - } - - @override - String toString() { - return 'KROutboundItem(name: ${nodeListItem.name}, protocol: ${nodeListItem.protocol}, server: ${nodeListItem.serverAddr}, port: ${nodeListItem.port})'; + // 解析配置 } /// 构建传输配置 @@ -304,6 +246,27 @@ class KROutboundItem { if (kDebugMode) { print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}'); } + if (kDebugMode) { + print('📋 节点详细信息:'); + } + if (kDebugMode) { + print(' - serverAddr: ${nodeListItem.serverAddr}'); + } + if (kDebugMode) { + print(' - port: ${nodeListItem.port}'); + } + if (kDebugMode) { + print(' - uuid: ${nodeListItem.uuid}'); + } + if (kDebugMode) { + print(' - method: ${nodeListItem.method}'); + } + if (kDebugMode) { + print(' - config: ${nodeListItem.config}'); + } + if (kDebugMode) { + print(' - protocols: ${nodeListItem.protocols}'); + } // 🔧 尝试从 config 字段解析 transport 配置 Map? transportConfig; diff --git a/lib/app/modules/hi_node_list/controllers/hi_node_list_controller.dart b/lib/app/modules/hi_node_list/controllers/hi_node_list_controller.dart index 069e082..ebfa9fa 100644 --- a/lib/app/modules/hi_node_list/controllers/hi_node_list_controller.dart +++ b/lib/app/modules/hi_node_list/controllers/hi_node_list_controller.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart'; import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart'; +import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart'; import '../../../localization/app_translations.dart'; import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart'; @@ -40,52 +41,34 @@ class HINodeListController extends GetxController with WidgetsBindingObserver { KRLogUtil.kr_i('连接类型已更新为: $type', tag: 'HINodeListController'); // 这里可以添加其他需要的逻辑 } - - // 如果当前有选择的国家,触发重选检查 - if (homeController.currentSelectedCountry.isNotEmpty) { - KRLogUtil.kr_i('连接类型更新后,检查是否需要重选节点', tag: 'HINodeListController'); - // 延迟一下让连接类型更新完成 - Future.delayed(const Duration(milliseconds: 500), () { - // homeController.checkCountryReselection(KRSingBoxImp.instance.kr_activeGroups); - }); - } } /// 处理模式按钮点击(用于激活调试模式) void kr_handleModeButtonClick() { final now = DateTime.now(); - + // 检查是否在2秒内连续点击 - if (lastModeButtonClickTime != null && + if (lastModeButtonClickTime != null && now.difference(lastModeButtonClickTime!).inSeconds > 2) { // 超过2秒,重置计数器 modeButtonClickCount = 0; } - + modeButtonClickCount++; lastModeButtonClickTime = now; - - KRLogUtil.kr_i('模式按钮点击次数: $modeButtonClickCount', tag: 'HINodeListController'); - + + KRLogUtil.kr_i('模式按钮点击次数: $modeButtonClickCount', + tag: 'HINodeListController'); + if (modeButtonClickCount >= 5) { // 激活调试模式 isDebugMode.value = true; modeButtonClickCount = 0; // 重置计数器 KRLogUtil.kr_i('🐛 调试模式已激活!', tag: 'HINodeListController'); - } } /// 获取要显示的节点列表(根据调试模式过滤) - List kr_getFilteredNodeList() { - if (isDebugMode.value) { - // 调试模式:显示所有节点 - return kr_subscribeService.allList; - } else { - // 正常模式:只显示 country-auto 节点 - return kr_subscribeService.allList.where((node) => node.tag.endsWith('-auto')).toList(); - } - } /// 重置调试模式 void kr_resetDebugMode() { @@ -97,6 +80,15 @@ class HINodeListController extends GetxController with WidgetsBindingObserver { Future kr_handleRefresh() async { await kr_subscribeService.kr_refreshAll(); + try { + final savedNode = + await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG'); + if (savedNode != null && savedNode.isNotEmpty) { + homeController.kr_currentNodeName.value = savedNode; + homeController.kr_cutTag.value = savedNode; + homeController.kr_cutSeletedTag.value = savedNode; + } + } catch (_) {} if (!homeController.kr_isLatency.value) { homeController.kr_urlTest(); } @@ -106,11 +98,6 @@ class HINodeListController extends GetxController with WidgetsBindingObserver { void onInit() { super.onInit(); WidgetsBinding.instance.addObserver(this); - ever(homeController.kr_cutTag, (tag) { - if (homeController.kr_isLatency.value) return; - KRLogUtil.kr_i('🔄 节点切换成功 - 自动触发延迟测试', tag: 'HINodeListView'); - homeController.kr_urlTest(); - }); } @override @@ -126,7 +113,7 @@ class HINodeListController extends GetxController with WidgetsBindingObserver { super.didChangeAppLifecycleState(state); // 当应用从后台切换回前台时 if (state == AppLifecycleState.resumed) { - if (homeController.kr_isLatency.value) return; + if (homeController.kr_isLatency.value) return; KRLogUtil.kr_i('🔄 节点列表显示 - 自动触发延迟测试', tag: 'HINodeListView'); homeController.kr_urlTest(); } @@ -137,5 +124,4 @@ class HINodeListController extends GetxController with WidgetsBindingObserver { WidgetsBinding.instance.removeObserver(this); super.onClose(); } - } diff --git a/lib/app/modules/hi_node_list/views/hi_node_list_view.dart b/lib/app/modules/hi_node_list/views/hi_node_list_view.dart index 0648876..2a75c1a 100755 --- a/lib/app/modules/hi_node_list/views/hi_node_list_view.dart +++ b/lib/app/modules/hi_node_list/views/hi_node_list_view.dart @@ -26,13 +26,14 @@ class HINodeListView extends GetView { /// 获取分组内最快节点的延迟值(单位:ms) /// 如果列表为空,返回 0 int getFastestNodeDelay( - HINodeListController controller, - List outboundList, - ) { + HINodeListController controller, + List outboundList, + ) { if (outboundList.isEmpty) return 0; // 返回最小延迟值 - outboundList.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value)); + outboundList + .sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value)); return outboundList.first.urlTestDelay.value; } @@ -152,36 +153,39 @@ class HINodeListView extends GetView { ], ), ), - Obx(() => controller.homeController.kr_coutryText.value == 'auto' - ? KrLocalImage( - imageName: 'radio-active-icon', - imageType: ImageType.svg, - width: 16.w, // 适当缩小 SVG 尺寸,留出白边 - height: 16.h, - ) - : KrLocalImage( - imageName: 'radio-icon', - imageType: ImageType.svg, - width: 16.w, // 适当缩小 SVG 尺寸,留出白边 - height: 16.h, - )), + Obx(() => + controller.homeController.kr_cutSeletedTag.value == + 'auto' + ? KrLocalImage( + imageName: 'radio-active-icon', + imageType: ImageType.svg, + width: 16.w, // 适当缩小 SVG 尺寸,留出白边 + height: 16.h, + ) + : KrLocalImage( + imageName: 'radio-icon', + imageType: ImageType.svg, + width: 16.w, // 适当缩小 SVG 尺寸,留出白边 + height: 16.h, + )), ], ), ), ), - ...controller.kr_subscribeService.countryOutboundList.map((country) { + ...controller.kr_subscribeService.countryOutboundList + .map((country) { return InkWell( onTap: () async { try { - final success = - await controller.homeController.kr_performNodeSwitch('auto'); + final fastest = findFastestNode(country.outboundList); + final success = await controller.homeController + .kr_performNodeSwitch(fastest.tag); if (success) { controller.homeController.kr_currentListStatus.value = KRHomeViewsListStatus.kr_none; } } catch (e) { - KRLogUtil.kr_e('Auto选项切换异常: $e', - tag: 'NodeListView'); + KRLogUtil.kr_e('国家选择切换异常: $e', tag: 'NodeListView'); } }, child: _kr_buildCountryListItem(context, country: country), @@ -189,86 +193,87 @@ class HINodeListView extends GetView { }).toList(), ] ] // - ), + ), ); }); } + /// 构建默认的订阅节点列表 Widget _buildSubscribeList(BuildContext context) { return Obx(() { return _kr_buildListContainer( context, child: ListView( - padding: EdgeInsets.symmetric(vertical: 8.w), - // 2. 使用 children 属性,并一次性构建所有列表项 - children: [ - ...[ - InkWell( - // 🔧 修复:改为 async,等待节点切换完成后再关闭列表 - onTap: () async { - try { - final success = - await controller.homeController.kr_performNodeSwitch('auto'); - if (success) { - controller.homeController.kr_currentListStatus.value = - KRHomeViewsListStatus.kr_none; - } - } catch (e) { - KRLogUtil.kr_e('Auto选项切换异常: $e', - tag: 'NodeListView'); - } - }, - child: Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white.withOpacity(0.3), - width: 1.0, - ), - ), - ), - padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - width: 32.w, - height: 22.w, + padding: EdgeInsets.symmetric(vertical: 8.w), + // 2. 使用 children 属性,并一次性构建所有列表项 + children: [ + ...[ + InkWell( + // 🔧 修复:改为 async,等待节点切换完成后再关闭列表 + onTap: () async { + try { + final success = await controller.homeController + .kr_performNodeSwitch('auto'); + if (success) { + controller.homeController.kr_currentListStatus.value = + KRHomeViewsListStatus.kr_none; + } + } catch (e) { + KRLogUtil.kr_e('Auto选项切换异常: $e', tag: 'NodeListView'); + } + }, + child: Container( decoration: BoxDecoration( - // 2. 设置背景色为主题色 - color: Theme.of(context).primaryColor, - ), - // 4. 使用 Center 来确保内部的图片水平和垂直居中 - child: Center( - child: KrLocalImage( - imageName: "hi-home-logo", - width: 14.w, - height: 14.h, + border: Border( + bottom: BorderSide( + color: Colors.white.withOpacity(0.3), + width: 1.0, + ), ), ), - ), - SizedBox(width: 8.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + padding: + EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - '自动匹配最快网络', // 您指定的文本 - style: KrAppTextStyle( - fontSize: 14, - color: Colors.white, - fontWeight: FontWeight.w600, // 600 加粗 + Container( + width: 32.w, + height: 22.w, + decoration: BoxDecoration( + // 2. 设置背景色为主题色 + color: Theme.of(context).primaryColor, + ), + // 4. 使用 Center 来确保内部的图片水平和垂直居中 + child: Center( + child: KrLocalImage( + imageName: "hi-home-logo", + width: 14.w, + height: 14.h, + ), ), ), - // 2. 第二个 Text: "根据网络IP自动匹配最快线路" - Text( - '根据网络IP自动匹配最快线路', // 默认文本 - style: KrAppTextStyle( - fontSize: 10, - color: Colors.white, - ), - ), - /* Obx(() { + SizedBox(width: 8.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '自动匹配最快网络', // 您指定的文本 + style: KrAppTextStyle( + fontSize: 14, + color: Colors.white, + fontWeight: FontWeight.w600, // 600 加粗 + ), + ), + // 2. 第二个 Text: "根据网络IP自动匹配最快线路" + Text( + '根据网络IP自动匹配最快线路', // 默认文本 + style: KrAppTextStyle( + fontSize: 10, + color: Colors.white, + ), + ), + /* Obx(() { // 当选择全局 auto 时,显示当前选中的节点信息 if (controller.homeController.kr_cutTag.value == 'auto') { final autoNodeInfo = controller.homeController.kr_getGlobalAutoSelectedNode(); @@ -290,51 +295,49 @@ class HINodeListView extends GetView { ), ); }),*/ + ], + ), + ), + Obx(() => + controller.homeController.kr_cutTag.value == 'auto' + ? KrLocalImage( + imageName: 'radio-active-icon', + imageType: ImageType.svg, + width: 16.w, // 适当缩小 SVG 尺寸,留出白边 + height: 16.h, + ) + : KrLocalImage( + imageName: 'radio-icon', + imageType: ImageType.svg, + width: 16.w, // 适当缩小 SVG 尺寸,留出白边 + height: 16.h, + )), ], ), ), - Obx(() => controller.homeController.kr_cutTag.value == 'auto' - ? KrLocalImage( - imageName: 'radio-active-icon', - imageType: ImageType.svg, - width: 16.w, // 适当缩小 SVG 尺寸,留出白边 - height: 16.h, - ) - : KrLocalImage( - imageName: 'radio-icon', - imageType: ImageType.svg, - width: 16.w, // 适当缩小 SVG 尺寸,留出白边 - height: 16.h, - )), - ], - ), + ), + ...controller.kr_subscribeService.allList().map((item) { + return InkWell( + // 🔧 修复:改为 async,等待节点切换完成后再关闭列表 + onTap: () async { + try { + KRLogUtil.kr_i('🔄 用户点击节点: ${item.tag}'); + final success = await controller.homeController + .kr_performNodeSwitch(item.tag); + if (success) { + controller.homeController.kr_currentListStatus.value = + KRHomeViewsListStatus.kr_none; + } + } catch (e) { + KRLogUtil.kr_e('节点切换异常: $e', tag: 'NodeListView'); + } + }, + child: _kr_buildNodeListItem(context, item: item), + ); + }).toList(), + ] + ] // ), - ), - ...controller.kr_subscribeService.allList().map((item) { - return InkWell( - // 🔧 修复:改为 async,等待节点切换完成后再关闭列表 - onTap: () async { - try { - KRLogUtil.kr_i( - '🔄 用户点击节点: ${item.tag}'); - final success = await controller.homeController - .kr_performNodeSwitch(item.tag); - if (success) { - controller.homeController.kr_currentListStatus.value = - KRHomeViewsListStatus.kr_none; - } - } catch (e) { - KRLogUtil.kr_e( - '节点切换异常: $e', - tag: 'NodeListView'); - } - }, - child: _kr_buildNodeListItem(context, item: item), - ); - }).toList(), - ] - ] // - ), ); }); } @@ -346,7 +349,8 @@ class HINodeListView extends GetView { alignment: Alignment.center, child: Text( text, - style: KrAppTextStyle(fontSize: 14, color: Theme.of(context).textTheme.bodySmall?.color), + style: KrAppTextStyle( + fontSize: 14, color: Theme.of(context).textTheme.bodySmall?.color), ), ); } @@ -363,7 +367,8 @@ class HINodeListView extends GetView { } /// 构建单个节点列表项的UI - Widget _kr_buildNodeListItem(BuildContext context, {required KROutboundItem item}) { + Widget _kr_buildNodeListItem(BuildContext context, + {required KROutboundItem item}) { return Container( key: ValueKey(item.id), decoration: BoxDecoration( @@ -378,7 +383,12 @@ class HINodeListView extends GetView { padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w), child: Row( children: [ - KRCountryFlag(countryCode: item.country, width: 30.w, height: 20.w, isCircle: false, maintainSize: false), + KRCountryFlag( + countryCode: item.country, + width: 30.w, + height: 20.w, + isCircle: false, + maintainSize: false), SizedBox(width: 12.w), Expanded( child: Obx(() { @@ -404,7 +414,8 @@ class HINodeListView extends GetView { bool isTesting = controller.homeController.kr_isLatency.value; // 极简逻辑:只根据节点类型决定显示方式 - print('🔄 _kr_buildNodeListItem节点: item.tag=${item.tag}, country=${item.country}'); + print( + '🔄 _kr_buildNodeListItem节点: item.tag=${item.tag}, country=${item.country}'); // 普通节点,直接显示节点自身的速度 displayDelay = item.urlTestDelay.value; @@ -441,19 +452,23 @@ class HINodeListView extends GetView { ); }), SizedBox(width: 12.w), - Obx(() => controller.homeController.kr_cutTag.value == item.tag - ? KrLocalImage( - imageName: 'radio-active-icon', - imageType: ImageType.svg, - width: 16.w, // 适当缩小 SVG 尺寸,留出白边 - height: 16.h, - ) - : KrLocalImage( - imageName: 'radio-icon', - imageType: ImageType.svg, - width: 16.w, // 适当缩小 SVG 尺寸,留出白边 - height: 16.h, - )), + Obx(() { + final selected = + controller.homeController.kr_cutSeletedTag.value == item.tag; + return selected + ? KrLocalImage( + imageName: 'radio-active-icon', + imageType: ImageType.svg, + width: 16.w, + height: 16.h, + ) + : KrLocalImage( + imageName: 'radio-icon', + imageType: ImageType.svg, + width: 16.w, + height: 16.h, + ); + }), ], ), ); @@ -461,7 +476,6 @@ class HINodeListView extends GetView { /// 构建国家列表项的UI Widget _kr_buildCountryListItem(BuildContext context, {required country}) { - return Container( key: ValueKey(country), decoration: BoxDecoration( @@ -476,17 +490,26 @@ class HINodeListView extends GetView { padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w), child: Row( children: [ - KRCountryFlag(countryCode: country.country, width: 30.w, height: 20.w, isCircle: false, maintainSize: false), + KRCountryFlag( + countryCode: country.country, + width: 30.w, + height: 20.w, + isCircle: false, + maintainSize: false), SizedBox(width: 12.w), Expanded( child: Text( controller.homeController.kr_getCountryFullName(country.country), - style: KrAppTextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Colors.white), + style: KrAppTextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.white), ), ), Obx(() { // 1. 获取延迟值和测速状态 - int displayDelay = getFastestNodeDelay(controller, country.outboundList); + int displayDelay = + getFastestNodeDelay(controller, country.outboundList); bool isTesting = controller.homeController.kr_isLatency.value; // 2. 声明文本和颜色变量 @@ -520,21 +543,36 @@ class HINodeListView extends GetView { ); }), SizedBox(width: 12.w), - Obx(() => controller.homeController.kr_cutTag.value == '${country.country}-auto' - ? KrLocalImage( - imageName: 'radio-active-icon', - imageType: ImageType.svg, - width: 16.w, // 适当缩小 SVG 尺寸,留出白边 - height: 16.h, - ) - : KrLocalImage( - imageName: 'radio-icon', - imageType: ImageType.svg, - width: 16.w, // 适当缩小 SVG 尺寸,留出白边 - height: 16.h, - )), + Obx(() { + final selectedTag = + controller.homeController.kr_cutSeletedTag.value; + if (selectedTag == 'auto') { + return KrLocalImage( + imageName: 'radio-icon', + imageType: ImageType.svg, + width: 16.w, + height: 16.h, + ); + } + final node = controller.kr_subscribeService.keyList[selectedTag]; + final selectedCountry = node?.country ?? ''; + final selected = selectedCountry == country.country; + return selected + ? KrLocalImage( + imageName: 'radio-active-icon', + imageType: ImageType.svg, + width: 16.w, + height: 16.h, + ) + : KrLocalImage( + imageName: 'radio-icon', + imageType: ImageType.svg, + width: 16.w, + height: 16.h, + ); + }), ], ), ); } -} \ No newline at end of file +} diff --git a/lib/app/modules/kr_home/controllers/kr_home_controller.dart b/lib/app/modules/kr_home/controllers/kr_home_controller.dart index b977ca2..8b24e35 100755 --- a/lib/app/modules/kr_home/controllers/kr_home_controller.dart +++ b/lib/app/modules/kr_home/controllers/kr_home_controller.dart @@ -124,10 +124,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 闪连状态存储键 static const String _quickConnectKey = 'kr_quick_connect_enabled'; - // 国家内节点重选相关属性 - final RxString currentSelectedCountry = ''.obs; - final int countryReselectionLatencyThreshold = 3000; // 延迟阈值(毫秒) - // 添加一个方法来切换状态 void toggleQuickConnect(bool? value) async { // 只有当传入的值不为 null 时才更新状态 @@ -395,10 +391,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { _kr_addStatusSyncCheck(); if (AppConfig().kr_is_daytime == true) { - Future.delayed(const Duration(seconds: 1), () { + Future.delayed(const Duration(seconds: 5), () { KRUpdateUtil().kr_checkUpdate(); - Future.delayed(const Duration(seconds: 1), () { + Future.delayed(const Duration(seconds: 5), () { // 不做语言切换 // KRLanguageSwitchDialog.kr_show(); }); @@ -552,7 +548,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { if (isValidLogin) { kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn; KRLogUtil.kr_i('登录状态变化:设置为已登录', tag: 'HomeController'); - + KRAnnouncementService().kr_checkAnnouncement(); // 订阅服务已在 splash 页面初始化,此处无需重复初始化 @@ -590,9 +586,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { try { final currentLoginStatus = KRAppRunData().kr_isLogin.value; final currentViewStatus = kr_currentViewStatus.value; - + KRLogUtil.kr_i('状态同步检查: login=$currentLoginStatus, view=$currentViewStatus', tag: 'HomeController'); - + // 检查状态是否一致 if (currentViewStatus == KRHomeViewsStatus.kr_loggedIn && !currentLoginStatus) { KRLogUtil.kr_w('状态不一致:视图显示已登录但实际未登录,修正状态', tag: 'HomeController'); @@ -692,13 +688,15 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { ever(KRSingBoxImp.instance.kr_status, (status) { KRLogUtil.kr_i('🔄 连接状态变化: $status', tag: 'HomeController'); KRLogUtil.kr_i('📊 当前状态类型: ${status.runtimeType}', tag: 'HomeController'); - + switch (status) { case SingboxStopped(): KRLogUtil.kr_i('🔴 状态: 已停止', tag: 'HomeController'); kr_connectText.value = AppTranslations.kr_home.disconnected; kr_stopConnectionTimer(); kr_resetConnectionInfo(); + // 取消连接超时处理 + _cancelConnectionTimeout(); kr_currentSpeed.value = "--"; kr_isLatency.value = false; kr_isConnected.value = false; @@ -750,12 +748,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { break; case SingboxStopping(): KRLogUtil.kr_i('🟠 状态: 正在停止', tag: 'HomeController'); + // 取消连接超时处理 + _cancelConnectionTimeout(); kr_connectText.value = AppTranslations.kr_home.disconnecting; kr_isConnected.value = false; kr_currentSpeed.value = "--"; break; } - + // 强制更新UI update(); }); @@ -839,7 +839,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) { KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', tag: 'HomeController'); if (kDebugMode) { - } + } return; } @@ -848,12 +848,11 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 开启连接 KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController'); if (kDebugMode) { - } + } await KRSingBoxImp.instance.kr_start(); - KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController'); if (kDebugMode) { - } + } // 🔧 修复: 等待状态更新,最多3秒 await _waitForStatus(SingboxStarted, maxSeconds: 3); @@ -861,7 +860,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 关闭连接 KRLogUtil.kr_i('🛑 开始断开连接...', tag: 'HomeController'); if (kDebugMode) { - } + } await KRSingBoxImp.instance.kr_stop().timeout( const Duration(seconds: 10), onTimeout: () { @@ -871,7 +870,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { ); KRLogUtil.kr_i('✅ 断开命令已发送', tag: 'HomeController'); if (kDebugMode) { - } + } // 🔧 修复: 等待状态更新,最多2秒 await _waitForStatus(SingboxStopped, maxSeconds: 2); @@ -879,13 +878,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } catch (e) { KRLogUtil.kr_e('❌ 切换失败: $e', tag: 'HomeController'); if (kDebugMode) { - } + } // 发生错误时强制同步状态 kr_forceSyncConnectionStatus(); } if (kDebugMode) { - } + } } /// 🔧 等待状态达到预期值 @@ -1096,14 +1095,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { return node.urlTestDelay.value < 65535 && node.urlTestDelay.value > 0; } - - /// 设置当前选择的国家(由hi_node_list_controller调用) - void setCurrentSelectedCountry(String country) { - currentSelectedCountry.value = country; - KRLogUtil.kr_i('🌍 设置当前选择国家: $country', tag: 'HomeController'); - } - - /// 更新自动模式延迟 void _kr_updateAutoLatency(dynamic element) { for (var subElement in element.items) { @@ -1137,11 +1128,36 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } } + // 🔒 节点切换状态锁和节流控制 + bool _isSwitchingNode = false; + DateTime? _lastSwitchTime; + static const Duration _switchThrottleDuration = Duration(milliseconds: 2000); // 2秒节流 + /// 🔧 修复:统一的节点切换方法(包含UI同步和后台操作等待) /// 执行节点切换,包括UI更新和后台操作的完整同步 /// 返回 true 表示切换成功,false 表示失败 Future kr_performNodeSwitch(String tag) async { try { + // 🔒 状态锁:防止并发切换 + if (_isSwitchingNode) { + KRLogUtil.kr_w('⚠️ 节点切换正在进行中,忽略重复请求', tag: 'HomeController'); + KRCommonUtil.kr_showToast('请等待当前节点切换完成'); + return false; + } + + // 🔒 节流控制:2秒内的重复切换请求直接忽略 + final now = DateTime.now(); + if (_lastSwitchTime != null && now.difference(_lastSwitchTime!) < _switchThrottleDuration) { + final remainingTime = _switchThrottleDuration.inMilliseconds - now.difference(_lastSwitchTime!).inMilliseconds; + KRLogUtil.kr_w('⚠️ 切换过于频繁,请等待 ${remainingTime}ms', tag: 'HomeController'); + KRCommonUtil.kr_showToast('切换过于频繁,请稍后再试'); + return false; + } + + // 🔒 设置状态锁和记录时间 + _isSwitchingNode = true; + _lastSwitchTime = now; + KRLogUtil.kr_i('🔄 开始切换节点: $tag', tag: 'HomeController'); // 1. 保存原节点,以备失败恢复 @@ -1155,6 +1171,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { if (!kr_isConnected.value) { KRLogUtil.kr_i('📴 VPN未连接,只更新UI变量: $tag', tag: 'HomeController'); kr_cutSeletedTag.value = tag; + kr_updateConnectionInfo(); // kr_moveToSelectedNode(); // 🔧 修复:保存节点选择以便VPN启动时应用 @@ -1191,29 +1208,29 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { KRLogUtil.kr_i('💾 保存新节点选择: $tag', tag: 'HomeController'); await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag); - // 🔧 方案A增强:重启VPN连接以断开所有现有长连接 - KRLogUtil.kr_i('🔄 [增强] 停止VPN连接以断开现有连接...', tag: 'HomeController'); - await KRSingBoxImp.instance.kr_stop(); // 先停止VPN + // 🔧 方案A优化:重启VPN连接以断开所有现有长连接 + KRLogUtil.kr_i('🔄 [优化] 停止VPN连接以断开现有连接...', tag: 'HomeController'); + await KRSingBoxImp.instance.kr_stop(); // 先停止VPN(已跳过DNS恢复) - // 🚀 方案A增强:增加等待时间,确保所有连接完全释放 - KRLogUtil.kr_i('⏳ [增强] 等待VPN完全停止(1500ms,确保旧连接全部断开)...', tag: 'HomeController'); - await Future.delayed(const Duration(milliseconds: 1500)); // 从800ms增加到1500ms + // 🚀 优化:减少等待时间(DNS操作已优化,无需过长等待) + KRLogUtil.kr_i('⏳ [优化] 等待VPN完全停止(800ms)...', tag: 'HomeController'); + await Future.delayed(const Duration(milliseconds: 800)); // 从1500ms减少到800ms - KRLogUtil.kr_i('🔄 [增强] 启动VPN并应用新节点: $tag', tag: 'HomeController'); - await KRSingBoxImp.instance.kr_start(); // 重新启动VPN,会自动使用新保存的节点 + KRLogUtil.kr_i('🔄 [优化] 启动VPN并应用新节点: $tag', tag: 'HomeController'); + await KRSingBoxImp.instance.kr_start(); // 重新启动VPN(已跳过DNS备份) - // 🚀 方案A增强:增加启动等待时间,确保新VPN完全建立 - KRLogUtil.kr_i('⏳ [增强] 等待VPN完全启动(2500ms,确保新连接完全建立)...', tag: 'HomeController'); - await Future.delayed(const Duration(milliseconds: 2500)); // 从1500ms增加到2500ms + // 🚀 优化:减少等待时间(DNS操作已优化) + KRLogUtil.kr_i('⏳ [优化] 等待VPN完全启动(1200ms)...', tag: 'HomeController'); + await Future.delayed(const Duration(milliseconds: 1200)); // 从2500ms减少到1200ms // 后台切换成功,更新UI kr_cutSeletedTag.value = tag; kr_updateConnectionInfo(); - // kr_moveToSelectedNode(); + kr_moveToSelectedNode(); - // 🚀 方案A增强:增加验证前等待时间,确保活动组完全更新 - KRLogUtil.kr_i('⏳ [增强] 等待活动组更新(500ms)...', tag: 'HomeController'); - await Future.delayed(const Duration(milliseconds: 500)); // 从200ms增加到500ms + // 🚀 优化:减少验证等待时间 + KRLogUtil.kr_i('⏳ [优化] 等待活动组更新(300ms)...', tag: 'HomeController'); + await Future.delayed(const Duration(milliseconds: 300)); // 从500ms减少到300ms // 🚀 方案A增强:验证节点是否真正切换成功 KRLogUtil.kr_i('🔍 [增强] 验证节点切换是否成功...', tag: 'HomeController'); @@ -1221,7 +1238,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 刷新活动组信息 final activeGroups = KRSingBoxImp.instance.kr_activeGroups; final selectGroup = activeGroups.firstWhere( - (group) => group.tag == 'select', + (group) => group.tag == 'select', orElse: () => throw Exception('未找到 select 组'), ); @@ -1271,6 +1288,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { KRCommonUtil.kr_showToast('节点切换异常,请重试'); return false; } finally { + // 🔒 释放状态锁 + _isSwitchingNode = false; // 关闭加载状态 kr_isLatency.value = false; KRLogUtil.kr_i('🔄 节点切换流程完成', tag: 'HomeController'); @@ -1308,8 +1327,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } // 2. 如果是 auto,优先使用 kr_cutSeletedTag(保存了实际选中的节点) else if (kr_cutSeletedTag.value.isNotEmpty && - kr_cutSeletedTag.value != 'auto' && - kr_cutSeletedTag.value != 'select') { + kr_cutSeletedTag.value != 'auto' && + kr_cutSeletedTag.value != 'select') { // auto 模式下,使用保存的实际节点 actualTag = kr_cutSeletedTag.value; KRLogUtil.kr_i('✅ 使用 auto 模式下的实际节点 (kr_cutSeletedTag): $actualTag', tag: 'getCurrentNodeCountry'); @@ -1344,7 +1363,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { print('[getCurrentNodeCountry] select 组选中的是 auto,查找 urltest 组'); // 如果 select 组选中的是 auto,从 urltest 组获取 final urlTestGroup = allGroups.firstWhere( - (group) => group.type == ProxyType.urltest, + (group) => group.type == ProxyType.urltest, orElse: () => throw Exception('未找到 urltest 组'), ); @@ -1366,7 +1385,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 活动组不为空,从活动组获取 // 从 SingBox 活动组中找到 "select" 选择器组 final selectGroup = KRSingBoxImp.instance.kr_activeGroups.firstWhere( - (group) => group.tag == 'select', + (group) => group.tag == 'select', orElse: () => throw Exception('未找到 select 组'), ); @@ -1420,88 +1439,41 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } - /// 获取真实连接的节点信息(auto 模式下获取实际连接的节点) + /// 获取真实连接的节点信息(统一为手动选择场景) Map kr_getRealConnectedNodeInfo() { - // 如果不是 auto 模式,也不是 country-auto 模式,直接返回当前选中的节点信息 - if (kr_cutTag.value != 'auto' && !kr_cutTag.value.endsWith('-auto')) { - final node = kr_subscribeService.keyList[kr_cutSeletedTag.value]; - return { - 'nodeName': kr_cutSeletedTag.value, - 'delay': node?.urlTestDelay.value ?? -2, - 'country': node?.country ?? '', - }; - } - - // 处理 auto 模式(包括全局 auto 和 country-auto) - print('当前活动组----${KRSingBoxImp.instance.kr_activeGroups.length}'); - for (var group in KRSingBoxImp.instance.kr_activeGroups) { - print('当前活动组----$group}'); - - // 处理全局 auto 模式 - if (kr_cutTag.value.endsWith('auto') && group.type == ProxyType.urltest && group.tag == 'auto') { - final selectedNode = group.selected; - final node = kr_subscribeService.keyList[selectedNode]; - return { - 'nodeName': selectedNode, - 'delay': node?.urlTestDelay.value ?? -2, - 'country': node?.country ?? '', - }; - } - } - - // 处理 country-auto 模式的备用方案(当 SingBox 组数据不可用时) - if (kr_cutTag.value.endsWith('-auto')) { - final countryCode = kr_cutTag.value.replaceAll('-auto', ''); - KRLogUtil.kr_i('🔄 kr_getRealConnectedNodeInfo 使用备用方案获取 country-auto 信息: $countryCode', tag: 'HomeController'); - final autoNodeInfo = kr_getCountryAutoSelectedNode(countryCode); - if (autoNodeInfo != null) { - return { - 'nodeName': autoNodeInfo['tag'], - 'delay': autoNodeInfo['delay'] ?? -2, - 'country': autoNodeInfo['country'] ?? countryCode, - }; - } - } - - // 如果没有找到 urltest 组,返回默认值 + String actualTag = kr_cutSeletedTag == 'auto' ? kr_cutSeletedTag.value : kr_cutTag.value; + final node = kr_subscribeService.keyList[actualTag]; return { 'nodeName': kr_cutTag.value, - 'delay': -2, - 'country': '', + 'delay': node?.urlTestDelay.value ?? -2, + 'country': node?.country ?? '', }; } - /// 获取真实连接的节点名称 - String kr_getRealConnectedNodeName() { - final info = kr_getRealConnectedNodeInfo(); - return info['nodeName'] as String; - } - - /// 获取真实连接的节点延迟 - int kr_getRealConnectedNodeDelay() { - final info = kr_getRealConnectedNodeInfo(); - return info['delay'] as int; - } /// 获取真实连接的节点国家 String kr_getRealConnectedNodeCountry() { - final info = kr_getRealConnectedNodeInfo(); - final delay = info['delay'] as int; // 使用真实连接的节点延迟,而不是 kr_currentNodeLatency.value - final country1 = kr_getCurrentNodeCountry(); - print('country----$country1'); - print('kr_getRealConnectedNodeCountry - delay from info: $delay, country from info: ${info['country']}'); - final country = kr_getCountryFullName(info['country']); - if (delay == -2) { - return '--'; - } else if (delay == -1) { - return '${country} ${AppTranslations.kr_home.connecting}'; - } else if (delay == 0) { - return '${country} ${AppTranslations.kr_home.connected}'; - } else if (delay >= 3000) { - return '${country} ${AppTranslations.kr_home.timeout}'; - } else { - return '${country} ${delay}ms'; - } + final country = kr_getCurrentNodeCountry(); + if(country.isEmpty) return ''; + return kr_getCountryFullName(country); + // controller.kr_cutSeletedTag.value + // final info = kr_getRealConnectedNodeInfo(); + // final delay = info['delay'] as int; // 使用真实连接的节点延迟,而不是 kr_currentNodeLatency.value + // final country1 = kr_getCurrentNodeCountry(); + // print('country----$country1'); + // print('kr_getRealConnectedNodeCountry - delay from info: $delay, country from info: ${info['country']}'); + // final country = kr_getCountryFullName(country1); + // if (delay == -2) { + // return '--'; + // } else if (delay == -1) { + // return '${country} ${AppTranslations.kr_home.connecting}'; + // } else if (delay == 0) { + // return '${country} ${AppTranslations.kr_home.connected}'; + // } else if (delay >= 3000) { + // return '${country} ${AppTranslations.kr_home.timeout}'; + // } else { + // return '${country} ${delay}ms'; + // } } @@ -1840,10 +1812,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { Future kr_forceDirectTest() async { try { KRLogUtil.kr_i('🔧 强制使用直接连接测试...', tag: 'HomeController'); - + // 使用直接连接测试所有节点 await _kr_testLatencyWithoutVpn(); - + KRLogUtil.kr_i('✅ 直接连接测试完成', tag: 'HomeController'); } catch (e) { KRLogUtil.kr_e('❌ 直接连接测试失败: $e', tag: 'HomeController'); @@ -1857,15 +1829,15 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { try { KRLogUtil.kr_i('🧪 开始延迟测试...', tag: 'HomeController'); KRLogUtil.kr_i('📊 当前连接状态: ${kr_isConnected.value}', tag: 'HomeController'); - + if (kr_isConnected.value) { // 已连接状态:使用 SingBox 通过代理测试 KRLogUtil.kr_i('🔗 已连接状态 - 使用 SingBox 通过代理测试延迟', tag: 'HomeController'); await KRSingBoxImp.instance.kr_urlTest("select"); - + // 等待一段时间让 SingBox 完成测试 await Future.delayed(const Duration(seconds: 3)); - + // 再次检查活动组状态 KRLogUtil.kr_i('🔄 检查代理测试后的活动组状态...', tag: 'HomeController'); final activeGroups = KRSingBoxImp.instance.kr_activeGroups; @@ -2127,16 +2099,16 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { /// 连接超时处理 Timer? _connectionTimeoutTimer; - + void _startConnectionTimeout() { _connectionTimeoutTimer?.cancel(); _connectionTimeoutTimer = Timer(const Duration(seconds: 30), () { KRLogUtil.kr_w('⏰ 连接超时,强制重置状态', tag: 'HomeController'); - + // 检查是否仍在连接中 if (KRSingBoxImp.instance.kr_status.value is SingboxStarting) { KRLogUtil.kr_w('🔄 连接超时,强制停止并重置', tag: 'HomeController'); - + // 强制停止连接 KRSingBoxImp.instance.kr_stop().then((_) { // 重置状态 @@ -2146,7 +2118,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { kr_currentNodeLatency.value = -2; kr_resetConnectionInfo(); update(); - + KRLogUtil.kr_i('✅ 连接超时处理完成', tag: 'HomeController'); }).catchError((e) { KRLogUtil.kr_e('❌ 连接超时处理失败: $e', tag: 'HomeController'); @@ -2154,7 +2126,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } }); } - + void _cancelConnectionTimeout() { _connectionTimeoutTimer?.cancel(); _connectionTimeoutTimer = null; @@ -2197,209 +2169,33 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { }); } - /// 获取国家-auto组的当前选中子节点信息 + /// 获取国家选中子节点信息(已不使用自动选择逻辑) Map? kr_getCountryAutoSelectedNode(String countryCode) { - try { - final activeGroups = KRSingBoxImp.instance.kr_activeGroups; - final allGroups = KRSingBoxImp.instance.kr_allGroups; - final autoGroupTag = '${countryCode}-auto'; - - KRLogUtil.kr_i('🔍 开始获取国家-auto选中节点: countryCode=$countryCode, autoGroupTag=$autoGroupTag', tag: 'HomeController'); - KRLogUtil.kr_i('📊 活跃组数量: ${activeGroups.length}, 所有组数量: ${allGroups.length}', tag: 'HomeController'); - - // 优先检查活跃组 - if (activeGroups.isNotEmpty) { - KRLogUtil.kr_i('✅ 活跃组不为空,优先检查活跃组', tag: 'HomeController'); - for (var group in activeGroups) { - KRLogUtil.kr_i('🔄 检查活跃组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController'); - - if (group.tag == autoGroupTag && group.type == ProxyType.urltest) { - KRLogUtil.kr_i('✅ 在活跃组中找到匹配的urltest组: $autoGroupTag', tag: 'HomeController'); - return _kr_extractSelectedNodeInfo(group, countryCode); - } - } - } - - // 如果活跃组中没有,检查所有组 - if (allGroups.isNotEmpty) { - KRLogUtil.kr_i('🔍 活跃组中未找到,检查所有组', tag: 'HomeController'); - for (var group in allGroups) { - KRLogUtil.kr_i('🔄 检查所有组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController'); - - if (group.tag == autoGroupTag && group.type == ProxyType.urltest) { - KRLogUtil.kr_i('✅ 在所有组中找到匹配的urltest组: $autoGroupTag', tag: 'HomeController'); - return _kr_extractSelectedNodeInfo(group, countryCode); - } - } - } - - // 如果组数据都为空,使用备用方案:从订阅服务中找该国家最快的节点 - KRLogUtil.kr_i('🔄 组数据为空,使用备用方案从订阅服务获取最快节点', tag: 'HomeController'); - return _kr_getFastestNodeFromSubscribeService(countryCode); - - } catch (e) { - KRLogUtil.kr_e('💥 获取国家-auto选中节点异常: $e', tag: 'HomeController'); - } return null; } - - /// 从订阅服务获取该国家最快节点的备用方案 - Map? _kr_getFastestNodeFromSubscribeService(String countryCode) { - try { - KRLogUtil.kr_i('🔄 使用订阅服务查找国家最快节点: $countryCode', tag: 'HomeController'); - - // 从订阅服务中获取该国家的所有节点 - final allNodes = kr_subscribeService.allList.where((node) => - node.country == countryCode && !node.tag.endsWith('-auto') - ).toList(); - - KRLogUtil.kr_i('📊 找到 ${allNodes.length} 个该国家的普通节点', tag: 'HomeController'); - - if (allNodes.isEmpty) { - KRLogUtil.kr_w('⚠️ 未找到该国家的任何普通节点: $countryCode', tag: 'HomeController'); - return null; - } - - // 找出延迟最小的节点(排除0和超时) - KROutboundItem? fastestNode; - int fastestDelay = 999999; - - for (var node in allNodes) { - final delay = node.urlTestDelay.value; - KRLogUtil.kr_w('🔍 检查节点 ${node.tag} 的延迟: $delay', tag: 'HomeController'); - if (delay > 0 && delay < fastestDelay) { - fastestDelay = delay; - fastestNode = node; - } - } - - if (fastestNode != null) { - KRLogUtil.kr_i('✅ 找到最快节点: ${fastestNode.tag}, 延迟: ${fastestDelay}ms', tag: 'HomeController'); - return { - 'tag': fastestNode.tag, - 'delay': fastestDelay, - 'country': countryCode, - }; - } else { - KRLogUtil.kr_w('⚠️ 该国家的节点都没有有效延迟数据', tag: 'HomeController'); - return null; - } - } catch (e) { - KRLogUtil.kr_e('💥 获取订阅服务最快节点异常: $e', tag: 'HomeController'); - return null; - } - } - - /// 提取选中节点信息的辅助方法 - Map? _kr_extractSelectedNodeInfo(dynamic group, String countryCode) { - try { - // 获取当前选中的节点 - final selectedNode = group.selected; - KRLogUtil.kr_i('🎯 当前选中节点: $selectedNode', tag: 'HomeController'); - KRLogUtil.kr_i('📋 组内项目数量: ${group.items.length}', tag: 'HomeController'); - - if (selectedNode != null && selectedNode.isNotEmpty) { - KRLogUtil.kr_i('✅ 选中节点有效,开始查找节点详情', tag: 'HomeController'); - - // 打印所有节点信息用于调试 - for (var item in group.items) { - KRLogUtil.kr_i('📄 组内节点: tag=${item.tag}, delay=${item.urlTestDelay}', tag: 'HomeController'); - } - - // 在组内查找选中的节点 - try { - final selectedItem = group.items.firstWhere( - (item) => item.tag == selectedNode, - ); - - KRLogUtil.kr_i('🎉 成功找到选中节点: tag=${selectedItem.tag}, delay=${selectedItem.urlTestDelay}', tag: 'HomeController'); - - return { - 'tag': selectedNode, - 'delay': selectedItem.urlTestDelay, - 'country': countryCode, - }; - } catch (e) { - KRLogUtil.kr_e('❌ 在组内未找到选中节点: $selectedNode', tag: 'HomeController'); - return null; - } - } else { - KRLogUtil.kr_w('⚠️ 选中节点为空或无效', tag: 'HomeController'); - return null; - } - } catch (e) { - KRLogUtil.kr_e('💥 提取选中节点信息异常: $e', tag: 'HomeController'); - return null; - } - } - /// 获取全局 auto 组的当前选中子节点信息 - Map? kr_getGlobalAutoSelectedNode() { - try { - final activeGroups = KRSingBoxImp.instance.kr_activeGroups; - - KRLogUtil.kr_i('🌍 开始获取全局auto选中节点信息', tag: 'HomeController'); - KRLogUtil.kr_i('📊 活跃组数量: ${activeGroups.length}', tag: 'HomeController'); - - for (var group in activeGroups) { - KRLogUtil.kr_i('🔄 检查全局组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController'); - KRLogUtil.kr_i('📋 全局组内项目数量: ${group.items.length}', tag: 'HomeController'); - - if (group.tag == 'auto' && group.type == ProxyType.urltest) { - KRLogUtil.kr_i('✅ 找到全局auto urltest组', tag: 'HomeController'); - - // 获取当前选中的节点 - final selectedNode = group.selected; - KRLogUtil.kr_i('🎯 全局auto当前选中节点: $selectedNode', tag: 'HomeController'); - - if (selectedNode != null && selectedNode.isNotEmpty) { - KRLogUtil.kr_i('✅ 全局auto选中节点有效,开始查找详情', tag: 'HomeController'); - - // 打印所有节点信息用于调试 - for (var item in group.items) { - KRLogUtil.kr_i('📄 全局auto组内节点: tag=${item.tag}, delay=${item.urlTestDelay}', tag: 'HomeController'); - } - - // 在组内查找选中的节点 - try { - final selectedItem = group.items.firstWhere( - (item) => item.tag == selectedNode, - ); - - KRLogUtil.kr_i('🎉 成功找到全局auto选中节点: tag=${selectedItem.tag}, delay=${selectedItem.urlTestDelay}', tag: 'HomeController'); - - return { - 'tag': selectedNode, - 'delay': selectedItem.urlTestDelay, - 'country': '', - }; - } catch (e) { - KRLogUtil.kr_e('❌ 在全局auto组内未找到选中节点: $selectedNode', tag: 'HomeController'); - return null; - } - } else { - KRLogUtil.kr_w('⚠️ 全局auto选中节点为空或无效', tag: 'HomeController'); - } - } - } - - KRLogUtil.kr_w('❌ 未找到全局auto组', tag: 'HomeController'); - } catch (e) { - KRLogUtil.kr_e('💥 获取全局auto选中节点异常: $e', tag: 'HomeController'); - } + /// 从订阅服务获取该国家最快节点的备用方案 + Map? _kr_getFastestNodeFromSubscribeService(String countryCode) { return null; } + /// 提取选中节点信息的辅助方法 + Map? _kr_extractSelectedNodeInfo(dynamic group, String countryCode) { + return null; + } + + // 已移除:全局 auto 选中节点信息获取(统一为手动选择模式) + /// 获取指定国家的所有真实节点延迟列表 List> kr_getCountryRealNodeDelays(String countryCode) { final delays = >[]; - + try { // 从订阅服务中获取该国家的节点列表 final countryNodes = kr_subscribeService.keyList.values - .where((item) => item.country == countryCode && !item.tag.endsWith('-auto')) + .where((item) => item.country == countryCode) .toList(); - + for (final node in countryNodes) { delays.add({ 'tag': node.tag, @@ -2407,13 +2203,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { 'city': node.city, }); } - + // 按延迟排序 delays.sort((a, b) => a['delay'].compareTo(b['delay'])); } catch (e) { KRLogUtil.kr_e('获取国家真实节点延迟列表失败: $e', tag: 'HomeController'); } - + return delays; } @@ -2432,24 +2228,11 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { if (group.type == ProxyType.selector) { KRLogUtil.kr_d('找到 selector 组: ${group.tag}, 选中: ${group.selected}', tag: 'HomeController'); - // 如果是auto模式,从urltest组获取延迟 - if (kr_cutTag.value.endsWith('auto')) { - for (var item in group.items) { - if (item.tag == "auto" && item.urlTestDelay != 0) { - kr_currentNodeLatency.value = item.urlTestDelay; - KRLogUtil.kr_i('✅ auto模式延迟值: ${item.urlTestDelay}ms', tag: 'HomeController'); - return true; - } - } - } - // 手动选择模式 - else { - for (var item in group.items) { - if (item.tag == kr_cutTag.value && item.urlTestDelay != 0) { - kr_currentNodeLatency.value = item.urlTestDelay; - KRLogUtil.kr_i('✅ 手动模式延迟值: ${item.urlTestDelay}ms', tag: 'HomeController'); - return true; - } + for (var item in group.items) { + if (item.tag == kr_cutTag.value && item.urlTestDelay != 0) { + kr_currentNodeLatency.value = item.urlTestDelay; + KRLogUtil.kr_i('✅ 延迟值: ${item.urlTestDelay}ms', tag: 'HomeController'); + return true; } } } diff --git a/lib/app/modules/kr_home/views/hi_animated_connect_button.dart b/lib/app/modules/kr_home/views/hi_animated_connect_button.dart index 4c3f12e..1615748 100644 --- a/lib/app/modules/kr_home/views/hi_animated_connect_button.dart +++ b/lib/app/modules/kr_home/views/hi_animated_connect_button.dart @@ -34,7 +34,7 @@ class HIAnimatedConnectButton extends GetView { print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching'); - final isShow = isConnected; // delay == -1 || isConnected; + final isShow = delay == -1 || isConnected; final Color buttonColor = Theme.of(context).primaryColor; final double screenWidth = Get.width; diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 5b2f249..efce263 100755 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -586,6 +586,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SWIFT_VERSION = 5.0; }; name = Profile; @@ -720,6 +721,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; @@ -748,6 +750,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SWIFT_VERSION = 5.0; }; name = Release;