// // TUIChatPopContextController.m // TUIChat // // Created by wyl on 2022/10/24. // Copyright © 2023 Tencent. All rights reserved. // #import "TUIChatPopContextController.h" #import #import "UIImage+ImageEffects.h" #import @interface TUIChatPopContextController () @property(nonatomic, strong) UIView *recentView; @property(nonatomic, strong) UIView *alertContainerView; @property(nonatomic, strong) TUIMessageCell *alertView; @property(nonatomic, strong) TUIChatPopContextExtionView *extionView; @property(nonatomic, strong) UIColor *backgroundColor; // set backgroundColor @property(nonatomic, strong) UIView *backgroundView; // you set coustom view to it @property(nonatomic, strong) UITapGestureRecognizer *singleTap; @property(nonatomic, assign) BOOL backgoundTapDismissEnable; // default NO @end @implementation TUIChatPopContextController - (instancetype)init { if (self = [super init]) { [self configureController]; } return self; } - (void)configureController { self.providesPresentationContextTransitionStyle = YES; self.definesPresentationContext = YES; self.modalPresentationStyle = UIModalPresentationCustom; _backgroundColor = [UIColor clearColor]; _backgoundTapDismissEnable = YES; [[V2TIMManager sharedInstance] addAdvancedMsgListener:self]; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor clearColor]; [self addBackgroundView]; [self addSingleTapGesture]; [self configureAlertView]; [self configRecentView]; [self configExtionView]; [self.view layoutIfNeeded]; [self showHapticFeedback]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (_viewWillShowHandler) { _viewWillShowHandler(_alertView); } } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; if (_viewDidShowHandler) { _viewDidShowHandler(_alertView); } // // Too far to the top CGFloat moveY = 0; if (self.recentView.frame.origin.y < NavBar_Height) { CGFloat deal = NavBar_Height - self.recentView.frame.origin.y; moveY = deal + NavBar_Height + 50; } // // Too far to the right CGFloat moveX = 0; if (self.recentView.frame.origin.x + self.recentView.frame.size.width > self.view.frame.size.width) { CGFloat deal = self.recentView.frame.origin.x + self.recentView.frame.size.width - self.view.frame.size.width; moveX = deal + 5; } // // too far down if (self.extionView.frame.origin.y + self.extionView.frame.size.height > self.view.frame.size.height) { CGFloat deal = self.extionView.frame.origin.y + self.extionView.frame.size.height - self.view.frame.size.height; moveY = -deal - 50; } BOOL oneScreenCanFillCheck = NO; // Can only one screen fit if (self.recentView.frame.size.height + self.originFrame.size.height + self.extionView.frame.size.height + kScale390(100) > self.view.bounds.size.height) { oneScreenCanFillCheck = YES; } if (oneScreenCanFillCheck) { // recentView CGFloat recentViewMoveY = NavBar_Height + 50; self.recentView.frame = CGRectMake(self.recentView.frame.origin.x - moveX, recentViewMoveY, self.recentView.frame.size.width, self.recentView.frame.size.height); // alertView [UIView animateWithDuration:0.3 animations:^{ self.alertContainerView.frame = CGRectMake(0, self.recentView.frame.origin.y + kScale390(8) + self.recentView.frame.size.height, self.view.frame.size.width, self.originFrame.size.height); } completion:^(BOOL finished){ }]; // extionView CGFloat deal = self.extionView.frame.origin.y + self.extionView.frame.size.height - self.view.frame.size.height; CGFloat extionViewMoveY = -deal - 50; self.extionView.frame = CGRectMake(self.extionView.frame.origin.x - moveX, self.extionView.frame.origin.y + extionViewMoveY, self.extionView.frame.size.width, self.extionView.frame.size.height); self.extionView.transform = CGAffineTransformMakeScale(0.1, 0.1); [UIView animateWithDuration:0.5 animations:^{ // Bounces self.extionView.transform = CGAffineTransformMakeScale(1, 1); } completion:^(BOOL finished){ }]; return; } else { // When the container need a displacement change // Or do nothing if (moveY != 0) { [UIView animateWithDuration:0.3 animations:^{ self.alertContainerView.frame = CGRectMake(0, self.originFrame.origin.y + moveY, self.view.frame.size.width, self.originFrame.size.height); } completion:^(BOOL finished){ }]; } self.recentView.frame = CGRectMake(self.recentView.frame.origin.x - moveX, self.recentView.frame.origin.y, self.recentView.frame.size.width, self.recentView.frame.size.height); [UIView animateWithDuration:0.2 animations:^{ // When recentView needs to have displacement animation self.recentView.frame = CGRectMake(self.recentView.frame.origin.x, self.recentView.frame.origin.y + moveY, self.recentView.frame.size.width, self.recentView.frame.size.height); } completion:^(BOOL finished){ }]; self.extionView.frame = CGRectMake(self.extionView.frame.origin.x - moveX, self.extionView.frame.origin.y + moveY, self.extionView.frame.size.width, self.extionView.frame.size.height); self.extionView.transform = CGAffineTransformMakeScale(0.1, 0.1); [UIView animateWithDuration:0.5 animations:^{ // Bounces self.extionView.transform = CGAffineTransformMakeScale(1, 1); } completion:^(BOOL finished){ }]; } } - (void)addBackgroundView { if (_backgroundView == nil) { UIView *backgroundView = [[UIView alloc] init]; backgroundView.backgroundColor = _backgroundColor; _backgroundView = backgroundView; } _backgroundView.translatesAutoresizingMaskIntoConstraints = NO; [self.view insertSubview:_backgroundView atIndex:0]; [self addConstraintToView:_backgroundView edgeInset:UIEdgeInsetsZero]; } - (void)setBackgroundView:(UIView *)backgroundView { if (_backgroundView == nil) { _backgroundView = backgroundView; } else if (_backgroundView != backgroundView) { backgroundView.translatesAutoresizingMaskIntoConstraints = NO; [self.view insertSubview:backgroundView aboveSubview:_backgroundView]; [self addConstraintToView:backgroundView edgeInset:UIEdgeInsetsZero]; backgroundView.alpha = 0; [UIView animateWithDuration:0.3 animations:^{ backgroundView.alpha = 1; } completion:^(BOOL finished) { [_backgroundView removeFromSuperview]; _backgroundView = backgroundView; [self addSingleTapGesture]; }]; } } - (void)addSingleTapGesture { self.view.userInteractionEnabled = YES; _backgroundView.userInteractionEnabled = YES; UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)]; singleTap.enabled = _backgoundTapDismissEnable; [_backgroundView addGestureRecognizer:singleTap]; _singleTap = singleTap; } - (void)configureAlertView { self.alertContainerView = [[UIView alloc] init]; [self.view addSubview:self.alertContainerView]; _alertView = [[self.alertCellClass alloc] init]; [self.alertContainerView addSubview:_alertView]; _alertView.userInteractionEnabled = YES; if ([self.alertView isKindOfClass:NSClassFromString(@"TUIMergeMessageCell_Minimalist")]) { _alertView.userInteractionEnabled = NO; } [_alertView fillWithData:self.alertViewCellData]; [_alertView layoutIfNeeded]; self.alertContainerView.frame = CGRectMake(0, _originFrame.origin.y, self.view.frame.size.width, _originFrame.size.height); _alertView.frame = CGRectMake(0, 0, self.alertContainerView.frame.size.width, self.alertContainerView.frame.size.height); for (UIView *view in _alertView.contentView.subviews) { if(view != _alertView.container) { view.hidden = YES; } } [_alertView.container mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(_originFrame.origin.x); make.top.mas_equalTo(0); make.size.mas_equalTo(_originFrame.size); }]; } - (void)configRecentView { _recentView = [[UIView alloc] init]; _recentView.backgroundColor = [UIColor clearColor]; [self.view addSubview:_recentView]; _recentView.frame = CGRectMake(_originFrame.origin.x, _originFrame.origin.y - kScale390(8 + 40), MAX(kTIMDefaultEmojiSize.width *8,kScale390(208)), kScale390(40)); NSDictionary *param = @{TUICore_TUIChatExtension_ChatPopMenuReactRecentView_Delegate : self}; [TUICore raiseExtension:TUICore_TUIChatExtension_ChatPopMenuReactRecentView_MinimalistExtensionID parentView:self.recentView param:param]; } - (void)configExtionView { _extionView = [[TUIChatPopContextExtionView alloc] init]; _extionView.backgroundColor = [UIColor tui_colorWithHex:@"f9f9f9"]; _extionView.layer.cornerRadius = kScale390(16); [self.view addSubview:_extionView]; CGFloat height = [self configAndCaculateExtionHeight]; _extionView.frame = CGRectMake(_originFrame.origin.x, _originFrame.origin.y + _originFrame.size.height + kScale390(8), kScale390(180), height); } - (CGFloat)configAndCaculateExtionHeight { NSMutableArray *items = self.items; CGFloat height = 0; for (int i = 0; i < items.count; i++) { height += kScale390(40); } CGFloat topMargin = kScale390(6); CGFloat bottomMargin = kScale390(6); height += topMargin; height += bottomMargin; [_extionView configUIWithItems:items topBottomMargin:topMargin]; return height; } - (void)updateExtionView { CGFloat height = [self configAndCaculateExtionHeight]; _extionView.frame = CGRectMake(_extionView.frame.origin.x, _extionView.frame.origin.y, _extionView.frame.size.width, height); } - (void)setBackgoundTapDismissEnable:(BOOL)backgoundTapDismissEnable { _backgoundTapDismissEnable = backgoundTapDismissEnable; _singleTap.enabled = backgoundTapDismissEnable; } - (void)addConstraintToView:(UIView *)view edgeInset:(UIEdgeInsets)edgeInset { [self addConstraintWithView:view topView:self.view leftView:self.view bottomView:self.view rightView:self.view edgeInset:edgeInset]; } - (void)addConstraintWithView:(UIView *)view topView:(UIView *)topView leftView:(UIView *)leftView bottomView:(UIView *)bottomView rightView:(UIView *)rightView edgeInset:(UIEdgeInsets)edgeInset { if (topView) { [self.view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:topView attribute:NSLayoutAttributeTop multiplier:1 constant:edgeInset.top]]; } if (leftView) { [self.view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:leftView attribute:NSLayoutAttributeLeft multiplier:1 constant:edgeInset.left]]; } if (rightView) { [self.view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:rightView attribute:NSLayoutAttributeRight multiplier:1 constant:edgeInset.right]]; } if (bottomView) { [self.view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bottomView attribute:NSLayoutAttributeBottom multiplier:1 constant:edgeInset.bottom]]; } } #pragma mark - public - (void)setBlurEffectWithView:(UIView *)view { [self setBlurEffectWithView:view style:BlurEffectStyleDarkEffect]; } - (void)setBlurEffectWithView:(UIView *)view style:(BlurEffectStyle)blurStyle { UIImage *snapshotImage = [UIImage snapshotImageWithView:view]; UIImage *blurImage = [self blurImageWithSnapshotImage:snapshotImage style:blurStyle]; dispatch_async(dispatch_get_main_queue(), ^{ UIImageView *blurImageView = [[UIImageView alloc] initWithImage:blurImage]; self.backgroundView = blurImageView; }); } - (void)setBlurEffectWithView:(UIView *)view effectTintColor:(UIColor *)effectTintColor { UIImage *snapshotImage = [UIImage snapshotImageWithView:view]; UIImage *blurImage = [snapshotImage applyTintEffectWithColor:effectTintColor]; UIImageView *blurImageView = [[UIImageView alloc] initWithImage:blurImage]; self.backgroundView = blurImageView; } #pragma mark - private - (void)showHapticFeedback { if (@available(iOS 10.0, *)) { dispatch_async(dispatch_get_main_queue(), ^{ UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; [generator prepare]; [generator impactOccurred]; }); } else { // Fallback on earlier versions } } - (UIImage *)blurImageWithSnapshotImage:(UIImage *)snapshotImage style:(BlurEffectStyle)blurStyle { switch (blurStyle) { case BlurEffectStyleLight: return [snapshotImage applyLightEffect]; case BlurEffectStyleDarkEffect: return [snapshotImage applyDarkEffect]; case BlurEffectStyleExtraLight: return [snapshotImage applyExtraLightEffect]; default: return nil; } } - (void)blurDismissViewControllerAnimated:(BOOL)animated completion:(void (^__nullable)(BOOL finished))completion { [self dismissViewControllerAnimated:animated]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (completion) { completion(YES); } }); } - (void)dismissViewControllerAnimated:(BOOL)animated { [UIView animateWithDuration:0.3 animations:^{ self.recentView.frame = CGRectMake(self.recentView.frame.origin.x, self.originFrame.origin.y - self.recentView.frame.size.height, self.recentView.frame.size.width, self.recentView.frame.size.height); } completion:^(BOOL finished){ }]; [UIView animateWithDuration:0.3 animations:^{ self.alertContainerView.frame = CGRectMake(0, self.originFrame.origin.y , self.view.frame.size.width, self.originFrame.size.height); } completion:^(BOOL finished) { if (finished) { [self dismissViewControllerAnimated:animated completion:self.dismissComplete]; } }]; self.extionView.transform = CGAffineTransformMakeScale(1, 1); [UIView animateWithDuration:0.2 animations:^{ self.extionView.transform = CGAffineTransformMakeScale(0.1, 0.1); } completion:^(BOOL finished) { if (finished) { self.extionView.transform = CGAffineTransformMakeScale(0, 0); } }]; } #pragma mark - action - (void)singleTap:(UITapGestureRecognizer *)sender { [self dismissViewControllerAnimated:NO]; } // MARK: V2TIMAdvancedMsgListener - (void)onRecvMessageRevoked:(NSString *)msgID operateUser:(V2TIMUserFullInfo *)operateUser reason:(NSString *)reason { if ([msgID isEqualToString:self.alertViewCellData.msgID]) { UIViewController *controller = self; while(controller.presentingViewController != nil){ controller = controller.presentingViewController; } [controller dismissViewControllerAnimated:YES completion:^{ [self blurDismissViewControllerAnimated:NO completion:nil]; }]; } } @end