chromium/ios/chrome/browser/ntp/ui_bundled/feed_top_section/feed_top_section_coordinator.mm

// Copyright 2022 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/ntp/ui_bundled/feed_top_section/feed_top_section_coordinator.h"

#import "base/metrics/histogram_functions.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "components/search_engines/prepopulated_engines.h"
#import "components/search_engines/template_url.h"
#import "components/search_engines/template_url_prepopulate_data.h"
#import "components/search_engines/template_url_service.h"
#import "components/signin/public/base/signin_metrics.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_top_section/feed_top_section_mediator.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_top_section/feed_top_section_view_controller.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_top_section/notifications_promo_view_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_utils.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
#import "ios/chrome/browser/push_notification/model/push_notification_service.h"
#import "ios/chrome/browser/push_notification/model/push_notification_util.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.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/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/settings_commands.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/authentication/signin_presenter.h"
#import "ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h"
#import "ios/chrome/browser/ui/push_notification/notifications_opt_in_alert_coordinator.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util_mac.h"

using base::RecordAction;
using base::UmaHistogramEnumeration;
using base::UserMetricsAction;

@interface FeedTopSectionCoordinator () <
    SigninPresenter,
    NotificationsOptInAlertCoordinatorDelegate>

@property(nonatomic, strong) FeedTopSectionMediator* feedTopSectionMediator;
@property(nonatomic, strong)
    FeedTopSectionViewController* feedTopSectionViewController;
@property(nonatomic, strong) SigninPromoViewMediator* signinPromoMediator;

// Returns `YES` if the signin promo is visible in the NTP at the current scroll
// point.
@property(nonatomic, assign) BOOL isSigninPromoVisibleOnScreen;

// Returns `YES` if the signin promo exists on the current NTP.
@property(nonatomic, assign) BOOL isSignInPromoEnabled;

// Alert Coordinator used to display the notifications system prompt.
@property(nonatomic, strong)
    NotificationsOptInAlertCoordinator* optInAlertCoordinator;

@end

@implementation FeedTopSectionCoordinator

// Synthesized from ChromeCoordinator.
@synthesize viewController = _viewController;

- (void)start {
  DCHECK(self.NTPDelegate);
  self.feedTopSectionViewController =
      [[FeedTopSectionViewController alloc] init];
  _viewController = self.feedTopSectionViewController;

  ChromeBrowserState* browserState = self.browser->GetBrowserState();
  signin::IdentityManager* identityManager =
      IdentityManagerFactory::GetForBrowserState(browserState);
  AuthenticationService* authenticationService =
      AuthenticationServiceFactory::GetForBrowserState(browserState);
  syncer::SyncService* syncService =
      SyncServiceFactory::GetForBrowserState(browserState);

  self.feedTopSectionMediator = [[FeedTopSectionMediator alloc]
      initWithConsumer:self.feedTopSectionViewController
       identityManager:identityManager
           authService:authenticationService
           isIncognito:browserState->IsOffTheRecord()
           prefService:browserState->GetPrefs()];
  self.isSignInPromoEnabled =
      ShouldShowTopOfFeedSyncPromo() && authenticationService &&
      [self.NTPDelegate isSignInAllowed] &&
      !authenticationService->HasPrimaryIdentity(signin::ConsentLevel::kSignin);

  // If the user is signed out and signin is allowed, then start the top-of-feed
  // signin promo components.
  if (self.isSignInPromoEnabled) {
    ChromeAccountManagerService* accountManagerService =
        ChromeAccountManagerServiceFactory::GetForBrowserState(browserState);
    self.signinPromoMediator = [[SigninPromoViewMediator alloc]
        initWithAccountManagerService:accountManagerService
                          authService:AuthenticationServiceFactory::
                                          GetForBrowserState(browserState)
                          prefService:browserState->GetPrefs()
                          syncService:syncService
                          accessPoint:signin_metrics::AccessPoint::
                                          ACCESS_POINT_NTP_FEED_TOP_PROMO
                      signinPresenter:self
             accountSettingsPresenter:nil];

    self.signinPromoMediator.signinPromoAction =
        SigninPromoAction::kSigninWithNoDefaultIdentity;
    self.signinPromoMediator.consumer = self.feedTopSectionMediator;
    self.feedTopSectionMediator.signinPromoMediator = self.signinPromoMediator;
    self.feedTopSectionViewController.signinPromoDelegate =
        self.signinPromoMediator;
  }

  const TemplateURL* defaultSearchURLTemplate =
      ios::TemplateURLServiceFactory::GetForBrowserState(browserState)
          ->GetDefaultSearchProvider();

  bool isDefaultSearchEngine =
      defaultSearchURLTemplate && defaultSearchURLTemplate->prepopulate_id() ==
                                      TemplateURLPrepopulateData::google.id;
  self.feedTopSectionMediator.isDefaultSearchEngine = isDefaultSearchEngine;
  self.feedTopSectionMediator.presenter = self;
  self.feedTopSectionMediator.NTPDelegate = self.NTPDelegate;
  self.feedTopSectionViewController.delegate = self.feedTopSectionMediator;
  self.feedTopSectionViewController.feedTopSectionMutator =
      self.feedTopSectionMediator;
  self.feedTopSectionViewController.NTPDelegate = self.NTPDelegate;
  [self.feedTopSectionMediator setUp];
}

