将KRLatencyTester.testMultipleNodes方法的timeout参数从Duration类型改为直接使用毫秒数值,保持与接口定义一致
2222 lines
85 KiB
Dart
Executable File
2222 lines
85 KiB
Dart
Executable File
import 'dart:async';
|
||
import 'dart:io';
|
||
import 'package:http/http.dart' as http;
|
||
import 'dart:convert';
|
||
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:path_provider/path_provider.dart';
|
||
import 'package:flutter_map/flutter_map.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
||
|
||
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
|
||
import 'package:latlong2/latlong.dart';
|
||
import '../../../../singbox/model/singbox_proxy_type.dart';
|
||
import '../../../../singbox/model/singbox_status.dart';
|
||
import '../../../common/app_config.dart';
|
||
import '../../../localization/app_translations.dart';
|
||
import '../../../localization/kr_language_utils.dart';
|
||
import '../../../model/business/kr_group_outbound_list.dart';
|
||
import '../../../services/kr_announcement_service.dart';
|
||
import '../../../utils/kr_event_bus.dart';
|
||
import '../../../utils/kr_update_util.dart';
|
||
import '../../../widgets/dialogs/kr_dialog.dart';
|
||
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
|
||
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||
|
||
|
||
import '../models/kr_home_views_status.dart';
|
||
|
||
import 'package:kaer_with_panels/app/model/response/kr_user_available_subscribe.dart';
|
||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
|
||
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
|
||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:kaer_with_panels/app/utils/kr_init_log_collector.dart'; // 🔧 新增:导入日志收集器
|
||
import 'package:kaer_with_panels/app/utils/kr_latency_tester.dart'; // 🔧 新增:导入真实延迟测试工具
|
||
|
||
class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||
// 🔧 新增:日志收集器实例
|
||
final _initLog = KRInitLogCollector();
|
||
/// 订阅服务
|
||
final KRSubscribeService kr_subscribeService = KRSubscribeService();
|
||
|
||
// 修改地图控制器为可空类型
|
||
MapController kr_mapController = MapController();
|
||
|
||
/// 当前视图状态,登录状态
|
||
final Rx<KRHomeViewsStatus> kr_currentViewStatus =
|
||
KRHomeViewsStatus.kr_notLoggedIn.obs;
|
||
|
||
/// 当前列表视图状态
|
||
final kr_currentListStatus = KRHomeViewsListStatus.kr_loading.obs;
|
||
|
||
/// 底部面板高度常量
|
||
static const double kr_baseHeight = 120.0; // 基础高度(连接选项)
|
||
static const double kr_subscriptionCardHeight = 200.0; // 订阅卡片高度
|
||
static const double kr_connectionInfoHeight = 126.0; // 连接信息卡片高度(修复后)
|
||
static const double kr_trialCardHeight = 120.0; // 试用卡片高度
|
||
static const double kr_lastDayCardHeight = 120.0; // 最后一天卡片高度
|
||
static const double kr_nodeListHeight = 400.0; // 节点列表高度
|
||
static const double kr_errorHeight = 100.0; // 错误状态高度
|
||
static const double kr_loadingHeight = 100.0; // 加载状态高度
|
||
|
||
/// 间距常量
|
||
static const double kr_marginTop = 12.0; // 顶部间距
|
||
static const double kr_marginBottom = 12.0; // 底部间距
|
||
static const double kr_marginHorizontal = 16.0; // 水平间距
|
||
static const double kr_marginVertical = 12.0; // 垂直间距
|
||
|
||
/// 底部面板高度
|
||
final kr_bottomPanelHeight = 200.0.obs;
|
||
|
||
/// 连接字符串
|
||
final kr_connectText = AppTranslations.kr_home.disconnected.obs;
|
||
|
||
// 当前节点名称
|
||
final kr_currentNodeName = 'auto'.obs;
|
||
|
||
/// 当前连接速率
|
||
final RxString kr_currentSpeed = "--".obs;
|
||
|
||
// 当前节点延迟
|
||
final kr_currentNodeLatency = (-2).obs;
|
||
|
||
// 是否已连接
|
||
final kr_isConnected = false.obs;
|
||
|
||
// 是否显示延迟
|
||
final kr_isLatency = false.obs;
|
||
|
||
/// 默认
|
||
var kr_cutTag = 'auto'.obs;
|
||
var kr_cutSeletedTag = 'auto'.obs;
|
||
var kr_coutryText = 'auto'.obs;
|
||
|
||
/// 当前连接信息
|
||
final RxString kr_currentIp = AppTranslations.kr_home.disconnected.obs;
|
||
final RxString kr_currentProtocol = AppTranslations.kr_home.disconnected.obs;
|
||
final RxString kr_connectionTime = '00:00:00'.obs;
|
||
|
||
// 连接计时器
|
||
Timer? _kr_connectionTimer;
|
||
int _kr_connectionSeconds = 0;
|
||
|
||
// 当前选中的组
|
||
final Rx<KRGroupOutboundList?> kr_currentGroup =
|
||
Rx<KRGroupOutboundList?>(null);
|
||
|
||
// 添加是否用户正在移动地图的标志
|
||
final kr_isUserMoving = false.obs;
|
||
|
||
// 添加最后的地图中心点
|
||
final kr_lastMapCenter = LatLng(35.0, 105.0).obs;
|
||
|
||
// 为"闪连"Checkbox添加一个响应式变量,默认为 false
|
||
final isQuickConnectEnabled = false.obs;
|
||
|
||
// 存储实例
|
||
final KRSecureStorage _storage = KRSecureStorage();
|
||
|
||
// 闪连状态存储键
|
||
static const String _quickConnectKey = 'kr_quick_connect_enabled';
|
||
|
||
// 国家内节点重选相关属性
|
||
final RxString currentSelectedCountry = ''.obs;
|
||
final int countryReselectionLatencyThreshold = 3000; // 延迟阈值(毫秒)
|
||
|
||
// 添加一个方法来切换状态
|
||
void toggleQuickConnect(bool? value) async {
|
||
// 只有当传入的值不为 null 时才更新状态
|
||
if (value != null) {
|
||
isQuickConnectEnabled.value = value;
|
||
// 保存闪连状态到本地存储
|
||
await _storage.kr_saveBool(key: _quickConnectKey, value: value);
|
||
KRLogUtil.kr_i('闪连状态已更新: $value', tag: 'QuickConnect');
|
||
}
|
||
}
|
||
|
||
// 从本地存储加载闪连状态
|
||
Future<void> _loadQuickConnectStatus() async {
|
||
try {
|
||
final enabled = await _storage.kr_getBool(key: _quickConnectKey);
|
||
if (enabled != null) {
|
||
isQuickConnectEnabled.value = enabled;
|
||
KRLogUtil.kr_i('从本地存储加载闪连状态: $enabled', tag: 'QuickConnect');
|
||
} else {
|
||
// 如果没有保存过状态,使用默认值false
|
||
isQuickConnectEnabled.value = false;
|
||
KRLogUtil.kr_i(
|
||
'未找到保存的闪连状态,使用默认值: false', tag: 'QuickConnect');
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('加载闪连状态失败: $e', tag: 'QuickConnect');
|
||
// 出错时使用默认值
|
||
isQuickConnectEnabled.value = false;
|
||
}
|
||
}
|
||
|
||
|
||
// 获取当前闪连状态
|
||
bool get isQuickConnectActive => isQuickConnectEnabled.value;
|
||
|
||
/// 检查闪连自动连接启动条件
|
||
Future<void> _checkQuickConnectAutoStart() async {
|
||
try {
|
||
KRLogUtil.kr_i('检查闪连自动连接启动条件', tag: 'QuickConnect');
|
||
|
||
// 检查闪连是否开启
|
||
final enabled = await _storage.kr_getBool(key: _quickConnectKey);
|
||
if (enabled == null || enabled == false) {
|
||
KRLogUtil.kr_i('闪连功能未开启,跳过自动连接', tag: 'QuickConnect');
|
||
return;
|
||
}
|
||
|
||
// 检查用户是否已登录
|
||
if (!KRAppRunData
|
||
.getInstance()
|
||
.kr_isLogin
|
||
.value) {
|
||
KRLogUtil.kr_i('用户未登录,跳过自动连接', tag: 'QuickConnect');
|
||
return;
|
||
}
|
||
|
||
// 检查当前是否已连接
|
||
if (kr_isConnected.value) {
|
||
KRLogUtil.kr_i('当前已连接,跳过自动连接', tag: 'QuickConnect');
|
||
return;
|
||
}
|
||
|
||
|
||
KRLogUtil.kr_i('满足闪连自动连接条件,开始自动连接', tag: 'QuickConnect');
|
||
await _autoConnect();
|
||
} catch (e) {
|
||
KRLogUtil.kr_e(
|
||
'检查闪连自动连接启动条件时发生错误: $e', tag: 'QuickConnect');
|
||
}
|
||
}
|
||
|
||
/// 执行自动连接
|
||
Future<void> _autoConnect() async {
|
||
try {
|
||
KRLogUtil.kr_i('开始执行闪连自动连接', tag: 'QuickConnect');
|
||
|
||
// 防止重复操作
|
||
if (KRSingBoxImp.instance.kr_status.value == SingboxStarted()) {
|
||
KRLogUtil.kr_w('连接操作正在进行中,跳过自动连接', tag: 'QuickConnect');
|
||
return;
|
||
}
|
||
|
||
// 调用连接方法
|
||
await KRSingBoxImp.instance.kr_start();
|
||
KRLogUtil.kr_i('闪连自动连接执行完成', tag: 'QuickConnect');
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('闪连自动连接失败: $e', tag: 'QuickConnect');
|
||
}
|
||
}
|
||
|
||
/// 手动触发闪连状态变更(公共方法)
|
||
Future<void> triggerQuickConnect() async {
|
||
try {
|
||
// 切换闪连状态
|
||
bool newStatus = !isQuickConnectEnabled.value;
|
||
await _storage.kr_saveBool(key: _quickConnectKey, value: newStatus);
|
||
isQuickConnectEnabled.value = newStatus;
|
||
|
||
KRLogUtil.kr_i('手动切换闪连状态: ${newStatus ? "开启" : "关闭"}',
|
||
tag: 'QuickConnect');
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('切换闪连状态失败: $e', tag: 'QuickConnect');
|
||
}
|
||
}
|
||
|
||
/// 检查是否满足自动连接条件(不执行连接)
|
||
bool canAutoConnect() {
|
||
return isQuickConnectEnabled.value &&
|
||
KRAppRunData
|
||
.getInstance()
|
||
.kr_isLogin
|
||
.value &&
|
||
!kr_isConnected.value;
|
||
}
|
||
|
||
|
||
@override
|
||
void onInit() async {
|
||
super.onInit();
|
||
|
||
// 🔧 紧急诊断:直接写文件验证 onInit 是否被调用
|
||
try {
|
||
final dir = await getApplicationDocumentsDirectory();
|
||
final debugFile = File('${dir.path}/HOME_CONTROLLER_DEBUG.txt');
|
||
await debugFile.writeAsString(
|
||
'=' * 60 + '\n'
|
||
'HomeController.onInit 被调用!\n'
|
||
'时间: ${DateTime.now()}\n'
|
||
'实例 HashCode: ${hashCode}\n'
|
||
'线程: ${Platform.isAndroid ? "Android" : "Unknown"}\n'
|
||
'=' * 60 + '\n',
|
||
mode: FileMode.append,
|
||
);
|
||
} catch (e) {
|
||
// 忽略错误,确保不影响主流程
|
||
}
|
||
|
||
// 🔧 新增:记录 HomeController 初始化开始
|
||
_initLog.logPhaseStart('HomeController 初始化');
|
||
_initLog.log('KRHomeController.onInit 被调用', tag: 'Home');
|
||
|
||
// 🔧 Android 15 紧急修复:立即设置默认高度,确保底部面板可见
|
||
// kr_updateBottomPanelHeight();
|
||
|
||
/// 底部面板高度处理
|
||
// _kr_initBottomPanelHeight();
|
||
// 加载闪连状态
|
||
_loadQuickConnectStatus();
|
||
// 绑定订阅状态
|
||
_bindSubscribeStatus();
|
||
|
||
/// 登录处理
|
||
_kr_initLoginStatus();
|
||
|
||
// 绑定连接状态
|
||
_bindConnectionStatus();
|
||
|
||
// 注册应用生命周期监听
|
||
WidgetsBinding.instance.addObserver(this);
|
||
|
||
// 🔧 新增:恢复上次选择的节点显示
|
||
_restoreSelectedNode();
|
||
|
||
// 延迟同步连接状态,确保状态正确
|
||
Future.delayed(const Duration(milliseconds: 500), () {
|
||
kr_forceSyncConnectionStatus();
|
||
_checkQuickConnectAutoStart();
|
||
});
|
||
|
||
// 🔧 Android 15 新增:5秒后再次强制更新高度,兜底保护
|
||
Future.delayed(const Duration(seconds: 5), () {
|
||
if (kr_bottomPanelHeight.value < 100) {
|
||
KRLogUtil.kr_w('检测到底部面板高度异常(${kr_bottomPanelHeight.value}),强制修正', tag: 'HomeController');
|
||
kr_updateBottomPanelHeight();
|
||
}
|
||
});
|
||
}
|
||
|
||
/// 🔧 新增:恢复上次选择的节点显示
|
||
Future<void> _restoreSelectedNode() async {
|
||
try {
|
||
// 从 Hive 读取保存的节点
|
||
final savedNode = await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
||
|
||
if (savedNode != null && savedNode.isNotEmpty) {
|
||
KRLogUtil.kr_i('📖 恢复上次选择的节点显示: $savedNode', tag: 'HomeController');
|
||
|
||
// 更新 UI 显示
|
||
kr_currentNodeName.value = savedNode;
|
||
kr_cutTag.value = savedNode;
|
||
kr_cutSeletedTag.value = savedNode;
|
||
|
||
// 如果是国家节点,更新国家文本
|
||
if (savedNode != 'auto') {
|
||
kr_coutryText.value = savedNode;
|
||
}
|
||
|
||
KRLogUtil.kr_i('✅ 节点显示已恢复: $savedNode', tag: 'HomeController');
|
||
} else {
|
||
KRLogUtil.kr_i('ℹ️ 没有保存的节点,使用默认 auto', tag: 'HomeController');
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 恢复节点显示失败: $e', tag: 'HomeController');
|
||
// 失败时保持默认值 'auto'
|
||
}
|
||
}
|
||
|
||
/// 底部面板高度处理
|
||
void _kr_initBottomPanelHeight() {
|
||
ever(kr_currentListStatus, (status) {
|
||
kr_updateBottomPanelHeight();
|
||
KRLogUtil.kr_i(status.toString(), tag: "_kr_initBottomPanelHeight");
|
||
});
|
||
}
|
||
|
||
void _kr_initLoginStatus() {
|
||
_initLog.log('开始初始化登录状态处理', tag: 'Home');
|
||
KRLogUtil.kr_i('初始化登录状态', tag: 'HomeController');
|
||
|
||
// 🔧 Android 15 紧急修复:8秒超时,更快响应移动网络慢的情况
|
||
Timer(const Duration(seconds: 8), () {
|
||
if (kr_currentListStatus.value == KRHomeViewsListStatus.kr_loading) {
|
||
_initLog.logWarning('⏱️ 订阅加载超时(8秒保护),强制设置为无数据状态', tag: 'Subscribe');
|
||
_initLog.log('当前列表状态仍为: loading,触发超时保护', tag: 'Subscribe');
|
||
KRLogUtil.kr_w('⏱️ 订阅服务初始化超时(8秒),强制设置为无数据状态', tag: 'HomeController');
|
||
// 🔧 Android 15 优化:超时设置为 none 而非 error,避免底部面板显示错误界面
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||
// 强制更新底部面板高度,确保显示正常
|
||
kr_updateBottomPanelHeight();
|
||
_initLog.log('已强制切换到默认视图, 底部面板高度: ${kr_bottomPanelHeight.value}', tag: 'Subscribe');
|
||
KRLogUtil.kr_i('✅ 已强制切换到默认视图', tag: 'HomeController');
|
||
}
|
||
});
|
||
|
||
// 🔧 Android 15 新增:3秒警告,帮助诊断
|
||
Timer(const Duration(seconds: 3), () {
|
||
if (kr_currentListStatus.value == KRHomeViewsListStatus.kr_loading) {
|
||
_initLog.logWarning('⚠️ 订阅服务已加载3秒,网络可能较慢', tag: 'Subscribe');
|
||
KRLogUtil.kr_w('⚠️ 订阅服务初始化已耗时3秒,可能网络较慢', tag: 'HomeController');
|
||
}
|
||
});
|
||
|
||
// 延迟初始化,确保所有异步操作完成
|
||
Future.delayed(const Duration(milliseconds: 100), () {
|
||
_kr_validateAndSetLoginStatus();
|
||
});
|
||
|
||
// 注册登录状态监听器
|
||
ever(KRAppRunData().kr_isLogin, (isLoggedIn) {
|
||
KRLogUtil.kr_i('登录状态变化: $isLoggedIn', tag: 'HomeController');
|
||
_kr_handleLoginStatusChange(isLoggedIn);
|
||
});
|
||
|
||
// 添加状态同步检查
|
||
_kr_addStatusSyncCheck();
|
||
|
||
if (AppConfig().kr_is_daytime == true) {
|
||
Future.delayed(const Duration(seconds: 1), () {
|
||
KRUpdateUtil().kr_checkUpdate();
|
||
|
||
Future.delayed(const Duration(seconds: 1), () {
|
||
// 不做语言切换
|
||
// KRLanguageSwitchDialog.kr_show();
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
/// 验证并设置登录状态
|
||
void _kr_validateAndSetLoginStatus() {
|
||
try {
|
||
_initLog.log('🔍 开始验证登录状态', tag: 'Home');
|
||
|
||
// 多重验证登录状态
|
||
final hasToken = KRAppRunData().kr_token != null && KRAppRunData().kr_token!.isNotEmpty;
|
||
final isLoginFlag = KRAppRunData().kr_isLogin.value;
|
||
final isValidLogin = hasToken && isLoginFlag;
|
||
|
||
_initLog.log('登录验证结果: hasToken=$hasToken, isLogin=$isLoginFlag, isValid=$isValidLogin', tag: 'Home');
|
||
KRLogUtil.kr_i('登录状态验证: hasToken=$hasToken, isLogin=$isLoginFlag, isValid=$isValidLogin', tag: 'HomeController');
|
||
KRLogUtil.kr_i('Token内容: ${KRAppRunData().kr_token?.substring(0, 10)}...', tag: 'HomeController');
|
||
|
||
if (isValidLogin) {
|
||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
|
||
_initLog.logSuccess('用户已登录,准备加载订阅数据', tag: 'Home');
|
||
KRLogUtil.kr_i('设置为已登录状态', tag: 'HomeController');
|
||
|
||
// 订阅服务已在 splash 页面初始化,此处无需重复初始化
|
||
KRLogUtil.kr_i('订阅服务已在启动页初始化,跳过重复初始化', tag: 'HomeController');
|
||
} else {
|
||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
||
_initLog.logWarning('用户未登录,跳过订阅加载', tag: 'Home');
|
||
KRLogUtil.kr_i('设置为未登录状态', tag: 'HomeController');
|
||
}
|
||
} catch (e) {
|
||
_initLog.logError('登录状态验证失败', tag: 'Home', error: e);
|
||
KRLogUtil.kr_e('登录状态验证失败: $e', tag: 'HomeController');
|
||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
||
}
|
||
}
|
||
|
||
/// 确保订阅服务初始化
|
||
void _kr_ensureSubscribeServiceInitialized() {
|
||
try {
|
||
_initLog.logSeparator();
|
||
_initLog.log('📦 开始订阅服务初始化', tag: 'Subscribe');
|
||
|
||
// 🔧 修复1: 添加登录状态检查 - 只有已登录用户才能初始化订阅
|
||
final isLoggedIn = KRAppRunData().kr_isLogin.value;
|
||
if (!isLoggedIn) {
|
||
_initLog.logWarning('未登录用户,跳过订阅服务初始化', tag: 'Subscribe');
|
||
KRLogUtil.kr_w('⚠️ 未登录用户,不初始化订阅服务', tag: 'HomeController');
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||
return;
|
||
}
|
||
|
||
// 检查订阅服务状态
|
||
final currentStatus = kr_subscribeService.kr_currentStatus.value;
|
||
_initLog.log('当前订阅服务状态: $currentStatus', tag: 'Subscribe');
|
||
KRLogUtil.kr_i('订阅服务当前状态: $currentStatus', tag: 'HomeController');
|
||
|
||
if (currentStatus == KRSubscribeServiceStatus.kr_none ||
|
||
currentStatus == KRSubscribeServiceStatus.kr_error) {
|
||
_initLog.log('订阅服务需要初始化,开始加载...', tag: 'Subscribe');
|
||
KRLogUtil.kr_i('订阅服务未初始化或错误,开始初始化', tag: 'HomeController');
|
||
|
||
// 设置加载状态
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_loading;
|
||
_initLog.log('设置列表状态为: loading', tag: 'Subscribe');
|
||
|
||
final startTime = DateTime.now();
|
||
_initLog.log('开始调用 kr_refreshAll() 加载订阅数据', tag: 'Subscribe');
|
||
|
||
// 🔧 Android 15 增强:添加 10 秒超时保护
|
||
kr_subscribeService.kr_refreshAll().timeout(
|
||
const Duration(seconds: 10),
|
||
onTimeout: () {
|
||
final elapsed = DateTime.now().difference(startTime).inMilliseconds;
|
||
_initLog.logWarning('订阅服务初始化超时(10秒), 实际耗时: ${elapsed}ms', tag: 'Subscribe');
|
||
KRLogUtil.kr_w('⏱️ 订阅服务初始化超时(10秒)', tag: 'HomeController');
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||
throw TimeoutException('订阅服务初始化超时');
|
||
},
|
||
).then((_) async {
|
||
final elapsed = DateTime.now().difference(startTime).inMilliseconds;
|
||
_initLog.logSuccess('订阅服务初始化完成, 耗时: ${elapsed}ms', tag: 'Subscribe');
|
||
_initLog.log('最终列表状态: ${kr_currentListStatus.value}', tag: 'Subscribe');
|
||
|
||
// 🔧 新增:记录订阅数据详情
|
||
final subscribeList = kr_subscribeService.kr_availableSubscribes;
|
||
_initLog.log('订阅列表数量: ${subscribeList.length}', tag: 'Subscribe');
|
||
if (subscribeList.isEmpty) {
|
||
_initLog.logWarning('⚠️ 订阅列表为空!用户可能没有有效订阅', tag: 'Subscribe');
|
||
} else {
|
||
for (int i = 0; i < subscribeList.length; i++) {
|
||
final sub = subscribeList[i];
|
||
_initLog.log('订阅[$i]: ID=${sub.id}, 名称=${sub.name}, 试用=${sub.isTryOut}', tag: 'Subscribe');
|
||
_initLog.log(' 过期时间: ${sub.expireTime}, 流量: ${sub.traffic}', tag: 'Subscribe');
|
||
}
|
||
}
|
||
|
||
// 记录当前选中的订阅
|
||
final currentSub = kr_subscribeService.kr_currentSubscribe.value;
|
||
if (currentSub != null) {
|
||
_initLog.log('当前选中订阅: ${currentSub.name} (ID: ${currentSub.id})', tag: 'Subscribe');
|
||
} else {
|
||
_initLog.logWarning('⚠️ 没有选中任何订阅', tag: 'Subscribe');
|
||
}
|
||
|
||
// 记录订阅服务的最终状态
|
||
final finalServiceStatus = kr_subscribeService.kr_currentStatus.value;
|
||
_initLog.log('订阅服务最终状态: $finalServiceStatus', tag: 'Subscribe');
|
||
|
||
KRLogUtil.kr_i('✅ 订阅服务初始化完成', tag: 'HomeController');
|
||
|
||
// 🔧 关键修复:订阅服务初始化完成后,关闭日志文件
|
||
await _initLog.finalize();
|
||
|
||
// 成功后状态由服务自己控制,不在这里设置
|
||
}).catchError((error) async {
|
||
final elapsed = DateTime.now().difference(startTime).inMilliseconds;
|
||
_initLog.logError('订阅服务初始化失败, 耗时: ${elapsed}ms', tag: 'Subscribe', error: error);
|
||
_initLog.log('设置列表状态为: none (失败)', tag: 'Subscribe');
|
||
KRLogUtil.kr_e('❌ 订阅服务初始化失败: $error', tag: 'HomeController');
|
||
|
||
// 🔧 关键修复:即使失败也要关闭日志文件
|
||
await _initLog.finalize();
|
||
// 🔧 Android 15 优化:失败时设置为 none 而非 error,允许用户手动重试
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||
});
|
||
} else if (currentStatus == KRSubscribeServiceStatus.kr_loading) {
|
||
KRLogUtil.kr_i('订阅服务正在初始化中', tag: 'HomeController');
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_loading;
|
||
} else if (currentStatus == KRSubscribeServiceStatus.kr_success) {
|
||
KRLogUtil.kr_i('订阅服务已成功初始化', tag: 'HomeController');
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 确保订阅服务初始化异常: $e', tag: 'HomeController');
|
||
// 🔧 Android 15 优化:异常时设置为 none 而非 error
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||
}
|
||
}
|
||
|
||
|
||
/// 处理登录状态变化
|
||
void _kr_handleLoginStatusChange(bool isLoggedIn) {
|
||
try {
|
||
if (isLoggedIn) {
|
||
// 再次验证登录状态的有效性
|
||
final isValidLogin = KRAppRunData().kr_token != null && KRAppRunData().kr_token!.isNotEmpty;
|
||
if (isValidLogin) {
|
||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
|
||
KRLogUtil.kr_i('登录状态变化:设置为已登录', tag: 'HomeController');
|
||
|
||
KRAnnouncementService().kr_checkAnnouncement();
|
||
|
||
// 订阅服务已在 splash 页面初始化,此处无需重复初始化
|
||
KRLogUtil.kr_i('订阅服务已在启动页初始化,跳过重复初始化', tag: 'HomeController');
|
||
} else {
|
||
KRLogUtil.kr_w('登录状态为true但token为空,重置为未登录', tag: 'HomeController');
|
||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
||
}
|
||
} else {
|
||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
||
KRLogUtil.kr_i('登录状态变化:设置为未登录', tag: 'HomeController');
|
||
|
||
// 重置列表状态,防止出现无限高度
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||
|
||
// 退出登录清理订阅服务
|
||
kr_subscribeService.kr_logout();
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('处理登录状态变化失败: $e', tag: 'HomeController');
|
||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
||
}
|
||
}
|
||
|
||
/// 添加状态同步检查
|
||
void _kr_addStatusSyncCheck() {
|
||
// 在下一帧检查状态同步
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
_kr_syncLoginStatus();
|
||
});
|
||
}
|
||
|
||
/// 同步登录状态
|
||
void _kr_syncLoginStatus() {
|
||
try {
|
||
final currentLoginStatus = KRAppRunData().kr_isLogin.value;
|
||
final currentViewStatus = kr_currentViewStatus.value;
|
||
|
||
KRLogUtil.kr_i('状态同步检查: login=$currentLoginStatus, view=$currentViewStatus', tag: 'HomeController');
|
||
|
||
// 检查状态是否一致
|
||
if (currentViewStatus == KRHomeViewsStatus.kr_loggedIn && !currentLoginStatus) {
|
||
KRLogUtil.kr_w('状态不一致:视图显示已登录但实际未登录,修正状态', tag: 'HomeController');
|
||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
||
} else if (currentViewStatus == KRHomeViewsStatus.kr_notLoggedIn && currentLoginStatus) {
|
||
KRLogUtil.kr_w('状态不一致:视图显示未登录但实际已登录,修正状态', tag: 'HomeController');
|
||
_kr_validateAndSetLoginStatus();
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('状态同步检查失败: $e', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
/// 属性数据
|
||
void kr_refreshAll() {
|
||
kr_subscribeService.kr_refreshAll();
|
||
}
|
||
|
||
/// 绑定订阅状态
|
||
void _bindSubscribeStatus() {
|
||
ever(kr_subscribeService.kr_currentStatus, (data) {
|
||
KRLogUtil.kr_i('订阅服务状态变化: $data', tag: 'HomeController');
|
||
|
||
if (KRAppRunData.getInstance().kr_isLogin.value) {
|
||
switch (data) {
|
||
case KRSubscribeServiceStatus.kr_loading:
|
||
KRLogUtil.kr_i('订阅服务加载中', tag: 'HomeController');
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_loading;
|
||
break;
|
||
case KRSubscribeServiceStatus.kr_error:
|
||
KRLogUtil.kr_w('订阅服务错误', tag: 'HomeController');
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_error;
|
||
// 不再自动重试,让用户手动刷新
|
||
break;
|
||
case KRSubscribeServiceStatus.kr_success:
|
||
KRLogUtil.kr_i('订阅服务成功', tag: 'HomeController');
|
||
kr_cutTag.value = 'auto';
|
||
kr_cutSeletedTag.value = 'auto';
|
||
kr_currentNodeName.value = "auto";
|
||
if (kr_currentListStatus.value != KRHomeViewsListStatus.kr_none) {
|
||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||
} else {
|
||
// kr_updateBottomPanelHeight();
|
||
}
|
||
break;
|
||
case KRSubscribeServiceStatus.kr_none:
|
||
KRLogUtil.kr_i('订阅服务未初始化', tag: 'HomeController');
|
||
// 订阅服务已在 splash 页面初始化,如果状态为none可能是网络问题
|
||
if (kr_currentViewStatus.value == KRHomeViewsStatus.kr_loggedIn &&
|
||
kr_subscribeService.kr_availableSubscribes.isEmpty) {
|
||
KRLogUtil.kr_i('订阅服务状态为none,可能是网络问题,已在启动页初始化', tag: 'HomeController');
|
||
}
|
||
break;
|
||
}
|
||
} else {
|
||
KRLogUtil.kr_i('用户未登录,忽略订阅状态变化', tag: 'HomeController');
|
||
}
|
||
});
|
||
|
||
// 监听所有支付相关消息
|
||
KREventBus().kr_listenMessages(
|
||
[KRMessageType.kr_payment, KRMessageType.kr_subscribe_update],
|
||
_kr_handleMessage,
|
||
);
|
||
}
|
||
|
||
/// 处理消息
|
||
void _kr_handleMessage(KRMessageData message) {
|
||
switch (message.kr_type) {
|
||
case KRMessageType.kr_payment:
|
||
kr_refreshAll();
|
||
break;
|
||
case KRMessageType.kr_subscribe_update:
|
||
// 处理订阅更新消息
|
||
// 显示提示框
|
||
|
||
KRDialog.show(
|
||
title: AppTranslations.kr_home.subscriptionUpdated,
|
||
message: AppTranslations.kr_home.subscriptionUpdatedMessage,
|
||
confirmText: AppTranslations.kr_dialog.kr_confirm,
|
||
cancelText: AppTranslations.kr_dialog.kr_cancel,
|
||
onConfirm: () {
|
||
kr_refreshAll();
|
||
},
|
||
onCancel: () => Get.back(),
|
||
);
|
||
|
||
break;
|
||
|
||
// TODO: Handle this case.
|
||
}
|
||
}
|
||
|
||
/// 绑定连接状态
|
||
void _bindConnectionStatus() {
|
||
// 添加更详细的状态监听
|
||
ever(KRSingBoxImp.instance.kr_status, (status) {
|
||
KRLogUtil.kr_i('🔄 连接状态变化: $status', tag: 'HomeController');
|
||
KRLogUtil.kr_i('📊 当前状态类型: ${status.runtimeType}', tag: 'HomeController');
|
||
|
||
switch (status) {
|
||
case SingboxStopped():
|
||
KRLogUtil.kr_i('🔴 状态: 已停止', tag: 'HomeController');
|
||
kr_connectText.value = AppTranslations.kr_home.disconnected;
|
||
kr_stopConnectionTimer();
|
||
kr_resetConnectionInfo();
|
||
kr_currentSpeed.value = "--";
|
||
kr_isLatency.value = false;
|
||
kr_isConnected.value = false;
|
||
kr_currentNodeLatency.value = -2;
|
||
// 强制刷新 isConnected 状态
|
||
kr_isConnected.refresh();
|
||
break;
|
||
case SingboxStarting():
|
||
KRLogUtil.kr_i('🟡 状态: 正在启动', tag: 'HomeController');
|
||
kr_connectText.value = AppTranslations.kr_home.connecting;
|
||
kr_currentSpeed.value = AppTranslations.kr_home.connecting;
|
||
kr_currentNodeLatency.value = -1;
|
||
kr_isConnected.value = false; // 修复:启动中应该为false
|
||
// 启动连接超时处理
|
||
_startConnectionTimeout();
|
||
break;
|
||
case SingboxStarted():
|
||
KRLogUtil.kr_i('🟢 状态: 已启动', tag: 'HomeController');
|
||
if (kDebugMode) {
|
||
}
|
||
|
||
// 取消连接超时处理
|
||
_cancelConnectionTimeout();
|
||
kr_connectText.value = AppTranslations.kr_home.connected;
|
||
kr_startConnectionTimer();
|
||
kr_updateConnectionInfo();
|
||
kr_isLatency.value = false;
|
||
kr_isConnected.value = true;
|
||
|
||
// 🔧 关键修复:如果延迟还是-1(连接中状态),立即设置为0(已连接但延迟未知)
|
||
if (kr_currentNodeLatency.value == -1) {
|
||
if (kDebugMode) {
|
||
}
|
||
kr_currentNodeLatency.value = 0;
|
||
kr_currentNodeLatency.refresh(); // 强制刷新延迟值
|
||
if (kDebugMode) {
|
||
}
|
||
}
|
||
|
||
// 🔧 修复:立即尝试更新延迟值
|
||
_kr_updateLatencyOnConnected();
|
||
|
||
// 强制刷新 isConnected 状态
|
||
kr_isConnected.refresh();
|
||
// 强制更新UI
|
||
update();
|
||
if (kDebugMode) {
|
||
}
|
||
break;
|
||
case SingboxStopping():
|
||
KRLogUtil.kr_i('🟠 状态: 正在停止', tag: 'HomeController');
|
||
kr_connectText.value = AppTranslations.kr_home.disconnecting;
|
||
kr_isConnected.value = false;
|
||
kr_currentSpeed.value = "--";
|
||
break;
|
||
}
|
||
|
||
// 强制更新UI
|
||
update();
|
||
});
|
||
|
||
// 添加活动组监听,确保状态同步
|
||
ever(KRSingBoxImp.instance.kr_activeGroups, (value) {
|
||
KRLogUtil.kr_i('📡 活动组更新,数量: ${value.length}', tag: 'HomeController');
|
||
|
||
if (value.isEmpty) {
|
||
KRLogUtil.kr_w('⚠️ 活动组为空', tag: 'HomeController');
|
||
// 🔧 修复:如果已连接但活动组为空,设置延迟为0而不是-1
|
||
if (kr_isConnected.value && kr_currentNodeLatency.value == -1) {
|
||
KRLogUtil.kr_w('⚠️ 已连接但活动组为空,设置延迟为0', tag: 'HomeController');
|
||
kr_currentNodeLatency.value = 0;
|
||
}
|
||
return;
|
||
}
|
||
|
||
try {
|
||
bool hasSelector = false;
|
||
for (var element in value) {
|
||
KRLogUtil.kr_i('📋 处理组: ${element.tag}, 类型: ${element.type}, 选中: ${element.selected}', tag: 'HomeController');
|
||
|
||
if (element.type == ProxyType.selector) {
|
||
hasSelector = true;
|
||
_kr_handleSelectorProxy(element, value);
|
||
} else if (element.type == ProxyType.urltest) {
|
||
KRLogUtil.kr_d('URL测试代理选中: ${element.selected}', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
// 🔧 修复:如果已连接但没有selector组,设置延迟为0
|
||
if (!hasSelector && kr_isConnected.value && kr_currentNodeLatency.value == -1) {
|
||
KRLogUtil.kr_w('⚠️ 已连接但无selector组,设置延迟为0', tag: 'HomeController');
|
||
kr_currentNodeLatency.value = 0;
|
||
}
|
||
|
||
// 强制更新UI
|
||
update();
|
||
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('处理活动组时发生错误: $e', tag: 'HomeController');
|
||
// 🔧 修复:发生错误时,如果已连接,设置延迟为0
|
||
if (kr_isConnected.value && kr_currentNodeLatency.value == -1) {
|
||
KRLogUtil.kr_w('⚠️ 处理活动组错误,设置延迟为0', tag: 'HomeController');
|
||
kr_currentNodeLatency.value = 0;
|
||
}
|
||
}
|
||
});
|
||
|
||
ever(KRSingBoxImp.instance.kr_allGroups, (value) {
|
||
List<String> updateTags = []; // 收集需要更新的标记ID
|
||
for (var element in value) {
|
||
for (var subElement in element.items) {
|
||
var node = kr_subscribeService.keyList[subElement.tag];
|
||
if (node != null) {
|
||
if (subElement.urlTestDelay != 0) {
|
||
node.urlTestDelay.value = subElement.urlTestDelay;
|
||
updateTags.add(subElement.tag); // 添加需要更新的标记ID
|
||
}
|
||
}
|
||
}
|
||
|
||
// 批量更新所有变化的标记
|
||
}
|
||
if (updateTags.isNotEmpty) {
|
||
kr_updateMarkers(updateTags);
|
||
}
|
||
});
|
||
}
|
||
|
||
/// 🔧 重构: 参考 hiddify-app 的 toggleConnection 实现
|
||
Future<void> kr_toggleSwitch(bool value) async {
|
||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||
|
||
KRLogUtil.kr_i('🔵 toggleSwitch 被调用: value=$value, currentStatus=$currentStatus', tag: 'HomeController');
|
||
if (kDebugMode) {
|
||
}
|
||
|
||
// 🔧 关键: 如果正在切换中,直接忽略(参考 hiddify-app 的 "switching status, debounce")
|
||
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
|
||
KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', tag: 'HomeController');
|
||
if (kDebugMode) {
|
||
}
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (value) {
|
||
// 开启连接
|
||
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
|
||
if (kDebugMode) {
|
||
}
|
||
await KRSingBoxImp.instance.kr_start();
|
||
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
|
||
if (kDebugMode) {
|
||
}
|
||
|
||
// 🔧 修复: 等待状态更新,最多3秒
|
||
await _waitForStatus(SingboxStarted, maxSeconds: 3);
|
||
} else {
|
||
// 关闭连接
|
||
KRLogUtil.kr_i('🛑 开始断开连接...', tag: 'HomeController');
|
||
if (kDebugMode) {
|
||
}
|
||
await KRSingBoxImp.instance.kr_stop().timeout(
|
||
const Duration(seconds: 10),
|
||
onTimeout: () {
|
||
KRLogUtil.kr_e('⚠️ 停止操作超时', tag: 'HomeController');
|
||
throw TimeoutException('Stop operation timeout');
|
||
},
|
||
);
|
||
KRLogUtil.kr_i('✅ 断开命令已发送', tag: 'HomeController');
|
||
if (kDebugMode) {
|
||
}
|
||
|
||
// 🔧 修复: 等待状态更新,最多2秒
|
||
await _waitForStatus(SingboxStopped, maxSeconds: 2);
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 切换失败: $e', tag: 'HomeController');
|
||
if (kDebugMode) {
|
||
}
|
||
// 发生错误时强制同步状态
|
||
kr_forceSyncConnectionStatus();
|
||
}
|
||
|
||
if (kDebugMode) {
|
||
}
|
||
}
|
||
|
||
/// 🔧 等待状态达到预期值
|
||
Future<void> _waitForStatus(Type expectedType, {int maxSeconds = 3}) async {
|
||
if (kDebugMode) {
|
||
}
|
||
final startTime = DateTime.now();
|
||
|
||
while (DateTime.now().difference(startTime).inSeconds < maxSeconds) {
|
||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||
if (kDebugMode) {
|
||
}
|
||
|
||
if (currentStatus.runtimeType == expectedType) {
|
||
if (kDebugMode) {
|
||
}
|
||
// 强制同步确保 kr_isConnected 正确
|
||
kr_forceSyncConnectionStatus();
|
||
return;
|
||
}
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
}
|
||
|
||
if (kDebugMode) {
|
||
}
|
||
kr_forceSyncConnectionStatus();
|
||
}
|
||
|
||
/// 处理选择器代理
|
||
void _kr_handleSelectorProxy(dynamic element, List<dynamic> allGroups) {
|
||
try {
|
||
KRLogUtil.kr_d(
|
||
'处理选择器代理 - 当前选择: ${element.selected}, 用户选择: ${kr_cutTag.value}',
|
||
tag: 'HomeController');
|
||
|
||
|
||
// 🔧 关键修复:仅更新 UI 状态,不要触发重新选择,避免死循环
|
||
// 更新 kr_cutSeletedTag 以反映实际选中的节点
|
||
if (element.selected.isNotEmpty) {
|
||
kr_cutSeletedTag.value = element.selected;
|
||
}
|
||
|
||
// 如果用户手动选择了节点(不是auto)
|
||
if (kr_cutTag.value != "auto") {
|
||
_kr_handleManualMode(element);
|
||
return;
|
||
}
|
||
|
||
// 默认auto模式处理
|
||
_kr_handleAutoMode(element, allGroups);
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('处理选择器代理出错: $e', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
/// 处理手动模式
|
||
void _kr_handleManualMode(dynamic element) {
|
||
try {
|
||
KRLogUtil.kr_d('处理手动模式 - 选择: ${element.selected}', tag: 'HomeController');
|
||
|
||
// 🔧 关键修复:仅更新 UI 状态,不要重新选择节点,避免死循环
|
||
kr_cutSeletedTag.value = element.selected;
|
||
// 更新延迟值
|
||
_kr_updateNodeLatency(element);
|
||
kr_currentNodeName.value =
|
||
kr_truncateText(element.selected, maxLength: 25);
|
||
// kr_moveToSelectedNode();
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('处理手动模式出错: $e', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
/// 更新节点延迟
|
||
void _kr_updateNodeLatency(dynamic element) {
|
||
try {
|
||
bool delayUpdated = false;
|
||
for (var subElement in element.items) {
|
||
if (subElement.tag == element.selected) {
|
||
// 检查延迟是否有效
|
||
if (subElement.urlTestDelay != 0) {
|
||
kr_currentNodeLatency.value = subElement.urlTestDelay;
|
||
delayUpdated = true;
|
||
}
|
||
// 更新速度显示
|
||
// kr_updateSpeed(subElement.urlTestDelay);
|
||
// // 停止动画
|
||
// _kr_speedAnimationController.reverse();
|
||
KRLogUtil.kr_d('更新节点延迟: ${subElement.urlTestDelay}',
|
||
tag: 'HomeController');
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 🔧 修复:如果已连接但延迟未更新,设置为0
|
||
if (!delayUpdated && kr_isConnected.value && kr_currentNodeLatency.value == -1) {
|
||
KRLogUtil.kr_w('⚠️ 已连接但延迟未更新,设置为0', tag: 'HomeController');
|
||
kr_currentNodeLatency.value = 0;
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('更新节点延迟出错: $e', tag: 'HomeController');
|
||
// 🔧 修复:发生错误时,根据连接状态设置合适的值
|
||
if (kr_isConnected.value) {
|
||
kr_currentNodeLatency.value = 0; // 已连接但延迟未知
|
||
} else {
|
||
kr_currentNodeLatency.value = -2; // 未连接
|
||
}
|
||
kr_currentSpeed.value = "--";
|
||
// 停止动画
|
||
// _kr_speedAnimationController.reverse();
|
||
}
|
||
}
|
||
|
||
/// 处理自动模式
|
||
void _kr_handleAutoMode(dynamic element, List<dynamic> allGroups) {
|
||
KRLogUtil.kr_d('处理自动模式 - 活动组: ${allGroups.toString()}',
|
||
tag: 'HomeController');
|
||
|
||
// 更新auto模式的延迟
|
||
_kr_updateAutoLatency(element);
|
||
|
||
// 查找并处理urltest类型的组
|
||
for (var item in allGroups) {
|
||
if (item.type == ProxyType.urltest) {
|
||
// 检查延迟是否有效(小于65535)
|
||
if (item.selected != null && _kr_isValidLatency(item.selected)) {
|
||
kr_cutSeletedTag.value = item.selected;
|
||
|
||
kr_currentNodeName.value =
|
||
kr_truncateText("${item.selected}(auto)", maxLength: 25);
|
||
// kr_moveToSelectedNode();
|
||
kr_updateConnectionInfo();
|
||
break;
|
||
} else {
|
||
// 如果延迟无效,尝试选择延迟最小的节点
|
||
_kr_selectBestLatencyNode(item.items);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 选择延迟最小的节点
|
||
void _kr_selectBestLatencyNode(List<dynamic> items) {
|
||
int minDelay = 65535;
|
||
String? bestNode = null;
|
||
|
||
for (var item in items) {
|
||
// 只考虑有效的延迟值(小于65535且大于0)
|
||
if (item.urlTestDelay < minDelay &&
|
||
item.urlTestDelay < 65535 &&
|
||
item.urlTestDelay > 0) {
|
||
minDelay = item.urlTestDelay;
|
||
bestNode = item.tag;
|
||
}
|
||
}
|
||
|
||
if (bestNode != null) {
|
||
kr_cutSeletedTag.value = bestNode;
|
||
kr_currentNodeName.value =
|
||
kr_truncateText("${bestNode}(auto)", maxLength: 25);
|
||
// kr_moveToSelectedNode();
|
||
kr_updateConnectionInfo();
|
||
}
|
||
}
|
||
|
||
/// 更新连接信息
|
||
void kr_updateConnectionInfo() {
|
||
try {
|
||
final selectedNode = kr_subscribeService.keyList[kr_cutSeletedTag.value];
|
||
if (selectedNode != null) {
|
||
KRLogUtil.kr_d(
|
||
'更新节点信息 - 协议: ${selectedNode.protocol}, IP: ${selectedNode.serverAddr}',
|
||
tag: 'HomeController');
|
||
kr_currentProtocol.value =
|
||
kr_truncateText(selectedNode.protocol, maxLength: 15);
|
||
kr_currentIp.value =
|
||
kr_truncateText(selectedNode.serverAddr, maxLength: 20);
|
||
} else {
|
||
KRLogUtil.kr_d('未找到选中的节点: ${kr_cutSeletedTag.value}',
|
||
tag: 'HomeController');
|
||
kr_currentProtocol.value = "--";
|
||
kr_currentIp.value = "--";
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('更新连接信息失败: $e', tag: 'HomeController');
|
||
kr_currentProtocol.value = "--";
|
||
kr_currentIp.value = "--";
|
||
}
|
||
}
|
||
|
||
/// 处理文本截断
|
||
String kr_truncateText(String text, {int maxLength = 20}) {
|
||
if (text.length <= maxLength) return text;
|
||
return '${text.substring(0, maxLength)}...';
|
||
}
|
||
|
||
/// 检查延迟是否有效
|
||
bool _kr_isValidLatency(String? nodeTag) {
|
||
if (nodeTag == null) return false;
|
||
|
||
// 从keyList中获取节点信息
|
||
final node = kr_subscribeService.keyList[nodeTag];
|
||
if (node == null) return false;
|
||
|
||
// 检查延迟是否有效(小于65535且大于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) {
|
||
for (var subElement in element.items) {
|
||
if (subElement.tag == "auto") {
|
||
if (subElement.urlTestDelay != 0) {
|
||
kr_currentNodeLatency.value = subElement.urlTestDelay;
|
||
} else {
|
||
kr_currentNodeLatency.value = -2; // 当延迟为 0 时,设置为未连接状态
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 切换列表状态
|
||
void kr_switchListStatus(KRHomeViewsListStatus status) {
|
||
kr_currentListStatus.value = status;
|
||
}
|
||
|
||
// 切换订阅
|
||
|
||
Future<void> kr_switchSubscribe(
|
||
KRUserAvailableSubscribeItem subscribe) async {
|
||
try {
|
||
KRLogUtil.kr_i("kr_switchSubscribe", tag: "kr_switchSubscribe");
|
||
// 通知订阅服务切换订阅
|
||
await kr_subscribeService.kr_switchSubscribe(subscribe);
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('切换订阅失败: $e', tag: 'HomeController');
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
/// 🔧 修复:统一的节点切换方法(包含UI同步和后台操作等待)
|
||
/// 执行节点切换,包括UI更新和后台操作的完整同步
|
||
/// 返回 true 表示切换成功,false 表示失败
|
||
Future<bool> kr_performNodeSwitch(String tag) async {
|
||
try {
|
||
KRLogUtil.kr_i('🔄 开始切换节点: $tag', tag: 'HomeController');
|
||
|
||
// 1. 保存原节点,以备失败恢复
|
||
final originalTag = kr_cutTag.value;
|
||
|
||
// 2. 设置切换中状态
|
||
kr_cutTag.value = tag;
|
||
kr_currentNodeName.value = tag; // 🔧 修复:更新节点名称
|
||
|
||
// 3. 如果VPN未连接,只更新UI变量即可
|
||
if (!kr_isConnected.value) {
|
||
KRLogUtil.kr_i('📴 VPN未连接,只更新UI变量: $tag', tag: 'HomeController');
|
||
kr_cutSeletedTag.value = tag;
|
||
// kr_moveToSelectedNode();
|
||
|
||
// 🔧 修复:保存节点选择以便VPN启动时应用
|
||
KRLogUtil.kr_i('💾 保存节点选择以便稍后应用: $tag', tag: 'HomeController');
|
||
KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag).then((_) {
|
||
KRLogUtil.kr_i('✅ 节点选择已保存: $tag', tag: 'HomeController');
|
||
}).catchError((e) {
|
||
KRLogUtil.kr_e('❌ 保存节点选择失败: $e', tag: 'HomeController');
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
// 4. VPN已连接,需要重启VPN以断开现有连接并应用新节点
|
||
try {
|
||
KRLogUtil.kr_i('🔌 VPN已连接,将重启VPN以断开现有连接: $tag', tag: 'HomeController');
|
||
|
||
// 🔧 诊断:打印当前活动组信息
|
||
KRLogUtil.kr_i('📊 当前活动组数量: ${KRSingBoxImp.instance.kr_activeGroups.length}', tag: 'HomeController');
|
||
for (var group in KRSingBoxImp.instance.kr_activeGroups) {
|
||
KRLogUtil.kr_i('📋 活动组: tag=${group.tag}, type=${group.type}, 节点数=${group.items.length}', tag: 'HomeController');
|
||
for (var item in group.items) {
|
||
if (item.tag == tag) {
|
||
KRLogUtil.kr_i('✅ 找到目标节点: ${item.tag}', tag: 'HomeController');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 🔧 修复:VPN已连接时,设置延迟为-1(切换中状态),显示"正在连接"
|
||
kr_currentNodeLatency.value = -1;
|
||
kr_isLatency.value = true; // 显示加载动画
|
||
|
||
// 🔧 关键修复:保存新节点选择
|
||
KRLogUtil.kr_i('💾 保存新节点选择: $tag', tag: 'HomeController');
|
||
await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag);
|
||
|
||
// 🔧 方案A增强:重启VPN连接以断开所有现有长连接
|
||
KRLogUtil.kr_i('🔄 [增强] 停止VPN连接以断开现有连接...', tag: 'HomeController');
|
||
await KRSingBoxImp.instance.kr_stop(); // 先停止VPN
|
||
|
||
// 🚀 方案A增强:增加等待时间,确保所有连接完全释放
|
||
KRLogUtil.kr_i('⏳ [增强] 等待VPN完全停止(1500ms,确保旧连接全部断开)...', tag: 'HomeController');
|
||
await Future.delayed(const Duration(milliseconds: 1500)); // 从800ms增加到1500ms
|
||
|
||
KRLogUtil.kr_i('🔄 [增强] 启动VPN并应用新节点: $tag', tag: 'HomeController');
|
||
await KRSingBoxImp.instance.kr_start(); // 重新启动VPN,会自动使用新保存的节点
|
||
|
||
// 🚀 方案A增强:增加启动等待时间,确保新VPN完全建立
|
||
KRLogUtil.kr_i('⏳ [增强] 等待VPN完全启动(2500ms,确保新连接完全建立)...', tag: 'HomeController');
|
||
await Future.delayed(const Duration(milliseconds: 2500)); // 从1500ms增加到2500ms
|
||
|
||
// 后台切换成功,更新UI
|
||
kr_cutSeletedTag.value = tag;
|
||
kr_updateConnectionInfo();
|
||
// kr_moveToSelectedNode();
|
||
|
||
// 🚀 方案A增强:增加验证前等待时间,确保活动组完全更新
|
||
KRLogUtil.kr_i('⏳ [增强] 等待活动组更新(500ms)...', tag: 'HomeController');
|
||
await Future.delayed(const Duration(milliseconds: 500)); // 从200ms增加到500ms
|
||
|
||
// 🚀 方案A增强:验证节点是否真正切换成功
|
||
KRLogUtil.kr_i('🔍 [增强] 验证节点切换是否成功...', tag: 'HomeController');
|
||
try {
|
||
// 刷新活动组信息
|
||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||
final selectGroup = activeGroups.firstWhere(
|
||
(group) => group.tag == 'select',
|
||
orElse: () => throw Exception('未找到 select 组'),
|
||
);
|
||
|
||
KRLogUtil.kr_i('📊 [增强] Select组当前选中: ${selectGroup.selected}', tag: 'HomeController');
|
||
KRLogUtil.kr_i('📊 [增强] 目标节点: $tag', tag: 'HomeController');
|
||
|
||
if (selectGroup.selected != tag) {
|
||
KRLogUtil.kr_w('⚠️ [增强] 节点选择验证失败,实际选中: ${selectGroup.selected}', tag: 'HomeController');
|
||
// 不抛出异常,但记录警告
|
||
} else {
|
||
KRLogUtil.kr_i('✅ [增强] 节点选择验证成功!', tag: 'HomeController');
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_w('⚠️ [增强] 节点验证过程出错: $e', tag: 'HomeController');
|
||
}
|
||
|
||
// 更新延迟信息
|
||
_kr_updateLatencyOnConnected();
|
||
|
||
KRLogUtil.kr_i('✅ 节点切换成功(已重启VPN断开旧连接): $tag', tag: 'HomeController');
|
||
return true;
|
||
|
||
} catch (switchError) {
|
||
// 后台切换失败,恢复到原节点
|
||
KRLogUtil.kr_e('❌ 后台节点切换失败: $switchError', tag: 'HomeController');
|
||
|
||
// 恢复原状态
|
||
kr_cutTag.value = originalTag;
|
||
kr_currentNodeName.value = originalTag; // 🔧 修复:同时恢复节点名称显示
|
||
kr_currentNodeLatency.value = -2; // 恢复为未连接状态
|
||
|
||
// 恢复原节点选择
|
||
try {
|
||
await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: originalTag);
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 恢复节点选择失败: $e', tag: 'HomeController');
|
||
}
|
||
|
||
// 显示错误提示给用户
|
||
KRCommonUtil.kr_showToast('节点切换失败,已恢复为: $originalTag');
|
||
|
||
return false;
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 节点切换异常: $e', tag: 'HomeController');
|
||
kr_isLatency.value = false;
|
||
KRCommonUtil.kr_showToast('节点切换异常,请重试');
|
||
return false;
|
||
} finally {
|
||
// 关闭加载状态
|
||
kr_isLatency.value = false;
|
||
KRLogUtil.kr_i('🔄 节点切换流程完成', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
Future<void> onCountrySelected(String countryTag) async {
|
||
await KRSingBoxImp.instance.kr_selectCountry(countryTag);
|
||
KRCommonUtil.kr_showToast('已切换到 $countryTag 节点组');
|
||
}
|
||
|
||
/// 🔧 修复:简化的 kr_selectNode 方法
|
||
/// 现在只是委托给新的 kr_performNodeSwitch 方法
|
||
/// 为了保持向后兼容,保留此方法但改为调用新方法
|
||
Future<bool> kr_selectNode(String tag) async {
|
||
return await kr_performNodeSwitch(tag);
|
||
}
|
||
|
||
/// 获取当前节点国家
|
||
/// 🔧 修复:优先使用 kr_cutSeletedTag,避免依赖可能为空的 kr_activeGroups
|
||
/// 策略:
|
||
/// 1. 如果 kr_cutTag 不是 auto,直接使用(用户手动选择的节点)
|
||
/// 2. 如果是 auto,优先使用 kr_cutSeletedTag(保存了实际选中的节点)
|
||
/// 3. 如果 kr_cutSeletedTag 也是 auto 或空,再尝试从 kr_activeGroups 获取
|
||
String kr_getCurrentNodeCountry() {
|
||
KRLogUtil.kr_i('========== 开始获取国家代码 ==========', tag: 'getCurrentNodeCountry');
|
||
KRLogUtil.kr_i('kr_cutTag: ${kr_cutTag.value}', tag: 'getCurrentNodeCountry');
|
||
KRLogUtil.kr_i('kr_cutSeletedTag: ${kr_cutSeletedTag.value}', tag: 'getCurrentNodeCountry');
|
||
KRLogUtil.kr_i('keyList 节点总数: ${kr_subscribeService.keyList.length}', tag: 'getCurrentNodeCountry');
|
||
|
||
String actualTag;
|
||
|
||
// 🔧 优先策略:
|
||
// 1. 如果 kr_cutTag 不是 auto,直接使用(用户手动选择的节点)
|
||
if (kr_cutTag.value != 'auto' && kr_cutTag.value != 'select' && kr_cutTag.value.isNotEmpty) {
|
||
// 用户手动选择了具体节点
|
||
actualTag = kr_cutTag.value;
|
||
KRLogUtil.kr_i('✅ 使用手动选择的节点: $actualTag', tag: 'getCurrentNodeCountry');
|
||
}
|
||
// 2. 如果是 auto,优先使用 kr_cutSeletedTag(保存了实际选中的节点)
|
||
else if (kr_cutSeletedTag.value.isNotEmpty &&
|
||
kr_cutSeletedTag.value != 'auto' &&
|
||
kr_cutSeletedTag.value != 'select') {
|
||
// auto 模式下,使用保存的实际节点
|
||
actualTag = kr_cutSeletedTag.value;
|
||
KRLogUtil.kr_i('✅ 使用 auto 模式下的实际节点 (kr_cutSeletedTag): $actualTag', tag: 'getCurrentNodeCountry');
|
||
}
|
||
// 3. 降级:尝试从活动组获取
|
||
else {
|
||
try {
|
||
KRLogUtil.kr_i('⚠️ 尝试从活动组获取实际节点', tag: 'getCurrentNodeCountry');
|
||
KRLogUtil.kr_i('活动组数量: ${KRSingBoxImp.instance.kr_activeGroups.length}', tag: 'getCurrentNodeCountry');
|
||
|
||
// 🔧 修复:活动组为空时,尝试使用 allGroups
|
||
if (KRSingBoxImp.instance.kr_activeGroups.isEmpty) {
|
||
print('[getCurrentNodeCountry] ⚠️ 活动组为空,尝试使用 allGroups');
|
||
KRLogUtil.kr_w('⚠️ 活动组为空,尝试使用 allGroups', tag: 'getCurrentNodeCountry');
|
||
|
||
final allGroups = KRSingBoxImp.instance.kr_allGroups;
|
||
print('[getCurrentNodeCountry] allGroups 数量: ${allGroups.length}');
|
||
if (allGroups.isEmpty) {
|
||
print('[getCurrentNodeCountry] ❌ allGroups 也为空,返回空字符串');
|
||
KRLogUtil.kr_w('❌ allGroups 也为空,返回空字符串', tag: 'getCurrentNodeCountry');
|
||
return '';
|
||
}
|
||
|
||
// 从 allGroups 中查找 select 组
|
||
final selectGroup = allGroups.firstWhere(
|
||
(group) => group.tag == 'select',
|
||
orElse: () => throw Exception('未找到 select 组'),
|
||
);
|
||
print('[getCurrentNodeCountry] selectGroup.selected: ${selectGroup.selected}');
|
||
|
||
if (selectGroup.selected.isEmpty || selectGroup.selected == 'auto' || selectGroup.selected == 'select') {
|
||
print('[getCurrentNodeCountry] select 组选中的是 auto,查找 urltest 组');
|
||
// 如果 select 组选中的是 auto,从 urltest 组获取
|
||
final urlTestGroup = allGroups.firstWhere(
|
||
(group) => group.type == ProxyType.urltest,
|
||
orElse: () => throw Exception('未找到 urltest 组'),
|
||
);
|
||
|
||
if (urlTestGroup.selected.isNotEmpty) {
|
||
actualTag = urlTestGroup.selected;
|
||
print('[getCurrentNodeCountry] ✅ 从 allGroups 的 urltest 组获取节点: $actualTag');
|
||
KRLogUtil.kr_i('✅ 从 allGroups 的 urltest 组获取节点: $actualTag', tag: 'getCurrentNodeCountry');
|
||
} else {
|
||
print('[getCurrentNodeCountry] ❌ urltest 组的 selected 也为空');
|
||
KRLogUtil.kr_w('❌ urltest 组的 selected 也为空', tag: 'getCurrentNodeCountry');
|
||
return '';
|
||
}
|
||
} else {
|
||
actualTag = selectGroup.selected;
|
||
print('[getCurrentNodeCountry] ✅ 从 allGroups 的 select 组获取节点: $actualTag');
|
||
KRLogUtil.kr_i('✅ 从 allGroups 的 select 组获取节点: $actualTag', tag: 'getCurrentNodeCountry');
|
||
}
|
||
} else {
|
||
// 活动组不为空,从活动组获取
|
||
// 从 SingBox 活动组中找到 "select" 选择器组
|
||
final selectGroup = KRSingBoxImp.instance.kr_activeGroups.firstWhere(
|
||
(group) => group.tag == 'select',
|
||
orElse: () => throw Exception('未找到 select 组'),
|
||
);
|
||
|
||
if (selectGroup.selected.isEmpty) {
|
||
KRLogUtil.kr_w('❌ select 组的 selected 为空', tag: 'getCurrentNodeCountry');
|
||
return '';
|
||
}
|
||
|
||
actualTag = selectGroup.selected;
|
||
KRLogUtil.kr_i('✅ 从活动组获取节点: $actualTag', tag: 'getCurrentNodeCountry');
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 从活动组获取节点失败: $e', tag: 'getCurrentNodeCountry');
|
||
return '';
|
||
}
|
||
}
|
||
|
||
// 验证 actualTag 是否有效
|
||
if (actualTag.isEmpty) {
|
||
KRLogUtil.kr_w('❌ 节点标签为空', tag: 'getCurrentNodeCountry');
|
||
return '';
|
||
}
|
||
|
||
// 使用实际节点标签查找国家代码
|
||
KRLogUtil.kr_i('查找节点: $actualTag', tag: 'getCurrentNodeCountry');
|
||
final node = kr_subscribeService.keyList[actualTag];
|
||
|
||
if (node == null) {
|
||
KRLogUtil.kr_e('❌ 节点未找到: $actualTag', tag: 'getCurrentNodeCountry');
|
||
KRLogUtil.kr_e('keyList 中的所有节点标签:', tag: 'getCurrentNodeCountry');
|
||
int count = 0;
|
||
for (var key in kr_subscribeService.keyList.keys) {
|
||
count++;
|
||
KRLogUtil.kr_e(' [$count] $key -> country: ${kr_subscribeService.keyList[key]?.country}', tag: 'getCurrentNodeCountry');
|
||
if (count >= 10) {
|
||
KRLogUtil.kr_e(' ... 还有 ${kr_subscribeService.keyList.length - 10} 个节点', tag: 'getCurrentNodeCountry');
|
||
break;
|
||
}
|
||
}
|
||
return '';
|
||
}
|
||
|
||
KRLogUtil.kr_i('✅ 找到节点: $actualTag', tag: 'getCurrentNodeCountry');
|
||
KRLogUtil.kr_i(' - city: ${node.city}', tag: 'getCurrentNodeCountry');
|
||
KRLogUtil.kr_i(' - country: "${node.country}"', tag: 'getCurrentNodeCountry');
|
||
KRLogUtil.kr_i(' - country.isEmpty: ${node.country.isEmpty}', tag: 'getCurrentNodeCountry');
|
||
KRLogUtil.kr_i(' - country.length: ${node.country.length}', tag: 'getCurrentNodeCountry');
|
||
KRLogUtil.kr_i('========== 国家代码获取结束 ==========', tag: 'getCurrentNodeCountry');
|
||
|
||
return node.country;
|
||
}
|
||
|
||
|
||
/// 获取真实连接的节点信息(auto 模式下获取实际连接的节点)
|
||
Map<String, dynamic> kr_getRealConnectedNodeInfo() {
|
||
// 如果不是 auto 模式,直接返回当前选中的节点信息
|
||
if (kr_cutTag.value != 'auto') {
|
||
final node = kr_subscribeService.keyList[kr_cutSeletedTag.value];
|
||
return {
|
||
'nodeName': kr_cutSeletedTag.value,
|
||
'delay': node?.urlTestDelay.value ?? -2,
|
||
'country': node?.country ?? '',
|
||
};
|
||
}
|
||
|
||
// auto 模式下,获取 urltest 组的实际连接节点
|
||
print('当前活动组----${KRSingBoxImp.instance.kr_activeGroups.length}');
|
||
for (var group in KRSingBoxImp.instance.kr_activeGroups) {
|
||
print('当前活动组----$group}');
|
||
if (group.type == ProxyType.urltest) {
|
||
final selectedNode = group.selected;
|
||
final node = kr_subscribeService.keyList[selectedNode];
|
||
return {
|
||
'nodeName': selectedNode,
|
||
'delay': node?.urlTestDelay.value ?? -2,
|
||
'country': node?.country ?? '',
|
||
};
|
||
}
|
||
}
|
||
print('hhhhhh${kr_subscribeService.keyList}', );
|
||
|
||
// 如果没有找到 urltest 组,返回默认值
|
||
return {
|
||
'nodeName': 'auto',
|
||
'delay': -2,
|
||
'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() {
|
||
final info = kr_getRealConnectedNodeInfo();
|
||
final delay = kr_currentNodeLatency.value;
|
||
final country1 = kr_getCurrentNodeCountry();
|
||
print('country----$country1');
|
||
final country = kr_getCountryFullName(info['country']);
|
||
if (delay == -2) {
|
||
return '--';
|
||
} else if (delay == -1) {
|
||
return '${country} ${AppTranslations.kr_home.connecting}';
|
||
} else if (delay == 0) {
|
||
return '${country} ${AppTranslations.kr_home.connected}';
|
||
} else if (delay >= 3000) {
|
||
return '${country} ${AppTranslations.kr_home.timeout}';
|
||
} else {
|
||
return '${country} ${delay}ms';
|
||
}
|
||
}
|
||
|
||
|
||
// 格式化字节数
|
||
String kr_formatBytes(int bytes) {
|
||
if (bytes < 1024) {
|
||
return '$bytes B';
|
||
} else if (bytes < 1024 * 1024) {
|
||
return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||
} else if (bytes < 1024 * 1024 * 1024) {
|
||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||
} else {
|
||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
|
||
}
|
||
}
|
||
|
||
// 设置当前选中的组
|
||
void kr_setCurrentGroup(dynamic group) {
|
||
try {
|
||
KRLogUtil.kr_i('设置当前组: ${group.tag}', tag: 'HomeController');
|
||
kr_currentGroup.value = group;
|
||
update(); // 通知 GetBuilder 更新
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('设置当前组失败: $e', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
/// 获取国家全称
|
||
/// [countryCode] 国家代码(大小写不敏感)
|
||
String kr_getCountryFullName(String countryCode) {
|
||
final Map<String, String> countryNames = {
|
||
'CN': '中国',
|
||
'HK': '香港',
|
||
'TW': '台湾',
|
||
'MO': '澳门',
|
||
'US': '美国',
|
||
'JP': '日本',
|
||
'KR': '韩国',
|
||
'SG': '新加坡',
|
||
'MY': '马来西亚',
|
||
'TH': '泰国',
|
||
'VN': '越南',
|
||
'ID': '印度尼西亚',
|
||
'PH': '菲律宾',
|
||
'IN': '印度',
|
||
'RU': '俄罗斯',
|
||
'GB': '英国',
|
||
'DE': '德国',
|
||
'FR': '法国',
|
||
'IT': '意大利',
|
||
'ES': '西班牙',
|
||
'NL': '荷兰',
|
||
'CH': '瑞士',
|
||
'SE': '瑞典',
|
||
'NO': '挪威',
|
||
'FI': '芬兰',
|
||
'DK': '丹麦',
|
||
'IE': '爱尔兰',
|
||
'AT': '奥地利',
|
||
'PT': '葡萄牙',
|
||
'PL': '波兰',
|
||
'UA': '乌克兰',
|
||
'CA': '加拿大',
|
||
'MX': '墨西哥',
|
||
'BR': '巴西',
|
||
'AR': '阿根廷',
|
||
'AU': '澳大利亚',
|
||
'NZ': '新西兰',
|
||
'ZA': '南非',
|
||
'AE': '阿拉伯联合酋长国',
|
||
'IL': '以色列',
|
||
'TR': '土耳其',
|
||
};
|
||
|
||
final String code = countryCode.toUpperCase();
|
||
return countryNames[code] ?? 'Unknown Country';
|
||
}
|
||
|
||
@override
|
||
void onReady() {
|
||
super.onReady();
|
||
}
|
||
|
||
@override
|
||
void onClose() {
|
||
// 注销应用生命周期监听
|
||
WidgetsBinding.instance.removeObserver(this);
|
||
super.onClose();
|
||
}
|
||
|
||
/// 应用生命周期状态变化处理
|
||
@override
|
||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||
super.didChangeAppLifecycleState(state);
|
||
|
||
KRLogUtil.kr_i('应用生命周期状态变化: $state', tag: 'QuickConnect');
|
||
|
||
switch (state) {
|
||
case AppLifecycleState.resumed:
|
||
// 应用从后台恢复到前台,同步连接状态
|
||
KRLogUtil.kr_i('应用恢复到前台,同步连接状态', tag: 'QuickConnect');
|
||
_onAppResumed();
|
||
break;
|
||
case AppLifecycleState.paused:
|
||
// 应用进入后台
|
||
KRLogUtil.kr_i('应用进入后台', tag: 'QuickConnect');
|
||
break;
|
||
case AppLifecycleState.detached:
|
||
// 应用被销毁
|
||
KRLogUtil.kr_i('应用被销毁', tag: 'QuickConnect');
|
||
break;
|
||
case AppLifecycleState.inactive:
|
||
// 应用处于非活动状态(如来电话时)
|
||
KRLogUtil.kr_i('应用处于非活动状态', tag: 'QuickConnect');
|
||
break;
|
||
case AppLifecycleState.hidden:
|
||
// 应用被隐藏
|
||
KRLogUtil.kr_i('应用被隐藏', tag: 'QuickConnect');
|
||
break;
|
||
}
|
||
}
|
||
|
||
/// 应用恢复到前台时的处理
|
||
void _onAppResumed() {
|
||
// 同步连接状态,确保UI显示正确
|
||
Future.delayed(const Duration(milliseconds: 500), () {
|
||
kr_forceSyncConnectionStatus();
|
||
});
|
||
}
|
||
|
||
// 更新底部面板高度
|
||
void kr_updateBottomPanelHeight() {
|
||
// 🔧 Android 15 优化:加载状态时也允许更新高度,显示加载指示器
|
||
if (kr_subscribeService.kr_currentStatus ==
|
||
KRHomeViewsListStatus.kr_loading &&
|
||
kr_currentListStatus.value != KRHomeViewsListStatus.kr_loading) {
|
||
return;
|
||
}
|
||
|
||
KRLogUtil.kr_i('更新底部面板高度', tag: 'HomeController');
|
||
KRLogUtil.kr_i('当前视图状态: ${kr_currentViewStatus.value}',
|
||
tag: 'HomeController');
|
||
|
||
KRLogUtil.kr_i('当前列表状态: ${kr_currentListStatus.value}',
|
||
tag: 'HomeController');
|
||
KRLogUtil.kr_i('是否试用: ${kr_subscribeService.kr_isTrial.value}',
|
||
tag: 'HomeController');
|
||
KRLogUtil.kr_i(
|
||
'是否最后一天: ${kr_subscribeService.kr_isLastDayOfSubscription.value}',
|
||
tag: 'HomeController');
|
||
|
||
double targetHeight = 0.0;
|
||
|
||
if (kr_currentViewStatus.value == KRHomeViewsStatus.kr_notLoggedIn) {
|
||
// 未登录状态下,高度由内容撑开
|
||
targetHeight = kr_subscriptionCardHeight +
|
||
kr_baseHeight +
|
||
kr_marginTop +
|
||
kr_marginBottom +
|
||
kr_marginVertical * 2;
|
||
KRLogUtil.kr_i(
|
||
'未登录状态,目标高度: $targetHeight', tag: 'HomeController');
|
||
} else if (kr_currentListStatus.value ==
|
||
KRHomeViewsListStatus.kr_serverList ||
|
||
kr_currentListStatus.value ==
|
||
KRHomeViewsListStatus.kr_countrySubscribeList ||
|
||
kr_currentListStatus.value ==
|
||
KRHomeViewsListStatus.kr_serverSubscribeList ||
|
||
kr_currentListStatus.value == KRHomeViewsListStatus.kr_subscribeList) {
|
||
targetHeight = kr_nodeListHeight + kr_marginVertical * 2;
|
||
KRLogUtil.kr_i('节点列表状态,目标高度: $targetHeight', tag: 'HomeController');
|
||
}
|
||
// 🔧 Android 15 新增:处理加载和错误状态
|
||
else if (kr_currentListStatus.value == KRHomeViewsListStatus.kr_loading ||
|
||
kr_currentListStatus.value == KRHomeViewsListStatus.kr_error) {
|
||
// 加载或错误状态下显示最小高度
|
||
targetHeight = kr_loadingHeight + kr_marginTop + kr_marginBottom;
|
||
KRLogUtil.kr_i('加载/错误状态,目标高度: $targetHeight', tag: 'HomeController');
|
||
}
|
||
else {
|
||
// 已登录状态下的默认高度计算
|
||
targetHeight = kr_baseHeight + kr_marginTop + kr_marginBottom;
|
||
KRLogUtil.kr_i('基础高度: $targetHeight', tag: 'HomeController');
|
||
|
||
// 🔧 关键修复:增加防御性检查,确保订阅服务访问异常时高度计算仍正常
|
||
try {
|
||
if (kr_subscribeService.kr_currentSubscribe.value != null) {
|
||
targetHeight += kr_connectionInfoHeight + kr_marginTop;
|
||
KRLogUtil.kr_i('添加连接信息卡片高度: $targetHeight', tag: 'HomeController');
|
||
} else {
|
||
targetHeight += kr_subscriptionCardHeight + kr_marginTop;
|
||
KRLogUtil.kr_i('添加订阅卡片高度: $targetHeight', tag: 'HomeController');
|
||
}
|
||
|
||
// 如果有试用状态,添加试用卡片高度
|
||
if (kr_subscribeService.kr_isTrial.value) {
|
||
targetHeight += kr_trialCardHeight + kr_marginTop;
|
||
KRLogUtil.kr_i('添加试用卡片高度: $targetHeight', tag: 'HomeController');
|
||
}
|
||
// 如果是最后一天,添加最后一天卡片高度
|
||
else if (kr_subscribeService.kr_isLastDayOfSubscription.value) {
|
||
targetHeight += kr_lastDayCardHeight + kr_marginTop;
|
||
KRLogUtil.kr_i('添加最后一天卡片高度: $targetHeight', tag: 'HomeController');
|
||
}
|
||
} catch (e) {
|
||
// 🔧 修复:订阅服务访问异常时,使用默认订阅卡片高度
|
||
KRLogUtil.kr_e('访问订阅服务数据异常,使用默认高度: $e', tag: 'HomeController');
|
||
targetHeight += kr_subscriptionCardHeight + kr_marginTop;
|
||
KRLogUtil.kr_i('使用默认订阅卡片高度: $targetHeight', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
// 🔧 Android 15 优化:确保最小高度,避免出现 0 高度
|
||
if (targetHeight < 100) {
|
||
KRLogUtil.kr_w('计算的高度过小($targetHeight),设置为最小高度', tag: 'HomeController');
|
||
targetHeight = kr_loadingHeight;
|
||
}
|
||
|
||
KRLogUtil.kr_i('最终目标高度: $targetHeight', tag: 'HomeController');
|
||
kr_bottomPanelHeight.value = targetHeight;
|
||
}
|
||
|
||
// 移动到选中节点
|
||
void kr_moveToSelectedNode() {
|
||
try {
|
||
if (kr_cutSeletedTag.isEmpty) return;
|
||
|
||
final selectedNode = kr_subscribeService.keyList[kr_cutSeletedTag.value];
|
||
if (selectedNode == null) return;
|
||
|
||
final location = LatLng(selectedNode.latitude, selectedNode.longitude);
|
||
kr_moveToLocation(location);
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('移动到选中节点失败: $e', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
// 简化移动地图方法
|
||
void kr_moveToLocation(LatLng location, [double zoom = 5.0]) {
|
||
try {
|
||
// 🔧 关键修复:约束坐标到有效范围,防止超出地图边界
|
||
final constrainedLocation = _constrainCoordinates(location);
|
||
kr_mapController.move(constrainedLocation, zoom);
|
||
kr_isUserMoving.value = false;
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('移动地图失败: $e', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
/// 🔧 新增:约束坐标到有效范围内
|
||
/// 确保坐标在 [-85, 85] 纬度和 [-180, 180] 经度范围内
|
||
LatLng _constrainCoordinates(LatLng coords) {
|
||
double lat = coords.latitude.clamp(-85.0, 85.0);
|
||
double lng = coords.longitude.clamp(-180.0, 180.0);
|
||
|
||
if (lat != coords.latitude || lng != coords.longitude) {
|
||
KRLogUtil.kr_w(
|
||
'⚠️ 坐标超出范围,已约束: (${coords.latitude}, ${coords.longitude}) → ($lat, $lng)',
|
||
tag: 'HomeController'
|
||
);
|
||
}
|
||
|
||
return LatLng(lat, lng);
|
||
}
|
||
|
||
// 添加一个方法来批量更新标记
|
||
void kr_updateMarkers(List<String> tags) {
|
||
// 使用Set来去重
|
||
final Set<String> updateIds = tags.toSet();
|
||
// 一次性更新所有需要更新的标记
|
||
update(updateIds.toList());
|
||
|
||
// 延迟2秒后关闭加载状态
|
||
Future.delayed(const Duration(seconds: 1), () {
|
||
kr_isLatency.value = false;
|
||
});
|
||
}
|
||
|
||
/// 刷新地图标记
|
||
void showMarkersMap() {
|
||
KRLogUtil.kr_i('========== 刷新地图标记 ==========', tag: 'HomeController');
|
||
KRLogUtil.kr_i('当前选中节点: ${kr_cutSeletedTag.value}', tag: 'HomeController');
|
||
KRLogUtil.kr_i('可用节点数: ${kr_subscribeService.allList.length}', tag: 'HomeController');
|
||
KRLogUtil.kr_i('国家分组数: ${kr_subscribeService.countryOutboundList.length}', tag: 'HomeController');
|
||
// 手动触发地图标记更新
|
||
update(['map_markers']);
|
||
KRLogUtil.kr_i('✅ 地图标记更新完成', tag: 'HomeController');
|
||
}
|
||
|
||
/// 选择地图标记
|
||
void selectMarkerMap(int index) {
|
||
try {
|
||
if (index >= 0 && index < kr_subscribeService.allList.length) {
|
||
// 重置所有节点的选中状态
|
||
for (var item in kr_subscribeService.allList) {
|
||
item.selected = 0;
|
||
}
|
||
// 设置当前节点为选中状态
|
||
kr_subscribeService.allList[index].selected = 1;
|
||
// 手动触发更新
|
||
update(['map_markers']);
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('选择地图标记失败: $e', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
/// 手动触发 SingBox URL 测试(调试用)
|
||
Future<void> kr_manualUrlTest() async {
|
||
try {
|
||
KRLogUtil.kr_i('🔧 手动触发 SingBox URL 测试...', tag: 'HomeController');
|
||
|
||
// 直接调用 SingBox 的 URL 测试
|
||
await KRSingBoxImp.instance.kr_urlTest("auto");
|
||
|
||
// 等待测试完成
|
||
await Future.delayed(const Duration(seconds: 5));
|
||
|
||
// 检查结果
|
||
KRLogUtil.kr_i('📊 检查手动测试结果...', tag: 'HomeController');
|
||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||
for (int i = 0; i < activeGroups.length; i++) {
|
||
final group = activeGroups[i];
|
||
KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
|
||
for (int j = 0; j < group.items.length; j++) {
|
||
final item = group.items[j];
|
||
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'HomeController');
|
||
}
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 手动 URL 测试失败: $e', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
/// 强制使用直接连接测试(绕过 SingBox URL 测试)
|
||
Future<void> kr_forceDirectTest() async {
|
||
try {
|
||
KRLogUtil.kr_i('🔧 强制使用直接连接测试...', tag: 'HomeController');
|
||
|
||
// 使用直接连接测试所有节点
|
||
await _kr_testLatencyWithoutVpn();
|
||
|
||
KRLogUtil.kr_i('✅ 直接连接测试完成', tag: 'HomeController');
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 直接连接测试失败: $e', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
/// 测试延迟
|
||
Future<void> kr_urlTest() async {
|
||
kr_isLatency.value = true;
|
||
|
||
try {
|
||
KRLogUtil.kr_i('🧪 开始延迟测试...', tag: 'HomeController');
|
||
KRLogUtil.kr_i('📊 当前连接状态: ${kr_isConnected.value}', tag: 'HomeController');
|
||
|
||
if (kr_isConnected.value) {
|
||
// 已连接状态:使用 SingBox 通过代理测试
|
||
KRLogUtil.kr_i('🔗 已连接状态 - 使用 SingBox 通过代理测试延迟', tag: 'HomeController');
|
||
await KRSingBoxImp.instance.kr_urlTest("select");
|
||
|
||
// 等待一段时间让 SingBox 完成测试
|
||
await Future.delayed(const Duration(seconds: 3));
|
||
|
||
// 再次检查活动组状态
|
||
KRLogUtil.kr_i('🔄 检查代理测试后的活动组状态...', tag: 'HomeController');
|
||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||
for (int i = 0; i < activeGroups.length; i++) {
|
||
final group = activeGroups[i];
|
||
KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
|
||
for (int j = 0; j < group.items.length; j++) {
|
||
final item = group.items[j];
|
||
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'HomeController');
|
||
}
|
||
}
|
||
} else {
|
||
// 未连接状态:使用本机网络直接ping节点IP
|
||
KRLogUtil.kr_i('🔌 未连接状态 - 使用本机网络直接ping节点IP测试延迟', tag: 'HomeController');
|
||
KRLogUtil.kr_i('🌐 这将绕过代理,直接使用本机网络连接节点', tag: 'HomeController');
|
||
await _kr_testLatencyWithoutVpn();
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 延迟测试失败: $e', tag: 'HomeController');
|
||
} finally {
|
||
// 延迟1秒后关闭加载状态
|
||
Future.delayed(const Duration(seconds: 1), () {
|
||
kr_isLatency.value = false;
|
||
});
|
||
}
|
||
}
|
||
|
||
/// 未连接状态下的真实延迟测试(TCP连接测试)
|
||
Future<void> _kr_testLatencyWithoutVpn() async {
|
||
kr_isLatency.value = true;
|
||
try {
|
||
KRLogUtil.kr_i('🚀 开始真实延迟测试(TCP连接测试)', tag: 'HomeController');
|
||
KRLogUtil.kr_i('📊 当前连接状态: ${kr_isConnected.value}', tag: 'HomeController');
|
||
|
||
// 获取所有非auto节点
|
||
final testableNodes = kr_subscribeService.allList
|
||
.where((item) => item.tag != 'auto')
|
||
.toList();
|
||
|
||
KRLogUtil.kr_i('📋 找到 ${testableNodes.length} 个可测试节点', tag: 'HomeController');
|
||
|
||
if (testableNodes.isEmpty) {
|
||
KRLogUtil.kr_w('⚠️ 没有可测试的节点', tag: 'HomeController');
|
||
return;
|
||
}
|
||
|
||
// 准备节点地址列表
|
||
final nodeAddresses = <MapEntry<String, SocketAddress>>[];
|
||
|
||
for (final node in testableNodes) {
|
||
// 从节点配置中提取服务器地址和端口
|
||
try {
|
||
String host = node.serverAddr;
|
||
int port = 0;
|
||
|
||
// 尝试从config中获取端口
|
||
if (node.config.containsKey('server_port')) {
|
||
port = node.config['server_port'] as int;
|
||
} else if (node.config.containsKey('port')) {
|
||
port = node.config['port'] as int;
|
||
}
|
||
|
||
if (host.isNotEmpty && port > 0) {
|
||
nodeAddresses.add(MapEntry(
|
||
node.tag,
|
||
SocketAddress(host, port),
|
||
));
|
||
KRLogUtil.kr_i('✓ 节点 ${node.tag}: $host:$port', tag: 'HomeController');
|
||
} else {
|
||
KRLogUtil.kr_w('⚠️ 节点 ${node.tag} 缺少地址或端口信息', tag: 'HomeController');
|
||
// 设置为失败
|
||
node.urlTestDelay.value = 65535;
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 解析节点 ${node.tag} 配置失败: $e', tag: 'HomeController');
|
||
node.urlTestDelay.value = 65535;
|
||
}
|
||
}
|
||
|
||
if (nodeAddresses.isEmpty) {
|
||
KRLogUtil.kr_w('⚠️ 没有有效的节点地址可测试', tag: 'HomeController');
|
||
return;
|
||
}
|
||
|
||
KRLogUtil.kr_i('🔌 开始批量测试 ${nodeAddresses.length} 个节点...', tag: 'HomeController');
|
||
|
||
// 使用真实的延迟测试工具
|
||
final results = await KRLatencyTester.testMultipleNodes(
|
||
nodes: nodeAddresses,
|
||
concurrency: 10, // 每批10个并发
|
||
timeout: 5000, // 超时时间5秒(毫秒)
|
||
);
|
||
|
||
// 更新节点延迟
|
||
for (final node in testableNodes) {
|
||
if (results.containsKey(node.tag)) {
|
||
node.urlTestDelay.value = results[node.tag]!;
|
||
}
|
||
}
|
||
|
||
// 统计测试结果
|
||
final successCount = testableNodes.where((item) => item.urlTestDelay.value < 65535).length;
|
||
final failCount = testableNodes.length - successCount;
|
||
|
||
KRLogUtil.kr_i('✅ 真实延迟测试完成', tag: 'HomeController');
|
||
KRLogUtil.kr_i('📊 测试结果: 成功 $successCount 个,失败 $failCount 个', tag: 'HomeController');
|
||
|
||
// 显示延迟最低的前3个节点
|
||
final sortedNodes = testableNodes
|
||
.where((item) => item.urlTestDelay.value < 65535)
|
||
.toList()
|
||
..sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value));
|
||
|
||
if (sortedNodes.isNotEmpty) {
|
||
KRLogUtil.kr_i('🏆 延迟最低的前3个节点:', tag: 'HomeController');
|
||
for (int i = 0; i < 3 && i < sortedNodes.length; i++) {
|
||
final node = sortedNodes[i];
|
||
KRLogUtil.kr_i(' ${i + 1}. ${node.tag}: ${node.urlTestDelay.value}ms', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 真实延迟测试过程出错: $e', tag: 'HomeController');
|
||
KRLogUtil.kr_e('❌ 错误堆栈: ${StackTrace.current}', tag: 'HomeController');
|
||
} finally {
|
||
kr_isLatency.value = false;
|
||
}
|
||
}
|
||
|
||
/// 开始连接计时
|
||
void kr_startConnectionTimer() {
|
||
kr_stopConnectionTimer();
|
||
_kr_connectionSeconds = 0;
|
||
_kr_connectionTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||
_kr_connectionSeconds++;
|
||
kr_connectionTime.value = kr_formatDuration(_kr_connectionSeconds);
|
||
KRLogUtil.kr_i(kr_connectText.value);
|
||
});
|
||
}
|
||
|
||
/// 停止连接计时
|
||
void kr_stopConnectionTimer() {
|
||
_kr_connectionTimer?.cancel();
|
||
_kr_connectionTimer = null;
|
||
}
|
||
|
||
/// 格式化时长
|
||
String kr_formatDuration(int seconds) {
|
||
final hours = seconds ~/ 3600;
|
||
final minutes = (seconds % 3600) ~/ 60;
|
||
final remainingSeconds = seconds % 60;
|
||
|
||
return '${hours.toString().padLeft(2, '0')}:'
|
||
'${minutes.toString().padLeft(2, '0')}:'
|
||
'${remainingSeconds.toString().padLeft(2, '0')}';
|
||
}
|
||
|
||
/// 重置连接信息
|
||
void kr_resetConnectionInfo() {
|
||
kr_currentIp.value = AppTranslations.kr_home.disconnected;
|
||
kr_currentProtocol.value = AppTranslations.kr_home.disconnected;
|
||
kr_currentSpeed.value = "--";
|
||
kr_connectionTime.value = '00:00:00';
|
||
_kr_connectionSeconds = 0;
|
||
kr_currentNodeLatency.value = -2; // 设置为未连接状态
|
||
}
|
||
|
||
/// 调试:打印所有节点的坐标信息
|
||
void kr_debugPrintNodeCoordinates() {
|
||
KRLogUtil.kr_i('========== 节点坐标调试信息 ==========', tag: 'HomeController');
|
||
KRLogUtil.kr_i('节点总数: ${kr_subscribeService.allList.length}', tag: 'HomeController');
|
||
|
||
if (kr_subscribeService.allList.isEmpty) {
|
||
KRLogUtil.kr_w('节点列表为空!请检查:', tag: 'HomeController');
|
||
KRLogUtil.kr_w('1. 是否已登录', tag: 'HomeController');
|
||
KRLogUtil.kr_w('2. 是否有订阅', tag: 'HomeController');
|
||
KRLogUtil.kr_w('3. 订阅是否已加载完成', tag: 'HomeController');
|
||
return;
|
||
}
|
||
|
||
int validNodes = 0;
|
||
int invalidNodes = 0;
|
||
|
||
for (int i = 0; i < kr_subscribeService.allList.length; i++) {
|
||
final node = kr_subscribeService.allList[i];
|
||
if (node.latitude != 0.0 || node.longitude != 0.0) {
|
||
validNodes++;
|
||
if (i < 5) { // 只打印前5个有效节点
|
||
KRLogUtil.kr_i('节点[$i] ${node.tag}: (${node.latitude}, ${node.longitude})', tag: 'HomeController');
|
||
}
|
||
} else {
|
||
invalidNodes++;
|
||
if (i < 3) { // 只打印前3个无效节点
|
||
KRLogUtil.kr_w('节点[$i] ${node.tag}: 坐标为(0, 0) - 无效!', tag: 'HomeController');
|
||
}
|
||
}
|
||
}
|
||
|
||
KRLogUtil.kr_i('有效节点: $validNodes', tag: 'HomeController');
|
||
KRLogUtil.kr_w('无效节点(坐标为0): $invalidNodes', tag: 'HomeController');
|
||
|
||
if (invalidNodes > 0) {
|
||
KRLogUtil.kr_w('⚠️ 发现 $invalidNodes 个节点坐标为0,这些节点不会显示在地图上', tag: 'HomeController');
|
||
KRLogUtil.kr_w('可能原因:', tag: 'HomeController');
|
||
KRLogUtil.kr_w('1. 后端API未返回 latitude/longitude 字段', tag: 'HomeController');
|
||
KRLogUtil.kr_w('2. 后端数据库中节点坐标未配置', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
/// 强制同步连接状态
|
||
void kr_forceSyncConnectionStatus() {
|
||
try {
|
||
KRLogUtil.kr_i('🔄 强制同步连接状态...', tag: 'HomeController');
|
||
|
||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||
KRLogUtil.kr_i('📊 当前 SingBox 状态: $currentStatus', tag: 'HomeController');
|
||
|
||
// 根据当前状态强制更新UI
|
||
switch (currentStatus) {
|
||
case SingboxStopped():
|
||
kr_connectText.value = AppTranslations.kr_home.disconnected;
|
||
kr_isConnected.value = false;
|
||
kr_currentSpeed.value = "--";
|
||
kr_currentNodeLatency.value = -2;
|
||
break;
|
||
case SingboxStarting():
|
||
kr_connectText.value = AppTranslations.kr_home.connecting;
|
||
kr_isConnected.value = false;
|
||
kr_currentSpeed.value = AppTranslations.kr_home.connecting;
|
||
kr_currentNodeLatency.value = -1;
|
||
break;
|
||
case SingboxStarted():
|
||
kr_connectText.value = AppTranslations.kr_home.connected;
|
||
kr_isConnected.value = true;
|
||
kr_startConnectionTimer();
|
||
kr_updateConnectionInfo();
|
||
|
||
// 🔧 修复:同步已启动状态时,尝试更新延迟值
|
||
if (!_kr_tryUpdateDelayFromActiveGroups()) {
|
||
// 如果获取不到延迟值,设置为0(已连接但延迟未知)
|
||
kr_currentNodeLatency.value = 0;
|
||
KRLogUtil.kr_w('⚠️ 强制同步时无法获取延迟值,设置为0', tag: 'HomeController');
|
||
}
|
||
break;
|
||
case SingboxStopping():
|
||
kr_connectText.value = AppTranslations.kr_home.disconnecting;
|
||
kr_isConnected.value = false;
|
||
kr_currentSpeed.value = "--";
|
||
break;
|
||
}
|
||
|
||
// 强制更新UI
|
||
update();
|
||
KRLogUtil.kr_i('✅ 连接状态同步完成', tag: 'HomeController');
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('❌ 强制同步连接状态失败: $e', tag: 'HomeController');
|
||
}
|
||
}
|
||
|
||
/// 连接超时处理
|
||
Timer? _connectionTimeoutTimer;
|
||
|
||
void _startConnectionTimeout() {
|
||
_connectionTimeoutTimer?.cancel();
|
||
_connectionTimeoutTimer = Timer(const Duration(seconds: 30), () {
|
||
KRLogUtil.kr_w('⏰ 连接超时,强制重置状态', tag: 'HomeController');
|
||
|
||
// 检查是否仍在连接中
|
||
if (KRSingBoxImp.instance.kr_status.value is SingboxStarting) {
|
||
KRLogUtil.kr_w('🔄 连接超时,强制停止并重置', tag: 'HomeController');
|
||
|
||
// 强制停止连接
|
||
KRSingBoxImp.instance.kr_stop().then((_) {
|
||
// 重置状态
|
||
kr_connectText.value = AppTranslations.kr_home.disconnected;
|
||
kr_isConnected.value = false;
|
||
kr_currentSpeed.value = "--";
|
||
kr_currentNodeLatency.value = -2;
|
||
kr_resetConnectionInfo();
|
||
update();
|
||
|
||
KRLogUtil.kr_i('✅ 连接超时处理完成', tag: 'HomeController');
|
||
}).catchError((e) {
|
||
KRLogUtil.kr_e('❌ 连接超时处理失败: $e', tag: 'HomeController');
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
void _cancelConnectionTimeout() {
|
||
_connectionTimeoutTimer?.cancel();
|
||
_connectionTimeoutTimer = null;
|
||
}
|
||
|
||
/// 连接成功后更新延迟值
|
||
void _kr_updateLatencyOnConnected() {
|
||
KRLogUtil.kr_i('🔧 尝试获取连接延迟值...', tag: 'HomeController');
|
||
|
||
// 立即尝试从活动组获取延迟
|
||
bool delayUpdated = _kr_tryUpdateDelayFromActiveGroups();
|
||
|
||
if (delayUpdated) {
|
||
KRLogUtil.kr_i('✅ 延迟值已从活动组更新', tag: 'HomeController');
|
||
return;
|
||
}
|
||
|
||
// 如果立即获取失败,设置临时值并启动延迟重试
|
||
KRLogUtil.kr_w('⚠️ 活动组暂无延迟数据,设置临时值并启动重试', tag: 'HomeController');
|
||
kr_currentNodeLatency.value = 0; // 设置为0表示已连接但延迟未知
|
||
|
||
// 延迟500ms后重试(等待活动组数据到达)
|
||
Future.delayed(const Duration(milliseconds: 500), () {
|
||
if (_kr_tryUpdateDelayFromActiveGroups()) {
|
||
KRLogUtil.kr_i('✅ 延迟重试成功,延迟值已更新', tag: 'HomeController');
|
||
return;
|
||
}
|
||
|
||
// 再次延迟1秒重试
|
||
Future.delayed(const Duration(seconds: 1), () {
|
||
if (_kr_tryUpdateDelayFromActiveGroups()) {
|
||
KRLogUtil.kr_i('✅ 第二次延迟重试成功', tag: 'HomeController');
|
||
return;
|
||
}
|
||
|
||
// 如果还是获取不到,保持为0(表示已连接但延迟未知)
|
||
KRLogUtil.kr_w('⚠️ 多次重试后仍无法获取延迟值,保持为已连接状态', tag: 'HomeController');
|
||
kr_currentNodeLatency.value = 0;
|
||
});
|
||
});
|
||
}
|
||
|
||
/// 尝试从活动组更新延迟值
|
||
bool _kr_tryUpdateDelayFromActiveGroups() {
|
||
try {
|
||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||
|
||
if (activeGroups.isEmpty) {
|
||
KRLogUtil.kr_d('活动组为空', tag: 'HomeController');
|
||
return false;
|
||
}
|
||
|
||
// 查找 selector 类型的组
|
||
for (var group in activeGroups) {
|
||
if (group.type == ProxyType.selector) {
|
||
KRLogUtil.kr_d('找到 selector 组: ${group.tag}, 选中: ${group.selected}', tag: 'HomeController');
|
||
|
||
// 如果是auto模式,从urltest组获取延迟
|
||
if (kr_cutTag.value == "auto") {
|
||
for (var item in group.items) {
|
||
if (item.tag == "auto" && item.urlTestDelay != 0) {
|
||
kr_currentNodeLatency.value = item.urlTestDelay;
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
KRLogUtil.kr_d('未找到匹配的延迟数据', tag: 'HomeController');
|
||
return false;
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('获取延迟值失败: $e', tag: 'HomeController');
|
||
return false;
|
||
}
|
||
}
|
||
}
|