chromium/ios/chrome/browser/ui/push_notification/notifications_opt_in_mediator.mm

// Copyright 2024 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/ui/push_notification/notifications_opt_in_mediator.h"

#import "base/memory/raw_ptr.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "components/prefs/pref_service.h"
#import "components/signin/public/base/consent_level.h"
#import "ios/chrome/browser/ntp/model/set_up_list_item_type.h"
#import "ios/chrome/browser/ntp/model/set_up_list_prefs.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_settings_util.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/ui/push_notification/metrics.h"
#import "ios/chrome/browser/ui/push_notification/notifications_opt_in_consumer.h"
#import "ios/chrome/browser/ui/push_notification/notifications_opt_in_item_identifier.h"
#import "ios/chrome/browser/ui/push_notification/notifications_opt_in_presenter.h"
#import "ios/chrome/common/ui/promo_style/promo_style_view_controller_delegate.h"

@implementation NotificationsOptInMediator {
  // Used to check the user's primary identity and sign-in status.
  raw_ptr<AuthenticationService> _authenticationService;
  // Stores whether items are selected.
  std::map<NotificationsOptInItemIdentifier, BOOL> _selected;
}

- (instancetype)initWithAuthenticationService:
    (AuthenticationService*)authenticationService {
  if ((self = [super init])) {
    _authenticationService = authenticationService;
    _selected = {{NotificationsOptInItemIdentifier::kContent, NO},
                 {NotificationsOptInItemIdentifier::kTips, NO},
                 {NotificationsOptInItemIdentifier::kPriceTracking, NO}};
  }
  return self;
}

- (void)configureConsumer {
  std::string gaiaID = base::SysNSStringToUTF8([self primaryIdentity].gaiaID);
  for (auto [item, selection] : _selected) {
    selection = push_notification_settings::
        GetMobileNotificationPermissionStatusForMultipleClients(
            [self clientIDsForItem:item], gaiaID);
    [self.consumer setOptInItem:item enabled:selection];
  }
}

- (void)disableUserSelectionForItem:
    (NotificationsOptInItemIdentifier)itemIdentifier {
  _selected[itemIdentifier] = NO;
  [self.consumer setOptInItem:itemIdentifier enabled:NO];
}

#pragma mark - PromoStyleViewControllerDelegate

- (void)didTapPrimaryActionButton {
  std::vector<PushNotificationClientId> selectedClientIds;
  std::vector<PushNotificationClientId> deselectedClientIds;
  std::string gaiaID = base::SysNSStringToUTF8([self primaryIdentity].gaiaID);

  for (auto [item, selection] : _selected) {
    std::vector<PushNotificationClientId> clientIDs =
        [self clientIDsForItem:item];
    BOOL enabled = push_notification_settings::
        GetMobileNotificationPermissionStatusForMultipleClients(clientIDs,
                                                                gaiaID);
    // Only add the clientId if there has been a change from the original opt-in
    // status.
    if (selection && !enabled) {
      selectedClientIds.insert(selectedClientIds.end(), clientIDs.begin(),
                               clientIDs.end());
    } else if (!selection && enabled) {
      deselectedClientIds.insert(deselectedClientIds.end(), clientIDs.begin(),
                                 clientIDs.end());
    }
  }

  [self disableNotifications:deselectedClientIds];
  [self.presenter presentNotificationsAlertForClientIds:selectedClientIds];
  base::UmaHistogramEnumeration(
      kNotificationsOptInPromptActionHistogram,
      NotificationsOptInPromptActionType::kEnableNotificationsTapped);
}

- (void)didTapSecondaryActionButton {
  PrefService* localState = GetApplicationContext()->GetLocalState();
  set_up_list_prefs::MarkItemComplete(localState,
                                      SetUpListItemType::kNotifications);
  base::UmaHistogramEnumeration(
      kNotificationsOptInPromptActionHistogram,
      NotificationsOptInPromptActionType::kNoThanksTapped);
  [self.presenter dismiss];
}

#pragma mark - NotificationsOptInViewControllerDelegate

- (void)selectionChangedForItemType:
            (NotificationsOptInItemIdentifier)itemIdentifier
                           selected:(BOOL)selected {
  _selected[itemIdentifier] = selected;
  // Present the sign in view if Content is selected and user is not signed in.
  if (itemIdentifier == kContent && selected && ![self primaryIdentity]) {
    [self.presenter presentSignIn];
  }
}

#pragma mark - Private

// Returns the user's primary identity, or nil if not signed in.
- (id<SystemIdentity>)primaryIdentity {
  return _authenticationService->GetPrimaryIdentity(
      signin::ConsentLevel::kSignin);
}

- (std::vector<PushNotificationClientId>)clientIDsForItem:
    (NotificationsOptInItemIdentifier)item {
  switch (item) {
    case kContent:
      return {PushNotificationClientId::kContent,
              PushNotificationClientId::kSports};
    case kTips:
      return {PushNotificationClientId::kTips};
    case kPriceTracking:
      return {PushNotificationClientId::kCommerce};
  }
}

// Disables notifications in prefs for the clients in `clientIds`.
- (void)disableNotifications:(std::vector<PushNotificationClientId>)clientIds {
  NSString* gaiaID = [self primaryIdentity].gaiaID;
  for (PushNotificationClientId clientId : clientIds) {
    GetApplicationContext()->GetPushNotificationService()->SetPreference(
        gaiaID, clientId, false);
  }
}

@end