chromium/ios/chrome/browser/account_picker/ui_bundled/account_picker_coordinator.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_coordinator.h"

#import "base/metrics/user_metrics.h"
#import "base/strings/sys_string_conversions.h"
#import "components/prefs/pref_service.h"
#import "components/signin/public/base/signin_metrics.h"
#import "ios/chrome/browser/shared/coordinator/alert/alert_coordinator.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h"
#import "ios/chrome/browser/signin/model/constants.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/signin/model/system_identity.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_configuration.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_confirmation/account_picker_confirmation_screen_coordinator.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_confirmation/account_picker_confirmation_screen_coordinator_delegate.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_coordinator_delegate.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_layout_delegate.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_logger.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_screen/account_picker_screen_navigation_controller.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_screen/account_picker_screen_presentation_controller.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_screen/account_picker_screen_slide_transition_animator.h"
#import "ios/chrome/browser/account_picker/ui_bundled/account_picker_selection/account_picker_selection_screen_coordinator.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"

@interface AccountPickerCoordinator () <
    AccountPickerConfirmationScreenCoordinatorDelegate,
    AccountPickerLayoutDelegate,
    AccountPickerSelectionScreenCoordinatorDelegate,
    AccountPickerScreenPresentationControllerDelegate,
    UINavigationControllerDelegate,
    UIViewControllerTransitioningDelegate>

@end

@implementation AccountPickerCoordinator {
  // Navigation controller for the account picker.
  __strong AccountPickerScreenNavigationController* _navigationController;

  // Coordinator to display modal alerts to the user.
  __strong AlertCoordinator* _alertCoordinator;
  // Coordinator for the first screen.
  __strong AccountPickerConfirmationScreenCoordinator*
      _accountPickerConfirmationScreenCoordinator;
  // Coordinator to select another identity.
  __strong AccountPickerSelectionScreenCoordinator*
      _accountPickerSelectionScreenCoordinator;

  // The configuration for the account picker.
  __strong AccountPickerConfiguration* _configuration;
}

#pragma mark - Public

- (instancetype)initWithBaseViewController:(UIViewController*)baseViewController
                                   browser:(Browser*)browser
                             configuration:
                                 (AccountPickerConfiguration*)configuration {
  self = [super initWithBaseViewController:baseViewController browser:browser];
  if (self) {
    _configuration = configuration;
  }
  return self;
}

- (void)stopAnimated:(BOOL)animated {
  __weak __typeof(self) weakSelf = self;
  [_navigationController.presentingViewController
      dismissViewControllerAnimated:animated
                         completion:^{
                           [weakSelf.delegate
                               accountPickerCoordinatorDidStop:weakSelf];
                         }];
  _navigationController.delegate = nil;
  _navigationController.transitioningDelegate = nil;
  _navigationController = nil;

  [_alertCoordinator stop];
  _alertCoordinator = nil;
  [_accountPickerSelectionScreenCoordinator stop];
  _accountPickerSelectionScreenCoordinator = nil;
  [_accountPickerConfirmationScreenCoordinator stop];
  _accountPickerConfirmationScreenCoordinator = nil;
}

#pragma mark - AccountPickerConsumer

- (void)startValidationSpinner {
  [_accountPickerConfirmationScreenCoordinator startValidationSpinner];
}

- (void)stopValidationSpinner {
  [_accountPickerConfirmationScreenCoordinator stopValidationSpinner];
}

- (void)setIdentityButtonHidden:(BOOL)hidden animated:(BOOL)animated {
  [_accountPickerConfirmationScreenCoordinator
      setIdentityButtonHidden:hidden
                     animated:animated];
}

#pragma mark - ChromeCoordinator

- (void)start {
  // Create AccountPickerConfirmationScreenCoordinator.
  _accountPickerConfirmationScreenCoordinator =
      [[AccountPickerConfirmationScreenCoordinator alloc]
          initWithBaseViewController:_navigationController
                             browser:self.browser
                       configuration:_configuration];
  _accountPickerConfirmationScreenCoordinator.delegate = self;
  _accountPickerConfirmationScreenCoordinator.layoutDelegate = self;
  _accountPickerConfirmationScreenCoordinator.childViewController =
      self.accountConfirmationChildViewController;
  [_accountPickerConfirmationScreenCoordinator start];

  // Create AccountPickerScreenNavigationController.
  _navigationController = [[AccountPickerScreenNavigationController alloc]
      initWithRootViewController:_accountPickerConfirmationScreenCoordinator
                                     .viewController];
  _navigationController.delegate = self;
  _navigationController.modalPresentationStyle = UIModalPresentationCustom;
  _navigationController.transitioningDelegate = self;
  // Present the view.
  [self.baseViewController presentViewController:_navigationController
                                        animated:YES
                                      completion:nil];
}

