chromium/ios/chrome/browser/account_picker/ui_bundled/account_picker_screen/account_picker_screen_slide_transition_animator.mm

// Copyright 2023 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/account_picker/ui_bundled/account_picker_screen/account_picker_screen_slide_transition_animator.h"

#import "base/i18n/rtl.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_screen/account_picker_screen_navigation_controller.h"

namespace {

// Slide animation duration in seconds.
const CGFloat kAnimationDuration = 0.25;

}  // namespace

@interface AccountPickerScreenSlideTransitionAnimator ()

@property(nonatomic, assign, readonly)
    AccountPickerScreenSlideAnimation animation;

@end

@implementation AccountPickerScreenSlideTransitionAnimator

- (instancetype)initWithAnimation:(AccountPickerScreenSlideAnimation)animation
             navigationController:(AccountPickerScreenNavigationController*)
                                      navigationController {
  self = [super init];
  if (self) {
    _animation = animation;
    _navigationController = navigationController;
  }
  return self;
}

#pragma mark - UIViewControllerAnimatedTransitioning

- (NSTimeInterval)transitionDuration:
    (id<UIViewControllerContextTransitioning>)transitionContext {
  return kAnimationDuration;
}

- (void)animateTransition:
    (id<UIViewControllerContextTransitioning>)transitionContext {
  // The view to slide out.
  UIView* fromView =
      [transitionContext viewForKey:UITransitionContextFromViewKey];
  // The view to slide in.
  UIView* toView = [transitionContext viewForKey:UITransitionContextToViewKey];
  if (!fromView || !toView) {
    return;
  }
  [transitionContext.containerView addSubview:toView];

  // Save the pre-layout frame for the navigation and destination views. These
  // will be replaced with the post-layout frames if the underlying child view
  // controller requires a layout change in `layoutFittingSizeForWidth:`.
  CGRect viewControllerFrame = self.navigationController.view.frame;
  CGRect fromViewFrame = fromView.frame;

  CGSize toViewSize = [self.navigationController
      layoutFittingSizeForWidth:viewControllerFrame.size.width];

  // Restore frame layouts used prior to the layout change.
  fromView.frame = fromViewFrame;

  CGFloat sizeDifference = toViewSize.height - fromView.frame.size.height;
  CGRect fromViewFrameAfterAnimation = fromView.frame;
  CGRect toViewFrameBeforeAnimation = fromView.frame;
  CGRect toViewFrameAfterAnimation =
      CGRectMake(0, 0, toViewSize.width, toViewSize.height);
  CGRect navigationFrameAfterAnimation = viewControllerFrame;

  BOOL fromViewShouldMoveTowardsTheRight =
      (self.animation == kAccountPickerScreenSlideAnimationPopping &&
       !base::i18n::IsRTL()) ||
      (self.animation == kAccountPickerScreenSlideAnimationPushing &&
       base::i18n::IsRTL());
  if (fromViewShouldMoveTowardsTheRight) {
    toViewFrameBeforeAnimation.origin.x = -toView.frame.size.width;
    fromViewFrameAfterAnimation.origin.x = fromView.frame.size.width;
  } else {
    toViewFrameBeforeAnimation.origin.x = toView.frame.size.width;
    fromViewFrameAfterAnimation.origin.x = -fromView.frame.size.width;
  }
  toView.frame = toViewFrameBeforeAnimation;
  switch (self.navigationController.displayStyle) {
    case AccountPickerSheetDisplayStyle::kBottom:
      navigationFrameAfterAnimation.origin.y -= sizeDifference;
      navigationFrameAfterAnimation.size.height += sizeDifference;
      break;
    case AccountPickerSheetDisplayStyle::kCentered:
      navigationFrameAfterAnimation.origin.y -= sizeDifference / 2.;
      navigationFrameAfterAnimation.size.height += sizeDifference;
      break;
  }

  // Restore frame to pre-layout value before triggering animations.
  self.navigationController.view.frame = viewControllerFrame;
  [self.navigationController didUpdateControllerViewFrame];
  NSTimeInterval duration = [self transitionDuration:transitionContext];
  void (^animations)() = ^() {
    [UIView addKeyframeWithRelativeStartTime:.0
                            relativeDuration:1.
                                  animations:^{
                                    self.navigationController.view.frame =
                                        navigationFrameAfterAnimation;
                                    [self.navigationController
                                            didUpdateControllerViewFrame];
                                    toView.frame = toViewFrameAfterAnimation;
                                    fromView.frame =
                                        fromViewFrameAfterAnimation;
                                  }];
  };
  void (^completion)(BOOL finished) = ^(BOOL finished) {
    [transitionContext
        completeTransition:!transitionContext.transitionWasCancelled];
  };
  [UIView
      animateKeyframesWithDuration:duration
                             delay:0
                           options:
                               UIViewKeyframeAnimationOptionCalculationModeLinear
                        animations:animations
                        completion:completion];
}

@end