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 e8e6021..bbc111e 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 @@ -48,17 +48,9 @@ class HINodeListView extends GetView { ) { if (outboundList.isEmpty) return 0; - // 过滤掉不可用节点(延迟标记为 65535 或负数) - final validNodes = outboundList.where((node) { - final delay = node.urlTestDelay.value; - return delay > 0 && delay < 65535; - }).toList(); - - if (validNodes.isEmpty) return 0; - // 返回最小延迟值 - validNodes.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value)); - return validNodes.first.urlTestDelay.value; + outboundList.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value)); + return outboundList.first.urlTestDelay.value; } /// 找出延迟最低(最快)的节点对象 @@ -88,8 +80,8 @@ class HINodeListView extends GetView { // 并设置透明背景,让父组件的背景可以透出来 return Material( color: Colors.transparent, - child: _buildSubscribeList(context) - // child: _kr_buildRegionList(context) + // child: _buildSubscribeList(context) + child: _kr_buildRegionList(context) ); } @@ -117,7 +109,7 @@ class HINodeListView extends GetView { onTap: () async { try { final success = - await controller.homeController.kr_performNodeSwitch('auto'); + await controller.homeController.kr_performCountrySwitch('auto'); if (success) { controller.homeController.kr_currentListStatus.value = KRHomeViewsListStatus.kr_none; @@ -199,9 +191,18 @@ class HINodeListView extends GetView { ), ...controller.kr_subscribeService.countryOutboundList.map((country) { return InkWell( - onTap: () { - // 自动选择这个国家下的节点延迟中最快的 - controller.homeController.onCountrySelected(country.country); + onTap: () async { + try { + final success = + await controller.homeController.kr_performCountrySwitch(country.country); + if (success) { + controller.homeController.kr_currentListStatus.value = + KRHomeViewsListStatus.kr_none; + } + } catch (e) { + KRLogUtil.kr_e('Auto选项切换异常: $e', + tag: 'NodeListView'); + } }, child: _kr_buildCountryListItem(context, country: country), ); @@ -403,13 +404,36 @@ class HINodeListView extends GetView { ), SizedBox(height: 2.w), Obx(() { - // 2. 获取用于显示的延迟值 - final int delay = _getDisplayDelay(controller, item); + // 1. 获取延迟值和测速状态 + int displayDelay = item.urlTestDelay.value; + bool isTesting = controller.homeController.kr_isLatency.value; + + // 2. 声明文本和颜色变量 + String delayText; + Color delayColor; + + // 3. 根据状态设置文本和颜色 + if (isTesting && displayDelay == 0) { + delayText = '测速中...'; + delayColor = Colors.grey; // 测速时使用灰色 + } else if (displayDelay == 0) { + delayText = '- ms'; // 未测速或初始状态 + delayColor = Colors.grey; + } else if (displayDelay >= 3000) { + delayText = AppTranslations.kr_home.timeout; // "超时" + delayColor = Colors.red; // 超时状态使用红色 + } else { + delayText = '${displayDelay}ms'; + // 正常延迟,根据快慢设置不同颜色 + delayColor = (displayDelay < 500) ? krModernGreen : Colors.orange; + } + + // 4. 返回最终的 Text 组件 return Text( - '${delay}ms', + delayText, style: KrAppTextStyle( fontSize: 10, - color: krModernGreen, + color: delayColor, // 使用动态计算出的颜色 fontWeight: FontWeight.w500, ), ); @@ -441,18 +465,7 @@ class HINodeListView extends GetView { /// 构建国家列表项的UI Widget _kr_buildCountryListItem(BuildContext context, {required country}) { - // 获取延迟颜色 - Color getLatencyColor(int delay) { - if (delay == 0) { - return Colors.transparent; - } else if (delay < 500) { - return krModernGreen; - } else if (delay < 3000) { - return Color(0xFFFFB700); // 使用更容易看清的黄色 - } else { - return Colors.red; - } - } + return Container( key: ValueKey(country), decoration: BoxDecoration( @@ -476,16 +489,36 @@ class HINodeListView extends GetView { ), ), Obx(() { - final int delay = getFastestNodeDelay(controller, country.outboundList); + // 1. 获取延迟值和测速状态 + int displayDelay = getFastestNodeDelay(controller, country.outboundList); + bool isTesting = controller.homeController.kr_isLatency.value; + + // 2. 声明文本和颜色变量 + String delayText; + Color delayColor; + + // 3. 根据状态设置文本和颜色 + if (isTesting && displayDelay == 0) { + delayText = '测速中...'; + delayColor = Colors.grey; // 测速时使用灰色 + } else if (displayDelay == 0) { + delayText = '- ms'; // 未测速或初始状态 + delayColor = Colors.grey; + } else if (displayDelay >= 3000) { + delayText = AppTranslations.kr_home.timeout; // "超时" + delayColor = Colors.red; // 超时状态使用红色 + } else { + delayText = '${displayDelay}ms'; + // 正常延迟,根据快慢设置不同颜色 + delayColor = (displayDelay < 500) ? krModernGreen : Colors.orange; + } + + // 4. 返回最终的 Text 组件 return Text( - delay == 0 - ? '' - : delay >= 3000 - ? AppTranslations.kr_home.timeout - : '${delay}ms', + delayText, style: KrAppTextStyle( fontSize: 10, - color: getLatencyColor(delay), + color: delayColor, // 使用动态计算出的颜色 fontWeight: FontWeight.w500, ), ); 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 eff3c4f..1f1b196 100755 --- a/lib/app/modules/kr_home/controllers/kr_home_controller.dart +++ b/lib/app/modules/kr_home/controllers/kr_home_controller.dart @@ -1275,11 +1275,108 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } } - Future onCountrySelected(String countryTag) async { - await KRSingBoxImp.instance.kr_selectCountry(countryTag); - KRCommonUtil.kr_showToast('已切换到 $countryTag 节点组'); + /// 🌍 执行国家切换(包含UI同步与VPN热重载) + /// 返回 true 表示切换成功,false 表示失败 + Future kr_performCountrySwitch(String countryTag) async { + try { + KRLogUtil.kr_i('🔄 开始切换国家: $countryTag', tag: 'HomeController'); + + // 1. 保存原国家,以备失败恢复 + final originalCountry = kr_coutryText.value; + + // 2. 设置切换中状态 + kr_coutryText.value = countryTag; + kr_currentNodeName.value = countryTag; // UI显示当前国家 + + // 3. 获取该国家的节点列表 + final countryData = kr_subscribeService.countryOutboundList + .firstWhereOrNull((c) => c.country.toUpperCase() == countryTag.toUpperCase()); + + if (countryData == null || countryData.outboundList.isEmpty) { + KRLogUtil.kr_w('⚠️ 未找到国家 [$countryTag] 的节点列表', tag: 'HomeController'); + KRCommonUtil.kr_showToast('该国家暂无可用节点'); + return false; + } + + // 取第一个节点作为默认节点(后续由 sing-box 自动在该组内切换) + final defaultNode = countryData.outboundList.first.tag; + KRLogUtil.kr_i( + '📊 国家 [$countryTag] 包含 ${countryData.outboundList.length} 个节点,默认节点: $defaultNode', + tag: 'HomeController', + ); + + // 4. 如果VPN未连接,只更新UI变量即可 + if (!kr_isConnected.value) { + KRLogUtil.kr_i('📴 VPN未连接,只更新UI变量: $countryTag', tag: 'HomeController'); + kr_cutSeletedTag.value = defaultNode; + kr_cutTag.value = defaultNode; + kr_currentNodeLatency.value = -2; + + // 保存国家选择与默认节点 + await KRSecureStorage().kr_saveData(key: 'SELECTED_COUNTRY_TAG', value: countryTag); + await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: defaultNode); + + KRLogUtil.kr_i('✅ 已保存国家选择 [$countryTag] (未连接状态)', tag: 'HomeController'); + return true; + } + + // 5. VPN已连接状态,执行完整重载逻辑 + try { + KRLogUtil.kr_i('🔌 VPN已连接,准备切换国家 [$countryTag]', tag: 'HomeController'); + + // 显示连接中 + kr_currentNodeLatency.value = -1; + kr_isLatency.value = true; + + // 保存选择 + await KRSecureStorage().kr_saveData(key: 'SELECTED_COUNTRY_TAG', value: countryTag); + await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: defaultNode); + + // 🔧 调用 SingBox 层逻辑切换国家分组(稍后在 kr_sing_box_imp.dart 实现) + KRLogUtil.kr_i('🧩 调用 sing-box 切换国家分组逻辑: $countryTag', tag: 'HomeController'); + await KRSingBoxImp.instance.kr_selectCountry(countryTag); + + // 🔁 停止并重启 VPN + await KRSingBoxImp.instance.kr_stop(); + KRLogUtil.kr_i('⏳ 等待VPN停止(1500ms)', tag: 'HomeController'); + await Future.delayed(const Duration(milliseconds: 1500)); + + await KRSingBoxImp.instance.kr_start(); + KRLogUtil.kr_i('⏳ 等待VPN启动(2500ms)', tag: 'HomeController'); + await Future.delayed(const Duration(milliseconds: 2500)); + + // ✅ 切换成功,更新UI + kr_cutSeletedTag.value = defaultNode; + kr_cutTag.value = defaultNode; + kr_updateConnectionInfo(); + + // 更新延迟信息 + _kr_updateLatencyOnConnected(); + + KRLogUtil.kr_i('✅ 国家切换成功: $countryTag', tag: 'HomeController'); + return true; + } catch (switchError) { + KRLogUtil.kr_e('❌ 国家切换失败: $switchError', tag: 'HomeController'); + + // 恢复原国家状态 + kr_coutryText.value = originalCountry; + await KRSecureStorage().kr_saveData(key: 'SELECTED_COUNTRY_TAG', value: originalCountry ?? ''); + + KRCommonUtil.kr_showToast('切换国家失败,已恢复为: ${originalCountry ?? "无"}'); + return false; + } + } catch (e) { + KRLogUtil.kr_e('❌ 国家切换异常: $e', tag: 'HomeController'); + KRCommonUtil.kr_showToast('国家切换异常,请重试'); + return false; + } finally { + kr_isLatency.value = false; + KRLogUtil.kr_i('🔄 国家切换流程完成', tag: 'HomeController'); + } } + + /// 🔧 修复:简化的 kr_selectNode 方法 /// 现在只是委托给新的 kr_performNodeSwitch 方法 /// 为了保持向后兼容,保留此方法但改为调用新方法 diff --git a/lib/app/services/singbox_imp/kr_sing_box_imp.dart b/lib/app/services/singbox_imp/kr_sing_box_imp.dart index 518b607..da6a1d3 100755 --- a/lib/app/services/singbox_imp/kr_sing_box_imp.dart +++ b/lib/app/services/singbox_imp/kr_sing_box_imp.dart @@ -1482,67 +1482,6 @@ class KRSingBoxImp { return; } - /*final selectGroup = kr_activeGroups.firstWhere( - (group) => group.tag == 'select', - orElse: () => throw Exception('未找到 "select" 组'), - ); - - final countryNodes = selectGroup.items.where((item) { - final nodeCountry = item.options?['country'] ?? ''; - return nodeCountry.toString().toLowerCase() == country.toLowerCase(); - }).toList(); - - if (countryNodes.isEmpty) { - KRLogUtil.kr_w('⚠️ 未找到该国家的节点: $country', tag: 'SingBox'); - for (var item in selectGroup.items) { - KRLogUtil.kr_d( - ' 可用节点: ${item.tag} (${item.options?['country'] ?? '未知'})', - tag: 'SingBox', - ); - } - return; - } - - KRLogUtil.kr_i('🇨🇭 找到 ${countryNodes.length} 个属于 "$country" 的节点', tag: 'SingBox'); - - // 按延迟排序(无延迟则 9999) - countryNodes.sort((a, b) => - (a.urlTestDelay ?? 9999).compareTo(b.urlTestDelay ?? 9999)); - - final fastestNode = countryNodes.first; - final selectedTag = fastestNode.tag; - final delay = fastestNode.urlTestDelay ?? -1; - - KRLogUtil.kr_i('✅ 选中该国家最快节点: $selectedTag (${delay}ms)', tag: 'SingBox'); - - try { - await KRSecureStorage().kr_saveData(key: 'selected_country', value: country); - await KRSecureStorage().kr_saveData(key: _keySelectedNode, value: selectedTag); - } catch (e) { - KRLogUtil.kr_w('⚠️ 保存国家/节点信息失败: $e', tag: 'SingBox'); - } - - try { - await _kr_ensureCommandClientInitialized(); - await _kr_selectOutboundWithRetry('select', selectedTag, - maxAttempts: 3, initialDelay: 100); - KRLogUtil.kr_i('✅ 国家内节点切换成功: $selectedTag', tag: 'SingBox'); - } catch (e) { - KRLogUtil.kr_e('❌ 国家内节点选择失败: $e', tag: 'SingBox'); - rethrow; - } - - _nodeSelectionTimer?.cancel(); - KRLogUtil.kr_i('🔁 启动定时器防止节点被 auto 覆盖', tag: 'SingBox'); - _nodeSelectionTimer = - Timer.periodic(const Duration(seconds: 20), (timer) async { - try { - await kr_singBox.selectOutbound('select', selectedTag).run(); - KRLogUtil.kr_d('🔁 定时确认国家内节点: $selectedTag', tag: 'SingBox'); - } catch (e) { - KRLogUtil.kr_w('🔁 定时确认失败: $e', tag: 'SingBox'); - } - });*/ }