521 lines
16 KiB
Objective-C
521 lines
16 KiB
Objective-C
//
|
||
// GiftComboManager.m
|
||
// YuMi
|
||
//
|
||
// Created by P on 2024/9/5.
|
||
//
|
||
// 处理连击面板逻辑
|
||
|
||
#import "GiftComboManager.h"
|
||
|
||
#import "Api+Gift.h"
|
||
|
||
#import "UserInfoModel.h"
|
||
#import "AttachmentModel.h"
|
||
#import "XPGiftCountModel.h"
|
||
#import "GiftReceiveInfoModel.h"
|
||
#import "XPMessageRemoteExtModel.h"
|
||
|
||
#import "GiftComboFlagView.h"
|
||
|
||
@interface GiftComboManager ()
|
||
|
||
@property (nonatomic, assign) BOOL enableCombo;
|
||
|
||
@property (nonatomic, strong) NSMutableArray *requestQueue;
|
||
|
||
// 用来存储 GiftReceiveInfoModel 和 NSDictionary 的元数据队列
|
||
@property (nonatomic, strong) NSMutableArray *giftComboQueue;
|
||
@property (nonatomic, strong) NSMutableArray *comboFlagQueue;
|
||
@property (nonatomic, strong) NSTimer *comboFlagTimer;
|
||
@property (nonatomic, strong) UIView *containerView;
|
||
|
||
// 定时器,处理请求的调度器
|
||
@property (nonatomic, strong) dispatch_source_t timer;
|
||
|
||
@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 - 单例方法
|
||
|
||
+ (instancetype)sharedManager {
|
||
static GiftComboManager *sharedInstance = nil;
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
sharedInstance = [[self alloc] init];
|
||
sharedInstance.giftComboQueue = [NSMutableArray array];
|
||
sharedInstance.activeViews = [NSMutableArray array];
|
||
sharedInstance.comboFlagQueue = [NSMutableArray array];
|
||
sharedInstance.requestQueue = [NSMutableArray array];
|
||
[sharedInstance startProcessingGiftComboFlagQueue];
|
||
});
|
||
return sharedInstance;
|
||
}
|
||
|
||
|
||
|
||
//// 添加 GiftReceiveInfoModel 和 metadata 到队列
|
||
- (void)addGiftComboWithInfo:(GiftReceiveInfoModel *)info andMetadata:(NSDictionary *)metadata {
|
||
if (info && metadata) {
|
||
// 将元数据打包成字典并添加到队列
|
||
@synchronized (self) {
|
||
NSDictionary *comboData = @{@"info": info, @"metadata": metadata};
|
||
[self.giftComboQueue addObject:comboData];
|
||
}
|
||
// 启动定时器
|
||
[self startProcessingQueue];
|
||
}
|
||
}
|
||
|
||
- (void)addComboFromNIMAttachment:(AttachmentModel *)attachment {
|
||
if (attachment) {
|
||
// 将元数据打包成字典并添加到队列
|
||
@synchronized (self) {
|
||
[self.giftComboQueue addObject:attachment];
|
||
}
|
||
// 启动定时器
|
||
[self startProcessingQueue];
|
||
}
|
||
}
|
||
|
||
// 开始连击,重置
|
||
- (void)resetCombo {
|
||
_combo = 1;
|
||
_errorMessage = @"";
|
||
if (self.actionCallback && self.enableCombo) {
|
||
self.actionCallback(ComboAction_ShowPanel);
|
||
self.isCombing = YES;
|
||
}
|
||
|
||
if (self.handleRoomUIChanged) {
|
||
self.handleRoomUIChanged(YES);
|
||
}
|
||
}
|
||
|
||
- (void)registerActions:(void (^)(ComboActionType))action {
|
||
_combo = 1;
|
||
_errorMessage = @"";
|
||
self.actionCallback = action;
|
||
}
|
||
|
||
- (void)forceRemove {
|
||
if (self.actionCallback) {
|
||
self.actionCallback(ComboAction_RemovePanel);
|
||
}
|
||
if (self.handleRoomUIChanged) {
|
||
self.handleRoomUIChanged(NO);
|
||
}
|
||
self.isCombing = NO;
|
||
}
|
||
|
||
- (NSInteger)loadComboCountFromSendGiftView {
|
||
|
||
if (!self.enableCombo) {
|
||
return 0;
|
||
}
|
||
|
||
NSInteger temp = self.combo;
|
||
self.combo += 1;
|
||
return temp;
|
||
}
|
||
|
||
- (NSInteger)loadComboCount {
|
||
return self.combo;
|
||
}
|
||
|
||
- (NSInteger)loadTotalGiftNum {
|
||
return self.combo * self.countModel.giftNumber.integerValue * self.sendGiftToUIDs.count;
|
||
}
|
||
|
||
- (BOOL)isGiftCombing {
|
||
return self.isCombing;
|
||
}
|
||
|
||
#pragma mark - 处理飘屏逻辑
|
||
- (void)receiveGiftInfoForDisplayComboFlags:(GiftReceiveInfoModel *)receiveInfo
|
||
container:(UIView *)container {
|
||
self.containerView = container;
|
||
[self.giftComboQueue addObject:receiveInfo];
|
||
[self startProcessingGiftComboFlagQueue];
|
||
}
|
||
|
||
- (void)startProcessingGiftComboFlagQueue {
|
||
self.comboFlagTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(processGiftFlagQueue) userInfo:nil repeats:YES];
|
||
}
|
||
|
||
- (void)stopProcessingGiftComboFlagQueue {
|
||
[self.comboFlagTimer invalidate];
|
||
self.comboFlagTimer = nil;
|
||
}
|
||
|
||
- (void)processGiftFlagQueue {
|
||
if (self.giftComboQueue.count == 0) {
|
||
return;
|
||
}
|
||
|
||
GiftReceiveInfoModel *receiveInfo = [self.giftComboQueue firstObject];
|
||
[self.giftComboQueue removeObjectAtIndex:0];
|
||
[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.2s 一次的频率发送消息
|
||
#pragma mark - 管理队列
|
||
|
||
// 开始处理队列
|
||
- (void)startProcessingQueue {
|
||
if (self.timer) {
|
||
return; // 如果定时器已经在运行,直接返回
|
||
}
|
||
|
||
// 创建 GCD 定时器
|
||
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
|
||
|
||
// 设置定时器时间间隔:每 0.2 秒执行一次
|
||
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC, 0.01 * NSEC_PER_SEC);
|
||
|
||
// 定时器触发的事件处理
|
||
dispatch_source_set_event_handler(self.timer, ^{
|
||
[self processRequestQueue];
|
||
[self processGiftComboQueue];
|
||
});
|
||
|
||
// **立即执行一次处理方法**
|
||
[self processRequestQueue];
|
||
[self processGiftComboQueue];
|
||
|
||
// 启动定时器
|
||
dispatch_resume(self.timer);
|
||
}
|
||
|
||
// 停止处理队列
|
||
- (void)stopProcessingQueue {
|
||
if (self.timer) {
|
||
if (self.requestQueue.count == 0 && self.giftComboQueue.count == 0) {
|
||
// 取消定时器
|
||
dispatch_source_cancel(self.timer);
|
||
|
||
// 设置取消回调,在资源完全释放后将 timer 置为 nil
|
||
@kWeakify(self);
|
||
dispatch_source_set_cancel_handler(self.timer, ^{
|
||
@kStrongify(self);
|
||
self.timer = nil;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理队列中的第一个请求
|
||
- (void)processGiftComboQueue {
|
||
@synchronized (self) {
|
||
if (self.giftComboQueue.count > 0) {
|
||
// 获取并移除队列中的第一个元数据
|
||
AttachmentModel *attachment = [self.giftComboQueue firstObject];
|
||
[self.giftComboQueue removeObjectAtIndex:0];
|
||
|
||
// 处理逻辑
|
||
[self processGiftComboWith:attachment];
|
||
} 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 firstObject];
|
||
[self handleSendGift:dic];
|
||
[self.requestQueue removeObject:dic];
|
||
} else {
|
||
[self stopProcessingQueue];
|
||
}
|
||
}
|
||
}
|
||
|
||
#pragma mark - Gift meta data
|
||
|
||
- (void)enableToCombo:(BOOL)enable {
|
||
self.enableCombo = enable;
|
||
}
|
||
|
||
- (BOOL)loadEnable {
|
||
return self.enableCombo;
|
||
}
|
||
|
||
- (void)saveSendGiftTo:(NSArray *)UIDs
|
||
{
|
||
_sendGiftToUIDs = UIDs;
|
||
}
|
||
|
||
- (void)saveGiftSourceType:(GiftSourceType)type
|
||
{
|
||
_giftSourceType = type;
|
||
}
|
||
|
||
- (void)saveSendGiftInfo:(GiftInfoModel *)model
|
||
{
|
||
_giftInfo = model;
|
||
}
|
||
|
||
- (void)saveSendGiftType:(RoomSendGiftType)type
|
||
{
|
||
_roomSendGiftType = type;
|
||
}
|
||
|
||
- (void)saveRoomUID:(NSString *)roomUID {
|
||
_roomUID = roomUID;
|
||
}
|
||
|
||
- (void)saveSendGiftNum:(NSString *)numString
|
||
{
|
||
_giftNumPerTimes = numString;
|
||
}
|
||
|
||
- (void)saveUserInfo:(UserInfoModel *)userInfo {
|
||
_sendGiftUserInfo = userInfo;
|
||
}
|
||
|
||
- (void)saveSessionID:(NSString *)sessionID {
|
||
_sessionID = sessionID;
|
||
}
|
||
|
||
- (void)saveGiftCountModel:(XPGiftCountModel *)model {
|
||
_countModel = model;
|
||
}
|
||
|
||
#pragma mark - XPGiftPresenter
|
||
|
||
- (void)sendGift {
|
||
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
|
||
};
|
||
|
||
[self.requestQueue addObject:dic];
|
||
[self startProcessingQueue];
|
||
}
|
||
|
||
- (void)handleSendGift:(NSDictionary *)dic {
|
||
NSString *allUIDs = [dic objectForKey:@"targetUids"];
|
||
@kWeakify(self);
|
||
[Api requestSendGift:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||
@kStrongify(self);
|
||
if (!self) {
|
||
return;
|
||
}
|
||
if (code == 200) {
|
||
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 {
|
||
self.errorMessage = msg;
|
||
if (self.actionCallback) {
|
||
self.actionCallback(ComboAction_Error);
|
||
}
|
||
[self forceRemove];
|
||
}
|
||
}
|
||
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 {
|
||
if (self.actionCallback) {
|
||
self.actionCallback(ComboAction_Combo_Count_Update);
|
||
}
|
||
|
||
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:response.data];
|
||
[dic setObject:@(self.combo) forKey:@"comboCount"];
|
||
|
||
self.sendGiftReceiveInfo = receive;
|
||
|
||
if (self.handleComboSuccess) {
|
||
self.handleComboSuccess(receive, dic);
|
||
}
|
||
|
||
if (self.actionCallback) {
|
||
self.actionCallback(ComboAction_Update_After_Send_Success);
|
||
}
|
||
}
|
||
|
||
- (void)sendCustomMessage:(AttachmentModel *)attachment {
|
||
NIMMessage *message = [[NIMMessage alloc]init];
|
||
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;
|
||
NSMutableDictionary *remoteExt = [NSMutableDictionary dictionaryWithObject:extModel.model2dictionary forKey:[NSString stringWithFormat:@"%ld", userInfo.uid]];
|
||
message.remoteExt = remoteExt;
|
||
|
||
//构造会话
|
||
NIMSession *session = [NIMSession session:self.sessionID type:NIMSessionTypeChatroom];
|
||
[[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:nil];
|
||
}
|
||
|
||
|
||
@end
|