// // 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 *giftQueue; @property (nonatomic, strong) GiftAnimationHelper *animationHelper; // 🔧 新增:Combo状态管理属性 @property (nonatomic, strong) NSMutableDictionary *userComboStates; @property (nonatomic, strong) NSMutableDictionary *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) { [self stopGiftQueue]; return; } GiftReceiveInfoModel *giftInfo = self.giftQueue.firstObject; if (!giftInfo) { return; } // 检查并修复连击计数 if (giftInfo.comboCount < 1) { giftInfo.comboCount = 1; } // 在同一线程中移除元素 [self.giftQueue xpSafeRemoveObjectAtIndex:0]; @kWeakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @kStrongify(self); if (self) { [self distributeGiftAnimation:giftInfo]; } else { } }); }); } - (void)distributeGiftAnimation:(GiftReceiveInfoModel *)giftInfo { NSArray *targetUids = [self resolveTargetUids:giftInfo]; CGPoint startPoint = CGPointZero; BOOL isComboAnimation = [self shouldUseComboAnimationForSender:giftInfo.uid]; if (isComboAnimation) { startPoint = [self comboAnimationStartPoint]; } else { startPoint = [self calculateAnimationPoint:giftInfo.uid isEndPoint:NO]; } NSTimeInterval delay = isComboAnimation ? self.comboAnimationDelay : self.standardAnimationDelay; for (NSString *targetUid in targetUids) { CGPoint endPoint = [self calculateAnimationPoint:targetUid isEndPoint:YES]; [self scheduleAnimationWithDelay:delay giftInfo:giftInfo.gift startPoint:startPoint endPoint:endPoint isComboAnimation:isComboAnimation]; } } - (NSArray *)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 *)ensureArrayContainsOnlyStrings:(NSArray *)inputArray { // 用于存放最终结果 NSMutableArray *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) { return; } dispatch_async(self.queue, ^{ [self.giftQueue addObject:giftInfo]; [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]; 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]; return isCurrentUser; } } 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) { return; } if (isCombo) { self.userComboStates[uid] = @(YES); } else { [self.userComboStates removeObjectForKey: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]; } - (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]; } } @end