feat: 节点测速调整

This commit is contained in:
speakeloudest 2025-11-26 19:23:15 -08:00
parent 909020654f
commit b059d01556
6 changed files with 513 additions and 740 deletions

View File

@ -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;

View File

@ -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,15 +41,6 @@ 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);
});
}
} }
/// ///
@ -65,27 +57,18 @@ class HINodeListController extends GetxController with WidgetsBindingObserver {
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();
} }
} }

View File

@ -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,19 +543,34 @@ 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,
);
}),
], ],
), ),
); );

View File

@ -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();
}); });
@ -699,6 +695,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
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,6 +748,8 @@ 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 = "--";
@ -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(); // VPNDNS恢复
// 🚀 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(); // VPNDNS备份
// 🚀 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';
// }
} }
@ -2197,199 +2169,23 @@ 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) { Map<String, dynamic>? _kr_getFastestNodeFromSubscribeService(String countryCode) {
try { return null;
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) { 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() {
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;
} }
// 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>>[];
@ -2397,7 +2193,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
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) {
@ -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;
}
} }
} }
} }

View File

@ -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;

View File

@ -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;