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; 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)); outboundList.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value));
return validNodes.first.urlTestDelay.value; return outboundList.first.urlTestDelay.value;
} }
/// ///
@ -88,8 +80,8 @@ class HINodeListView extends GetView<HINodeListController> {
// //
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: _buildSubscribeList(context) // child: _buildSubscribeList(context)
// child: _kr_buildRegionList(context) child: _kr_buildRegionList(context)
); );
} }
@ -117,7 +109,7 @@ class HINodeListView extends GetView<HINodeListController> {
onTap: () async { onTap: () async {
try { try {
final success = final success =
await controller.homeController.kr_performNodeSwitch('auto'); await controller.homeController.kr_performCountrySwitch('auto');
if (success) { if (success) {
controller.homeController.kr_currentListStatus.value = controller.homeController.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none; KRHomeViewsListStatus.kr_none;
@ -199,9 +191,18 @@ class HINodeListView extends GetView<HINodeListController> {
), ),
...controller.kr_subscribeService.countryOutboundList.map((country) { ...controller.kr_subscribeService.countryOutboundList.map((country) {
return InkWell( return InkWell(
onTap: () { onTap: () async {
// try {
controller.homeController.onCountrySelected(country.country); 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), child: _kr_buildCountryListItem(context, country: country),
); );
@ -403,13 +404,36 @@ class HINodeListView extends GetView<HINodeListController> {
), ),
SizedBox(height: 2.w), SizedBox(height: 2.w),
Obx(() { Obx(() {
// 2. // 1.
final int delay = _getDisplayDelay(controller, item); 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( return Text(
'${delay}ms', delayText,
style: KrAppTextStyle( style: KrAppTextStyle(
fontSize: 10, fontSize: 10,
color: krModernGreen, color: delayColor, // 使
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
); );
@ -441,18 +465,7 @@ class HINodeListView extends GetView<HINodeListController> {
/// UI /// UI
Widget _kr_buildCountryListItem(BuildContext context, {required country}) { 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( return Container(
key: ValueKey(country), key: ValueKey(country),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -476,16 +489,36 @@ class HINodeListView extends GetView<HINodeListController> {
), ),
), ),
Obx(() { 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( return Text(
delay == 0 delayText,
? ''
: delay >= 3000
? AppTranslations.kr_home.timeout
: '${delay}ms',
style: KrAppTextStyle( style: KrAppTextStyle(
fontSize: 10, fontSize: 10,
color: getLatencyColor(delay), color: delayColor, // 使
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
); );

View File

@ -1275,11 +1275,108 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
} }
} }
Future<void> onCountrySelected(String countryTag) async { /// 🌍 UI同步与VPN热重载
await KRSingBoxImp.instance.kr_selectCountry(countryTag); /// true false
KRCommonUtil.kr_showToast('已切换到 $countryTag 节点组'); 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_selectNode
/// kr_performNodeSwitch /// kr_performNodeSwitch
/// ///

View File

@ -1482,67 +1482,6 @@ class KRSingBoxImp {
return; 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');
}
});*/
} }