chromium/ios/chrome/browser/link_to_text/ui_bundled/link_to_text_mediator.mm

// Copyright 2020 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/link_to_text/ui_bundled/link_to_text_mediator.h"

#import "base/memory/weak_ptr.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "components/shared_highlighting/core/common/shared_highlighting_metrics.h"
#import "ios/chrome/browser/link_to_text/model/link_to_text_payload.h"
#import "ios/chrome/browser/link_to_text/model/link_to_text_response.h"
#import "ios/chrome/browser/link_to_text/model/link_to_text_tab_helper.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/share_highlight_command.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/ui/browser_container/edit_menu_alert_delegate.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/web/public/web_state.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/strings/grit/ui_strings.h"

using shared_highlighting::LinkGenerationError;

namespace {
typedef void (^ProceduralBlockWithItemArray)(NSArray<UIMenuElement*>*);
typedef void (^ProceduralBlockWithBlockWithItemArray)(
    ProceduralBlockWithItemArray);
}  // namespace

@implementation LinkToTextMediator {
  // The Browser's WebStateList.
  base::WeakPtr<WebStateList> _webStateList;
}

- (instancetype)initWithWebStateList:(WebStateList*)webStateList {
  if ((self = [super init])) {
    DCHECK(webStateList);
    _webStateList = webStateList->AsWeakPtr();
  }
  return self;
}

- (void)dealloc {
  if (_webStateList) {
    _webStateList = nullptr;
  }
}

- (BOOL)shouldOfferLinkToText {
  DCHECK(base::FeatureList::IsEnabled(kSharedHighlightingIOS));
  LinkToTextTabHelper* tabHelper = [self linkToTextTabHelper];
  if (!tabHelper) {
    return NO;
  }
  return tabHelper->ShouldOffer();
}

- (void)handleLinkToTextSelection {
  DCHECK(base::FeatureList::IsEnabled(kSharedHighlightingIOS));
  LinkToTextTabHelper* tabHelper = [self linkToTextTabHelper];
  if (!tabHelper) {
    return;
  }
  __weak __typeof(self) weakSelf = self;
  tabHelper->GetLinkToText(base::BindOnce(^(LinkToTextResponse* response) {
    [weakSelf receivedLinkToTextResponse:response];
  }));
}

- (void)receivedLinkToTextResponse:(LinkToTextResponse*)response {
  DCHECK(response);
  if (response.error.has_value()) {
    LinkGenerationError error = response.error.value();
    shared_highlighting::LogLinkGeneratedErrorUkmEvent(response.sourceID,
                                                       error);
    shared_highlighting::LogGenerateErrorLatency(response.latency);
    [self linkGenerationFailedWithError:error];
  } else {
    shared_highlighting::LogLinkGeneratedSuccessUkmEvent(response.sourceID);
    shared_highlighting::LogGenerateSuccessLatency(response.latency);
    [self shareLinkToText:response.payload];
  }
}

- (void)shareLinkToText:(LinkToTextPayload*)payload {
  DCHECK(payload);
  shared_highlighting::LogLinkGenerationStatus(
      shared_highlighting::LinkGenerationStatus::kSuccess);
  ShareHighlightCommand* command =
      [[ShareHighlightCommand alloc] initWithURL:payload.URL
                                           title:payload.title
                                    selectedText:payload.selectedText
                                      sourceView:payload.sourceView
                                      sourceRect:payload.sourceRect];
  [self.activityServiceHandler shareHighlight:command];
}

- (void)linkGenerationFailedWithError:(LinkGenerationError)error {
  if (!self.alertDelegate) {
    return;
  }
  shared_highlighting::LogLinkGenerationStatus(
      shared_highlighting::LinkGenerationStatus::kFailure);
  shared_highlighting::LogLinkGenerationErrorReason(error);

  __weak __typeof(self) weakSelf = self;
  EditMenuAlertDelegateAction* cancelAction =
      [[EditMenuAlertDelegateAction alloc]
          initWithTitle:l10n_util::GetNSString(IDS_APP_OK)
                 action:^{
                   base::RecordAction(base::UserMetricsAction(
                       "SharedHighlights.LinkGenerated.Error.OK"));
                 }
                  style:UIAlertActionStyleCancel
              preferred:NO];
  EditMenuAlertDelegateAction* translateAction =
      [[EditMenuAlertDelegateAction alloc]
          initWithTitle:l10n_util::GetNSString(IDS_IOS_SHARE_PAGE_BUTTON_LABEL)
                 action:^{
                   base::RecordAction(base::UserMetricsAction(
                       "SharedHighlights.LinkGenerated.Error.SharePage"));
                   [weakSelf.activityServiceHandler sharePage];
                 }
                  style:UIAlertActionStyleDefault
              preferred:NO];

  [self.alertDelegate
      showAlertWithTitle:l10n_util::GetNSString(
                             IDS_IOS_LINK_TO_TEXT_ERROR_TITLE)
                 message:l10n_util::GetNSString(
                             IDS_IOS_LINK_TO_TEXT_ERROR_DESCRIPTION)
                 actions:@[ cancelAction, translateAction ]];
}

- (LinkToTextTabHelper*)linkToTextTabHelper {
  web::WebState* webState =
      _webStateList ? _webStateList->GetActiveWebState() : nullptr;
  if (!webState) {
    return nullptr;
  }
  LinkToTextTabHelper* helper = LinkToTextTabHelper::FromWebState(webState);
  DCHECK(helper);
  return helper;
}

- (void)addItemWithCompletion:(ProceduralBlockWithItemArray)completion {
  if (![self shouldOfferLinkToText]) {
    completion(@[]);
    return;
  }

  __weak __typeof(self) weakSelf = self;
  NSString* title = l10n_util::GetNSString(IDS_IOS_SHARE_LINK_TO_TEXT);
  NSString* linkToTextId = @"chromecommand.linktotext";
  UIAction* action =
      [UIAction actionWithTitle:title
                          image:DefaultSymbolWithPointSize(
                                    kHighlighterSymbol, kSymbolActionPointSize)
                     identifier:linkToTextId
                        handler:^(UIAction* a) {
                          [weakSelf handleLinkToTextSelection];
                        }];
  completion(@[ action ]);
}

#pragma mark - EditMenuProvider

- (void)buildMenuWithBuilder:(id<UIMenuBuilder>)builder {
  NSString* linkToTextId = @"chromecommand.menu.linktotext";

  __weak __typeof(self) weakSelf = self;
  ProceduralBlockWithBlockWithItemArray provider =
      ^(ProceduralBlockWithItemArray completion) {
        [weakSelf addItemWithCompletion:completion];
      };
  // Use a deferred element so that the item is displayed depending on the text
  // selection and updated on selection change.
  UIDeferredMenuElement* deferredMenuElement =
      [UIDeferredMenuElement elementWithProvider:provider];

  UIMenu* linkToTextMenu = [UIMenu menuWithTitle:@""
                                           image:nil
                                      identifier:linkToTextId
                                         options:UIMenuOptionsDisplayInline
                                        children:@[ deferredMenuElement ]];
  [builder insertChildMenu:linkToTextMenu atEndOfMenuForIdentifier:UIMenuRoot];
}

@end