chromium/ios/chrome/browser/ui/settings/notifications/notifications_mediator.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/ui/settings/notifications/notifications_mediator.h"

#import "base/apple/foundation_util.h"
#import "base/notreached.h"
#import "base/strings/sys_string_conversions.h"
#import "components/prefs/pref_service.h"
#import "ios/chrome/browser/push_notification/model/push_notification_account_context_manager.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
#import "ios/chrome/browser/push_notification/model/push_notification_profile_service.h"
#import "ios/chrome/browser/push_notification/model/push_notification_profile_service_factory.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/push_notification/model/push_notification_util.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/list_model/list_model.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_icon_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_link_header_footer_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_multi_detail_text_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_item.h"
#import "ios/chrome/browser/ui/push_notification/notifications_alert_presenter.h"
#import "ios/chrome/browser/ui/settings/notifications/notifications_constants.h"
#import "ios/chrome/browser/ui/settings/notifications/notifications_consumer.h"
#import "ios/chrome/browser/ui/settings/notifications/notifications_item_identifier.h"
#import "ios/chrome/browser/ui/settings/notifications/notifications_navigation_commands.h"
#import "ios/chrome/browser/ui/settings/notifications/notifications_settings_observer.h"
#import "ios/chrome/browser/ui/settings/notifications/tips_notifications_alert_presenter.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 "ui/base/l10n/l10n_util.h"

@interface NotificationsMediator ()

// All the items for the price notifications section.
@property(nonatomic, strong, readonly) TableViewItem* priceTrackingItem;
// Items for the Content Notifications settings.
@property(nonatomic, strong, readonly) TableViewItem* contentNotificationsItem;
// Items for the Tips Notifications settings.
@property(nonatomic, strong, readonly)
    TableViewSwitchItem* tipsNotificationsItem;
// Item for the Tips Notifications footer.
@property(nonatomic, strong)
    TableViewLinkHeaderFooterItem* tipsNotificationsFooterItem;
// Pref Service object.
@property(nonatomic, assign) PrefService* prefService;

@end

@implementation NotificationsMediator {
  // Identity object that contains the user's account details.
  std::string _gaiaID;
}

@synthesize priceTrackingItem = _priceTrackingItem;
@synthesize contentNotificationsItem = _contentNotificationsItem;
@synthesize tipsNotificationsItem = _tipsNotificationsItem;

- (instancetype)initWithPrefService:(PrefService*)prefs
                             gaiaID:(const std::string&)gaiaID {
  self = [super init];
  if (self) {
    DCHECK(prefs);
    _prefService = prefs;
    _gaiaID = gaiaID;
  }

  return self;
}

#pragma mark - Public

- (void)deniedPermissionsForClientIds:
    (std::vector<PushNotificationClientId>)clientIds {
  for (PushNotificationClientId clientID : clientIds) {
    [self switchItemForClientId:clientID].on = push_notification_settings::
        GetMobileNotificationPermissionStatusForClient(clientID, _gaiaID);
  }
  [self.consumer reloadData];
}

#pragma mark - Properties

- (TableViewItem*)priceTrackingItem {
  if (!_priceTrackingItem) {
    if (IsIOSTipsNotificationsEnabled()) {
      _priceTrackingItem = [self
               detailItemWithType:NotificationsItemIdentifier::
                                      ItemIdentifierPriceTracking
                             text:
                                 l10n_util::GetNSString(
                                     IDS_IOS_NOTIFICATIONS_OPT_IN_PRICE_TRACKING_TOGGLE_TITLE)
               trailingDetailText:nil
                leadingDetailText:
                    l10n_util::GetNSString(
                        IDS_IOS_NOTIFICATIONS_OPT_IN_PRICE_TRACKING_TOGGLE_MESSAGE)
          accessibilityIdentifier:kSettingsNotificationsPriceTrackingCellId];
    } else {
      _priceTrackingItem = [self
               detailItemWithType:NotificationsItemIdentifier::
                                      ItemIdentifierPriceTracking
                             text:
                                 l10n_util::GetNSString(
                                     IDS_IOS_PRICE_NOTIFICATIONS_PRICE_TRACKING_TITLE)
                       detailText:nil
                           symbol:CustomSettingsRootSymbol(kDownTrendSymbol)
                       symbolTint:UIColor.whiteColor
            symbolBackgroundColor:[UIColor colorNamed:kPink500Color]
          accessibilityIdentifier:kSettingsNotificationsPriceTrackingCellId];
    }
    [self updateDetailTextForItem:_priceTrackingItem
                    withClientIDs:{PushNotificationClientId::kCommerce}];
  }
  return _priceTrackingItem;
}

