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 ebfa9fa..85394b6 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 @@ -98,6 +98,7 @@ class HINodeListController extends GetxController with WidgetsBindingObserver { void onInit() { super.onInit(); WidgetsBinding.instance.addObserver(this); + _loadSelectedCountry(); } @override @@ -124,4 +125,14 @@ class HINodeListController extends GetxController with WidgetsBindingObserver { WidgetsBinding.instance.removeObserver(this); super.onClose(); } + + Future _loadSelectedCountry() async { + try { + final v = + await KRSecureStorage().kr_readData(key: 'SELECTED_COUNTRY_TAG'); + if (v != null && v.isNotEmpty) { + homeController.kr_coutryText.value = v; + } + } catch (_) {} + } } 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 2a75c1a..d5a1a57 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 @@ -8,6 +8,7 @@ import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; import 'package:kaer_with_panels/app/widgets/kr_country_flag.dart'; import '../../../model/business/kr_outbound_item.dart'; import '../../../utils/kr_log_util.dart'; +import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart'; import '../controllers/hi_node_list_controller.dart'; import 'package:kaer_with_panels/app/modules/kr_home/models/kr_home_views_status.dart'; import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart'; @@ -83,21 +84,24 @@ class HINodeListView extends GetView { // 2. 使用 children 属性,并一次性构建所有列表项 children: [ if (controller.kr_subscribeService.countryOutboundList.isEmpty) - _buildEmptyListPlaceholder(context, AppTranslations.kr_home.noRegions) + _buildEmptyListPlaceholder( + context, AppTranslations.kr_home.noRegions) else ...[ InkWell( // 🔧 修复:改为 async,等待节点切换完成后再关闭列表 onTap: () async { try { - final success = - await controller.homeController.kr_performNodeSwitch('auto'); + await KRSecureStorage().kr_saveData( + key: 'SELECTED_COUNTRY_TAG', value: 'auto'); + controller.homeController.kr_coutryText.value = 'auto'; + 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'); + KRLogUtil.kr_e('Auto选项切换异常: $e', tag: 'NodeListView'); } }, child: Container( @@ -109,7 +113,8 @@ class HINodeListView extends GetView { ), ), ), - padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w), + padding: + EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -154,7 +159,7 @@ class HINodeListView extends GetView { ), ), Obx(() => - controller.homeController.kr_cutSeletedTag.value == + controller.homeController.kr_coutryText.value == 'auto' ? KrLocalImage( imageName: 'radio-active-icon', @@ -177,6 +182,11 @@ class HINodeListView extends GetView { return InkWell( onTap: () async { try { + await KRSecureStorage().kr_saveData( + key: 'SELECTED_COUNTRY_TAG', + value: country.country); + controller.homeController.kr_coutryText.value = + country.country; final fastest = findFastestNode(country.outboundList); final success = await controller.homeController .kr_performNodeSwitch(fastest.tag); @@ -544,9 +554,9 @@ class HINodeListView extends GetView { }), SizedBox(width: 12.w), Obx(() { - final selectedTag = - controller.homeController.kr_cutSeletedTag.value; - if (selectedTag == 'auto') { + final selectedCountryField = + controller.homeController.kr_coutryText.value; + if (selectedCountryField == 'auto') { return KrLocalImage( imageName: 'radio-icon', imageType: ImageType.svg, @@ -554,9 +564,7 @@ class HINodeListView extends GetView { height: 16.h, ); } - final node = controller.kr_subscribeService.keyList[selectedTag]; - final selectedCountry = node?.country ?? ''; - final selected = selectedCountry == country.country; + final selected = selectedCountryField == country.country; return selected ? KrLocalImage( imageName: 'radio-active-icon', 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 8b24e35..ea17978 100755 --- a/lib/app/modules/kr_home/controllers/kr_home_controller.dart +++ b/lib/app/modules/kr_home/controllers/kr_home_controller.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:http/http.dart' as http; import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -26,7 +25,6 @@ import '../../../widgets/dialogs/kr_dialog.dart'; import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart'; import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; - import '../models/kr_home_views_status.dart'; import 'package:kaer_with_panels/app/model/response/kr_user_available_subscribe.dart'; @@ -35,12 +33,13 @@ import 'package:kaer_with_panels/app/utils/kr_common_util.dart'; import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart'; import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart'; import 'package:flutter/foundation.dart'; -import 'package:kaer_with_panels/app/utils/kr_init_log_collector.dart'; // 🔧 新增:导入日志收集器 -import 'package:kaer_with_panels/app/utils/kr_latency_tester.dart'; // 🔧 新增:导入真实延迟测试工具 +import 'package:kaer_with_panels/app/utils/kr_init_log_collector.dart'; // 🔧 新增:导入日志收集器 +import 'package:kaer_with_panels/app/utils/kr_latency_tester.dart'; // 🔧 新增:导入真实延迟测试工具 class KRHomeController extends GetxController with WidgetsBindingObserver { // 🔧 新增:日志收集器实例 final _initLog = KRInitLogCollector(); + /// 订阅服务 final KRSubscribeService kr_subscribeService = KRSubscribeService(); @@ -155,8 +154,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } else { // 如果没有保存过状态,使用默认值false isQuickConnectEnabled.value = false; - KRLogUtil.kr_i( - '未找到保存的闪连状态,使用默认值: false', tag: 'QuickConnect'); + KRLogUtil.kr_i('未找到保存的闪连状态,使用默认值: false', tag: 'QuickConnect'); } } catch (e) { KRLogUtil.kr_e('加载闪连状态失败: $e', tag: 'QuickConnect'); @@ -165,7 +163,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } } - // 获取当前闪连状态 bool get isQuickConnectActive => isQuickConnectEnabled.value; @@ -182,10 +179,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } // 检查用户是否已登录 - if (!KRAppRunData - .getInstance() - .kr_isLogin - .value) { + if (!KRAppRunData.getInstance().kr_isLogin.value) { KRLogUtil.kr_i('用户未登录,跳过自动连接', tag: 'QuickConnect'); return; } @@ -196,12 +190,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { return; } - KRLogUtil.kr_i('满足闪连自动连接条件,开始自动连接', tag: 'QuickConnect'); await _autoConnect(); } catch (e) { - KRLogUtil.kr_e( - '检查闪连自动连接启动条件时发生错误: $e', tag: 'QuickConnect'); + KRLogUtil.kr_e('检查闪连自动连接启动条件时发生错误: $e', tag: 'QuickConnect'); } } @@ -216,7 +208,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { return; } - // 调用连接方法 + await _kr_prepareCountrySelectionBeforeStart(); await KRSingBoxImp.instance.kr_start(); KRLogUtil.kr_i('闪连自动连接执行完成', tag: 'QuickConnect'); } catch (e) { @@ -242,14 +234,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { /// 检查是否满足自动连接条件(不执行连接) bool canAutoConnect() { return isQuickConnectEnabled.value && - KRAppRunData - .getInstance() - .kr_isLogin - .value && + KRAppRunData.getInstance().kr_isLogin.value && !kr_isConnected.value; } - @override void onInit() async { super.onInit(); @@ -259,12 +247,15 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { final dir = await getApplicationDocumentsDirectory(); final debugFile = File('${dir.path}/HOME_CONTROLLER_DEBUG.txt'); await debugFile.writeAsString( - '=' * 60 + '\n' - 'HomeController.onInit 被调用!\n' - '时间: ${DateTime.now()}\n' - '实例 HashCode: ${hashCode}\n' - '线程: ${Platform.isAndroid ? "Android" : "Unknown"}\n' - '=' * 60 + '\n', + '=' * 60 + + '\n' + 'HomeController.onInit 被调用!\n' + '时间: ${DateTime.now()}\n' + '实例 HashCode: ${hashCode}\n' + '线程: ${Platform.isAndroid ? "Android" : "Unknown"}\n' + '=' * + 60 + + '\n', mode: FileMode.append, ); } catch (e) { @@ -306,7 +297,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 🔧 Android 15 新增:5秒后再次强制更新高度,兜底保护 Future.delayed(const Duration(seconds: 5), () { if (kr_bottomPanelHeight.value < 100) { - KRLogUtil.kr_w('检测到底部面板高度异常(${kr_bottomPanelHeight.value}),强制修正', tag: 'HomeController'); + KRLogUtil.kr_w('检测到底部面板高度异常(${kr_bottomPanelHeight.value}),强制修正', + tag: 'HomeController'); kr_updateBottomPanelHeight(); } }); @@ -316,7 +308,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { Future _restoreSelectedNode() async { try { // 从 Hive 读取保存的节点 - final savedNode = await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG'); + final savedNode = + await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG'); if (savedNode != null && savedNode.isNotEmpty) { KRLogUtil.kr_i('📖 恢复上次选择的节点显示: $savedNode', tag: 'HomeController'); @@ -363,7 +356,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { kr_currentListStatus.value = KRHomeViewsListStatus.kr_none; // 强制更新底部面板高度,确保显示正常 kr_updateBottomPanelHeight(); - _initLog.log('已强制切换到默认视图, 底部面板高度: ${kr_bottomPanelHeight.value}', tag: 'Subscribe'); + _initLog.log('已强制切换到默认视图, 底部面板高度: ${kr_bottomPanelHeight.value}', + tag: 'Subscribe'); KRLogUtil.kr_i('✅ 已强制切换到默认视图', tag: 'HomeController'); } }); @@ -408,13 +402,19 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { _initLog.log('🔍 开始验证登录状态', tag: 'Home'); // 多重验证登录状态 - final hasToken = KRAppRunData().kr_token != null && KRAppRunData().kr_token!.isNotEmpty; + final hasToken = KRAppRunData().kr_token != null && + KRAppRunData().kr_token!.isNotEmpty; final isLoginFlag = KRAppRunData().kr_isLogin.value; final isValidLogin = hasToken && isLoginFlag; - _initLog.log('登录验证结果: hasToken=$hasToken, isLogin=$isLoginFlag, isValid=$isValidLogin', tag: 'Home'); - KRLogUtil.kr_i('登录状态验证: hasToken=$hasToken, isLogin=$isLoginFlag, isValid=$isValidLogin', tag: 'HomeController'); - KRLogUtil.kr_i('Token内容: ${KRAppRunData().kr_token?.substring(0, 10)}...', tag: 'HomeController'); + _initLog.log( + '登录验证结果: hasToken=$hasToken, isLogin=$isLoginFlag, isValid=$isValidLogin', + tag: 'Home'); + KRLogUtil.kr_i( + '登录状态验证: hasToken=$hasToken, isLogin=$isLoginFlag, isValid=$isValidLogin', + tag: 'HomeController'); + KRLogUtil.kr_i('Token内容: ${KRAppRunData().kr_token?.substring(0, 10)}...', + tag: 'HomeController'); if (isValidLogin) { kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn; @@ -472,7 +472,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { const Duration(seconds: 10), onTimeout: () { final elapsed = DateTime.now().difference(startTime).inMilliseconds; - _initLog.logWarning('订阅服务初始化超时(10秒), 实际耗时: ${elapsed}ms', tag: 'Subscribe'); + _initLog.logWarning('订阅服务初始化超时(10秒), 实际耗时: ${elapsed}ms', + tag: 'Subscribe'); KRLogUtil.kr_w('⏱️ 订阅服务初始化超时(10秒)', tag: 'HomeController'); kr_currentListStatus.value = KRHomeViewsListStatus.kr_none; throw TimeoutException('订阅服务初始化超时'); @@ -480,7 +481,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { ).then((_) async { final elapsed = DateTime.now().difference(startTime).inMilliseconds; _initLog.logSuccess('订阅服务初始化完成, 耗时: ${elapsed}ms', tag: 'Subscribe'); - _initLog.log('最终列表状态: ${kr_currentListStatus.value}', tag: 'Subscribe'); + _initLog.log('最终列表状态: ${kr_currentListStatus.value}', + tag: 'Subscribe'); // 🔧 新增:记录订阅数据详情 final subscribeList = kr_subscribeService.kr_availableSubscribes; @@ -490,15 +492,19 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } else { for (int i = 0; i < subscribeList.length; i++) { final sub = subscribeList[i]; - _initLog.log('订阅[$i]: ID=${sub.id}, 名称=${sub.name}, 试用=${sub.isTryOut}', tag: 'Subscribe'); - _initLog.log(' 过期时间: ${sub.expireTime}, 流量: ${sub.traffic}', tag: 'Subscribe'); + _initLog.log( + '订阅[$i]: ID=${sub.id}, 名称=${sub.name}, 试用=${sub.isTryOut}', + tag: 'Subscribe'); + _initLog.log(' 过期时间: ${sub.expireTime}, 流量: ${sub.traffic}', + tag: 'Subscribe'); } } // 记录当前选中的订阅 final currentSub = kr_subscribeService.kr_currentSubscribe.value; if (currentSub != null) { - _initLog.log('当前选中订阅: ${currentSub.name} (ID: ${currentSub.id})', tag: 'Subscribe'); + _initLog.log('当前选中订阅: ${currentSub.name} (ID: ${currentSub.id})', + tag: 'Subscribe'); } else { _initLog.logWarning('⚠️ 没有选中任何订阅', tag: 'Subscribe'); } @@ -515,7 +521,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 成功后状态由服务自己控制,不在这里设置 }).catchError((error) async { final elapsed = DateTime.now().difference(startTime).inMilliseconds; - _initLog.logError('订阅服务初始化失败, 耗时: ${elapsed}ms', tag: 'Subscribe', error: error); + _initLog.logError('订阅服务初始化失败, 耗时: ${elapsed}ms', + tag: 'Subscribe', error: error); _initLog.log('设置列表状态为: none (失败)', tag: 'Subscribe'); KRLogUtil.kr_e('❌ 订阅服务初始化失败: $error', tag: 'HomeController'); @@ -538,13 +545,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } } - /// 处理登录状态变化 void _kr_handleLoginStatusChange(bool isLoggedIn) { try { if (isLoggedIn) { // 再次验证登录状态的有效性 - final isValidLogin = KRAppRunData().kr_token != null && KRAppRunData().kr_token!.isNotEmpty; + final isValidLogin = KRAppRunData().kr_token != null && + KRAppRunData().kr_token!.isNotEmpty; if (isValidLogin) { kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn; KRLogUtil.kr_i('登录状态变化:设置为已登录', tag: 'HomeController'); @@ -587,13 +594,17 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { final currentLoginStatus = KRAppRunData().kr_isLogin.value; final currentViewStatus = kr_currentViewStatus.value; - KRLogUtil.kr_i('状态同步检查: login=$currentLoginStatus, view=$currentViewStatus', tag: 'HomeController'); + KRLogUtil.kr_i( + '状态同步检查: login=$currentLoginStatus, view=$currentViewStatus', + tag: 'HomeController'); // 检查状态是否一致 - if (currentViewStatus == KRHomeViewsStatus.kr_loggedIn && !currentLoginStatus) { + if (currentViewStatus == KRHomeViewsStatus.kr_loggedIn && + !currentLoginStatus) { KRLogUtil.kr_w('状态不一致:视图显示已登录但实际未登录,修正状态', tag: 'HomeController'); kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn; - } else if (currentViewStatus == KRHomeViewsStatus.kr_notLoggedIn && currentLoginStatus) { + } else if (currentViewStatus == KRHomeViewsStatus.kr_notLoggedIn && + currentLoginStatus) { KRLogUtil.kr_w('状态不一致:视图显示未登录但实际已登录,修正状态', tag: 'HomeController'); _kr_validateAndSetLoginStatus(); } @@ -639,7 +650,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 订阅服务已在 splash 页面初始化,如果状态为none可能是网络问题 if (kr_currentViewStatus.value == KRHomeViewsStatus.kr_loggedIn && kr_subscribeService.kr_availableSubscribes.isEmpty) { - KRLogUtil.kr_i('订阅服务状态为none,可能是网络问题,已在启动页初始化', tag: 'HomeController'); + KRLogUtil.kr_i('订阅服务状态为none,可能是网络问题,已在启动页初始化', + tag: 'HomeController'); } break; } @@ -715,8 +727,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { break; case SingboxStarted(): KRLogUtil.kr_i('🟢 状态: 已启动', tag: 'HomeController'); - if (kDebugMode) { - } + if (kDebugMode) {} // 取消连接超时处理 _cancelConnectionTimeout(); @@ -728,12 +739,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 🔧 关键修复:如果延迟还是-1(连接中状态),立即设置为0(已连接但延迟未知) if (kr_currentNodeLatency.value == -1) { - if (kDebugMode) { - } + if (kDebugMode) {} kr_currentNodeLatency.value = 0; kr_currentNodeLatency.refresh(); // 强制刷新延迟值 - if (kDebugMode) { - } + if (kDebugMode) {} } // 🔧 修复:立即尝试更新延迟值 @@ -743,8 +752,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { kr_isConnected.refresh(); // 强制更新UI update(); - if (kDebugMode) { - } + if (kDebugMode) {} break; case SingboxStopping(): KRLogUtil.kr_i('🟠 状态: 正在停止', tag: 'HomeController'); @@ -777,25 +785,29 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { try { bool hasSelector = false; for (var element in value) { - KRLogUtil.kr_i('📋 处理组: ${element.tag}, 类型: ${element.type}, 选中: ${element.selected}', tag: 'HomeController'); + KRLogUtil.kr_i( + '📋 处理组: ${element.tag}, 类型: ${element.type}, 选中: ${element.selected}', + tag: 'HomeController'); if (element.type == ProxyType.selector) { hasSelector = true; _kr_handleSelectorProxy(element, value); } else if (element.type == ProxyType.urltest) { - KRLogUtil.kr_d('URL测试代理选中: ${element.selected}', tag: 'HomeController'); + KRLogUtil.kr_d('URL测试代理选中: ${element.selected}', + tag: 'HomeController'); } } // 🔧 修复:如果已连接但没有selector组,设置延迟为0 - if (!hasSelector && kr_isConnected.value && kr_currentNodeLatency.value == -1) { + if (!hasSelector && + kr_isConnected.value && + kr_currentNodeLatency.value == -1) { KRLogUtil.kr_w('⚠️ 已连接但无selector组,设置延迟为0', tag: 'HomeController'); kr_currentNodeLatency.value = 0; } // 强制更新UI update(); - } catch (e) { KRLogUtil.kr_e('处理活动组时发生错误: $e', tag: 'HomeController'); // 🔧 修复:发生错误时,如果已连接,设置延迟为0 @@ -831,15 +843,16 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { Future kr_toggleSwitch(bool value) async { final currentStatus = KRSingBoxImp.instance.kr_status.value; - KRLogUtil.kr_i('🔵 toggleSwitch 被调用: value=$value, currentStatus=$currentStatus', tag: 'HomeController'); - if (kDebugMode) { - } + KRLogUtil.kr_i( + '🔵 toggleSwitch 被调用: value=$value, currentStatus=$currentStatus', + tag: 'HomeController'); + if (kDebugMode) {} // 🔧 关键: 如果正在切换中,直接忽略(参考 hiddify-app 的 "switching status, debounce") if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) { - KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', tag: 'HomeController'); - if (kDebugMode) { - } + KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', + tag: 'HomeController'); + if (kDebugMode) {} return; } @@ -847,20 +860,18 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { if (value) { // 开启连接 KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController'); - if (kDebugMode) { - } + if (kDebugMode) {} + await _kr_prepareCountrySelectionBeforeStart(); await KRSingBoxImp.instance.kr_start(); KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController'); - if (kDebugMode) { - } + if (kDebugMode) {} // 🔧 修复: 等待状态更新,最多3秒 await _waitForStatus(SingboxStarted, maxSeconds: 3); } else { // 关闭连接 KRLogUtil.kr_i('🛑 开始断开连接...', tag: 'HomeController'); - if (kDebugMode) { - } + if (kDebugMode) {} await KRSingBoxImp.instance.kr_stop().timeout( const Duration(seconds: 10), onTimeout: () { @@ -869,38 +880,32 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { }, ); KRLogUtil.kr_i('✅ 断开命令已发送', tag: 'HomeController'); - if (kDebugMode) { - } + if (kDebugMode) {} // 🔧 修复: 等待状态更新,最多2秒 await _waitForStatus(SingboxStopped, maxSeconds: 2); } } catch (e) { KRLogUtil.kr_e('❌ 切换失败: $e', tag: 'HomeController'); - if (kDebugMode) { - } + if (kDebugMode) {} // 发生错误时强制同步状态 kr_forceSyncConnectionStatus(); } - if (kDebugMode) { - } + if (kDebugMode) {} } /// 🔧 等待状态达到预期值 Future _waitForStatus(Type expectedType, {int maxSeconds = 3}) async { - if (kDebugMode) { - } + if (kDebugMode) {} final startTime = DateTime.now(); while (DateTime.now().difference(startTime).inSeconds < maxSeconds) { final currentStatus = KRSingBoxImp.instance.kr_status.value; - if (kDebugMode) { - } + if (kDebugMode) {} if (currentStatus.runtimeType == expectedType) { - if (kDebugMode) { - } + if (kDebugMode) {} // 强制同步确保 kr_isConnected 正确 kr_forceSyncConnectionStatus(); return; @@ -909,11 +914,151 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { await Future.delayed(const Duration(milliseconds: 100)); } - if (kDebugMode) { - } + if (kDebugMode) {} kr_forceSyncConnectionStatus(); } + Future _kr_prepareCountrySelectionBeforeStart() async { + try { + KRLogUtil.kr_i('开始准备国家选择', tag: 'CountrySelect'); + final storedCountry = + await KRSecureStorage().kr_readData(key: 'SELECTED_COUNTRY_TAG') ?? + 'auto'; + KRLogUtil.kr_i('读取 SELECTED_COUNTRY_TAG: $storedCountry', + tag: 'CountrySelect'); + if (storedCountry == 'auto') { + KRLogUtil.kr_w('当前为 auto,按全局最优节点进行选择', tag: 'CountrySelect'); + final best = _kr_selectBestNodeTagGlobal(); + if (best != null && best.isNotEmpty) { + KRLogUtil.kr_i('选中全局最优节点: $best', tag: 'CountrySelect'); + await KRSecureStorage() + .kr_saveData(key: 'SELECTED_NODE_TAG', value: best); + final verify = + await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG'); + KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verify', + tag: 'CountrySelect'); + kr_cutSeletedTag.value = best; + kr_updateConnectionInfo(); + return; + } + KRLogUtil.kr_w('未找到可用的全局最优节点', tag: 'CountrySelect'); + KRLogUtil.kr_i('触发直接延迟测试以获取有效延迟', tag: 'CountrySelect'); + await _kr_testLatencyWithoutVpn(); + final bestAfterTest = _kr_selectBestNodeTagGlobal(); + if (bestAfterTest != null && bestAfterTest.isNotEmpty) { + KRLogUtil.kr_i('延迟测试后选中全局最优节点: $bestAfterTest', tag: 'CountrySelect'); + await KRSecureStorage() + .kr_saveData(key: 'SELECTED_NODE_TAG', value: bestAfterTest); + final verifyA = + await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG'); + KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verifyA', + tag: 'CountrySelect'); + kr_cutSeletedTag.value = bestAfterTest; + kr_updateConnectionInfo(); + return; + } + String? fallback = kr_subscribeService.keyList.values + .where((n) => n.tag != 'auto') + .map((n) => n.tag) + .cast() + .firstWhere((t) => t != null && t.isNotEmpty, orElse: () => null); + if (fallback != null && fallback.isNotEmpty) { + KRLogUtil.kr_w('使用后备节点: $fallback', tag: 'CountrySelect'); + await KRSecureStorage() + .kr_saveData(key: 'SELECTED_NODE_TAG', value: fallback); + final verifyB = + await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG'); + KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verifyB', + tag: 'CountrySelect'); + kr_cutSeletedTag.value = fallback; + kr_updateConnectionInfo(); + } else { + KRLogUtil.kr_w('没有可用的后备节点', tag: 'CountrySelect'); + } + return; + } + final currentSelected = + await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG'); + KRLogUtil.kr_i('当前 SELECTED_NODE_TAG: ${currentSelected ?? ''}', + tag: 'CountrySelect'); + bool valid = false; + if (currentSelected != null && currentSelected.isNotEmpty) { + final node = kr_subscribeService.keyList[currentSelected]; + if (node != null) { + final d = node.urlTestDelay.value; + if (node.country == storedCountry && d > 0 && d < 65535) { + valid = true; + } + KRLogUtil.kr_i( + '校验节点: tag=${node.tag}, country=${node.country}, delay=$d, valid=$valid', + tag: 'CountrySelect'); + } + } + if (!valid) { + KRLogUtil.kr_w('当前节点无效或不匹配国家,将按国家选择最优节点: $storedCountry', + tag: 'CountrySelect'); + final bestInCountry = _kr_selectBestNodeTagInCountry(storedCountry); + if (bestInCountry != null && bestInCountry.isNotEmpty) { + KRLogUtil.kr_i('选中国家最优节点: $bestInCountry', tag: 'CountrySelect'); + await KRSecureStorage() + .kr_saveData(key: 'SELECTED_NODE_TAG', value: bestInCountry); + final verify2 = + await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG'); + KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verify2', + tag: 'CountrySelect'); + kr_cutSeletedTag.value = bestInCountry; + kr_updateConnectionInfo(); + } else { + KRLogUtil.kr_w('未找到国家范围内的可用节点: $storedCountry', tag: 'CountrySelect'); + } + } + KRLogUtil.kr_i('国家选择准备完成', tag: 'CountrySelect'); + } catch (_) {} + } + + String? _kr_selectBestNodeTagGlobal() { + try { + String? bestTag; + int bestDelay = 65535; + for (final node in kr_subscribeService.keyList.values) { + final tag = node.tag; + final delay = node.urlTestDelay.value; + if (tag == 'auto') continue; + if (delay > 0 && delay < 65535) { + if (delay < bestDelay) { + bestDelay = delay; + bestTag = tag; + } + } + } + return bestTag; + } catch (_) { + return null; + } + } + + String? _kr_selectBestNodeTagInCountry(String countryCode) { + try { + String? bestTag; + int bestDelay = 65535; + for (final node in kr_subscribeService.keyList.values) { + final tag = node.tag; + final delay = node.urlTestDelay.value; + if (tag == 'auto') continue; + if (node.country != countryCode) continue; + if (delay > 0 && delay < 65535) { + if (delay < bestDelay) { + bestDelay = delay; + bestTag = tag; + } + } + } + return bestTag; + } catch (_) { + return null; + } + } + /// 处理选择器代理 void _kr_handleSelectorProxy(dynamic element, List allGroups) { try { @@ -921,7 +1066,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { '处理选择器代理 - 当前选择: ${element.selected}, 用户选择: ${kr_cutTag.value}', tag: 'HomeController'); - // 🔧 关键修复:仅更新 UI 状态,不要触发重新选择,避免死循环 // 更新 kr_cutSeletedTag 以反映实际选中的节点 if (element.selected.isNotEmpty) { @@ -981,7 +1125,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } // 🔧 修复:如果已连接但延迟未更新,设置为0 - if (!delayUpdated && kr_isConnected.value && kr_currentNodeLatency.value == -1) { + if (!delayUpdated && + kr_isConnected.value && + kr_currentNodeLatency.value == -1) { KRLogUtil.kr_w('⚠️ 已连接但延迟未更新,设置为0', tag: 'HomeController'); kr_currentNodeLatency.value = 0; } @@ -1131,7 +1277,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 🔒 节点切换状态锁和节流控制 bool _isSwitchingNode = false; DateTime? _lastSwitchTime; - static const Duration _switchThrottleDuration = Duration(milliseconds: 2000); // 2秒节流 + static const Duration _switchThrottleDuration = + Duration(milliseconds: 2000); // 2秒节流 /// 🔧 修复:统一的节点切换方法(包含UI同步和后台操作等待) /// 执行节点切换,包括UI更新和后台操作的完整同步 @@ -1147,9 +1294,12 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 🔒 节流控制: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'); + 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; } @@ -1176,7 +1326,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 🔧 修复:保存节点选择以便VPN启动时应用 KRLogUtil.kr_i('💾 保存节点选择以便稍后应用: $tag', tag: 'HomeController'); - KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag).then((_) { + KRSecureStorage() + .kr_saveData(key: 'SELECTED_NODE_TAG', value: tag) + .then((_) { KRLogUtil.kr_i('✅ 节点选择已保存: $tag', tag: 'HomeController'); }).catchError((e) { KRLogUtil.kr_e('❌ 保存节点选择失败: $e', tag: 'HomeController'); @@ -1190,9 +1342,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { KRLogUtil.kr_i('🔌 VPN已连接,将重启VPN以断开现有连接: $tag', tag: 'HomeController'); // 🔧 诊断:打印当前活动组信息 - KRLogUtil.kr_i('📊 当前活动组数量: ${KRSingBoxImp.instance.kr_activeGroups.length}', tag: 'HomeController'); + KRLogUtil.kr_i( + '📊 当前活动组数量: ${KRSingBoxImp.instance.kr_activeGroups.length}', + tag: 'HomeController'); for (var group in KRSingBoxImp.instance.kr_activeGroups) { - KRLogUtil.kr_i('📋 活动组: tag=${group.tag}, type=${group.type}, 节点数=${group.items.length}', tag: 'HomeController'); + KRLogUtil.kr_i( + '📋 活动组: tag=${group.tag}, type=${group.type}, 节点数=${group.items.length}', + tag: 'HomeController'); for (var item in group.items) { if (item.tag == tag) { KRLogUtil.kr_i('✅ 找到目标节点: ${item.tag}', tag: 'HomeController'); @@ -1206,7 +1362,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 🔧 关键修复:保存新节点选择 KRLogUtil.kr_i('💾 保存新节点选择: $tag', tag: 'HomeController'); - await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag); + await KRSecureStorage() + .kr_saveData(key: 'SELECTED_NODE_TAG', value: tag); // 🔧 方案A优化:重启VPN连接以断开所有现有长连接 KRLogUtil.kr_i('🔄 [优化] 停止VPN连接以断开现有连接...', tag: 'HomeController'); @@ -1214,14 +1371,16 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 🚀 优化:减少等待时间(DNS操作已优化,无需过长等待) KRLogUtil.kr_i('⏳ [优化] 等待VPN完全停止(800ms)...', tag: 'HomeController'); - await Future.delayed(const Duration(milliseconds: 800)); // 从1500ms减少到800ms + await Future.delayed( + const Duration(milliseconds: 800)); // 从1500ms减少到800ms KRLogUtil.kr_i('🔄 [优化] 启动VPN并应用新节点: $tag', tag: 'HomeController'); await KRSingBoxImp.instance.kr_start(); // 重新启动VPN(已跳过DNS备份) // 🚀 优化:减少等待时间(DNS操作已优化) KRLogUtil.kr_i('⏳ [优化] 等待VPN完全启动(1200ms)...', tag: 'HomeController'); - await Future.delayed(const Duration(milliseconds: 1200)); // 从2500ms减少到1200ms + await Future.delayed( + const Duration(milliseconds: 1200)); // 从2500ms减少到1200ms // 后台切换成功,更新UI kr_cutSeletedTag.value = tag; @@ -1230,7 +1389,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 🚀 优化:减少验证等待时间 KRLogUtil.kr_i('⏳ [优化] 等待活动组更新(300ms)...', tag: 'HomeController'); - await Future.delayed(const Duration(milliseconds: 300)); // 从500ms减少到300ms + await Future.delayed( + const Duration(milliseconds: 300)); // 从500ms减少到300ms // 🚀 方案A增强:验证节点是否真正切换成功 KRLogUtil.kr_i('🔍 [增强] 验证节点切换是否成功...', tag: 'HomeController'); @@ -1238,15 +1398,17 @@ 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 组'), ); - KRLogUtil.kr_i('📊 [增强] Select组当前选中: ${selectGroup.selected}', tag: 'HomeController'); + KRLogUtil.kr_i('📊 [增强] Select组当前选中: ${selectGroup.selected}', + tag: 'HomeController'); KRLogUtil.kr_i('📊 [增强] 目标节点: $tag', tag: 'HomeController'); if (selectGroup.selected != tag) { - KRLogUtil.kr_w('⚠️ [增强] 节点选择验证失败,实际选中: ${selectGroup.selected}', tag: 'HomeController'); + KRLogUtil.kr_w('⚠️ [增强] 节点选择验证失败,实际选中: ${selectGroup.selected}', + tag: 'HomeController'); // 不抛出异常,但记录警告 } else { KRLogUtil.kr_i('✅ [增强] 节点选择验证成功!', tag: 'HomeController'); @@ -1260,7 +1422,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { KRLogUtil.kr_i('✅ 节点切换成功(已重启VPN断开旧连接): $tag', tag: 'HomeController'); return true; - } catch (switchError) { // 后台切换失败,恢复到原节点 KRLogUtil.kr_e('❌ 后台节点切换失败: $switchError', tag: 'HomeController'); @@ -1272,7 +1433,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 恢复原节点选择 try { - await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: originalTag); + await KRSecureStorage() + .kr_saveData(key: 'SELECTED_NODE_TAG', value: originalTag); } catch (e) { KRLogUtil.kr_e('❌ 恢复节点选择失败: $e', tag: 'HomeController'); } @@ -1296,7 +1458,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } } - /// 🔧 修复:简化的 kr_selectNode 方法 /// 现在只是委托给新的 kr_performNodeSwitch 方法 /// 为了保持向后兼容,保留此方法但改为调用新方法 @@ -1311,16 +1472,22 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { /// 2. 如果是 auto,优先使用 kr_cutSeletedTag(保存了实际选中的节点) /// 3. 如果 kr_cutSeletedTag 也是 auto 或空,再尝试从 kr_activeGroups 获取 String kr_getCurrentNodeCountry() { - KRLogUtil.kr_i('========== 开始获取国家代码 ==========', tag: 'getCurrentNodeCountry'); - KRLogUtil.kr_i('kr_cutTag: ${kr_cutTag.value}', tag: 'getCurrentNodeCountry'); - KRLogUtil.kr_i('kr_cutSeletedTag: ${kr_cutSeletedTag.value}', tag: 'getCurrentNodeCountry'); - KRLogUtil.kr_i('keyList 节点总数: ${kr_subscribeService.keyList.length}', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i('========== 开始获取国家代码 ==========', + tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i('kr_cutTag: ${kr_cutTag.value}', + tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i('kr_cutSeletedTag: ${kr_cutSeletedTag.value}', + tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i('keyList 节点总数: ${kr_subscribeService.keyList.length}', + tag: 'getCurrentNodeCountry'); String actualTag; // 🔧 优先策略: // 1. 如果 kr_cutTag 不是 auto,直接使用(用户手动选择的节点) - if (kr_cutTag.value != 'auto' && kr_cutTag.value != 'select' && kr_cutTag.value.isNotEmpty) { + if (kr_cutTag.value != 'auto' && + kr_cutTag.value != 'select' && + kr_cutTag.value.isNotEmpty) { // 用户手动选择了具体节点 actualTag = kr_cutTag.value; KRLogUtil.kr_i('✅ 使用手动选择的节点: $actualTag', tag: 'getCurrentNodeCountry'); @@ -1331,71 +1498,85 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { kr_cutSeletedTag.value != 'select') { // auto 模式下,使用保存的实际节点 actualTag = kr_cutSeletedTag.value; - KRLogUtil.kr_i('✅ 使用 auto 模式下的实际节点 (kr_cutSeletedTag): $actualTag', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i('✅ 使用 auto 模式下的实际节点 (kr_cutSeletedTag): $actualTag', + tag: 'getCurrentNodeCountry'); } // 3. 降级:尝试从活动组获取 else { try { KRLogUtil.kr_i('⚠️ 尝试从活动组获取实际节点', tag: 'getCurrentNodeCountry'); - KRLogUtil.kr_i('活动组数量: ${KRSingBoxImp.instance.kr_activeGroups.length}', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i('活动组数量: ${KRSingBoxImp.instance.kr_activeGroups.length}', + tag: 'getCurrentNodeCountry'); // 🔧 修复:活动组为空时,尝试使用 allGroups if (KRSingBoxImp.instance.kr_activeGroups.isEmpty) { print('[getCurrentNodeCountry] ⚠️ 活动组为空,尝试使用 allGroups'); - KRLogUtil.kr_w('⚠️ 活动组为空,尝试使用 allGroups', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_w('⚠️ 活动组为空,尝试使用 allGroups', + tag: 'getCurrentNodeCountry'); final allGroups = KRSingBoxImp.instance.kr_allGroups; print('[getCurrentNodeCountry] allGroups 数量: ${allGroups.length}'); if (allGroups.isEmpty) { print('[getCurrentNodeCountry] ❌ allGroups 也为空,返回空字符串'); - KRLogUtil.kr_w('❌ allGroups 也为空,返回空字符串', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_w('❌ allGroups 也为空,返回空字符串', + tag: 'getCurrentNodeCountry'); return ''; } // 从 allGroups 中查找 select 组 final selectGroup = allGroups.firstWhere( - (group) => group.tag == 'select', + (group) => group.tag == 'select', orElse: () => throw Exception('未找到 select 组'), ); - print('[getCurrentNodeCountry] selectGroup.selected: ${selectGroup.selected}'); + print( + '[getCurrentNodeCountry] selectGroup.selected: ${selectGroup.selected}'); - if (selectGroup.selected.isEmpty || selectGroup.selected == 'auto' || selectGroup.selected == 'select') { + if (selectGroup.selected.isEmpty || + selectGroup.selected == 'auto' || + selectGroup.selected == 'select') { 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 组'), ); if (urlTestGroup.selected.isNotEmpty) { actualTag = urlTestGroup.selected; - print('[getCurrentNodeCountry] ✅ 从 allGroups 的 urltest 组获取节点: $actualTag'); - KRLogUtil.kr_i('✅ 从 allGroups 的 urltest 组获取节点: $actualTag', tag: 'getCurrentNodeCountry'); + print( + '[getCurrentNodeCountry] ✅ 从 allGroups 的 urltest 组获取节点: $actualTag'); + KRLogUtil.kr_i('✅ 从 allGroups 的 urltest 组获取节点: $actualTag', + tag: 'getCurrentNodeCountry'); } else { print('[getCurrentNodeCountry] ❌ urltest 组的 selected 也为空'); - KRLogUtil.kr_w('❌ urltest 组的 selected 也为空', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_w('❌ urltest 组的 selected 也为空', + tag: 'getCurrentNodeCountry'); return ''; } } else { actualTag = selectGroup.selected; - print('[getCurrentNodeCountry] ✅ 从 allGroups 的 select 组获取节点: $actualTag'); - KRLogUtil.kr_i('✅ 从 allGroups 的 select 组获取节点: $actualTag', tag: 'getCurrentNodeCountry'); + print( + '[getCurrentNodeCountry] ✅ 从 allGroups 的 select 组获取节点: $actualTag'); + KRLogUtil.kr_i('✅ 从 allGroups 的 select 组获取节点: $actualTag', + tag: 'getCurrentNodeCountry'); } } else { // 活动组不为空,从活动组获取 // 从 SingBox 活动组中找到 "select" 选择器组 final selectGroup = KRSingBoxImp.instance.kr_activeGroups.firstWhere( - (group) => group.tag == 'select', + (group) => group.tag == 'select', orElse: () => throw Exception('未找到 select 组'), ); if (selectGroup.selected.isEmpty) { - KRLogUtil.kr_w('❌ select 组的 selected 为空', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_w('❌ select 组的 selected 为空', + tag: 'getCurrentNodeCountry'); return ''; } actualTag = selectGroup.selected; - KRLogUtil.kr_i('✅ 从活动组获取节点: $actualTag', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i('✅ 从活动组获取节点: $actualTag', + tag: 'getCurrentNodeCountry'); } } catch (e) { KRLogUtil.kr_e('❌ 从活动组获取节点失败: $e', tag: 'getCurrentNodeCountry'); @@ -1419,9 +1600,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { int count = 0; for (var key in kr_subscribeService.keyList.keys) { count++; - KRLogUtil.kr_e(' [$count] $key -> country: ${kr_subscribeService.keyList[key]?.country}', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_e( + ' [$count] $key -> country: ${kr_subscribeService.keyList[key]?.country}', + tag: 'getCurrentNodeCountry'); if (count >= 10) { - KRLogUtil.kr_e(' ... 还有 ${kr_subscribeService.keyList.length - 10} 个节点', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_e( + ' ... 还有 ${kr_subscribeService.keyList.length - 10} 个节点', + tag: 'getCurrentNodeCountry'); break; } } @@ -1430,18 +1615,22 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { KRLogUtil.kr_i('✅ 找到节点: $actualTag', tag: 'getCurrentNodeCountry'); KRLogUtil.kr_i(' - city: ${node.city}', tag: 'getCurrentNodeCountry'); - KRLogUtil.kr_i(' - country: "${node.country}"', tag: 'getCurrentNodeCountry'); - KRLogUtil.kr_i(' - country.isEmpty: ${node.country.isEmpty}', tag: 'getCurrentNodeCountry'); - KRLogUtil.kr_i(' - country.length: ${node.country.length}', tag: 'getCurrentNodeCountry'); - KRLogUtil.kr_i('========== 国家代码获取结束 ==========', tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i(' - country: "${node.country}"', + tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i(' - country.isEmpty: ${node.country.isEmpty}', + tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i(' - country.length: ${node.country.length}', + tag: 'getCurrentNodeCountry'); + KRLogUtil.kr_i('========== 国家代码获取结束 ==========', + tag: 'getCurrentNodeCountry'); return node.country; } - /// 获取真实连接的节点信息(统一为手动选择场景) Map kr_getRealConnectedNodeInfo() { - String actualTag = kr_cutSeletedTag == 'auto' ? kr_cutSeletedTag.value : kr_cutTag.value; + String actualTag = + kr_cutSeletedTag == 'auto' ? kr_cutSeletedTag.value : kr_cutTag.value; final node = kr_subscribeService.keyList[actualTag]; return { 'nodeName': kr_cutTag.value, @@ -1450,12 +1639,11 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { }; } - /// 获取真实连接的节点国家 String kr_getRealConnectedNodeCountry() { - final country = kr_getCurrentNodeCountry(); - if(country.isEmpty) return ''; - return kr_getCountryFullName(country); + 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 @@ -1476,7 +1664,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // } } - // 格式化字节数 String kr_formatBytes(int bytes) { if (bytes < 1024) { @@ -1573,24 +1760,24 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { switch (state) { case AppLifecycleState.resumed: - // 应用从后台恢复到前台,同步连接状态 + // 应用从后台恢复到前台,同步连接状态 KRLogUtil.kr_i('应用恢复到前台,同步连接状态', tag: 'QuickConnect'); _onAppResumed(); break; case AppLifecycleState.paused: - // 应用进入后台 + // 应用进入后台 KRLogUtil.kr_i('应用进入后台', tag: 'QuickConnect'); break; case AppLifecycleState.detached: - // 应用被销毁 + // 应用被销毁 KRLogUtil.kr_i('应用被销毁', tag: 'QuickConnect'); break; case AppLifecycleState.inactive: - // 应用处于非活动状态(如来电话时) + // 应用处于非活动状态(如来电话时) KRLogUtil.kr_i('应用处于非活动状态', tag: 'QuickConnect'); break; case AppLifecycleState.hidden: - // 应用被隐藏 + // 应用被隐藏 KRLogUtil.kr_i('应用被隐藏', tag: 'QuickConnect'); break; } @@ -1608,7 +1795,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { void kr_updateBottomPanelHeight() { // 🔧 Android 15 优化:加载状态时也允许更新高度,显示加载指示器 if (kr_subscribeService.kr_currentStatus == - KRHomeViewsListStatus.kr_loading && + KRHomeViewsListStatus.kr_loading && kr_currentListStatus.value != KRHomeViewsListStatus.kr_loading) { return; } @@ -1634,10 +1821,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { kr_marginTop + kr_marginBottom + kr_marginVertical * 2; - KRLogUtil.kr_i( - '未登录状态,目标高度: $targetHeight', tag: 'HomeController'); + KRLogUtil.kr_i('未登录状态,目标高度: $targetHeight', tag: 'HomeController'); } else if (kr_currentListStatus.value == - KRHomeViewsListStatus.kr_serverList || + KRHomeViewsListStatus.kr_serverList || kr_currentListStatus.value == KRHomeViewsListStatus.kr_countrySubscribeList || kr_currentListStatus.value == @@ -1648,12 +1834,11 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { } // 🔧 Android 15 新增:处理加载和错误状态 else if (kr_currentListStatus.value == KRHomeViewsListStatus.kr_loading || - kr_currentListStatus.value == KRHomeViewsListStatus.kr_error) { + kr_currentListStatus.value == KRHomeViewsListStatus.kr_error) { // 加载或错误状态下显示最小高度 targetHeight = kr_loadingHeight + kr_marginTop + kr_marginBottom; KRLogUtil.kr_i('加载/错误状态,目标高度: $targetHeight', tag: 'HomeController'); - } - else { + } else { // 已登录状态下的默认高度计算 targetHeight = kr_baseHeight + kr_marginTop + kr_marginBottom; KRLogUtil.kr_i('基础高度: $targetHeight', tag: 'HomeController'); @@ -1731,9 +1916,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { if (lat != coords.latitude || lng != coords.longitude) { KRLogUtil.kr_w( - '⚠️ 坐标超出范围,已约束: (${coords.latitude}, ${coords.longitude}) → ($lat, $lng)', - tag: 'HomeController' - ); + '⚠️ 坐标超出范围,已约束: (${coords.latitude}, ${coords.longitude}) → ($lat, $lng)', + tag: 'HomeController'); } return LatLng(lat, lng); @@ -1756,8 +1940,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { void showMarkersMap() { KRLogUtil.kr_i('========== 刷新地图标记 ==========', tag: 'HomeController'); KRLogUtil.kr_i('当前选中节点: ${kr_cutSeletedTag.value}', tag: 'HomeController'); - KRLogUtil.kr_i('可用节点数: ${kr_subscribeService.allList.length}', tag: 'HomeController'); - KRLogUtil.kr_i('国家分组数: ${kr_subscribeService.countryOutboundList.length}', tag: 'HomeController'); + KRLogUtil.kr_i('可用节点数: ${kr_subscribeService.allList.length}', + tag: 'HomeController'); + KRLogUtil.kr_i('国家分组数: ${kr_subscribeService.countryOutboundList.length}', + tag: 'HomeController'); // 手动触发地图标记更新 update(['map_markers']); KRLogUtil.kr_i('✅ 地图标记更新完成', tag: 'HomeController'); @@ -1797,10 +1983,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { final activeGroups = KRSingBoxImp.instance.kr_activeGroups; for (int i = 0; i < activeGroups.length; i++) { final group = activeGroups[i]; - KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController'); + KRLogUtil.kr_i( + '📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', + tag: 'HomeController'); for (int j = 0; j < group.items.length; j++) { final item = group.items[j]; - KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'HomeController'); + KRLogUtil.kr_i( + ' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', + tag: 'HomeController'); } } } catch (e) { @@ -1828,7 +2018,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { try { KRLogUtil.kr_i('🧪 开始延迟测试...', tag: 'HomeController'); - KRLogUtil.kr_i('📊 当前连接状态: ${kr_isConnected.value}', tag: 'HomeController'); + KRLogUtil.kr_i('📊 当前连接状态: ${kr_isConnected.value}', + tag: 'HomeController'); if (kr_isConnected.value) { // 已连接状态:使用 SingBox 通过代理测试 @@ -1843,15 +2034,20 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { final activeGroups = KRSingBoxImp.instance.kr_activeGroups; for (int i = 0; i < activeGroups.length; i++) { final group = activeGroups[i]; - KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController'); + KRLogUtil.kr_i( + '📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', + tag: 'HomeController'); for (int j = 0; j < group.items.length; j++) { final item = group.items[j]; - KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'HomeController'); + KRLogUtil.kr_i( + ' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', + tag: 'HomeController'); } } } else { // 未连接状态:使用本机网络直接ping节点IP - KRLogUtil.kr_i('🔌 未连接状态 - 使用本机网络直接ping节点IP测试延迟', tag: 'HomeController'); + KRLogUtil.kr_i('🔌 未连接状态 - 使用本机网络直接ping节点IP测试延迟', + tag: 'HomeController'); KRLogUtil.kr_i('🌐 这将绕过代理,直接使用本机网络连接节点', tag: 'HomeController'); await _kr_testLatencyWithoutVpn(); } @@ -1869,96 +2065,140 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { Future _kr_testLatencyWithoutVpn() async { kr_isLatency.value = true; try { - KRLogUtil.kr_i('🚀 开始真实延迟测试(TCP连接测试)', tag: 'HomeController'); - KRLogUtil.kr_i('📊 当前连接状态: ${kr_isConnected.value}', tag: 'HomeController'); + KRLogUtil.kr_i('🚀 开始真实延迟测试(按国家分两阶段)', tag: 'HomeController'); + KRLogUtil.kr_i('📊 当前连接状态: ${kr_isConnected.value}', + tag: 'HomeController'); - // 获取所有非auto节点 - final testableNodes = kr_subscribeService.allList + final allNodes = kr_subscribeService.allList .where((item) => item.tag != 'auto') .toList(); - KRLogUtil.kr_i('📋 找到 ${testableNodes.length} 个可测试节点', tag: 'HomeController'); - - if (testableNodes.isEmpty) { + KRLogUtil.kr_i('📋 可测试节点总数: ${allNodes.length}', tag: 'HomeController'); + if (allNodes.isEmpty) { KRLogUtil.kr_w('⚠️ 没有可测试的节点', tag: 'HomeController'); return; } - // 准备节点地址列表 - final nodeAddresses = >[]; + final Map> byCountry = {}; + for (final node in allNodes) { + final code = node.country; + byCountry.putIfAbsent(code, () => []).add(node); + } + KRLogUtil.kr_i('🌎 国家数: ${byCountry.length}', tag: 'HomeController'); - for (final node in testableNodes) { - // 从节点配置中提取服务器地址和端口 + final Map tagToNode = { + for (final n in allNodes) n.tag: n, + }; + + final List> stage1 = []; + final List stage1Tags = []; + for (final entry in byCountry.entries) { + final list = entry.value; + if (list.isEmpty) continue; + final node = list.first; try { String host = node.serverAddr; int port = 0; - - // 尝试从config中获取端口 if (node.config.containsKey('server_port')) { port = node.config['server_port'] as int; } else if (node.config.containsKey('port')) { port = node.config['port'] as int; } - if (host.isNotEmpty && port > 0) { - nodeAddresses.add(MapEntry( - node.tag, - SocketAddress(host, port), - )); - KRLogUtil.kr_i('✓ 节点 ${node.tag}: $host:$port', tag: 'HomeController'); + stage1.add(MapEntry(node.tag, SocketAddress(host, port))); + stage1Tags.add(node.tag); + KRLogUtil.kr_i('阶段1加入: ${entry.key}/${node.tag} -> $host:$port', + tag: 'HomeController'); } else { - KRLogUtil.kr_w('⚠️ 节点 ${node.tag} 缺少地址或端口信息', tag: 'HomeController'); - // 设置为失败 - node.urlTestDelay.value = 65535; + final n = tagToNode[node.tag]; + if (n != null) n.urlTestDelay.value = 65535; + KRLogUtil.kr_w('阶段1节点缺少地址或端口: ${node.tag}', tag: 'HomeController'); } } catch (e) { - KRLogUtil.kr_e('❌ 解析节点 ${node.tag} 配置失败: $e', tag: 'HomeController'); - node.urlTestDelay.value = 65535; + final n = tagToNode[node.tag]; + if (n != null) n.urlTestDelay.value = 65535; + KRLogUtil.kr_w('阶段1解析失败: ${node.tag}, $e', tag: 'HomeController'); } } - if (nodeAddresses.isEmpty) { - KRLogUtil.kr_w('⚠️ 没有有效的节点地址可测试', tag: 'HomeController'); - return; + if (stage1.isNotEmpty) { + KRLogUtil.kr_i('🔌 阶段1测试国家首个节点: ${stage1.length}', + tag: 'HomeController'); + final results1 = await KRLatencyTester.testMultipleNodes( + nodes: stage1, + concurrency: 5, + timeout: const Duration(seconds: 5), + ); + int s1ok = 0; + for (final tag in stage1Tags) { + final delay = results1[tag]; + if (delay != null) { + final node = tagToNode[tag]; + if (node != null) { + node.urlTestDelay.value = delay; + s1ok++; + } + } + } + KRLogUtil.kr_i('✅ 阶段1完成: 成功 $s1ok/${stage1Tags.length}', + tag: 'HomeController'); } - KRLogUtil.kr_i('🔌 开始批量测试 ${nodeAddresses.length} 个节点...', tag: 'HomeController'); - - // 使用真实的延迟测试工具 - final results = await KRLatencyTester.testMultipleNodes( - nodes: nodeAddresses, - concurrency: 10, // 每批10个并发 - timeout: const Duration(seconds: 5), - ); - - // 更新节点延迟 - for (final node in testableNodes) { - if (results.containsKey(node.tag)) { - node.urlTestDelay.value = results[node.tag]!; + final List> stage2 = []; + final List stage2Tags = []; + for (final entry in byCountry.entries) { + final list = entry.value; + if (list.length <= 1) continue; + for (final node in list.skip(1)) { + try { + String host = node.serverAddr; + int port = 0; + if (node.config.containsKey('server_port')) { + port = node.config['server_port'] as int; + } else if (node.config.containsKey('port')) { + port = node.config['port'] as int; + } + if (host.isNotEmpty && port > 0) { + stage2.add(MapEntry(node.tag, SocketAddress(host, port))); + stage2Tags.add(node.tag); + } else { + final n = tagToNode[node.tag]; + if (n != null) n.urlTestDelay.value = 65535; + } + } catch (e) { + final n = tagToNode[node.tag]; + if (n != null) n.urlTestDelay.value = 65535; + } } } - // 统计测试结果 - final successCount = testableNodes.where((item) => item.urlTestDelay.value < 65535).length; - final failCount = testableNodes.length - successCount; - - KRLogUtil.kr_i('✅ 真实延迟测试完成', tag: 'HomeController'); - KRLogUtil.kr_i('📊 测试结果: 成功 $successCount 个,失败 $failCount 个', tag: 'HomeController'); - - // 显示延迟最低的前3个节点 - final sortedNodes = testableNodes - .where((item) => item.urlTestDelay.value < 65535) - .toList() - ..sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value)); - - if (sortedNodes.isNotEmpty) { - KRLogUtil.kr_i('🏆 延迟最低的前3个节点:', tag: 'HomeController'); - for (int i = 0; i < 3 && i < sortedNodes.length; i++) { - final node = sortedNodes[i]; - KRLogUtil.kr_i(' ${i + 1}. ${node.tag}: ${node.urlTestDelay.value}ms', tag: 'HomeController'); + if (stage2.isNotEmpty) { + KRLogUtil.kr_i('🔌 阶段2测试剩余节点: ${stage2.length}', tag: 'HomeController'); + final results2 = await KRLatencyTester.testMultipleNodes( + nodes: stage2, + concurrency: 10, + timeout: const Duration(seconds: 5), + ); + int s2ok = 0; + for (final tag in stage2Tags) { + final delay = results2[tag]; + if (delay != null) { + final node = tagToNode[tag]; + if (node != null) { + node.urlTestDelay.value = delay; + s2ok++; + } + } } + KRLogUtil.kr_i('✅ 阶段2完成: 成功 $s2ok/${stage2Tags.length}', + tag: 'HomeController'); } + final tested = + allNodes.where((n) => n.urlTestDelay.value < 65535).toList(); + final notTested = allNodes.length - tested.length; + KRLogUtil.kr_i('📊 总结: 可用 ${tested.length}, 不可用/失败 ${notTested}', + tag: 'HomeController'); } catch (e) { KRLogUtil.kr_e('❌ 真实延迟测试过程出错: $e', tag: 'HomeController'); KRLogUtil.kr_e('❌ 错误堆栈: ${StackTrace.current}', tag: 'HomeController'); @@ -2008,7 +2248,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { /// 调试:打印所有节点的坐标信息 void kr_debugPrintNodeCoordinates() { KRLogUtil.kr_i('========== 节点坐标调试信息 ==========', tag: 'HomeController'); - KRLogUtil.kr_i('节点总数: ${kr_subscribeService.allList.length}', tag: 'HomeController'); + KRLogUtil.kr_i('节点总数: ${kr_subscribeService.allList.length}', + tag: 'HomeController'); if (kr_subscribeService.allList.isEmpty) { KRLogUtil.kr_w('节点列表为空!请检查:', tag: 'HomeController'); @@ -2025,13 +2266,18 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { final node = kr_subscribeService.allList[i]; if (node.latitude != 0.0 || node.longitude != 0.0) { validNodes++; - if (i < 5) { // 只打印前5个有效节点 - KRLogUtil.kr_i('节点[$i] ${node.tag}: (${node.latitude}, ${node.longitude})', tag: 'HomeController'); + if (i < 5) { + // 只打印前5个有效节点 + KRLogUtil.kr_i( + '节点[$i] ${node.tag}: (${node.latitude}, ${node.longitude})', + tag: 'HomeController'); } } else { invalidNodes++; - if (i < 3) { // 只打印前3个无效节点 - KRLogUtil.kr_w('节点[$i] ${node.tag}: 坐标为(0, 0) - 无效!', tag: 'HomeController'); + if (i < 3) { + // 只打印前3个无效节点 + KRLogUtil.kr_w('节点[$i] ${node.tag}: 坐标为(0, 0) - 无效!', + tag: 'HomeController'); } } } @@ -2040,9 +2286,11 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { KRLogUtil.kr_w('无效节点(坐标为0): $invalidNodes', tag: 'HomeController'); if (invalidNodes > 0) { - KRLogUtil.kr_w('⚠️ 发现 $invalidNodes 个节点坐标为0,这些节点不会显示在地图上', tag: 'HomeController'); + KRLogUtil.kr_w('⚠️ 发现 $invalidNodes 个节点坐标为0,这些节点不会显示在地图上', + tag: 'HomeController'); KRLogUtil.kr_w('可能原因:', tag: 'HomeController'); - KRLogUtil.kr_w('1. 后端API未返回 latitude/longitude 字段', tag: 'HomeController'); + KRLogUtil.kr_w('1. 后端API未返回 latitude/longitude 字段', + tag: 'HomeController'); KRLogUtil.kr_w('2. 后端数据库中节点坐标未配置', tag: 'HomeController'); } } @@ -2169,23 +2417,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { }); } - /// 获取国家选中子节点信息(已不使用自动选择逻辑) - Map? kr_getCountryAutoSelectedNode(String countryCode) { - return null; - } - - /// 从订阅服务获取该国家最快节点的备用方案 - Map? _kr_getFastestNodeFromSubscribeService(String countryCode) { - return null; - } - - /// 提取选中节点信息的辅助方法 - Map? _kr_extractSelectedNodeInfo(dynamic group, String countryCode) { - return null; - } - - // 已移除:全局 auto 选中节点信息获取(统一为手动选择模式) - /// 获取指定国家的所有真实节点延迟列表 List> kr_getCountryRealNodeDelays(String countryCode) { final delays = >[]; @@ -2226,12 +2457,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver { // 查找 selector 类型的组 for (var group in activeGroups) { if (group.type == ProxyType.selector) { - KRLogUtil.kr_d('找到 selector 组: ${group.tag}, 选中: ${group.selected}', tag: 'HomeController'); + KRLogUtil.kr_d('找到 selector 组: ${group.tag}, 选中: ${group.selected}', + tag: 'HomeController'); 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'); + KRLogUtil.kr_i('✅ 延迟值: ${item.urlTestDelay}ms', + tag: 'HomeController'); return true; } }