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

This commit is contained in:
speakeloudest 2025-11-11 01:29:16 -08:00
parent a884e6c838
commit 748ec6bee9
3 changed files with 172 additions and 103 deletions

View File

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

View File

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

View File

@ -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');
}
});*/
}