/* Copyright 2024 New Vector Ltd. Copyright 2017 Aram Sargsyan SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ #import "MXKAttachmentInteractionController.h" #import "MXLog.h" @interface MXKAttachmentInteractionController () @property (nonatomic, weak) UIViewController *destinationViewController; @property (nonatomic, weak) UIViewController *sourceViewController; @property (nonatomic) UIImageView *transitioningImageView; @property (nonatomic, weak) id transitionContext; @property (nonatomic) CGPoint translation; @property (nonatomic) CGPoint delta; @end @implementation MXKAttachmentInteractionController #pragma mark - Lifecycle - (instancetype)initWithDestinationViewController:(UIViewController *)viewController sourceViewController:(UIViewController *)sourceViewController { self = [super init]; if (self) { self.destinationViewController = viewController; self.sourceViewController = sourceViewController; self.interactionInProgress = NO; [self preparePanGestureRecognizerInView:viewController.view]; } return self; } #pragma mark - Gesture recognizer - (void)preparePanGestureRecognizerInView:(UIView *)view { UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)]; recognizer.minimumNumberOfTouches = 1; recognizer.maximumNumberOfTouches = 3; [view addGestureRecognizer:recognizer]; } - (void)handleGesture:(UIPanGestureRecognizer *)recognizer { CGPoint translation = [recognizer translationInView:self.destinationViewController.view]; self.delta = CGPointMake(translation.x - self.translation.x, translation.y - self.translation.y); self.translation = translation; switch (recognizer.state) { case UIGestureRecognizerStateBegan: self.interactionInProgress = YES; if (self.destinationViewController.navigationController) { [self.destinationViewController.navigationController popViewControllerAnimated:YES]; } else { [self.destinationViewController dismissViewControllerAnimated:YES completion:nil]; } break; case UIGestureRecognizerStateChanged: [self updateInteractiveTransition:(ABS(translation.y) / (CGRectGetHeight(self.destinationViewController.view.frame) / 2))]; break; case UIGestureRecognizerStateCancelled: self.interactionInProgress = NO; [self cancelInteractiveTransition]; break; case UIGestureRecognizerStateEnded: self.interactionInProgress = NO; if (ABS(self.translation.y) < CGRectGetHeight(self.destinationViewController.view.frame)/6) { [self cancelInteractiveTransition]; } else { [self finishInteractiveTransition]; } break; default: MXLogDebug(@"UIGestureRecognizerState not handled"); break; } } #pragma mark - UIPercentDrivenInteractiveTransition - (void)startInteractiveTransition:(id )transitionContext { self.transitionContext = transitionContext; [self.destinationViewController prepareSubviewsForTransition:YES]; UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIImageView *destinationImageView = [self.destinationViewController finalImageView]; destinationImageView.hidden = YES; UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; [[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view]; UIImageView *originalImageView = [self.sourceViewController originalImageView]; originalImageView.hidden = YES; self.transitioningImageView = [[UIImageView alloc] initWithImage:destinationImageView.image]; self.transitioningImageView.frame = [MXKAttachmentAnimator aspectFitImage:destinationImageView.image inFrame:destinationImageView.frame]; [[transitionContext containerView] addSubview:self.transitioningImageView]; } - (void)updateInteractiveTransition:(CGFloat)percentComplete { self.destinationViewController.view.alpha = MAX(0, (1 - percentComplete)); CGRect newFrame = CGRectMake(self.transitioningImageView.frame.origin.x, self.transitioningImageView.frame.origin.y + self.delta.y, CGRectGetWidth(self.transitioningImageView.frame), CGRectGetHeight(self.transitioningImageView.frame)); self.transitioningImageView.frame = newFrame; } - (void)cancelInteractiveTransition { UIViewController *fromViewController = [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIImageView *destinationImageView = [self.destinationViewController finalImageView]; UIImageView *originalImageView = [self.sourceViewController originalImageView]; __weak typeof(self) weakSelf = self; [UIView animateWithDuration:([self transitionDuration:self.transitionContext]/2) animations:^{ if (weakSelf) { typeof(self) self = weakSelf; fromViewController.view.alpha = 1; self.transitioningImageView.frame = [MXKAttachmentAnimator aspectFitImage:destinationImageView.image inFrame:destinationImageView.frame]; } } completion:^(BOOL finished) { if (weakSelf) { typeof(self) self = weakSelf; destinationImageView.hidden = NO; originalImageView.hidden = NO; [self.transitioningImageView removeFromSuperview]; [self.transitionContext cancelInteractiveTransition]; [self.transitionContext completeTransition:NO]; [self.destinationViewController prepareSubviewsForTransition:NO]; } }]; } - (void)finishInteractiveTransition { UIViewController *fromViewController = [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIImageView *destinationImageView = [self.destinationViewController finalImageView]; UIImageView *originalImageView = [self.sourceViewController originalImageView]; CGRect originalImageViewFrame = [self.sourceViewController convertedFrameForOriginalImageView]; __weak typeof(self) weakSelf = self; [UIView animateWithDuration:[self transitionDuration:self.transitionContext] animations:^{ if (weakSelf) { typeof(self) self = weakSelf; fromViewController.view.alpha = 0.0; self.transitioningImageView.frame = originalImageViewFrame; } } completion:^(BOOL finished) { if (weakSelf) { typeof(self) self = weakSelf; [self.transitioningImageView removeFromSuperview]; destinationImageView.hidden = NO; originalImageView.hidden = NO; [self.transitionContext finishInteractiveTransition]; [self.transitionContext completeTransition:YES]; } }]; } #pragma mark - UIViewControllerAnimatedTransitioning - (NSTimeInterval)transitionDuration:(id)transitionContext { return 0.3; } @end