- (void)stop {
  [self stopAnimated:NO];
}

#pragma mark - Properties

- (id<SystemIdentity>)selectedIdentity {
  return _accountPickerConfirmationScreenCoordinator.selectedIdentity;
}

- (void)setSelectedIdentity:(id<SystemIdentity>)selectedIdentity {
  _accountPickerConfirmationScreenCoordinator.selectedIdentity =
      selectedIdentity;
}

- (UIViewController*)viewController {
  return _navigationController;
}

#pragma mark - Private

// Called on completion of the AddAccountSigninCoordinator view.
- (void)addAccountCompletionWithIdentity:(id<SystemIdentity>)identity {
  if (!identity) {
    return;
  }
  _accountPickerConfirmationScreenCoordinator.selectedIdentity = identity;
  [self.logger logAccountPickerAddAccountCompleted];
}

// Opens an AddAccountSigninCoordinator to add an account to the device.
- (void)openAddAccountCoordinator {
  __weak __typeof(self) weakSelf = self;
  [self.delegate accountPickerCoordinator:self
             openAddAccountWithCompletion:^(id<SystemIdentity> identity) {
               [weakSelf addAccountCompletionWithIdentity:identity];
             }];
  [self.logger logAccountPickerAddAccountScreenOpened];
}

// Starts the validation flow.
- (void)startValidation {
  [self.delegate
      accountPickerCoordinator:self
             didSelectIdentity:self.selectedIdentity
                  askEveryTime:_accountPickerConfirmationScreenCoordinator
                                   .askEveryTime];
}

#pragma mark - AccountPickerLayoutDelegate

- (AccountPickerSheetDisplayStyle)displayStyle {
  return _navigationController.displayStyle;
}

#pragma mark - UINavigationControllerDelegate

- (id<UIViewControllerAnimatedTransitioning>)
               navigationController:
                   (UINavigationController*)navigationController
    animationControllerForOperation:(UINavigationControllerOperation)operation
                 fromViewController:(UIViewController*)fromVC
                   toViewController:(UIViewController*)toVC {
  switch (operation) {
    case UINavigationControllerOperationNone:
      return nil;
    case UINavigationControllerOperationPush:
      return [[AccountPickerScreenSlideTransitionAnimator alloc]
             initWithAnimation:kAccountPickerScreenSlideAnimationPushing
          navigationController:_navigationController];
    case UINavigationControllerOperationPop:
      return [[AccountPickerScreenSlideTransitionAnimator alloc]
             initWithAnimation:kAccountPickerScreenSlideAnimationPopping
          navigationController:_navigationController];
  }
}

- (id<UIViewControllerInteractiveTransitioning>)
                           navigationController:
                               (UINavigationController*)navigationController
    interactionControllerForAnimationController:
        (id<UIViewControllerAnimatedTransitioning>)animationController {
  return _navigationController.interactionTransition;
}

- (void)navigationController:(UINavigationController*)navigationController
       didShowViewController:(UIViewController*)viewController
                    animated:(BOOL)animated {
  DCHECK(navigationController == _navigationController)
      << base::SysNSStringToUTF8([self description]);
  DCHECK(navigationController.viewControllers.count > 0)
      << base::SysNSStringToUTF8([self description]);
  DCHECK(navigationController.viewControllers[0] ==
         _accountPickerConfirmationScreenCoordinator.viewController)
      << base::SysNSStringToUTF8([self description]);
  if (_navigationController.viewControllers.count == 1 &&
      _accountPickerSelectionScreenCoordinator) {
    // AccountChooserCoordinator has been removed by "Back" button.
    [_accountPickerSelectionScreenCoordinator stop];
    _accountPickerSelectionScreenCoordinator = nil;
    [self.logger logAccountPickerSelectionScreenClosed];
  }
}

