chromium/ios/chrome/browser/text_fragments/ui_bundled/text_fragments_coordinator.mm

// Copyright 2021 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/text_fragments/ui_bundled/text_fragments_coordinator.h"

#import <memory>

#import "base/metrics/user_metrics.h"
#import "base/strings/sys_string_conversions.h"
#import "components/shared_highlighting/core/common/fragment_directives_utils.h"
#import "components/shared_highlighting/core/common/text_fragment.h"
#import "components/shared_highlighting/ios/shared_highlighting_constants.h"
#import "ios/chrome/browser/shared/coordinator/alert/action_sheet_coordinator.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/web_state_list/active_web_state_observation_forwarder.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/commands/activity_service_commands.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/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/share_highlight_command.h"
#import "ios/chrome/browser/text_fragments/ui_bundled/text_fragments_mediator.h"
#import "ios/chrome/browser/web_state_list/model/web_state_dependency_installer_bridge.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/web/public/text_fragments/text_fragments_manager.h"
#import "ios/web/public/web_state.h"
#import "ios/web/public/web_state_observer_bridge.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/strings/grit/ui_strings.h"
#import "url/gurl.h"

@interface TextFragmentsCoordinator () <DependencyInstalling,
                                        TextFragmentsDelegate,
                                        CRWWebStateObserver>

@property(nonatomic, strong, readonly) TextFragmentsMediator* mediator;

@property(nonatomic, strong) ActionSheetCoordinator* actionSheet;

@end

@implementation TextFragmentsCoordinator {
  // Bridge which observes WebStateList and alerts this coordinator when this
  // needs to register the Mediator with a new WebState.
  std::unique_ptr<WebStateDependencyInstallerBridge> _dependencyInstallerBridge;

  // Used to observe the active WebState
  std::unique_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
  std::unique_ptr<ActiveWebStateObservationForwarder> _forwarder;
}

- (instancetype)initWithBaseViewController:(UIViewController*)baseViewController
                                   browser:(Browser*)browser {
  if ((self = [super initWithBaseViewController:baseViewController
                                        browser:browser])) {
    _mediator = [[TextFragmentsMediator alloc] initWithConsumer:self];
    _dependencyInstallerBridge =
        std::make_unique<WebStateDependencyInstallerBridge>(
            self, browser->GetWebStateList());
    _webStateObserverBridge =
        std::make_unique<web::WebStateObserverBridge>(self);
    _forwarder = std::make_unique<ActiveWebStateObservationForwarder>(
        browser->GetWebStateList(), _webStateObserverBridge.get());
  }
  return self;
}

#pragma mark - TextFragmentsDelegate methods

- (void)userTappedTextFragmentInWebState:(web::WebState*)webState {
}

- (void)userTappedTextFragmentInWebState:(web::WebState*)webState
                              withSender:(CGRect)rect
                                withText:(NSString*)text
                           withFragments:
                               (std::vector<shared_highlighting::TextFragment>)
                                   fragments {
  base::RecordAction(base::UserMetricsAction("TextFragments.Menu.Opened"));

  self.actionSheet = [[ActionSheetCoordinator alloc]
      initWithBaseViewController:[self baseViewController]
                         browser:[self browser]
                           title:l10n_util::GetNSString(
                                     IDS_IOS_SHARED_HIGHLIGHT_MENU_TITLE)
                         message:nil
                            rect:rect
                            view:[self.baseViewController view]];

  __weak TextFragmentsCoordinator* weakSelf = self;
  [self.actionSheet
      addItemWithTitle:l10n_util::GetNSString(IDS_IOS_SHARED_HIGHLIGHT_REMOVE)
                action:^{
                  base::RecordAction(
                      base::UserMetricsAction("TextFragments.Menu.Removed"));
                  [weakSelf.mediator removeTextFragmentsInWebState:webState];
                  [weakSelf dismissActionSheet];
                }
                 style:UIAlertActionStyleDestructive];
  [self.actionSheet
      addItemWithTitle:l10n_util::GetNSString(IDS_IOS_SHARED_HIGHLIGHT_RESHARE)
                action:^{
                  base::RecordAction(
                      base::UserMetricsAction("TextFragments.Menu.Reshared"));
                  id<ActivityServiceCommands> handler = HandlerForProtocol(
                      weakSelf.browser->GetCommandDispatcher(),
                      ActivityServiceCommands);

                  auto* activeWebState =
                      weakSelf.browser->GetWebStateList()->GetActiveWebState();

                  // Take the fragments from the vector and put them into the
                  // last committed URL. In most cases this will yield the same
                  // URL, but it's necessary in case same-document navigation
                  // has cleared the fragments from the URL.
                  GURL sharingURL =
                      shared_highlighting::AppendFragmentDirectives(
                          activeWebState->GetLastCommittedURL(), fragments);

                  ShareHighlightCommand* command =
                      [[ShareHighlightCommand alloc]
                           initWithURL:sharingURL
                                 title:base::SysUTF16ToNSString(
                                           activeWebState->GetTitle())
                          selectedText:text
                            sourceView:activeWebState->GetView()
                            sourceRect:rect];

                  [handler shareHighlight:command];
                  [weakSelf dismissActionSheet];
                }
                 style:UIAlertActionStyleDefault];
  [self.actionSheet
      addItemWithTitle:l10n_util::GetNSString(
                           IDS_IOS_SHARED_HIGHLIGHT_LEARN_MORE)
                action:^{
                  base::RecordAction(base::UserMetricsAction(
                      "TextFragments.Menu.LearnMoreOpened"));
                  id<ApplicationCommands> handler = HandlerForProtocol(
                      weakSelf.browser->GetCommandDispatcher(),
                      ApplicationCommands);
                  [handler openURLInNewTab:[OpenNewTabCommand
                                               commandWithURLFromChrome:
                                                   GURL(shared_highlighting::
                                                            kLearnMoreUrl)]];
                  [weakSelf dismissActionSheet];
                }
                 style:UIAlertActionStyleDefault];
  [self.actionSheet addItemWithTitle:l10n_util::GetNSString(IDS_APP_CANCEL)
                              action:^{
                                [weakSelf dismissActionSheet];
                              }
                               style:UIAlertActionStyleCancel];
  [self.actionSheet start];
}

#pragma mark - DependencyInstalling methods

- (void)installDependencyForWebState:(web::WebState*)webState {
  [self.mediator registerWithWebState:webState];
}

#pragma mark - ChromeCoordinator methods

- (void)stop {
  [self dismissActionSheet];
  // Reset this observer manually. We want this to go out of scope now, ensuring
  // it detaches before `browser` and its WebStateList get destroyed.
  _dependencyInstallerBridge.reset();
}

#pragma mark - CRWWebStateObserver methods

- (void)webState:(web::WebState*)webState
    didStartNavigation:(web::NavigationContext*)navigationContext {
  [self dismissActionSheet];
}

#pragma mark - Private

- (void)dismissActionSheet {
  [self.actionSheet stop];
  self.actionSheet = nil;
}

@end