feat: 节点测速调整
This commit is contained in:
parent
909020654f
commit
b059d01556
@ -28,66 +28,11 @@ class KROutboundItem {
|
|||||||
|
|
||||||
/// URL
|
/// URL
|
||||||
String url = "";
|
String url = "";
|
||||||
// ✅ 1. 将传入的 nodeListItem 保存为类的 final 成员变量
|
|
||||||
final KrNodeListItem nodeListItem;
|
|
||||||
/// 服务器类型
|
/// 服务器类型
|
||||||
|
|
||||||
/// 构造函数,接受 KrItem 对象并初始化 KROutboundItem
|
/// 构造函数,接受 KrItem 对象并初始化 KROutboundItem
|
||||||
KROutboundItem(this.nodeListItem) {
|
KROutboundItem(KrNodeListItem nodeListItem) {
|
||||||
_initFromNodeListItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 静态工厂:构造虚拟 urltest 节点(用于 ${country}-auto)
|
|
||||||
factory KROutboundItem.fromVirtual(String tag, String country, Map<String, dynamic> config) {
|
|
||||||
// 构造一个虚拟 KrNodeListItem,仅填充必要字段
|
|
||||||
final virtualNode = KrNodeListItem(
|
|
||||||
id: 0,
|
|
||||||
name: tag,
|
|
||||||
protocol: 'urltest',
|
|
||||||
serverAddr: '',
|
|
||||||
port: 0,
|
|
||||||
uuid: '',
|
|
||||||
config: jsonEncode(config),
|
|
||||||
city: '',
|
|
||||||
country: country,
|
|
||||||
tags: [],
|
|
||||||
latitude: 0,
|
|
||||||
longitude: 0,
|
|
||||||
latitudeCountry: 0,
|
|
||||||
longitudeCountry: 0,
|
|
||||||
relayNode: '',
|
|
||||||
relayMode: 'none',
|
|
||||||
protocols: '',
|
|
||||||
method: '',
|
|
||||||
speedLimit: 0,
|
|
||||||
traffic: 0,
|
|
||||||
trafficRatio: 0,
|
|
||||||
upload: 0,
|
|
||||||
download: 0,
|
|
||||||
startTime: '',
|
|
||||||
expireTime: '',
|
|
||||||
);
|
|
||||||
return KROutboundItem._virtual(virtualNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 私有构造:用于虚拟节点,避免重复解析
|
|
||||||
KROutboundItem._virtual(this.nodeListItem) {
|
|
||||||
// 直接填充虚拟节点所需字段
|
|
||||||
id = nodeListItem.id.toString();
|
|
||||||
protocol = nodeListItem.protocol;
|
|
||||||
tag = nodeListItem.name;
|
|
||||||
serverAddr = nodeListItem.serverAddr;
|
|
||||||
city = nodeListItem.city;
|
|
||||||
country = nodeListItem.country;
|
|
||||||
latitude = nodeListItem.latitude;
|
|
||||||
latitudeCountry = nodeListItem.latitudeCountry;
|
|
||||||
longitude = nodeListItem.longitude;
|
|
||||||
longitudeCountry = nodeListItem.longitudeCountry;
|
|
||||||
config = jsonDecode(nodeListItem.config); // 已知 config 有效
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 初始化逻辑提取,供主构造调用
|
|
||||||
void _initFromNodeListItem() {
|
|
||||||
id = nodeListItem.id.toString();
|
id = nodeListItem.id.toString();
|
||||||
protocol = nodeListItem.protocol;
|
protocol = nodeListItem.protocol;
|
||||||
latitude = nodeListItem.latitude;
|
latitude = nodeListItem.latitude;
|
||||||
@ -110,163 +55,160 @@ class KROutboundItem {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兜底:尝试从 config 字段解析(旧API格式)
|
// 兜底:尝试解析 config 字段(旧API格式)
|
||||||
if (nodeListItem.config.isNotEmpty) {
|
if (nodeListItem.config.isEmpty) {
|
||||||
try {
|
if (kDebugMode) {
|
||||||
final json = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
print('❌ 节点 ${nodeListItem.name} 缺少配置信息(无port或config)');
|
||||||
if (kDebugMode) {
|
}
|
||||||
print('📄 解析到 config JSON: $json');
|
config = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
late Map<String, dynamic> json;
|
||||||
|
try {
|
||||||
|
json = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('❌ 节点 ${nodeListItem.name} 的 config 解析失败: $e,尝试使用直接字段');
|
||||||
|
}
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('📄 Config 内容: ${nodeListItem.config}');
|
||||||
|
}
|
||||||
|
_buildConfigFromFields(nodeListItem);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (nodeListItem.protocol) {
|
||||||
|
case "vless":
|
||||||
|
final securityConfig =
|
||||||
|
json["security_config"] as Map<String, dynamic>? ?? {};
|
||||||
|
|
||||||
|
// 智能设置 server_name
|
||||||
|
String serverName = securityConfig["sni"] ?? "";
|
||||||
|
if (serverName.isEmpty) {
|
||||||
|
serverName = nodeListItem.serverAddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取 transport 配置
|
config = {
|
||||||
Map<String, dynamic>? transportConfig;
|
"type": "vless",
|
||||||
if (json['transport'] != null && json['transport'] != 'tcp') {
|
"tag": nodeListItem.name,
|
||||||
transportConfig = _buildTransport(json);
|
"server": nodeListItem.serverAddr,
|
||||||
if (kDebugMode) {
|
"server_port": json["port"],
|
||||||
print('✅ 找到 transport 配置: $transportConfig');
|
"uuid": nodeListItem.uuid,
|
||||||
}
|
if (json["flow"] != null && json["flow"] != "none")
|
||||||
}
|
"flow": json["flow"],
|
||||||
|
if (json["transport"] != null && json["transport"] != "tcp")
|
||||||
// 提取 security_config
|
"transport": _buildTransport(json),
|
||||||
Map<String, dynamic>? securityConfig;
|
"tls": {
|
||||||
if (json['security_config'] != null) {
|
"enabled": json["security"] == "tls",
|
||||||
securityConfig = json['security_config'] as Map<String, dynamic>;
|
"server_name": serverName,
|
||||||
if (kDebugMode) {
|
"insecure": securityConfig["allow_insecure"] ?? true,
|
||||||
print('✅ 找到 security_config: $securityConfig');
|
"utls": {
|
||||||
}
|
"enabled": true,
|
||||||
}
|
"fingerprint": securityConfig["fingerprint"] ?? "chrome"
|
||||||
|
|
||||||
// 根据协议类型构建配置
|
|
||||||
switch (nodeListItem.protocol) {
|
|
||||||
case "shadowsocks":
|
|
||||||
config = {
|
|
||||||
"type": "shadowsocks",
|
|
||||||
"tag": nodeListItem.name,
|
|
||||||
"server": nodeListItem.serverAddr,
|
|
||||||
"server_port": json["port"],
|
|
||||||
"method": json["method"],
|
|
||||||
"password": nodeListItem.uuid
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case "vless":
|
|
||||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
|
||||||
.hasMatch(nodeListItem.serverAddr);
|
|
||||||
|
|
||||||
config = {
|
|
||||||
"type": "vless",
|
|
||||||
"tag": nodeListItem.name,
|
|
||||||
"server": nodeListItem.serverAddr,
|
|
||||||
"server_port": json["port"],
|
|
||||||
"uuid": nodeListItem.uuid,
|
|
||||||
if (transportConfig != null) "transport": transportConfig,
|
|
||||||
if (json["security"] == "tls") "tls": {
|
|
||||||
"enabled": true,
|
|
||||||
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
|
|
||||||
"insecure": securityConfig?["allow_insecure"] ?? true,
|
|
||||||
"utls": {
|
|
||||||
"enabled": true,
|
|
||||||
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case "vmess":
|
|
||||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
|
||||||
.hasMatch(nodeListItem.serverAddr);
|
|
||||||
|
|
||||||
config = {
|
|
||||||
"type": "vmess",
|
|
||||||
"tag": nodeListItem.name,
|
|
||||||
"server": nodeListItem.serverAddr,
|
|
||||||
"server_port": json["port"],
|
|
||||||
"uuid": nodeListItem.uuid,
|
|
||||||
"alter_id": 0,
|
|
||||||
"security": "auto",
|
|
||||||
if (transportConfig != null) "transport": transportConfig,
|
|
||||||
if (json["security"] == "tls") "tls": {
|
|
||||||
"enabled": true,
|
|
||||||
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
|
|
||||||
"insecure": securityConfig?["allow_insecure"] ?? true,
|
|
||||||
"utls": {
|
|
||||||
"enabled": true,
|
|
||||||
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case "trojan":
|
|
||||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
|
||||||
.hasMatch(nodeListItem.serverAddr);
|
|
||||||
|
|
||||||
config = {
|
|
||||||
"type": "trojan",
|
|
||||||
"tag": nodeListItem.name,
|
|
||||||
"server": nodeListItem.serverAddr,
|
|
||||||
"server_port": json["port"],
|
|
||||||
"password": nodeListItem.uuid,
|
|
||||||
if (transportConfig != null) "transport": transportConfig,
|
|
||||||
"tls": {
|
|
||||||
"enabled": json["security"] == "tls",
|
|
||||||
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
|
|
||||||
"insecure": securityConfig?["allow_insecure"] ?? true,
|
|
||||||
"utls": {
|
|
||||||
"enabled": true,
|
|
||||||
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case "hysteria":
|
|
||||||
case "hysteria2":
|
|
||||||
final securityConfig = json["security_config"] as Map<String, dynamic>? ?? {};
|
|
||||||
config = {
|
|
||||||
"type": "hysteria2",
|
|
||||||
"tag": nodeListItem.name,
|
|
||||||
"server": nodeListItem.serverAddr,
|
|
||||||
"server_port": json["port"],
|
|
||||||
"password": nodeListItem.uuid,
|
|
||||||
"up_mbps": 100,
|
|
||||||
"down_mbps": 100,
|
|
||||||
"obfs": {
|
|
||||||
"type": "salamander",
|
|
||||||
"password": json["obfs_password"] ?? nodeListItem.uuid
|
|
||||||
},
|
|
||||||
"tls": {
|
|
||||||
"enabled": true,
|
|
||||||
"server_name": securityConfig["sni"] ?? "",
|
|
||||||
"insecure": securityConfig["allow_insecure"] ?? true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (kDebugMode) {
|
|
||||||
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');
|
|
||||||
}
|
}
|
||||||
config = {};
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "vmess":
|
||||||
|
final securityConfig =
|
||||||
|
json["security_config"] as Map<String, dynamic>? ?? {};
|
||||||
|
|
||||||
|
// 智能设置 server_name
|
||||||
|
String serverName = securityConfig["sni"] ?? "";
|
||||||
|
if (serverName.isEmpty) {
|
||||||
|
serverName = nodeListItem.serverAddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查 relayNode 是否为 JSON 字符串并解析
|
config = {
|
||||||
if (nodeListItem.relayNode.isNotEmpty && nodeListItem.relayMode != "none") {
|
"type": "vmess",
|
||||||
final relayNodeJson = jsonDecode(nodeListItem.relayNode);
|
"tag": nodeListItem.name,
|
||||||
if (relayNodeJson is List && nodeListItem.relayMode != "none") {
|
"server": nodeListItem.serverAddr,
|
||||||
// 随机选择一个元素
|
"server_port": json["port"],
|
||||||
final randomNode = (relayNodeJson..shuffle()).first;
|
"uuid": nodeListItem.uuid,
|
||||||
config["server"] = randomNode["host"]; // 提取 host
|
"alter_id": 0,
|
||||||
config["server_port"] = randomNode["port"]; // 提取 port
|
"security": "auto",
|
||||||
|
if (json["transport"] != null && json["transport"] != "tcp")
|
||||||
|
"transport": _buildTransport(json),
|
||||||
|
"tls": {
|
||||||
|
"enabled": json["security"] == "tls",
|
||||||
|
"server_name": serverName,
|
||||||
|
"insecure": securityConfig["allow_insecure"] ?? true,
|
||||||
|
"utls": {"enabled": true, "fingerprint": "chrome"}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "shadowsocks":
|
||||||
|
config = {
|
||||||
|
"type": "shadowsocks",
|
||||||
|
"tag": nodeListItem.name,
|
||||||
|
"server": nodeListItem.serverAddr,
|
||||||
|
"server_port": json["port"],
|
||||||
|
"method": json["method"],
|
||||||
|
"password": nodeListItem.uuid
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "hysteria":
|
||||||
|
case "hysteria2":
|
||||||
|
// 后端的 "hysteria" 实际上是 Hysteria2 协议
|
||||||
|
final securityConfig =
|
||||||
|
json["security_config"] as Map<String, dynamic>? ?? {};
|
||||||
|
config = {
|
||||||
|
"type": "hysteria2",
|
||||||
|
"tag": nodeListItem.name,
|
||||||
|
"server": nodeListItem.serverAddr,
|
||||||
|
"server_port": json["port"],
|
||||||
|
"password": nodeListItem.uuid,
|
||||||
|
"up_mbps": 100,
|
||||||
|
"down_mbps": 100,
|
||||||
|
"obfs": {
|
||||||
|
"type": "salamander",
|
||||||
|
"password": json["obfs_password"] ?? nodeListItem.uuid
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"enabled": true,
|
||||||
|
"server_name": securityConfig["sni"] ?? "",
|
||||||
|
"insecure": securityConfig["allow_insecure"] ?? true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "trojan":
|
||||||
|
final securityConfig =
|
||||||
|
json["security_config"] as Map<String, dynamic>? ?? {};
|
||||||
|
|
||||||
|
// 智能设置 server_name
|
||||||
|
String serverName = securityConfig["sni"] ?? "";
|
||||||
|
if (serverName.isEmpty) {
|
||||||
|
// 如果没有配置 SNI,使用服务器地址
|
||||||
|
serverName = nodeListItem.serverAddr;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
if (kDebugMode) {
|
config = {
|
||||||
print('⚠️ 解析 config 字段失败: $e');
|
"type": "trojan",
|
||||||
}
|
"tag": nodeListItem.name,
|
||||||
config = {};
|
"server": nodeListItem.serverAddr,
|
||||||
|
"server_port": json["port"],
|
||||||
|
"password": nodeListItem.uuid,
|
||||||
|
"tls": {
|
||||||
|
"enabled": json["security"] == "tls",
|
||||||
|
"server_name": serverName,
|
||||||
|
"insecure": securityConfig["allow_insecure"] ?? true,
|
||||||
|
"utls": {"enabled": true, "fingerprint": "chrome"}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 relayNode 是否为 JSON 字符串并解析
|
||||||
|
if (nodeListItem.relayNode.isNotEmpty && nodeListItem.relayMode != "none") {
|
||||||
|
final relayNodeJson = jsonDecode(nodeListItem.relayNode);
|
||||||
|
if (relayNodeJson is List && nodeListItem.relayMode != "none") {
|
||||||
|
// 随机选择一个元素
|
||||||
|
final randomNode = (relayNodeJson..shuffle()).first;
|
||||||
|
config["server"] = randomNode["host"]; // 提取 host
|
||||||
|
config["server_port"] = randomNode["port"]; // 提取 port
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// 解析配置
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'KROutboundItem(name: ${nodeListItem.name}, protocol: ${nodeListItem.protocol}, server: ${nodeListItem.serverAddr}, port: ${nodeListItem.port})';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建传输配置
|
/// 构建传输配置
|
||||||
@ -304,6 +246,27 @@ class KROutboundItem {
|
|||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}');
|
print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}');
|
||||||
}
|
}
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('📋 节点详细信息:');
|
||||||
|
}
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||||||
|
}
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(' - port: ${nodeListItem.port}');
|
||||||
|
}
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(' - uuid: ${nodeListItem.uuid}');
|
||||||
|
}
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(' - method: ${nodeListItem.method}');
|
||||||
|
}
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(' - config: ${nodeListItem.config}');
|
||||||
|
}
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(' - protocols: ${nodeListItem.protocols}');
|
||||||
|
}
|
||||||
|
|
||||||
// 🔧 尝试从 config 字段解析 transport 配置
|
// 🔧 尝试从 config 字段解析 transport 配置
|
||||||
Map<String, dynamic>? transportConfig;
|
Map<String, dynamic>? transportConfig;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||||
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
|
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
|
||||||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||||
|
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
|
||||||
import '../../../localization/app_translations.dart';
|
import '../../../localization/app_translations.dart';
|
||||||
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
|
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
|
||||||
|
|
||||||
@ -40,52 +41,34 @@ class HINodeListController extends GetxController with WidgetsBindingObserver {
|
|||||||
KRLogUtil.kr_i('连接类型已更新为: $type', tag: 'HINodeListController');
|
KRLogUtil.kr_i('连接类型已更新为: $type', tag: 'HINodeListController');
|
||||||
// 这里可以添加其他需要的逻辑
|
// 这里可以添加其他需要的逻辑
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果当前有选择的国家,触发重选检查
|
|
||||||
if (homeController.currentSelectedCountry.isNotEmpty) {
|
|
||||||
KRLogUtil.kr_i('连接类型更新后,检查是否需要重选节点', tag: 'HINodeListController');
|
|
||||||
// 延迟一下让连接类型更新完成
|
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
|
||||||
// homeController.checkCountryReselection(KRSingBoxImp.instance.kr_activeGroups);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 处理模式按钮点击(用于激活调试模式)
|
/// 处理模式按钮点击(用于激活调试模式)
|
||||||
void kr_handleModeButtonClick() {
|
void kr_handleModeButtonClick() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
|
||||||
// 检查是否在2秒内连续点击
|
// 检查是否在2秒内连续点击
|
||||||
if (lastModeButtonClickTime != null &&
|
if (lastModeButtonClickTime != null &&
|
||||||
now.difference(lastModeButtonClickTime!).inSeconds > 2) {
|
now.difference(lastModeButtonClickTime!).inSeconds > 2) {
|
||||||
// 超过2秒,重置计数器
|
// 超过2秒,重置计数器
|
||||||
modeButtonClickCount = 0;
|
modeButtonClickCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
modeButtonClickCount++;
|
modeButtonClickCount++;
|
||||||
lastModeButtonClickTime = now;
|
lastModeButtonClickTime = now;
|
||||||
|
|
||||||
KRLogUtil.kr_i('模式按钮点击次数: $modeButtonClickCount', tag: 'HINodeListController');
|
KRLogUtil.kr_i('模式按钮点击次数: $modeButtonClickCount',
|
||||||
|
tag: 'HINodeListController');
|
||||||
|
|
||||||
if (modeButtonClickCount >= 5) {
|
if (modeButtonClickCount >= 5) {
|
||||||
// 激活调试模式
|
// 激活调试模式
|
||||||
isDebugMode.value = true;
|
isDebugMode.value = true;
|
||||||
modeButtonClickCount = 0; // 重置计数器
|
modeButtonClickCount = 0; // 重置计数器
|
||||||
KRLogUtil.kr_i('🐛 调试模式已激活!', tag: 'HINodeListController');
|
KRLogUtil.kr_i('🐛 调试模式已激活!', tag: 'HINodeListController');
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取要显示的节点列表(根据调试模式过滤)
|
/// 获取要显示的节点列表(根据调试模式过滤)
|
||||||
List<dynamic> kr_getFilteredNodeList() {
|
|
||||||
if (isDebugMode.value) {
|
|
||||||
// 调试模式:显示所有节点
|
|
||||||
return kr_subscribeService.allList;
|
|
||||||
} else {
|
|
||||||
// 正常模式:只显示 country-auto 节点
|
|
||||||
return kr_subscribeService.allList.where((node) => node.tag.endsWith('-auto')).toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 重置调试模式
|
/// 重置调试模式
|
||||||
void kr_resetDebugMode() {
|
void kr_resetDebugMode() {
|
||||||
@ -97,6 +80,15 @@ class HINodeListController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
Future<void> kr_handleRefresh() async {
|
Future<void> kr_handleRefresh() async {
|
||||||
await kr_subscribeService.kr_refreshAll();
|
await kr_subscribeService.kr_refreshAll();
|
||||||
|
try {
|
||||||
|
final savedNode =
|
||||||
|
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
||||||
|
if (savedNode != null && savedNode.isNotEmpty) {
|
||||||
|
homeController.kr_currentNodeName.value = savedNode;
|
||||||
|
homeController.kr_cutTag.value = savedNode;
|
||||||
|
homeController.kr_cutSeletedTag.value = savedNode;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
if (!homeController.kr_isLatency.value) {
|
if (!homeController.kr_isLatency.value) {
|
||||||
homeController.kr_urlTest();
|
homeController.kr_urlTest();
|
||||||
}
|
}
|
||||||
@ -106,11 +98,6 @@ class HINodeListController extends GetxController with WidgetsBindingObserver {
|
|||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
ever(homeController.kr_cutTag, (tag) {
|
|
||||||
if (homeController.kr_isLatency.value) return;
|
|
||||||
KRLogUtil.kr_i('🔄 节点切换成功 - 自动触发延迟测试', tag: 'HINodeListView');
|
|
||||||
homeController.kr_urlTest();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -126,7 +113,7 @@ class HINodeListController extends GetxController with WidgetsBindingObserver {
|
|||||||
super.didChangeAppLifecycleState(state);
|
super.didChangeAppLifecycleState(state);
|
||||||
// 当应用从后台切换回前台时
|
// 当应用从后台切换回前台时
|
||||||
if (state == AppLifecycleState.resumed) {
|
if (state == AppLifecycleState.resumed) {
|
||||||
if (homeController.kr_isLatency.value) return;
|
if (homeController.kr_isLatency.value) return;
|
||||||
KRLogUtil.kr_i('🔄 节点列表显示 - 自动触发延迟测试', tag: 'HINodeListView');
|
KRLogUtil.kr_i('🔄 节点列表显示 - 自动触发延迟测试', tag: 'HINodeListView');
|
||||||
homeController.kr_urlTest();
|
homeController.kr_urlTest();
|
||||||
}
|
}
|
||||||
@ -137,5 +124,4 @@ class HINodeListController extends GetxController with WidgetsBindingObserver {
|
|||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,13 +26,14 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
/// 获取分组内最快节点的延迟值(单位:ms)
|
/// 获取分组内最快节点的延迟值(单位:ms)
|
||||||
/// 如果列表为空,返回 0
|
/// 如果列表为空,返回 0
|
||||||
int getFastestNodeDelay(
|
int getFastestNodeDelay(
|
||||||
HINodeListController controller,
|
HINodeListController controller,
|
||||||
List<KROutboundItem> outboundList,
|
List<KROutboundItem> outboundList,
|
||||||
) {
|
) {
|
||||||
if (outboundList.isEmpty) return 0;
|
if (outboundList.isEmpty) return 0;
|
||||||
|
|
||||||
// 返回最小延迟值
|
// 返回最小延迟值
|
||||||
outboundList.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value));
|
outboundList
|
||||||
|
.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value));
|
||||||
return outboundList.first.urlTestDelay.value;
|
return outboundList.first.urlTestDelay.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,36 +153,39 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Obx(() => controller.homeController.kr_coutryText.value == 'auto'
|
Obx(() =>
|
||||||
? KrLocalImage(
|
controller.homeController.kr_cutSeletedTag.value ==
|
||||||
imageName: 'radio-active-icon',
|
'auto'
|
||||||
imageType: ImageType.svg,
|
? KrLocalImage(
|
||||||
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
imageName: 'radio-active-icon',
|
||||||
height: 16.h,
|
imageType: ImageType.svg,
|
||||||
)
|
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
||||||
: KrLocalImage(
|
height: 16.h,
|
||||||
imageName: 'radio-icon',
|
)
|
||||||
imageType: ImageType.svg,
|
: KrLocalImage(
|
||||||
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
imageName: 'radio-icon',
|
||||||
height: 16.h,
|
imageType: ImageType.svg,
|
||||||
)),
|
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
||||||
|
height: 16.h,
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...controller.kr_subscribeService.countryOutboundList.map((country) {
|
...controller.kr_subscribeService.countryOutboundList
|
||||||
|
.map((country) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
try {
|
try {
|
||||||
final success =
|
final fastest = findFastestNode(country.outboundList);
|
||||||
await controller.homeController.kr_performNodeSwitch('auto');
|
final success = await controller.homeController
|
||||||
|
.kr_performNodeSwitch(fastest.tag);
|
||||||
if (success) {
|
if (success) {
|
||||||
controller.homeController.kr_currentListStatus.value =
|
controller.homeController.kr_currentListStatus.value =
|
||||||
KRHomeViewsListStatus.kr_none;
|
KRHomeViewsListStatus.kr_none;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
KRLogUtil.kr_e('Auto选项切换异常: $e',
|
KRLogUtil.kr_e('国家选择切换异常: $e', tag: 'NodeListView');
|
||||||
tag: 'NodeListView');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: _kr_buildCountryListItem(context, country: country),
|
child: _kr_buildCountryListItem(context, country: country),
|
||||||
@ -189,86 +193,87 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
]
|
]
|
||||||
] //
|
] //
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建默认的订阅节点列表
|
/// 构建默认的订阅节点列表
|
||||||
Widget _buildSubscribeList(BuildContext context) {
|
Widget _buildSubscribeList(BuildContext context) {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
return _kr_buildListContainer(
|
return _kr_buildListContainer(
|
||||||
context,
|
context,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.w),
|
padding: EdgeInsets.symmetric(vertical: 8.w),
|
||||||
// 2. 使用 children 属性,并一次性构建所有列表项
|
// 2. 使用 children 属性,并一次性构建所有列表项
|
||||||
children: [
|
children: [
|
||||||
...[
|
...[
|
||||||
InkWell(
|
InkWell(
|
||||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
try {
|
try {
|
||||||
final success =
|
final success = await controller.homeController
|
||||||
await controller.homeController.kr_performNodeSwitch('auto');
|
.kr_performNodeSwitch('auto');
|
||||||
if (success) {
|
if (success) {
|
||||||
controller.homeController.kr_currentListStatus.value =
|
controller.homeController.kr_currentListStatus.value =
|
||||||
KRHomeViewsListStatus.kr_none;
|
KRHomeViewsListStatus.kr_none;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
KRLogUtil.kr_e('Auto选项切换异常: $e',
|
KRLogUtil.kr_e('Auto选项切换异常: $e', tag: 'NodeListView');
|
||||||
tag: 'NodeListView');
|
}
|
||||||
}
|
},
|
||||||
},
|
child: Container(
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: Colors.white.withOpacity(0.3),
|
|
||||||
width: 1.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 32.w,
|
|
||||||
height: 22.w,
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
// 2. 设置背景色为主题色
|
border: Border(
|
||||||
color: Theme.of(context).primaryColor,
|
bottom: BorderSide(
|
||||||
),
|
color: Colors.white.withOpacity(0.3),
|
||||||
// 4. 使用 Center 来确保内部的图片水平和垂直居中
|
width: 1.0,
|
||||||
child: Center(
|
),
|
||||||
child: KrLocalImage(
|
|
||||||
imageName: "hi-home-logo",
|
|
||||||
width: 14.w,
|
|
||||||
height: 14.h,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
padding:
|
||||||
SizedBox(width: 8.w),
|
EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
|
||||||
Expanded(
|
child: Row(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Container(
|
||||||
'自动匹配最快网络', // 您指定的文本
|
width: 32.w,
|
||||||
style: KrAppTextStyle(
|
height: 22.w,
|
||||||
fontSize: 14,
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
// 2. 设置背景色为主题色
|
||||||
fontWeight: FontWeight.w600, // 600 加粗
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
// 4. 使用 Center 来确保内部的图片水平和垂直居中
|
||||||
|
child: Center(
|
||||||
|
child: KrLocalImage(
|
||||||
|
imageName: "hi-home-logo",
|
||||||
|
width: 14.w,
|
||||||
|
height: 14.h,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 2. 第二个 Text: "根据网络IP自动匹配最快线路"
|
SizedBox(width: 8.w),
|
||||||
Text(
|
Expanded(
|
||||||
'根据网络IP自动匹配最快线路', // 默认文本
|
child: Column(
|
||||||
style: KrAppTextStyle(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
fontSize: 10,
|
children: [
|
||||||
color: Colors.white,
|
Text(
|
||||||
),
|
'自动匹配最快网络', // 您指定的文本
|
||||||
),
|
style: KrAppTextStyle(
|
||||||
/* Obx(() {
|
fontSize: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w600, // 600 加粗
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 2. 第二个 Text: "根据网络IP自动匹配最快线路"
|
||||||
|
Text(
|
||||||
|
'根据网络IP自动匹配最快线路', // 默认文本
|
||||||
|
style: KrAppTextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
/* Obx(() {
|
||||||
// 当选择全局 auto 时,显示当前选中的节点信息
|
// 当选择全局 auto 时,显示当前选中的节点信息
|
||||||
if (controller.homeController.kr_cutTag.value == 'auto') {
|
if (controller.homeController.kr_cutTag.value == 'auto') {
|
||||||
final autoNodeInfo = controller.homeController.kr_getGlobalAutoSelectedNode();
|
final autoNodeInfo = controller.homeController.kr_getGlobalAutoSelectedNode();
|
||||||
@ -290,51 +295,49 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),*/
|
}),*/
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Obx(() =>
|
||||||
|
controller.homeController.kr_cutTag.value == 'auto'
|
||||||
|
? KrLocalImage(
|
||||||
|
imageName: 'radio-active-icon',
|
||||||
|
imageType: ImageType.svg,
|
||||||
|
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
||||||
|
height: 16.h,
|
||||||
|
)
|
||||||
|
: KrLocalImage(
|
||||||
|
imageName: 'radio-icon',
|
||||||
|
imageType: ImageType.svg,
|
||||||
|
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
||||||
|
height: 16.h,
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Obx(() => controller.homeController.kr_cutTag.value == 'auto'
|
),
|
||||||
? KrLocalImage(
|
...controller.kr_subscribeService.allList().map((item) {
|
||||||
imageName: 'radio-active-icon',
|
return InkWell(
|
||||||
imageType: ImageType.svg,
|
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||||
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
onTap: () async {
|
||||||
height: 16.h,
|
try {
|
||||||
)
|
KRLogUtil.kr_i('🔄 用户点击节点: ${item.tag}');
|
||||||
: KrLocalImage(
|
final success = await controller.homeController
|
||||||
imageName: 'radio-icon',
|
.kr_performNodeSwitch(item.tag);
|
||||||
imageType: ImageType.svg,
|
if (success) {
|
||||||
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
controller.homeController.kr_currentListStatus.value =
|
||||||
height: 16.h,
|
KRHomeViewsListStatus.kr_none;
|
||||||
)),
|
}
|
||||||
],
|
} catch (e) {
|
||||||
),
|
KRLogUtil.kr_e('节点切换异常: $e', tag: 'NodeListView');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: _kr_buildNodeListItem(context, item: item),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
]
|
||||||
|
] //
|
||||||
),
|
),
|
||||||
),
|
|
||||||
...controller.kr_subscribeService.allList().map((item) {
|
|
||||||
return InkWell(
|
|
||||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
KRLogUtil.kr_i(
|
|
||||||
'🔄 用户点击节点: ${item.tag}');
|
|
||||||
final success = await controller.homeController
|
|
||||||
.kr_performNodeSwitch(item.tag);
|
|
||||||
if (success) {
|
|
||||||
controller.homeController.kr_currentListStatus.value =
|
|
||||||
KRHomeViewsListStatus.kr_none;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
KRLogUtil.kr_e(
|
|
||||||
'节点切换异常: $e',
|
|
||||||
tag: 'NodeListView');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: _kr_buildNodeListItem(context, item: item),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
]
|
|
||||||
] //
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -346,7 +349,8 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
style: KrAppTextStyle(fontSize: 14, color: Theme.of(context).textTheme.bodySmall?.color),
|
style: KrAppTextStyle(
|
||||||
|
fontSize: 14, color: Theme.of(context).textTheme.bodySmall?.color),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -363,7 +367,8 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 构建单个节点列表项的UI
|
/// 构建单个节点列表项的UI
|
||||||
Widget _kr_buildNodeListItem(BuildContext context, {required KROutboundItem item}) {
|
Widget _kr_buildNodeListItem(BuildContext context,
|
||||||
|
{required KROutboundItem item}) {
|
||||||
return Container(
|
return Container(
|
||||||
key: ValueKey(item.id),
|
key: ValueKey(item.id),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -378,7 +383,12 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
|
padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
KRCountryFlag(countryCode: item.country, width: 30.w, height: 20.w, isCircle: false, maintainSize: false),
|
KRCountryFlag(
|
||||||
|
countryCode: item.country,
|
||||||
|
width: 30.w,
|
||||||
|
height: 20.w,
|
||||||
|
isCircle: false,
|
||||||
|
maintainSize: false),
|
||||||
SizedBox(width: 12.w),
|
SizedBox(width: 12.w),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Obx(() {
|
child: Obx(() {
|
||||||
@ -404,7 +414,8 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
bool isTesting = controller.homeController.kr_isLatency.value;
|
bool isTesting = controller.homeController.kr_isLatency.value;
|
||||||
|
|
||||||
// 极简逻辑:只根据节点类型决定显示方式
|
// 极简逻辑:只根据节点类型决定显示方式
|
||||||
print('🔄 _kr_buildNodeListItem节点: item.tag=${item.tag}, country=${item.country}');
|
print(
|
||||||
|
'🔄 _kr_buildNodeListItem节点: item.tag=${item.tag}, country=${item.country}');
|
||||||
|
|
||||||
// 普通节点,直接显示节点自身的速度
|
// 普通节点,直接显示节点自身的速度
|
||||||
displayDelay = item.urlTestDelay.value;
|
displayDelay = item.urlTestDelay.value;
|
||||||
@ -441,19 +452,23 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
SizedBox(width: 12.w),
|
SizedBox(width: 12.w),
|
||||||
Obx(() => controller.homeController.kr_cutTag.value == item.tag
|
Obx(() {
|
||||||
? KrLocalImage(
|
final selected =
|
||||||
imageName: 'radio-active-icon',
|
controller.homeController.kr_cutSeletedTag.value == item.tag;
|
||||||
imageType: ImageType.svg,
|
return selected
|
||||||
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
? KrLocalImage(
|
||||||
height: 16.h,
|
imageName: 'radio-active-icon',
|
||||||
)
|
imageType: ImageType.svg,
|
||||||
: KrLocalImage(
|
width: 16.w,
|
||||||
imageName: 'radio-icon',
|
height: 16.h,
|
||||||
imageType: ImageType.svg,
|
)
|
||||||
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
: KrLocalImage(
|
||||||
height: 16.h,
|
imageName: 'radio-icon',
|
||||||
)),
|
imageType: ImageType.svg,
|
||||||
|
width: 16.w,
|
||||||
|
height: 16.h,
|
||||||
|
);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -461,7 +476,6 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
|
|
||||||
/// 构建国家列表项的UI
|
/// 构建国家列表项的UI
|
||||||
Widget _kr_buildCountryListItem(BuildContext context, {required country}) {
|
Widget _kr_buildCountryListItem(BuildContext context, {required country}) {
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
key: ValueKey(country),
|
key: ValueKey(country),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -476,17 +490,26 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
|
padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
KRCountryFlag(countryCode: country.country, width: 30.w, height: 20.w, isCircle: false, maintainSize: false),
|
KRCountryFlag(
|
||||||
|
countryCode: country.country,
|
||||||
|
width: 30.w,
|
||||||
|
height: 20.w,
|
||||||
|
isCircle: false,
|
||||||
|
maintainSize: false),
|
||||||
SizedBox(width: 12.w),
|
SizedBox(width: 12.w),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
controller.homeController.kr_getCountryFullName(country.country),
|
controller.homeController.kr_getCountryFullName(country.country),
|
||||||
style: KrAppTextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Colors.white),
|
style: KrAppTextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
// 1. 获取延迟值和测速状态
|
// 1. 获取延迟值和测速状态
|
||||||
int displayDelay = getFastestNodeDelay(controller, country.outboundList);
|
int displayDelay =
|
||||||
|
getFastestNodeDelay(controller, country.outboundList);
|
||||||
bool isTesting = controller.homeController.kr_isLatency.value;
|
bool isTesting = controller.homeController.kr_isLatency.value;
|
||||||
|
|
||||||
// 2. 声明文本和颜色变量
|
// 2. 声明文本和颜色变量
|
||||||
@ -520,21 +543,36 @@ class HINodeListView extends GetView<HINodeListController> {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
SizedBox(width: 12.w),
|
SizedBox(width: 12.w),
|
||||||
Obx(() => controller.homeController.kr_cutTag.value == '${country.country}-auto'
|
Obx(() {
|
||||||
? KrLocalImage(
|
final selectedTag =
|
||||||
imageName: 'radio-active-icon',
|
controller.homeController.kr_cutSeletedTag.value;
|
||||||
imageType: ImageType.svg,
|
if (selectedTag == 'auto') {
|
||||||
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
return KrLocalImage(
|
||||||
height: 16.h,
|
imageName: 'radio-icon',
|
||||||
)
|
imageType: ImageType.svg,
|
||||||
: KrLocalImage(
|
width: 16.w,
|
||||||
imageName: 'radio-icon',
|
height: 16.h,
|
||||||
imageType: ImageType.svg,
|
);
|
||||||
width: 16.w, // 适当缩小 SVG 尺寸,留出白边
|
}
|
||||||
height: 16.h,
|
final node = controller.kr_subscribeService.keyList[selectedTag];
|
||||||
)),
|
final selectedCountry = node?.country ?? '';
|
||||||
|
final selected = selectedCountry == country.country;
|
||||||
|
return selected
|
||||||
|
? KrLocalImage(
|
||||||
|
imageName: 'radio-active-icon',
|
||||||
|
imageType: ImageType.svg,
|
||||||
|
width: 16.w,
|
||||||
|
height: 16.h,
|
||||||
|
)
|
||||||
|
: KrLocalImage(
|
||||||
|
imageName: 'radio-icon',
|
||||||
|
imageType: ImageType.svg,
|
||||||
|
width: 16.w,
|
||||||
|
height: 16.h,
|
||||||
|
);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -124,10 +124,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 闪连状态存储键
|
// 闪连状态存储键
|
||||||
static const String _quickConnectKey = 'kr_quick_connect_enabled';
|
static const String _quickConnectKey = 'kr_quick_connect_enabled';
|
||||||
|
|
||||||
// 国家内节点重选相关属性
|
|
||||||
final RxString currentSelectedCountry = ''.obs;
|
|
||||||
final int countryReselectionLatencyThreshold = 3000; // 延迟阈值(毫秒)
|
|
||||||
|
|
||||||
// 添加一个方法来切换状态
|
// 添加一个方法来切换状态
|
||||||
void toggleQuickConnect(bool? value) async {
|
void toggleQuickConnect(bool? value) async {
|
||||||
// 只有当传入的值不为 null 时才更新状态
|
// 只有当传入的值不为 null 时才更新状态
|
||||||
@ -395,10 +391,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
_kr_addStatusSyncCheck();
|
_kr_addStatusSyncCheck();
|
||||||
|
|
||||||
if (AppConfig().kr_is_daytime == true) {
|
if (AppConfig().kr_is_daytime == true) {
|
||||||
Future.delayed(const Duration(seconds: 1), () {
|
Future.delayed(const Duration(seconds: 5), () {
|
||||||
KRUpdateUtil().kr_checkUpdate();
|
KRUpdateUtil().kr_checkUpdate();
|
||||||
|
|
||||||
Future.delayed(const Duration(seconds: 1), () {
|
Future.delayed(const Duration(seconds: 5), () {
|
||||||
// 不做语言切换
|
// 不做语言切换
|
||||||
// KRLanguageSwitchDialog.kr_show();
|
// KRLanguageSwitchDialog.kr_show();
|
||||||
});
|
});
|
||||||
@ -552,7 +548,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
if (isValidLogin) {
|
if (isValidLogin) {
|
||||||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
|
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
|
||||||
KRLogUtil.kr_i('登录状态变化:设置为已登录', tag: 'HomeController');
|
KRLogUtil.kr_i('登录状态变化:设置为已登录', tag: 'HomeController');
|
||||||
|
|
||||||
KRAnnouncementService().kr_checkAnnouncement();
|
KRAnnouncementService().kr_checkAnnouncement();
|
||||||
|
|
||||||
// 订阅服务已在 splash 页面初始化,此处无需重复初始化
|
// 订阅服务已在 splash 页面初始化,此处无需重复初始化
|
||||||
@ -590,9 +586,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
try {
|
try {
|
||||||
final currentLoginStatus = KRAppRunData().kr_isLogin.value;
|
final currentLoginStatus = KRAppRunData().kr_isLogin.value;
|
||||||
final currentViewStatus = kr_currentViewStatus.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');
|
KRLogUtil.kr_w('状态不一致:视图显示已登录但实际未登录,修正状态', tag: 'HomeController');
|
||||||
@ -692,13 +688,15 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
ever(KRSingBoxImp.instance.kr_status, (status) {
|
ever(KRSingBoxImp.instance.kr_status, (status) {
|
||||||
KRLogUtil.kr_i('🔄 连接状态变化: $status', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 连接状态变化: $status', tag: 'HomeController');
|
||||||
KRLogUtil.kr_i('📊 当前状态类型: ${status.runtimeType}', tag: 'HomeController');
|
KRLogUtil.kr_i('📊 当前状态类型: ${status.runtimeType}', tag: 'HomeController');
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case SingboxStopped():
|
case SingboxStopped():
|
||||||
KRLogUtil.kr_i('🔴 状态: 已停止', tag: 'HomeController');
|
KRLogUtil.kr_i('🔴 状态: 已停止', tag: 'HomeController');
|
||||||
kr_connectText.value = AppTranslations.kr_home.disconnected;
|
kr_connectText.value = AppTranslations.kr_home.disconnected;
|
||||||
kr_stopConnectionTimer();
|
kr_stopConnectionTimer();
|
||||||
kr_resetConnectionInfo();
|
kr_resetConnectionInfo();
|
||||||
|
// 取消连接超时处理
|
||||||
|
_cancelConnectionTimeout();
|
||||||
kr_currentSpeed.value = "--";
|
kr_currentSpeed.value = "--";
|
||||||
kr_isLatency.value = false;
|
kr_isLatency.value = false;
|
||||||
kr_isConnected.value = false;
|
kr_isConnected.value = false;
|
||||||
@ -750,12 +748,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
break;
|
break;
|
||||||
case SingboxStopping():
|
case SingboxStopping():
|
||||||
KRLogUtil.kr_i('🟠 状态: 正在停止', tag: 'HomeController');
|
KRLogUtil.kr_i('🟠 状态: 正在停止', tag: 'HomeController');
|
||||||
|
// 取消连接超时处理
|
||||||
|
_cancelConnectionTimeout();
|
||||||
kr_connectText.value = AppTranslations.kr_home.disconnecting;
|
kr_connectText.value = AppTranslations.kr_home.disconnecting;
|
||||||
kr_isConnected.value = false;
|
kr_isConnected.value = false;
|
||||||
kr_currentSpeed.value = "--";
|
kr_currentSpeed.value = "--";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 强制更新UI
|
// 强制更新UI
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
@ -839,7 +839,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
|
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
|
||||||
KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', tag: 'HomeController');
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -848,12 +848,11 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 开启连接
|
// 开启连接
|
||||||
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
}
|
}
|
||||||
await KRSingBoxImp.instance.kr_start();
|
await KRSingBoxImp.instance.kr_start();
|
||||||
|
|
||||||
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
|
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔧 修复: 等待状态更新,最多3秒
|
// 🔧 修复: 等待状态更新,最多3秒
|
||||||
await _waitForStatus(SingboxStarted, maxSeconds: 3);
|
await _waitForStatus(SingboxStarted, maxSeconds: 3);
|
||||||
@ -861,7 +860,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 关闭连接
|
// 关闭连接
|
||||||
KRLogUtil.kr_i('🛑 开始断开连接...', tag: 'HomeController');
|
KRLogUtil.kr_i('🛑 开始断开连接...', tag: 'HomeController');
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
}
|
}
|
||||||
await KRSingBoxImp.instance.kr_stop().timeout(
|
await KRSingBoxImp.instance.kr_stop().timeout(
|
||||||
const Duration(seconds: 10),
|
const Duration(seconds: 10),
|
||||||
onTimeout: () {
|
onTimeout: () {
|
||||||
@ -871,7 +870,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
);
|
);
|
||||||
KRLogUtil.kr_i('✅ 断开命令已发送', tag: 'HomeController');
|
KRLogUtil.kr_i('✅ 断开命令已发送', tag: 'HomeController');
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔧 修复: 等待状态更新,最多2秒
|
// 🔧 修复: 等待状态更新,最多2秒
|
||||||
await _waitForStatus(SingboxStopped, maxSeconds: 2);
|
await _waitForStatus(SingboxStopped, maxSeconds: 2);
|
||||||
@ -879,13 +878,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
KRLogUtil.kr_e('❌ 切换失败: $e', tag: 'HomeController');
|
KRLogUtil.kr_e('❌ 切换失败: $e', tag: 'HomeController');
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
}
|
}
|
||||||
// 发生错误时强制同步状态
|
// 发生错误时强制同步状态
|
||||||
kr_forceSyncConnectionStatus();
|
kr_forceSyncConnectionStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 🔧 等待状态达到预期值
|
/// 🔧 等待状态达到预期值
|
||||||
@ -1096,14 +1095,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
return node.urlTestDelay.value < 65535 && node.urlTestDelay.value > 0;
|
return node.urlTestDelay.value < 65535 && node.urlTestDelay.value > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 设置当前选择的国家(由hi_node_list_controller调用)
|
|
||||||
void setCurrentSelectedCountry(String country) {
|
|
||||||
currentSelectedCountry.value = country;
|
|
||||||
KRLogUtil.kr_i('🌍 设置当前选择国家: $country', tag: 'HomeController');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// 更新自动模式延迟
|
/// 更新自动模式延迟
|
||||||
void _kr_updateAutoLatency(dynamic element) {
|
void _kr_updateAutoLatency(dynamic element) {
|
||||||
for (var subElement in element.items) {
|
for (var subElement in element.items) {
|
||||||
@ -1137,11 +1128,36 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔒 节点切换状态锁和节流控制
|
||||||
|
bool _isSwitchingNode = false;
|
||||||
|
DateTime? _lastSwitchTime;
|
||||||
|
static const Duration _switchThrottleDuration = Duration(milliseconds: 2000); // 2秒节流
|
||||||
|
|
||||||
/// 🔧 修复:统一的节点切换方法(包含UI同步和后台操作等待)
|
/// 🔧 修复:统一的节点切换方法(包含UI同步和后台操作等待)
|
||||||
/// 执行节点切换,包括UI更新和后台操作的完整同步
|
/// 执行节点切换,包括UI更新和后台操作的完整同步
|
||||||
/// 返回 true 表示切换成功,false 表示失败
|
/// 返回 true 表示切换成功,false 表示失败
|
||||||
Future<bool> kr_performNodeSwitch(String tag) async {
|
Future<bool> kr_performNodeSwitch(String tag) async {
|
||||||
try {
|
try {
|
||||||
|
// 🔒 状态锁:防止并发切换
|
||||||
|
if (_isSwitchingNode) {
|
||||||
|
KRLogUtil.kr_w('⚠️ 节点切换正在进行中,忽略重复请求', tag: 'HomeController');
|
||||||
|
KRCommonUtil.kr_showToast('请等待当前节点切换完成');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔒 节流控制: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');
|
||||||
|
KRCommonUtil.kr_showToast('切换过于频繁,请稍后再试');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔒 设置状态锁和记录时间
|
||||||
|
_isSwitchingNode = true;
|
||||||
|
_lastSwitchTime = now;
|
||||||
|
|
||||||
KRLogUtil.kr_i('🔄 开始切换节点: $tag', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 开始切换节点: $tag', tag: 'HomeController');
|
||||||
|
|
||||||
// 1. 保存原节点,以备失败恢复
|
// 1. 保存原节点,以备失败恢复
|
||||||
@ -1155,6 +1171,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
if (!kr_isConnected.value) {
|
if (!kr_isConnected.value) {
|
||||||
KRLogUtil.kr_i('📴 VPN未连接,只更新UI变量: $tag', tag: 'HomeController');
|
KRLogUtil.kr_i('📴 VPN未连接,只更新UI变量: $tag', tag: 'HomeController');
|
||||||
kr_cutSeletedTag.value = tag;
|
kr_cutSeletedTag.value = tag;
|
||||||
|
kr_updateConnectionInfo();
|
||||||
// kr_moveToSelectedNode();
|
// kr_moveToSelectedNode();
|
||||||
|
|
||||||
// 🔧 修复:保存节点选择以便VPN启动时应用
|
// 🔧 修复:保存节点选择以便VPN启动时应用
|
||||||
@ -1191,29 +1208,29 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
KRLogUtil.kr_i('💾 保存新节点选择: $tag', tag: 'HomeController');
|
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连接以断开所有现有长连接
|
// 🔧 方案A优化:重启VPN连接以断开所有现有长连接
|
||||||
KRLogUtil.kr_i('🔄 [增强] 停止VPN连接以断开现有连接...', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 [优化] 停止VPN连接以断开现有连接...', tag: 'HomeController');
|
||||||
await KRSingBoxImp.instance.kr_stop(); // 先停止VPN
|
await KRSingBoxImp.instance.kr_stop(); // 先停止VPN(已跳过DNS恢复)
|
||||||
|
|
||||||
// 🚀 方案A增强:增加等待时间,确保所有连接完全释放
|
// 🚀 优化:减少等待时间(DNS操作已优化,无需过长等待)
|
||||||
KRLogUtil.kr_i('⏳ [增强] 等待VPN完全停止(1500ms,确保旧连接全部断开)...', tag: 'HomeController');
|
KRLogUtil.kr_i('⏳ [优化] 等待VPN完全停止(800ms)...', tag: 'HomeController');
|
||||||
await Future.delayed(const Duration(milliseconds: 1500)); // 从800ms增加到1500ms
|
await Future.delayed(const Duration(milliseconds: 800)); // 从1500ms减少到800ms
|
||||||
|
|
||||||
KRLogUtil.kr_i('🔄 [增强] 启动VPN并应用新节点: $tag', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 [优化] 启动VPN并应用新节点: $tag', tag: 'HomeController');
|
||||||
await KRSingBoxImp.instance.kr_start(); // 重新启动VPN,会自动使用新保存的节点
|
await KRSingBoxImp.instance.kr_start(); // 重新启动VPN(已跳过DNS备份)
|
||||||
|
|
||||||
// 🚀 方案A增强:增加启动等待时间,确保新VPN完全建立
|
// 🚀 优化:减少等待时间(DNS操作已优化)
|
||||||
KRLogUtil.kr_i('⏳ [增强] 等待VPN完全启动(2500ms,确保新连接完全建立)...', tag: 'HomeController');
|
KRLogUtil.kr_i('⏳ [优化] 等待VPN完全启动(1200ms)...', tag: 'HomeController');
|
||||||
await Future.delayed(const Duration(milliseconds: 2500)); // 从1500ms增加到2500ms
|
await Future.delayed(const Duration(milliseconds: 1200)); // 从2500ms减少到1200ms
|
||||||
|
|
||||||
// 后台切换成功,更新UI
|
// 后台切换成功,更新UI
|
||||||
kr_cutSeletedTag.value = tag;
|
kr_cutSeletedTag.value = tag;
|
||||||
kr_updateConnectionInfo();
|
kr_updateConnectionInfo();
|
||||||
// kr_moveToSelectedNode();
|
kr_moveToSelectedNode();
|
||||||
|
|
||||||
// 🚀 方案A增强:增加验证前等待时间,确保活动组完全更新
|
// 🚀 优化:减少验证等待时间
|
||||||
KRLogUtil.kr_i('⏳ [增强] 等待活动组更新(500ms)...', tag: 'HomeController');
|
KRLogUtil.kr_i('⏳ [优化] 等待活动组更新(300ms)...', tag: 'HomeController');
|
||||||
await Future.delayed(const Duration(milliseconds: 500)); // 从200ms增加到500ms
|
await Future.delayed(const Duration(milliseconds: 300)); // 从500ms减少到300ms
|
||||||
|
|
||||||
// 🚀 方案A增强:验证节点是否真正切换成功
|
// 🚀 方案A增强:验证节点是否真正切换成功
|
||||||
KRLogUtil.kr_i('🔍 [增强] 验证节点切换是否成功...', tag: 'HomeController');
|
KRLogUtil.kr_i('🔍 [增强] 验证节点切换是否成功...', tag: 'HomeController');
|
||||||
@ -1221,7 +1238,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 刷新活动组信息
|
// 刷新活动组信息
|
||||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||||
final selectGroup = activeGroups.firstWhere(
|
final selectGroup = activeGroups.firstWhere(
|
||||||
(group) => group.tag == 'select',
|
(group) => group.tag == 'select',
|
||||||
orElse: () => throw Exception('未找到 select 组'),
|
orElse: () => throw Exception('未找到 select 组'),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1271,6 +1288,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
KRCommonUtil.kr_showToast('节点切换异常,请重试');
|
KRCommonUtil.kr_showToast('节点切换异常,请重试');
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
|
// 🔒 释放状态锁
|
||||||
|
_isSwitchingNode = false;
|
||||||
// 关闭加载状态
|
// 关闭加载状态
|
||||||
kr_isLatency.value = false;
|
kr_isLatency.value = false;
|
||||||
KRLogUtil.kr_i('🔄 节点切换流程完成', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 节点切换流程完成', tag: 'HomeController');
|
||||||
@ -1308,8 +1327,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
// 2. 如果是 auto,优先使用 kr_cutSeletedTag(保存了实际选中的节点)
|
// 2. 如果是 auto,优先使用 kr_cutSeletedTag(保存了实际选中的节点)
|
||||||
else if (kr_cutSeletedTag.value.isNotEmpty &&
|
else if (kr_cutSeletedTag.value.isNotEmpty &&
|
||||||
kr_cutSeletedTag.value != 'auto' &&
|
kr_cutSeletedTag.value != 'auto' &&
|
||||||
kr_cutSeletedTag.value != 'select') {
|
kr_cutSeletedTag.value != 'select') {
|
||||||
// auto 模式下,使用保存的实际节点
|
// auto 模式下,使用保存的实际节点
|
||||||
actualTag = kr_cutSeletedTag.value;
|
actualTag = kr_cutSeletedTag.value;
|
||||||
KRLogUtil.kr_i('✅ 使用 auto 模式下的实际节点 (kr_cutSeletedTag): $actualTag', tag: 'getCurrentNodeCountry');
|
KRLogUtil.kr_i('✅ 使用 auto 模式下的实际节点 (kr_cutSeletedTag): $actualTag', tag: 'getCurrentNodeCountry');
|
||||||
@ -1344,7 +1363,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
print('[getCurrentNodeCountry] select 组选中的是 auto,查找 urltest 组');
|
print('[getCurrentNodeCountry] select 组选中的是 auto,查找 urltest 组');
|
||||||
// 如果 select 组选中的是 auto,从 urltest 组获取
|
// 如果 select 组选中的是 auto,从 urltest 组获取
|
||||||
final urlTestGroup = allGroups.firstWhere(
|
final urlTestGroup = allGroups.firstWhere(
|
||||||
(group) => group.type == ProxyType.urltest,
|
(group) => group.type == ProxyType.urltest,
|
||||||
orElse: () => throw Exception('未找到 urltest 组'),
|
orElse: () => throw Exception('未找到 urltest 组'),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1366,7 +1385,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 活动组不为空,从活动组获取
|
// 活动组不为空,从活动组获取
|
||||||
// 从 SingBox 活动组中找到 "select" 选择器组
|
// 从 SingBox 活动组中找到 "select" 选择器组
|
||||||
final selectGroup = KRSingBoxImp.instance.kr_activeGroups.firstWhere(
|
final selectGroup = KRSingBoxImp.instance.kr_activeGroups.firstWhere(
|
||||||
(group) => group.tag == 'select',
|
(group) => group.tag == 'select',
|
||||||
orElse: () => throw Exception('未找到 select 组'),
|
orElse: () => throw Exception('未找到 select 组'),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1420,88 +1439,41 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 获取真实连接的节点信息(auto 模式下获取实际连接的节点)
|
/// 获取真实连接的节点信息(统一为手动选择场景)
|
||||||
Map<String, dynamic> kr_getRealConnectedNodeInfo() {
|
Map<String, dynamic> kr_getRealConnectedNodeInfo() {
|
||||||
// 如果不是 auto 模式,也不是 country-auto 模式,直接返回当前选中的节点信息
|
String actualTag = kr_cutSeletedTag == 'auto' ? kr_cutSeletedTag.value : kr_cutTag.value;
|
||||||
if (kr_cutTag.value != 'auto' && !kr_cutTag.value.endsWith('-auto')) {
|
final node = kr_subscribeService.keyList[actualTag];
|
||||||
final node = kr_subscribeService.keyList[kr_cutSeletedTag.value];
|
|
||||||
return {
|
|
||||||
'nodeName': kr_cutSeletedTag.value,
|
|
||||||
'delay': node?.urlTestDelay.value ?? -2,
|
|
||||||
'country': node?.country ?? '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理 auto 模式(包括全局 auto 和 country-auto)
|
|
||||||
print('当前活动组----${KRSingBoxImp.instance.kr_activeGroups.length}');
|
|
||||||
for (var group in KRSingBoxImp.instance.kr_activeGroups) {
|
|
||||||
print('当前活动组----$group}');
|
|
||||||
|
|
||||||
// 处理全局 auto 模式
|
|
||||||
if (kr_cutTag.value.endsWith('auto') && group.type == ProxyType.urltest && group.tag == 'auto') {
|
|
||||||
final selectedNode = group.selected;
|
|
||||||
final node = kr_subscribeService.keyList[selectedNode];
|
|
||||||
return {
|
|
||||||
'nodeName': selectedNode,
|
|
||||||
'delay': node?.urlTestDelay.value ?? -2,
|
|
||||||
'country': node?.country ?? '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理 country-auto 模式的备用方案(当 SingBox 组数据不可用时)
|
|
||||||
if (kr_cutTag.value.endsWith('-auto')) {
|
|
||||||
final countryCode = kr_cutTag.value.replaceAll('-auto', '');
|
|
||||||
KRLogUtil.kr_i('🔄 kr_getRealConnectedNodeInfo 使用备用方案获取 country-auto 信息: $countryCode', tag: 'HomeController');
|
|
||||||
final autoNodeInfo = kr_getCountryAutoSelectedNode(countryCode);
|
|
||||||
if (autoNodeInfo != null) {
|
|
||||||
return {
|
|
||||||
'nodeName': autoNodeInfo['tag'],
|
|
||||||
'delay': autoNodeInfo['delay'] ?? -2,
|
|
||||||
'country': autoNodeInfo['country'] ?? countryCode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有找到 urltest 组,返回默认值
|
|
||||||
return {
|
return {
|
||||||
'nodeName': kr_cutTag.value,
|
'nodeName': kr_cutTag.value,
|
||||||
'delay': -2,
|
'delay': node?.urlTestDelay.value ?? -2,
|
||||||
'country': '',
|
'country': node?.country ?? '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取真实连接的节点名称
|
|
||||||
String kr_getRealConnectedNodeName() {
|
|
||||||
final info = kr_getRealConnectedNodeInfo();
|
|
||||||
return info['nodeName'] as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取真实连接的节点延迟
|
|
||||||
int kr_getRealConnectedNodeDelay() {
|
|
||||||
final info = kr_getRealConnectedNodeInfo();
|
|
||||||
return info['delay'] as int;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取真实连接的节点国家
|
/// 获取真实连接的节点国家
|
||||||
String kr_getRealConnectedNodeCountry() {
|
String kr_getRealConnectedNodeCountry() {
|
||||||
final info = kr_getRealConnectedNodeInfo();
|
final country = kr_getCurrentNodeCountry();
|
||||||
final delay = info['delay'] as int; // 使用真实连接的节点延迟,而不是 kr_currentNodeLatency.value
|
if(country.isEmpty) return '';
|
||||||
final country1 = kr_getCurrentNodeCountry();
|
return kr_getCountryFullName(country);
|
||||||
print('country----$country1');
|
// controller.kr_cutSeletedTag.value
|
||||||
print('kr_getRealConnectedNodeCountry - delay from info: $delay, country from info: ${info['country']}');
|
// final info = kr_getRealConnectedNodeInfo();
|
||||||
final country = kr_getCountryFullName(info['country']);
|
// final delay = info['delay'] as int; // 使用真实连接的节点延迟,而不是 kr_currentNodeLatency.value
|
||||||
if (delay == -2) {
|
// final country1 = kr_getCurrentNodeCountry();
|
||||||
return '--';
|
// print('country----$country1');
|
||||||
} else if (delay == -1) {
|
// print('kr_getRealConnectedNodeCountry - delay from info: $delay, country from info: ${info['country']}');
|
||||||
return '${country} ${AppTranslations.kr_home.connecting}';
|
// final country = kr_getCountryFullName(country1);
|
||||||
} else if (delay == 0) {
|
// if (delay == -2) {
|
||||||
return '${country} ${AppTranslations.kr_home.connected}';
|
// return '--';
|
||||||
} else if (delay >= 3000) {
|
// } else if (delay == -1) {
|
||||||
return '${country} ${AppTranslations.kr_home.timeout}';
|
// return '${country} ${AppTranslations.kr_home.connecting}';
|
||||||
} else {
|
// } else if (delay == 0) {
|
||||||
return '${country} ${delay}ms';
|
// return '${country} ${AppTranslations.kr_home.connected}';
|
||||||
}
|
// } else if (delay >= 3000) {
|
||||||
|
// return '${country} ${AppTranslations.kr_home.timeout}';
|
||||||
|
// } else {
|
||||||
|
// return '${country} ${delay}ms';
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1840,10 +1812,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
Future<void> kr_forceDirectTest() async {
|
Future<void> kr_forceDirectTest() async {
|
||||||
try {
|
try {
|
||||||
KRLogUtil.kr_i('🔧 强制使用直接连接测试...', tag: 'HomeController');
|
KRLogUtil.kr_i('🔧 强制使用直接连接测试...', tag: 'HomeController');
|
||||||
|
|
||||||
// 使用直接连接测试所有节点
|
// 使用直接连接测试所有节点
|
||||||
await _kr_testLatencyWithoutVpn();
|
await _kr_testLatencyWithoutVpn();
|
||||||
|
|
||||||
KRLogUtil.kr_i('✅ 直接连接测试完成', tag: 'HomeController');
|
KRLogUtil.kr_i('✅ 直接连接测试完成', tag: 'HomeController');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
KRLogUtil.kr_e('❌ 直接连接测试失败: $e', tag: 'HomeController');
|
KRLogUtil.kr_e('❌ 直接连接测试失败: $e', tag: 'HomeController');
|
||||||
@ -1857,15 +1829,15 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
try {
|
try {
|
||||||
KRLogUtil.kr_i('🧪 开始延迟测试...', tag: 'HomeController');
|
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) {
|
if (kr_isConnected.value) {
|
||||||
// 已连接状态:使用 SingBox 通过代理测试
|
// 已连接状态:使用 SingBox 通过代理测试
|
||||||
KRLogUtil.kr_i('🔗 已连接状态 - 使用 SingBox 通过代理测试延迟', tag: 'HomeController');
|
KRLogUtil.kr_i('🔗 已连接状态 - 使用 SingBox 通过代理测试延迟', tag: 'HomeController');
|
||||||
await KRSingBoxImp.instance.kr_urlTest("select");
|
await KRSingBoxImp.instance.kr_urlTest("select");
|
||||||
|
|
||||||
// 等待一段时间让 SingBox 完成测试
|
// 等待一段时间让 SingBox 完成测试
|
||||||
await Future.delayed(const Duration(seconds: 3));
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
|
|
||||||
// 再次检查活动组状态
|
// 再次检查活动组状态
|
||||||
KRLogUtil.kr_i('🔄 检查代理测试后的活动组状态...', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 检查代理测试后的活动组状态...', tag: 'HomeController');
|
||||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||||
@ -2127,16 +2099,16 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
/// 连接超时处理
|
/// 连接超时处理
|
||||||
Timer? _connectionTimeoutTimer;
|
Timer? _connectionTimeoutTimer;
|
||||||
|
|
||||||
void _startConnectionTimeout() {
|
void _startConnectionTimeout() {
|
||||||
_connectionTimeoutTimer?.cancel();
|
_connectionTimeoutTimer?.cancel();
|
||||||
_connectionTimeoutTimer = Timer(const Duration(seconds: 30), () {
|
_connectionTimeoutTimer = Timer(const Duration(seconds: 30), () {
|
||||||
KRLogUtil.kr_w('⏰ 连接超时,强制重置状态', tag: 'HomeController');
|
KRLogUtil.kr_w('⏰ 连接超时,强制重置状态', tag: 'HomeController');
|
||||||
|
|
||||||
// 检查是否仍在连接中
|
// 检查是否仍在连接中
|
||||||
if (KRSingBoxImp.instance.kr_status.value is SingboxStarting) {
|
if (KRSingBoxImp.instance.kr_status.value is SingboxStarting) {
|
||||||
KRLogUtil.kr_w('🔄 连接超时,强制停止并重置', tag: 'HomeController');
|
KRLogUtil.kr_w('🔄 连接超时,强制停止并重置', tag: 'HomeController');
|
||||||
|
|
||||||
// 强制停止连接
|
// 强制停止连接
|
||||||
KRSingBoxImp.instance.kr_stop().then((_) {
|
KRSingBoxImp.instance.kr_stop().then((_) {
|
||||||
// 重置状态
|
// 重置状态
|
||||||
@ -2146,7 +2118,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
kr_currentNodeLatency.value = -2;
|
kr_currentNodeLatency.value = -2;
|
||||||
kr_resetConnectionInfo();
|
kr_resetConnectionInfo();
|
||||||
update();
|
update();
|
||||||
|
|
||||||
KRLogUtil.kr_i('✅ 连接超时处理完成', tag: 'HomeController');
|
KRLogUtil.kr_i('✅ 连接超时处理完成', tag: 'HomeController');
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
KRLogUtil.kr_e('❌ 连接超时处理失败: $e', tag: 'HomeController');
|
KRLogUtil.kr_e('❌ 连接超时处理失败: $e', tag: 'HomeController');
|
||||||
@ -2154,7 +2126,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _cancelConnectionTimeout() {
|
void _cancelConnectionTimeout() {
|
||||||
_connectionTimeoutTimer?.cancel();
|
_connectionTimeoutTimer?.cancel();
|
||||||
_connectionTimeoutTimer = null;
|
_connectionTimeoutTimer = null;
|
||||||
@ -2197,209 +2169,33 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取国家-auto组的当前选中子节点信息
|
/// 获取国家选中子节点信息(已不使用自动选择逻辑)
|
||||||
Map<String, dynamic>? kr_getCountryAutoSelectedNode(String countryCode) {
|
Map<String, dynamic>? kr_getCountryAutoSelectedNode(String countryCode) {
|
||||||
try {
|
|
||||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
|
||||||
final allGroups = KRSingBoxImp.instance.kr_allGroups;
|
|
||||||
final autoGroupTag = '${countryCode}-auto';
|
|
||||||
|
|
||||||
KRLogUtil.kr_i('🔍 开始获取国家-auto选中节点: countryCode=$countryCode, autoGroupTag=$autoGroupTag', tag: 'HomeController');
|
|
||||||
KRLogUtil.kr_i('📊 活跃组数量: ${activeGroups.length}, 所有组数量: ${allGroups.length}', tag: 'HomeController');
|
|
||||||
|
|
||||||
// 优先检查活跃组
|
|
||||||
if (activeGroups.isNotEmpty) {
|
|
||||||
KRLogUtil.kr_i('✅ 活跃组不为空,优先检查活跃组', tag: 'HomeController');
|
|
||||||
for (var group in activeGroups) {
|
|
||||||
KRLogUtil.kr_i('🔄 检查活跃组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
|
|
||||||
|
|
||||||
if (group.tag == autoGroupTag && group.type == ProxyType.urltest) {
|
|
||||||
KRLogUtil.kr_i('✅ 在活跃组中找到匹配的urltest组: $autoGroupTag', tag: 'HomeController');
|
|
||||||
return _kr_extractSelectedNodeInfo(group, countryCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果活跃组中没有,检查所有组
|
|
||||||
if (allGroups.isNotEmpty) {
|
|
||||||
KRLogUtil.kr_i('🔍 活跃组中未找到,检查所有组', tag: 'HomeController');
|
|
||||||
for (var group in allGroups) {
|
|
||||||
KRLogUtil.kr_i('🔄 检查所有组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
|
|
||||||
|
|
||||||
if (group.tag == autoGroupTag && group.type == ProxyType.urltest) {
|
|
||||||
KRLogUtil.kr_i('✅ 在所有组中找到匹配的urltest组: $autoGroupTag', tag: 'HomeController');
|
|
||||||
return _kr_extractSelectedNodeInfo(group, countryCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果组数据都为空,使用备用方案:从订阅服务中找该国家最快的节点
|
|
||||||
KRLogUtil.kr_i('🔄 组数据为空,使用备用方案从订阅服务获取最快节点', tag: 'HomeController');
|
|
||||||
return _kr_getFastestNodeFromSubscribeService(countryCode);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
KRLogUtil.kr_e('💥 获取国家-auto选中节点异常: $e', tag: 'HomeController');
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从订阅服务获取该国家最快节点的备用方案
|
|
||||||
Map<String, dynamic>? _kr_getFastestNodeFromSubscribeService(String countryCode) {
|
|
||||||
try {
|
|
||||||
KRLogUtil.kr_i('🔄 使用订阅服务查找国家最快节点: $countryCode', tag: 'HomeController');
|
|
||||||
|
|
||||||
// 从订阅服务中获取该国家的所有节点
|
|
||||||
final allNodes = kr_subscribeService.allList.where((node) =>
|
|
||||||
node.country == countryCode && !node.tag.endsWith('-auto')
|
|
||||||
).toList();
|
|
||||||
|
|
||||||
KRLogUtil.kr_i('📊 找到 ${allNodes.length} 个该国家的普通节点', tag: 'HomeController');
|
|
||||||
|
|
||||||
if (allNodes.isEmpty) {
|
|
||||||
KRLogUtil.kr_w('⚠️ 未找到该国家的任何普通节点: $countryCode', tag: 'HomeController');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 找出延迟最小的节点(排除0和超时)
|
|
||||||
KROutboundItem? fastestNode;
|
|
||||||
int fastestDelay = 999999;
|
|
||||||
|
|
||||||
for (var node in allNodes) {
|
|
||||||
final delay = node.urlTestDelay.value;
|
|
||||||
KRLogUtil.kr_w('🔍 检查节点 ${node.tag} 的延迟: $delay', tag: 'HomeController');
|
|
||||||
if (delay > 0 && delay < fastestDelay) {
|
|
||||||
fastestDelay = delay;
|
|
||||||
fastestNode = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fastestNode != null) {
|
|
||||||
KRLogUtil.kr_i('✅ 找到最快节点: ${fastestNode.tag}, 延迟: ${fastestDelay}ms', tag: 'HomeController');
|
|
||||||
return {
|
|
||||||
'tag': fastestNode.tag,
|
|
||||||
'delay': fastestDelay,
|
|
||||||
'country': countryCode,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
KRLogUtil.kr_w('⚠️ 该国家的节点都没有有效延迟数据', tag: 'HomeController');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
KRLogUtil.kr_e('💥 获取订阅服务最快节点异常: $e', tag: 'HomeController');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 提取选中节点信息的辅助方法
|
|
||||||
Map<String, dynamic>? _kr_extractSelectedNodeInfo(dynamic group, String countryCode) {
|
|
||||||
try {
|
|
||||||
// 获取当前选中的节点
|
|
||||||
final selectedNode = group.selected;
|
|
||||||
KRLogUtil.kr_i('🎯 当前选中节点: $selectedNode', tag: 'HomeController');
|
|
||||||
KRLogUtil.kr_i('📋 组内项目数量: ${group.items.length}', tag: 'HomeController');
|
|
||||||
|
|
||||||
if (selectedNode != null && selectedNode.isNotEmpty) {
|
|
||||||
KRLogUtil.kr_i('✅ 选中节点有效,开始查找节点详情', tag: 'HomeController');
|
|
||||||
|
|
||||||
// 打印所有节点信息用于调试
|
|
||||||
for (var item in group.items) {
|
|
||||||
KRLogUtil.kr_i('📄 组内节点: tag=${item.tag}, delay=${item.urlTestDelay}', tag: 'HomeController');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在组内查找选中的节点
|
|
||||||
try {
|
|
||||||
final selectedItem = group.items.firstWhere(
|
|
||||||
(item) => item.tag == selectedNode,
|
|
||||||
);
|
|
||||||
|
|
||||||
KRLogUtil.kr_i('🎉 成功找到选中节点: tag=${selectedItem.tag}, delay=${selectedItem.urlTestDelay}', tag: 'HomeController');
|
|
||||||
|
|
||||||
return {
|
|
||||||
'tag': selectedNode,
|
|
||||||
'delay': selectedItem.urlTestDelay,
|
|
||||||
'country': countryCode,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
KRLogUtil.kr_e('❌ 在组内未找到选中节点: $selectedNode', tag: 'HomeController');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
KRLogUtil.kr_w('⚠️ 选中节点为空或无效', tag: 'HomeController');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
KRLogUtil.kr_e('💥 提取选中节点信息异常: $e', tag: 'HomeController');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取全局 auto 组的当前选中子节点信息
|
/// 从订阅服务获取该国家最快节点的备用方案
|
||||||
Map<String, dynamic>? kr_getGlobalAutoSelectedNode() {
|
Map<String, dynamic>? _kr_getFastestNodeFromSubscribeService(String countryCode) {
|
||||||
try {
|
|
||||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
|
||||||
|
|
||||||
KRLogUtil.kr_i('🌍 开始获取全局auto选中节点信息', tag: 'HomeController');
|
|
||||||
KRLogUtil.kr_i('📊 活跃组数量: ${activeGroups.length}', tag: 'HomeController');
|
|
||||||
|
|
||||||
for (var group in activeGroups) {
|
|
||||||
KRLogUtil.kr_i('🔄 检查全局组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
|
|
||||||
KRLogUtil.kr_i('📋 全局组内项目数量: ${group.items.length}', tag: 'HomeController');
|
|
||||||
|
|
||||||
if (group.tag == 'auto' && group.type == ProxyType.urltest) {
|
|
||||||
KRLogUtil.kr_i('✅ 找到全局auto urltest组', tag: 'HomeController');
|
|
||||||
|
|
||||||
// 获取当前选中的节点
|
|
||||||
final selectedNode = group.selected;
|
|
||||||
KRLogUtil.kr_i('🎯 全局auto当前选中节点: $selectedNode', tag: 'HomeController');
|
|
||||||
|
|
||||||
if (selectedNode != null && selectedNode.isNotEmpty) {
|
|
||||||
KRLogUtil.kr_i('✅ 全局auto选中节点有效,开始查找详情', tag: 'HomeController');
|
|
||||||
|
|
||||||
// 打印所有节点信息用于调试
|
|
||||||
for (var item in group.items) {
|
|
||||||
KRLogUtil.kr_i('📄 全局auto组内节点: tag=${item.tag}, delay=${item.urlTestDelay}', tag: 'HomeController');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在组内查找选中的节点
|
|
||||||
try {
|
|
||||||
final selectedItem = group.items.firstWhere(
|
|
||||||
(item) => item.tag == selectedNode,
|
|
||||||
);
|
|
||||||
|
|
||||||
KRLogUtil.kr_i('🎉 成功找到全局auto选中节点: tag=${selectedItem.tag}, delay=${selectedItem.urlTestDelay}', tag: 'HomeController');
|
|
||||||
|
|
||||||
return {
|
|
||||||
'tag': selectedNode,
|
|
||||||
'delay': selectedItem.urlTestDelay,
|
|
||||||
'country': '',
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
KRLogUtil.kr_e('❌ 在全局auto组内未找到选中节点: $selectedNode', tag: 'HomeController');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
KRLogUtil.kr_w('⚠️ 全局auto选中节点为空或无效', tag: 'HomeController');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KRLogUtil.kr_w('❌ 未找到全局auto组', tag: 'HomeController');
|
|
||||||
} catch (e) {
|
|
||||||
KRLogUtil.kr_e('💥 获取全局auto选中节点异常: $e', tag: 'HomeController');
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 提取选中节点信息的辅助方法
|
||||||
|
Map<String, dynamic>? _kr_extractSelectedNodeInfo(dynamic group, String countryCode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已移除:全局 auto 选中节点信息获取(统一为手动选择模式)
|
||||||
|
|
||||||
/// 获取指定国家的所有真实节点延迟列表
|
/// 获取指定国家的所有真实节点延迟列表
|
||||||
List<Map<String, dynamic>> kr_getCountryRealNodeDelays(String countryCode) {
|
List<Map<String, dynamic>> kr_getCountryRealNodeDelays(String countryCode) {
|
||||||
final delays = <Map<String, dynamic>>[];
|
final delays = <Map<String, dynamic>>[];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 从订阅服务中获取该国家的节点列表
|
// 从订阅服务中获取该国家的节点列表
|
||||||
final countryNodes = kr_subscribeService.keyList.values
|
final countryNodes = kr_subscribeService.keyList.values
|
||||||
.where((item) => item.country == countryCode && !item.tag.endsWith('-auto'))
|
.where((item) => item.country == countryCode)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
for (final node in countryNodes) {
|
for (final node in countryNodes) {
|
||||||
delays.add({
|
delays.add({
|
||||||
'tag': node.tag,
|
'tag': node.tag,
|
||||||
@ -2407,13 +2203,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
'city': node.city,
|
'city': node.city,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按延迟排序
|
// 按延迟排序
|
||||||
delays.sort((a, b) => a['delay'].compareTo(b['delay']));
|
delays.sort((a, b) => a['delay'].compareTo(b['delay']));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
KRLogUtil.kr_e('获取国家真实节点延迟列表失败: $e', tag: 'HomeController');
|
KRLogUtil.kr_e('获取国家真实节点延迟列表失败: $e', tag: 'HomeController');
|
||||||
}
|
}
|
||||||
|
|
||||||
return delays;
|
return delays;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2432,24 +2228,11 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
if (group.type == ProxyType.selector) {
|
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');
|
||||||
|
|
||||||
// 如果是auto模式,从urltest组获取延迟
|
for (var item in group.items) {
|
||||||
if (kr_cutTag.value.endsWith('auto')) {
|
if (item.tag == kr_cutTag.value && item.urlTestDelay != 0) {
|
||||||
for (var item in group.items) {
|
kr_currentNodeLatency.value = item.urlTestDelay;
|
||||||
if (item.tag == "auto" && item.urlTestDelay != 0) {
|
KRLogUtil.kr_i('✅ 延迟值: ${item.urlTestDelay}ms', tag: 'HomeController');
|
||||||
kr_currentNodeLatency.value = item.urlTestDelay;
|
return true;
|
||||||
KRLogUtil.kr_i('✅ auto模式延迟值: ${item.urlTestDelay}ms', tag: 'HomeController');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 手动选择模式
|
|
||||||
else {
|
|
||||||
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');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
|||||||
|
|
||||||
print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
||||||
|
|
||||||
final isShow = isConnected; // delay == -1 || isConnected;
|
final isShow = delay == -1 || isConnected;
|
||||||
|
|
||||||
final Color buttonColor = Theme.of(context).primaryColor;
|
final Color buttonColor = Theme.of(context).primaryColor;
|
||||||
final double screenWidth = Get.width;
|
final double screenWidth = Get.width;
|
||||||
|
|||||||
@ -586,6 +586,7 @@
|
|||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Profile;
|
name = Profile;
|
||||||
@ -720,6 +721,7 @@
|
|||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
@ -748,6 +750,7 @@
|
|||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user