feat: bugfix

This commit is contained in:
2025-12-01 06:54:18 -08:00
parent 1b1b38aba5
commit 86687cac58
22 changed files with 827 additions and 493 deletions
@@ -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');
}
@@ -55,7 +55,7 @@ class CustomCircleRoute extends PageRouteBuilder {
required this.child,
required this.startOffset,
required this.transitionColor,
this.customDuration = const Duration(milliseconds: 1500),
this.customDuration = const Duration(milliseconds: 250),
}) : super(
opaque: false,
transitionDuration: customDuration,
@@ -162,7 +162,7 @@ class _HISubscriptionCornerButtonState extends State<HISubscriptionCornerButton>
child: KRPurchaseMembershipView(),
startOffset: startOffset,
transitionColor: transitionColor,
customDuration: const Duration(milliseconds: 1500),
customDuration: const Duration(milliseconds: 500),
),
).then((_) {
// 页面返回后恢复默认主题色