feat: 提交国家选择
This commit is contained in:
parent
a884e6c838
commit
748ec6bee9
@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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 方法
|
||||||
/// 为了保持向后兼容,保留此方法但改为调用新方法
|
/// 为了保持向后兼容,保留此方法但改为调用新方法
|
||||||
|
|||||||
@ -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');
|
|
||||||
}
|
|
||||||
});*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user