chromium/ios/chrome/browser/mini_map/ui_bundled/mini_map_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/mini_map/ui_bundled/mini_map_coordinator.h"

#import "components/prefs/pref_service.h"
#import "ios/chrome/browser/mini_map/ui_bundled/mini_map_action_handler.h"
#import "ios/chrome/browser/mini_map/ui_bundled/mini_map_interstitial_view_controller.h"
#import "ios/chrome/browser/mini_map/ui_bundled/mini_map_mediator.h"
#import "ios/chrome/browser/mini_map/ui_bundled/mini_map_mediator_delegate.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/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/mini_map_commands.h"
#import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/chrome/browser/web/model/annotations/annotations_util.h"
#import "ios/chrome/common/string_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/mini_map/mini_map_api.h"
#import "ios/web/public/web_state.h"
#import "net/base/apple/url_conversions.h"
#import "ui/base/l10n/l10n_util.h"

@interface MiniMapCoordinator () <MiniMapActionHandler, MiniMapMediatorDelegate>

// The view controller to get user consent.
@property(nonatomic, strong)
    MiniMapInterstitialViewController* consentViewController;

// The controller that shows the mini map.
@property(nonatomic, strong) id<MiniMapController> miniMapController;

// Mediator to handle the consent logic.
@property(nonatomic, strong) MiniMapMediator* mediator;

// The WebState that triggered the request.
@property(assign) base::WeakPtr<web::WebState> webState;

// The text to be recognized as an address.
@property(nonatomic, copy) NSString* text;

// Whether the consent of the user is required.
@property(assign) BOOL consentRequired;

// The mode to display the map.
@property(assign) MiniMapMode mode;

@end

@implementation MiniMapCoordinator {
  BOOL _stopCalled;

  BOOL _showingMap;
}

- (instancetype)initWithBaseViewController:(UIViewController*)viewController
                                   browser:(Browser*)browser
                                  webState:(web::WebState*)webState
                                      text:(NSString*)text
                           consentRequired:(BOOL)consentRequired
                                      mode:(MiniMapMode)mode {
  self = [super initWithBaseViewController:viewController browser:browser];
  if (self) {
    _text = text;
    if (webState) {
      _webState = webState->GetWeakPtr();
    }
    _consentRequired = consentRequired;
    _mode = mode;
  }
  return self;
}

- (void)start {
  [super start];

  PrefService* prefService = self.browser->GetBrowserState()->GetPrefs();
  self.mediator = [[MiniMapMediator alloc] initWithPrefs:prefService
                                                webState:self.webState.get()];
  self.mediator.delegate = self;
  [self.mediator userInitiatedMiniMapConsentRequired:self.consentRequired];
}

- (void)stop {
  _stopCalled = YES;
  [self.mediator disconnect];
  [self dismissConsentInterstitialWithCompletion:nil];
  [super stop];
}

#pragma mark - MiniMapMediatorDelegate

- (void)showConsentInterstitial {
  self.consentViewController = [[MiniMapInterstitialViewController alloc] init];
  self.consentViewController.actionHandler = self;
  self.consentViewController.modalPresentationStyle =
      UIModalPresentationPageSheet;
  UISheetPresentationController* presentationController =
      self.consentViewController.sheetPresentationController;
  presentationController.prefersEdgeAttachedInCompactHeight = YES;
  presentationController.detents =
      @[ UISheetPresentationControllerDetent.largeDetent ];
  [self.baseViewController presentViewController:self.consentViewController
                                        animated:YES
                                      completion:nil];
}

- (void)dismissConsentInterstitialWithCompletion:(ProceduralBlock)completion {
  [self.consentViewController.presentingViewController
      dismissViewControllerAnimated:YES
                         completion:completion];
  self.consentViewController = nil;
}

- (void)showMapWithIPH:(BOOL)showIPH {
  _showingMap = YES;
  if (!self.consentViewController) {
    [self doShowMapWithIPH:showIPH];
    return;
  }
  __weak __typeof(self) weakSelf = self;
  [self dismissConsentInterstitialWithCompletion:^{
    [weakSelf doShowMapWithIPH:showIPH];
  }];
}

#pragma mark - MiniMapActionHandler

- (void)userPressedConsent {
  [self.mediator userConsented];
}

- (void)userPressedNoThanks {
  [self.mediator userDeclined];
  [self workflowEnded];
}

