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

496 lines
15 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 "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;
// 用来存储 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 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;
}
}
- (void)registerActions:(void (^)(ComboActionType))action {
self.actionCallback = action;
}
- (void)forceRemove {
if (self.actionCallback) {
self.actionCallback(ComboAction_RemovePanel);
}
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 processGiftComboQueue];
});
// 启动定时器
dispatch_resume(self.timer);
}
// 停止处理队列
- (void)stopProcessingQueue {
if (self.timer) {
dispatch_source_cancel(self.timer);
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];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"礼物处理完成");
});
}
}
}
// 处理元数据的实际逻辑
- (void)processGiftComboWith:(AttachmentModel *)info {
[self sendCustomMessage:info];
}
- (NSString *)loadErrorMessage {
return self.errorMessage;
}
#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];
}
@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 = self.giftSourceType;
receive.roomSendGiftType = self.roomSendGiftType;
[self handleSendGiftSuccess:data];
} else {
self.errorMessage = msg;
self.actionCallback(ComboAction_RemovePanel);
self.actionCallback(ComboAction_Error);
}
}
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
msg:@""
uid:[AccountInfoStorage instance].getUid];
}
- (void)handleSendGiftSuccess:(BaseModel *)response {
if (self.actionCallback) {
self.actionCallback(ComboAction_Combo_Count_Update);
}
GiftReceiveInfoModel *receive = [GiftReceiveInfoModel modelWithJSON:response.data];
if (!receive) {
return;
}
NSDictionary *tempDic = response.data;
NSMutableDictionary *data = [NSMutableDictionary dictionary];
[data addEntriesFromDictionary:tempDic];
AttachmentModel *attachment = [[AttachmentModel alloc] init];
switch (self.roomSendGiftType) {
case RoomSendGiftType_AllMic: {
attachment.first = CustomMessageType_AllMicroSend;
attachment.second = Custom_Message_Sub_AllMicroSend;
[data setObject:[tempDic valueForKeyPath:@"targetUsers.uid"] forKey:@"targetUids"];
attachment.data = data;
}
break;
case RoomSendGiftType_MutableOnMic: {
attachment.first = CustomMessageType_AllMicroSend;
attachment.second = Custom_Message_Sub_AllBatchSend;
attachment.data = data;
}
break;
case RoomSendGiftType_ToOne: {
attachment.first = CustomMessageType_Gift;
attachment.second = Custom_Message_Sub_Gift_Send;
NSDictionary *targetUsers = ((NSArray *)[data objectForKey:@"targetUsers"]).firstObject;
NSString *avatar = [targetUsers valueForKeyPath:@"avatar"];
[data setObject:[targetUsers valueForKeyPath:@"uid"] forKey:@"targetUid"];
[data setObject:[targetUsers valueForKeyPath:@"nick"] forKey:@"targetNick"];
if (avatar.length > 0) {
[data setObject:avatar forKey:@"targetAvatar"];
}
attachment.data = data;
}
break;
default:
attachment = nil;
break;
}
if (attachment) {
[attachment.data setObject:@(self.combo) forKey:@"comboCount"];
[self sendCustomMessage:attachment];
}
self.combo += 1;
}
- (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