Files
peko-ios/YuMi/Modules/YMRoom/View/SendGiftView/GiftComboManager.m

985 lines
35 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// GiftComboManager.m
// YuMi
//
// Created by P on 2024/9/5.
//
// 处理连击面板逻辑
#import "GiftComboManager.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <Bugly/Bugly.h>
#import "BuglyManager.h"
#import "Api+Gift.h"
#import "UserInfoModel.h"
#import "AttachmentModel.h"
#import "XPGiftCountModel.h"
#import "GiftReceiveInfoModel.h"
#import "XPMessageRemoteExtModel.h"
#import "GiftComboFlagView.h"
// 通知常量实现
NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotification";
@interface GiftComboManager ()
@property (nonatomic, assign) BOOL enableCombo;
@property (nonatomic, strong) NSMutableArray *requestQueue;
// 用来存储 GiftReceiveInfoModel 和 NSDictionary 的元数据队列
@property (nonatomic, strong) NSMutableArray *networkQueue; // 网络通信队列AttachmentModel
@property (nonatomic, strong) NSMutableArray *uiQueue; // UI动画队列GiftReceiveInfoModel
@property (nonatomic, strong) dispatch_source_t comboFlagTimer;
@property (nonatomic, strong) UIView *containerView;
// 定时器,处理请求的调度器
@property (nonatomic, strong) dispatch_source_t timer;
// 新增:后台处理队列
@property (nonatomic, strong) dispatch_queue_t backgroundQueue;
@property (nonatomic, strong) dispatch_queue_t networkProcessingQueue;
@property (nonatomic, copy) NSArray *sendGiftToUIDs;
@property (nonatomic, assign) GiftSourceType giftSourceType;
@property (nonatomic, strong) GiftInfoModel *giftInfo;
@property (nonatomic, assign) RoomSendGiftType roomSendGiftType;
@property (nonatomic, copy) NSString *roomUID;
@property (nonatomic, copy) NSString *giftNumPerTimes;
@property (nonatomic, strong) UserInfoModel *sendGiftUserInfo;
@property (nonatomic, copy) NSString *sessionID;
@property (nonatomic, strong) XPGiftCountModel *countModel;
@property (nonatomic, assign) NSInteger combo;
@property (nonatomic, assign) bool isCombing;
@property (nonatomic, copy) void (^actionCallback)(ComboActionType type);
@property (nonatomic, copy) NSString *errorMessage;
@property (nonatomic, strong) NSMutableArray<GiftComboFlagView *> *activeViews; // 用于存储最多2个活跃的动画视图
@end
@implementation GiftComboManager
#pragma mark - 单例方法
- (void)dealloc {
NSLog(@"[Combo effect] 🗑️ GiftComboManager dealloc开始 - %p", self);
// 🔥 修复确保所有timer都被停止
[self forceStopAllTimers];
// 停止UI队列处理
[self stopProcessingUIQueue];
// 清空所有队列
[self clearAllQueues];
// 重置状态
self.isCombing = NO;
self.enableCombo = NO;
self.actionCallback = nil;
NSLog(@"[Combo effect] 🗑️ GiftComboManager dealloc完成 - %p", self);
}
+ (instancetype)sharedManager {
static GiftComboManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
// sharedInstance.giftComboQueue = [NSMutableArray array];
sharedInstance.networkQueue = [NSMutableArray array];
sharedInstance.uiQueue = [NSMutableArray array];
sharedInstance.activeViews = [NSMutableArray array];
sharedInstance.requestQueue = [NSMutableArray array];
// 初始化后台处理队列
sharedInstance.backgroundQueue = dispatch_queue_create("com.yumi.giftcombo.background", DISPATCH_QUEUE_CONCURRENT);
sharedInstance.networkProcessingQueue = dispatch_queue_create("com.yumi.giftcombo.network", DISPATCH_QUEUE_SERIAL);
[sharedInstance startProcessingUIQueue];
});
return sharedInstance;
}
//// 添加 GiftReceiveInfoModel 和 metadata 到队列
- (void)addGiftComboWithInfo:(GiftReceiveInfoModel *)info andMetadata:(NSDictionary *)metadata {
if (info && metadata) {
// 将元数据打包成字典并添加到队列
@synchronized (self) {
NSDictionary *comboData = @{@"info": info, @"metadata": metadata};
[self.networkQueue addObject:comboData];
}
// 启动定时器
[self startProcessingQueue];
}
}
- (void)addComboFromNIMAttachment:(AttachmentModel *)attachment {
if (attachment) {
// 将元数据打包成字典并添加到队列
@synchronized (self) {
[self.networkQueue addObject:attachment];
}
// 启动定时器
[self startProcessingQueue];
}
}
// 开始连击,重置
- (void)reset {
NSLog(@"[Combo effect] 🔄 开始连击重置 - combo: %ld -> 1, enableCombo: %@, actionCallback: %@",
(long)self.combo,
self.enableCombo ? @"YES" : @"NO",
self.actionCallback ? @"可用" : @"为空");
// 🔥 修复:检查是否正在重置过程中
if (self.isCombing && self.combo == 0) {
NSLog(@"[Combo effect] ⚠️ 正在重置过程中,跳过重复重置");
return;
}
// 🔥 修复如果actionCallback为空延迟执行reset
if (!self.actionCallback) {
NSLog(@"[Combo effect] ⚠️ actionCallback为空延迟执行reset");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self reset];
});
return;
}
// 🔥 修复确保enableCombo状态正确
if (!self.enableCombo) {
NSLog(@"[Combo effect] ⚠️ enableCombo为NO尝试重新激活");
self.enableCombo = YES;
}
// 确保连击计数正确重置为 1
_combo = 1;
_errorMessage = @"";
// 验证重置后的状态
NSLog(@"[Combo effect] 🔍 重置后验证 - combo: %ld, enableCombo: %@", (long)self.combo, self.enableCombo ? @"YES" : @"NO");
// 发送通知,让 GiftComboView 重置显示
[[NSNotificationCenter defaultCenter] postNotificationName:@"ComboCountReset" object:nil];
// 🔥 修复:确保状态正确设置后再触发回调
self.isCombing = YES;
// 检查是否应该显示连击面板 - 确保在主线程执行UI回调
if (self.actionCallback) {
NSLog(@"[Combo effect] 📱 触发连击面板显示回调");
@kWeakify(self);
[self safeExecuteUIBlock:^{
@kStrongify(self);
if (self && self.actionCallback) {
self.actionCallback(ComboAction_ShowPanel);
}
}];
} else {
NSLog(@"[Combo effect] ⚠️ actionCallback为空不显示连击面板");
}
if (self.handleRoomUIChanged) {
NSLog(@"[Combo effect] 🎮 隐藏房间UI元素");
@kWeakify(self);
[self safeExecuteUIBlock:^{
@kStrongify(self);
if (self && self.handleRoomUIChanged) {
self.handleRoomUIChanged(YES);
}
}];
}
NSLog(@"[Combo effect] ✅ 连击重置完成 - isCombing: %@, enableCombo: %@", self.isCombing ? @"YES" : @"NO", self.enableCombo ? @"YES" : @"NO");
}
- (void)registerActions:(void (^)(ComboActionType))action {
_combo = 1;
_errorMessage = @"";
self.actionCallback = action;
}
// 新增:实现其他简化接口方法
- (void)activate {
NSLog(@"[Combo effect] 🔧 激活连击功能");
self.enableCombo = YES;
}
- (void)deactivate {
NSLog(@"[Combo effect] 🔧 停用连击功能");
self.enableCombo = NO;
}
- (BOOL)isActive {
return self.isCombing && self.enableCombo;
}
- (NSInteger)currentCount {
// 确保连击计数最少为 1
if (self.combo < 1) {
NSLog(@"[Combo effect] ⚠️ currentCount: 连击计数异常重置为1 - 当前: %ld", (long)self.combo);
self.combo = 1;
}
return self.combo;
}
- (void)incrementCount {
NSLog(@"[Combo effect] 🔢 增加连击计数 - 当前: %ld -> %ld", (long)self.combo, (long)(self.combo + 1));
self.combo += 1;
}
- (void)clear {
NSLog(@"[Combo effect] 🗑️ 清除连击状态");
[self forceBoomStateReset];
// 🔥 修复确保在主线程执行UI回调并检查回调是否有效
if (self.actionCallback) {
NSLog(@"[Combo effect] 📱 触发连击面板移除回调");
@kWeakify(self);
[self safeExecuteUIBlock:^{
@kStrongify(self);
if (self && self.actionCallback) {
self.actionCallback(ComboAction_RemovePanel);
}
}];
}
}
- (void)send {
NSLog(@"[Combo effect] 📤 发送连击礼物");
[self sendGift];
}
- (NSDictionary *)stateInfo {
return [self getComboStateInfo];
}
- (BOOL)canStartCombo {
return self.enableCombo && self.giftInfo != nil && self.sendGiftToUIDs.count > 0;
}
- (void)validateState {
[self validateAndFixComboCount];
}
- (void)handleError:(NSError *)error {
NSLog(@"[Combo effect] ❌ 处理错误: %@", error.localizedDescription);
self.errorMessage = error.localizedDescription;
}
- (NSString *)lastErrorMessage {
return self.errorMessage ?: @"";
}
- (void)clearError {
self.errorMessage = @"";
}
- (void)forceBoomStateReset {
NSLog(@"[Combo effect] 🚨 执行强制Boom连击状态重置");
// 1. 立即停止所有定时器(无条件停止)
NSLog(@"[Combo effect] ⏰ 停止所有定时器");
[self forceStopAllTimers];
// 2. 清空所有队列
NSLog(@"[Combo effect] 🗑️ 清空所有队列");
[self clearAllQueues];
// 3. 重置所有状态标志
NSLog(@"[Combo effect] 🔄 重置状态标志 - isCombing: %@ -> NO",
self.isCombing ? @"YES" : @"NO");
self.isCombing = NO;
// 4. 重置combo计数为0
_combo = 0;
NSLog(@"[Combo effect] 🔄 combo计数重置为0");
// 注意:不重置 enableCombo保持连击功能可用状态
// self.enableCombo = NO; // 移除这行,保持连击功能可用
// 注意:不清理 actionCallback保持回调可用以便重新进入连击状态
// self.actionCallback = nil; // 移除这行,保持回调可用
// 5. 发送通知(优先级最高,通知所有相关组件)
NSLog(@"[Combo effect] 📢 发送强制重置通知");
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kBoomStateForceResetNotification
object:nil];
});
// 6. 强制恢复UI确保底部栏和侧栏显示
if (self.handleRoomUIChanged) {
NSLog(@"[Combo effect] 🎮 恢复房间UI元素");
dispatch_async(dispatch_get_main_queue(), ^{
self.handleRoomUIChanged(NO);
});
}
NSLog(@"[Combo effect] ✅ 强制重置完成 - enableCombo保持: %@, actionCallback保持: %@",
self.enableCombo ? @"YES" : @"NO",
self.actionCallback ? @"可用" : @"为空");
}
// 无条件停止定时器
- (void)forceStopAllTimers {
NSLog(@"[Combo effect] ⏰ 强制停止所有Timer");
// 停止主定时器
if (self.timer) {
dispatch_source_cancel(self.timer);
self.timer = nil;
}
// 停止UI队列定时器
if (self.comboFlagTimer) {
dispatch_source_cancel(self.comboFlagTimer);
self.comboFlagTimer = nil;
}
}
// 清空所有队列
- (void)clearAllQueues {
@synchronized (self) {
[self.requestQueue removeAllObjects];
[self.networkQueue removeAllObjects];
[self.uiQueue removeAllObjects];
}
}
- (NSInteger)loadTotalGiftNum {
return self.combo * self.countModel.giftNumber.integerValue * self.sendGiftToUIDs.count;
}
// 新增:检查连击状态是否有效
- (BOOL)isComboStateValid {
@synchronized (self) {
return self.isCombing &&
self.enableCombo &&
self.combo > 0 &&
self.giftInfo != nil &&
self.sendGiftToUIDs.count > 0;
}
}
// 新增:获取连击状态信息
- (NSDictionary *)getComboStateInfo {
@synchronized (self) {
return @{
@"isCombing": @(self.isCombing),
@"enableCombo": @(self.enableCombo),
@"combo": @(self.combo),
@"hasGiftInfo": @(self.giftInfo != nil),
@"targetCount": @(self.sendGiftToUIDs.count),
@"errorMessage": self.errorMessage ?: @""
};
}
}
// 新增:打印当前连击状态(用于调试)
- (void)printComboState {
NSDictionary *stateInfo = [self getComboStateInfo];
NSLog(@"[Combo effect] 📊 当前连击状态:");
NSLog(@"[Combo effect] - isCombing: %@", [stateInfo[@"isCombing"] boolValue] ? @"YES" : @"NO");
NSLog(@"[Combo effect] - enableCombo: %@", [stateInfo[@"enableCombo"] boolValue] ? @"YES" : @"NO");
NSLog(@"[Combo effect] - combo: %@", stateInfo[@"combo"]);
NSLog(@"[Combo effect] - hasGiftInfo: %@", [stateInfo[@"hasGiftInfo"] boolValue] ? @"YES" : @"NO");
NSLog(@"[Combo effect] - targetCount: %@", stateInfo[@"targetCount"]);
NSLog(@"[Combo effect] - errorMessage: %@", stateInfo[@"errorMessage"]);
// 检查并修复连击计数异常
[self validateAndFixComboCount];
}
// 新增:验证并修复连击计数
- (void)validateAndFixComboCount {
@synchronized (self) {
if (self.combo < 1) {
NSLog(@"[Combo effect] 🚨 检测到连击计数异常,自动修复 - 当前: %ld -> 1", (long)self.combo);
self.combo = 1;
}
if (self.combo > 1000) {
NSLog(@"[Combo effect] 🚨 检测到连击计数异常,自动修复 - 当前: %ld -> 100", (long)self.combo);
self.combo = 100;
}
}
}
#pragma mark - 处理飘屏逻辑
- (void)receiveGiftInfoForDisplayComboFlags:(GiftReceiveInfoModel *)receiveInfo
container:(UIView *)container {
NSLog(@"[Combo effect] 🎪 收到连击飘屏请求 - combo: %ld, giftId: %ld", (long)receiveInfo.comboCount, (long)receiveInfo.gift.giftId);
self.containerView = container;
[self.uiQueue addObject:receiveInfo];
NSLog(@"[Combo effect] 📊 连击飘屏队列数量: %ld", (long)self.uiQueue.count);
[self startProcessingUIQueue];
}
- (void)removeComboFlag {
self.containerView = nil;
[self stopProcessingQueue];
[self stopProcessingUIQueue];
}
- (void)startProcessingUIQueue {
if (self.comboFlagTimer) {
return;
}
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(timer,
DISPATCH_TIME_NOW,
0.2 * NSEC_PER_SEC,
0.01 * NSEC_PER_SEC);
@kWeakify(self);
dispatch_source_set_event_handler(timer, ^{
@kStrongify(self);
[self processGiftFlagQueue];
});
dispatch_resume(timer);
self.comboFlagTimer = timer;
}
- (void)stopProcessingUIQueue {
if (self.comboFlagTimer) {
dispatch_source_cancel(self.comboFlagTimer);
@kWeakify(self);
dispatch_source_set_cancel_handler(self.comboFlagTimer, ^{
@kStrongify(self);
self.comboFlagTimer = nil;
});
}
}
- (void)processGiftFlagQueue {
@synchronized (self) {
if (self.uiQueue.count == 0) {
return;
}
GiftReceiveInfoModel *receiveInfo = [self.uiQueue firstObject];
NSLog(@"[Combo effect] 🎪 处理连击飘屏 - combo: %ld, giftId: %ld", (long)receiveInfo.comboCount, (long)receiveInfo.gift.giftId);
[self.uiQueue xpSafeRemoveObjectAtIndex:0];
NSLog(@"[Combo effect] <20><> 移除后UI动画队列数量: %ld", (long)self.uiQueue.count);
dispatch_async(dispatch_get_main_queue(), ^{
[self handleGiftInfo:receiveInfo];
});
}
}
- (void)handleGiftInfo:(GiftReceiveInfoModel *)receiveInfo {
if (receiveInfo.comboCount < 1) {
// 不正常的数据,不处理
return;
}
if ([self updateExistingViewWithModel:receiveInfo]) {
// 如果更新了现有视图,就不需要创建新视图
return;
}
if (self.activeViews.count >= 2) {
GiftComboFlagView *oldestView = [self.activeViews firstObject];
[self animateRemoveView:oldestView];
}
CGFloat positionY = kGetScaleWidth(380);
CGFloat positionX = isMSRTL() ? -self.containerView.bounds.size.width : self.containerView.bounds.size.width;
GiftComboFlagView *flagView = [[GiftComboFlagView alloc] initWithFrame:CGRectMake(positionX,
positionY,
kGetScaleWidth(300),
50)];
@kWeakify(self);
@kWeakify(flagView);
[flagView setTimerEnd:^{
@kStrongify(self);
@kStrongify(flagView);
[self animateRemoveView:flagView];
}];
[flagView updateReceiveInfoModel:receiveInfo animationType:0];
[self allCurrentFlagMoveDown];
[self.containerView addSubview:flagView];
[self.activeViews insertObject:flagView atIndex:0];
[self animateView:flagView positionY:positionY];
}
- (void)allCurrentFlagMoveDown {
CGFloat positionY = kGetScaleWidth(380);
[self.activeViews enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(GiftComboFlagView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
dispatch_async(dispatch_get_main_queue(), ^{
CGRect rect = obj.frame;
rect.origin.y = positionY + idx * 50;
obj.frame = rect;
});
}];
}
- (void)animateView:(GiftComboFlagView *)flagView positionY:(CGFloat)positionY {
[UIView animateWithDuration:0.1
animations:^{
if (isMSRTL()) {
// 获取屏幕宽度
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
// 计算新的 x 坐标:屏幕宽度 - 视图宽度 - 右边距
CGFloat newX = screenWidth - 20 - kGetScaleWidth(300);
// 更新 flagView 的 frame使右边距离屏幕右边 20 像素
flagView.frame = CGRectMake(newX, positionY, kGetScaleWidth(300), 50);
} else {
flagView.frame = CGRectMake(20, positionY, kGetScaleWidth(300), 50);
}
} completion:^(BOOL finished) {
[self allCurrentFlagMoveDown];
}];
}
- (void)animateRemoveView:(GiftComboFlagView *)flagView {
[flagView removeFromSuperview];
if ([self.activeViews containsObject:flagView]) {
[self.activeViews removeObject:flagView];
flagView = nil;
}
[self allCurrentFlagMoveDown];
}
// 更新现有 View 的数据,如果存在相同的 ID
- (BOOL)updateExistingViewWithModel:(GiftReceiveInfoModel *)model {
NSInteger index = 0;
for (GiftComboFlagView *existingFlag in self.activeViews) {
if (existingFlag.superview == nil) {
continue;
}
if ([existingFlag.receiveInfo isEqual:model]) {
[self updateComboFlag:existingFlag with:model];
return YES;
}
index++;
}
return NO;
}
- (void)updateComboFlag:(GiftComboFlagView *)flagView with:(GiftReceiveInfoModel *)model {
[flagView updateReceiveInfoModel:model animationType:1];
}
// MARK: Logic is 连击面板出现后,每点击一次,就触发一次面板最后的请求,请求成功后,构造云信消息体,消息体进入队列并按 0.25s 一次的频率发送消息
#pragma mark - 管理队列
// 开始处理队列
- (void)startProcessingQueue {
if (self.timer) {
return; // 如果定时器已经在运行,直接返回
}
// 创建 GCD 定时器 - 使用后台队列避免主线程阻塞
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.backgroundQueue);
//#if DEBUG
// dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC, 0.01 * NSEC_PER_SEC);
//#else
// 优化减少间隔提高响应速度从0.25秒改为0.1秒
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC, 0.01 * NSEC_PER_SEC);
//#endif
// 定时器触发的事件处理 - 在后台队列执行
@kWeakify(self);
dispatch_source_set_event_handler(self.timer, ^{
@kStrongify(self);
if (!self) {
NSLog(@"[Combo effect] ⚠️ self已释放忽略timer回调");
return;
}
[self processRequestQueue];
[self processGiftComboQueue];
});
// **立即执行一次处理方法**
[self processRequestQueue];
[self processGiftComboQueue];
// 启动定时器
dispatch_resume(self.timer);
}
// 停止处理队列
- (void)stopProcessingQueue {
if (self.timer) {
// 🔥 修复无条件停止timer不依赖队列状态
dispatch_source_cancel(self.timer);
// 设置取消回调,在资源完全释放后将 timer 置为 nil
@kWeakify(self);
dispatch_source_set_cancel_handler(self.timer, ^{
@kStrongify(self);
if (self) {
self.timer = nil;
}
});
}
}
// 处理队列中的第一个请求
- (void)processGiftComboQueue {
@synchronized (self) {
if (self.networkQueue.count > 0) {
// 获取并移除队列中的第一个网络数据
id networkData = [self.networkQueue firstObject];
[self.networkQueue xpSafeRemoveObjectAtIndex:0];
// 处理逻辑
if ([networkData isKindOfClass:[AttachmentModel class]]) {
dispatch_async(self.networkProcessingQueue, ^{
[self processGiftComboWith:(AttachmentModel *)networkData];
});
} else if ([networkData isKindOfClass:[NSDictionary class]]) {
// 处理包含info和metadata的字典
NSDictionary *comboData = (NSDictionary *)networkData;
GiftReceiveInfoModel *info = comboData[@"info"];
NSDictionary *metadata = comboData[@"metadata"];
// 这里可以添加相应的处理逻辑
NSLog(@"[Combo effect] <20><> 处理网络数据 - info: %@, metadata: %@", info, metadata);
}
} else {
[self stopProcessingQueue];
}
}
}
// 处理元数据的实际逻辑
- (void)processGiftComboWith:(AttachmentModel *)info {
[self sendCustomMessage:info];
}
- (NSString *)loadErrorMessage {
return self.errorMessage;
}
- (void)processRequestQueue {
@synchronized (self) {
if (self.requestQueue.count > 0) {
// 获取并移除队列中的第一个元数据
NSDictionary *dic = [self.requestQueue xpSafeObjectAtIndex:0];
if (dic) {
// 优化在后台队列处理API请求避免阻塞主线程
dispatch_async(self.networkProcessingQueue, ^{
[self handleSendGift:dic];
});
[self.requestQueue removeObject:dic];
}
} else {
[self stopProcessingQueue];
}
}
}
#pragma mark - Gift meta data
// 统一配置方法替代多个save方法
- (void)configureWithGiftInfo:(GiftInfoModel *)giftInfo
targetUIDs:(NSArray *)UIDs
roomUID:(NSString *)roomUID
sessionID:(NSString *)sessionID
userInfo:(UserInfoModel *)userInfo
countModel:(XPGiftCountModel *)countModel
sourceType:(GiftSourceType)sourceType
sendType:(RoomSendGiftType)sendType
giftNum:(NSString *)giftNum {
NSLog(@"[Combo effect] 🔧 统一配置连击参数");
self.giftInfo = giftInfo;
self.sendGiftToUIDs = UIDs;
self.roomUID = roomUID;
self.sessionID = sessionID;
self.sendGiftUserInfo = userInfo;
self.countModel = countModel;
self.giftSourceType = sourceType;
self.roomSendGiftType = sendType;
self.giftNumPerTimes = giftNum;
NSLog(@"[Combo effect] ✅ 连击参数配置完成 - giftId: %ld, targetCount: %ld",
(long)giftInfo.giftId, (long)UIDs.count);
}
- (BOOL)loadEnable {
return self.enableCombo;
}
#pragma mark - XPGiftPresenter
- (void)sendGift {
NSLog(@"[Combo effect] 🎁 开始发送连击礼物 - combo: %ld, isCombing: %@", (long)self.combo, self.isCombing ? @"YES" : @"NO");
NSString *allUIDs = @"";
for (NSString *item in self.sendGiftToUIDs) {
if (allUIDs.length > 0) {
allUIDs = [allUIDs stringByAppendingString:@","];
}
allUIDs = [allUIDs stringByAppendingString:item];
}
NSDictionary *dic = @{
@"targetUids":allUIDs,
@"giftNum":self.giftNumPerTimes,
@"sendType":[NSString stringWithFormat:@"%ld", GiftSendType_OnMic],
@"giftId":[NSString stringWithFormat:@"%ld", self.giftInfo.giftId],
@"giftSource":[NSString stringWithFormat:@"%ld", self.giftSourceType],
@"giftType":[NSString stringWithFormat:@"%ld", self.roomSendGiftType],
@"roomUid":self.roomUID
};
NSLog(@"[Combo effect] 📦 添加礼物请求到队列 - giftId: %ld, targetUids: %@", (long)self.giftInfo.giftId, allUIDs);
[self.requestQueue addObject:dic];
[self startProcessingQueue];
}
- (void)handleSendGift:(NSDictionary *)dic {
NSString *allUIDs = [dic objectForKey:@"targetUids"];
NSString *giftId = [dic objectForKey:@"giftId"];
NSLog(@"[Combo effect] 🌐 开始调用送礼API - giftId: %@, targetUids: %@", giftId, allUIDs);
@kWeakify(self);
[Api requestSendGift:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
@kStrongify(self);
if (!self) {
NSLog(@"[Combo effect] ⚠️ self已释放忽略API回调");
return;
}
if (code == 200) {
NSLog(@"[Combo effect] ✅ 送礼API成功 - giftId: %@, combo: %ld", giftId, (long)self.combo);
GiftReceiveInfoModel *receive = [GiftReceiveInfoModel modelWithJSON:data.data];
receive.sourceType = [[dic objectForKey:@"giftSource"] integerValue];
receive.roomSendGiftType = [[dic objectForKey:@"giftType"] integerValue];
NSArray *array = [allUIDs componentsSeparatedByString:@","];
receive.receiveGiftNumberUser = array.count;
[self handleSendGiftSuccess:receive sourceData:data];
[[NSNotificationCenter defaultCenter] postNotificationName:@"receiveLuckGiftWinning"
object:@{@"CurrentGold": receive.userPurse.diamonds,
@"Price": @(receive.gift.goldPrice * receive.giftNum * array.count),
@"isFromWinning":@(NO)}];
} else {
NSLog(@"[Combo effect] ❌ 送礼API失败 - code: %ld, msg: %@", (long)code, msg);
// 区分错误类型,优化恢复策略
if (code > 500 && code < 600) {
// 服务器错误,可能是临时问题,保持连击状态
NSLog(@"[Combo effect] 🔄 服务器错误,保持连击状态 - code: %ld", (long)code);
#if DEBUG
self.errorMessage = [NSString stringWithFormat:@"服务器繁忙,请稍后重试 - %@", msg];
#else
self.errorMessage = @"Over Heat & try later";
// 使用 BuglyManager 统一上报礼物发送错误
NSString *uid = [AccountInfoStorage instance].getUid ?: @"未知用户";
NSMutableDictionary *userInfo = [@{@"targetUids": allUIDs,
@"giftNum": self.giftNumPerTimes} mutableCopy];
[userInfo addEntriesFromDictionary:dic];
[userInfo setObject:msg forKey:@"error message"];
[[BuglyManager sharedManager] reportNetworkError:uid
api:@"gift/sendV5"
code:code
userInfo:userInfo];
#endif
// 临时错误,不重置连击状态,允许用户重试
} else if (code == 31005) {
// 余额不足,需要重置连击状态
NSLog(@"[Combo effect] 💰 余额不足,强制移除连击状态");
self.errorMessage = YMLocalizedString(@"XPCandyTreeInsufficientBalanceView1");
[self clear];
} else if (code == 8535) {
// VIP等级不足需要重置连击状态, 但不可能出现
NSLog(@"[Combo effect] 👑 VIP等级不足强制移除连击状态");
self.errorMessage = @"";
[self clear];
} else {
// 其他错误,根据错误类型决定是否重置连击状态
self.errorMessage = msg;
// 对于网络错误等临时问题,保持连击状态
if (code >= 1000 && code < 2000) {
// 网络相关错误,保持连击状态
NSLog(@"[Combo effect] 🌐 网络错误,保持连击状态 - code: %ld", (long)code);
} else {
// 其他错误,重置连击状态
NSLog(@"[Combo effect] 🚨 其他错误,强制移除连击状态 - code: %ld", (long)code);
[self clear];
}
}
// 确保在主线程执行UI回调
if (self.actionCallback) {
@kWeakify(self);
[self safeExecuteUIBlock:^{
@kStrongify(self);
self.actionCallback(ComboAction_Error);
}];
}
}
}
targetUids:allUIDs
giftNum:self.giftNumPerTimes
sendType:[dic objectForKey:@"sendType"]
giftId:[dic objectForKey:@"giftId"]
giftSource:[dic objectForKey:@"giftSource"]
giftType:[dic objectForKey:@"giftType"]
roomUid:[dic objectForKey:@"roomUid"]
msg:@""
uid:[AccountInfoStorage instance].getUid];
}
- (void)handleSendGiftSuccess:(GiftReceiveInfoModel *)receive
sourceData:(BaseModel *)response {
NSLog(@"[Combo effect] 🎉 连击礼物发送成功 - 当前combo: %ld", (long)self.combo);
// 验证连击计数有效性
[self validateAndFixComboCount];
// 在API成功时递增combo计数
if (self.isCombing) {
NSLog(@"[Combo effect] 🔢 API成功递增连击计数 - 当前: %ld -> %ld", (long)self.combo, (long)(self.combo + 1));
self.combo += 1;
// 更新UI显示 - 确保在主线程执行UI回调
if (self.actionCallback) {
@kWeakify(self);
[self safeExecuteUIBlock:^{
@kStrongify(self);
self.actionCallback(ComboAction_Combo_Count_Update);
}];
}
}
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:response.data];
// 确保连击计数最少为 1
NSInteger comboToSet = self.combo;
if (comboToSet < 1) {
NSLog(@"[Combo effect] 🚨 发送云信消息时连击计数异常,修复为 1 - 当前: %ld", (long)comboToSet);
comboToSet = 1;
}
[dic setObject:@(comboToSet) forKey:@"comboCount"];
// 验证连击计数设置
NSNumber *setComboCount = dic[@"comboCount"];
NSLog(@"[Combo effect] 🔍 连击计数设置验证 - 设置值: %@, 当前combo: %ld", setComboCount, (long)self.combo);
self.sendGiftReceiveInfo = receive;
if (self.handleComboSuccess) {
NSLog(@"[Combo effect] 📨 调用连击成功回调,发送云信消息");
self.handleComboSuccess(receive, dic);
}
// 确保在主线程执行UI回调
if (self.actionCallback) {
@kWeakify(self);
[self safeExecuteUIBlock:^{
@kStrongify(self);
self.actionCallback(ComboAction_Update_After_Send_Success);
}];
}
NSLog(@"[Combo effect] ✅ 连击礼物处理完成");
}
- (void)sendCustomMessage:(AttachmentModel *)attachment {
NSLog(@"[Combo effect] 📨 发送云信自定义消息 - combo: %ld", (long)self.combo);
NIMMessage *message = [[NIMMessage alloc]init];
message.setting.quickDeliveryEnabled = YES; // 开启快速投递
NIMCustomObject *object = [[NIMCustomObject alloc] init];
object.attachment = attachment;
message.messageObject = object;
UserInfoModel *userInfo = self.sendGiftUserInfo;
XPMessageRemoteExtModel *extModel = [[XPMessageRemoteExtModel alloc] init];
extModel.androidBubbleUrl = userInfo.androidBubbleUrl;
extModel.iosBubbleUrl = userInfo.iosBubbleUrl;
extModel.fromSayHelloChannel = userInfo.fromSayHelloChannel;
extModel.platformRole = userInfo.platformRole;
NSMutableDictionary *remoteExt = [NSMutableDictionary dictionaryWithObject:extModel.model2dictionary forKey:[NSString stringWithFormat:@"%ld", userInfo.uid]];
message.remoteExt = remoteExt;
//构造会话
NIMSession *session = [NIMSession session:self.sessionID type:NIMSessionTypeChatroom];
// 优化确保在主线程发送云信消息因为云信SDK可能期望在主线程调用
[self safeExecuteUIBlock:^{
NSError *error = nil;
[[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:&error];
if (error) {
NSLog(@"[Combo effect] ❌ 云信消息发送失败 - error: %@", error.localizedDescription);
} else {
NSLog(@"[Combo effect] ✅ 云信消息发送成功");
}
}];
}
// 新增辅助方法统一处理UI回调的线程安全
- (void)safeExecuteUIBlock:(void (^)(void))uiBlock {
if (!uiBlock) return;
if ([NSThread isMainThread]) {
uiBlock();
} else {
dispatch_async(dispatch_get_main_queue(), uiBlock);
}
}
// 新增:性能监控方法
- (void)logPerformanceMetrics {
NSLog(@"[Combo effect] 📊 性能指标 - 网络队列: %lu, 请求队列: %lu, 定时器: %@",
(unsigned long)self.networkQueue.count,
(unsigned long)self.requestQueue.count,
self.timer ? @"运行中" : @"已停止");
}
// 🔧 新增:状态通知方法实现
- (void)setUserComboState:(BOOL)isCombo forUser:(NSString *)uid {
if (!uid || uid.length == 0) {
NSLog(@"[Combo effect] ⚠️ 用户ID为空无法设置combo状态");
return;
}
NSLog(@"[Combo effect] 🔔 通知动画管理器设置用户 %@ 的combo状态为: %@", uid, isCombo ? @"YES" : @"NO");
// 通过通知中心通知动画管理器
[[NSNotificationCenter defaultCenter] postNotificationName:@"GiftComboStateChanged"
object:nil
userInfo:@{
@"uid": uid,
@"isCombo": @(isCombo)
}];
}
- (void)clearUserComboState:(NSString *)uid {
[self setUserComboState:NO forUser:uid];
}
@end