feat: bugfix
This commit is contained in:
@@ -91,9 +91,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
final kr_isLatency = false.obs;
|
||||
|
||||
/// 默认
|
||||
var kr_cutTag = 'auto'.obs;
|
||||
var kr_cutSeletedTag = 'auto'.obs;
|
||||
var kr_coutryText = 'auto'.obs;
|
||||
var kr_cutTag = ''.obs;
|
||||
var kr_cutSeletedTag = ''.obs;
|
||||
var kr_coutryText = ''.obs;
|
||||
|
||||
var kr_selectedCountryTag = 'auto'.obs;
|
||||
void kr_setSelectedCountryTag(String country) {
|
||||
@@ -216,6 +216,17 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
await _kr_prepareCountrySelectionBeforeStart();
|
||||
final selectedAfter =
|
||||
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
||||
KRLogUtil.kr_i('准备后 SELECTED_NODE_TAG_autoConnect: ${selectedAfter ?? ''}',
|
||||
tag: 'HomeController');
|
||||
KRLogUtil.kr_i('准备后 kr_currentNodeName_autoConnect: ${kr_currentNodeName.value}',
|
||||
tag: 'HomeController');
|
||||
KRLogUtil.kr_i('准备后 kr_cutTag_autoConnect: ${kr_cutTag.value}',
|
||||
tag: 'HomeController');
|
||||
KRLogUtil.kr_i('准备后 kr_cutSeletedTag_autoConnect: ${kr_cutSeletedTag.value}',
|
||||
tag: 'HomeController');
|
||||
await kr_performNodeSwitch(selectedAfter!);
|
||||
await KRSingBoxImp.instance.kr_start();
|
||||
KRLogUtil.kr_i('闪连自动连接执行完成', tag: 'QuickConnect');
|
||||
} catch (e) {
|
||||
@@ -659,9 +670,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
break;
|
||||
case KRSubscribeServiceStatus.kr_success:
|
||||
KRLogUtil.kr_i('订阅服务成功', tag: 'HomeController');
|
||||
kr_cutTag.value = 'auto';
|
||||
kr_cutSeletedTag.value = 'auto';
|
||||
kr_currentNodeName.value = "auto";
|
||||
kr_cutTag.value = '';
|
||||
kr_cutSeletedTag.value = '';
|
||||
kr_currentNodeName.value = "";
|
||||
if (kr_currentListStatus.value != KRHomeViewsListStatus.kr_none) {
|
||||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||||
} else {
|
||||
@@ -779,8 +790,6 @@ 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 = "--";
|
||||
@@ -885,6 +894,17 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
|
||||
if (kDebugMode) {}
|
||||
await _kr_prepareCountrySelectionBeforeStart();
|
||||
final selectedAfter =
|
||||
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
||||
KRLogUtil.kr_i('准备后 SELECTED_NODE_TAG: ${selectedAfter ?? ''}',
|
||||
tag: 'HomeController');
|
||||
KRLogUtil.kr_i('准备后 kr_currentNodeName: ${kr_currentNodeName.value}',
|
||||
tag: 'HomeController');
|
||||
KRLogUtil.kr_i('准备后 kr_cutTag: ${kr_cutTag.value}',
|
||||
tag: 'HomeController');
|
||||
KRLogUtil.kr_i('准备后 kr_cutSeletedTag: ${kr_cutSeletedTag.value}',
|
||||
tag: 'HomeController');
|
||||
await kr_performNodeSwitch(selectedAfter!);
|
||||
await KRSingBoxImp.instance.kr_start();
|
||||
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
|
||||
if (kDebugMode) {}
|
||||
@@ -958,6 +978,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
||||
KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verify',
|
||||
tag: 'CountrySelect');
|
||||
kr_currentNodeName.value = best;
|
||||
kr_cutTag.value = best;
|
||||
kr_cutSeletedTag.value = best;
|
||||
kr_updateConnectionInfo();
|
||||
return;
|
||||
@@ -974,6 +996,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
||||
KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verifyA',
|
||||
tag: 'CountrySelect');
|
||||
kr_currentNodeName.value = bestAfterTest;
|
||||
kr_cutTag.value = bestAfterTest;
|
||||
kr_cutSeletedTag.value = bestAfterTest;
|
||||
kr_updateConnectionInfo();
|
||||
return;
|
||||
@@ -991,6 +1015,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
||||
KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verifyB',
|
||||
tag: 'CountrySelect');
|
||||
kr_currentNodeName.value = fallback;
|
||||
kr_cutTag.value = fallback;
|
||||
kr_cutSeletedTag.value = fallback;
|
||||
kr_updateConnectionInfo();
|
||||
} else {
|
||||
@@ -1027,6 +1053,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
||||
KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verify2',
|
||||
tag: 'CountrySelect');
|
||||
kr_currentNodeName.value = bestInCountry;
|
||||
kr_cutTag.value = bestInCountry;
|
||||
kr_cutSeletedTag.value = bestInCountry;
|
||||
kr_updateConnectionInfo();
|
||||
} else {
|
||||
@@ -1084,22 +1112,33 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
void _kr_handleSelectorProxy(dynamic element, List<dynamic> allGroups) {
|
||||
try {
|
||||
KRLogUtil.kr_d(
|
||||
'处理选择器代理 - 当前选择: ${element.selected}, 用户选择: ${kr_cutTag.value}',
|
||||
'处理选择器代理 - 内核选择: ${element.selected}, 用户选择: ${kr_cutTag.value}, 切换中: $_isSwitchingNode',
|
||||
tag: 'HomeController');
|
||||
|
||||
// 🔧 关键修复:仅更新 UI 状态,不要触发重新选择,避免死循环
|
||||
// 更新 kr_cutSeletedTag 以反映实际选中的节点
|
||||
if (element.selected.isNotEmpty) {
|
||||
kr_cutSeletedTag.value = element.selected;
|
||||
// 🔧 关键修复:如果正在切换节点中,不要用内核返回值覆盖 UI
|
||||
// 这是导致"跳两次"问题的根本原因
|
||||
if (_isSwitchingNode) {
|
||||
KRLogUtil.kr_d('⏳ 节点切换进行中,跳过内核状态同步', tag: 'HomeController');
|
||||
// 只更新延迟值,不更新选中状态
|
||||
_kr_updateNodeLatency(element);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果用户手动选择了节点(不是auto)
|
||||
if (kr_cutTag.value != "auto") {
|
||||
// 🔧 修复:用户手动选择时,不要用内核返回的 selected 覆盖用户选择
|
||||
// 只有当内核返回的节点与用户选择一致时,才更新 kr_cutSeletedTag
|
||||
if (element.selected == kr_cutTag.value) {
|
||||
kr_cutSeletedTag.value = element.selected;
|
||||
}
|
||||
_kr_handleManualMode(element);
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认auto模式处理
|
||||
// 默认auto模式处理 - 此时可以用内核返回值更新 UI
|
||||
if (element.selected.isNotEmpty) {
|
||||
kr_cutSeletedTag.value = element.selected;
|
||||
}
|
||||
_kr_handleAutoMode(element, allGroups);
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('处理选择器代理出错: $e', tag: 'HomeController');
|
||||
@@ -1109,15 +1148,25 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
/// 处理手动模式
|
||||
void _kr_handleManualMode(dynamic element) {
|
||||
try {
|
||||
KRLogUtil.kr_d('处理手动模式 - 选择: ${element.selected}', tag: 'HomeController');
|
||||
KRLogUtil.kr_d('处理手动模式 - 内核选择: ${element.selected}, 用户选择: ${kr_cutTag.value}', tag: 'HomeController');
|
||||
|
||||
// 🔧 关键修复:仅更新 UI 状态,不要重新选择节点,避免死循环
|
||||
kr_cutSeletedTag.value = element.selected;
|
||||
// 更新延迟值
|
||||
// 🔧 关键修复:不要用内核返回的 selected 覆盖用户手动选择
|
||||
// 只有当内核返回的节点与用户选择一致时,才更新相关状态
|
||||
// 这可以防止 UI 跳动
|
||||
|
||||
// 更新延迟值(这个始终需要更新)
|
||||
_kr_updateNodeLatency(element);
|
||||
kr_currentNodeName.value =
|
||||
kr_truncateText(element.selected, maxLength: 25);
|
||||
// kr_moveToSelectedNode();
|
||||
|
||||
// 只有当内核确认切换成功时,才更新 UI 状态
|
||||
if (element.selected == kr_cutTag.value) {
|
||||
kr_cutSeletedTag.value = element.selected;
|
||||
kr_currentNodeName.value = kr_truncateText(element.selected, maxLength: 25);
|
||||
// kr_moveToSelectedNode();
|
||||
KRLogUtil.kr_d('✅ 内核确认节点切换成功: ${element.selected}', tag: 'HomeController');
|
||||
} else {
|
||||
// 内核返回的节点与用户选择不一致,保持用户选择的显示
|
||||
KRLogUtil.kr_d('⏳ 等待内核切换到用户选择的节点: ${kr_cutTag.value}', tag: 'HomeController');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('处理手动模式出错: $e', tag: 'HomeController');
|
||||
}
|
||||
@@ -1315,12 +1364,9 @@ 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;
|
||||
}
|
||||
@@ -1347,9 +1393,7 @@ 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');
|
||||
@@ -1358,91 +1402,93 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. VPN已连接,需要重启VPN以断开现有连接并应用新节点
|
||||
// 4. VPN已连接,使用热切换方式(selectOutbound)切换节点
|
||||
try {
|
||||
KRLogUtil.kr_i('🔌 VPN已连接,将重启VPN以断开现有连接: $tag', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('🔌 VPN已连接,使用热切换方式切换节点: $tag', 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');
|
||||
for (var item in group.items) {
|
||||
if (item.tag == tag) {
|
||||
KRLogUtil.kr_i('✅ 找到目标节点: ${item.tag}', tag: 'HomeController');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 🔧 修复:VPN已连接时,设置延迟为-1(切换中状态),显示"正在连接"
|
||||
// 🔧 设置切换中状态,显示"正在连接"
|
||||
kr_currentNodeLatency.value = -1;
|
||||
kr_isLatency.value = true; // 显示加载动画
|
||||
|
||||
// 🔧 关键修复:保存新节点选择
|
||||
// 🔧 保存新节点选择
|
||||
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');
|
||||
await KRSingBoxImp.instance.kr_stop(); // 先停止VPN(已跳过DNS恢复)
|
||||
// 🚀 核心改进:使用 selectOutbound 进行热切换(参考 hiddify-app)
|
||||
// 优势:不重启VPN,保持连接状态,切换瞬间完成,VPN开关不闪烁
|
||||
KRLogUtil.kr_i('🔄 [热切换] 调用 selectOutbound 切换节点...', tag: 'HomeController');
|
||||
|
||||
// 🚀 优化:减少等待时间(DNS操作已优化,无需过长等待)
|
||||
KRLogUtil.kr_i('⏳ [优化] 等待VPN完全停止(800ms)...', tag: 'HomeController');
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 800)); // 从1500ms减少到800ms
|
||||
// 🔧 关键修复:确定正确的 selector 组 tag
|
||||
// selectOutbound(groupTag, outboundTag) - 第一个参数是组的tag,不是节点的tag
|
||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||
String selectorGroupTag = 'select'; // 默认值
|
||||
|
||||
KRLogUtil.kr_i('🔄 [优化] 启动VPN并应用新节点: $tag', tag: 'HomeController');
|
||||
await KRSingBoxImp.instance.kr_start(); // 重新启动VPN(已跳过DNS备份)
|
||||
// 查找 selector 类型的组
|
||||
for (var group in activeGroups) {
|
||||
if (group.type == ProxyType.selector) {
|
||||
selectorGroupTag = group.tag;
|
||||
KRLogUtil.kr_i('🔍 找到 selector 组: $selectorGroupTag', tag: 'HomeController');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 🚀 优化:减少等待时间(DNS操作已优化)
|
||||
KRLogUtil.kr_i('⏳ [优化] 等待VPN完全启动(1200ms)...', tag: 'HomeController');
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 1200)); // 从2500ms减少到1200ms
|
||||
KRLogUtil.kr_i('📡 调用 selectOutbound("$selectorGroupTag", "$tag")', tag: 'HomeController');
|
||||
|
||||
// 后台切换成功,更新UI
|
||||
// 调用 sing-box 的 selectOutbound API
|
||||
final result = await KRSingBoxImp.instance.kr_singBox
|
||||
.selectOutbound(selectorGroupTag, tag)
|
||||
.run();
|
||||
|
||||
// 处理切换结果
|
||||
result.fold(
|
||||
(error) {
|
||||
// 切换失败
|
||||
KRLogUtil.kr_e('❌ selectOutbound 调用失败: $error', tag: 'HomeController');
|
||||
throw Exception('节点切换失败: $error');
|
||||
},
|
||||
(_) {
|
||||
// 切换成功
|
||||
KRLogUtil.kr_i('✅ selectOutbound 调用成功', tag: 'HomeController');
|
||||
},
|
||||
);
|
||||
|
||||
// 后台切换成功,立即更新UI(乐观更新)
|
||||
kr_cutSeletedTag.value = tag;
|
||||
kr_updateConnectionInfo();
|
||||
kr_moveToSelectedNode();
|
||||
// kr_moveToSelectedNode();
|
||||
|
||||
// 🚀 优化:减少验证等待时间
|
||||
KRLogUtil.kr_i('⏳ [优化] 等待活动组更新(300ms)...', tag: 'HomeController');
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 300)); // 从500ms减少到300ms
|
||||
// 🔧 短暂等待以确保内核状态同步(相比重启,等待时间大幅缩短)
|
||||
KRLogUtil.kr_i('⏳ [热切换] 等待内核状态同步(200ms)...', tag: 'HomeController');
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
// 🚀 方案A增强:验证节点是否真正切换成功
|
||||
KRLogUtil.kr_i('🔍 [增强] 验证节点切换是否成功...', tag: 'HomeController');
|
||||
// 🔍 验证节点是否真正切换成功
|
||||
KRLogUtil.kr_i('🔍 [验证] 检查节点切换结果...', tag: 'HomeController');
|
||||
try {
|
||||
// 刷新活动组信息
|
||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||
final selectGroup = activeGroups.firstWhere(
|
||||
(group) => group.tag == 'select',
|
||||
orElse: () => throw Exception('未找到 select 组'),
|
||||
final updatedGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||
final selectGroup = updatedGroups.firstWhere(
|
||||
(group) => group.type == ProxyType.selector,
|
||||
orElse: () => throw Exception('未找到 selector 组'),
|
||||
);
|
||||
|
||||
KRLogUtil.kr_i('📊 [增强] Select组当前选中: ${selectGroup.selected}',
|
||||
tag: 'HomeController');
|
||||
KRLogUtil.kr_i('📊 [增强] 目标节点: $tag', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('📊 [验证] ${selectGroup.tag}组当前选中: ${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');
|
||||
KRLogUtil.kr_i('✅ [验证] 节点选择验证成功!', tag: 'HomeController');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ [增强] 节点验证过程出错: $e', tag: 'HomeController');
|
||||
KRLogUtil.kr_w('⚠️ [验证] 节点验证过程出错: $e', tag: 'HomeController');
|
||||
}
|
||||
|
||||
// 更新延迟信息
|
||||
_kr_updateLatencyOnConnected();
|
||||
|
||||
KRLogUtil.kr_i('✅ 节点切换成功(已重启VPN断开旧连接): $tag', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('✅ 节点热切换成功,VPN保持连接: $tag', tag: 'HomeController');
|
||||
return true;
|
||||
|
||||
} catch (switchError) {
|
||||
// 后台切换失败,恢复到原节点
|
||||
KRLogUtil.kr_e('❌ 后台节点切换失败: $switchError', tag: 'HomeController');
|
||||
@@ -1454,8 +1500,7 @@ 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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user