diff --git a/android/app/build.gradle b/android/app/build.gradle
index cc5255a..782d479 100755
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -57,7 +57,7 @@ android {
defaultConfig {
applicationId "app.hifastvpn.com"
minSdkVersion flutter.minSdkVersion
- targetSdkVersion 36
+ targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 147943f..d94a247 100755
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -34,6 +34,11 @@
+
+
+
+
+
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();
protocol = nodeListItem.protocol;
latitude = nodeListItem.latitude;
@@ -56,167 +110,165 @@ class KROutboundItem {
return;
}
- // 兜底:尝试解析 config 字段(旧API格式)
- if (nodeListItem.config.isEmpty) {
- if (kDebugMode) {
- print('❌ 节点 ${nodeListItem.name} 缺少配置信息(无port或config)');
- }
- config = {};
- return;
- }
-
- late Map json;
- try {
- json = jsonDecode(nodeListItem.config) as Map;
- } 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? ?? {};
-
- // 智能设置 server_name
- String serverName = securityConfig["sni"] ?? "";
- if (serverName.isEmpty) {
- serverName = nodeListItem.serverAddr;
+ // 兜底:尝试从 config 字段解析(旧API格式)
+ if (nodeListItem.config.isNotEmpty) {
+ try {
+ final json = jsonDecode(nodeListItem.config) as Map;
+ if (kDebugMode) {
+ print('📄 解析到 config JSON: $json');
}
- config = {
- "type": "vless",
- "tag": nodeListItem.name,
- "server": nodeListItem.serverAddr,
- "server_port": json["port"],
- "uuid": nodeListItem.uuid,
- if (json["flow"] != null && json["flow"] != "none")
- "flow": json["flow"],
- 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": securityConfig["fingerprint"] ?? "chrome"
+ // 提取 transport 配置
+ Map? transportConfig;
+ if (json['transport'] != null && json['transport'] != 'tcp') {
+ transportConfig = _buildTransport(json);
+ if (kDebugMode) {
+ print('✅ 找到 transport 配置: $transportConfig');
+ }
+ }
+
+ // 提取 security_config
+ Map? securityConfig;
+ if (json['security_config'] != null) {
+ securityConfig = json['security_config'] as Map;
+ if (kDebugMode) {
+ print('✅ 找到 security_config: $securityConfig');
+ }
+ }
+
+ // 根据协议类型构建配置
+ 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? ?? {};
+ 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}');
}
- }
- };
- break;
- case "vmess":
- final securityConfig =
- json["security_config"] as Map? ?? {};
-
- // 智能设置 server_name
- String serverName = securityConfig["sni"] ?? "";
- if (serverName.isEmpty) {
- serverName = nodeListItem.serverAddr;
+ config = {};
}
- config = {
- "type": "vmess",
- "tag": nodeListItem.name,
- "server": nodeListItem.serverAddr,
- "server_port": json["port"],
- "uuid": nodeListItem.uuid,
- "alter_id": 0,
- "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"}
+ // 检查 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
}
- };
- 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? ?? {};
- 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? ?? {};
-
- // 智能设置 server_name
- String serverName = securityConfig["sni"] ?? "";
- if (serverName.isEmpty) {
- // 如果没有配置 SNI,使用服务器地址
- serverName = nodeListItem.serverAddr;
}
-
- config = {
- "type": "trojan",
- "tag": nodeListItem.name,
- "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
+ } catch (e) {
+ if (kDebugMode) {
+ print('⚠️ 解析 config 字段失败: $e');
+ }
+ config = {};
}
}
- // 解析配置
}
@override
String toString() {
- // 现在它可以正确地访问已保存的 nodeListItem 成员
return 'KROutboundItem(name: ${nodeListItem.name}, protocol: ${nodeListItem.protocol}, server: ${nodeListItem.serverAddr}, port: ${nodeListItem.port})';
}
+
/// 构建传输配置
Map _buildTransport(Map json) {
final transportType = json["transport"] as String?;
@@ -252,27 +304,6 @@ class KROutboundItem {
if (kDebugMode) {
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 配置
Map? transportConfig;
diff --git a/lib/app/model/business/kr_outbounds_list.dart b/lib/app/model/business/kr_outbounds_list.dart
index c7279af..41853a6 100755
--- a/lib/app/model/business/kr_outbounds_list.dart
+++ b/lib/app/model/business/kr_outbounds_list.dart
@@ -88,6 +88,9 @@ class KrOutboundsList {
}
}
+ // 生成国家自动选择虚拟节点
+ _generateCountryAutoNodes(countryGroups);
+
// 将标签分组转换为 KRGroupOutboundList 并添加到 groupOutboundList
for (var tag in tagGroups.keys) {
final item = KRGroupOutboundList(
@@ -108,7 +111,40 @@ class KrOutboundsList {
country: country,
outboundList: countryGroups[country]!)); // 添加国家分组到列表
}
+ }
+ /// 生成国家自动选择虚拟节点
+ void _generateCountryAutoNodes(Map> countryGroups) {
+ for (var entry in countryGroups.entries) {
+ final country = entry.key;
+ final nodes = entry.value;
+ final autoTag = '${country}-auto';
+ if (kDebugMode) {
+ print('🤖 生成国家自动选择节点: $autoTag, 包含 ${nodes.length} 个节点');
+ }
+
+ // 构建 urltest 配置
+ final urltestConfig = {
+ 'type': 'urltest',
+ 'tag': autoTag,
+ 'outbounds': nodes.map((node) => node.tag).toList(),
+ 'url': 'https://www.google.com/generate_204',
+ 'interval': '10m',
+ 'tolerance': 50,
+ };
+
+ // 创建虚拟节点
+ final virtualNode = KROutboundItem.fromVirtual(autoTag, country, urltestConfig);
+
+ // 添加到各个列表
+ allList.add(virtualNode);
+ keyList[autoTag] = virtualNode;
+ configJsonList.add(urltestConfig);
+
+ if (kDebugMode) {
+ print('✅ 生成虚拟节点: $autoTag, 配置: ${urltestConfig.toString()}');
+ }
+ }
}
}
diff --git a/lib/app/modules/hi_node_list/controllers/hi_node_list_controller.dart b/lib/app/modules/hi_node_list/controllers/hi_node_list_controller.dart
index 2e6c79e..a5e41d9 100644
--- a/lib/app/modules/hi_node_list/controllers/hi_node_list_controller.dart
+++ b/lib/app/modules/hi_node_list/controllers/hi_node_list_controller.dart
@@ -12,6 +12,15 @@ class HINodeListController extends GetxController {
/// 首页服务
final KRHomeController homeController = Get.find();
+ /// 调试模式状态
+ final RxBool isDebugMode = false.obs;
+
+ /// 模式按钮点击计数器
+ int modeButtonClickCount = 0;
+
+ /// 最后一次点击时间
+ DateTime? lastModeButtonClickTime;
+
/// 获取连接类型字符串
String kr_getConnectionTypeString() {
final connectionType = KRSingBoxImp.instance.kr_connectionType.value;
@@ -41,4 +50,48 @@ class HINodeListController extends GetxController {
}
}
+ /// 处理模式按钮点击(用于激活调试模式)
+ void kr_handleModeButtonClick() {
+ final now = DateTime.now();
+
+ // 检查是否在2秒内连续点击
+ if (lastModeButtonClickTime != null &&
+ now.difference(lastModeButtonClickTime!).inSeconds > 2) {
+ // 超过2秒,重置计数器
+ modeButtonClickCount = 0;
+ }
+
+ modeButtonClickCount++;
+ lastModeButtonClickTime = now;
+
+ KRLogUtil.kr_i('模式按钮点击次数: $modeButtonClickCount', tag: 'HINodeListController');
+
+ if (modeButtonClickCount >= 5) {
+ // 激活调试模式
+ isDebugMode.value = true;
+ modeButtonClickCount = 0; // 重置计数器
+ KRLogUtil.kr_i('🐛 调试模式已激活!', tag: 'HINodeListController');
+
+ }
+ }
+
+ /// 获取要显示的节点列表(根据调试模式过滤)
+ List 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() {
+ isDebugMode.value = false;
+ modeButtonClickCount = 0;
+ lastModeButtonClickTime = null;
+ KRLogUtil.kr_i('调试模式已重置', tag: 'HINodeListController');
+ }
+
}
diff --git a/lib/app/modules/hi_node_list/views/hi_node_list_view.dart b/lib/app/modules/hi_node_list/views/hi_node_list_view.dart
index 9cc1f35..86044e4 100755
--- a/lib/app/modules/hi_node_list/views/hi_node_list_view.dart
+++ b/lib/app/modules/hi_node_list/views/hi_node_list_view.dart
@@ -231,8 +231,9 @@ class HINodeListView extends GetView {
padding: EdgeInsets.symmetric(vertical: 8.w),
// 2. 使用 children 属性,并一次性构建所有列表项
children: [
- if (controller.kr_subscribeService.allList.isEmpty)
- _buildEmptyListPlaceholder(context, AppTranslations.kr_home.noNodes)
+ if (controller.kr_getFilteredNodeList().isEmpty)
+ _buildEmptyListPlaceholder(context,
+ controller.isDebugMode.value ? '调试模式:无节点数据' : AppTranslations.kr_home.noNodes)
else ...[
InkWell(
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
@@ -292,13 +293,28 @@ class HINodeListView extends GetView {
),
),
// 2. 第二个 Text: "根据网络IP自动匹配最快线路"
- Text(
- '根据网络IP自动匹配最快线路', // 您指定的文本
- style: KrAppTextStyle(
- fontSize: 10,
- color: Colors.white,
- ),
- ),
+ Obx(() {
+ // 当选择全局 auto 时,显示当前选中的节点信息
+ if (controller.homeController.kr_cutTag.value == 'auto') {
+ final autoNodeInfo = controller.homeController.kr_getGlobalAutoSelectedNode();
+ if (autoNodeInfo != null) {
+ return Text(
+ '当前: ${autoNodeInfo['tag']} (${autoNodeInfo['delay']}ms)',
+ style: KrAppTextStyle(
+ fontSize: 10,
+ color: Colors.white.withOpacity(0.8),
+ ),
+ );
+ }
+ }
+ return Text(
+ '根据网络IP自动匹配最快线路', // 默认文本
+ style: KrAppTextStyle(
+ fontSize: 10,
+ color: Colors.white,
+ ),
+ );
+ }),
],
),
),
@@ -319,7 +335,7 @@ class HINodeListView extends GetView {
),
),
),
- ...controller.kr_subscribeService.allList.map((item) {
+ ...controller.kr_getFilteredNodeList().map((item) {
return InkWell(
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
onTap: () async {
@@ -390,78 +406,75 @@ class HINodeListView extends GetView {
KRCountryFlag(countryCode: item.country, width: 30.w, height: 20.w, isCircle: false, maintainSize: false),
SizedBox(width: 12.w),
Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Flexible(
- child: Text(
- '${controller.homeController.kr_getCountryFullName(item.country)}-${item.tag}',
- style: KrAppTextStyle(fontSize: 14, color: Colors.white),
- overflow: TextOverflow.ellipsis,
- maxLines: 1,
- ),
- ),
- Obx(() => controller.homeController.kr_cutTag.value == item.tag
- ? Container(
- margin: EdgeInsets.only(left: 4.w),
- padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.w),
- decoration: BoxDecoration(
- color: krModernGreenLight.withOpacity(0.1),
- borderRadius: BorderRadius.circular(4.w),
- ),
- child: Text(
- AppTranslations.kr_home.selected,
- style: KrAppTextStyle(fontSize: 10, color: krModernGreen, fontWeight: FontWeight.w500),
- ),
- )
- : const SizedBox.shrink()),
- ],
+ child: Obx(() {
+ final isDebug = controller.isDebugMode.value;
+
+ final text = isDebug
+ ? '${controller.homeController.kr_getCountryFullName(item.country)} - ${item.tag}'
+ : item.country;
+
+ return Text(
+ text,
+ style: KrAppTextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: Colors.white,
),
- SizedBox(height: 2.w),
- Obx(() {
- // 1. 获取延迟值和测速状态
- int displayDelay = item.urlTestDelay.value;
- bool isTesting = controller.homeController.kr_isLatency.value;
-
- // 2. 声明文本和颜色变量
- String delayText;
- Color delayColor;
-
- // 3. 根据状态设置文本和颜色
- if (isTesting && displayDelay == 0) {
- delayText = '测速中...';
- delayColor = Colors.grey; // 测速时使用灰色
- } else if (displayDelay == 0) {
- delayText = '- ms'; // 未测速或初始状态
- delayColor = Colors.grey;
- } else if (displayDelay >= 3000) {
- delayText = AppTranslations.kr_home.timeout; // "超时"
- delayColor = Colors.red; // 超时状态使用红色
- } else {
- delayText = '${displayDelay}ms';
- // 正常延迟,根据快慢设置不同颜色
- delayColor = (displayDelay < 500) ? krModernGreen : Colors.orange;
- }
-
- // 4. 返回最终的 Text 组件
- return Text(
- delayText,
- style: KrAppTextStyle(
- fontSize: 10,
- color: delayColor, // 使用动态计算出的颜色
- fontWeight: FontWeight.w500,
- ),
- );
- }),
- Text(
- item.city,
- style: KrAppTextStyle(fontSize: 12, color: Theme.of(context).textTheme.bodySmall?.color),
- ),
- ],
- ),
+ );
+ }),
),
+ Obx(() {
+ // 1. 获取延迟值和测速状态
+ int displayDelay;
+ bool isTesting = controller.homeController.kr_isLatency.value;
+
+ // 极简逻辑:只根据节点类型决定显示方式
+ print('🔄 _kr_buildNodeListItem节点: item.tag=${item.tag}, country=${item.country}');
+
+ // 如果节点本身是auto组节点,显示组内最快节点的速度
+ if (item.tag.endsWith('-auto')) {
+ print('🎯 检测到auto组节点,获取最快节点信息');
+ final autoNodeInfo = controller.homeController.kr_getCountryAutoSelectedNode(item.country);
+ print('📊 _kr_buildNodeListItem获取auto最快节点: $autoNodeInfo');
+ displayDelay = autoNodeInfo?['delay'] ?? 0;
+ print('✅ _kr_buildNodeListItem使用auto最快节点延迟: $displayDelay');
+ } else {
+ // 普通节点,直接显示节点自身的速度
+ displayDelay = item.urlTestDelay.value;
+ print('🔄 _kr_buildNodeListItem使用普通节点延迟: $displayDelay');
+ }
+
+ // 2. 声明文本和颜色变量
+ String delayText;
+ Color delayColor;
+
+ // 3. 根据状态设置文本和颜色
+ if (isTesting && displayDelay == 0) {
+ delayText = '测速中...';
+ delayColor = Colors.grey; // 测速时使用灰色
+ } else if (displayDelay == 0) {
+ delayText = '- ms'; // 未测速或初始状态
+ delayColor = Colors.grey;
+ } else if (displayDelay >= 3000) {
+ delayText = AppTranslations.kr_home.timeout; // "超时"
+ delayColor = Colors.red; // 超时状态使用红色
+ } else {
+ delayText = '${displayDelay}ms';
+ // 正常延迟,根据快慢设置不同颜色
+ delayColor = (displayDelay < 500) ? krModernGreen : Colors.orange;
+ }
+
+ // 4. 返回最终的 Text 组件
+ return Text(
+ delayText,
+ style: KrAppTextStyle(
+ fontSize: 10,
+ color: delayColor, // 使用动态计算出的颜色
+ fontWeight: FontWeight.w500,
+ ),
+ );
+ }),
+ SizedBox(width: 12.w),
Obx(() => controller.homeController.kr_cutTag.value == item.tag
? KrLocalImage(
imageName: 'radio-active-icon',
diff --git a/lib/app/modules/hi_node_list/views/hi_page_node_view.dart b/lib/app/modules/hi_node_list/views/hi_page_node_view.dart
index 3f80e09..9f36e50 100644
--- a/lib/app/modules/hi_node_list/views/hi_page_node_view.dart
+++ b/lib/app/modules/hi_node_list/views/hi_page_node_view.dart
@@ -23,7 +23,49 @@ class HINodePageView extends GetView {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- // 模式切换器
+ Positioned(
+ left: 0, // ⭐ 所有内容整体贴到左边
+ child: Obx(() => controller.isDebugMode.value
+ ? GestureDetector(
+ onTap: () {
+ controller.kr_resetDebugMode();
+ Get.snackbar(
+ '调试模式',
+ '已关闭调试模式',
+ snackPosition: SnackPosition.BOTTOM,
+ duration: const Duration(seconds: 2),
+ );
+ },
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w),
+ decoration: BoxDecoration(
+ color: Colors.red.withOpacity(0.8),
+ borderRadius: BorderRadius.circular(12.w),
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(
+ Icons.bug_report,
+ size: 12.w,
+ color: Colors.white,
+ ),
+ SizedBox(width: 3.w),
+ Text(
+ '关闭调试',
+ style: TextStyle(
+ fontSize: 10.sp,
+ color: Colors.white,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ],
+ ),
+ ),
+ )
+ : const SizedBox.shrink()),
+ ),
+ // 模式切换器
Padding(
padding: EdgeInsets.only(left: 100.w, right: 60.w),
child: Row(
@@ -159,7 +201,12 @@ class HINodePageView extends GetView {
}) {
return Expanded(
child: GestureDetector(
- onTap: onTap,
+ onTap: () {
+ // 处理模式按钮点击(用于调试模式激活)
+ controller.kr_handleModeButtonClick();
+ // 执行原有的点击逻辑
+ onTap();
+ },
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: EdgeInsets.symmetric(vertical: 4.w),
diff --git a/lib/app/modules/kr_home/controllers/kr_home_controller.dart b/lib/app/modules/kr_home/controllers/kr_home_controller.dart
index f2eaed5..b1d539c 100755
--- a/lib/app/modules/kr_home/controllers/kr_home_controller.dart
+++ b/lib/app/modules/kr_home/controllers/kr_home_controller.dart
@@ -18,6 +18,7 @@ import '../../../common/app_config.dart';
import '../../../localization/app_translations.dart';
import '../../../localization/kr_language_utils.dart';
import '../../../model/business/kr_group_outbound_list.dart';
+import '../../../model/business/kr_outbound_item.dart';
import '../../../services/kr_announcement_service.dart';
import '../../../utils/kr_event_bus.dart';
import '../../../utils/kr_update_util.dart';
@@ -1421,8 +1422,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
/// 获取真实连接的节点信息(auto 模式下获取实际连接的节点)
Map kr_getRealConnectedNodeInfo() {
- // 如果不是 auto 模式,直接返回当前选中的节点信息
- if (kr_cutTag.value != 'auto') {
+ // 如果不是 auto 模式,也不是 country-auto 模式,直接返回当前选中的节点信息
+ if (kr_cutTag.value != 'auto' && !kr_cutTag.value.endsWith('-auto')) {
final node = kr_subscribeService.keyList[kr_cutSeletedTag.value];
return {
'nodeName': kr_cutSeletedTag.value,
@@ -1431,11 +1432,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
};
}
- // auto 模式下,获取 urltest 组的实际连接节点
+ // 处理 auto 模式(包括全局 auto 和 country-auto)
print('当前活动组----${KRSingBoxImp.instance.kr_activeGroups.length}');
for (var group in KRSingBoxImp.instance.kr_activeGroups) {
print('当前活动组----$group}');
- if (group.type == ProxyType.urltest) {
+
+ // 处理全局 auto 模式
+ if (kr_cutTag.value == 'auto' && group.type == ProxyType.urltest && group.tag == 'auto') {
final selectedNode = group.selected;
final node = kr_subscribeService.keyList[selectedNode];
return {
@@ -1444,12 +1447,39 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
'country': node?.country ?? '',
};
}
+
+ // 处理 country-auto 模式
+ if (kr_cutTag.value.endsWith('-auto') && group.type == ProxyType.urltest) {
+ if (group.tag == kr_cutTag.value) {
+ final selectedNode = group.selected;
+ final node = kr_subscribeService.keyList[selectedNode];
+ return {
+ 'nodeName': selectedNode,
+ 'delay': node?.urlTestDelay.value ?? -2,
+ 'country': node?.country ?? '',
+ };
+ }
+ }
}
print('hhhhhh${kr_subscribeService.keyList}', );
+ // 处理 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 {
- 'nodeName': 'auto',
+ 'nodeName': kr_cutTag.value,
'delay': -2,
'country': '',
};
@@ -1470,9 +1500,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
/// 获取真实连接的节点国家
String kr_getRealConnectedNodeCountry() {
final info = kr_getRealConnectedNodeInfo();
- final delay = kr_currentNodeLatency.value;
+ final delay = info['delay'] as int; // 使用真实连接的节点延迟,而不是 kr_currentNodeLatency.value
final country1 = kr_getCurrentNodeCountry();
print('country----$country1');
+ print('kr_getRealConnectedNodeCountry - delay from info: $delay, country from info: ${info['country']}');
final country = kr_getCountryFullName(info['country']);
if (delay == -2) {
return '--';
@@ -2181,6 +2212,225 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
});
}
+ /// 获取国家-auto组的当前选中子节点信息
+ Map? 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;
+ }
+
+ /// 从订阅服务获取该国家最快节点的备用方案
+ Map? _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;
+ if (delay > 0 && delay < 3000 && 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? _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? kr_getGlobalAutoSelectedNode() {
+ 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;
+ }
+
+ /// 获取指定国家的所有真实节点延迟列表
+ List