// // LuckyGiftWinningBannerView.m // YuMi // // Created by P on 2024/9/10. // #import "LuckyGiftWinningBannerView.h" #import #import "AttachmentModel.h" #import "i18nGiftNameMap.h" #import "XPRoomViewController.h" #import "XCCurrentVCStackManager.h" // Constants static const CGFloat kBannerWidth = 346.5f; static const CGFloat kBannerHeight = 55.0; static const CGFloat kBannerTopMargin = 0;// 80.0f; //static const CGFloat kAvatarSize = 43.0f; //static const CGFloat kAnimationDuration = 0.25f; //static const CGFloat kDisplayDuration = 2.0f; //static const CGFloat kRoomTransitionDelay = 2.0f; @interface LuckyGiftWinningBannerViewModel : PIBaseModel @property (nonatomic, copy, readonly) NSString *times; @property (nonatomic, copy, readonly) NSString *avatar; @property (nonatomic, copy, readonly) NSString *coins; @property (nonatomic, copy, readonly) NSDictionary *giftNameMap; @property (nonatomic, copy, readonly) NSString *roomUid; @property (nonatomic, copy, readonly) NSString *giftName; @end @implementation LuckyGiftWinningBannerViewModel + (NSDictionary *)replacedKeyFromPropertyName { return @{@"avatar": @"sender.avatar"}; } - (NSString *)giftName { if (isMSRTL() && [self.giftNameMap[@"ar"] length]) { return self.giftNameMap[@"ar"]; } if (isMSZH() && [self.giftNameMap[@"zh"] length]) { return self.giftNameMap[@"zh"]; } return self.giftNameMap[@"en"] ?: @""; } @end @interface LuckyGiftWinningBannerView () @property (nonatomic, strong) LuckyGiftWinningBannerViewModel *model; @property (nonatomic, strong) UIImageView *backgroundImageView; @property (nonatomic, strong) UIImageView *ballImageView; @property (nonatomic, strong) UIImageView *bigBallImageView; @property (nonatomic, strong) NetImageView *avatarImageView; @property (nonatomic, strong) UILabel *giftNameLabel; @property (nonatomic, strong) UILabel *timesLabel; @property (nonatomic, strong) UILabel *coinsLabel; @property (nonatomic, assign) NSInteger currentRoomUid; @property (nonatomic, copy) void(^completeDisplay)(void); @property (nonatomic, copy) void(^exitCurrentRoom)(void); @property (nonatomic, assign) BOOL isAnimationCancelled; @end @implementation LuckyGiftWinningBannerView - (void)dealloc { } + (void)display:(UIView *)superView inRoomUid:(NSInteger)roomUid with:(AttachmentModel *)attachment complete:(void(^)(void))complete exitCurrentRoom:(void(^)(void))exit { #if DEBUG NSParameterAssert(superView); NSParameterAssert(attachment); #else if (!superView || !attachment) { if (complete) { complete(); } return; } #endif LuckyGiftWinningBannerViewModel *model = [LuckyGiftWinningBannerViewModel modelWithDictionary:attachment.data]; CGFloat width = KScreenWidth;//kGetScaleWidth(kBannerWidth); CGFloat height = kBannerHeight;// kGetScaleWidth(kBannerHeight); CGRect frame = CGRectMake(KScreenWidth, kBannerTopMargin, width, height); LuckyGiftWinningBannerView *bannerView = [[LuckyGiftWinningBannerView alloc] initWithFrame:frame]; bannerView.model = model; bannerView.completeDisplay = complete; bannerView.exitCurrentRoom = exit; bannerView.currentRoomUid = roomUid; [superView addSubview:bannerView]; NSInteger time = 3; @kWeakify(bannerView); [bannerView popEnterAnimation:^(BOOL finished) { @kStrongify(bannerView); [bannerView addNotification]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [bannerView popLeaveAnimation:^(bool finished) { if (bannerView.completeDisplay) { bannerView.completeDisplay(); } [bannerView removeNotification]; [bannerView removeFromSuperview]; }]; }); }]; } - (void)handleSwipeNotification { [self dismissBanner]; } - (void)handleTapNotification:(NSNotification *)note { NSValue *value = note.userInfo[@"point"]; CGPoint point = [value CGPointValue]; // 计算中央 2/3 区域 CGFloat totalW = KScreenWidth; CGFloat regionW = totalW * 2.0 / 3.0; CGFloat originX = (totalW - regionW) / 2.0; CGRect centerRegion = CGRectMake(originX, 0, // 高度不做限制就填 0 regionW, self.bounds.size.height); if (CGRectContainsPoint(centerRegion, point)) { NSLog(@" Banner tap 点落在中央 2/3 区域"); [self handelTap]; } else { NSLog(@" Banner tap 点不落在中央 2/3 区域"); // 将 banner 中的点转换为屏幕坐标系 CGPoint screenPoint = [self convertPoint:point toView:nil]; // 发送通知给 FunctionContainer 处理,传递屏幕坐标 [[NSNotificationCenter defaultCenter] postNotificationName:@"BannerTapToFunctionContainer" object:nil userInfo:@{@"point": [NSValue valueWithCGPoint:screenPoint]}]; } } - (void)addNotification { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSwipeNotification) name:@"SwipeOutBanner" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTapNotification:) name:@"TapBanner" object:nil]; } - (void)removeNotification { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)dismissBanner { NSLog(@"🚨 LuckyGiftWinningBannerView dismissBanner 被调用"); [self pop_removeAllAnimations]; // 停止所有动画 [self popLeaveAnimation:^(bool finished) { NSLog(@"🚨 LuckyGiftWinningBannerView 动画完成,调用回调"); if (self.completeDisplay) { self.completeDisplay(); } else { NSLog(@"🚨 警告: completeDisplay 回调为空"); } [self removeFromSuperview]; }]; } - (void)popEnterAnimation:(void(^)(BOOL finished))finish { POPSpringAnimation *enterAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame]; enterAnimation.toValue = [NSValue valueWithCGRect:CGRectMake((KScreenWidth - kGetScaleWidth(kBannerWidth))/2, 0, kGetScaleWidth(kBannerWidth), kGetScaleWidth(kBannerHeight))]; enterAnimation.springBounciness = 10; // 弹性系数 enterAnimation.springSpeed = 12; // 动画速度 enterAnimation.completionBlock = ^(POPAnimation *anim, BOOL finished) { if (finish) { finish(finished); } }; [self pop_addAnimation:enterAnimation forKey:@"enterAnimation"]; } - (void)popLeaveAnimation:(void(^)(bool finished))finish { POPBasicAnimation *exitAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewFrame]; exitAnimation.toValue = [NSValue valueWithCGRect:CGRectMake(-KScreenWidth, 0, kGetScaleWidth(kBannerWidth), kGetScaleWidth(kBannerHeight))]; exitAnimation.duration = 0.25; // 动画持续时间 exitAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; exitAnimation.completionBlock = ^(POPAnimation *anim, BOOL finished) { if (finish) { finish(finished); } }; [self pop_addAnimation:exitAnimation forKey:@"exitAnimation"]; } - (void)setModel:(LuckyGiftWinningBannerViewModel *)model { _model = model; self.avatarImageView.imageUrl = model.avatar; self.giftNameLabel.text = [model giftName]; self.timesLabel.text = model.times; self.coinsLabel.text = model.coins; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setupUI]; UIButton *b = [UIButton buttonWithType:UIButtonTypeCustom]; [self addSubview:b]; [b mas_remakeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(self); }]; // [b addTarget:self action:@selector(handelTap) forControlEvents:UIControlEventTouchUpInside]; } return self; } - (void)handelTap { if (self.model.roomUid.integerValue == self.currentRoomUid) { return; } // 在弹框显示前保存必要数据,避免依赖banner对象 NSString *targetRoomUid = self.model.roomUid; void(^exitCurrentRoomBlock)(void) = self.exitCurrentRoom; @kWeakify(self); [TTPopup alertWithMessage:YMLocalizedString(@"Combo_10") confirmHandler:^{ @kStrongify(self); // 检查banner是否还存在 if (!self) { NSLog(@"⚠️ LuckyGiftWinningBannerView: banner已被移除,但弹框回调仍在执行"); // 即使banner被移除,仍然执行跳转逻辑 if (exitCurrentRoomBlock) { exitCurrentRoomBlock(); } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [XPRoomViewController openRoom:targetRoomUid viewController:[XCCurrentVCStackManager shareManager].getCurrentVC]; }); return; } // banner还存在,正常执行 if (self.exitCurrentRoom) { self.exitCurrentRoom(); } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [XPRoomViewController openRoom:targetRoomUid viewController:[XCCurrentVCStackManager shareManager].getCurrentVC]; }); } cancelHandler:^{}]; } - (void)setupUI { [self addSubview:self.backgroundImageView]; [self.backgroundImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(self); }]; [self addSubview:self.ballImageView]; [self.ballImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(self); make.trailing.mas_equalTo(self).offset(-30); make.width.height.mas_equalTo(59); }]; [self addSubview:self.avatarImageView]; [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(self); make.leading.mas_equalTo(kGetScaleWidth(36)); if (iPhoneXSeries) { make.width.height.mas_equalTo(34); } else { make.width.height.mas_equalTo(29); } }]; UILabel *titleLabel_1 = [UILabel labelInitWithText:YMLocalizedString(@"Combo_0") font:kFontSemibold(14) textColor:[UIColor whiteColor]]; [self addSubview:titleLabel_1]; [titleLabel_1 mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(self.avatarImageView).offset(-3); make.leading.mas_equalTo(self.avatarImageView.mas_trailing).offset(5); }]; [self addSubview:self.giftNameLabel]; [self.giftNameLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(titleLabel_1); make.leading.mas_equalTo(titleLabel_1.mas_trailing).offset(3); }]; UILabel *titleLabel_2 = [UILabel labelInitWithText:YMLocalizedString(@"Combo_4") font:kFontSemibold(14) textColor:[UIColor whiteColor]]; [self addSubview:titleLabel_2]; [titleLabel_2 mas_makeConstraints:^(MASConstraintMaker *make) { make.bottom.mas_equalTo(self.avatarImageView.mas_bottom); make.leading.mas_equalTo(self.avatarImageView.mas_trailing).offset(5); }]; [self addSubview:self.timesLabel]; [self.timesLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(titleLabel_2); make.leading.mas_equalTo(titleLabel_2.mas_trailing).offset(3); }]; UILabel *titleLabel_3 = [UILabel labelInitWithText:YMLocalizedString(@"Combo_9") font:kFontSemibold(14) textColor:[UIColor whiteColor]]; [self addSubview:titleLabel_3]; [titleLabel_3 mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(self.timesLabel); make.leading.mas_equalTo(self.timesLabel.mas_trailing).offset(3); }]; [self addSubview:self.coinsLabel]; [self.coinsLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self.ballImageView); make.top.mas_equalTo(self).offset(14); make.height.mas_equalTo(22); make.width.mas_lessThanOrEqualTo(self.ballImageView).multipliedBy(0.8); }]; UILabel *titleLabel_4 = [UILabel labelInitWithText:YMLocalizedString(@"Combo_5") font:kFontMedium(12) textColor:[UIColor whiteColor]]; [self addSubview:titleLabel_4]; [titleLabel_4 mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self.coinsLabel); make.top.mas_equalTo(self.coinsLabel.mas_bottom).offset(-2); }]; } #pragma mark - - (UIImageView *)backgroundImageView { if (!_backgroundImageView) { _backgroundImageView = [[UIImageView alloc] initWithImage:[kImage(@"luck_gift_flag") ms_SetImageForRTL]]; _backgroundImageView.contentMode = UIViewContentModeScaleAspectFit; _backgroundImageView.alpha = 0.9; } return _backgroundImageView; } - (UIImageView *)ballImageView { if (!_ballImageView) { _ballImageView = [[UIImageView alloc] initWithImage:kImage(@"luck_gift_flag_ball")]; _ballImageView.contentMode = UIViewContentModeScaleAspectFill; // _ballImageView.hidden = YES; } return _ballImageView; } - (UIImageView *)bigBallImageView { if (!_bigBallImageView) { _bigBallImageView = [[UIImageView alloc] initWithImage:kImage(@"luck_gift_flag_ball_BIG")]; _bigBallImageView.contentMode = UIViewContentModeScaleAspectFill; _bigBallImageView.hidden = YES; } return _bigBallImageView; } - (NetImageView *)avatarImageView { if (!_avatarImageView) { NetImageConfig * config = [[NetImageConfig alloc]init]; config.placeHolder = [UIImageConstant defaultAvatarPlaceholder]; config.imageType = ImageTypeCornerAvatar; _avatarImageView = [[NetImageView alloc] initWithConfig:config]; _avatarImageView.backgroundColor = [UIColor clearColor]; _avatarImageView.contentMode = UIViewContentModeScaleAspectFill; [_avatarImageView setCornerRadius: iPhoneXSeries ? 34/2 : 29/2 corners:kCALayerMaxXMaxYCorner | kCALayerMaxXMinYCorner | kCALayerMinXMaxYCorner | kCALayerMinXMinYCorner borderWidth:1 borderColor:[UIColor whiteColor]]; } return _avatarImageView; } - (UILabel *)giftNameLabel { if (!_giftNameLabel) { _giftNameLabel = [UILabel labelInitWithText:@"" font:kFontSemibold(14) textColor:UIColorFromRGB(0xffe375)]; } return _giftNameLabel; } - (UILabel *)coinsLabel { if (!_coinsLabel) { _coinsLabel = [UILabel labelInitWithText:@"" font:kFontSemibold(15) textColor:UIColorFromRGB(0xffe375)]; _coinsLabel.adjustsFontSizeToFitWidth = YES; _coinsLabel.minimumScaleFactor = 0.5; _coinsLabel.textAlignment = NSTextAlignmentCenter; } return _coinsLabel; } - (UILabel *)timesLabel { if (!_timesLabel) { _timesLabel = [UILabel labelInitWithText:@"" font:kFontSemibold(14) textColor:UIColorFromRGB(0xffe375)]; } return _timesLabel; } @end