777 lines
28 KiB
Dart
Executable File
777 lines
28 KiB
Dart
Executable File
import 'dart:async';
|
||
import 'package:get/get.dart';
|
||
|
||
import 'package:kaer_with_panels/app/model/response/kr_node_group_list.dart';
|
||
|
||
import 'package:kaer_with_panels/app/model/response/kr_user_available_subscribe.dart';
|
||
import 'package:kaer_with_panels/app/services/api_service/kr_subscribe_api.dart';
|
||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.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/localization/app_translations.dart';
|
||
import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
||
|
||
import '../../singbox/model/singbox_status.dart';
|
||
import '../model/business/kr_group_outbound_list.dart';
|
||
import '../model/business/kr_outbound_item.dart';
|
||
import '../model/business/kr_outbounds_list.dart';
|
||
import '../model/response/kr_already_subscribe.dart';
|
||
|
||
/// 首页列表视图状态枚举
|
||
enum KRSubscribeServiceStatus { kr_none, kr_loading, kr_error, kr_success }
|
||
|
||
/// 订阅服务类
|
||
/// 用于管理用户订阅相关的所有操作
|
||
class KRSubscribeService {
|
||
/// 单例实例
|
||
static final KRSubscribeService _instance = KRSubscribeService._internal();
|
||
|
||
/// 工厂构造函数
|
||
factory KRSubscribeService() => _instance;
|
||
|
||
/// 私有构造函数
|
||
KRSubscribeService._internal() {}
|
||
|
||
/// 订阅API
|
||
final KRSubscribeApi kr_subscribeApi = KRSubscribeApi();
|
||
|
||
/// 可用订阅列表
|
||
final RxList<KRUserAvailableSubscribeItem> kr_availableSubscribes =
|
||
<KRUserAvailableSubscribeItem>[].obs;
|
||
|
||
/// 当前选中的订阅
|
||
final Rx<KRUserAvailableSubscribeItem?> kr_currentSubscribe =
|
||
Rx<KRUserAvailableSubscribeItem?>(null);
|
||
|
||
/// 节点分组列表
|
||
final RxList<KRNodeGroupListItem> kr_nodeGroups = <KRNodeGroupListItem>[].obs;
|
||
|
||
/// 服务器分组
|
||
final RxList<KRGroupOutboundList> groupOutboundList =
|
||
<KRGroupOutboundList>[].obs;
|
||
|
||
/// 国家分组,包含所有国家
|
||
final RxList<KRCountryOutboundList> countryOutboundList =
|
||
<KRCountryOutboundList>[].obs;
|
||
|
||
/// 全部列表
|
||
final RxList<KROutboundItem> allList = <KROutboundItem>[].obs;
|
||
|
||
/// 标签列表
|
||
Map<String, KROutboundItem> keyList = {}; // 存储国家分组的列表
|
||
|
||
/// 试用剩余时间
|
||
final RxString kr_trialRemainingTime = ''.obs;
|
||
|
||
/// 订阅剩余时间
|
||
final RxString kr_subscriptionRemainingTime = ''.obs;
|
||
|
||
/// 剩余时间
|
||
final RxString remainingTime = ''.obs;
|
||
|
||
/// 是否处于试用状态
|
||
final RxBool kr_isTrial = false.obs;
|
||
|
||
/// 当前节点列表是否包含试用节点
|
||
final RxBool kr_hasTrialNodes = false.obs;
|
||
|
||
/// 订阅记录
|
||
final RxList<KRAlreadySubscribe> kr_alreadySubscribe =
|
||
<KRAlreadySubscribe>[].obs;
|
||
|
||
/// 是否处于订阅最后一天
|
||
final RxBool kr_isLastDayOfSubscription = false.obs;
|
||
|
||
/// 定期更新计时器
|
||
Timer? _kr_updateTimer;
|
||
|
||
/// 试用倒计时计时器
|
||
Timer? _kr_trialTimer;
|
||
|
||
/// 订阅倒计时计时器
|
||
Timer? _kr_subscriptionTimer;
|
||
|
||
/// 当前状态
|
||
final kr_currentStatus = KRSubscribeServiceStatus.kr_none.obs;
|
||
|
||
/// ✅ 方案4:请求去重标志位 - 防止重复刷新
|
||
bool _isRefreshing = false;
|
||
|
||
/// ✅ 方案5:将错误码转换为用户友好的错误信息
|
||
String _kr_getFriendlyErrorMessage(int errorCode, String originalMsg) {
|
||
switch (errorCode) {
|
||
case -90001:
|
||
return '连接超时,请检查网络连接';
|
||
case -90002:
|
||
return '发送数据超时,请稍后重试';
|
||
case -90003:
|
||
return '接收数据超时,网络较慢,请稍后重试';
|
||
case -90004:
|
||
return '服务器响应异常:$originalMsg';
|
||
case -90006:
|
||
return '网络连接失败,请检查网络设置';
|
||
case -90007:
|
||
return '安全证书验证失败';
|
||
case -90008:
|
||
return '网络连接被中断,请重试';
|
||
case 401:
|
||
return '登录已过期,请重新登录';
|
||
case 403:
|
||
return '没有访问权限';
|
||
case 404:
|
||
return '请求的资源不存在';
|
||
case 500:
|
||
return '服务器内部错误,请稍后重试';
|
||
case 503:
|
||
return '服务暂时不可用,请稍后重试';
|
||
default:
|
||
if (errorCode >= 500 && errorCode < 600) {
|
||
return '服务器错误($errorCode),请稍后重试';
|
||
} else if (errorCode >= 400 && errorCode < 500) {
|
||
return '请求错误($errorCode):$originalMsg';
|
||
} else if (errorCode < 0) {
|
||
return '网络请求失败,请检查网络连接';
|
||
}
|
||
return originalMsg.isNotEmpty ? originalMsg : '未知错误,请重试';
|
||
}
|
||
}
|
||
|
||
/// 重置订阅周期
|
||
Future<void> kr_resetSubscribePeriod() async {
|
||
if (kr_currentSubscribe.value == null) {
|
||
KRCommonUtil.kr_showToast('subscribe.pleaseSelectFirst'.tr);
|
||
return;
|
||
}
|
||
|
||
final result = await kr_subscribeApi
|
||
.kr_resetSubscribePeriod(kr_currentSubscribe.value!.id);
|
||
result.fold(
|
||
(error) {
|
||
KRCommonUtil.kr_showToast(error.msg);
|
||
KRLogUtil.kr_e('重置订阅周期失败: ${error.msg}', tag: 'SubscribeService');
|
||
},
|
||
(success) {
|
||
kr_refreshAll();
|
||
KRLogUtil.kr_i('重置订阅周期成功', tag: 'SubscribeService');
|
||
},
|
||
);
|
||
}
|
||
|
||
/// 获取可用订阅列表
|
||
Future<void> _kr_fetchAvailableSubscribes() async {
|
||
try {
|
||
KRLogUtil.kr_i('开始获取可用订阅列表', tag: 'SubscribeService');
|
||
|
||
// 🔧 修复:同时更新已订阅记录列表,确保判断准确
|
||
final alreadySubscribeResult = await kr_subscribeApi.kr_getAlreadySubscribe();
|
||
alreadySubscribeResult.fold(
|
||
(error) {
|
||
KRLogUtil.kr_e('获取已订阅列表失败: ${error.msg}', tag: 'SubscribeService');
|
||
},
|
||
(subscribes) {
|
||
kr_alreadySubscribe.value = subscribes;
|
||
KRLogUtil.kr_i('更新已订阅记录: ${subscribes.length} 个订阅',
|
||
tag: 'SubscribeService');
|
||
},
|
||
);
|
||
|
||
final result = await kr_subscribeApi.kr_userAvailableSubscribe();
|
||
|
||
result.fold(
|
||
(error) {
|
||
KRLogUtil.kr_e('获取可用订阅失败: ${error.msg}', tag: 'SubscribeService');
|
||
},
|
||
(subscribes) {
|
||
// 如果当前有选中的订阅,检查是否还在可用列表中
|
||
if (kr_currentSubscribe.value != null) {
|
||
final currentSubscribeExists = subscribes.any(
|
||
(subscribe) => subscribe.id == kr_currentSubscribe.value?.id,
|
||
);
|
||
|
||
// 如果当前订阅不在可用列表中,清除当前订阅
|
||
if (!currentSubscribeExists) {
|
||
// 如果当前订阅为null或者已过期,才设置新的订阅
|
||
if (kr_currentSubscribe.value == null ||
|
||
DateTime.parse(kr_currentSubscribe.value!.expireTime)
|
||
.isBefore(DateTime.now())) {
|
||
kr_availableSubscribes.assignAll(subscribes);
|
||
if (subscribes.isNotEmpty) {
|
||
// 🔧 修复:优先选择已购买的套餐
|
||
KRUserAvailableSubscribeItem? selectedSubscribe;
|
||
|
||
// 优先选择已购买的套餐(非试用)
|
||
for (var subscribe in subscribes) {
|
||
final isSubscribed = kr_alreadySubscribe.any(
|
||
(alreadySub) => alreadySub.userSubscribeId == subscribe.id,
|
||
);
|
||
if (isSubscribed) {
|
||
selectedSubscribe = subscribe;
|
||
KRLogUtil.kr_i('选择已购买的套餐: ${selectedSubscribe.name}',
|
||
tag: 'SubscribeService');
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 如果没有已购买的套餐,选择第一个(可能是试用套餐)
|
||
if (selectedSubscribe == null) {
|
||
selectedSubscribe = subscribes.first;
|
||
KRLogUtil.kr_i('没有已购买的套餐,选择第一个: ${selectedSubscribe.name}',
|
||
tag: 'SubscribeService');
|
||
}
|
||
|
||
kr_currentSubscribe.value = selectedSubscribe;
|
||
} else {
|
||
kr_currentSubscribe.value = null;
|
||
KRLogUtil.kr_i('没有可用的订阅,清除选中状态', tag: 'SubscribeService');
|
||
}
|
||
kr_clearCutNodeData();
|
||
} else {
|
||
KRLogUtil.kr_i('当前订阅仍然有效,保持选中状态', tag: 'SubscribeService');
|
||
}
|
||
} else {
|
||
// 如果当前订阅仍然有效,更新为最新的订阅信息
|
||
final updatedSubscribe = subscribes.firstWhere(
|
||
(subscribe) => subscribe.id == kr_currentSubscribe.value?.id,
|
||
);
|
||
|
||
// 检查订阅是否有效(未过期且未超出流量限制)
|
||
final isExpired = DateTime.parse(updatedSubscribe.expireTime)
|
||
.isBefore(DateTime.now());
|
||
final isOverTraffic = updatedSubscribe.traffic > 0 &&
|
||
(updatedSubscribe.download + updatedSubscribe.upload) >=
|
||
updatedSubscribe.traffic;
|
||
|
||
if (isExpired || isOverTraffic) {
|
||
if (KRSingBoxImp.instance.kr_status ==
|
||
SingboxStatus.started()) {
|
||
KRSingBoxImp.instance.kr_stop();
|
||
}
|
||
}
|
||
|
||
kr_currentSubscribe.value = updatedSubscribe;
|
||
KRLogUtil.kr_i('更新当前订阅信息', tag: 'SubscribeService');
|
||
|
||
// 更新可用订阅列表
|
||
kr_availableSubscribes.assignAll(subscribes);
|
||
}
|
||
}
|
||
|
||
KRLogUtil.kr_i('获取可用订阅列表成功: ${subscribes.length} 个订阅',
|
||
tag: 'SubscribeService');
|
||
KRLogUtil.kr_i(
|
||
'订阅列表: ${subscribes.map((s) => '${s.name}(${s.id})').join(', ')}',
|
||
tag: 'SubscribeService');
|
||
},
|
||
);
|
||
} catch (err) {
|
||
KRLogUtil.kr_e('获取可用订阅异常: $err', tag: 'SubscribeService');
|
||
}
|
||
}
|
||
|
||
/// 切换订阅
|
||
Future<void> kr_switchSubscribe(
|
||
KRUserAvailableSubscribeItem subscribe) async {
|
||
// 如果切换的是当前订阅,直接返回
|
||
if (subscribe.id == kr_currentSubscribe.value?.id) {
|
||
KRLogUtil.kr_i('切换的订阅与当前订阅相同,无需切换', tag: 'SubscribeService');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_loading;
|
||
await kr_clearCutNodeData();
|
||
KRLogUtil.kr_i('开始切换订阅: ${subscribe.name + subscribe.id.toString()}',
|
||
tag: 'SubscribeService');
|
||
|
||
// 更新当前订阅
|
||
kr_currentSubscribe.value = subscribe;
|
||
|
||
final result =
|
||
await kr_subscribeApi.kr_nodeList(kr_currentSubscribe.value!.id);
|
||
|
||
result.fold((error) {
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_error;
|
||
}, (nodes) {
|
||
// 记录当前节点列表是否包含试用节点
|
||
kr_hasTrialNodes.value = nodes.isTryOut;
|
||
KRLogUtil.kr_i('切换订阅 - 节点列表包含试用节点: ${kr_hasTrialNodes.value}', tag: 'SubscribeService');
|
||
|
||
// 处理节点列表(不使用分组)
|
||
final listModel = KrOutboundsList();
|
||
listModel.processOutboundItems(nodes.list, []);
|
||
|
||
// 更新UI数据
|
||
groupOutboundList.value = listModel.groupOutboundList;
|
||
countryOutboundList.value = listModel.countryOutboundList;
|
||
allList.value = listModel.allList;
|
||
keyList = listModel.keyList;
|
||
|
||
// 保存配置
|
||
KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList);
|
||
KRSingBoxImp.instance.kr_saveAllOutbounds(listModel.configJsonList);
|
||
|
||
// 更新试用和订阅状态
|
||
_kr_updateSubscribeStatus();
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_success;
|
||
});
|
||
} catch (e) {
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_error;
|
||
KRLogUtil.kr_e('切换订阅失败: $e', tag: 'SubscribeService');
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
/// 更新订阅状态
|
||
void _kr_updateSubscribeStatus() {
|
||
// 停止之前的计时器
|
||
_kr_trialTimer?.cancel();
|
||
_kr_subscriptionTimer?.cancel();
|
||
|
||
if (kr_currentSubscribe.value == null) {
|
||
kr_isTrial.value = false;
|
||
return;
|
||
}
|
||
|
||
// 🔧 关键修复:信任 API 返回的 isTryOut 字段,不再额外检查购买记录
|
||
// 之前的逻辑会在购买套餐后仍显示"试用中",因为购买记录可能未及时更新
|
||
final currentSubscribe = kr_currentSubscribe.value!;
|
||
|
||
// 1. 使用 API 返回的 isTryOut 字段(最权威的判断)
|
||
kr_isTrial.value = currentSubscribe.isTryOut;
|
||
KRLogUtil.kr_i('步骤1 - API isTryOut 字段: ${currentSubscribe.isTryOut}', tag: 'SubscribeService');
|
||
|
||
// 2. 仅在 API 没有明确标识时,才检查订阅名称作为备用方案
|
||
// 注意:只有当 API 说不是试用,但名称包含"试用"时才覆盖
|
||
if (!kr_isTrial.value && currentSubscribe.name.contains('试用')) {
|
||
kr_isTrial.value = true;
|
||
KRLogUtil.kr_i('步骤2 - 订阅名称包含"试用"关键字,判定为试用', tag: 'SubscribeService');
|
||
}
|
||
|
||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SubscribeService');
|
||
KRLogUtil.kr_i('当前订阅: ${currentSubscribe.name}(${currentSubscribe.id})', tag: 'SubscribeService');
|
||
KRLogUtil.kr_i('API isTryOut: ${currentSubscribe.isTryOut}', tag: 'SubscribeService');
|
||
KRLogUtil.kr_i('已订阅记录数: ${kr_alreadySubscribe.length}', tag: 'SubscribeService');
|
||
KRLogUtil.kr_i('已订阅ID列表: ${kr_alreadySubscribe.map((s) => s.userSubscribeId).join(', ')}', tag: 'SubscribeService');
|
||
KRLogUtil.kr_i('✅ 最终试用状态: ${kr_isTrial.value ? "试用" : "付费"}', tag: 'SubscribeService');
|
||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SubscribeService');
|
||
|
||
if (kr_isTrial.value) {
|
||
// 启动试用倒计时
|
||
_kr_startTrialTimer();
|
||
}
|
||
// 检查订阅状态
|
||
else if (kr_currentSubscribe.value != null) {
|
||
final expireTime = DateTime.parse(kr_currentSubscribe.value!.expireTime);
|
||
final now = DateTime.now();
|
||
final difference = expireTime.difference(now);
|
||
|
||
// 检查是否最后一天
|
||
kr_isLastDayOfSubscription.value = difference.inDays <= 1;
|
||
|
||
if (kr_isLastDayOfSubscription.value) {
|
||
// 启动订阅倒计时
|
||
_kr_startSubscriptionTimer();
|
||
KRLogUtil.kr_i('当前订阅最后一天', tag: 'SubscribeService');
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 启动试用倒计时
|
||
void _kr_startTrialTimer() {
|
||
_kr_trialTimer?.cancel();
|
||
|
||
// 立即执行一次
|
||
_kr_updateTrialTime();
|
||
|
||
// 设置定时器
|
||
_kr_trialTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||
_kr_updateTrialTime();
|
||
});
|
||
}
|
||
|
||
/// 更新试用时间
|
||
void _kr_updateTrialTime() {
|
||
if (kr_currentSubscribe.value == null) {
|
||
_kr_trialTimer?.cancel();
|
||
return;
|
||
}
|
||
|
||
final expireTime = DateTime.parse(kr_currentSubscribe.value!.expireTime);
|
||
final now = DateTime.now();
|
||
final difference = expireTime.difference(now);
|
||
|
||
if (difference.isNegative) {
|
||
/// 停止
|
||
if (KRSingBoxImp.instance.kr_status == SingboxStatus.started()) {
|
||
KRSingBoxImp.instance.kr_stop();
|
||
}
|
||
|
||
_kr_trialTimer?.cancel();
|
||
kr_trialRemainingTime.value = 'error.60001'.tr;
|
||
return;
|
||
}
|
||
|
||
final days = difference.inDays;
|
||
final hours = difference.inHours % 24;
|
||
final minutes = difference.inMinutes % 60;
|
||
final seconds = difference.inSeconds % 60;
|
||
|
||
kr_trialRemainingTime.value = AppTranslations.kr_home.trialTimeWithDays(
|
||
days,
|
||
hours,
|
||
minutes,
|
||
seconds,
|
||
);
|
||
}
|
||
|
||
/// 启动订阅倒计时
|
||
void _kr_startSubscriptionTimer() {
|
||
_kr_subscriptionTimer?.cancel();
|
||
|
||
// 立即执行一次
|
||
_kr_updateSubscriptionTime();
|
||
|
||
// 设置定时器
|
||
_kr_subscriptionTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||
_kr_updateSubscriptionTime();
|
||
});
|
||
}
|
||
|
||
/// 更新订阅时间
|
||
void _kr_updateSubscriptionTime() {
|
||
if (kr_currentSubscribe.value == null) {
|
||
_kr_subscriptionTimer?.cancel();
|
||
return;
|
||
}
|
||
|
||
final expireTime = DateTime.parse(kr_currentSubscribe.value!.expireTime);
|
||
final now = DateTime.now();
|
||
final difference = expireTime.difference(now);
|
||
|
||
if (difference.isNegative) {
|
||
_kr_subscriptionTimer?.cancel();
|
||
|
||
/// 停止
|
||
if (KRSingBoxImp.instance.kr_status == SingboxStatus.started()) {
|
||
KRSingBoxImp.instance.kr_stop();
|
||
}
|
||
|
||
kr_subscriptionRemainingTime.value = 'error.60001'.tr;
|
||
;
|
||
return;
|
||
}
|
||
|
||
final days = difference.inDays;
|
||
final hours = difference.inHours % 24;
|
||
final minutes = difference.inMinutes % 60;
|
||
final seconds = difference.inSeconds % 60;
|
||
|
||
kr_subscriptionRemainingTime.value =
|
||
AppTranslations.kr_home.trialTimeWithDays(
|
||
days,
|
||
hours,
|
||
minutes,
|
||
seconds,
|
||
);
|
||
}
|
||
|
||
/// 启动定期更新
|
||
void _kr_startPeriodicUpdate() {
|
||
// 每5分钟更新一次可用订阅列表
|
||
_kr_updateTimer = Timer.periodic(const Duration(seconds: 60), (timer) {
|
||
_kr_fetchAvailableSubscribes();
|
||
});
|
||
}
|
||
|
||
/// 刷新所有数据
|
||
Future<void> kr_refreshAll() async {
|
||
try {
|
||
// ✅ 方案4:请求去重检查 - 防止重复刷新
|
||
if (_isRefreshing) {
|
||
KRLogUtil.kr_w('⚠️ 正在刷新中,忽略重复请求', tag: 'SubscribeService');
|
||
return;
|
||
}
|
||
|
||
// 设置刷新标志
|
||
_isRefreshing = true;
|
||
KRLogUtil.kr_i('🔄 开始刷新订阅数据...', tag: 'SubscribeService');
|
||
|
||
// 🔧 修复2: 添加登录状态检查 - 只有已登录用户才能刷新订阅数据
|
||
if (!KRAppRunData().kr_isLogin.value) {
|
||
KRLogUtil.kr_e('❌ 未登录用户,无法刷新订阅数据', tag: 'SubscribeService');
|
||
kr_availableSubscribes.clear();
|
||
kr_currentSubscribe.value = null;
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_error;
|
||
_isRefreshing = false; // 重置标志
|
||
return;
|
||
}
|
||
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_loading;
|
||
await kr_clearData();
|
||
KRLogUtil.kr_i('开始刷新所有数据', tag: 'SubscribeService');
|
||
|
||
/// 数组有值 ,表示订阅过, 用于判断试用的
|
||
final alreadySubscribeResult =
|
||
await kr_subscribeApi.kr_getAlreadySubscribe();
|
||
alreadySubscribeResult.fold(
|
||
(error) {
|
||
// ✅ 方案5:使用友好的错误信息
|
||
final friendlyMsg = _kr_getFriendlyErrorMessage(error.code, error.msg);
|
||
KRLogUtil.kr_e('获取已订阅列表失败: $friendlyMsg (错误码: ${error.code})', tag: 'SubscribeService');
|
||
throw Exception('获取已订阅列表失败: $friendlyMsg');
|
||
},
|
||
(subscribes) {
|
||
kr_alreadySubscribe.value = subscribes;
|
||
KRLogUtil.kr_i('订阅记录: ${subscribes.length} 个订阅',
|
||
tag: 'SubscribeService');
|
||
},
|
||
);
|
||
|
||
// 🔧 取消节点分组的概念,不再调用 kr_nodeGroupList
|
||
// 直接使用节点列表,通过 is_try_out 字段区分免费/付费节点
|
||
kr_nodeGroups.clear();
|
||
KRLogUtil.kr_i('已取消节点分组,将直接使用节点列表', tag: 'SubscribeService');
|
||
|
||
// 保存当前选中的订阅名称
|
||
final currentSubscribeID = kr_currentSubscribe.value?.id;
|
||
|
||
// 获取可用订阅列表
|
||
final subscribeResult = await kr_subscribeApi.kr_userAvailableSubscribe();
|
||
|
||
// 处理订阅列表结果
|
||
final subscribes = await subscribeResult.fold(
|
||
(error) {
|
||
// ✅ 方案5:使用友好的错误信息
|
||
final friendlyMsg = _kr_getFriendlyErrorMessage(error.code, error.msg);
|
||
KRLogUtil.kr_e('获取可用订阅失败: $friendlyMsg (错误码: ${error.code})', tag: 'SubscribeService');
|
||
throw Exception('获取可用订阅失败: $friendlyMsg');
|
||
},
|
||
(subscribes) => subscribes,
|
||
);
|
||
|
||
// 如果获取订阅列表为空(试用结束且未购买),设置为成功状态但清空订阅
|
||
if (subscribes.isEmpty) {
|
||
KRLogUtil.kr_i('订阅列表为空(试用结束或未购买),清空订阅数据', tag: 'SubscribeService');
|
||
kr_availableSubscribes.clear();
|
||
kr_currentSubscribe.value = null;
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_success;
|
||
return;
|
||
}
|
||
|
||
// 更新订阅列表
|
||
kr_availableSubscribes.assignAll(subscribes);
|
||
|
||
KRLogUtil.kr_i('获取可用订阅列表成功: ${subscribes.length} 个订阅',
|
||
tag: 'SubscribeService');
|
||
|
||
// 如果之前的订阅名称在可用列表中,保持选中
|
||
if (subscribes.isNotEmpty) {
|
||
KRUserAvailableSubscribeItem? selectedSubscribe;
|
||
|
||
// 1. 首先尝试找到之前选中的订阅
|
||
if (currentSubscribeID != null) {
|
||
try {
|
||
selectedSubscribe = subscribes.firstWhere(
|
||
(subscribe) => subscribe.id == currentSubscribeID,
|
||
);
|
||
KRLogUtil.kr_i('保持之前选中的订阅: ${selectedSubscribe.name}',
|
||
tag: 'SubscribeService');
|
||
} catch (e) {
|
||
// 没找到之前的订阅,继续下一步
|
||
KRLogUtil.kr_i('之前选中的订阅已不存在,重新选择', tag: 'SubscribeService');
|
||
}
|
||
}
|
||
|
||
// 2. 如果没有找到之前的订阅,优先选择试用套餐
|
||
if (selectedSubscribe == null) {
|
||
KRLogUtil.kr_i('开始查找试用套餐...', tag: 'SubscribeService');
|
||
|
||
for (var subscribe in subscribes) {
|
||
KRLogUtil.kr_i('检查订阅: ${subscribe.name}(${subscribe.id}), 是否试用: ${subscribe.isTryOut}',
|
||
tag: 'SubscribeService');
|
||
|
||
if (subscribe.isTryOut) {
|
||
selectedSubscribe = subscribe;
|
||
KRLogUtil.kr_i('✅ 找到试用套餐,默认选择: ${selectedSubscribe.name}',
|
||
tag: 'SubscribeService');
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3. 如果没有试用套餐,选择已购买的套餐(非试用)
|
||
if (selectedSubscribe == null) {
|
||
KRLogUtil.kr_i('没有试用套餐,查找已购买的套餐...', tag: 'SubscribeService');
|
||
KRLogUtil.kr_i('已订阅记录: ${kr_alreadySubscribe.map((s) => s.userSubscribeId).join(', ')}',
|
||
tag: 'SubscribeService');
|
||
|
||
for (var subscribe in subscribes) {
|
||
final isSubscribed = kr_alreadySubscribe.any(
|
||
(alreadySub) => alreadySub.userSubscribeId == subscribe.id,
|
||
);
|
||
KRLogUtil.kr_i('检查订阅: ${subscribe.name}(${subscribe.id}), 是否已购买: $isSubscribed',
|
||
tag: 'SubscribeService');
|
||
|
||
if (isSubscribed) {
|
||
selectedSubscribe = subscribe;
|
||
KRLogUtil.kr_i('选择已购买的套餐: ${selectedSubscribe.name}',
|
||
tag: 'SubscribeService');
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4. 如果都没有找到,选择第一个
|
||
if (selectedSubscribe == null) {
|
||
selectedSubscribe = subscribes.first;
|
||
KRLogUtil.kr_i('没有找到匹配的套餐,选择第一个: ${selectedSubscribe.name}',
|
||
tag: 'SubscribeService');
|
||
}
|
||
|
||
kr_currentSubscribe.value = selectedSubscribe;
|
||
|
||
// 获取节点列表
|
||
final nodeResult =
|
||
await kr_subscribeApi.kr_nodeList(kr_currentSubscribe.value!.id);
|
||
|
||
// 处理节点列表结果
|
||
final nodes = await nodeResult.fold(
|
||
(error) {
|
||
// ✅ 方案5:使用友好的错误信息
|
||
final friendlyMsg = _kr_getFriendlyErrorMessage(error.code, error.msg);
|
||
KRLogUtil.kr_e('获取节点列表失败: $friendlyMsg (错误码: ${error.code})', tag: 'SubscribeService');
|
||
throw Exception('获取节点列表失败: $friendlyMsg');
|
||
},
|
||
(nodes) => nodes,
|
||
);
|
||
|
||
// 记录当前节点列表是否包含试用节点
|
||
kr_hasTrialNodes.value = nodes.isTryOut;
|
||
KRLogUtil.kr_i('节点列表包含试用节点: ${kr_hasTrialNodes.value}', tag: 'SubscribeService');
|
||
|
||
// 处理节点列表(不使用分组)
|
||
final listModel = KrOutboundsList();
|
||
listModel.processOutboundItems(nodes.list, []);
|
||
|
||
// 更新UI数据
|
||
groupOutboundList.value = listModel.groupOutboundList;
|
||
countryOutboundList.value = listModel.countryOutboundList;
|
||
allList.value = listModel.allList;
|
||
keyList = listModel.keyList;
|
||
|
||
// 保存配置
|
||
KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList);
|
||
KRSingBoxImp.instance.kr_saveAllOutbounds(listModel.configJsonList);
|
||
|
||
// 更新试用和订阅状态
|
||
_kr_updateSubscribeStatus();
|
||
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_success;
|
||
|
||
_kr_startPeriodicUpdate();
|
||
} else {
|
||
KRLogUtil.kr_w('没有可用的订阅', tag: 'SubscribeService');
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_error;
|
||
return;
|
||
}
|
||
} catch (err, stackTrace) {
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_error;
|
||
KRLogUtil.kr_e('刷新数据异常: $err\n$stackTrace', tag: 'SubscribeService');
|
||
} finally {
|
||
// ✅ 方案4:无论成功或失败,都重置刷新标志
|
||
_isRefreshing = false;
|
||
KRLogUtil.kr_i('✅ 刷新完成,重置刷新标志', tag: 'SubscribeService');
|
||
}
|
||
}
|
||
|
||
//// 清楚
|
||
Future<void> kr_clearData() async {
|
||
_kr_subscriptionTimer?.cancel();
|
||
_kr_trialTimer?.cancel();
|
||
_kr_updateTimer?.cancel();
|
||
|
||
kr_availableSubscribes.clear();
|
||
|
||
await kr_clearCutNodeData();
|
||
}
|
||
|
||
Future<void> kr_logout() async {
|
||
kr_alreadySubscribe.clear();
|
||
kr_nodeGroups.clear();
|
||
kr_currentSubscribe.value = null;
|
||
|
||
await kr_clearData();
|
||
}
|
||
|
||
Future<void> kr_clearCutNodeData() async {
|
||
kr_isLastDayOfSubscription.value = false;
|
||
kr_isTrial.value = false;
|
||
kr_hasTrialNodes.value = false;
|
||
|
||
kr_subscriptionRemainingTime.value = '';
|
||
kr_trialRemainingTime.value = '';
|
||
|
||
/// 停止
|
||
if (KRSingBoxImp.instance.kr_status == SingboxStatus.started()) {
|
||
await KRSingBoxImp.instance.kr_stop();
|
||
}
|
||
|
||
// 更新UI数据
|
||
groupOutboundList.clear();
|
||
countryOutboundList.clear();
|
||
allList.clear();
|
||
keyList.clear();
|
||
|
||
// 保存配置
|
||
KRSingBoxImp.instance.kr_saveOutbounds([]);
|
||
KRSingBoxImp.instance.kr_saveAllOutbounds([]);
|
||
|
||
}
|
||
|
||
/// 获取当前订阅
|
||
KRUserAvailableSubscribeItem? get kr_getCurrentSubscribe =>
|
||
kr_currentSubscribe.value;
|
||
|
||
/// 获取免费节点列表(试用节点)
|
||
/// 当试用结束时,返回空列表
|
||
List<KROutboundItem> get kr_freeNodes {
|
||
if (!kr_hasTrialNodes.value) {
|
||
return [];
|
||
}
|
||
|
||
// 如果当前是试用状态或有试用节点,返回所有节点作为免费节点
|
||
// 如果试用已结束(kr_isTrial=false 且 kr_hasTrialNodes=true),返回空列表
|
||
if (kr_isTrial.value) {
|
||
KRLogUtil.kr_i('返回免费节点: ${allList.length} 个', tag: 'SubscribeService');
|
||
return allList.toList();
|
||
} else {
|
||
KRLogUtil.kr_i('试用已结束,不显示免费节点', tag: 'SubscribeService');
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/// 获取付费节点列表
|
||
/// 如果当前不是试用,返回所有节点
|
||
/// 如果当前是试用,返回空列表
|
||
List<KROutboundItem> get kr_paidNodes {
|
||
if (kr_isTrial.value) {
|
||
KRLogUtil.kr_i('当前是试用状态,不显示付费节点', tag: 'SubscribeService');
|
||
return [];
|
||
} else {
|
||
KRLogUtil.kr_i('返回付费节点: ${allList.length} 个', tag: 'SubscribeService');
|
||
return allList.toList();
|
||
}
|
||
}
|
||
|
||
/// 是否应该显示免费标签页
|
||
bool get kr_shouldShowFreeTab {
|
||
return kr_hasTrialNodes.value && kr_isTrial.value;
|
||
}
|
||
|
||
/// 是否应该显示付费标签页
|
||
bool get kr_shouldShowPaidTab {
|
||
return !kr_isTrial.value;
|
||
}
|
||
}
|