- (TableViewItem*)contentNotificationsItem {
  if (!_contentNotificationsItem) {
    if (IsIOSTipsNotificationsEnabled()) {
      _contentNotificationsItem = [self
               detailItemWithType:NotificationsItemIdentifier::
                                      ItemIdentifierContent
                             text:
                                 l10n_util::GetNSString(
                                     IDS_IOS_CONTENT_NOTIFICATIONS_CONTENT_SETTINGS_TOGGLE_TITLE)
               trailingDetailText:nil
                leadingDetailText:
                    l10n_util::GetNSString(
                        IDS_IOS_CONTENT_NOTIFICATIONS_CONTENT_SETTINGS_FOOTER_TEXT)
          accessibilityIdentifier:kSettingsNotificationsContentCellId];
    } else {
      _contentNotificationsItem = [self
               detailItemWithType:NotificationsItemIdentifier::
                                      ItemIdentifierContent
                             text:
                                 l10n_util::GetNSString(
                                     IDS_IOS_CONTENT_NOTIFICATIONS_CONTENT_SETTINGS_TOGGLE_TITLE)
                       detailText:nil
                           symbol:DefaultSettingsRootSymbol(kNewspaperSFSymbol)
                       symbolTint:UIColor.whiteColor
            symbolBackgroundColor:[UIColor colorNamed:kPink500Color]
          accessibilityIdentifier:kSettingsNotificationsContentCellId];
    }
    [self updateDetailTextForItem:_contentNotificationsItem
                    withClientIDs:{PushNotificationClientId::kContent,
                                   PushNotificationClientId::kSports}];
  }
  return _contentNotificationsItem;
}

- (TableViewSwitchItem*)tipsNotificationsItem {
  if (!_tipsNotificationsItem) {
#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
    UIImage* image = MakeSymbolMulticolor(
        CustomSettingsRootSymbol(kMulticolorChromeballSymbol));
#else
    UIImage* image = CustomSettingsRootSymbol(kChromeProductSymbol);
#endif  // BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
    _tipsNotificationsItem = [self
             switchItemWithType:NotificationsItemIdentifier::ItemIdentifierTips
                           text:l10n_util::GetNSString(
                                    IDS_IOS_SET_UP_LIST_TIPS_TITLE)
                     detailText:l10n_util::GetNSString(
                                    IDS_IOS_TIPS_NOTIFICATION_SETTINGS_FOOTER)
                         symbol:image
                     symbolTint:nil
          symbolBackgroundColor:nil
              symbolBorderWidth:1
        accessibilityIdentifier:kSettingsNotificationsContentCellId];
    _tipsNotificationsItem.on = push_notification_settings::
        GetMobileNotificationPermissionStatusForClient(
            PushNotificationClientId::kTips, _gaiaID);
  }
  return _tipsNotificationsItem;
}

- (TableViewLinkHeaderFooterItem*)tipsNotificationsFooterItem {
  if (!_tipsNotificationsFooterItem) {
    _tipsNotificationsFooterItem = [[TableViewLinkHeaderFooterItem alloc]
        initWithType:NotificationsItemIdentifier::
                         ItemIdentifierTipsNotificationsFooter];
    _tipsNotificationsFooterItem.text =
        l10n_util::GetNSString(IDS_IOS_TIPS_NOTIFICATION_SETTINGS_FOOTER);
  }

  return _tipsNotificationsFooterItem;
}