- (void)dismissed {
  if (!_showingMap) {
    [self.mediator userDismissed];
    [self workflowEnded];
  }
}

- (void)userPressedContentSettings {
  [self.mediator userOpenedSettingsInConsent];
  id<SettingsCommands> settingsCommandHandler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), SettingsCommands);
  [settingsCommandHandler
      showContentsSettingsFromViewController:self.consentViewController];
}

#pragma mark - Private methods

- (void)doShowMapWithIPH:(BOOL)showIPH {
  __weak __typeof(self) weakSelf = self;
  self.miniMapController =
      ios::provider::CreateMiniMapController(self.text, ^(NSURL* url) {
        [weakSelf mapDismissedRequestingURL:url];
      });
  [self.miniMapController
      configureFooterWithTitle:l10n_util::GetNSString(
                                   IDS_IOS_MINI_MAP_FOOTER_STRING)
      leadingButtonTitle:l10n_util::GetNSString(IDS_IOS_CONTENT_SETTINGS_TITLE)
      trailingButtonTitle:l10n_util::GetNSString(
                              IDS_IOS_OPTIONS_REPORT_AN_ISSUE)
      leadingButtonAction:^(UIViewController* viewController) {
        [weakSelf
            showContentSettingsFromMiniMapInViewController:viewController];
      }
      trailingButtonAction:^(UIViewController* viewController) {
        [weakSelf reportAnIssueFromMiniMapInViewController:viewController];
      }];

  if (showIPH) {
    NSString* iphTitle = l10n_util::GetNSString(IDS_IOS_MINI_MAP_IPH_TITLE);
    NSString* iphSubtitle =
        l10n_util::GetNSString(IDS_IOS_MINI_MAP_IPH_SUBTITLE);

    NSDictionary* linkAttributes = @{
      NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor],
      NSLinkAttributeName : net::NSURLWithGURL(GURL("chrome://settings")),
    };
    NSAttributedString* attrSubtitle =
        AttributedStringFromStringWithLink(iphSubtitle, nil, linkAttributes);

    [self.miniMapController
        configureDisclaimerWithTitle:[[NSAttributedString alloc]
                                         initWithString:iphTitle]
                            subtitle:attrSubtitle
                       actionHandler:^(NSURL*,
                                       UIViewController* viewController) {
                         [weakSelf
                             showContentSettingsFromMiniMapInViewController:
                                 viewController];
                       }];
  }
  if (self.mode == MiniMapMode::kDirections) {
    [self.miniMapController
        presentDirectionsWithPresentingViewController:self.baseViewController];
  } else {
    [self.miniMapController
        presentMapsWithPresentingViewController:self.baseViewController];
  }
}

// Called at the end of the minimap workflow.
- (void)workflowEnded {
  if (!_stopCalled) {
    id<MiniMapCommands> miniMapHandler = HandlerForProtocol(
        self.browser->GetCommandDispatcher(), MiniMapCommands);
    [miniMapHandler hideMiniMap];
  }
}

- (void)showContentSettingsFromMiniMapInViewController:
    (UIViewController*)viewController {
  [self.mediator userOpenedSettingsFromMiniMap];
  id<SettingsCommands> settingsCommandHandler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), SettingsCommands);
  [settingsCommandHandler
      showContentsSettingsFromViewController:viewController];
}

- (void)reportAnIssueFromMiniMapInViewController:
    (UIViewController*)viewController {
  [self.mediator userReportedAnIssueFromMiniMap];
  id<ApplicationCommands> applicationCommandHandler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);
  [applicationCommandHandler
      showReportAnIssueFromViewController:viewController
                                   sender:UserFeedbackSender::MiniMap];
}

- (void)mapDismissedRequestingURL:(NSURL*)url {
  _showingMap = NO;
  if (url) {
    [self.mediator userOpenedURLFromMiniMap];
    OpenNewTabCommand* command = [OpenNewTabCommand
        commandWithURLFromChrome:net::GURLWithNSURL(url)
                     inIncognito:self.browser->GetBrowserState()
                                     ->IsOffTheRecord()];
    id<ApplicationCommands> applicationHandler = HandlerForProtocol(
        self.browser->GetCommandDispatcher(), ApplicationCommands);
    [applicationHandler openURLInNewTab:command];
  } else {
    [self.mediator userClosedMiniMap];
  }
  [self workflowEnded];
}

@end