Files
peko-ios/YuMi/Modules/YMRoom/View/AnimationView/GiftAnimationManager.m

272 lines
9.1 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.

//
// GiftAnimationManager.m
// YuMi
//
// Created by P on 2024/12/9.
//
#import "GiftAnimationManager.h"
#import "GiftComboManager.h"
#import "GiftAnimationHelper.h"
#import "GiftReceiveInfoModel.h"
@interface GiftAnimationManager ()
@property (nonatomic, strong) dispatch_source_t giftTimer;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, strong) NSMutableArray<GiftReceiveInfoModel *> *giftQueue;
@property (nonatomic, strong) GiftAnimationHelper *animationHelper;
@end
@implementation GiftAnimationManager
- (void)dealloc {
if (_queue) {
_queue = NULL;
}
if (_giftTimer) {
dispatch_source_cancel(_giftTimer);
_giftTimer = nil;
}
}
- (instancetype)initWithContainerView:(UIView *)containerView {
self = [super init];
if (self) {
_containerView = containerView;
_giftQueue = [NSMutableArray array];
_animationHelper = [[GiftAnimationHelper alloc] init];
_animationInterval = 0.2;
_comboAnimationDelay = 0.2;
_standardAnimationDelay = 0.3;
_queue = dispatch_queue_create("com.GiftAnimationManager.queue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)startGiftQueue {
if (self.giftTimer) {
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,
self.animationInterval * NSEC_PER_SEC,
0.01 * NSEC_PER_SEC);
@kWeakify(self);
dispatch_source_set_event_handler(timer, ^{
@kStrongify(self);
[self processNextGift];
});
// dispatch_async(dispatch_get_main_queue(), ^{
// @kStrongify(self);
// [self processNextGift];
// });
dispatch_resume(timer);
self.giftTimer = timer;
}
- (void)processNextGift {
dispatch_async(self.queue, ^{
if (self.giftQueue.count == 0) {
NSLog(@"[Combo effect] 📭 动画队列为空,停止处理");
[self stopGiftQueue];
return;
}
GiftReceiveInfoModel *giftInfo = self.giftQueue.firstObject;
if (!giftInfo) {
NSLog(@"[Combo effect] ⚠️ 队列第一个元素为空,跳过处理");
return;
}
// 检查并修复连击计数
if (giftInfo.comboCount < 1) {
NSLog(@"[Combo effect] 🚨 动画处理中检测到连击计数异常 - comboCount: %ld", (long)giftInfo.comboCount);
giftInfo.comboCount = 1;
NSLog(@"[Combo effect] 🔧 修复动画连击计数为 1");
}
NSLog(@"[Combo effect] 🎬 开始处理动画 - giftId: %ld, combo: %ld", (long)giftInfo.gift.giftId, (long)giftInfo.comboCount);
// 在同一线程中移除元素
[self.giftQueue xpSafeRemoveObjectAtIndex:0];
NSLog(@"[Combo effect] 📊 移除后动画队列数量: %ld", (long)self.giftQueue.count);
@kWeakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
@kStrongify(self);
if (self) {
[self distributeGiftAnimation:giftInfo];
} else {
NSLog(@"[Combo effect] ⚠️ self已释放跳过动画分发");
}
});
});
}
- (void)distributeGiftAnimation:(GiftReceiveInfoModel *)giftInfo {
NSLog(@"[Combo effect] 🎯 开始分发动画 - uid: %@", giftInfo.uid);
NSArray<NSString *> *targetUids = [self resolveTargetUids:giftInfo];
NSLog(@"[Combo effect] 🎯 目标用户数量: %ld", (long)targetUids.count);
CGPoint startPoint = CGPointZero;
BOOL isComboAnimation = [self shouldUseComboAnimationForSender:giftInfo.uid];
NSLog(@"[Combo effect] 🎯 是否使用连击动画: %@", isComboAnimation ? @"YES" : @"NO");
if (isComboAnimation) {
startPoint = [self comboAnimationStartPoint];
NSLog(@"[Combo effect] 🎯 使用连击动画起点: %@", NSStringFromCGPoint(startPoint));
} else {
startPoint = [self calculateAnimationPoint:giftInfo.uid isEndPoint:NO];
NSLog(@"[Combo effect] 🎯 使用普通动画起点: %@", NSStringFromCGPoint(startPoint));
}
NSTimeInterval delay = isComboAnimation ? self.comboAnimationDelay : self.standardAnimationDelay;
NSLog(@"[Combo effect] 🎯 动画延迟时间: %.2f", delay);
for (NSString *targetUid in targetUids) {
CGPoint endPoint = [self calculateAnimationPoint:targetUid isEndPoint:YES];
NSLog(@"[Combo effect] 🎯 为目标用户 %@ 创建动画 - 终点: %@", targetUid, NSStringFromCGPoint(endPoint));
[self scheduleAnimationWithDelay:delay
giftInfo:giftInfo.gift
startPoint:startPoint
endPoint:endPoint
isComboAnimation:isComboAnimation];
}
}
- (NSArray<NSString *> *)resolveTargetUids:(GiftReceiveInfoModel *)giftInfo {
if (!giftInfo) {
return @[];
}
if (giftInfo.isLuckyBagGift) {
return @[giftInfo.targetUid];
}
if (giftInfo.targetUids.count > 0) {
return [giftInfo.targetUids valueForKey:@"stringValue"];
}
if (giftInfo.targetUsers) {
NSArray *uidDatas = [self ensureArrayContainsOnlyStrings:[giftInfo.targetUsers valueForKeyPath:@"uid"]];
return uidDatas;//[giftInfo.targetUsers valueForKeyPath:@"uid"];
}
return [NSString isEmpty:giftInfo.targetUid] ? @[] : @[giftInfo.targetUid];
}
- (NSArray<NSString *> *)ensureArrayContainsOnlyStrings:(NSArray *)inputArray {
// 用于存放最终结果
NSMutableArray<NSString *> *resultArray = [NSMutableArray array];
for (id item in inputArray) {
if ([item isKindOfClass:[NSString class]]) {
// 如果是 NSString直接添加到结果数组
[resultArray addObject:item];
} else if ([item isKindOfClass:[NSNumber class]]) {
// 如果是 NSNumber转换为 NSString 后添加
[resultArray addObject:[item stringValue]];
} else {
// 对于非 NSString 或 NSNumber 的类型,可以选择忽略或者抛出异常
// NSLog(@"Warning: Unsupported item type: %@", [item class]);
}
}
return [resultArray copy]; // 返回不可变数组
}
- (CGPoint)calculateAnimationPoint:(NSString *)uid isEndPoint:(BOOL)isEndPoint {
if (uid.length == 0) {
return [self fallbackPointForEndPoint:isEndPoint];
}
CGPoint point = [self.delegate animationPointAtStageViewByUid:uid];
if (!CGPointEqualToPoint(point, CGPointZero)) {
return point;
}
return [self fallbackPointForEndPoint:isEndPoint];
}
- (void)scheduleAnimationWithDelay:(NSTimeInterval)delay
giftInfo:(GiftInfoModel *)giftInfo
startPoint:(CGPoint)startPoint
endPoint:(CGPoint)endPoint
isComboAnimation:(BOOL)isComboAnimation {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
[self.animationHelper beginGiftAnimation:giftInfo.giftUrl
startPoint:startPoint
endPoint:endPoint
isGiftCombing:isComboAnimation
toTargetView:self.containerView];
});
}
- (void)stopGiftQueue {
if (self.giftTimer) {
// 取消定时器
dispatch_source_cancel(self.giftTimer);
// 设置取消回调,在资源完全释放后将 timer 置为 nil
dispatch_source_set_cancel_handler(self.giftTimer, ^{
self.giftTimer = nil;
});
}
}
- (void)enqueueGift:(GiftReceiveInfoModel *)giftInfo {
if (!giftInfo) {
NSLog(@"[Combo effect] ⚠️ 礼物信息为空,跳过动画队列");
return;
}
NSLog(@"[Combo effect] 🎬 添加礼物到动画队列 - giftId: %ld, combo: %ld", (long)giftInfo.gift.giftId, (long)giftInfo.comboCount);
dispatch_async(self.queue, ^{
[self.giftQueue addObject:giftInfo];
NSLog(@"[Combo effect] 📊 动画队列当前数量: %ld", (long)self.giftQueue.count);
[self startGiftQueue];
});
}
// Helper methods
- (BOOL)shouldUseComboAnimationForSender:(NSString *)uid {
return [[GiftComboManager sharedManager] isActive] &&
[uid isEqualToString:[AccountInfoStorage instance].getUid];
}
- (CGPoint)fallbackPointForEndPoint:(BOOL)isEndPoint {
CGFloat x = [UIScreen mainScreen].bounds.size.width / 2;
if (isEndPoint) {
x += 30;
}
return CGPointMake(x, 44 + kSafeAreaTopHeight);
}
- (CGPoint)comboAnimationStartPoint {
CGFloat x = 0;
if (isMSRTL()) {
x = kGetScaleWidth(90);
} else {
x = KScreenWidth - kGetScaleWidth(90);
}
return CGPointMake(x,
KScreenHeight - kSafeAreaBottomHeight - kGetScaleWidth(140));
}
@end