- (void)setConsumer:(id<NotificationsConsumer>)consumer {
  if (_consumer == consumer) {
    return;
  }
  _consumer = consumer;
  [_consumer setPriceTrackingItem:self.priceTrackingItem];
  [_consumer setContentNotificationsItem:self.contentNotificationsItem];
  if (IsIOSTipsNotificationsEnabled()) {
    [_consumer setTipsNotificationsItem:self.tipsNotificationsItem];
    [_consumer setTipsNotificationsFooterItem:self.tipsNotificationsFooterItem];
  }
}

#pragma mark - Private methods

// Creates item with two text labels and icon image.
- (TableViewDetailIconItem*)detailItemWithType:(NSInteger)type
                                          text:(NSString*)text
                                    detailText:(NSString*)detailText
                                        symbol:(UIImage*)symbol
                                    symbolTint:(UIColor*)tint
                         symbolBackgroundColor:(UIColor*)backgroundColor
                       accessibilityIdentifier:
                           (NSString*)accessibilityIdentifier {
  TableViewDetailIconItem* detailItem =
      [[TableViewDetailIconItem alloc] initWithType:type];
  detailItem.text = text;
  detailItem.detailText = detailText;
  detailItem.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  detailItem.accessibilityTraits |= UIAccessibilityTraitButton;
  detailItem.accessibilityIdentifier = accessibilityIdentifier;
  detailItem.iconImage = symbol;
  detailItem.iconTintColor = tint;
  detailItem.iconCornerRadius = kColorfulBackgroundSymbolCornerRadius;
  detailItem.iconBackgroundColor = backgroundColor;

  return detailItem;
}

- (TableViewSwitchItem*)switchItemWithType:(NSInteger)type
                                      text:(NSString*)text
                                detailText:(NSString*)detailText
                                    symbol:(UIImage*)symbol
                                symbolTint:(UIColor*)tint
                     symbolBackgroundColor:(UIColor*)backgroundColor
                         symbolBorderWidth:(CGFloat)borderWidth
                   accessibilityIdentifier:(NSString*)accessibilityIdentifier {
  TableViewSwitchItem* switchItem =
      [[TableViewSwitchItem alloc] initWithType:type];
  switchItem.text = text;
  switchItem.accessibilityIdentifier = accessibilityIdentifier;
  if (IsIOSTipsNotificationsEnabled()) {
    switchItem.detailText = detailText;
  } else {
    switchItem.iconImage = symbol;
    switchItem.iconTintColor = tint;
    switchItem.iconCornerRadius = kColorfulBackgroundSymbolCornerRadius;
    switchItem.iconBackgroundColor = backgroundColor;
    switchItem.iconBorderWidth = borderWidth;
  }

  return switchItem;
}

// Creates item with three text labels.
- (TableViewMultiDetailTextItem*)
         detailItemWithType:(NSInteger)type
                       text:(NSString*)text
         trailingDetailText:(NSString*)trailingDetailText
          leadingDetailText:(NSString*)leadingDetailText
    accessibilityIdentifier:(NSString*)accessibilityIdentifier {
  TableViewMultiDetailTextItem* detailItem =
      [[TableViewMultiDetailTextItem alloc] initWithType:type];
  detailItem.text = text;
  detailItem.trailingDetailText = trailingDetailText;
  detailItem.leadingDetailText = leadingDetailText;
  detailItem.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  detailItem.accessibilityTraits |= UIAccessibilityTraitButton;
  detailItem.accessibilityIdentifier = accessibilityIdentifier;

  return detailItem;
}