#pragma mark - AccountPickerSelectionScreenCoordinatorDelegate

- (void)accountPickerSelectionScreenCoordinatorIdentitySelected:
    (AccountPickerSelectionScreenCoordinator*)coordinator {
  if (_accountPickerSelectionScreenCoordinator.selectedIdentity !=
      _accountPickerConfirmationScreenCoordinator.selectedIdentity) {
    [self.logger logAccountPickerNewIdentitySelected];
  }
  _accountPickerConfirmationScreenCoordinator.selectedIdentity =
      _accountPickerSelectionScreenCoordinator.selectedIdentity;
  [_accountPickerSelectionScreenCoordinator stop];
  _accountPickerSelectionScreenCoordinator = nil;
  [_navigationController popViewControllerAnimated:YES];
}

- (void)accountPickerSelectionScreenCoordinatorOpenAddAccount:
    (AccountPickerSelectionScreenCoordinator*)coordinator {
  [self openAddAccountCoordinator];
}

#pragma mark - AccountPickerConfirmationScreenCoordinatorDelegate

- (void)accountPickerConfirmationScreenCoordinatorCancel:
    (AccountPickerConfirmationScreenCoordinator*)coordinator {
  [self.delegate accountPickerCoordinatorCancel:self];
}

- (void)
    accountPickerConfirmationScreenCoordinatorOpenAccountPickerSelectionScreen:
        (AccountPickerConfirmationScreenCoordinator*)coordinator {
  if (_accountPickerSelectionScreenCoordinator) {
    // If the user taps the identity button several times in the confirmation
    // screen before the transition to the selection screen, this method might
    // be invoked more than once. Hence an early return if the selection screen
    // coordinator has already been created.
    return;
  }

  _accountPickerSelectionScreenCoordinator =
      [[AccountPickerSelectionScreenCoordinator alloc]
          initWithBaseViewController:_navigationController
                             browser:self.browser];
  _accountPickerSelectionScreenCoordinator.delegate = self;
  _accountPickerSelectionScreenCoordinator.layoutDelegate = self;
  [_accountPickerSelectionScreenCoordinator
      startWithSelectedIdentity:_accountPickerConfirmationScreenCoordinator
                                    .selectedIdentity];
  [_navigationController
      pushViewController:_accountPickerSelectionScreenCoordinator.viewController
                animated:YES];
  [self.logger logAccountPickerSelectionScreenOpened];
}

- (void)accountPickerConfirmationScreenCoordinatorSubmit:
    (AccountPickerConfirmationScreenCoordinator*)coordinator {
  [self startValidation];
}

- (void)accountPickerConfirmationScreenCoordinatorOpenAddAccount:
    (AccountPickerConfirmationScreenCoordinator*)coordinator {
  [self openAddAccountCoordinator];
}

#pragma mark - UIViewControllerTransitioningDelegate

- (UIPresentationController*)
    presentationControllerForPresentedViewController:
        (UIViewController*)presentedViewController
                            presentingViewController:
                                (UIViewController*)presentingViewController
                                sourceViewController:(UIViewController*)source {
  DCHECK_EQ(_navigationController, presentedViewController)
      << base::SysNSStringToUTF8([self description]);
  AccountPickerScreenPresentationController* controller =
      [[AccountPickerScreenPresentationController alloc]
          initWithAccountPickerScreenNavigationController:_navigationController
                                 presentingViewController:
                                     presentingViewController];
  controller.actionDelegate = self;
  return controller;
}

#pragma mark - AccountPickerScreenPresentationControllerDelegate

- (void)accountPickerScreenPresentationControllerBackgroundTapped:
    (AccountPickerScreenPresentationController*)controller {
  if (_configuration.dismissOnBackgroundTap) {
    [self.delegate accountPickerCoordinatorCancel:self];
  }
}

#pragma mark - NSObject

- (NSString*)description {
  return [NSString
      stringWithFormat:@"<%@: %p, accountPickerConfirmationScreenCoordinator: "
                       @"%p, alertCoordinator: %p, "
                       @"accountPickerSelectionScreenCoordinator %p>",
                       self.class.description, self,
                       _accountPickerConfirmationScreenCoordinator,
                       _alertCoordinator,
                       _accountPickerSelectionScreenCoordinator];
}

@end