feat: 提交国家选择
Build Windows / build (push) Successful in 7h7m43s
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m1s

This commit is contained in:
2025-11-11 01:29:16 -08:00
parent a884e6c838
commit 748ec6bee9
3 changed files with 172 additions and 103 deletions
@@ -48,17 +48,9 @@ class HINodeListView extends GetView<HINodeListController> {
) {
if (outboundList.isEmpty) return 0;
// 过滤掉不可用节点(延迟标记为 65535 或负数)
final validNodes = outboundList.where((node) {
final delay = node.urlTestDelay.value;
return delay > 0 && delay < 65535;
}).toList();
if (validNodes.isEmpty) return 0;
// 返回最小延迟值
validNodes.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value));
return validNodes.first.urlTestDelay.value;
outboundList.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value));
return outboundList.first.urlTestDelay.value;
}
/// 找出延迟最低(最快)的节点对象
@@ -88,8 +80,8 @@ class HINodeListView extends GetView<HINodeListController> {
// 并设置透明背景,让父组件的背景可以透出来
return Material(
color: Colors.transparent,
child: _buildSubscribeList(context)
// child: _kr_buildRegionList(context)
// child: _buildSubscribeList(context)
child: _kr_buildRegionList(context)
);
}
@@ -117,7 +109,7 @@ class HINodeListView extends GetView<HINodeListController> {
onTap: () async {
try {
final success =
await controller.homeController.kr_performNodeSwitch('auto');
await controller.homeController.kr_performCountrySwitch('auto');
if (success) {
controller.homeController.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
@@ -199,9 +191,18 @@ class HINodeListView extends GetView<HINodeListController> {
),
...controller.kr_subscribeService.countryOutboundList.map((country) {
return InkWell(
onTap: () {
// 自动选择这个国家下的节点延迟中最快的
controller.homeController.onCountrySelected(country.country);
onTap: () async {
try {
final success =
await controller.homeController.kr_performCountrySwitch(country.country);
if (success) {
controller.homeController.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
}
} catch (e) {
KRLogUtil.kr_e('Auto选项切换异常: $e',
tag: 'NodeListView');
}
},
child: _kr_buildCountryListItem(context, country: country),
);
@@ -403,13 +404,36 @@ class HINodeListView extends GetView<HINodeListController> {
),
SizedBox(height: 2.w),
Obx(() {
// 2. 获取用于显示的延迟值
final int delay = _getDisplayDelay(controller, item);
// 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(
'${delay}ms',
delayText,
style: KrAppTextStyle(
fontSize: 10,
color: krModernGreen,
color: delayColor, // 使用动态计算出的颜色
fontWeight: FontWeight.w500,
),
);
@@ -441,18 +465,7 @@ class HINodeListView extends GetView<HINodeListController> {
/// 构建国家列表项的UI
Widget _kr_buildCountryListItem(BuildContext context, {required country}) {
// 获取延迟颜色
Color getLatencyColor(int delay) {
if (delay == 0) {
return Colors.transparent;
} else if (delay < 500) {
return krModernGreen;
} else if (delay < 3000) {
return Color(0xFFFFB700); // 使用更容易看清的黄色
} else {
return Colors.red;
}
}
return Container(
key: ValueKey(country),
decoration: BoxDecoration(
@@ -476,16 +489,36 @@ class HINodeListView extends GetView<HINodeListController> {
),
),
Obx(() {
final int delay = getFastestNodeDelay(controller, country.outboundList);
// 1. 获取延迟值和测速状态
int displayDelay = getFastestNodeDelay(controller, country.outboundList);
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(
delay == 0
? ''
: delay >= 3000
? AppTranslations.kr_home.timeout
: '${delay}ms',
delayText,
style: KrAppTextStyle(
fontSize: 10,
color: getLatencyColor(delay),
color: delayColor, // 使用动态计算出的颜色
fontWeight: FontWeight.w500,
),
);
@@ -1275,11 +1275,108 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
}
}
Future<void> onCountrySelected(String countryTag) async {
await KRSingBoxImp.instance.kr_selectCountry(countryTag);
KRCommonUtil.kr_showToast('已切换到 $countryTag 节点组');
/// 🌍 执行国家切换(包含UI同步与VPN热重载)
/// 返回 true 表示切换成功,false 表示失败
Future<bool> kr_performCountrySwitch(String countryTag) async {
try {
KRLogUtil.kr_i('🔄 开始切换国家: $countryTag', tag: 'HomeController');
// 1. 保存原国家,以备失败恢复
final originalCountry = kr_coutryText.value;
// 2. 设置切换中状态
kr_coutryText.value = countryTag;
kr_currentNodeName.value = countryTag; // UI显示当前国家
// 3. 获取该国家的节点列表
final countryData = kr_subscribeService.countryOutboundList
.firstWhereOrNull((c) => c.country.toUpperCase() == countryTag.toUpperCase());
if (countryData == null || countryData.outboundList.isEmpty) {
KRLogUtil.kr_w('⚠️ 未找到国家 [$countryTag] 的节点列表', tag: 'HomeController');
KRCommonUtil.kr_showToast('该国家暂无可用节点');
return false;
}
// 取第一个节点作为默认节点(后续由 sing-box 自动在该组内切换)
final defaultNode = countryData.outboundList.first.tag;
KRLogUtil.kr_i(
'📊 国家 [$countryTag] 包含 ${countryData.outboundList.length} 个节点,默认节点: $defaultNode',
tag: 'HomeController',
);
// 4. 如果VPN未连接,只更新UI变量即可
if (!kr_isConnected.value) {
KRLogUtil.kr_i('📴 VPN未连接,只更新UI变量: $countryTag', tag: 'HomeController');
kr_cutSeletedTag.value = defaultNode;
kr_cutTag.value = defaultNode;
kr_currentNodeLatency.value = -2;
// 保存国家选择与默认节点
await KRSecureStorage().kr_saveData(key: 'SELECTED_COUNTRY_TAG', value: countryTag);
await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: defaultNode);
KRLogUtil.kr_i('✅ 已保存国家选择 [$countryTag] (未连接状态)', tag: 'HomeController');
return true;
}
// 5. VPN已连接状态,执行完整重载逻辑
try {
KRLogUtil.kr_i('🔌 VPN已连接,准备切换国家 [$countryTag]', tag: 'HomeController');
// 显示连接中
kr_currentNodeLatency.value = -1;
kr_isLatency.value = true;
// 保存选择
await KRSecureStorage().kr_saveData(key: 'SELECTED_COUNTRY_TAG', value: countryTag);
await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: defaultNode);
// 🔧 调用 SingBox 层逻辑切换国家分组(稍后在 kr_sing_box_imp.dart 实现)
KRLogUtil.kr_i('🧩 调用 sing-box 切换国家分组逻辑: $countryTag', tag: 'HomeController');
await KRSingBoxImp.instance.kr_selectCountry(countryTag);
// 🔁 停止并重启 VPN
await KRSingBoxImp.instance.kr_stop();
KRLogUtil.kr_i('⏳ 等待VPN停止(1500ms', tag: 'HomeController');
await Future.delayed(const Duration(milliseconds: 1500));
await KRSingBoxImp.instance.kr_start();
KRLogUtil.kr_i('⏳ 等待VPN启动(2500ms', tag: 'HomeController');
await Future.delayed(const Duration(milliseconds: 2500));
// ✅ 切换成功,更新UI
kr_cutSeletedTag.value = defaultNode;
kr_cutTag.value = defaultNode;
kr_updateConnectionInfo();
// 更新延迟信息
_kr_updateLatencyOnConnected();
KRLogUtil.kr_i('✅ 国家切换成功: $countryTag', tag: 'HomeController');
return true;
} catch (switchError) {
KRLogUtil.kr_e('❌ 国家切换失败: $switchError', tag: 'HomeController');
// 恢复原国家状态
kr_coutryText.value = originalCountry;
await KRSecureStorage().kr_saveData(key: 'SELECTED_COUNTRY_TAG', value: originalCountry ?? '');
KRCommonUtil.kr_showToast('切换国家失败,已恢复为: ${originalCountry ?? ""}');
return false;
}
} catch (e) {
KRLogUtil.kr_e('❌ 国家切换异常: $e', tag: 'HomeController');
KRCommonUtil.kr_showToast('国家切换异常,请重试');
return false;
} finally {
kr_isLatency.value = false;
KRLogUtil.kr_i('🔄 国家切换流程完成', tag: 'HomeController');
}
}
/// 🔧 修复:简化的 kr_selectNode 方法
/// 现在只是委托给新的 kr_performNodeSwitch 方法
/// 为了保持向后兼容,保留此方法但改为调用新方法
@@ -1482,67 +1482,6 @@ class KRSingBoxImp {
return;
}
/*final selectGroup = kr_activeGroups.firstWhere(
(group) => group.tag == 'select',
orElse: () => throw Exception('未找到 "select" 组'),
);
final countryNodes = selectGroup.items.where((item) {
final nodeCountry = item.options?['country'] ?? '';
return nodeCountry.toString().toLowerCase() == country.toLowerCase();
}).toList();
if (countryNodes.isEmpty) {
KRLogUtil.kr_w('⚠️ 未找到该国家的节点: $country', tag: 'SingBox');
for (var item in selectGroup.items) {
KRLogUtil.kr_d(
' 可用节点: ${item.tag} (${item.options?['country'] ?? '未知'})',
tag: 'SingBox',
);
}
return;
}
KRLogUtil.kr_i('🇨🇭 找到 ${countryNodes.length} 个属于 "$country" 的节点', tag: 'SingBox');
// 按延迟排序(无延迟则 9999)
countryNodes.sort((a, b) =>
(a.urlTestDelay ?? 9999).compareTo(b.urlTestDelay ?? 9999));
final fastestNode = countryNodes.first;
final selectedTag = fastestNode.tag;
final delay = fastestNode.urlTestDelay ?? -1;
KRLogUtil.kr_i('✅ 选中该国家最快节点: $selectedTag (${delay}ms)', tag: 'SingBox');
try {
await KRSecureStorage().kr_saveData(key: 'selected_country', value: country);
await KRSecureStorage().kr_saveData(key: _keySelectedNode, value: selectedTag);
} catch (e) {
KRLogUtil.kr_w('⚠️ 保存国家/节点信息失败: $e', tag: 'SingBox');
}
try {
await _kr_ensureCommandClientInitialized();
await _kr_selectOutboundWithRetry('select', selectedTag,
maxAttempts: 3, initialDelay: 100);
KRLogUtil.kr_i('✅ 国家内节点切换成功: $selectedTag', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('❌ 国家内节点选择失败: $e', tag: 'SingBox');
rethrow;
}
_nodeSelectionTimer?.cancel();
KRLogUtil.kr_i('🔁 启动定时器防止节点被 auto 覆盖', tag: 'SingBox');
_nodeSelectionTimer =
Timer.periodic(const Duration(seconds: 20), (timer) async {
try {
await kr_singBox.selectOutbound('select', selectedTag).run();
KRLogUtil.kr_d('🔁 定时确认国家内节点: $selectedTag', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_w('🔁 定时确认失败: $e', tag: 'SingBox');
}
});*/
}