// Updates the detail text for the TableViewItem located in
// PriceNotificationsTableViewController on the previous screen to read either
// 'On/Off' to match the change to the client's push notification permission
// state.
- (void)updateDetailTextForItem:(TableViewItem*)item
                  withClientIDs:
                      (std::vector<PushNotificationClientId>)clientIDs {
  DCHECK(item);
  push_notification_settings::ClientPermissionState permissionState;
  permissionState =
      push_notification_settings::GetClientPermissionStateForMultipleClients(
          clientIDs, _gaiaID, _prefService);
  NSString* detailText = nil;
  if (permissionState ==
      push_notification_settings::ClientPermissionState::ENABLED) {
    detailText = l10n_util::GetNSString(IDS_IOS_SETTING_ON);
  } else if (permissionState ==
             push_notification_settings::ClientPermissionState::DISABLED) {
    detailText = l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
  }

  if (IsIOSTipsNotificationsEnabled()) {
    TableViewMultiDetailTextItem* detailItem =
        base::apple::ObjCCastStrict<TableViewMultiDetailTextItem>(item);
    detailItem.trailingDetailText = detailText;
    [self.consumer reconfigureCellsForItems:@[ detailItem ]];
  } else {
    TableViewDetailIconItem* iconItem =
        base::apple::ObjCCastStrict<TableViewDetailIconItem>(item);
    iconItem.detailText = detailText;
    [self.consumer reconfigureCellsForItems:@[ iconItem ]];
  }
}

#pragma mark - NotificationsViewControllerDelegate

- (void)didToggleSwitchItem:(TableViewSwitchItem*)item withValue:(BOOL)value {
  NotificationsItemIdentifier itemIdentifier =
      static_cast<NotificationsItemIdentifier>(item.type);
  switch (itemIdentifier) {
    case ItemIdentifierTips: {
      if (value) {
        [self.presenter presentTipsNotificationPermissionAlert];
      } else {
        [self disablePreferenceFor:PushNotificationClientId::kTips];
        self.tipsNotificationsItem.on = push_notification_settings::
            GetMobileNotificationPermissionStatusForClient(
                PushNotificationClientId::kTips, _gaiaID);
      }
      break;
    }
    default:
      // Not a switch.
      NOTREACHED_IN_MIGRATION();
      break;
  }
}

- (void)didSelectItem:(TableViewItem*)item {
  NotificationsItemIdentifier type =
      static_cast<NotificationsItemIdentifier>(item.type);
  switch (type) {
    case ItemIdentifierPriceTracking:
      [self.handler showTrackingPrice];
      break;
    case ItemIdentifierContent:
      [self.handler showContent];
      break;
    case ItemIdentifierTips:
      break;
    default:
      NOTREACHED_IN_MIGRATION();
      break;
  }
}

#pragma mark - NotificationsSettingsObserverDelegate

- (void)notificationsSettingsDidChangeForClient:
    (PushNotificationClientId)clientID {
  switch (clientID) {
    case PushNotificationClientId::kCommerce: {
      [self updateDetailTextForItem:_priceTrackingItem
                      withClientIDs:{clientID}];
      break;
    }
    case PushNotificationClientId::kContent:
    case PushNotificationClientId::kSports: {
      [self updateDetailTextForItem:_contentNotificationsItem
                      withClientIDs:{PushNotificationClientId::kSports,
                                     PushNotificationClientId::kContent}];
      break;
    }
    case PushNotificationClientId::kTips: {
      self.tipsNotificationsItem.on = push_notification_settings::
          GetMobileNotificationPermissionStatusForClient(
              PushNotificationClientId::kTips, _gaiaID);
      [self.consumer reconfigureCellsForItems:@[ self.tipsNotificationsItem ]];
      break;
    }
    case PushNotificationClientId::kSafetyCheck:
      // TODO(crbug.com/347975024): Integrate Safety Check Notifications with
      // notifications settings UI.
      break;
  }
}

#pragma mark - private

// Updates the current user's permission preference for the given `client_id`.
- (void)disablePreferenceFor:(PushNotificationClientId)clientID {
  PushNotificationService* service =
      GetApplicationContext()->GetPushNotificationService();
  service->SetPreference(base::SysUTF8ToNSString(_gaiaID), clientID, false);
}

// Returns the TableViewSwitchItem for the given `clientId`.
- (TableViewSwitchItem*)switchItemForClientId:
    (PushNotificationClientId)clientId {
  switch (clientId) {
    case PushNotificationClientId::kTips:
      return _tipsNotificationsItem;
    case PushNotificationClientId::kCommerce:
    case PushNotificationClientId::kSafetyCheck:
    case PushNotificationClientId::kContent:
    case PushNotificationClientId::kSports:
      // Not a switch.
      NOTREACHED();
  }
}

@end