463 lines
18 KiB
Objective-C
463 lines
18 KiB
Objective-C
//
|
|
// TUIChatPopContextController.m
|
|
// TUIChat
|
|
//
|
|
// Created by wyl on 2022/10/24.
|
|
// Copyright © 2023 Tencent. All rights reserved.
|
|
//
|
|
|
|
#import "TUIChatPopContextController.h"
|
|
#import <TIMCommon/TIMDefine.h>
|
|
#import "UIImage+ImageEffects.h"
|
|
#import <TUICore/TUICore.h>
|
|
|
|
@interface TUIChatPopContextController ()<V2TIMAdvancedMsgListener>
|
|
@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
|