chromium/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_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/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_coordinator.h"

#import "components/autofill/ios/form_util/form_activity_params.h"
#import "ios/chrome/browser/autofill/model/personal_data_manager_factory.h"
#import "ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_exit_reason.h"
#import "ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_mediator.h"
#import "ios/chrome/browser/autofill/ui_bundled/bottom_sheet/payments_suggestion_bottom_sheet_view_controller.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/web/public/web_state.h"

using PaymentsSuggestionBottomSheetExitReason::kCouldNotPresent;
using PaymentsSuggestionBottomSheetExitReason::kDismissal;
using PaymentsSuggestionBottomSheetExitReason::kShowPaymentDetails;
using PaymentsSuggestionBottomSheetExitReason::kShowPaymentMethods;
using PaymentsSuggestionBottomSheetExitReason::kUsePaymentsSuggestion;

@interface PaymentsSuggestionBottomSheetCoordinator () {
  // Information regarding the triggering form for this bottom sheet.
  autofill::FormActivityParams _params;

  // Currently in the process of dismissing the bottom sheet.
  bool _dismissing;
}

// This mediator is used to fetch data related to the bottom sheet.
@property(nonatomic, strong) PaymentsSuggestionBottomSheetMediator* mediator;

// This view controller is used to display the bottom sheet.
@property(nonatomic, strong)
    PaymentsSuggestionBottomSheetViewController* viewController;

// Used to find the CreditCard object and use it to open the credit card details
// view.
@property(nonatomic, assign) autofill::PersonalDataManager* personalDataManager;

@end

@implementation PaymentsSuggestionBottomSheetCoordinator

- (instancetype)initWithBaseViewController:(UIViewController*)viewController
                                   browser:(Browser*)browser
                                    params:(const autofill::FormActivityParams&)
                                               params {
  self = [super initWithBaseViewController:viewController browser:browser];
  if (self) {
    _params = params;
    _dismissing = NO;

    ChromeBrowserState* browserState =
        browser->GetBrowserState()->GetOriginalChromeBrowserState();

    self.personalDataManager =
        autofill::PersonalDataManagerFactory::GetForBrowserState(
            browserState->GetOriginalChromeBrowserState());
  }
  return self;
}

#pragma mark - ChromeCoordinator

- (void)start {
  WebStateList* webStateList = self.browser->GetWebStateList();
  self.mediator = [[PaymentsSuggestionBottomSheetMediator alloc]
      initWithWebStateList:webStateList
                    params:_params
       personalDataManager:self.personalDataManager];
  const GURL& URL = webStateList->GetActiveWebState()->GetLastCommittedURL();
  self.viewController =
      [[PaymentsSuggestionBottomSheetViewController alloc] initWithHandler:self
                                                                       URL:URL];
  self.mediator.consumer = self.viewController;
  self.viewController.delegate = self.mediator;

  // This is a fallback since the code enabling the bottom sheet happens earlier
  // than the code which retrieves credit card suggestions for the bottom sheet
  // and other operations which may modify the list of available credit cards
  // can happen between these two operations.
  if (!self.mediator.hasCreditCards) {
    [self.mediator disableBottomSheetAndRefocus:YES];
    [self.mediator disconnect];
    return;
  }

  self.viewController.parentViewControllerHeight =
      self.baseViewController.view.frame.size.height;
  __weak __typeof(self) weakSelf = self;
  [self.baseViewController presentViewController:self.viewController
                                        animated:YES
                                      completion:^{
                                        [weakSelf setInitialVoiceOverFocus];
                                      }];

  // Dismiss right away if the presentation failed to avoid having a zombie
  // coordinator. This is the best proxy we have to know whether the view
  // controller for the bottom sheet could really be presented as the completion
  // block is only called when presentation really happens, and we can't get any
  // error message or signal. Based on what we could test, we know that
  // presentingViewController is only set if the view controller can be
  // presented, where it is left to nil if the presentation is rejected for
  // various reasons (having another view controller already presented is one of
  // them). One should not think they can know all the reasons why the
  // presentation fails.
  //
  // Keep this line at the end of -start because the
  // delegate will likely -stop the coordinator when closing suggestions, so the
  // coordinator should be in the most up to date state where it can be safely
  // stopped.
  if (!self.viewController.presentingViewController) {
    [self.mediator logExitReason:kCouldNotPresent];
    [self.browserCoordinatorCommandsHandler dismissPaymentSuggestions];
  }
}

- (void)stop {
  [super stop];
  [self.viewController dismissViewControllerAnimated:NO completion:nil];
  self.viewController = nil;
  [self.mediator disconnect];
  self.mediator = nil;
}

#pragma mark - PaymentsSuggestionBottomSheetHandler

- (void)displayPaymentMethods {
  _dismissing = YES;
  [self.mediator logExitReason:kShowPaymentMethods];
  __weak __typeof(self) weakSelf = self;
  [self.baseViewController.presentedViewController
      dismissViewControllerAnimated:NO
                         completion:^{
                           [weakSelf.settingsHandler showCreditCardSettings];
                           [weakSelf.browserCoordinatorCommandsHandler
                                   dismissPaymentSuggestions];
                         }];
}

- (void)displayPaymentDetailsForCreditCardIdentifier:
    (NSString*)creditCardIdentifier {
  _dismissing = YES;
  autofill::CreditCard* creditCard =
      [self.mediator creditCardForIdentifier:creditCardIdentifier];
  if (creditCard) {
    [self.mediator logExitReason:kShowPaymentDetails];
    __weak __typeof(self) weakSelf = self;
    [self.baseViewController.presentedViewController
        dismissViewControllerAnimated:NO
                           completion:^{
                             [weakSelf.settingsHandler
                                 showCreditCardDetails:creditCard
                                            inEditMode:NO];
                             [weakSelf.browserCoordinatorCommandsHandler
                                     dismissPaymentSuggestions];
                           }];
  }
}

- (void)primaryButtonTappedForCard:(CreditCardData*)creditCardData
                           atIndex:(NSInteger)index {
  _dismissing = YES;
  [self.mediator logExitReason:kUsePaymentsSuggestion];
  __weak __typeof(self) weakSelf = self;
  [self.viewController
      dismissViewControllerAnimated:NO
                         completion:^{
                           [weakSelf didSelectCreditCard:creditCardData
                                                 atIndex:index];
                           [weakSelf.browserCoordinatorCommandsHandler
                                   dismissPaymentSuggestions];
                         }];
}

- (void)secondaryButtonTapped {
  // "Use Keyboard" button, which dismisses the bottom sheet.
  [self.viewController dismissViewControllerAnimated:YES completion:NULL];
}

- (void)viewDidDisappear {
  if (_dismissing) {
    return;
  }

  [self.mediator logExitReason:kDismissal];
  [self.mediator disconnect];
  [_browserCoordinatorCommandsHandler dismissPaymentSuggestions];
}

#pragma mark - Private

- (void)didSelectCreditCard:(CreditCardData*)creditCardData
                    atIndex:(NSInteger)index {
  // Send a notification to fill the credit card related fields.
  [self.mediator didSelectCreditCard:creditCardData atIndex:index];
  [self.mediator disconnect];
}

- (void)setInitialVoiceOverFocus {
  UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
                                  self.viewController.image);
}

@end