552 lines
17 KiB
Dart
Executable File
552 lines
17 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 '../../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 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;
|
||
|
||
/// 重置订阅周期
|
||
Future<void> kr_resetSubscribePeriod() async {
|
||
if (kr_currentSubscribe.value == null) {
|
||
KRCommonUtil.kr_showToast('请先选择订阅');
|
||
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 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) {
|
||
kr_currentSubscribe.value = subscribes.first;
|
||
KRLogUtil.kr_i('设置新的订阅: ${subscribes.first.name}',
|
||
tag: 'SubscribeService');
|
||
} 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) {
|
||
// 处理节点列表
|
||
final listModel = KrOutboundsList();
|
||
listModel.processOutboundItems(nodes.list, kr_nodeGroups);
|
||
|
||
// 更新UI数据
|
||
groupOutboundList.value = listModel.groupOutboundList;
|
||
countryOutboundList.value = listModel.countryOutboundList;
|
||
allList.value = listModel.allList;
|
||
keyList = listModel.keyList;
|
||
|
||
// 保存配置
|
||
KRSingBoxImp.instance.kr_saveOutbounds(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();
|
||
|
||
// 检查试用状态
|
||
final bool kr_isSubscribed = kr_currentSubscribe.value != null &&
|
||
kr_alreadySubscribe.any((subscribe) =>
|
||
kr_currentSubscribe.value?.id == subscribe.userSubscribeId);
|
||
|
||
KRLogUtil.kr_i('当前订阅状态: ${kr_isSubscribed ? "已订阅" : "未订阅"}',
|
||
tag: 'SubscribeService');
|
||
KRLogUtil.kr_i('当前订阅ID: ${kr_currentSubscribe.value?.id}',
|
||
tag: 'SubscribeService');
|
||
KRLogUtil.kr_i(
|
||
'已订阅记录: ${kr_alreadySubscribe.map((s) => s.userSubscribeId).join(', ')}',
|
||
tag: 'SubscribeService');
|
||
|
||
// 设置试用状态
|
||
kr_isTrial.value = kr_currentSubscribe.value != null && !kr_isSubscribed;
|
||
|
||
KRLogUtil.kr_i('试用状态: ${kr_isTrial.value ? "是" : "否"}',
|
||
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 {
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_loading;
|
||
await kr_clearData();
|
||
KRLogUtil.kr_i('开始刷新所有数据', tag: 'SubscribeService');
|
||
|
||
/// 数组有值 ,表示订阅过, 用于判断试用的
|
||
final alreadySubscribeResult =
|
||
await kr_subscribeApi.kr_getAlreadySubscribe();
|
||
alreadySubscribeResult.fold(
|
||
(error) {
|
||
throw Exception('获取已订阅列表失败: ${error.msg}');
|
||
},
|
||
(subscribes) {
|
||
kr_alreadySubscribe.value = subscribes;
|
||
KRLogUtil.kr_i('订阅记录: ${subscribes.length} 个订阅',
|
||
tag: 'SubscribeService');
|
||
},
|
||
);
|
||
|
||
final result = await kr_subscribeApi.kr_nodeGroupList();
|
||
result.fold(
|
||
(error) {
|
||
throw Exception('获取节点分组失败: ${error.msg}');
|
||
},
|
||
(groups) {
|
||
kr_nodeGroups.value = groups;
|
||
},
|
||
);
|
||
|
||
// 保存当前选中的订阅名称
|
||
final currentSubscribeID = kr_currentSubscribe.value?.id;
|
||
|
||
// 获取可用订阅列表
|
||
final subscribeResult = await kr_subscribeApi.kr_userAvailableSubscribe();
|
||
|
||
// 处理订阅列表结果
|
||
final subscribes = await subscribeResult.fold(
|
||
(error) {
|
||
throw Exception('获取可用订阅失败: ${error.msg}');
|
||
},
|
||
(subscribes) => subscribes,
|
||
);
|
||
|
||
// 如果获取订阅列表失败,直接返回
|
||
if (subscribes.isEmpty) {
|
||
kr_currentStatus.value = KRSubscribeServiceStatus.kr_none;
|
||
kr_availableSubscribes.clear();
|
||
return;
|
||
}
|
||
|
||
// 更新订阅列表
|
||
kr_availableSubscribes.assignAll(subscribes);
|
||
|
||
KRLogUtil.kr_i('获取可用订阅列表成功: ${subscribes.length} 个订阅',
|
||
tag: 'SubscribeService');
|
||
|
||
// 如果之前的订阅名称在可用列表中,保持选中
|
||
if (subscribes.isNotEmpty) {
|
||
final previousSubscribe = subscribes.firstWhere(
|
||
(subscribe) => subscribe.id == currentSubscribeID,
|
||
orElse: () => subscribes.first,
|
||
);
|
||
|
||
if (previousSubscribe.id != currentSubscribeID) {
|
||
kr_currentSubscribe.value = previousSubscribe;
|
||
KRLogUtil.kr_i('切换订阅: ${previousSubscribe.name}',
|
||
tag: 'SubscribeService');
|
||
} else {
|
||
kr_currentSubscribe.value = previousSubscribe;
|
||
}
|
||
|
||
// 获取节点列表
|
||
final nodeResult =
|
||
await kr_subscribeApi.kr_nodeList(kr_currentSubscribe.value!.id);
|
||
|
||
// 处理节点列表结果
|
||
final nodes = await nodeResult.fold(
|
||
(error) {
|
||
throw Exception('获取节点列表失败: ${error.msg}');
|
||
},
|
||
(nodes) => nodes,
|
||
);
|
||
|
||
// 处理节点列表
|
||
final listModel = KrOutboundsList();
|
||
listModel.processOutboundItems(nodes.list, kr_nodeGroups);
|
||
|
||
// 更新UI数据
|
||
groupOutboundList.value = listModel.groupOutboundList;
|
||
countryOutboundList.value = listModel.countryOutboundList;
|
||
allList.value = listModel.allList;
|
||
keyList = listModel.keyList;
|
||
|
||
// 保存配置
|
||
KRSingBoxImp.instance.kr_saveOutbounds(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');
|
||
}
|
||
}
|
||
|
||
//// 清楚
|
||
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_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([]);
|
||
}
|
||
|
||
/// 获取当前订阅
|
||
KRUserAvailableSubscribeItem? get kr_getCurrentSubscribe =>
|
||
kr_currentSubscribe.value;
|
||
}
|