2024-12-09 19:27:02 +08:00
|
|
|
|
//
|
|
|
|
|
// 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) NSMutableArray<GiftReceiveInfoModel *> *giftQueue;
|
|
|
|
|
@property (nonatomic, strong) GiftAnimationHelper *animationHelper;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation GiftAnimationManager
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithContainerView:(UIView *)containerView {
|
|
|
|
|
self = [super init];
|
|
|
|
|
if (self) {
|
|
|
|
|
_containerView = containerView;
|
|
|
|
|
_giftQueue = [NSMutableArray array];
|
|
|
|
|
_animationHelper = [[GiftAnimationHelper alloc] init];
|
2024-12-11 10:48:01 +08:00
|
|
|
|
_animationInterval = 0.2;
|
|
|
|
|
_comboAnimationDelay = 0.2;
|
|
|
|
|
_standardAnimationDelay = 0.3;
|
2024-12-09 19:27:02 +08:00
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)startGiftQueue {
|
2024-12-11 10:48:01 +08:00
|
|
|
|
if (self.giftTimer) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-12-09 19:27:02 +08:00
|
|
|
|
|
|
|
|
|
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,
|
2024-12-11 10:48:01 +08:00
|
|
|
|
0.01 * NSEC_PER_SEC);
|
2024-12-09 19:27:02 +08:00
|
|
|
|
|
|
|
|
|
@kWeakify(self);
|
|
|
|
|
dispatch_source_set_event_handler(timer, ^{
|
2024-12-11 10:48:01 +08:00
|
|
|
|
@kStrongify(self);
|
|
|
|
|
[self processNextGift];
|
2024-12-09 19:27:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
2025-01-02 17:31:17 +08:00
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
@kStrongify(self);
|
|
|
|
|
[self processNextGift];
|
|
|
|
|
});
|
|
|
|
|
|
2024-12-11 10:48:01 +08:00
|
|
|
|
|
2024-12-09 19:27:02 +08:00
|
|
|
|
dispatch_resume(timer);
|
|
|
|
|
self.giftTimer = timer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)processNextGift {
|
|
|
|
|
if (self.giftQueue.count == 0) {
|
|
|
|
|
[self stopGiftQueue];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GiftReceiveInfoModel *giftInfo = self.giftQueue.firstObject;
|
2025-01-13 14:10:27 +08:00
|
|
|
|
|
|
|
|
|
@kWeakify(self);
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
@kStrongify(self);
|
|
|
|
|
[self distributeGiftAnimation:giftInfo];
|
|
|
|
|
});
|
|
|
|
|
|
2025-01-02 17:31:17 +08:00
|
|
|
|
if (self.giftQueue.count > 0) {
|
|
|
|
|
[self.giftQueue removeObjectAtIndex:0];
|
|
|
|
|
}
|
2024-12-09 19:27:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)distributeGiftAnimation:(GiftReceiveInfoModel *)giftInfo {
|
|
|
|
|
NSArray<NSString *> *targetUids = [self resolveTargetUids:giftInfo];
|
|
|
|
|
CGPoint startPoint = [self calculateAnimationPoint:giftInfo.uid isEndPoint:NO];
|
|
|
|
|
|
|
|
|
|
BOOL isComboAnimation = [self shouldUseComboAnimationForSender:giftInfo.uid];
|
|
|
|
|
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.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 {
|
|
|
|
|
CGPoint point = [self.delegate animationPointAtStageViewByUid:uid];
|
|
|
|
|
|
|
|
|
|
if (point.x <= 0 || point.y <= 0) {
|
|
|
|
|
point = [self fallbackPointForEndPoint:isEndPoint];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ([self shouldUseComboAnimationForSender:uid] && !isEndPoint) {
|
|
|
|
|
point = [self comboAnimationStartPoint];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return point;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (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);
|
2024-12-11 10:48:01 +08:00
|
|
|
|
// 取消定时器
|
|
|
|
|
dispatch_source_cancel(self.giftTimer);
|
|
|
|
|
|
|
|
|
|
// 设置取消回调,在资源完全释放后将 timer 置为 nil
|
|
|
|
|
dispatch_source_set_cancel_handler(self.giftTimer, ^{
|
|
|
|
|
self.giftTimer = nil;
|
|
|
|
|
});
|
2024-12-09 19:27:02 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)enqueueGift:(GiftReceiveInfoModel *)giftInfo {
|
|
|
|
|
[self.giftQueue addObject:giftInfo];
|
|
|
|
|
[self startGiftQueue];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper methods
|
|
|
|
|
- (BOOL)shouldUseComboAnimationForSender:(NSString *)uid {
|
|
|
|
|
return [[GiftComboManager sharedManager] isGiftCombing] &&
|
|
|
|
|
[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 {
|
2024-12-11 10:48:01 +08:00
|
|
|
|
CGFloat x = 0;
|
2024-12-09 19:27:02 +08:00
|
|
|
|
if (isMSRTL()) {
|
2024-12-11 10:48:01 +08:00
|
|
|
|
x = kGetScaleWidth(90);
|
|
|
|
|
} else {
|
|
|
|
|
x = KScreenWidth - kGetScaleWidth(90);
|
2024-12-09 19:27:02 +08:00
|
|
|
|
}
|
|
|
|
|
return CGPointMake(x,
|
2024-12-11 10:48:01 +08:00
|
|
|
|
KScreenHeight - kSafeAreaBottomHeight - kGetScaleWidth(140));
|
2024-12-09 19:27:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|