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

332 lines
9.8 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;
// 🔧 新增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) {
[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<NSString *> *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<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) {
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