feat: 优化未连接节点时候批量节点测速功能,修复网络初始化报错后,导致的连锁反应界面出现错乱和DioExceptionType.unknown报错信息,直连模式,不依赖未初始化的核心
Some checks failed
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 构建 iOS (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (iOS/tvOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
Some checks failed
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 构建 iOS (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (iOS/tvOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
This commit is contained in:
parent
33f45aa4a5
commit
6131a80b2c
@ -35,6 +35,7 @@ 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:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||||
import 'package:flutter/foundation.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_init_log_collector.dart'; // 🔧 新增:导入日志收集器
|
||||||
|
import 'package:kaer_with_panels/app/utils/kr_latency_tester.dart'; // 🔧 新增:导入真实延迟测试工具
|
||||||
|
|
||||||
class KRHomeController extends GetxController with WidgetsBindingObserver {
|
class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||||
// 🔧 新增:日志收集器实例
|
// 🔧 新增:日志收集器实例
|
||||||
@ -55,7 +56,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
/// 底部面板高度常量
|
/// 底部面板高度常量
|
||||||
static const double kr_baseHeight = 120.0; // 基础高度(连接选项)
|
static const double kr_baseHeight = 120.0; // 基础高度(连接选项)
|
||||||
static const double kr_subscriptionCardHeight = 200.0; // 订阅卡片高度
|
static const double kr_subscriptionCardHeight = 200.0; // 订阅卡片高度
|
||||||
static const double kr_connectionInfoHeight = 126.0; // 连接信息卡片高度
|
static const double kr_connectionInfoHeight = 126.0; // 连接信息卡片高度(修复后)
|
||||||
static const double kr_trialCardHeight = 120.0; // 试用卡片高度
|
static const double kr_trialCardHeight = 120.0; // 试用卡片高度
|
||||||
static const double kr_lastDayCardHeight = 120.0; // 最后一天卡片高度
|
static const double kr_lastDayCardHeight = 120.0; // 最后一天卡片高度
|
||||||
static const double kr_nodeListHeight = 400.0; // 节点列表高度
|
static const double kr_nodeListHeight = 400.0; // 节点列表高度
|
||||||
@ -105,7 +106,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
// 当前选中的组
|
// 当前选中的组
|
||||||
final Rx<KRGroupOutboundList?> kr_currentGroup =
|
final Rx<KRGroupOutboundList?> kr_currentGroup =
|
||||||
Rx<KRGroupOutboundList?>(null);
|
Rx<KRGroupOutboundList?>(null);
|
||||||
|
|
||||||
// 添加是否用户正在移动地图的标志
|
// 添加是否用户正在移动地图的标志
|
||||||
final kr_isUserMoving = false.obs;
|
final kr_isUserMoving = false.obs;
|
||||||
@ -137,16 +138,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存闪连状态到本地存储
|
|
||||||
Future<void> _saveQuickConnectStatus(bool enabled) async {
|
|
||||||
try {
|
|
||||||
await _storage.kr_saveBool(key: _quickConnectKey, value: enabled);
|
|
||||||
KRLogUtil.kr_i('闪连状态已保存到本地存储: $enabled', tag: 'QuickConnect');
|
|
||||||
} catch (e) {
|
|
||||||
KRLogUtil.kr_e('保存闪连状态失败: $e', tag: 'QuickConnect');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从本地存储加载闪连状态
|
// 从本地存储加载闪连状态
|
||||||
Future<void> _loadQuickConnectStatus() async {
|
Future<void> _loadQuickConnectStatus() async {
|
||||||
try {
|
try {
|
||||||
@ -167,6 +158,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 获取当前闪连状态
|
// 获取当前闪连状态
|
||||||
bool get isQuickConnectActive => isQuickConnectEnabled.value;
|
bool get isQuickConnectActive => isQuickConnectEnabled.value;
|
||||||
|
|
||||||
@ -250,9 +242,37 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
!kr_isConnected.value;
|
!kr_isConnected.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() async {
|
||||||
super.onInit();
|
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();
|
_loadQuickConnectStatus();
|
||||||
// 绑定订阅状态
|
// 绑定订阅状态
|
||||||
@ -267,6 +287,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 注册应用生命周期监听
|
// 注册应用生命周期监听
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
|
// 🔧 新增:恢复上次选择的节点显示
|
||||||
|
_restoreSelectedNode();
|
||||||
|
|
||||||
// 延迟同步连接状态,确保状态正确
|
// 延迟同步连接状态,确保状态正确
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
kr_forceSyncConnectionStatus();
|
kr_forceSyncConnectionStatus();
|
||||||
@ -282,7 +305,42 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 🔧 新增:恢复上次选择的节点显示
|
||||||
|
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() {
|
void _kr_initLoginStatus() {
|
||||||
_initLog.log('开始初始化登录状态处理', tag: 'Home');
|
_initLog.log('开始初始化登录状态处理', tag: 'Home');
|
||||||
@ -340,36 +398,138 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
/// 验证并设置登录状态
|
/// 验证并设置登录状态
|
||||||
void _kr_validateAndSetLoginStatus() {
|
void _kr_validateAndSetLoginStatus() {
|
||||||
try {
|
try {
|
||||||
|
_initLog.log('🔍 开始验证登录状态', tag: 'Home');
|
||||||
|
|
||||||
// 多重验证登录状态
|
// 多重验证登录状态
|
||||||
final hasToken = KRAppRunData().kr_token != null &&
|
final hasToken = KRAppRunData().kr_token != null && KRAppRunData().kr_token!.isNotEmpty;
|
||||||
KRAppRunData().kr_token!.isNotEmpty;
|
|
||||||
final isLoginFlag = KRAppRunData().kr_isLogin.value;
|
final isLoginFlag = KRAppRunData().kr_isLogin.value;
|
||||||
final isValidLogin = hasToken && isLoginFlag;
|
final isValidLogin = hasToken && isLoginFlag;
|
||||||
|
|
||||||
KRLogUtil.kr_i(
|
_initLog.log('登录验证结果: hasToken=$hasToken, isLogin=$isLoginFlag, isValid=$isValidLogin', tag: 'Home');
|
||||||
'登录状态验证: hasToken=$hasToken, isLogin=$isLoginFlag, isValid=$isValidLogin',
|
KRLogUtil.kr_i('登录状态验证: hasToken=$hasToken, isLogin=$isLoginFlag, isValid=$isValidLogin', tag: 'HomeController');
|
||||||
tag: 'HomeController');
|
KRLogUtil.kr_i('Token内容: ${KRAppRunData().kr_token?.substring(0, 10)}...', tag: 'HomeController');
|
||||||
KRLogUtil.kr_i(
|
|
||||||
'Token内容: ${KRAppRunData().kr_token?.substring(0, 10)}...',
|
|
||||||
tag: 'HomeController');
|
|
||||||
|
|
||||||
if (isValidLogin) {
|
if (isValidLogin) {
|
||||||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
|
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
|
||||||
|
_initLog.logSuccess('用户已登录,准备加载订阅数据', tag: 'Home');
|
||||||
KRLogUtil.kr_i('设置为已登录状态', tag: 'HomeController');
|
KRLogUtil.kr_i('设置为已登录状态', tag: 'HomeController');
|
||||||
|
|
||||||
// 订阅服务已在 splash 页面初始化,此处无需重复初始化
|
// 订阅服务已在 splash 页面初始化,此处无需重复初始化
|
||||||
KRLogUtil.kr_i('订阅服务已在启动页初始化,跳过重复初始化', tag: 'HomeController');
|
KRLogUtil.kr_i('订阅服务已在启动页初始化,跳过重复初始化', tag: 'HomeController');
|
||||||
} else {
|
} else {
|
||||||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
||||||
|
_initLog.logWarning('用户未登录,跳过订阅加载', tag: 'Home');
|
||||||
KRLogUtil.kr_i('设置为未登录状态', tag: 'HomeController');
|
KRLogUtil.kr_i('设置为未登录状态', tag: 'HomeController');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
_initLog.logError('登录状态验证失败', tag: 'Home', error: e);
|
||||||
KRLogUtil.kr_e('登录状态验证失败: $e', tag: 'HomeController');
|
KRLogUtil.kr_e('登录状态验证失败: $e', tag: 'HomeController');
|
||||||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 处理登录状态变化
|
/// 处理登录状态变化
|
||||||
@ -377,8 +537,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
try {
|
try {
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
// 再次验证登录状态的有效性
|
// 再次验证登录状态的有效性
|
||||||
final isValidLogin = KRAppRunData().kr_token != null &&
|
final isValidLogin = KRAppRunData().kr_token != null && KRAppRunData().kr_token!.isNotEmpty;
|
||||||
KRAppRunData().kr_token!.isNotEmpty;
|
|
||||||
if (isValidLogin) {
|
if (isValidLogin) {
|
||||||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
|
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
|
||||||
KRLogUtil.kr_i('登录状态变化:设置为已登录', tag: 'HomeController');
|
KRLogUtil.kr_i('登录状态变化:设置为已登录', tag: 'HomeController');
|
||||||
@ -388,8 +547,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 订阅服务已在 splash 页面初始化,此处无需重复初始化
|
// 订阅服务已在 splash 页面初始化,此处无需重复初始化
|
||||||
KRLogUtil.kr_i('订阅服务已在启动页初始化,跳过重复初始化', tag: 'HomeController');
|
KRLogUtil.kr_i('订阅服务已在启动页初始化,跳过重复初始化', tag: 'HomeController');
|
||||||
} else {
|
} else {
|
||||||
KRLogUtil.kr_w(
|
KRLogUtil.kr_w('登录状态为true但token为空,重置为未登录', tag: 'HomeController');
|
||||||
'登录状态为true但token为空,重置为未登录', tag: 'HomeController');
|
|
||||||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -422,20 +580,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
final currentLoginStatus = KRAppRunData().kr_isLogin.value;
|
final currentLoginStatus = KRAppRunData().kr_isLogin.value;
|
||||||
final currentViewStatus = kr_currentViewStatus.value;
|
final currentViewStatus = kr_currentViewStatus.value;
|
||||||
|
|
||||||
KRLogUtil.kr_i(
|
KRLogUtil.kr_i('状态同步检查: login=$currentLoginStatus, view=$currentViewStatus', tag: 'HomeController');
|
||||||
'状态同步检查: login=$currentLoginStatus, view=$currentViewStatus',
|
|
||||||
tag: 'HomeController');
|
|
||||||
|
|
||||||
// 检查状态是否一致
|
// 检查状态是否一致
|
||||||
if (currentViewStatus == KRHomeViewsStatus.kr_loggedIn &&
|
if (currentViewStatus == KRHomeViewsStatus.kr_loggedIn && !currentLoginStatus) {
|
||||||
!currentLoginStatus) {
|
KRLogUtil.kr_w('状态不一致:视图显示已登录但实际未登录,修正状态', tag: 'HomeController');
|
||||||
KRLogUtil.kr_w('状态不一致:视图显示已登录但实际未登录,修正状态',
|
|
||||||
tag: 'HomeController');
|
|
||||||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
||||||
} else if (currentViewStatus == KRHomeViewsStatus.kr_notLoggedIn &&
|
} else if (currentViewStatus == KRHomeViewsStatus.kr_notLoggedIn && currentLoginStatus) {
|
||||||
currentLoginStatus) {
|
KRLogUtil.kr_w('状态不一致:视图显示未登录但实际已登录,修正状态', tag: 'HomeController');
|
||||||
KRLogUtil.kr_w('状态不一致:视图显示未登录但实际已登录,修正状态',
|
|
||||||
tag: 'HomeController');
|
|
||||||
_kr_validateAndSetLoginStatus();
|
_kr_validateAndSetLoginStatus();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -453,10 +605,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
ever(kr_subscribeService.kr_currentStatus, (data) {
|
ever(kr_subscribeService.kr_currentStatus, (data) {
|
||||||
KRLogUtil.kr_i('订阅服务状态变化: $data', tag: 'HomeController');
|
KRLogUtil.kr_i('订阅服务状态变化: $data', tag: 'HomeController');
|
||||||
|
|
||||||
if (KRAppRunData
|
if (KRAppRunData.getInstance().kr_isLogin.value) {
|
||||||
.getInstance()
|
|
||||||
.kr_isLogin
|
|
||||||
.value) {
|
|
||||||
switch (data) {
|
switch (data) {
|
||||||
case KRSubscribeServiceStatus.kr_loading:
|
case KRSubscribeServiceStatus.kr_loading:
|
||||||
KRLogUtil.kr_i('订阅服务加载中', tag: 'HomeController');
|
KRLogUtil.kr_i('订阅服务加载中', tag: 'HomeController');
|
||||||
@ -758,8 +907,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
void _kr_handleSelectorProxy(dynamic element, List<dynamic> allGroups) {
|
void _kr_handleSelectorProxy(dynamic element, List<dynamic> allGroups) {
|
||||||
try {
|
try {
|
||||||
KRLogUtil.kr_d(
|
KRLogUtil.kr_d(
|
||||||
'处理选择器代理 - 当前选择: ${element.selected}, 用户选择: ${kr_cutTag
|
'处理选择器代理 - 当前选择: ${element.selected}, 用户选择: ${kr_cutTag.value}',
|
||||||
.value}',
|
|
||||||
tag: 'HomeController');
|
tag: 'HomeController');
|
||||||
|
|
||||||
|
|
||||||
@ -785,8 +933,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
/// 处理手动模式
|
/// 处理手动模式
|
||||||
void _kr_handleManualMode(dynamic element) {
|
void _kr_handleManualMode(dynamic element) {
|
||||||
try {
|
try {
|
||||||
KRLogUtil.kr_d(
|
KRLogUtil.kr_d('处理手动模式 - 选择: ${element.selected}', tag: 'HomeController');
|
||||||
'处理手动模式 - 选择: ${element.selected}', tag: 'HomeController');
|
|
||||||
|
|
||||||
// 🔧 关键修复:仅更新 UI 状态,不要重新选择节点,避免死循环
|
// 🔧 关键修复:仅更新 UI 状态,不要重新选择节点,避免死循环
|
||||||
kr_cutSeletedTag.value = element.selected;
|
kr_cutSeletedTag.value = element.selected;
|
||||||
@ -900,8 +1047,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
final selectedNode = kr_subscribeService.keyList[kr_cutSeletedTag.value];
|
final selectedNode = kr_subscribeService.keyList[kr_cutSeletedTag.value];
|
||||||
if (selectedNode != null) {
|
if (selectedNode != null) {
|
||||||
KRLogUtil.kr_d(
|
KRLogUtil.kr_d(
|
||||||
'更新节点信息 - 协议: ${selectedNode.protocol}, IP: ${selectedNode
|
'更新节点信息 - 协议: ${selectedNode.protocol}, IP: ${selectedNode.serverAddr}',
|
||||||
.serverAddr}',
|
|
||||||
tag: 'HomeController');
|
tag: 'HomeController');
|
||||||
kr_currentProtocol.value =
|
kr_currentProtocol.value =
|
||||||
kr_truncateText(selectedNode.protocol, maxLength: 15);
|
kr_truncateText(selectedNode.protocol, maxLength: 15);
|
||||||
@ -1154,8 +1300,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
// 2. 如果是 auto,优先使用 kr_cutSeletedTag(保存了实际选中的节点)
|
// 2. 如果是 auto,优先使用 kr_cutSeletedTag(保存了实际选中的节点)
|
||||||
else if (kr_cutSeletedTag.value.isNotEmpty &&
|
else if (kr_cutSeletedTag.value.isNotEmpty &&
|
||||||
kr_cutSeletedTag.value != 'auto' &&
|
kr_cutSeletedTag.value != 'auto' &&
|
||||||
kr_cutSeletedTag.value != 'select') {
|
kr_cutSeletedTag.value != 'select') {
|
||||||
// auto 模式下,使用保存的实际节点
|
// auto 模式下,使用保存的实际节点
|
||||||
actualTag = kr_cutSeletedTag.value;
|
actualTag = kr_cutSeletedTag.value;
|
||||||
KRLogUtil.kr_i('✅ 使用 auto 模式下的实际节点 (kr_cutSeletedTag): $actualTag', tag: 'getCurrentNodeCountry');
|
KRLogUtil.kr_i('✅ 使用 auto 模式下的实际节点 (kr_cutSeletedTag): $actualTag', tag: 'getCurrentNodeCountry');
|
||||||
@ -1190,7 +1336,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
print('[getCurrentNodeCountry] select 组选中的是 auto,查找 urltest 组');
|
print('[getCurrentNodeCountry] select 组选中的是 auto,查找 urltest 组');
|
||||||
// 如果 select 组选中的是 auto,从 urltest 组获取
|
// 如果 select 组选中的是 auto,从 urltest 组获取
|
||||||
final urlTestGroup = allGroups.firstWhere(
|
final urlTestGroup = allGroups.firstWhere(
|
||||||
(group) => group.type == ProxyType.urltest,
|
(group) => group.type == ProxyType.urltest,
|
||||||
orElse: () => throw Exception('未找到 urltest 组'),
|
orElse: () => throw Exception('未找到 urltest 组'),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1212,7 +1358,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 活动组不为空,从活动组获取
|
// 活动组不为空,从活动组获取
|
||||||
// 从 SingBox 活动组中找到 "select" 选择器组
|
// 从 SingBox 活动组中找到 "select" 选择器组
|
||||||
final selectGroup = KRSingBoxImp.instance.kr_activeGroups.firstWhere(
|
final selectGroup = KRSingBoxImp.instance.kr_activeGroups.firstWhere(
|
||||||
(group) => group.tag == 'select',
|
(group) => group.tag == 'select',
|
||||||
orElse: () => throw Exception('未找到 select 组'),
|
orElse: () => throw Exception('未找到 select 组'),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1502,37 +1648,54 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
KRHomeViewsListStatus.kr_serverSubscribeList ||
|
KRHomeViewsListStatus.kr_serverSubscribeList ||
|
||||||
kr_currentListStatus.value == KRHomeViewsListStatus.kr_subscribeList) {
|
kr_currentListStatus.value == KRHomeViewsListStatus.kr_subscribeList) {
|
||||||
targetHeight = kr_nodeListHeight + kr_marginVertical * 2;
|
targetHeight = kr_nodeListHeight + kr_marginVertical * 2;
|
||||||
KRLogUtil.kr_i(
|
KRLogUtil.kr_i('节点列表状态,目标高度: $targetHeight', tag: 'HomeController');
|
||||||
'节点列表状态,目标高度: $targetHeight', tag: 'HomeController');
|
}
|
||||||
} else {
|
// 🔧 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;
|
targetHeight = kr_baseHeight + kr_marginTop + kr_marginBottom;
|
||||||
KRLogUtil.kr_i('基础高度: $targetHeight', tag: 'HomeController');
|
KRLogUtil.kr_i('基础高度: $targetHeight', tag: 'HomeController');
|
||||||
|
|
||||||
if (kr_subscribeService.kr_currentSubscribe.value != null) {
|
// 🔧 关键修复:增加防御性检查,确保订阅服务访问异常时高度计算仍正常
|
||||||
targetHeight += kr_connectionInfoHeight + kr_marginTop;
|
try {
|
||||||
KRLogUtil.kr_i(
|
if (kr_subscribeService.kr_currentSubscribe.value != null) {
|
||||||
'添加连接信息卡片高度: $targetHeight', tag: 'HomeController');
|
targetHeight += kr_connectionInfoHeight + kr_marginTop;
|
||||||
} else {
|
KRLogUtil.kr_i('添加连接信息卡片高度: $targetHeight', tag: 'HomeController');
|
||||||
targetHeight += kr_subscriptionCardHeight + kr_marginTop;
|
} else {
|
||||||
KRLogUtil.kr_i(
|
targetHeight += kr_subscriptionCardHeight + kr_marginTop;
|
||||||
'添加订阅卡片高度: $targetHeight', tag: 'HomeController');
|
KRLogUtil.kr_i('添加订阅卡片高度: $targetHeight', tag: 'HomeController');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有试用状态,添加试用卡片高度
|
// 如果有试用状态,添加试用卡片高度
|
||||||
if (kr_subscribeService.kr_isTrial.value) {
|
if (kr_subscribeService.kr_isTrial.value) {
|
||||||
targetHeight += kr_trialCardHeight + kr_marginTop;
|
targetHeight += kr_trialCardHeight + kr_marginTop;
|
||||||
KRLogUtil.kr_i(
|
KRLogUtil.kr_i('添加试用卡片高度: $targetHeight', tag: 'HomeController');
|
||||||
'添加试用卡片高度: $targetHeight', tag: 'HomeController');
|
}
|
||||||
}
|
// 如果是最后一天,添加最后一天卡片高度
|
||||||
// 如果是最后一天,添加最后一天卡片高度
|
else if (kr_subscribeService.kr_isLastDayOfSubscription.value) {
|
||||||
else if (kr_subscribeService.kr_isLastDayOfSubscription.value) {
|
targetHeight += kr_lastDayCardHeight + kr_marginTop;
|
||||||
targetHeight += kr_lastDayCardHeight + kr_marginTop;
|
KRLogUtil.kr_i('添加最后一天卡片高度: $targetHeight', tag: 'HomeController');
|
||||||
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');
|
KRLogUtil.kr_i('最终目标高度: $targetHeight', tag: 'HomeController');
|
||||||
kr_bottomPanelHeight.value = targetHeight;
|
kr_bottomPanelHeight.value = targetHeight;
|
||||||
}
|
}
|
||||||
@ -1555,13 +1718,31 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 简化移动地图方法
|
// 简化移动地图方法
|
||||||
void kr_moveToLocation(LatLng location, [double zoom = 5.0]) {
|
void kr_moveToLocation(LatLng location, [double zoom = 5.0]) {
|
||||||
try {
|
try {
|
||||||
kr_mapController.move(location, zoom);
|
// 🔧 关键修复:约束坐标到有效范围,防止超出地图边界
|
||||||
|
final constrainedLocation = _constrainCoordinates(location);
|
||||||
|
kr_mapController.move(constrainedLocation, zoom);
|
||||||
kr_isUserMoving.value = false;
|
kr_isUserMoving.value = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
KRLogUtil.kr_e('移动地图失败: $e', tag: 'HomeController');
|
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) {
|
void kr_updateMarkers(List<String> tags) {
|
||||||
// 使用Set来去重
|
// 使用Set来去重
|
||||||
@ -1620,13 +1801,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||||
for (int i = 0; i < activeGroups.length; i++) {
|
for (int i = 0; i < activeGroups.length; i++) {
|
||||||
final group = activeGroups[i];
|
final group = activeGroups[i];
|
||||||
KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group
|
KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
|
||||||
.type}, selected=${group.selected}', tag: 'HomeController');
|
|
||||||
for (int j = 0; j < group.items.length; j++) {
|
for (int j = 0; j < group.items.length; j++) {
|
||||||
final item = group.items[j];
|
final item = group.items[j];
|
||||||
KRLogUtil.kr_i(
|
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'HomeController');
|
||||||
' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item
|
|
||||||
.urlTestDelay}', tag: 'HomeController');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1654,39 +1832,31 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
KRLogUtil.kr_i('🧪 开始延迟测试...', tag: 'HomeController');
|
KRLogUtil.kr_i('🧪 开始延迟测试...', tag: 'HomeController');
|
||||||
KRLogUtil.kr_i(
|
KRLogUtil.kr_i('📊 当前连接状态: ${kr_isConnected.value}', tag: 'HomeController');
|
||||||
'📊 当前连接状态: ${kr_isConnected.value}', tag: 'HomeController');
|
|
||||||
|
|
||||||
if (kr_isConnected.value) {
|
if (kr_isConnected.value) {
|
||||||
// 已连接状态:使用 SingBox 通过代理测试
|
// 已连接状态:使用 SingBox 通过代理测试
|
||||||
KRLogUtil.kr_i('🔗 已连接状态 - 使用 SingBox 通过代理测试延迟',
|
KRLogUtil.kr_i('🔗 已连接状态 - 使用 SingBox 通过代理测试延迟', tag: 'HomeController');
|
||||||
tag: 'HomeController');
|
|
||||||
await KRSingBoxImp.instance.kr_urlTest("select");
|
await KRSingBoxImp.instance.kr_urlTest("select");
|
||||||
|
|
||||||
// 等待一段时间让 SingBox 完成测试
|
// 等待一段时间让 SingBox 完成测试
|
||||||
await Future.delayed(const Duration(seconds: 3));
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
|
|
||||||
// 再次检查活动组状态
|
// 再次检查活动组状态
|
||||||
KRLogUtil.kr_i(
|
KRLogUtil.kr_i('🔄 检查代理测试后的活动组状态...', tag: 'HomeController');
|
||||||
'🔄 检查代理测试后的活动组状态...', tag: 'HomeController');
|
|
||||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||||
for (int i = 0; i < activeGroups.length; i++) {
|
for (int i = 0; i < activeGroups.length; i++) {
|
||||||
final group = activeGroups[i];
|
final group = activeGroups[i];
|
||||||
KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group
|
KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
|
||||||
.type}, selected=${group.selected}', tag: 'HomeController');
|
|
||||||
for (int j = 0; j < group.items.length; j++) {
|
for (int j = 0; j < group.items.length; j++) {
|
||||||
final item = group.items[j];
|
final item = group.items[j];
|
||||||
KRLogUtil.kr_i(
|
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'HomeController');
|
||||||
' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item
|
|
||||||
.urlTestDelay}', tag: 'HomeController');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 未连接状态:使用本机网络直接ping节点IP
|
// 未连接状态:使用本机网络直接ping节点IP
|
||||||
KRLogUtil.kr_i('🔌 未连接状态 - 使用本机网络直接ping节点IP测试延迟',
|
KRLogUtil.kr_i('🔌 未连接状态 - 使用本机网络直接ping节点IP测试延迟', tag: 'HomeController');
|
||||||
tag: 'HomeController');
|
KRLogUtil.kr_i('🌐 这将绕过代理,直接使用本机网络连接节点', tag: 'HomeController');
|
||||||
KRLogUtil.kr_i(
|
|
||||||
'🌐 这将绕过代理,直接使用本机网络连接节点', tag: 'HomeController');
|
|
||||||
await _kr_testLatencyWithoutVpn();
|
await _kr_testLatencyWithoutVpn();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1699,57 +1869,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _kr_testSingleNode(dynamic item) async {
|
/// 未连接状态下的真实延迟测试(TCP连接测试)
|
||||||
final stopwatch = Stopwatch()..start();
|
|
||||||
try {
|
|
||||||
// 解析地址和端口
|
|
||||||
String address = item.serverAddr;
|
|
||||||
int port = 443; // 默认端口
|
|
||||||
|
|
||||||
// 如果 serverAddr 带端口
|
|
||||||
if (item.serverAddr.contains(':')) {
|
|
||||||
final parts = item.serverAddr.split(':');
|
|
||||||
address = parts[0];
|
|
||||||
port = int.tryParse(parts[1]) ?? 443;
|
|
||||||
} else if (item.config['server_port'] != null) {
|
|
||||||
port = item.config['server_port'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCP 测试连接
|
|
||||||
final socket = await Socket.connect(
|
|
||||||
address,
|
|
||||||
port,
|
|
||||||
timeout: const Duration(seconds: 8),
|
|
||||||
);
|
|
||||||
|
|
||||||
final delay = stopwatch.elapsedMilliseconds;
|
|
||||||
socket.destroy();
|
|
||||||
|
|
||||||
// 设置延迟阈值:超过5秒认为不可用
|
|
||||||
if (delay > 5000) {
|
|
||||||
item.urlTestDelay.value = 65535;
|
|
||||||
} else {
|
|
||||||
item.urlTestDelay.value = delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
print('✅ 节点 ${item.tag} 测试完成,延迟: ${item.urlTestDelay.value}ms');
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
// 连接失败
|
|
||||||
item.urlTestDelay.value = 65535;
|
|
||||||
print('⚠️ 节点 ${item.tag} 测试失败: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 未连接状态下的延迟测试(使用本机网络真实测速)
|
|
||||||
Future<void> _kr_testLatencyWithoutVpn() async {
|
Future<void> _kr_testLatencyWithoutVpn() async {
|
||||||
kr_isLatency.value = true;
|
kr_isLatency.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KRLogUtil.kr_i('🔌 开始未连接状态延迟测试(真实测速)', tag: 'HomeController');
|
KRLogUtil.kr_i('🚀 开始真实延迟测试(TCP连接测试)', tag: 'HomeController');
|
||||||
KRLogUtil.kr_i('📊 当前连接状态: ${kr_isConnected.value}', tag: 'HomeController');
|
KRLogUtil.kr_i('📊 当前连接状态: ${kr_isConnected.value}', tag: 'HomeController');
|
||||||
|
|
||||||
// 获取所有非 auto 节点
|
// 获取所有非auto节点
|
||||||
final testableNodes = kr_subscribeService.allList
|
final testableNodes = kr_subscribeService.allList
|
||||||
.where((item) => item.tag != 'auto')
|
.where((item) => item.tag != 'auto')
|
||||||
.toList();
|
.toList();
|
||||||
@ -1761,13 +1888,68 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 依次测试每个节点延迟
|
// 准备节点地址列表
|
||||||
for (var node in testableNodes) {
|
final nodeAddresses = <MapEntry<String, SocketAddress>>[];
|
||||||
await _kr_testSingleNode(node); // 真实测速
|
|
||||||
KRLogUtil.kr_i('节点 ${node.tag} 延迟: ${node.urlTestDelay.value}ms', tag: 'HomeController');
|
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: const Duration(seconds: 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
|
final sortedNodes = testableNodes
|
||||||
.where((item) => item.urlTestDelay.value < 65535)
|
.where((item) => item.urlTestDelay.value < 65535)
|
||||||
.toList()
|
.toList()
|
||||||
@ -1781,9 +1963,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KRLogUtil.kr_i('✅ 本机网络延迟测试完成', tag: 'HomeController');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
KRLogUtil.kr_e('❌ 本机网络延迟测试过程出错: $e', tag: 'HomeController');
|
KRLogUtil.kr_e('❌ 真实延迟测试过程出错: $e', tag: 'HomeController');
|
||||||
|
KRLogUtil.kr_e('❌ 错误堆栈: ${StackTrace.current}', tag: 'HomeController');
|
||||||
} finally {
|
} finally {
|
||||||
kr_isLatency.value = false;
|
kr_isLatency.value = false;
|
||||||
}
|
}
|
||||||
@ -1827,14 +2009,55 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
kr_currentNodeLatency.value = -2; // 设置为未连接状态
|
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() {
|
void kr_forceSyncConnectionStatus() {
|
||||||
try {
|
try {
|
||||||
KRLogUtil.kr_i('🔄 强制同步连接状态...', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 强制同步连接状态...', tag: 'HomeController');
|
||||||
|
|
||||||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||||||
KRLogUtil.kr_i(
|
KRLogUtil.kr_i('📊 当前 SingBox 状态: $currentStatus', tag: 'HomeController');
|
||||||
'📊 当前 SingBox 状态: $currentStatus', tag: 'HomeController');
|
|
||||||
|
|
||||||
// 根据当前状态强制更新UI
|
// 根据当前状态强制更新UI
|
||||||
switch (currentStatus) {
|
switch (currentStatus) {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||||
@ -26,24 +25,17 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
|||||||
static const Color krModernGreen = Color(0xFF4CAF50);
|
static const Color krModernGreen = Color(0xFF4CAF50);
|
||||||
static const Color krModernGreenLight = Color(0xFF81C784);
|
static const Color krModernGreenLight = Color(0xFF81C784);
|
||||||
|
|
||||||
// 存储每个节点的随机延迟值(仅用于界面显示)
|
// 🔧 修复无限刷新:添加标志位确保自动测试只触发一次
|
||||||
static final Map<String, int> _fakeDelays = {};
|
static bool _hasTriggeredAutoTest = false;
|
||||||
|
|
||||||
/// 获取显示的延迟值
|
/// 获取显示的延迟值
|
||||||
|
/// ✅ 修复:始终显示真实的 TCP 测试结果
|
||||||
int _getDisplayDelay(KRHomeController controller, KROutboundItem item) {
|
int _getDisplayDelay(KRHomeController controller, KROutboundItem item) {
|
||||||
// 如果已连接,使用真实的延迟值
|
// 直接返回真实的延迟测试结果
|
||||||
if (controller.kr_isConnected.value) {
|
// 无论是否连接VPN,都使用 item.urlTestDelay.value
|
||||||
return item.urlTestDelay.value;
|
// - 已连接:通过 SingBox 代理测试的真实延迟
|
||||||
}
|
// - 未连接:通过 TCP Socket 直连测试的真实延迟
|
||||||
|
return item.urlTestDelay.value;
|
||||||
// 如果未连接,使用随机延迟值
|
|
||||||
if (!_fakeDelays.containsKey(item.tag)) {
|
|
||||||
// 生成30ms-100ms之间的随机延迟
|
|
||||||
final random = Random();
|
|
||||||
_fakeDelays[item.tag] = 30 + random.nextInt(71); // 30 + (0-70) = 30-100ms
|
|
||||||
}
|
|
||||||
|
|
||||||
return _fakeDelays[item.tag] ?? 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -658,13 +650,16 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自动触发延迟测试(仅在未连接状态下)
|
// 🔧 修复无限刷新:自动触发延迟测试(仅在未连接状态下,且只触发一次)
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
if (!_hasTriggeredAutoTest) {
|
||||||
if (!controller.kr_isConnected.value && !controller.kr_isLatency.value) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
KRLogUtil.kr_i('🔄 节点列表显示 - 自动触发延迟测试', tag: 'NodeListView');
|
if (!controller.kr_isConnected.value && !controller.kr_isLatency.value && !_hasTriggeredAutoTest) {
|
||||||
controller.kr_urlTest();
|
_hasTriggeredAutoTest = true; // 标记已触发
|
||||||
}
|
KRLogUtil.kr_i('🔄 节点列表显示 - 自动触发延迟测试(首次)', tag: 'NodeListView');
|
||||||
});
|
controller.kr_urlTest();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return _kr_buildListContainer(
|
return _kr_buildListContainer(
|
||||||
context,
|
context,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
|||||||
@ -17,21 +17,17 @@ class KRSiteConfigService extends ChangeNotifier {
|
|||||||
_dio.options.sendTimeout = const Duration(seconds: 20);
|
_dio.options.sendTimeout = const Duration(seconds: 20);
|
||||||
_dio.options.receiveTimeout = const Duration(seconds: 20);
|
_dio.options.receiveTimeout = const Duration(seconds: 20);
|
||||||
|
|
||||||
// 🔧 配置HttpClientAdapter使用sing-box的mixed代理
|
// 🔧 关键修复:网站配置请求不使用代理
|
||||||
_dio.httpClientAdapter = IOHttpClientAdapter(
|
// 原因:网站配置是应用启动的第一步,此时 SingBox 还未初始化
|
||||||
createHttpClient: () {
|
// 必须直接连接服务器获取配置,避免循环依赖和初始化失败
|
||||||
final client = HttpClient();
|
// 之前的代理配置会导致 DioExceptionType.unknown 错误
|
||||||
client.findProxy = (url) {
|
KRLogUtil.kr_i(
|
||||||
final proxyConfig = KRSingBoxImp.instance.kr_buildProxyRule();
|
'🌐 网站配置服务:使用直连模式(不通过代理)',
|
||||||
KRLogUtil.kr_i(
|
tag: 'KRSiteConfigService',
|
||||||
'🔍 KRSiteConfigService 请求使用代理: $proxyConfig, url: $url',
|
|
||||||
tag: 'KRSiteConfigService',
|
|
||||||
);
|
|
||||||
return proxyConfig;
|
|
||||||
};
|
|
||||||
return client;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('🌐 网站配置服务:使用直连模式,避免 SingBox 未初始化问题');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KRSiteConfig? _siteConfig;
|
KRSiteConfig? _siteConfig;
|
||||||
|
|||||||
@ -290,31 +290,19 @@ class KRSubscribeService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先使用 API 返回的 isTryOut 字段判断试用状态
|
// 🔧 关键修复:信任 API 返回的 isTryOut 字段,不再额外检查购买记录
|
||||||
|
// 之前的逻辑会在购买套餐后仍显示"试用中",因为购买记录可能未及时更新
|
||||||
final currentSubscribe = kr_currentSubscribe.value!;
|
final currentSubscribe = kr_currentSubscribe.value!;
|
||||||
|
|
||||||
// 1. 优先使用 API 返回的 isTryOut 字段
|
// 1. 使用 API 返回的 isTryOut 字段(最权威的判断)
|
||||||
kr_isTrial.value = currentSubscribe.isTryOut;
|
kr_isTrial.value = currentSubscribe.isTryOut;
|
||||||
KRLogUtil.kr_i('步骤1 - API isTryOut 字段: ${currentSubscribe.isTryOut}', tag: 'SubscribeService');
|
KRLogUtil.kr_i('步骤1 - API isTryOut 字段: ${currentSubscribe.isTryOut}', tag: 'SubscribeService');
|
||||||
|
|
||||||
// 2. 如果 API 说不是试用,检查是否有购买记录
|
// 2. 仅在 API 没有明确标识时,才检查订阅名称作为备用方案
|
||||||
if (!kr_isTrial.value) {
|
// 注意:只有当 API 说不是试用,但名称包含"试用"时才覆盖
|
||||||
final bool kr_isSubscribed = kr_alreadySubscribe.any(
|
|
||||||
(subscribe) => currentSubscribe.id == subscribe.userSubscribeId
|
|
||||||
);
|
|
||||||
KRLogUtil.kr_i('步骤2 - 检查购买记录: $kr_isSubscribed', tag: 'SubscribeService');
|
|
||||||
|
|
||||||
// 如果没有购买记录,判断为试用
|
|
||||||
if (!kr_isSubscribed) {
|
|
||||||
kr_isTrial.value = true;
|
|
||||||
KRLogUtil.kr_i('步骤2 - 没有购买记录,判定为试用', tag: 'SubscribeService');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 最后检查订阅名称是否包含"试用"关键字(最后的备用方案)
|
|
||||||
if (!kr_isTrial.value && currentSubscribe.name.contains('试用')) {
|
if (!kr_isTrial.value && currentSubscribe.name.contains('试用')) {
|
||||||
kr_isTrial.value = true;
|
kr_isTrial.value = true;
|
||||||
KRLogUtil.kr_i('步骤3 - 订阅名称包含"试用"关键字,判定为试用', tag: 'SubscribeService');
|
KRLogUtil.kr_i('步骤2 - 订阅名称包含"试用"关键字,判定为试用', tag: 'SubscribeService');
|
||||||
}
|
}
|
||||||
|
|
||||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SubscribeService');
|
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SubscribeService');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user