解决开启关闭后UI界面状态不同步问题
Some checks failed
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 构建 iOS (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (iOS/tvOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
Some checks failed
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 构建 iOS (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (iOS/tvOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
(cherry picked from commit 23a4a5ce2e46ffbd3b8188333dfa7f4559984e4c)
This commit is contained in:
parent
5c8f0ca1fc
commit
74df08144f
@ -103,9 +103,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
// 添加最后的地图中心点
|
||||
final kr_lastMapCenter = LatLng(35.0, 105.0).obs;
|
||||
|
||||
// 添加一个标志来防止重复操作
|
||||
bool kr_isSwitching = false;
|
||||
|
||||
// 为"闪连"Checkbox添加一个响应式变量,默认为 false
|
||||
final isQuickConnectEnabled = false.obs;
|
||||
|
||||
@ -114,7 +111,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 闪连状态存储键
|
||||
static const String _quickConnectKey = 'kr_quick_connect_enabled';
|
||||
|
||||
|
||||
// 国家内节点重选相关属性
|
||||
final RxString currentSelectedCountry = ''.obs;
|
||||
final RxBool isCountryReselectionEnabled = true.obs;
|
||||
@ -206,7 +203,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
KRLogUtil.kr_i('开始执行闪连自动连接', tag: 'QuickConnect');
|
||||
|
||||
// 防止重复操作
|
||||
if (kr_isSwitching) {
|
||||
if (KRSingBoxImp.instance.kr_status == SingboxStarted) {
|
||||
KRLogUtil.kr_w('连接操作正在进行中,跳过自动连接', tag: 'QuickConnect');
|
||||
return;
|
||||
}
|
||||
@ -503,6 +500,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
void _bindConnectionStatus() {
|
||||
// 添加更详细的状态监听
|
||||
ever(KRSingBoxImp.instance.kr_status, (status) {
|
||||
print('🔵 Controller 收到状态变化: ${status.runtimeType}');
|
||||
KRLogUtil.kr_i('🔄 连接状态变化: $status', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('📊 当前状态类型: ${status.runtimeType}', tag: 'HomeController');
|
||||
|
||||
@ -594,7 +592,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 强制更新UI
|
||||
update();
|
||||
|
||||
|
||||
// 检查是否需要进行国家内节点重选
|
||||
_checkCountryReselection(value);
|
||||
} catch (e) {
|
||||
@ -656,54 +654,79 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
***/
|
||||
}
|
||||
|
||||
void kr_toggleSwitch(bool value) async {
|
||||
// 如果正在切换中,直接返回
|
||||
if (kr_isSwitching) {
|
||||
KRLogUtil.kr_i('正在切换中,忽略本次操作', tag: 'HomeController');
|
||||
/// 🔧 重构: 参考 hiddify-app 的 toggleConnection 实现
|
||||
Future<void> kr_toggleSwitch(bool value) async {
|
||||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||||
|
||||
KRLogUtil.kr_i('🔵 toggleSwitch 被调用: value=$value, currentStatus=$currentStatus', tag: 'HomeController');
|
||||
print('🔵 toggleSwitch: value=$value, currentStatus=$currentStatus');
|
||||
|
||||
// 🔧 关键: 如果正在切换中,直接忽略(参考 hiddify-app 的 "switching status, debounce")
|
||||
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
|
||||
KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', tag: 'HomeController');
|
||||
print('🔵 忽略操作:正在切换中');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
kr_isSwitching = true;
|
||||
KRLogUtil.kr_i('🔄 开始切换连接状态: $value', tag: 'HomeController');
|
||||
|
||||
if (value) {
|
||||
// 开启连接
|
||||
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
|
||||
print('🔵 执行 kr_start()');
|
||||
await KRSingBoxImp.instance.kr_start();
|
||||
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
|
||||
print('🔵 kr_start() 完成');
|
||||
|
||||
// 启动成功后立即同步一次,确保UI及时更新
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
kr_forceSyncConnectionStatus();
|
||||
});
|
||||
|
||||
// 再次延迟验证,确保状态稳定
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
kr_forceSyncConnectionStatus();
|
||||
});
|
||||
// 🔧 修复: 等待状态更新,最多3秒
|
||||
await _waitForStatus(SingboxStarted, maxSeconds: 3);
|
||||
} else {
|
||||
KRLogUtil.kr_i('🛑 准备停止连接...', tag: 'HomeController');
|
||||
// 添加超时保护
|
||||
// 关闭连接
|
||||
KRLogUtil.kr_i('🛑 开始断开连接...', tag: 'HomeController');
|
||||
print('🔵 执行 kr_stop()');
|
||||
await KRSingBoxImp.instance.kr_stop().timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () {
|
||||
KRLogUtil.kr_e('⚠️ 停止操作超时', tag: 'HomeController');
|
||||
// 强制同步状态
|
||||
kr_forceSyncConnectionStatus();
|
||||
throw TimeoutException('Stop operation timeout');
|
||||
},
|
||||
);
|
||||
KRLogUtil.kr_i('✅ 停止命令已发送', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('✅ 断开命令已发送', tag: 'HomeController');
|
||||
print('🔵 kr_stop() 完成');
|
||||
|
||||
// 🔧 修复: 等待状态更新,最多2秒
|
||||
await _waitForStatus(SingboxStopped, maxSeconds: 2);
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('切换失败: $e', tag: 'HomeController');
|
||||
// 当启动或停止失败时,强制同步状态
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
kr_forceSyncConnectionStatus();
|
||||
});
|
||||
} finally {
|
||||
// 确保在任何情况下都会重置标志
|
||||
KRLogUtil.kr_i('🔓 重置切换标志', tag: 'HomeController');
|
||||
kr_isSwitching = false;
|
||||
KRLogUtil.kr_e('❌ 切换失败: $e', tag: 'HomeController');
|
||||
print('🔵 切换失败: $e');
|
||||
// 发生错误时强制同步状态
|
||||
kr_forceSyncConnectionStatus();
|
||||
}
|
||||
|
||||
print('🔵 toggleSwitch 完成,当前 kr_isConnected=${kr_isConnected.value}');
|
||||
}
|
||||
|
||||
/// 🔧 等待状态达到预期值
|
||||
Future<void> _waitForStatus(Type expectedType, {int maxSeconds = 3}) async {
|
||||
print('🔵 等待状态变为: $expectedType');
|
||||
final startTime = DateTime.now();
|
||||
|
||||
while (DateTime.now().difference(startTime).inSeconds < maxSeconds) {
|
||||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||||
print('🔵 当前状态: ${currentStatus.runtimeType}');
|
||||
|
||||
if (currentStatus.runtimeType == expectedType) {
|
||||
print('🔵 状态已达到: $expectedType');
|
||||
// 强制同步确保 kr_isConnected 正确
|
||||
kr_forceSyncConnectionStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
print('🔵 等待超时,强制同步状态');
|
||||
kr_forceSyncConnectionStatus();
|
||||
}
|
||||
|
||||
/// 处理选择器代理
|
||||
@ -914,35 +937,35 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
// 检查延迟是否有效(小于65535且大于0)
|
||||
return node.urlTestDelay.value < 65535 && node.urlTestDelay.value > 0;
|
||||
}
|
||||
|
||||
|
||||
/// 检查是否需要进行国家内节点重选
|
||||
void _checkCountryReselection(List<dynamic> activeGroups) {
|
||||
// 如果未启用国家内重选功能,直接返回
|
||||
if (!isCountryReselectionEnabled.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 如果未连接,直接返回
|
||||
if (!kr_isConnected.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 如果没有选择国家,直接返回
|
||||
if (currentSelectedCountry.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 获取当前节点信息
|
||||
final currentNodeInfo = kr_getRealConnectedNodeInfo();
|
||||
final currentDelay = currentNodeInfo['delay'] as int;
|
||||
|
||||
|
||||
// 检查当前节点延迟是否超过阈值
|
||||
if (currentDelay > 0 && currentDelay < countryReselectionLatencyThreshold) {
|
||||
// 延迟正常,无需重选
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 如果延迟超过阈值或无效,尝试在当前国家内重选
|
||||
if (currentDelay >= countryReselectionLatencyThreshold || currentDelay <= 0) {
|
||||
KRLogUtil.kr_w('🔄 当前节点延迟过高(${currentDelay}ms),尝试国家内重选', tag: 'HomeController');
|
||||
@ -952,7 +975,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
KRLogUtil.kr_e('检查国家内重选时出错: $e', tag: 'HomeController');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 执行国家内节点重选
|
||||
void _performCountryReselection(String country) {
|
||||
try {
|
||||
@ -960,16 +983,16 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
final countryNodes = kr_subscribeService.allList
|
||||
.where((node) => node.country == country && node.tag != 'auto')
|
||||
.toList();
|
||||
|
||||
|
||||
if (countryNodes.isEmpty) {
|
||||
KRLogUtil.kr_w('⚠️ 国家 $country 内没有可用节点', tag: 'HomeController');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 找到延迟最小的有效节点
|
||||
String? bestNode;
|
||||
int minDelay = 65535;
|
||||
|
||||
|
||||
for (var node in countryNodes) {
|
||||
final delay = node.urlTestDelay.value;
|
||||
if (delay > 0 && delay < 65535 && delay < minDelay) {
|
||||
@ -977,7 +1000,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
bestNode = node.tag;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (bestNode != null && bestNode != kr_cutSeletedTag.value) {
|
||||
KRLogUtil.kr_i('🎯 国家内重选: $bestNode (${minDelay}ms)', tag: 'HomeController');
|
||||
kr_selectNode(bestNode);
|
||||
@ -988,33 +1011,33 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
KRLogUtil.kr_e('执行国家内重选时出错: $e', tag: 'HomeController');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 设置当前选择的国家(由hi_node_list_controller调用)
|
||||
void setCurrentSelectedCountry(String country) {
|
||||
currentSelectedCountry.value = country;
|
||||
KRLogUtil.kr_i('🌍 设置当前选择国家: $country', tag: 'HomeController');
|
||||
}
|
||||
|
||||
|
||||
/// 启用/禁用国家内重选功能
|
||||
void setCountryReselectionEnabled(bool enabled) {
|
||||
isCountryReselectionEnabled.value = enabled;
|
||||
KRLogUtil.kr_i('🔄 国家内重选功能已${enabled ? "启用" : "禁用"}', tag: 'HomeController');
|
||||
}
|
||||
|
||||
|
||||
/// 获取当前国家内的节点统计信息
|
||||
Map<String, dynamic> getCurrentCountryNodeStats() {
|
||||
if (currentSelectedCountry.isEmpty) {
|
||||
return {'error': '没有选择国家'};
|
||||
}
|
||||
|
||||
|
||||
final countryNodes = kr_subscribeService.allList
|
||||
.where((node) => node.country == currentSelectedCountry.value && node.tag != 'auto')
|
||||
.toList();
|
||||
|
||||
|
||||
if (countryNodes.isEmpty) {
|
||||
return {'error': '国家内没有节点'};
|
||||
}
|
||||
|
||||
|
||||
List<int> validDelays = [];
|
||||
int totalNodes = countryNodes.length;
|
||||
int validNodes = 0;
|
||||
@ -1022,27 +1045,27 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
int maxDelay = 0;
|
||||
String? fastestNode;
|
||||
String? slowestNode;
|
||||
|
||||
|
||||
for (var node in countryNodes) {
|
||||
final delay = node.urlTestDelay.value;
|
||||
if (delay > 0 && delay < 65535) {
|
||||
validDelays.add(delay);
|
||||
validNodes++;
|
||||
|
||||
|
||||
if (delay < minDelay) {
|
||||
minDelay = delay;
|
||||
fastestNode = node.tag;
|
||||
}
|
||||
|
||||
|
||||
if (delay > maxDelay) {
|
||||
maxDelay = delay;
|
||||
slowestNode = node.tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double avgDelay = validDelays.isEmpty ? 0 : validDelays.reduce((a, b) => a + b) / validDelays.length;
|
||||
|
||||
|
||||
return {
|
||||
'country': currentSelectedCountry.value,
|
||||
'totalNodes': totalNodes,
|
||||
|
||||
@ -7,6 +7,7 @@ import 'package:kaer_with_panels/app/widgets/kr_country_flag.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
import '../models/kr_home_views_status.dart';
|
||||
|
||||
@ -209,13 +210,31 @@ class KRHomeConnectionInfoView extends GetView<KRHomeController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
CupertinoSwitch(
|
||||
value: controller.kr_isConnected.value,
|
||||
onChanged: (bool value) {
|
||||
controller.kr_toggleSwitch(value);
|
||||
},
|
||||
activeColor: Colors.blue,
|
||||
),
|
||||
// 🔧 修复: 使用多层监听确保状态更新
|
||||
Obx(() {
|
||||
// 🔧 关键: 强制读取两个 observable 确保追踪
|
||||
final _ = KRSingBoxImp.instance.kr_status.value; // 强制追踪
|
||||
final isConnected = controller.kr_isConnected.value; // 使用 controller 的状态
|
||||
|
||||
// 再次读取状态用于判断
|
||||
final status = KRSingBoxImp.instance.kr_status.value;
|
||||
final isSwitching = status is SingboxStarting || status is SingboxStopping;
|
||||
|
||||
// 🔧 调试日志
|
||||
print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
||||
|
||||
return CupertinoSwitch(
|
||||
value: isConnected,
|
||||
// 🔧 关键: 切换中时 onChanged 为 null,Switch 自动禁用
|
||||
onChanged: isSwitching
|
||||
? null
|
||||
: (bool value) {
|
||||
print('🔵 Switch onChanged 触发: 请求=$value, 当前状态=$status');
|
||||
controller.kr_toggleSwitch(value);
|
||||
},
|
||||
activeColor: Colors.blue,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@ -548,10 +548,14 @@ class KRSingBoxImp {
|
||||
/// 订阅状态变化流
|
||||
/// 参考 hiddify-app: 监听 libcore 发送的状态事件来自动更新 UI
|
||||
void _kr_subscribeToStatus() {
|
||||
print('🔵 _kr_subscribeToStatus 被调用,重新订阅状态流');
|
||||
KRLogUtil.kr_i('🔵 _kr_subscribeToStatus 被调用', tag: 'SingBox');
|
||||
|
||||
// 取消之前的状态订阅
|
||||
for (var sub in _kr_subscriptions) {
|
||||
if (sub.hashCode.toString().contains('Status')) {
|
||||
sub.cancel();
|
||||
print('🔵 已取消旧的状态订阅');
|
||||
}
|
||||
}
|
||||
_kr_subscriptions
|
||||
@ -560,15 +564,19 @@ class KRSingBoxImp {
|
||||
_kr_subscriptions.add(
|
||||
kr_singBox.watchStatus().listen(
|
||||
(status) {
|
||||
print('🔵 收到 Native 状态更新: ${status.runtimeType}');
|
||||
KRLogUtil.kr_i('📡 收到状态更新: $status', tag: 'SingBox');
|
||||
kr_status.value = status;
|
||||
},
|
||||
onError: (error) {
|
||||
print('🔵 状态流错误: $error');
|
||||
KRLogUtil.kr_e('📡 状态流错误: $error', tag: 'SingBox');
|
||||
},
|
||||
cancelOnError: false,
|
||||
),
|
||||
);
|
||||
|
||||
print('🔵 状态流订阅完成');
|
||||
}
|
||||
|
||||
/// 订阅统计数据流
|
||||
@ -1034,6 +1042,9 @@ class KRSingBoxImp {
|
||||
KRLogUtil.kr_w('⚠️ 配置文件不存在: $_cutPath', tag: 'SingBox');
|
||||
}
|
||||
|
||||
// 🔧 修复: 在启动前重新订阅状态流,确保能收到状态更新
|
||||
_kr_subscribeToStatus();
|
||||
|
||||
await kr_singBox.start(_cutPath, kr_configName, false).map(
|
||||
(r) {
|
||||
KRLogUtil.kr_i('✅ SingBox 启动成功', tag: 'SingBox');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user