chromium/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_groups/create_tab_group_transition_delegate.mm

// Copyright 2024 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/tab_switcher/tab_grid/tab_groups/create_tab_group_transition_delegate.h"

#import "base/apple/foundation_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"

namespace {
const CGFloat kPresentationDuration = 0.25;
const CGFloat kDismissalDuration = 0.20;
const CGFloat kBackgroundAlpha = 0.1;
const CGFloat kScaleFactor = 0.8;
const CGFloat kShortDurationFactor = 0.9;

// Tags for the different views.
const NSInteger kBackgroundTag = 1;
const NSInteger kVisualEffectTag = 2;

// Returns the subview of `superview` with a `tag`.
UIView* ViewWithTag(NSInteger tag, UIView* superview) {
  for (UIView* view in superview.subviews) {
    if (view.tag == tag) {
      return view;
    }
  }
  return nil;
}
}  // namespace

@interface CreateTabGroupTransitionAnimator
    : NSObject <UIViewControllerAnimatedTransitioning>

@property(nonatomic, assign) BOOL appearing;

@end

@implementation CreateTabGroupTransitionAnimator

- (void)animateTransition:
    (id<UIViewControllerContextTransitioning>)transitionContext {
  UIViewController* presentedViewController = [transitionContext
      viewControllerForKey:self.appearing
                               ? UITransitionContextToViewControllerKey
                               : UITransitionContextFromViewControllerKey];

  UIView* presentedView = [transitionContext
      viewForKey:self.appearing ? UITransitionContextToViewKey
                                : UITransitionContextFromViewKey];

  UIView* containerView = [transitionContext containerView];

  NSTimeInterval duration = [self transitionDuration:transitionContext];
  if (self.appearing) {
    CGRect finalFrame =
        [transitionContext finalFrameForViewController:presentedViewController];

    UIView* backgroundView = [[UIView alloc] initWithFrame:finalFrame];
    backgroundView.autoresizingMask =
        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    backgroundView.alpha = 0;
    backgroundView.tag = kBackgroundTag;
    UIVisualEffectView* blurEffectView;

    if (!UIAccessibilityIsReduceTransparencyEnabled()) {
      blurEffectView = [[UIVisualEffectView alloc] initWithEffect:nil];
      blurEffectView.tag = kVisualEffectTag;
      blurEffectView.frame = backgroundView.bounds;
      blurEffectView.autoresizingMask =
          UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
      backgroundView.backgroundColor = [[UIColor colorNamed:kGrey200Color]
          colorWithAlphaComponent:kBackgroundAlpha];
      [backgroundView addSubview:blurEffectView];
    } else {
      backgroundView.backgroundColor = [UIColor blackColor];
    }

    [containerView addSubview:backgroundView];
    [containerView addSubview:presentedView];

    presentedView.frame = finalFrame;
    presentedView.alpha = 0;
    presentedView.transform =
        CGAffineTransformMakeScale(kScaleFactor, kScaleFactor);

    [UIView animateWithDuration:duration
        delay:0
        options:UIViewAnimationCurveEaseInOut
        animations:^{
          presentedView.alpha = 1;
          presentedView.transform = CGAffineTransformIdentity;
        }
        completion:^(BOOL finished) {
          [transitionContext completeTransition:YES];
        }];

    [UIView animateWithDuration:duration * kShortDurationFactor
                          delay:0
                        options:UIViewAnimationCurveEaseOut
                     animations:^{
                       UIBlurEffect* blurEffect = [UIBlurEffect
                           effectWithStyle:UIBlurEffectStyleSystemMaterial];
                       blurEffectView.effect = blurEffect;
                       backgroundView.alpha = 1;
                     }
                     completion:^(BOOL finished){
                         // No action here as it will finish first.
                     }];

  } else {
    UIView* backgroundView = ViewWithTag(kBackgroundTag, containerView);
    UIVisualEffectView* visualEffectView =
        base::apple::ObjCCast<UIVisualEffectView>(
            ViewWithTag(kVisualEffectTag, backgroundView));

    [UIView animateWithDuration:duration * kShortDurationFactor
                          delay:0
                        options:UIViewAnimationCurveEaseOut
                     animations:^{
                       presentedView.alpha = 0;
                       presentedView.transform = CGAffineTransformMakeScale(
                           kScaleFactor, kScaleFactor);
                     }
                     completion:^(BOOL finished){
                         // No action here as it will finish first.
                     }];

    [UIView animateWithDuration:duration
        delay:0
        options:UIViewAnimationCurveEaseIn
        animations:^{
          backgroundView.alpha = 0;
          visualEffectView.effect = nil;
        }
        completion:^(BOOL finished) {
          [presentedView removeFromSuperview];
          [transitionContext completeTransition:YES];
        }];
  }
}

- (NSTimeInterval)transitionDuration:
    (id<UIViewControllerContextTransitioning>)transitionContext {
  return self.appearing ? kPresentationDuration : kDismissalDuration;
}

@end

@implementation CreateTabGroupTransitionDelegate

- (id<UIViewControllerAnimatedTransitioning>)
    animationControllerForPresentedController:(UIViewController*)presented
                         presentingController:(UIViewController*)presenting
                             sourceController:(UIViewController*)source {
  CreateTabGroupTransitionAnimator* animator =
      [[CreateTabGroupTransitionAnimator alloc] init];
  animator.appearing = YES;
  return animator;
}

- (id<UIViewControllerAnimatedTransitioning>)
    animationControllerForDismissedController:(UIViewController*)dismissed {
  CreateTabGroupTransitionAnimator* animator =
      [[CreateTabGroupTransitionAnimator alloc] init];
  animator.appearing = NO;
  return animator;
}

@end