357 lines
12 KiB
Objective-C
357 lines
12 KiB
Objective-C
//
|
||
// 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;
|
||
|
||
// 🔧 新增:Combo状态管理属性
|
||
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *userComboStates;
|
||
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDate *> *userLastGiftTime;
|
||
@property (nonatomic, assign) NSTimeInterval comboTimeWindow;
|
||
|
||
@end
|
||
|
||
@implementation GiftAnimationManager
|
||
|
||
- (void)dealloc {
|
||
if (_queue) {
|
||
_queue = NULL;
|
||
}
|
||
if (_giftTimer) {
|
||
dispatch_source_cancel(_giftTimer);
|
||
_giftTimer = nil;
|
||
}
|
||
|
||
// 🔧 新增:清理combo状态管理
|
||
[self cleanupExpiredStates];
|
||
[self.userComboStates removeAllObjects];
|
||
[self.userLastGiftTime removeAllObjects];
|
||
}
|
||
|
||
- (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);
|
||
|
||
// 🔧 新增:初始化Combo状态管理属性
|
||
_userComboStates = [NSMutableDictionary dictionary];
|
||
_userLastGiftTime = [NSMutableDictionary dictionary];
|
||
_comboTimeWindow = 2.0; // 2秒combo窗口
|
||
}
|
||
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 {
|
||
if (!uid || uid.length == 0) {
|
||
return NO;
|
||
}
|
||
|
||
// 优先使用精确状态判断
|
||
BOOL isUserInCombo = [self.userComboStates[uid] boolValue];
|
||
if (isUserInCombo) {
|
||
BOOL isCurrentUser = [uid isEqualToString:[AccountInfoStorage instance].getUid];
|
||
NSLog(@"[Combo effect] 🎯 用户 %@ 处于combo状态,是否当前用户: %@", uid, isCurrentUser ? @"YES" : @"NO");
|
||
return isCurrentUser;
|
||
}
|
||
|
||
// 兜底:时间窗口判断
|
||
NSDate *lastGiftTime = self.userLastGiftTime[uid];
|
||
if (lastGiftTime) {
|
||
NSTimeInterval timeSinceLastGift = [[NSDate date] timeIntervalSinceDate:lastGiftTime];
|
||
if (timeSinceLastGift <= self.comboTimeWindow) {
|
||
BOOL isCurrentUser = [uid isEqualToString:[AccountInfoStorage instance].getUid];
|
||
NSLog(@"[Combo effect] 🎯 用户 %@ 在时间窗口内,是否当前用户: %@", uid, isCurrentUser ? @"YES" : @"NO");
|
||
return isCurrentUser;
|
||
}
|
||
}
|
||
|
||
NSLog(@"[Combo effect] 🎯 用户 %@ 不使用combo动画", uid);
|
||
return NO;
|
||
}
|
||
|
||
- (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));
|
||
}
|
||
|
||
// 🔧 新增:Combo状态管理方法实现
|
||
|
||
- (void)setUserComboState:(BOOL)isCombo forUser:(NSString *)uid {
|
||
if (!uid || uid.length == 0) {
|
||
NSLog(@"[Combo effect] ⚠️ 用户ID为空,无法设置combo状态");
|
||
return;
|
||
}
|
||
|
||
if (isCombo) {
|
||
self.userComboStates[uid] = @(YES);
|
||
NSLog(@"[Combo effect] ✅ 设置用户 %@ 为combo状态", uid);
|
||
} else {
|
||
[self.userComboStates removeObjectForKey:uid];
|
||
NSLog(@"[Combo effect] 🔄 清除用户 %@ 的combo状态", uid);
|
||
}
|
||
}
|
||
|
||
- (void)clearUserComboState:(NSString *)uid {
|
||
[self setUserComboState:NO forUser:uid];
|
||
}
|
||
|
||
- (void)updateUserGiftTime:(NSString *)uid {
|
||
if (!uid || uid.length == 0) {
|
||
return;
|
||
}
|
||
|
||
self.userLastGiftTime[uid] = [NSDate date];
|
||
NSLog(@"[Combo effect] ⏰ 更新用户 %@ 的送礼时间", uid);
|
||
}
|
||
|
||
- (void)cleanupExpiredStates {
|
||
NSDate *now = [NSDate date];
|
||
NSMutableArray *expiredUsers = [NSMutableArray array];
|
||
|
||
[self.userLastGiftTime enumerateKeysAndObjectsUsingBlock:^(NSString *uid, NSDate *lastTime, BOOL *stop) {
|
||
if ([now timeIntervalSinceDate:lastTime] > self.comboTimeWindow * 2) {
|
||
[expiredUsers addObject:uid];
|
||
}
|
||
}];
|
||
|
||
for (NSString *uid in expiredUsers) {
|
||
[self.userLastGiftTime removeObjectForKey:uid];
|
||
[self.userComboStates removeObjectForKey:uid];
|
||
NSLog(@"[Combo effect] 🧹 清理过期用户状态: %@", uid);
|
||
}
|
||
}
|
||
|
||
@end
|