chromium/ios/chrome/browser/ui/lens/lens_modal_animator.mm

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/ui/lens/lens_modal_animator.h"
#import "ios/chrome/browser/shared/public/commands/open_lens_input_selection_command.h"

// The time it takes for the presenting animation to complete.
static const CGFloat kPresentingTransitionAnimationDuration = .4;

// The time it takes for the dismissal animation to complete.
static const CGFloat kDismissalTransitionAnimationDuration = .3;

// Motion curves for the presentation animation.
static const CGFloat kPresentationAnimationCurve0 = 0.05;
static const CGFloat kPresentationAnimationCurve1 = 0.7;
static const CGFloat kPresentationAnimationCurve2 = 0.1;
static const CGFloat kPresentationAnimationCurve3 = 1.;

@interface LensModalAnimator ()

@end

@implementation LensModalAnimator

#pragma mark - UIViewControllerTransitioningDelegate

- (id<UIViewControllerInteractiveTransitioning>)
    interactionControllerForPresentation:
        (id<UIViewControllerAnimatedTransitioning>)animator {
  return nil;
}

- (id<UIViewControllerInteractiveTransitioning>)
    interactionControllerForDismissal:
        (id<UIViewControllerAnimatedTransitioning>)animator {
  return nil;
}

- (id<UIViewControllerAnimatedTransitioning>)
    animationControllerForPresentedController:(UIViewController*)presented
                         presentingController:(UIViewController*)presenting
                             sourceController:(UIViewController*)source {
  return self;
}

- (id<UIViewControllerAnimatedTransitioning>)
    animationControllerForDismissedController:(UIViewController*)dismissed {
  return self;
}

#pragma mark - UIViewControllerAnimatedTransitioning

- (NSTimeInterval)transitionDuration:
    (id<UIViewControllerContextTransitioning>)transitionContext {
  return [self isPresentingFromContext:transitionContext]
             ? kPresentingTransitionAnimationDuration
             : kDismissalTransitionAnimationDuration;
}

- (void)animateTransition:
    (id<UIViewControllerContextTransitioning>)transitionContext {
  BOOL presenting = [self isPresentingFromContext:transitionContext];
  if (presenting) {
    [self animatePresenting:transitionContext];
  } else {
    [self animateDismissal:transitionContext];
  }
}

#pragma mark - Private methods

- (void)animatePresenting:
    (id<UIViewControllerContextTransitioning>)transitionContext {
  UIView* containerView = [transitionContext containerView];
  UIView* lensView =
      [transitionContext viewForKey:UITransitionContextToViewKey];
  UIViewController* lensViewController = [transitionContext
      viewControllerForKey:UITransitionContextToViewControllerKey];

  [containerView addSubview:lensView];
  [containerView bringSubviewToFront:lensView];
  CGRect startingFrame = containerView.bounds;
  if (self.presentationStyle ==
      LensInputSelectionPresentationStyle::SlideFromLeft) {
    startingFrame.origin.x = -startingFrame.size.width;
  } else {
    startingFrame.origin.x = containerView.bounds.size.width;
  }

  lensView.frame = startingFrame;

  UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc]
      initWithDuration:[self transitionDuration:transitionContext]
         controlPoint1:CGPointMake(kPresentationAnimationCurve0,
                                   kPresentationAnimationCurve1)
         controlPoint2:CGPointMake(kPresentationAnimationCurve2,
                                   kPresentationAnimationCurve3)
            animations:^{
              lensView.frame = [transitionContext
                  finalFrameForViewController:lensViewController];
            }];

  // Completions should only be run once.
  const ProceduralBlock presentationCompletion = _presentationCompletion;
  _presentationCompletion = nil;

  [animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
    BOOL success = ![transitionContext transitionWasCancelled];

    if (success && presentationCompletion) {
      dispatch_async(dispatch_get_main_queue(), ^{
        presentationCompletion();
      });
    }

    [transitionContext completeTransition:success];
  }];
  [animator startAnimation];
}

- (void)animateDismissal:
    (id<UIViewControllerContextTransitioning>)transitionContext {
  UIView* containerView = [transitionContext containerView];
  UIView* toView = [transitionContext viewForKey:UITransitionContextToViewKey];
  UIView* lensView =
      [transitionContext viewForKey:UITransitionContextFromViewKey];

  // Create and add a Lens view snapshot.
  UIView* lensViewSnapshot = [lensView snapshotViewAfterScreenUpdates:YES];

  [containerView addSubview:toView];
  [containerView addSubview:lensViewSnapshot];
  [lensView removeFromSuperview];
  [containerView bringSubviewToFront:lensViewSnapshot];

  lensViewSnapshot.alpha = 1.0;

  [UIView animateWithDuration:[self transitionDuration:transitionContext]
      animations:^{
        lensViewSnapshot.alpha = 0.0;
      }

      completion:^(BOOL finished) {
        BOOL success = ![transitionContext transitionWasCancelled];

        if (success) {
          [lensViewSnapshot removeFromSuperview];
        }

        [transitionContext completeTransition:success];
      }];
}

- (BOOL)isPresentingFromContext:
    (id<UIViewControllerContextTransitioning>)transitionContext {
  UIViewController* fromViewController = [transitionContext
      viewControllerForKey:UITransitionContextFromViewControllerKey];
  UIViewController* toViewController = [transitionContext
      viewControllerForKey:UITransitionContextToViewControllerKey];
  UIViewController* toPresentingViewController =
      toViewController.presentingViewController;
  return (toPresentingViewController == fromViewController) ? YES : NO;
}

@end