- (void)stop {
  _viewController = nil;
  [self.feedTopSectionMediator shutdown];
  [self.signinPromoMediator disconnect];
  self.signinPromoMediator.consumer = nil;
  self.signinPromoMediator = nil;
  self.feedTopSectionMediator = nil;
  self.feedTopSectionViewController = nil;
}

#pragma mark - Public

- (void)signinPromoHasChangedVisibility:(BOOL)visible {
  if (!self.isSignInPromoEnabled ||
      self.isSigninPromoVisibleOnScreen == visible) {
    return;
  }
  // Early return if the current promo State is SigninPromoViewState::kClosed
  // since the visibility shouldn't be updated if the Promo has been closed.
  // TODO(b/1494171): Update visibility methods to properlyhandle close actions.
  if (self.signinPromoMediator.signinPromoViewState ==
      SigninPromoViewState::kClosed) {
    return;
  }
  if (visible) {
    [self.signinPromoMediator signinPromoViewIsVisible];
  } else {
    [self.signinPromoMediator signinPromoViewIsHidden];
  }
  self.isSigninPromoVisibleOnScreen = visible;
}

#pragma mark - SigninPresenter

- (void)showSignin:(ShowSigninCommand*)command {
  id<ApplicationCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);
  [handler showSignin:command baseViewController:self.baseViewController];
}

#pragma mark - Setters

- (void)setIsSignInPromoEnabled:(BOOL)isSignInPromoEnabled {
  _isSignInPromoEnabled = isSignInPromoEnabled;
  CHECK(self.feedTopSectionMediator);
  self.feedTopSectionMediator.isSignInPromoEnabled = isSignInPromoEnabled;
}

#pragma mark - NotificationsAlertPresenter

- (void)presentPushNotificationPermissionAlert {
  [_optInAlertCoordinator stop];
  _optInAlertCoordinator = [[NotificationsOptInAlertCoordinator alloc]
      initWithBaseViewController:self.viewController
                         browser:self.browser];
  _optInAlertCoordinator.clientIds = std::vector{
      PushNotificationClientId::kContent, PushNotificationClientId::kSports};
  _optInAlertCoordinator.alertMessage = l10n_util::GetNSString(
      IDS_IOS_CONTENT_NOTIFICATIONS_SETTINGS_ALERT_MESSAGE);
  _optInAlertCoordinator.confirmationMessage =
      l10n_util::GetNSString(IDS_IOS_CONTENT_NOTIFICATION_SNACKBAR_TITLE);
  _optInAlertCoordinator.delegate = self;
  [_optInAlertCoordinator start];
}

#pragma mark - NotificationsOptInAlertCoordinatorDelegate

- (void)notificationsOptInAlertCoordinator:
            (NotificationsOptInAlertCoordinator*)alertCoordinator
                                    result:
                                        (NotificationsOptInAlertResult)result {
  CHECK_EQ(_optInAlertCoordinator, alertCoordinator);
  std::vector<PushNotificationClientId> clientIds =
      alertCoordinator.clientIds.value();
  [_optInAlertCoordinator stop];
  _optInAlertCoordinator = nil;
  switch (result) {
    case NotificationsOptInAlertResult::kPermissionDenied:
      RecordAction(UserMetricsAction(
          "ContentNotifications.Promo.TopOfFeed.Permission.Declined"));
      [self logHistogramForAction:ContentNotificationTopOfFeedPromoAction::
                                      kDecline];
      [self.feedTopSectionMediator updateFeedTopSectionWhenClosed];
      break;
    case NotificationsOptInAlertResult::kCanceled:
      [self logHistogramForEvent:ContentNotificationTopOfFeedPromoEvent::
                                     kCanceled];
      [self.feedTopSectionMediator updateFeedTopSectionWhenClosed];
      break;
    case NotificationsOptInAlertResult::kError:
      [self
          logHistogramForEvent:ContentNotificationTopOfFeedPromoEvent::kError];
      break;
    case NotificationsOptInAlertResult::kOpenedSettings:
      [self logHistogramForEvent:ContentNotificationTopOfFeedPromoEvent::
                                     kNotifActive];
      [self.feedTopSectionMediator updateFeedTopSectionWhenClosed];
      break;
    case NotificationsOptInAlertResult::kPermissionGranted:
      RecordAction(UserMetricsAction(
          "ContentNotifications.Promo.TopOfFeed.Permission.Accepted"));
      [self logHistogramForAction:ContentNotificationTopOfFeedPromoAction::
                                      kAccept];
      [self.feedTopSectionMediator updateFeedTopSectionWhenClosed];
      break;
  }
}

#pragma mark - Metrics

- (void)logHistogramForAction:(ContentNotificationTopOfFeedPromoAction)action {
  UmaHistogramEnumeration("ContentNotifications.Promo.TopOfFeed.Action",
                          action);
}

- (void)logHistogramForEvent:(ContentNotificationTopOfFeedPromoEvent)event {
  UmaHistogramEnumeration("ContentNotifications.Promo.TopOfFeed.Event", event);
}

@end