chromium/ios/chrome/browser/ui/settings/settings_table_view_controller.mm

// Copyright 2015 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/settings_table_view_controller.h"

#import <memory>

#import "base/apple/foundation_util.h"
#import "base/feature_list.h"
#import "base/memory/raw_ptr.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 "base/task/sequenced_task_runner.h"
#import "build/branding_buildflags.h"
#import "components/autofill/core/common/autofill_prefs.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/keyed_service/core/service_access_type.h"
#import "components/password_manager/core/browser/manage_passwords_referrer.h"
#import "components/password_manager/core/browser/ui/password_check_referrer.h"
#import "components/password_manager/core/common/password_manager_pref_names.h"
#import "components/plus_addresses/features.h"
#import "components/prefs/ios/pref_observer_bridge.h"
#import "components/prefs/pref_member.h"
#import "components/prefs/pref_service.h"
#import "components/safe_browsing/core/common/features.h"
#import "components/safe_browsing/core/common/safe_browsing_prefs.h"
#import "components/search_engines/search_engines_pref_names.h"
#import "components/search_engines/util.h"
#import "components/signin/public/base/signin_metrics.h"
#import "components/signin/public/base/signin_pref_names.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
#import "components/strings/grit/components_strings.h"
#import "components/sync/service/sync_service.h"
#import "components/sync/service/sync_user_settings.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/browser/bubble/ui_bundled/bubble_constants.h"
#import "ios/chrome/browser/bubble/ui_bundled/bubble_view_controller_presenter.h"
#import "ios/chrome/browser/commerce/model/push_notification/push_notification_feature.h"
#import "ios/chrome/browser/content_notification/model/content_notification_util.h"
#import "ios/chrome/browser/default_browser/model/utils.h"
#import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
#import "ios/chrome/browser/language/model/language_model_manager_factory.h"
#import "ios/chrome/browser/net/model/crurl.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_util.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_feature.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_password_check_manager.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_password_check_manager_factory.h"
#import "ios/chrome/browser/passwords/model/password_check_observer_bridge.h"
#import "ios/chrome/browser/passwords/model/password_checkup_utils.h"
#import "ios/chrome/browser/photos/model/photos_service.h"
#import "ios/chrome/browser/photos/model/photos_service_factory.h"
#import "ios/chrome/browser/push_notification/model/push_notification_settings_util.h"
#import "ios/chrome/browser/search_engines/model/search_engine_observer_bridge.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/settings/model/sync/utils/identity_error_util.h"
#import "ios/chrome/browser/settings/model/sync/utils/sync_state.h"
#import "ios/chrome/browser/settings/model/sync/utils/sync_util.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/prefs/pref_backed_boolean.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/utils/first_run_util.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/popup_menu_commands.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/chrome/browser/shared/public/commands/show_signin_command.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/public/features/system_flags.h"
#import "ios/chrome/browser/shared/ui/symbols/buildflags.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_image_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_cell.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_cell.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_model.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.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/chrome_account_manager_service_observer_bridge.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/signin/model/system_identity.h"
#import "ios/chrome/browser/sync/model/enterprise_utils.h"
#import "ios/chrome/browser/sync/model/sync_observer_bridge.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/tabs/model/inactive_tabs/features.h"
#import "ios/chrome/browser/ui/authentication/cells/table_view_account_item.h"
#import "ios/chrome/browser/ui/authentication/signin/signin_utils.h"
#import "ios/chrome/browser/ui/authentication/signin_presenter.h"
#import "ios/chrome/browser/ui/settings/about_chrome_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/address_bar_preference/address_bar_preference_coordinator.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_credit_card_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/bandwidth/bandwidth_management_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/cells/account_sign_in_item.h"
#import "ios/chrome/browser/ui/settings/cells/enhanced_safe_browsing_inline_promo_item.h"
#import "ios/chrome/browser/ui/settings/cells/settings_check_item.h"
#import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_item.h"
#import "ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/default_browser/default_browser_settings_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/downloads/downloads_settings_coordinator.h"
#import "ios/chrome/browser/ui/settings/downloads/downloads_settings_coordinator_delegate.h"
#import "ios/chrome/browser/ui/settings/elements/enterprise_info_popover_view_controller.h"
#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_accounts/accounts_coordinator.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_constants.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.h"
#import "ios/chrome/browser/ui/settings/language/language_settings_mediator.h"
#import "ios/chrome/browser/ui/settings/language/language_settings_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/multi_identity/switch_profile_settings_coordinator.h"
#import "ios/chrome/browser/ui/settings/notifications/notifications_coordinator.h"
#import "ios/chrome/browser/ui/settings/notifications/notifications_settings_observer.h"
#import "ios/chrome/browser/ui/settings/password/passwords_coordinator.h"
#import "ios/chrome/browser/ui/settings/privacy/privacy_coordinator.h"
#import "ios/chrome/browser/ui/settings/privacy/safe_browsing/enhanced_safe_browsing_inline_promo_delegate.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_constants.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_utils.h"
#import "ios/chrome/browser/ui/settings/search_engine_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
#import "ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.h"
#import "ios/chrome/browser/ui/settings/tabs/tabs_settings_coordinator.h"
#import "ios/chrome/browser/ui/settings/voice_search_table_view_controller.h"
#import "ios/chrome/browser/upgrade/model/upgrade_utils.h"
#import "ios/chrome/browser/voice/model/speech_input_locale_config.h"
#import "ios/chrome/browser/voice/model/voice_search_prefs.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/signin/signin_resources_api.h"
#import "net/base/apple/url_conversions.h"
#import "ui/base/l10n/l10n_util_mac.h"

namespace {

// The maximum number of time a "new" IPH badge is shown.
const NSInteger kMaxShowCountNewIPHBadge = 3;
// The amount of time an install is considered as fresh. Don't show the "new"
// IPH badge on fresh installs.
const base::TimeDelta kFreshInstallTimeDelta = base::Days(1);

#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
NSString* kDevViewSourceKey = @"DevViewSource";
#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)

// Returns the branded version of the Google Services symbol.
UIImage* GetBrandedGoogleServicesSymbol() {
#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
  return CustomSettingsRootMulticolorSymbol(kGoogleIconSymbol);
#else
  return DefaultSettingsRootSymbol(@"gearshape.2");
#endif
}

// Struct used to count and store the number of active Enhanced Safe Browsing
// promos, as the FET does not support showing multiple badges for the same FET
// feature at the same time.
struct EnhancedSafeBrowsingActivePromoData
    : public base::SupportsUserData::Data {
  // The number of active menus.
  int active_promos = 0;

  // Key to use for this type in SupportsUserData
  static constexpr char key[] = "EnhancedSafeBrowsingActivePromoData";
};

}  // namespace

#pragma mark - SettingsTableViewController

@interface SettingsTableViewController () <
    BooleanObserver,
    ChromeAccountManagerServiceObserver,
    DownloadsSettingsCoordinatorDelegate,
    EnhancedSafeBrowsingInlinePromoDelegate,
    GoogleServicesSettingsCoordinatorDelegate,
    IdentityManagerObserverBridgeDelegate,
    ManageSyncSettingsCoordinatorDelegate,
    NotificationsSettingsObserverDelegate,
    PasswordCheckObserver,
    PasswordsCoordinatorDelegate,
    PopoverLabelViewControllerDelegate,
    PrefObserverDelegate,
    NotificationsCoordinatorDelegate,
    PrivacyCoordinatorDelegate,
    SafetyCheckCoordinatorDelegate,
    SearchEngineObserving,
    SyncObserverModelBridge> {
  // The browser where the settings are being displayed.
  raw_ptr<Browser> _browser;
  // The browser state for `_browser`. Never off the record.
  raw_ptr<ChromeBrowserState> _browserState;  // weak
  // Bridge for TemplateURLServiceObserver.
  std::unique_ptr<SearchEngineObserverBridge> _searchEngineObserverBridge;
  std::unique_ptr<signin::IdentityManagerObserverBridge>
      _identityObserverBridge;
  std::unique_ptr<SyncObserverBridge> _syncObserverBridge;
  // A helper object for observing changes in the password check status
  // and changes to the compromised credentials list.
  std::unique_ptr<PasswordCheckObserverBridge> _passwordCheckObserver;
  // The service responsible for password check feature.
  scoped_refptr<IOSChromePasswordCheckManager> _passwordCheckManager;
  // Whether the impression of the Signin button has already been recorded.
  BOOL _hasRecordedSigninImpression;
  // PrefBackedBoolean for ShowMemoryDebugTools switch.
  PrefBackedBoolean* _showMemoryDebugToolsEnabled;
  // PrefBackedBoolean for ArticlesForYou switch.
  PrefBackedBoolean* _articlesEnabled;
  // Preference value for the "Allow Chrome Sign-in" feature.
  PrefBackedBoolean* _allowChromeSigninPreference;
  // PrefBackedBoolean for ArticlesForYou switch enabling.
  PrefBackedBoolean* _contentSuggestionPolicyEnabled;
  // PrefBackedBoolean that overrides ArticlesForYou switch for supervised
  // users.
  PrefBackedBoolean* _contentSuggestionForSupervisedUsersEnabled;
  // PrefBackedBoolean for BottomOmnibox switch.
  PrefBackedBoolean* _bottomOmniboxEnabled;
  // The item related to the switch for the show suggestions setting.
  TableViewSwitchItem* _showMemoryDebugToolsItem;
  // The item related to the safety check.
  SettingsCheckItem* _safetyCheckItem;

  GoogleServicesSettingsCoordinator* _googleServicesSettingsCoordinator;
  ManageSyncSettingsCoordinator* _manageSyncSettingsCoordinator;

  // notifications coordinator.
  NotificationsCoordinator* _notificationsCoordinator;

  // Privacy coordinator.
  PrivacyCoordinator* _privacyCoordinator;

  // Safety Check coordinator.
  SafetyCheckCoordinator* _safetyCheckCoordinator;

  // Passwords coordinator.
  PasswordsCoordinator* _passwordsCoordinator;

  // Accounts coordinator.
  AccountsCoordinator* _accountsCoordinator;

  // Feature engagement tracker for the signin IPH.
  raw_ptr<feature_engagement::Tracker> _featureEngagementTracker;
  // Presenter for the signin IPH.
  BubbleViewControllerPresenter* _bubblePresenter;

  // Identity object and observer used for Account Item refresh.
  id<SystemIdentity> _identity;
  std::unique_ptr<ChromeAccountManagerServiceObserverBridge>
      _accountManagerServiceObserver;

  // PrefMember for voice locale code.
  StringPrefMember _voiceLocaleCode;
  // Pref observer to track changes to prefs.
  std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
  // TODO(crbug.com/40492152): Refactor PrefObserverBridge so it owns the
  // PrefChangeRegistrar.
  // Registrar for pref changes notifications.
  PrefChangeRegistrar _prefChangeRegistrar;

  // Updatable Items.
  TableViewDetailIconItem* _voiceSearchDetailItem;
  TableViewDetailIconItem* _defaultSearchEngineItem;
  TableViewInfoButtonItem* _managedSearchEngineItem;
  TableViewDetailIconItem* _addressBarPreferenceItem;
  TableViewDetailIconItem* _passwordsDetailItem;
  TableViewDetailIconItem* _autoFillProfileDetailItem;
  TableViewDetailIconItem* _autoFillCreditCardDetailItem;
  TableViewDetailIconItem* _notificationsItem;
  TableViewDetailIconItem* _plusAddressesItem;
  TableViewItem* _syncItem;

  // Whether Settings have been dismissed.
  BOOL _settingsAreDismissed;

  // Tabs settings coordinator.
  TabsSettingsCoordinator* _tabsCoordinator;

  // Switch profile coordinator.
  SwitchProfileSettingsCoordinator* _switchProfileCoordinator;

  // Address bar setting coordinator.
  AddressBarPreferenceCoordinator* _addressBarPreferenceCoordinator;

  // Downloads settings coordinator.
  DownloadsSettingsCoordinator* _downloadsSettingsCoordinator;
}

// The item related to the switch for the show feed settings.
@property(nonatomic, strong, readonly) TableViewSwitchItem* feedSettingsItem;
// The item related to the enterprise managed show feed settings.
@property(nonatomic, strong, readonly)
    TableViewInfoButtonItem* managedFeedSettingsItem;

// YES if the default browser settings row is currently showing the notification
// dot.
@property(nonatomic, assign) BOOL showingDefaultBrowserNotificationDot;

// YES if the sign-in is in progress.
@property(nonatomic, assign) BOOL isSigninInProgress;

// Account manager service to retrieve Chrome identities.
@property(nonatomic, assign) ChromeAccountManagerService* accountManagerService;

// An observer that tracks whether push notification permission settings have
// been modified.
@property(nonatomic, strong)
    NotificationsSettingsObserver* notificationsObserver;

@end

@implementation SettingsTableViewController
@synthesize managedFeedSettingsItem = _managedFeedSettingsItem;
@synthesize feedSettingsItem = _feedSettingsItem;

#pragma mark Initialization

- (instancetype)initWithBrowser:(Browser*)browser
       hasDefaultBrowserBlueDot:(BOOL)hasDefaultBrowserBlueDot {
  DCHECK(browser);
  DCHECK(!browser->GetBrowserState()->IsOffTheRecord());

  self = [super initWithStyle:ChromeTableViewStyle()];
  if (self) {
    _browser = browser;
    _browserState = _browser->GetBrowserState();
    self.showingDefaultBrowserNotificationDot = hasDefaultBrowserBlueDot;
    self.title = l10n_util::GetNSStringWithFixup(IDS_IOS_SETTINGS_TITLE);
    _searchEngineObserverBridge.reset(new SearchEngineObserverBridge(
        self,
        ios::TemplateURLServiceFactory::GetForBrowserState(_browserState)));
    signin::IdentityManager* identityManager =
        IdentityManagerFactory::GetForBrowserState(_browserState);
    _accountManagerService =
        ChromeAccountManagerServiceFactory::GetForBrowserState(_browserState);
    // It is expected that `identityManager` should never be nil except in
    // tests. In that case, the tests should be fixed.
    DCHECK(identityManager);
    _identityObserverBridge.reset(
        new signin::IdentityManagerObserverBridge(identityManager, self));
    syncer::SyncService* syncService =
        SyncServiceFactory::GetForBrowserState(_browserState);
    _syncObserverBridge.reset(new SyncObserverBridge(self, syncService));

    PrefService* localState = GetApplicationContext()->GetLocalState();
    _showMemoryDebugToolsEnabled = [[PrefBackedBoolean alloc]
        initWithPrefService:localState
                   prefName:prefs::kShowMemoryDebuggingTools];
    [_showMemoryDebugToolsEnabled setObserver:self];

    AuthenticationService* authService =
        AuthenticationServiceFactory::GetForBrowserState(_browserState);
    _identity = authService->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
    _accountManagerServiceObserver.reset(
        new ChromeAccountManagerServiceObserverBridge(self,
                                                      _accountManagerService));
    _featureEngagementTracker =
        feature_engagement::TrackerFactory::GetForBrowserState(_browserState);

    PrefService* prefService = _browserState->GetPrefs();

    _passwordCheckManager =
        IOSChromePasswordCheckManagerFactory::GetForBrowserState(_browserState);
    _passwordCheckObserver = std::make_unique<PasswordCheckObserverBridge>(
        self, _passwordCheckManager.get());

    _allowChromeSigninPreference =
        [[PrefBackedBoolean alloc] initWithPrefService:prefService
                                              prefName:prefs::kSigninAllowed];
    _allowChromeSigninPreference.observer = self;

    _articlesEnabled = [[PrefBackedBoolean alloc]
        initWithPrefService:prefService
                   prefName:prefs::kArticlesForYouEnabled];
    [_articlesEnabled setObserver:self];

    _bottomOmniboxEnabled =
        [[PrefBackedBoolean alloc] initWithPrefService:localState
                                              prefName:prefs::kBottomOmnibox];
    [_bottomOmniboxEnabled setObserver:self];

    _contentSuggestionPolicyEnabled = [[PrefBackedBoolean alloc]
        initWithPrefService:prefService
                   prefName:prefs::kNTPContentSuggestionsEnabled];
    [_contentSuggestionPolicyEnabled setObserver:self];

    _contentSuggestionForSupervisedUsersEnabled = [[PrefBackedBoolean alloc]
        initWithPrefService:prefService
                   prefName:prefs::
                                kNTPContentSuggestionsForSupervisedUserEnabled];
    [_contentSuggestionForSupervisedUsersEnabled setObserver:self];

    _voiceLocaleCode.Init(prefs::kVoiceSearchLocale, prefService);

    _prefChangeRegistrar.Init(prefService);
    _prefObserverBridge.reset(new PrefObserverBridge(self));
    // Register to observe any changes on Perf backed values displayed by the
    // screen.
    _prefObserverBridge->ObserveChangesForPreference(prefs::kVoiceSearchLocale,
                                                     &_prefChangeRegistrar);
    _prefObserverBridge->ObserveChangesForPreference(
        password_manager::prefs::kCredentialsEnableService,
        &_prefChangeRegistrar);
    _prefObserverBridge->ObserveChangesForPreference(
        autofill::prefs::kAutofillCreditCardEnabled, &_prefChangeRegistrar);
    _prefObserverBridge->ObserveChangesForPreference(
        autofill::prefs::kAutofillProfileEnabled, &_prefChangeRegistrar);
    _prefObserverBridge->ObserveChangesForPreference(
        DefaultSearchManager::kDefaultSearchProviderDataPrefName,
        &_prefChangeRegistrar);
    _prefObserverBridge->ObserveChangesForPreference(prefs::kSigninAllowed,
                                                     &_prefChangeRegistrar);
    _notificationsObserver =
        [[NotificationsSettingsObserver alloc] initWithPrefService:prefService
                                                        localState:localState];
    _notificationsObserver.delegate = self;

    // TODO(crbug.com/41344225): -loadModel should not be called from
    // initializer. A possible fix is to move this call to -viewDidLoad.
    [self loadModel];
  }
  return self;
}

- (void)dealloc {
  DCHECK(_settingsAreDismissed)
      << "-settingsWillBeDismissed must be called before -dealloc";
}

#pragma mark View lifecycle

- (void)viewDidLoad {
  [super viewDidLoad];

  self.tableView.accessibilityIdentifier = kSettingsTableViewId;

  // Change the separator inset from the settings default because this
  // TableView shows leading icons.
  self.tableView.separatorInset =
      UIEdgeInsetsMake(0, kTableViewSeparatorInsetWithIcon, 0, 0);

  self.navigationItem.largeTitleDisplayMode =
      UINavigationItemLargeTitleDisplayModeAlways;
}

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  // Update the `_safetyCheckItem` icon when returning to this view controller.
  [self updateSafetyCheckItemTrailingIcon];
  if (IsBottomOmniboxAvailable()) {
    // Update the address bar new IPH badge here as it depends on the number of
    // time it's shown.
    [self updateAddressBarNewIPHBadge];
  }
}

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  [self maybeShowSigninIPH];
}

#pragma mark SettingsRootTableViewController

- (void)loadModel {
  [super loadModel];

  // Sign-in section.
  [self updateSigninSection];

  // Defaults section.
  TableViewModel<TableViewItem*>* model = self.tableViewModel;
  [model addSectionWithIdentifier:SettingsSectionIdentifierDefaults];
  [model addItem:[self defaultBrowserCellItem]
      toSectionWithIdentifier:SettingsSectionIdentifierDefaults];

  // Show managed UI if default search engine is managed by policy.
  if ([self isDefaultSearchEngineManagedByPolicy]) {
    [model addItem:[self managedSearchEngineItem]
        toSectionWithIdentifier:SettingsSectionIdentifierDefaults];
  } else {
    [model addItem:[self searchEngineDetailItem]
        toSectionWithIdentifier:SettingsSectionIdentifierDefaults];
  }

  if (IsBottomOmniboxAvailable()) {
    [model addItem:[self addressBarPreferenceItem]
        toSectionWithIdentifier:SettingsSectionIdentifierDefaults];
  }

  // Basics section
  [model addSectionWithIdentifier:SettingsSectionIdentifierBasics];
  [model addItem:[self passwordsDetailItem]
      toSectionWithIdentifier:SettingsSectionIdentifierBasics];
  [model addItem:[self autoFillCreditCardDetailItem]
      toSectionWithIdentifier:SettingsSectionIdentifierBasics];
  [model addItem:[self autoFillProfileDetailItem]
      toSectionWithIdentifier:SettingsSectionIdentifierBasics];
  if (base::FeatureList::IsEnabled(
          plus_addresses::features::kPlusAddressesEnabled)) {
    _plusAddressesItem = [self plusAddressesItem];
    [model addItem:_plusAddressesItem
        toSectionWithIdentifier:SettingsSectionIdentifierBasics];
  }

  // Advanced Section
  [model addSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
  if ([self shouldShowNotificationsSettings]) {
    _notificationsItem = [self notificationsItem];
    [self updateNotificationsDetailText];
    [model addItem:_notificationsItem
        toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
  }
  [model addItem:[self voiceSearchDetailItem]
      toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
  [model addItem:[self safetyCheckDetailItem]
      toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
  [model addItem:[self privacyDetailItem]
      toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];

  // Feed is disabled in safe mode.
  SceneState* sceneState = _browser->GetSceneState();
  BOOL isSafeMode = [sceneState.appState resumingFromSafeMode];
  TemplateURLService* templateURLService =
      ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);

  if (!IsFeedAblationEnabled() && !isSafeMode &&
      IsContentSuggestionsForSupervisedUserEnabled(_browserState->GetPrefs()) &&
      !ShouldHideFeedWithSearchChoice(templateURLService)) {
    if ([_contentSuggestionPolicyEnabled value]) {
      [model addItem:self.feedSettingsItem
          toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];

    } else {
      [model addItem:self.managedFeedSettingsItem
          toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
    }
  }

  PhotosService* photosService =
      PhotosServiceFactory::GetForBrowserState(_browserState);
  bool shouldShowDownloadsSettings =
      photosService && photosService->IsSupported();
  if (IsInactiveTabsAvailable()) {
    [model addItem:[self tabsSettingsDetailItem]
        toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];

    // Info Section
    [model addSectionWithIdentifier:SettingsSectionIdentifierInfo];
    [model addItem:[self languageSettingsDetailItem]
        toSectionWithIdentifier:SettingsSectionIdentifierInfo];
    [model addItem:[self contentSettingsDetailItem]
        toSectionWithIdentifier:SettingsSectionIdentifierInfo];
    if (shouldShowDownloadsSettings) {
      [model addItem:[self downloadsSettingsDetailItem]
          toSectionWithIdentifier:SettingsSectionIdentifierInfo];
    }
    [model addItem:[self bandwidthManagementDetailItem]
        toSectionWithIdentifier:SettingsSectionIdentifierInfo];
  } else {
    [model addItem:[self languageSettingsDetailItem]
        toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
    [model addItem:[self contentSettingsDetailItem]
        toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
    if (shouldShowDownloadsSettings) {
      [model addItem:[self downloadsSettingsDetailItem]
          toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
    }
    [model addItem:[self bandwidthManagementDetailItem]
        toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];

    // Info Section
    [model addSectionWithIdentifier:SettingsSectionIdentifierInfo];
  }
  [model addItem:[self aboutChromeDetailItem]
      toSectionWithIdentifier:SettingsSectionIdentifierInfo];

  // Debug Section
  if ([self hasDebugSection]) {
    [model addSectionWithIdentifier:SettingsSectionIdentifierDebug];
  }

  if (experimental_flags::IsMemoryDebuggingEnabled()) {
    _showMemoryDebugToolsItem = [self showMemoryDebugSwitchItem];
    [model addItem:_showMemoryDebugToolsItem
        toSectionWithIdentifier:SettingsSectionIdentifierDebug];

    if (experimental_flags::DisplaySwitchProfile().has_value()) {
      [model addItem:[self switchProfileItem]
          toSectionWithIdentifier:SettingsSectionIdentifierDebug];
    }
  }

#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
  [model addItem:[self viewSourceSwitchItem]
      toSectionWithIdentifier:SettingsSectionIdentifierDebug];
  [model addItem:[self tableViewCatalogDetailItem]
      toSectionWithIdentifier:SettingsSectionIdentifierDebug];
#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
}

- (void)updateSigninSection {
  TableViewModel<TableViewItem*>* model = self.tableViewModel;
  if ([model hasSectionForSectionIdentifier:SettingsSectionIdentifierSignIn]) {
    [model removeSectionWithIdentifier:SettingsSectionIdentifierSignIn];
  }
  if ([model hasSectionForSectionIdentifier:SettingsSectionIdentifierAccount]) {
    [model removeSectionWithIdentifier:SettingsSectionIdentifierAccount];
  }

  [model insertSectionWithIdentifier:SettingsSectionIdentifierAccount
                             atIndex:0];
  [self addAccountToSigninSection];

  // Temporarily place this in the first index position in case it is populated.
  // If this is not the case SettingsSectionIdentifierAccount will remain at
  // index 0.
  [model insertSectionWithIdentifier:SettingsSectionIdentifierSignIn atIndex:0];
  [self addPromoToSigninSection];
  [self addPromoToEnhancedSafeBrowsingSection];
}

// Adds the identity promo to promote the sign-in or sync state.
- (void)addPromoToSigninSection {
  TableViewItem* item = nil;

  AuthenticationService* authService =
      AuthenticationServiceFactory::GetForBrowserState(_browserState);
  const AuthenticationService::ServiceStatus authServiceStatus =
      authService->GetServiceStatus();
  // If sign-in is disabled by policy there should not be a sign-in promo.
  if ((authServiceStatus ==
       AuthenticationService::ServiceStatus::SigninDisabledByPolicy)) {
    item = [self signinDisabledByPolicyTextItem];
  } else if ((authServiceStatus ==
                  AuthenticationService::ServiceStatus::SigninForcedByPolicy ||
              authServiceStatus ==
                  AuthenticationService::ServiceStatus::SigninAllowed) &&
             !authService->HasPrimaryIdentity(signin::ConsentLevel::kSignin)) {
    item = [self accountSignInItem];
  } else {
    [self.tableViewModel
        removeSectionWithIdentifier:SettingsSectionIdentifierSignIn];

    if (!_hasRecordedSigninImpression) {
      // Once the Settings are open, this button impression will at most be
      // recorded once until they are closed.
      signin_metrics::RecordSigninImpressionUserActionForAccessPoint(
          signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS);
      _hasRecordedSigninImpression = YES;
    }

    // The user is signed-in and syncing, exit early since the promo is not
    // required.
    return;
  }

  [self.tableViewModel addItem:item
       toSectionWithIdentifier:SettingsSectionIdentifierSignIn];
}

// Adds the account profile to the Account section if the user is signed in.
- (void)addAccountToSigninSection {
  TableViewModel<TableViewItem*>* model = self.tableViewModel;
  AuthenticationService* authService =
      AuthenticationServiceFactory::GetForBrowserState(_browserState);
  if (authService->HasPrimaryIdentity(signin::ConsentLevel::kSignin)) {
    // Account profile item.
    [model addItem:[self accountCellItem]
        toSectionWithIdentifier:SettingsSectionIdentifierAccount];
    _hasRecordedSigninImpression = NO;
  }

  // Sync item.
  if (authService->HasPrimaryIdentity(signin::ConsentLevel::kSignin) &&
      ![self shouldReplaceSyncSettingsWithAccountSettings]) {
    [model addItem:[self syncItem]
        toSectionWithIdentifier:SettingsSectionIdentifierAccount];
  }
  // Google Services item.
  [model addItem:[self googleServicesCellItem]
      toSectionWithIdentifier:SettingsSectionIdentifierAccount];
}

// Adds the Enhanced Safe Browsing inline promo to promote ESB.
- (void)addPromoToEnhancedSafeBrowsingSection {
  if (!base::FeatureList::IsEnabled(
          feature_engagement::kIPHiOSInlineEnhancedSafeBrowsingPromoFeature) ||
      ![self shouldShowEnhancedSafeBrowsingPromo]) {
    return;
  }

  if ([self.tableViewModel
          hasSectionForSectionIdentifier:SettingsSectionIdentifierESBPromo]) {
    [self.tableViewModel
        removeSectionWithIdentifier:SettingsSectionIdentifierESBPromo];
  }
  [self.tableViewModel
      insertSectionWithIdentifier:SettingsSectionIdentifierESBPromo
                          atIndex:0];

  if (![self.tableViewModel
          hasItemForItemType:SettingsItemTypeESBPromo
           sectionIdentifier:SettingsSectionIdentifierESBPromo]) {
    [self.tableViewModel addItem:[self enhancedSafeBrowsingInlinePromoItem]
         toSectionWithIdentifier:SettingsSectionIdentifierESBPromo];
  }

  [self maybeRecordEnhancedSafeBrowsingImpressionLimitReached];
}

#pragma mark - Model Items

- (TableViewItem*)accountSignInItem {
  AccountSignInItem* signInTextItem =
      [[AccountSignInItem alloc] initWithType:SettingsItemTypeSignInButton];
  signInTextItem.accessibilityIdentifier = kSettingsSignInCellId;
  // TODO(crbug.com/40064662): Make detailText private when the feature is
  // launched.
  signInTextItem.detailText =
      l10n_util::GetNSString(IDS_IOS_IDENTITY_DISC_SIGN_IN_PROMO_LABEL);
  return signInTextItem;
}

- (TableViewItem*)signinDisabledByPolicyTextItem {
  TableViewInfoButtonItem* signinDisabledItem =
      [self infoButtonWithType:SettingsItemTypeSigninDisabled
                             text:l10n_util::GetNSString(
                                      IDS_IOS_SIGN_IN_TO_CHROME_SETTING_TITLE)
                           status:l10n_util::GetNSString(IDS_IOS_SETTING_OFF)
                            image:nil
                  imageBackground:nil
                accessibilityHint:
                    l10n_util::GetNSString(
                        IDS_IOS_TOGGLE_SETTING_MANAGED_ACCESSIBILITY_HINT)
          accessibilityIdentifier:kSettingsSignInDisabledCellId];
  signinDisabledItem.textColor = [UIColor colorNamed:kTextSecondaryColor];
  return signinDisabledItem;
}

- (TableViewItem*)googleServicesCellItem {
  return [self detailItemWithType:SettingsItemTypeGoogleServices
                             text:l10n_util::GetNSString(
                                      IDS_IOS_GOOGLE_SERVICES_SETTINGS_TITLE)
                       detailText:nil
                           symbol:GetBrandedGoogleServicesSymbol()
            symbolBackgroundColor:nil
          accessibilityIdentifier:kSettingsGoogleServicesCellId];
}

- (TableViewItem*)syncDisabledByPolicyItem {
  return [self infoButtonWithType:SettingsItemTypeGoogleSync
                             text:l10n_util::GetNSString(
                                      IDS_IOS_GOOGLE_SYNC_SETTINGS_TITLE)
                           status:l10n_util::GetNSString(IDS_IOS_SETTING_OFF)
                            image:CustomSettingsRootSymbol(kSyncDisabledSymbol)
                  imageBackground:[UIColor colorNamed:kGrey400Color]
                accessibilityHint:
                    l10n_util::GetNSString(
                        IDS_IOS_TOGGLE_SETTING_MANAGED_ACCESSIBILITY_HINT)
          accessibilityIdentifier:kSettingsGoogleSyncAndServicesCellId];
}

- (TableViewItem*)syncItem {
  if ([self isSyncDisabledByPolicy]) {
    _syncItem = [self syncDisabledByPolicyItem];
    return _syncItem;
  }

  TableViewDetailIconItem* syncItem =
      [self detailItemWithType:SettingsItemTypeGoogleSync
                             text:l10n_util::GetNSString(
                                      IDS_IOS_GOOGLE_SYNC_SETTINGS_TITLE)
                       detailText:nil
                           symbol:nil
            symbolBackgroundColor:nil
          accessibilityIdentifier:kSettingsGoogleSyncAndServicesCellId];
  [self updateSyncItem:syncItem];
  _syncItem = syncItem;

  return _syncItem;
}

- (TableViewItem*)defaultBrowserCellItem {
  TableViewDetailIconItem* defaultBrowser = [[TableViewDetailIconItem alloc]
      initWithType:SettingsItemTypeDefaultBrowser];
  defaultBrowser.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  defaultBrowser.text =
      l10n_util::GetNSString(IDS_IOS_SETTINGS_SET_DEFAULT_BROWSER);

  defaultBrowser.iconImage = DefaultSettingsRootSymbol(kDefaultBrowserSymbol);
  defaultBrowser.iconBackgroundColor = [UIColor colorNamed:kPurple500Color];
  defaultBrowser.iconTintColor = UIColor.whiteColor;
  defaultBrowser.iconCornerRadius = kColorfulBackgroundSymbolCornerRadius;

  [self maybeActivateDefaultBrowserBlueDotPromo:defaultBrowser];

  return defaultBrowser;
}

- (TableViewItem*)accountCellItem {
  TableViewAccountItem* identityAccountItem =
      [[TableViewAccountItem alloc] initWithType:SettingsItemTypeAccount];
  identityAccountItem.accessoryType =
      UITableViewCellAccessoryDisclosureIndicator;
  identityAccountItem.accessibilityIdentifier = kSettingsAccountCellId;
  [self updateIdentityAccountItem:identityAccountItem];
  return identityAccountItem;
}

- (TableViewItem*)searchEngineDetailItem {
  NSString* defaultSearchEngineName =
      base::SysUTF16ToNSString(GetDefaultSearchEngineName(
          ios::TemplateURLServiceFactory::GetForBrowserState(_browserState)));

  _defaultSearchEngineItem =
      [self detailItemWithType:SettingsItemTypeSearchEngine
                             text:l10n_util::GetNSString(
                                      IDS_IOS_SEARCH_ENGINE_SETTING_TITLE)
                       detailText:defaultSearchEngineName
                           symbol:DefaultSettingsRootSymbol(kSearchSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kPurple500Color]
          accessibilityIdentifier:kSettingsSearchEngineCellId];

  return _defaultSearchEngineItem;
}

- (TableViewItem*)addressBarPreferenceItem {
  _addressBarPreferenceItem = [self
           detailItemWithType:SettingsItemTypeAddressBar
                         text:l10n_util::GetNSString(
                                  IDS_IOS_ADDRESS_BAR_SETTING)
                   detailText:[_bottomOmniboxEnabled value]
                                  ? l10n_util::GetNSString(
                                        IDS_IOS_BOTTOM_ADDRESS_BAR_OPTION)
                                  : l10n_util::GetNSString(
                                        IDS_IOS_TOP_ADDRESS_BAR_OPTION)
                       symbol:DefaultSettingsRootSymbol(kGlobeAmericasSymbol)
        symbolBackgroundColor:[UIColor colorNamed:kPurple500Color]
      accessibilityIdentifier:kSettingsAddressBarCellId];
  return _addressBarPreferenceItem;
}

- (TableViewInfoButtonItem*)managedSearchEngineItem {
  _managedSearchEngineItem =
      [self infoButtonWithType:SettingsItemTypeManagedDefaultSearchEngine
                             text:l10n_util::GetNSString(
                                      IDS_IOS_SEARCH_ENGINE_SETTING_TITLE)
                           status:[self managedSearchEngineDetailText]
                            image:DefaultSettingsRootSymbol(kSearchSymbol)
                  imageBackground:[UIColor colorNamed:kPurple500Color]
                accessibilityHint:
                    l10n_util::GetNSString(
                        IDS_IOS_TOGGLE_SETTING_MANAGED_ACCESSIBILITY_HINT)
          accessibilityIdentifier:kSettingsManagedSearchEngineCellId];

  return _managedSearchEngineItem;
}

- (TableViewItem*)passwordsDetailItem {
  BOOL passwordsEnabled = _browserState->GetPrefs()->GetBoolean(
      password_manager::prefs::kCredentialsEnableService);

  NSString* passwordsDetail = passwordsEnabled
                                  ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
                                  : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);

  NSString* passwordsSectionTitle =
      l10n_util::GetNSString(IDS_IOS_PASSWORD_MANAGER);

  _passwordsDetailItem =
      [self detailItemWithType:SettingsItemTypePasswords
                             text:passwordsSectionTitle
                       detailText:passwordsDetail
                           symbol:CustomSettingsRootSymbol(kPasswordSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kYellow500Color]
          accessibilityIdentifier:kSettingsPasswordsCellId];

  return _passwordsDetailItem;
}

- (TableViewItem*)autoFillCreditCardDetailItem {
  BOOL autofillCreditCardEnabled =
      autofill::prefs::IsAutofillPaymentMethodsEnabled(
          _browserState->GetPrefs());
  NSString* detailText = autofillCreditCardEnabled
                             ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
                             : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);

  _autoFillCreditCardDetailItem =
      [self detailItemWithType:SettingsItemTypeAutofillCreditCard
                             text:l10n_util::GetNSString(
                                      IDS_AUTOFILL_PAYMENT_METHODS)
                       detailText:detailText
                           symbol:DefaultSettingsRootSymbol(kCreditCardSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kYellow500Color]
          accessibilityIdentifier:kSettingsPaymentMethodsCellId];

  return _autoFillCreditCardDetailItem;
}

- (TableViewItem*)autoFillProfileDetailItem {
  BOOL autofillProfileEnabled =
      autofill::prefs::IsAutofillProfileEnabled(_browserState->GetPrefs());
  NSString* detailText = autofillProfileEnabled
                             ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
                             : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);

  _autoFillProfileDetailItem =
      [self detailItemWithType:SettingsItemTypeAutofillProfile
                             text:l10n_util::GetNSString(
                                      IDS_AUTOFILL_ADDRESSES_SETTINGS_TITLE)
                       detailText:detailText
                           symbol:CustomSettingsRootSymbol(kLocationSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kYellow500Color]
          accessibilityIdentifier:kSettingsAddressesAndMoreCellId];
  return _autoFillProfileDetailItem;
}

- (TableViewItem*)voiceSearchDetailItem {
  voice::SpeechInputLocaleConfig* localeConfig =
      voice::SpeechInputLocaleConfig::GetInstance();
  voice::SpeechInputLocale locale =
      _voiceLocaleCode.GetValue().length()
          ? localeConfig->GetLocaleForCode(_voiceLocaleCode.GetValue())
          : localeConfig->GetDefaultLocale();
  NSString* languageName = base::SysUTF16ToNSString(locale.display_name);

  _voiceSearchDetailItem =
      [self detailItemWithType:SettingsItemTypeVoiceSearch
                             text:l10n_util::GetNSString(
                                      IDS_IOS_VOICE_SEARCH_SETTING_TITLE)
                       detailText:languageName
                           symbol:DefaultSettingsRootSymbol(kMicrophoneSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kGreen500Color]
          accessibilityIdentifier:kSettingsVoiceSearchCellId];

  return _voiceSearchDetailItem;
}

- (SettingsCheckItem*)safetyCheckDetailItem {
  NSString* safetyCheckTitle =
      l10n_util::GetNSString(IDS_OPTIONS_ADVANCED_SECTION_TITLE_SAFETY_CHECK);
  _safetyCheckItem =
      [[SettingsCheckItem alloc] initWithType:SettingsItemTypeSafetyCheck];
  _safetyCheckItem.text = safetyCheckTitle;
  _safetyCheckItem.enabled = YES;
  _safetyCheckItem.indicatorHidden = YES;
  _safetyCheckItem.infoButtonHidden = YES;
  _safetyCheckItem.trailingImage = nil;
  _safetyCheckItem.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  _safetyCheckItem.leadingIcon = CustomSettingsRootSymbol(kSafetyCheckSymbol);
  _safetyCheckItem.leadingIconBackgroundColor =
      [UIColor colorNamed:kBlue500Color];
  _safetyCheckItem.leadingIconTintColor = UIColor.whiteColor;
  _safetyCheckItem.leadingIconCornerRadius =
      kColorfulBackgroundSymbolCornerRadius;
  _safetyCheckItem.accessibilityIdentifier = kSettingsSafetyCheckCellId;
  // Check if an issue state should be shown for updates.
  if (!IsAppUpToDate() && PreviousSafetyCheckIssueFound()) {
    [self updateSafetyCheckItemTrailingIcon];
  }

  return _safetyCheckItem;
}

- (TableViewDetailIconItem*)notificationsItem {
  NSString* title = l10n_util::GetNSString(IDS_IOS_NOTIFICATIONS_TITLE);
  return [self detailItemWithType:SettingsItemTypeNotifications
                             text:title
                       detailText:nil
                           symbol:DefaultSettingsRootSymbol(kBellSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kPink500Color]
          accessibilityIdentifier:kSettingsNotificationsId];
}

- (TableViewDetailIconItem*)plusAddressesItem {
  NSString* title = l10n_util::GetNSString(IDS_PLUS_ADDRESS_SETTINGS_LABEL);

  return [self
           detailItemWithType:SettingsItemTypePlusAddresses
                         text:title
                   detailText:nil
#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
                       symbol:CustomSettingsRootSymbol(kGooglePlusAddressSymbol)
#else
                       symbol:nil
#endif
        symbolBackgroundColor:[UIColor colorNamed:kYellow500Color]
      accessibilityIdentifier:kSettingsPlusAddressesId];
}

- (TableViewItem*)privacyDetailItem {
  NSString* title = nil;
  title = l10n_util::GetNSString(IDS_IOS_SETTINGS_PRIVACY_TITLE);

  return [self detailItemWithType:SettingsItemTypePrivacy
                             text:title
                       detailText:nil
                           symbol:CustomSettingsRootSymbol(kPrivacySymbol)
            symbolBackgroundColor:[UIColor colorNamed:kBlue500Color]
          accessibilityIdentifier:kSettingsPrivacyCellId];
}

- (TableViewSwitchItem*)feedSettingsItem {
  if (!_feedSettingsItem) {
    NSString* settingTitle = [self feedItemTitle];

    _feedSettingsItem =
        [self switchItemWithType:SettingsItemTypeArticlesForYou
                              title:settingTitle
                             symbol:DefaultSettingsRootSymbol(kDiscoverSymbol)
              symbolBackgroundColor:[UIColor colorNamed:kOrange500Color]
            accessibilityIdentifier:kSettingsArticleSuggestionsCellId];
    _feedSettingsItem.on = [_articlesEnabled value];
  }
  return _feedSettingsItem;
}

- (TableViewInfoButtonItem*)managedFeedSettingsItem {
  if (!_managedFeedSettingsItem) {
    _managedFeedSettingsItem =
        [self infoButtonWithType:SettingsItemTypeManagedArticlesForYou
                               text:[self feedItemTitle]
                             status:l10n_util::GetNSString(IDS_IOS_SETTING_OFF)
                              image:DefaultSettingsRootSymbol(kDiscoverSymbol)
                    imageBackground:[UIColor colorNamed:kOrange500Color]
                  accessibilityHint:
                      l10n_util::GetNSString(
                          IDS_IOS_TOGGLE_SETTING_MANAGED_ACCESSIBILITY_HINT)
            accessibilityIdentifier:kSettingsArticleSuggestionsCellId];
  }

  return _managedFeedSettingsItem;
}

- (TableViewItem*)languageSettingsDetailItem {
  return [self detailItemWithType:SettingsItemTypeLanguageSettings
                             text:l10n_util::GetNSString(
                                      IDS_IOS_LANGUAGE_SETTINGS_TITLE)
                       detailText:nil
                           symbol:CustomSettingsRootSymbol(kLanguageSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kGrey400Color]
          accessibilityIdentifier:kSettingsLanguagesCellId];
}

- (TableViewItem*)contentSettingsDetailItem {
  return [self
           detailItemWithType:SettingsItemTypeContentSettings
                         text:l10n_util::GetNSString(
                                  IDS_IOS_CONTENT_SETTINGS_TITLE)
                   detailText:nil
                       symbol:DefaultSettingsRootSymbol(kSettingsFilledSymbol)
        symbolBackgroundColor:[UIColor colorNamed:kGrey400Color]
      accessibilityIdentifier:kSettingsContentSettingsCellId];
}

- (TableViewItem*)downloadsSettingsDetailItem {
  return [self detailItemWithType:SettingsItemTypeDownloadsSettings
                             text:l10n_util::GetNSString(
                                      IDS_IOS_SETTINGS_DOWNLOADS_TITLE)
                       detailText:nil
                           symbol:DefaultSettingsRootSymbol(kDownloadSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kGrey400Color]
          accessibilityIdentifier:kSettingsDownloadsSettingsCellId];
}

- (TableViewItem*)tabsSettingsDetailItem {
  return [self detailItemWithType:SettingsItemTypeTabs
                             text:l10n_util::GetNSString(
                                      IDS_IOS_TABS_MANAGEMENT_SETTINGS)
                       detailText:nil
                           symbol:DefaultSettingsRootSymbol(kTabsSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kOrange500Color]
          accessibilityIdentifier:kSettingsTabsCellId];
}

- (TableViewItem*)bandwidthManagementDetailItem {
  return [self detailItemWithType:SettingsItemTypeBandwidth
                             text:l10n_util::GetNSString(
                                      IDS_IOS_BANDWIDTH_MANAGEMENT_SETTINGS)
                       detailText:nil
                           symbol:DefaultSettingsRootSymbol(kWifiSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kGrey400Color]
          accessibilityIdentifier:kSettingsBandwidthCellId];
}

- (TableViewItem*)aboutChromeDetailItem {
  return [self detailItemWithType:SettingsItemTypeAboutChrome
                             text:l10n_util::GetNSString(IDS_IOS_PRODUCT_NAME)
                       detailText:nil
                           symbol:DefaultSettingsRootSymbol(kInfoCircleSymbol)
            symbolBackgroundColor:[UIColor colorNamed:kGrey400Color]
          accessibilityIdentifier:kSettingsAboutCellId];
}

- (TableViewSwitchItem*)showMemoryDebugSwitchItem {
  TableViewSwitchItem* showMemoryDebugSwitchItem =
      [self switchItemWithType:SettingsItemTypeMemoryDebugging
                            title:@"Show memory debug tools"
                           symbol:DefaultSettingsRootSymbol(@"memorychip")
            symbolBackgroundColor:[UIColor colorNamed:kGrey400Color]
          accessibilityIdentifier:nil];
  showMemoryDebugSwitchItem.on = [_showMemoryDebugToolsEnabled value];

  return showMemoryDebugSwitchItem;
}

- (TableViewItem*)switchProfileItem {
  return [self
           detailItemWithType:SettingsItemTypeSwitchProfile
                         text:l10n_util::GetNSString(
                                  IDS_IOS_SWITCH_PROFILE_MANAGEMENT_SETTINGS)
                   detailText:nil
                       symbol:DefaultSettingsRootSymbol(kMultiIdentitySymbol)
        symbolBackgroundColor:[UIColor colorNamed:kGrey400Color]
      accessibilityIdentifier:nil];
}

- (TableViewItem*)enhancedSafeBrowsingInlinePromoItem {
  EnhancedSafeBrowsingInlinePromoItem* item =
      [[EnhancedSafeBrowsingInlinePromoItem alloc]
          initWithType:SettingsItemTypeESBPromo];
  item.delegate = self;
  return item;
}

#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)

- (TableViewSwitchItem*)viewSourceSwitchItem {
  UIImage* image;
  if (@available(iOS 16, *)) {
    image = DefaultSettingsRootSymbol(@"keyboard.badge.eye");
  } else {
    image = DefaultSettingsRootSymbol(@"keyboard");
  }
  TableViewSwitchItem* viewSourceItem =
      [self switchItemWithType:SettingsItemTypeViewSource
                            title:@"View source menu"
                           symbol:image
            symbolBackgroundColor:[UIColor colorNamed:kGrey400Color]
          accessibilityIdentifier:nil];

  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  viewSourceItem.on = [defaults boolForKey:kDevViewSourceKey];
  return viewSourceItem;
}

- (TableViewDetailIconItem*)tableViewCatalogDetailItem {
  return [self detailItemWithType:SettingsItemTypeTableCellCatalog
                             text:@"TableView Cell Catalog"
                       detailText:nil
                           symbol:DefaultSettingsRootSymbol(@"cart")
            symbolBackgroundColor:[UIColor colorNamed:kGrey400Color]
          accessibilityIdentifier:nil];
}
#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)

#pragma mark Item Constructors

- (TableViewDetailIconItem*)detailItemWithType:(NSInteger)type
                                          text:(NSString*)text
                                    detailText:(NSString*)detailText
                                        symbol:(UIImage*)symbol
                         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;
  if (backgroundColor) {
    detailItem.iconBackgroundColor = backgroundColor;
    detailItem.iconTintColor = UIColor.whiteColor;
  }
  detailItem.iconCornerRadius = kColorfulBackgroundSymbolCornerRadius;
  return detailItem;
}

- (TableViewSwitchItem*)switchItemWithType:(NSInteger)type
                                     title:(NSString*)title
                                    symbol:(UIImage*)symbol
                     symbolBackgroundColor:(UIColor*)backgroundColor
                   accessibilityIdentifier:(NSString*)accessibilityIdentifier {
  TableViewSwitchItem* switchItem =
      [[TableViewSwitchItem alloc] initWithType:type];
  switchItem.text = title;
  switchItem.iconImage = symbol;
  switchItem.iconTintColor = UIColor.whiteColor;
  switchItem.iconBackgroundColor = backgroundColor;
  switchItem.iconCornerRadius = kColorfulBackgroundSymbolCornerRadius;
  switchItem.accessibilityIdentifier = accessibilityIdentifier;

  return switchItem;
}

- (TableViewInfoButtonItem*)infoButtonWithType:(NSInteger)type
                                          text:(NSString*)text
                                        status:(NSString*)status
                                         image:(UIImage*)image
                               imageBackground:(UIColor*)imageBackground
                             accessibilityHint:(NSString*)accessibilityHint
                       accessibilityIdentifier:
                           (NSString*)accessibilityIdentifier {
  TableViewInfoButtonItem* infoButton =
      [[TableViewInfoButtonItem alloc] initWithType:type];
  infoButton.text = text;
  infoButton.statusText = status;
  if (image) {
    infoButton.iconImage = image;
    DCHECK(imageBackground);
    infoButton.iconBackgroundColor = imageBackground;
    infoButton.iconTintColor = UIColor.whiteColor;
    infoButton.iconCornerRadius = kColorfulBackgroundSymbolCornerRadius;
  }
  infoButton.accessibilityHint = accessibilityHint;
  infoButton.accessibilityIdentifier = accessibilityIdentifier;
  return infoButton;
}

#pragma mark - UITableViewDataSource

- (UITableViewCell*)tableView:(UITableView*)tableView
        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
  UITableViewCell* cell = [super tableView:tableView
                     cellForRowAtIndexPath:indexPath];
  if (_settingsAreDismissed)
    return cell;
  NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];

  switch (itemType) {
    case SettingsItemTypeMemoryDebugging: {
      TableViewSwitchCell* switchCell =
          base::apple::ObjCCastStrict<TableViewSwitchCell>(cell);
      [switchCell.switchView addTarget:self
                                action:@selector(memorySwitchToggled:)
                      forControlEvents:UIControlEventValueChanged];
      break;
    }
    case SettingsItemTypeArticlesForYou: {
      TableViewSwitchCell* switchCell =
          base::apple::ObjCCastStrict<TableViewSwitchCell>(cell);
      [switchCell.switchView addTarget:self
                                action:@selector(articlesForYouSwitchToggled:)
                      forControlEvents:UIControlEventValueChanged];
      break;
    }
    case SettingsItemTypeViewSource: {
#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
      TableViewSwitchCell* switchCell =
          base::apple::ObjCCastStrict<TableViewSwitchCell>(cell);
      [switchCell.switchView addTarget:self
                                action:@selector(viewSourceSwitchToggled:)
                      forControlEvents:UIControlEventValueChanged];
#else
      NOTREACHED_IN_MIGRATION();
#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
      break;
    }
    case SettingsItemTypeManagedDefaultSearchEngine: {
      TableViewInfoButtonCell* managedCell =
          base::apple::ObjCCastStrict<TableViewInfoButtonCell>(cell);
      [managedCell.trailingButton
                 addTarget:self
                    action:@selector(didTapManagedUIInfoButton:)
          forControlEvents:UIControlEventTouchUpInside];
      break;
    }
    case SettingsItemTypeSigninDisabled: {
      // Adds a trailing button with more information when the sign-in policy
      // has been enabled by the organization.
      TableViewInfoButtonCell* managedCell =
          base::apple::ObjCCastStrict<TableViewInfoButtonCell>(cell);
      [managedCell.trailingButton
                 addTarget:self
                    action:@selector(didTapSigninDisabledInfoButton:)
          forControlEvents:UIControlEventTouchUpInside];
      break;
    }
    case SettingsItemTypeManagedArticlesForYou: {
      TableViewInfoButtonCell* managedCell =
          base::apple::ObjCCastStrict<TableViewInfoButtonCell>(cell);
      [managedCell.trailingButton
                 addTarget:self
                    action:@selector(didTapManagedUIInfoButton:)
          forControlEvents:UIControlEventTouchUpInside];
      break;
    }
    case SettingsItemTypeGoogleSync: {
      if (![self isSyncDisabledByPolicy])
        break;
      TableViewInfoButtonCell* managedCell =
          base::apple::ObjCCastStrict<TableViewInfoButtonCell>(cell);
      [managedCell.trailingButton
                 addTarget:self
                    action:@selector(didTapSyncDisabledInfoButton:)
          forControlEvents:UIControlEventTouchUpInside];
      break;
    }
    default:
      break;
  }

  return cell;
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView
    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  if (_settingsAreDismissed)
    return;

  id object = [self.tableViewModel itemAtIndexPath:indexPath];
  if ([object respondsToSelector:@selector(isEnabled)] &&
      ![object performSelector:@selector(isEnabled)]) {
    // Don't perform any action if the cell isn't enabled.
    return;
  }

  NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];

  UIViewController<SettingsRootViewControlling>* controller;

  switch (itemType) {
    case SettingsItemTypeSignInButton:
      signin_metrics::RecordSigninUserActionForAccessPoint(
          signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS);
      [self showSignIn];
      break;
    case SettingsItemTypeAccount: {
      if ([self shouldReplaceSyncSettingsWithAccountSettings]) {
        // Redirect to Account Settings page if the user is signed-in and
        // not-syncing.
        base::RecordAction(base::UserMetricsAction("Settings.Sync"));
        [self showGoogleSync];
        break;
      }
      base::RecordAction(base::UserMetricsAction("Settings.MyAccount"));

      AccountsCoordinator* accountsCoordinator = [[AccountsCoordinator alloc]
          initWithBaseNavigationController:self.navigationController
                                   browser:_browser
                 closeSettingsOnAddAccount:NO];
      _accountsCoordinator = accountsCoordinator;
      [accountsCoordinator start];
      break;
    }
    case SettingsItemTypeGoogleServices:
      base::RecordAction(base::UserMetricsAction("Settings.GoogleServices"));
      [self showGoogleServices];
      break;
    case SettingsItemTypeGoogleSync: {
      base::RecordAction(base::UserMetricsAction("Settings.Sync"));
      switch (GetSyncFeatureState(
          SyncServiceFactory::GetForBrowserState(_browserState))) {
        case SyncState::kSyncConsentOff: {
          [self showSignIn];
          break;
        }
        case SyncState::kSyncOff: {
          [self showGoogleSync];
          break;
        }
        case SyncState::kSyncEnabled:
        case SyncState::kSyncEnabledWithError:
        case SyncState::kSyncEnabledWithNoSelectedTypes: {
          [self showGoogleSync];
          break;
        }
        case SyncState::kSyncDisabledByAdministrator:
          break;
      }
      break;
    }
    case SettingsItemTypeDefaultBrowser: {
      base::RecordAction(
          base::UserMetricsAction("Settings.ShowDefaultBrowser"));

      if (self.showingDefaultBrowserNotificationDot) {
        feature_engagement::Tracker* tracker =
            feature_engagement::TrackerFactory::GetForBrowserState(
                _browserState);
        if (tracker) {
          tracker->NotifyEvent(
              feature_engagement::events::kBlueDotPromoSettingsDismissed);
          id<PopupMenuCommands> popupMenuHandler = HandlerForProtocol(
              _browser->GetCommandDispatcher(), PopupMenuCommands);
          [popupMenuHandler updateToolsMenuBlueDotVisibility];
        }
        [self reloadData];
      }

      controller = [[DefaultBrowserSettingsTableViewController alloc] init];
      break;
    }
    case SettingsItemTypeSearchEngine:
      base::RecordAction(base::UserMetricsAction("EditSearchEngines"));
      controller = [[SearchEngineTableViewController alloc]
          initWithBrowserState:_browserState];
      break;
    case SettingsItemTypeAddressBar:
      base::RecordAction(base::UserMetricsAction("Settings.AddressBar.Opened"));
      [self showAddressBarPreferenceSetting];
      // Sets the "new" IPH badge shown count to max so it's not shown again.
      _browserState->GetPrefs()->SetInteger(
          prefs::kAddressBarSettingsNewBadgeShownCount, INT_MAX);
      break;
    case SettingsItemTypePasswords:
      base::RecordAction(
          base::UserMetricsAction("Options_ShowPasswordManager"));
      UMA_HISTOGRAM_ENUMERATION(
          "PasswordManager.ManagePasswordsReferrer",
          password_manager::ManagePasswordsReferrer::kChromeSettings);
      [self showPasswords];
      break;
    case SettingsItemTypeAutofillCreditCard:
      base::RecordAction(base::UserMetricsAction("AutofillCreditCardsViewed"));
      controller = [[AutofillCreditCardTableViewController alloc]
          initWithBrowser:_browser];
      break;
    case SettingsItemTypeAutofillProfile:
      base::RecordAction(base::UserMetricsAction("AutofillAddressesViewed"));
      controller =
          [[AutofillProfileTableViewController alloc] initWithBrowser:_browser];
      break;
    case SettingsItemTypeNotifications:
      CHECK([self shouldShowNotificationsSettings]);
      base::RecordAction(base::UserMetricsAction("Settings.Notifications"));
      [self showNotifications];
      break;
    case SettingsItemTypeVoiceSearch:
      base::RecordAction(base::UserMetricsAction("Settings.VoiceSearch"));
      controller = [[VoiceSearchTableViewController alloc]
          initWithPrefs:_browserState->GetPrefs()];
      break;
    case SettingsItemTypeSafetyCheck:
      base::RecordAction(base::UserMetricsAction("Settings.SafetyCheck"));
      [self showSafetyCheck];
      break;
    case SettingsItemTypePrivacy:
      base::RecordAction(base::UserMetricsAction("Settings.Privacy"));
      [self showPrivacy];
      break;
    case SettingsItemTypeLanguageSettings: {
      base::RecordAction(base::UserMetricsAction("Settings.Language"));
      language::LanguageModelManager* languageModelManager =
          LanguageModelManagerFactory::GetForBrowserState(_browserState);
      LanguageSettingsMediator* mediator = [[LanguageSettingsMediator alloc]
          initWithLanguageModelManager:languageModelManager
                           prefService:_browserState->GetPrefs()];
      LanguageSettingsTableViewController* languageSettingsTableViewController =
          [[LanguageSettingsTableViewController alloc]
              initWithDataSource:mediator
                  commandHandler:mediator];
      mediator.consumer = languageSettingsTableViewController;
      controller = languageSettingsTableViewController;
      break;
    }
    case SettingsItemTypeContentSettings:
      base::RecordAction(base::UserMetricsAction("Settings.ContentSettings"));
      controller =
          [[ContentSettingsTableViewController alloc] initWithBrowser:_browser];
      break;
    case SettingsItemTypeDownloadsSettings:
      base::RecordAction(base::UserMetricsAction("Settings.DownloadsSettings"));
      [self showDownloadsSettings];
      break;
    case SettingsItemTypeTabs:
      base::RecordAction(base::UserMetricsAction("Settings.Tabs"));
      [self showTabsSettings];
      break;
    case SettingsItemTypeBandwidth:
      base::RecordAction(base::UserMetricsAction("Settings.Bandwidth"));
      controller = [[BandwidthManagementTableViewController alloc]
          initWithBrowserState:_browserState];
      break;
    case SettingsItemTypeAboutChrome: {
      base::RecordAction(base::UserMetricsAction("AboutChrome"));
      AboutChromeTableViewController* aboutChromeTableViewController =
          [[AboutChromeTableViewController alloc] init];
      controller = aboutChromeTableViewController;
      break;
    }
    case SettingsItemTypeMemoryDebugging:
    case SettingsItemTypeViewSource:
      // Taps on these don't do anything. They have a switch as accessory view
      // and only the switch is tappable.
      break;
    case SettingsItemTypeTableCellCatalog:
      [self.navigationController
          pushViewController:[[TableCellCatalogViewController alloc] init]
                    animated:YES];
      break;
    case SettingsItemTypePlusAddresses: {
      base::RecordAction(base::UserMetricsAction("Settings.PlusAddresses"));
      OpenNewTabCommand* command = [OpenNewTabCommand
          commandWithURLFromChrome:
              GURL(plus_addresses::features::kPlusAddressManagementUrl.Get())];
      id<ApplicationCommands> handler = HandlerForProtocol(
          _browser->GetCommandDispatcher(), ApplicationCommands);
      [handler closeSettingsUIAndOpenURL:command];
      break;
    }
    case SettingsItemTypeSwitchProfile:
      [self showSwitchProfileSettings];
      break;
    default:
      break;
  }

  if (controller) {
    [self configureHandlersForRootViewController:controller];
    [self.navigationController pushViewController:controller animated:YES];
  }
}

#pragma mark - Actions

// Called when the user taps on the information button of a managed setting's UI
// cell.
- (void)didTapSigninDisabledInfoButton:(UIButton*)buttonView {
  NSString* popoverMessage =
      l10n_util::GetNSString(IDS_IOS_SETTINGS_SIGNIN_DISABLED_POPOVER_TEXT);
  EnterpriseInfoPopoverViewController* popover =
      [[EnterpriseInfoPopoverViewController alloc]
          initWithMessage:popoverMessage
           enterpriseName:nil];

  [self showEnterprisePopover:popover forInfoButton:buttonView];
}

// Called when the user taps on the information button of the sync setting
// while sync is disabled by policy.
- (void)didTapSyncDisabledInfoButton:(UIButton*)buttonView {
  NSString* popoverMessage =
      l10n_util::GetNSString(IDS_IOS_SYNC_SETTINGS_DISABLED_POPOVER_TEXT);
  EnterpriseInfoPopoverViewController* popover =
      [[EnterpriseInfoPopoverViewController alloc]
          initWithMessage:popoverMessage
           enterpriseName:nil];

  [self showEnterprisePopover:popover forInfoButton:buttonView];
}

// Called when the user taps on the information button of the sign-in setting
// while sign-in is disabled by policy.
- (void)didTapManagedUIInfoButton:(UIButton*)buttonView {
  EnterpriseInfoPopoverViewController* popover =
      [[EnterpriseInfoPopoverViewController alloc] initWithEnterpriseName:nil];

  [self showEnterprisePopover:popover forInfoButton:buttonView];
}

// Shows a contextual bubble explaining that the tapped setting is managed and
// includes a link to the chrome://management page.
- (void)showEnterprisePopover:(EnterpriseInfoPopoverViewController*)popover
                forInfoButton:(UIButton*)buttonView {
  popover.delegate = self;

  // Disable the button when showing the bubble.
  // The button will be enabled when close the bubble in
  // (void)popoverPresentationControllerDidDismissPopover: of
  // EnterpriseInfoPopoverViewController.
  buttonView.enabled = NO;

  // Set the anchor and arrow direction of the bubble.
  popover.popoverPresentationController.sourceView = buttonView;
  popover.popoverPresentationController.sourceRect = buttonView.bounds;
  popover.popoverPresentationController.permittedArrowDirections =
      UIPopoverArrowDirectionAny;

  [self presentViewController:popover animated:YES completion:nil];
}

#pragma mark Switch Actions

- (void)memorySwitchToggled:(UISwitch*)sender {
  NSIndexPath* switchPath =
      [self.tableViewModel indexPathForItemType:SettingsItemTypeMemoryDebugging
                              sectionIdentifier:SettingsSectionIdentifierDebug];

  TableViewSwitchItem* switchItem =
      base::apple::ObjCCastStrict<TableViewSwitchItem>(
          [self.tableViewModel itemAtIndexPath:switchPath]);

  BOOL newSwitchValue = sender.isOn;
  switchItem.on = newSwitchValue;
  [_showMemoryDebugToolsEnabled setValue:newSwitchValue];
}

- (void)articlesForYouSwitchToggled:(UISwitch*)sender {
  base::RecordAction(base::UserMetricsAction("Settings.ArticlesForYouToggled"));

  NSIndexPath* switchPath = [self.tableViewModel
      indexPathForItemType:SettingsItemTypeArticlesForYou
         sectionIdentifier:SettingsSectionIdentifierAdvanced];

  TableViewSwitchItem* switchItem =
      base::apple::ObjCCastStrict<TableViewSwitchItem>(
          [self.tableViewModel itemAtIndexPath:switchPath]);

  BOOL newSwitchValue = sender.isOn;
  switchItem.on = newSwitchValue;
  [_articlesEnabled setValue:newSwitchValue];
}

#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
- (void)viewSourceSwitchToggled:(UISwitch*)sender {
  NSIndexPath* switchPath =
      [self.tableViewModel indexPathForItemType:SettingsItemTypeViewSource
                              sectionIdentifier:SettingsSectionIdentifierDebug];

  TableViewSwitchItem* switchItem =
      base::apple::ObjCCastStrict<TableViewSwitchItem>(
          [self.tableViewModel itemAtIndexPath:switchPath]);

  BOOL newSwitchValue = sender.isOn;
  switchItem.on = newSwitchValue;
  [self setBooleanNSUserDefaultsValue:newSwitchValue forKey:kDevViewSourceKey];
}
#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)

#pragma mark - Private methods

// Returns true if sync is disabled by policy.
- (bool)isSyncDisabledByPolicy {
  return SyncServiceFactory::GetForBrowserState(_browserState)
      ->HasDisableReason(syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY);
}

- (void)showGoogleServices {
  DCHECK(!_googleServicesSettingsCoordinator);
  _googleServicesSettingsCoordinator =
      [[GoogleServicesSettingsCoordinator alloc]
          initWithBaseNavigationController:self.navigationController
                                   browser:_browser];
  _googleServicesSettingsCoordinator.delegate = self;
  [_googleServicesSettingsCoordinator start];
}

- (void)showTabsSettings {
  _tabsCoordinator = [[TabsSettingsCoordinator alloc]
      initWithBaseNavigationController:self.navigationController
                               browser:_browser];
  [_tabsCoordinator start];
}

- (void)showSwitchProfileSettings {
  _switchProfileCoordinator = [[SwitchProfileSettingsCoordinator alloc]
      initWithBaseNavigationController:self.navigationController
                               browser:_browser];
  [_switchProfileCoordinator start];
}

- (void)showAddressBarPreferenceSetting {
  _addressBarPreferenceCoordinator = [[AddressBarPreferenceCoordinator alloc]
      initWithBaseNavigationController:self.navigationController
                               browser:_browser];
  [_addressBarPreferenceCoordinator start];
}

- (BOOL)shouldReplaceSyncSettingsWithAccountSettings {
  // TODO(crbug.com/40066949): Remove usage of HasSyncConsent() after kSync
  // users migrated to kSignin in phase 3. See ConsentLevel::kSync
  // documentation for details.
  return !SyncServiceFactory::GetForBrowserState(_browserState)
              ->HasSyncConsent();
}

- (void)showGoogleSync {
  // TODO(crbug.com/40067451): Switch back to DCHECK if the number of reports is
  // low.
  DUMP_WILL_BE_CHECK(!_manageSyncSettingsCoordinator);
  SyncSettingsAccountState accountState =
      [self shouldReplaceSyncSettingsWithAccountSettings]
          ? SyncSettingsAccountState::kSignedIn
          : SyncSettingsAccountState::kSyncing;
  _manageSyncSettingsCoordinator = [[ManageSyncSettingsCoordinator alloc]
      initWithBaseNavigationController:self.navigationController
                               browser:_browser
                          accountState:accountState];
  _manageSyncSettingsCoordinator.delegate = self;
  [_manageSyncSettingsCoordinator start];
}

- (void)showPasswords {
  // TODO(crbug.com/40067451): Switch back to DCHECK if the number of reports is
  // low.
  DUMP_WILL_BE_CHECK(!_passwordsCoordinator);
  _passwordsCoordinator = [[PasswordsCoordinator alloc]
      initWithBaseNavigationController:self.navigationController
                               browser:_browser];
  _passwordsCoordinator.delegate = self;
  [_passwordsCoordinator start];
}

// Shows the Safety Check screen.
- (void)showSafetyCheck {
  // TODO(crbug.com/40067451): Switch back to DCHECK if the number of reports is
  // low.
  DUMP_WILL_BE_CHECK(!_safetyCheckCoordinator);

  _safetyCheckCoordinator = [[SafetyCheckCoordinator alloc]
      initWithBaseNavigationController:self.navigationController
                               browser:_browser
                              referrer:password_manager::PasswordCheckReferrer::
                                           kSafetyCheck];
  _safetyCheckCoordinator.delegate = self;
  [_safetyCheckCoordinator start];
}

// Checks if there are any remaining password issues that are not muted from the
// last time password check was run.
- (BOOL)hasPasswordIssuesRemaining {
  if (!_passwordCheckManager) {
    return NO;
  }
  return !_passwordCheckManager->GetInsecureCredentials().empty();
}

// Displays a warning icon in the `_safetyCheckItem` if there is a reamining
// issue for any of the safety checks.
- (void)updateSafetyCheckItemTrailingIcon {
  if (!_safetyCheckItem || !_passwordCheckManager) {
    return;
  }

  if (!PreviousSafetyCheckIssueFound()) {
    _safetyCheckItem.trailingImage = nil;
    _safetyCheckItem.trailingImageTintColor = nil;
    return;
  }

  if (!IsAppUpToDate()) {
    _safetyCheckItem.warningState = WarningState::kSevereWarning;
  } else if ([self hasPasswordIssuesRemaining]) {
    password_manager::WarningType warningType = GetWarningOfHighestPriority(
        _passwordCheckManager->GetInsecureCredentials());
    if (warningType ==
        password_manager::WarningType::kCompromisedPasswordsWarning) {
      _safetyCheckItem.warningState = WarningState::kSevereWarning;
    } else {
      // Getting here means that there are reused, weak and/or muted passwords.
      // In Safety Check, an icon is shown for passwords only when all passwords
      // are safe or when there are unmuted compromised passwords. When there
      // are reused, weak and/or muted passwords, no icon is shown.
      _safetyCheckItem.trailingImage = nil;
    }
  }
  [self reconfigureCellsForItems:@[ _safetyCheckItem ]];
}

// Shows Notifications screen.
- (void)showNotifications {
  DCHECK(!_notificationsCoordinator);
  DCHECK(self.navigationController);
  _notificationsCoordinator = [[NotificationsCoordinator alloc]
      initWithBaseNavigationController:self.navigationController
                               browser:_browser];
  _notificationsCoordinator.delegate = self;
  [_notificationsCoordinator start];
}

// Shows Privacy screen.
- (void)showPrivacy {
  // TODO(crbug.com/40067451): Switch back to DCHECK if the number of reports is
  // low.
  DUMP_WILL_BE_CHECK(!_privacyCoordinator);
  _privacyCoordinator = [[PrivacyCoordinator alloc]
      initWithBaseNavigationController:self.navigationController
                               browser:_browser];
  _privacyCoordinator.delegate = self;
  [_privacyCoordinator start];
}

// Sets the NSUserDefaults BOOL `value` for `key`.
- (void)setBooleanNSUserDefaultsValue:(BOOL)value forKey:(NSString*)key {
  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  [defaults setBool:value forKey:key];
  [defaults synchronize];
}

// Returns YES if a "Debug" section should be shown. This is always true for
// Chromium builds, but for official builds it is gated by an experimental flag
// because the "Debug" section should never be showing in stable channel.
- (BOOL)hasDebugSection {
#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
  return YES;
#else
  if (experimental_flags::IsMemoryDebuggingEnabled()) {
    return YES;
  }
  return NO;
#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
}

// Updates the identity cell.
- (void)updateIdentityAccountItem:(TableViewAccountItem*)identityAccountItem {
  AuthenticationService* authService =
      AuthenticationServiceFactory::GetForBrowserState(_browserState);
  _identity = authService->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
  if (!_identity) {
    // This could occur during the sign out process. Just ignore as the account
    // cell will be replaced by the "Sign in" button.
    return;
  }
  identityAccountItem.image =
      self.accountManagerService->GetIdentityAvatarWithIdentity(
          _identity, IdentityAvatarSize::TableViewIcon);
  identityAccountItem.text = _identity.userFullName;
  identityAccountItem.detailText = _identity.userEmail;

  syncer::SyncService* syncService =
      SyncServiceFactory::GetForBrowserState(_browserState);
  DCHECK(syncService);
  identityAccountItem.shouldDisplayError =
      GetAccountErrorUIInfo(syncService) != nil;
}

- (void)reloadAccountCell {
  if (![self.tableViewModel
          hasItemForItemType:SettingsItemTypeAccount
           sectionIdentifier:SettingsSectionIdentifierAccount]) {
    return;
  }
  NSIndexPath* accountCellIndexPath = [self.tableViewModel
      indexPathForItemType:SettingsItemTypeAccount
         sectionIdentifier:SettingsSectionIdentifierAccount];
  TableViewAccountItem* identityAccountItem =
      base::apple::ObjCCast<TableViewAccountItem>(
          [self.tableViewModel itemAtIndexPath:accountCellIndexPath]);
  if (identityAccountItem) {
    [self updateIdentityAccountItem:identityAccountItem];
    [self reconfigureCellsForItems:@[ identityAccountItem ]];
  }
}

- (void)maybeShowSigninIPH {
  if (_settingsAreDismissed) {
    return;
  }
  AuthenticationService* authService =
      AuthenticationServiceFactory::GetForBrowserState(
          _browser->GetBrowserState());
  BOOL shouldShowSigninIPH =
      authService->HasPrimaryIdentity(signin::ConsentLevel::kSignin) &&
      [self shouldReplaceSyncSettingsWithAccountSettings];
  if (!shouldShowSigninIPH) {
    return;
  }

  UITableViewCell* accountCell = nil;
  for (UITableViewCell* cell in [self.tableView visibleCells]) {
    if ([cell isKindOfClass:[TableViewAccountCell class]]) {
      accountCell = cell;
      break;
    }
  }
  if (!accountCell) {
    return;
  }

  __weak __typeof(self) weakSelf = self;
  CallbackWithIPHDismissalReasonType dismissalCallback =
      ^(IPHDismissalReasonType dismissReason,
        feature_engagement::Tracker::SnoozeAction snoozeAction) {
        [weakSelf signinIPHDismissed];
      };
  _bubblePresenter = [[BubbleViewControllerPresenter alloc]
           initWithText:l10n_util::GetNSString(IDS_IOS_SETTING_IPH_SIGNIN)
                  title:nil
                  image:nil
         arrowDirection:BubbleArrowDirectionUp
              alignment:BubbleAlignmentCenter
             bubbleType:BubbleViewTypeDefault
      dismissalCallback:dismissalCallback];
  CGPoint anchorPointInCell = CGPointMake(CGRectGetMidX(accountCell.bounds),
                                          CGRectGetMaxY(accountCell.bounds));
  CGPoint anchorPointInWindow = [self.view.window convertPoint:anchorPointInCell
                                                      fromView:accountCell];

  // The IPH must be presented if
  // `_featureEngagementTracker->ShouldTriggerHelpUI()` returns true.
  BOOL canShowSigninIPH =
      [_bubblePresenter canPresentInView:self.view
                             anchorPoint:anchorPointInWindow] &&
      _featureEngagementTracker->ShouldTriggerHelpUI(
          feature_engagement::kIPHiOSReplaceSyncPromosWithSignInPromos);
  if (canShowSigninIPH) {
    [_bubblePresenter presentInViewController:self
                                  anchorPoint:anchorPointInWindow];
  } else {
    _bubblePresenter = nil;
  }
}

- (void)signinIPHDismissed {
  _featureEngagementTracker->Dismissed(
      feature_engagement::kIPHiOSReplaceSyncPromosWithSignInPromos);
  _bubblePresenter = nil;
}

// Updates the Sync item to display the right icon and status message in the
// cell.
- (void)updateSyncItem:(TableViewDetailIconItem*)googleSyncItem {
  switch (GetSyncFeatureState(
      SyncServiceFactory::GetForBrowserState(_browserState))) {
    case SyncState::kSyncConsentOff: {
      googleSyncItem.detailText = l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
      googleSyncItem.iconImage = CustomSettingsRootSymbol(kSyncDisabledSymbol);
      googleSyncItem.iconBackgroundColor = [UIColor colorNamed:kGrey400Color];
      googleSyncItem.iconTintColor = UIColor.whiteColor;
      googleSyncItem.iconCornerRadius = kColorfulBackgroundSymbolCornerRadius;
      break;
    }
    case SyncState::kSyncOff:
    case SyncState::kSyncEnabledWithNoSelectedTypes: {
      googleSyncItem.detailText = nil;
      googleSyncItem.iconImage = CustomSettingsRootSymbol(kSyncDisabledSymbol);
      googleSyncItem.iconBackgroundColor = [UIColor colorNamed:kGrey400Color];
      googleSyncItem.iconTintColor = UIColor.whiteColor;
      googleSyncItem.iconCornerRadius = kColorfulBackgroundSymbolCornerRadius;
      break;
    }
    case SyncState::kSyncEnabledWithError: {
      syncer::SyncService* syncService =
          SyncServiceFactory::GetForBrowserState(_browserState);
      googleSyncItem.detailText =
          GetSyncErrorDescriptionForSyncService(syncService);
      googleSyncItem.iconImage = DefaultSettingsRootSymbol(kSyncErrorSymbol);
      googleSyncItem.iconBackgroundColor = [UIColor colorNamed:kRed500Color];
      googleSyncItem.iconTintColor = UIColor.whiteColor;
      googleSyncItem.iconCornerRadius = kColorfulBackgroundSymbolCornerRadius;
      // Return a vertical layout of title / subtitle in the case of a sync
      // error.
      googleSyncItem.textLayoutConstraintAxis = UILayoutConstraintAxisVertical;
      return;
    }
    case SyncState::kSyncEnabled: {
      googleSyncItem.detailText = l10n_util::GetNSString(IDS_IOS_SETTING_ON);

      googleSyncItem.iconImage = DefaultSettingsRootSymbol(kSyncEnabledSymbol);
      googleSyncItem.iconBackgroundColor = [UIColor colorNamed:kGreen500Color];
      googleSyncItem.iconTintColor = UIColor.whiteColor;
      googleSyncItem.iconCornerRadius = kColorfulBackgroundSymbolCornerRadius;
      break;
    }
    case SyncState::kSyncDisabledByAdministrator:
      // Nothing to update.
      break;
  }
  // Needed to update the item text layout in the case that it was previously
  // set to UILayoutConstraintAxisVertical due to a sync error.
  googleSyncItem.textLayoutConstraintAxis = UILayoutConstraintAxisHorizontal;
}

// Check if the default search engine is managed by policy.
- (BOOL)isDefaultSearchEngineManagedByPolicy {
  const base::Value::Dict& dict = _browserState->GetPrefs()->GetDict(
      DefaultSearchManager::kDefaultSearchProviderDataPrefName);

  if (dict.FindBoolByDottedPath(DefaultSearchManager::kDisabledByPolicy) ||
      dict.FindBoolByDottedPath(prefs::kDefaultSearchProviderEnabled))
    return YES;
  return NO;
}

// Returns the text to be displayed by the managed Search Engine item.
- (NSString*)managedSearchEngineDetailText {
  const base::Value::Dict& dict = _browserState->GetPrefs()->GetDict(
      DefaultSearchManager::kDefaultSearchProviderDataPrefName);
  if (dict.FindBoolByDottedPath(DefaultSearchManager::kDisabledByPolicy)) {
    // Default search engine is disabled by policy.
    return l10n_util::GetNSString(
        IDS_IOS_SEARCH_ENGINE_SETTING_DISABLED_STATUS);
  }
  // Default search engine is enabled and set by policy.
  const std::string* status =
      dict.FindStringByDottedPath(DefaultSearchManager::kShortName);
  return base::SysUTF8ToNSString(*status);
}

// Returns the appropriate text to update the title for the feed item.
- (NSString*)feedItemTitle {
  AuthenticationService* authService =
      AuthenticationServiceFactory::GetForBrowserState(
          _browser->GetBrowserState());
  BOOL isSignedIn =
      authService->HasPrimaryIdentity(signin::ConsentLevel::kSignin);
  return (isSignedIn && IsWebChannelsEnabled())
             ? l10n_util::GetNSString(IDS_IOS_DISCOVER_AND_FOLLOWING_FEED_TITLE)
             : l10n_util::GetNSString(IDS_IOS_DISCOVER_FEED_TITLE);
}

// Decides whether the default browser blue dot promo should be active, and adds
// the blue dot badge to the right settings row if it is.
- (void)maybeActivateDefaultBrowserBlueDotPromo:
    (TableViewDetailIconItem*)defaultBrowserCellItem {
  if (self.showingDefaultBrowserNotificationDot) {
    // Add the blue dot promo badge to the default browser row.
    defaultBrowserCellItem.badgeType = BadgeType::kNotificationDot;
  }
}

// Add or remove the "new" IPH badge from the address bar settings row. The
// badge is shown a maximum of `kMaxShowCountNewIPHBadge` times.
- (void)updateAddressBarNewIPHBadge {
  CHECK(_addressBarPreferenceItem);

  if (!_browserState) {
    return;
  }

  PrefService* prefService = _browserState->GetPrefs();
  NSInteger showCount =
      prefService->GetInteger(prefs::kAddressBarSettingsNewBadgeShownCount);

  BadgeType badgeType = BadgeType::kNone;

  const BOOL isFreshInstall = IsFirstRunRecent(kFreshInstallTimeDelta);

  if (!isFreshInstall && showCount < kMaxShowCountNewIPHBadge) {
    badgeType = BadgeType::kNew;
    prefService->SetInteger(prefs::kAddressBarSettingsNewBadgeShownCount,
                            showCount + 1);
  }

  if (badgeType != _addressBarPreferenceItem.badgeType) {
    _addressBarPreferenceItem.badgeType = badgeType;
    [self reconfigureCellsForItems:@[ _addressBarPreferenceItem ]];
  }
}

// Updates the string indicating the push notification state.
- (void)updateNotificationsDetailText {
  if (!_notificationsItem) {
    return;
  }

  NSString* detailText = nil;
  AuthenticationService* authService =
      AuthenticationServiceFactory::GetForBrowserState(_browserState);
  id<SystemIdentity> identity =
      authService->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
  PrefService* prefService = _browserState->GetPrefs();
  const std::string& gaiaID = base::SysNSStringToUTF8(identity.gaiaID);
  push_notification_settings::ClientPermissionState permission_state =
      push_notification_settings::GetNotificationPermissionState(gaiaID,
                                                                 prefService);
  if (permission_state ==
      push_notification_settings::ClientPermissionState::ENABLED) {
    detailText = l10n_util::GetNSString(IDS_IOS_SETTING_ON);
  } else if (permission_state ==
             push_notification_settings::ClientPermissionState::DISABLED) {
    detailText = l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
  }

  _notificationsItem.detailText = detailText;
  [self reconfigureCellsForItems:@[ _notificationsItem ]];
}

- (void)showDownloadsSettings {
  if (_downloadsSettingsCoordinator) {
    [_downloadsSettingsCoordinator stop];
    _downloadsSettingsCoordinator = nil;
  }

  _downloadsSettingsCoordinator = [[DownloadsSettingsCoordinator alloc]
      initWithBaseNavigationController:self.navigationController
                               browser:_browser];
  _downloadsSettingsCoordinator.delegate = self;
  [_downloadsSettingsCoordinator start];
}

// Returns YES if the Notifications settings should show.
- (BOOL)shouldShowNotificationsSettings {
  return base::FeatureList::IsEnabled(kNotificationSettingsMenuItem) &&
         (IsPriceNotificationsEnabled() ||
          IsContentNotificationEnabled(_browserState) ||
          IsIOSTipsNotificationsEnabled());
}

// Records that the user has reached the impression limit for the enhanced safe
// browsing inline promo.
- (void)maybeRecordEnhancedSafeBrowsingImpressionLimitReached {
  feature_engagement::Tracker* tracker =
      feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
  std::vector<std::pair<feature_engagement::EventConfig, int>> events =
      tracker->ListEvents(
          feature_engagement::kIPHiOSInlineEnhancedSafeBrowsingPromoFeature);
  for (const auto& event : events) {
    if (event.first.name == "inline_enhanced_safe_browsing_promo_trigger") {
      unsigned int impressionLimit = event.first.comparator.value;
      unsigned int numberOfImpressions = event.second;
      if (impressionLimit == numberOfImpressions) {
        base::RecordAction(base::UserMetricsAction(
            "MobileSettingsEnhancedSafeBrowsingInlineProm"
            "oImpressionLimitReached"));
      }
    }
  }
}

// Returns YES if the Enhanced Safe Browsing inline promo should show.
- (BOOL)shouldShowEnhancedSafeBrowsingPromo {
  // First check if another active settings page (e.g. in another
  // window) has an active promo. If so, just return that the promo should be
  // shown here without querying the FET. Only query the FET if there is no
  // currently active promo.
  feature_engagement::Tracker* tracker =
      feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
  EnhancedSafeBrowsingActivePromoData* data =
      static_cast<EnhancedSafeBrowsingActivePromoData*>(
          tracker->GetUserData(EnhancedSafeBrowsingActivePromoData::key));
  if (data) {
    data->active_promos++;
    return YES;
  }

  // The user must be:
  //   1.) Signed-in
  //   2.) Have Chrome set to default browser.
  //   3.) Have Safe Browsing standard protection enabled.
  //   4.) One of the trigerring criteria has been met.
  //   5.) Not have their Safe Browsing preferences enterprise-managed.
  AuthenticationService* authService =
      AuthenticationServiceFactory::GetForBrowserState(_browserState);
  bool isSignedInAndSynced =
      authService->HasPrimaryIdentity(signin::ConsentLevel::kSignin);
  bool isDefaultBrowser = IsChromeLikelyDefaultBrowser();
  bool isStandardProtectionEnabled =
      safe_browsing::GetSafeBrowsingState(*_browserState->GetPrefs()) ==
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION;
  bool triggerCriteriaMet = tracker->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSInlineEnhancedSafeBrowsingPromoFeature);
  bool isEnterpriseManaged =
      safe_browsing::IsSafeBrowsingPolicyManaged(*_browserState->GetPrefs());

  if (!isSignedInAndSynced || !isDefaultBrowser ||
      !isStandardProtectionEnabled || !triggerCriteriaMet ||
      isEnterpriseManaged) {
    return NO;
  }

  bool promoIsTriggered = tracker->ShouldTriggerHelpUI(
      feature_engagement::kIPHiOSInlineEnhancedSafeBrowsingPromoFeature);
  CHECK(promoIsTriggered, base::NotFatalUntil::M131);

  std::unique_ptr<EnhancedSafeBrowsingActivePromoData> new_data =
      std::make_unique<EnhancedSafeBrowsingActivePromoData>();
  new_data->active_promos++;
  tracker->SetUserData(EnhancedSafeBrowsingActivePromoData::key,
                       std::move(new_data));

  return YES;
}

// Check if this is the last active Enhanced Safe Browsing promo shown and
// dismisses the FET if so.
- (void)removeEnhancedSafeBrowsingPromoFETDataIfNeeded {
  CHECK(_browserState, base::NotFatalUntil::M131);
  feature_engagement::Tracker* tracker =
      feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
  EnhancedSafeBrowsingActivePromoData* data =
      static_cast<EnhancedSafeBrowsingActivePromoData*>(
          tracker->GetUserData(EnhancedSafeBrowsingActivePromoData::key));
  if (!data) {
    return;
  }

  data->active_promos--;
  if (data->active_promos <= 0) {
    tracker->RemoveUserData(EnhancedSafeBrowsingActivePromoData::key);
    tracker->Dismissed(
        feature_engagement::kIPHiOSInlineEnhancedSafeBrowsingPromoFeature);
  }
}

#pragma mark - Sign in

- (void)showSignIn {
  if (self.isSigninInProgress) {
    // According to crbug.com/1498153, it is possible for the user to tap twice
    // on the sign-in cell from the settings to open the sign-in dialog.
    // If this happens, the second tap should ignored.
    return;
  }
  self.isSigninInProgress = YES;
  __weak __typeof(self) weakSelf = self;
  ShowSigninCommand* command = [[ShowSigninCommand alloc]
      initWithOperation:AuthenticationOperation::kSheetSigninAndHistorySync
               identity:nil
            accessPoint:signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS
            promoAction:signin_metrics::PromoAction::
                            PROMO_ACTION_NO_SIGNIN_PROMO
               callback:^(SigninCoordinatorResult result,
                          SigninCompletionInfo* completionInfo) {
                 BOOL success = result == SigninCoordinatorResultSuccess;
                 [weakSelf didFinishSignin:success];
               }];
  [self.applicationHandler showSignin:command baseViewController:self];
}

- (void)didFinishSignin:(BOOL)signedIn {
  if (_settingsAreDismissed) {
    return;
  }

  // The sign-in is done. The sign-in promo cell or account cell can be
  // reloaded.
  DCHECK(self.isSigninInProgress);
  self.isSigninInProgress = NO;
  [self reloadData];

  // Post the task to show signin IPH so that the UI has had time to refresh
  // after `reloadData`.
  __weak __typeof(self) weakSelf = self;
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(^{
        [weakSelf maybeShowSigninIPH];
      }));
}

#pragma mark SettingsControllerProtocol

- (void)reportDismissalUserAction {
  base::RecordAction(base::UserMetricsAction("MobileSettingsClose"));
}

- (void)reportBackUserAction {
  // Not called for root settings controller.
  NOTREACHED_IN_MIGRATION();
}

- (void)settingsWillBeDismissed {
  DCHECK(!_settingsAreDismissed);

  // Remove Enhanced Safe Browsing Promo.
  [self removeEnhancedSafeBrowsingPromoFETDataIfNeeded];

  // Stop children coordinators.
  [_googleServicesSettingsCoordinator stop];
  _googleServicesSettingsCoordinator.delegate = nil;
  _googleServicesSettingsCoordinator = nil;

  [_safetyCheckCoordinator stop];
  _safetyCheckCoordinator = nil;

  [_passwordsCoordinator stop];
  _passwordsCoordinator.delegate = nil;
  _passwordsCoordinator = nil;

  [_accountsCoordinator stop];
  _accountsCoordinator = nil;

  [_notificationsCoordinator stop];
  _notificationsCoordinator = nil;

  [_privacyCoordinator stop];
  _privacyCoordinator = nil;

  [_manageSyncSettingsCoordinator stop];
  _manageSyncSettingsCoordinator = nil;

  [_tabsCoordinator stop];
  _tabsCoordinator = nil;

  [_switchProfileCoordinator stop];
  _switchProfileCoordinator = nil;

  [_addressBarPreferenceCoordinator stop];
  _addressBarPreferenceCoordinator = nil;

  [_downloadsSettingsCoordinator stop];
  _downloadsSettingsCoordinator = nil;

  // Stop observable prefs.
  [_showMemoryDebugToolsEnabled stop];
  [_showMemoryDebugToolsEnabled setObserver:nil];
  _showMemoryDebugToolsEnabled = nil;

  [_articlesEnabled stop];
  [_articlesEnabled setObserver:nil];
  _articlesEnabled = nil;

  [_allowChromeSigninPreference stop];
  [_allowChromeSigninPreference setObserver:nil];
  _allowChromeSigninPreference = nil;

  [_bottomOmniboxEnabled stop];
  [_bottomOmniboxEnabled setObserver:nil];
  _bottomOmniboxEnabled = nil;

  [_contentSuggestionPolicyEnabled stop];
  [_contentSuggestionPolicyEnabled setObserver:nil];
  _contentSuggestionPolicyEnabled = nil;

  [_contentSuggestionForSupervisedUsersEnabled stop];
  [_contentSuggestionForSupervisedUsersEnabled setObserver:nil];
  _contentSuggestionForSupervisedUsersEnabled = nil;

  // Remove pref changes registrations.
  _prefChangeRegistrar.RemoveAll();

  // Remove observer bridges.
  _prefObserverBridge.reset();
  _passwordCheckObserver.reset();
  _searchEngineObserverBridge.reset();
  _syncObserverBridge.reset();
  _identityObserverBridge.reset();
  _accountManagerServiceObserver.reset();

  // Remove PrefObserverDelegates.
  _notificationsObserver.delegate = nil;
  [_notificationsObserver disconnect];
  _notificationsObserver = nil;

  // Clear C++ ivars.
  _voiceLocaleCode.Destroy();
  _passwordCheckManager.reset();
  _browser = nullptr;
  _browserState = nullptr;

  _settingsAreDismissed = YES;
}

#pragma mark SyncObserverModelBridge

- (void)onSyncStateChanged {
  // Feed settings are subject to sign-in status and account type, ensure
  // that these sections are updated as necessary.
  [self booleanDidChange:_contentSuggestionPolicyEnabled];

  [self updateSigninSection];
  // The Identity section may be added or removed depending on sign-in is
  // allowed. Reload all sections in the model to account for the change.
  [self.tableView reloadData];
}

#pragma mark - SearchEngineObserverBridge

- (void)searchEngineChanged {
  if (_managedSearchEngineItem) {
    _managedSearchEngineItem.statusText = [self managedSearchEngineDetailText];
    [self reconfigureCellsForItems:@[ _managedSearchEngineItem ]];
  } else {
    // The two items are mutually exclusive.
    _defaultSearchEngineItem.detailText =
        base::SysUTF16ToNSString(GetDefaultSearchEngineName(
            ios::TemplateURLServiceFactory::GetForBrowserState(_browserState)));
    [self reconfigureCellsForItems:@[ _defaultSearchEngineItem ]];
  }
}

#pragma mark - ChromeAccountManagerServiceObserver

- (void)identityUpdated:(id<SystemIdentity>)identity {
  if ([_identity isEqual:identity]) {
    [self reloadAccountCell];
  }
}

- (void)onChromeAccountManagerServiceShutdown:
    (ChromeAccountManagerService*)accountManagerService {
  // TODO(crbug.com/40926211): settingsWillBeDismissed must be called before the
  // AccountManagerService is destroyed. Switch to DCHECK if the number of
  // reports is low.
  DUMP_WILL_BE_CHECK(!_accountManagerServiceObserver.get());
}

#pragma mark - BooleanObserver

- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
  if (observableBoolean == _showMemoryDebugToolsEnabled) {
    // Update the Item.
    _showMemoryDebugToolsItem.on = [_showMemoryDebugToolsEnabled value];
    // Update the Cell.
    [self reconfigureCellsForItems:@[ _showMemoryDebugToolsItem ]];
  } else if (observableBoolean == _allowChromeSigninPreference) {
    [self updateSigninSection];
    // The Identity section may be added or removed depending on sign-in is
    // allowed. Reload all sections in the model to account for the change.
    [self.tableView reloadData];
  } else if (observableBoolean == _articlesEnabled) {
    self.feedSettingsItem.on = [_articlesEnabled value];
    [self reconfigureCellsForItems:@[ self.feedSettingsItem ]];
  } else if (observableBoolean == _contentSuggestionPolicyEnabled) {
    NSIndexPath* itemIndexPath;
    NSInteger itemTypeToRemove;
    TableViewItem* itemToAdd;
    if ([_contentSuggestionPolicyEnabled value]) {
      if (![self.tableViewModel hasItem:self.managedFeedSettingsItem]) {
        return;
      }
      itemIndexPath =
          [self.tableViewModel indexPathForItem:self.managedFeedSettingsItem];
      itemTypeToRemove = SettingsItemTypeManagedArticlesForYou;
      itemToAdd = self.feedSettingsItem;
    } else {
      if (![self.tableViewModel hasItem:self.feedSettingsItem]) {
        return;
      }
      itemIndexPath =
          [self.tableViewModel indexPathForItem:self.feedSettingsItem];
      itemTypeToRemove = SettingsItemTypeArticlesForYou;
      itemToAdd = self.managedFeedSettingsItem;
    }
    [self.tableViewModel removeItemWithType:itemTypeToRemove
                  fromSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
    [self.tableViewModel insertItem:itemToAdd
            inSectionWithIdentifier:SettingsSectionIdentifierAdvanced
                            atIndex:itemIndexPath.row];
    [self.tableView reloadRowsAtIndexPaths:@[ itemIndexPath ]
                          withRowAnimation:UITableViewRowAnimationAutomatic];
  } else if (observableBoolean == _contentSuggestionForSupervisedUsersEnabled) {
    if ([_contentSuggestionForSupervisedUsersEnabled value]) {
      // Reset Feed settings back on the content suggestion policy.
      [self booleanDidChange:_contentSuggestionPolicyEnabled];
      return;
    }
    NSInteger itemTypeToRemove;
    if ([self.tableViewModel hasItem:self.feedSettingsItem]) {
      itemTypeToRemove = SettingsItemTypeArticlesForYou;
    } else if ([self.tableViewModel hasItem:self.managedFeedSettingsItem]) {
      itemTypeToRemove = SettingsItemTypeManagedArticlesForYou;
    } else {
      return;
    }
    [self.tableViewModel removeItemWithType:itemTypeToRemove
                  fromSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
    NSUInteger index = [self.tableViewModel
        sectionForSectionIdentifier:SettingsSectionIdentifierAdvanced];
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:index]
                  withRowAnimation:UITableViewRowAnimationAutomatic];
  } else if (observableBoolean == _bottomOmniboxEnabled) {
    _addressBarPreferenceItem.detailText =
        [_bottomOmniboxEnabled value]
            ? l10n_util::GetNSString(IDS_IOS_BOTTOM_ADDRESS_BAR_OPTION)
            : l10n_util::GetNSString(IDS_IOS_TOP_ADDRESS_BAR_OPTION);
    [self reconfigureCellsForItems:@[ _addressBarPreferenceItem ]];
  } else {
    NOTREACHED_IN_MIGRATION();
  }
}

#pragma mark - PasswordCheckObserver

- (void)passwordCheckStateDidChange:(PasswordCheckState)state {
  [self updateSafetyCheckItemTrailingIcon];
}

- (void)insecureCredentialsDidChange {
  [self updateSafetyCheckItemTrailingIcon];
}

- (void)passwordCheckManagerWillShutdown {
  _passwordCheckObserver.reset();
}

#pragma mark - PrefObserverDelegate

- (void)onPreferenceChanged:(const std::string&)preferenceName {
  if (preferenceName == prefs::kVoiceSearchLocale) {
    voice::SpeechInputLocaleConfig* localeConfig =
        voice::SpeechInputLocaleConfig::GetInstance();
    voice::SpeechInputLocale locale =
        _voiceLocaleCode.GetValue().length()
            ? localeConfig->GetLocaleForCode(_voiceLocaleCode.GetValue())
            : localeConfig->GetDefaultLocale();
    NSString* languageName = base::SysUTF16ToNSString(locale.display_name);
    _voiceSearchDetailItem.detailText = languageName;
    [self reconfigureCellsForItems:@[ _voiceSearchDetailItem ]];
  }

  if (preferenceName == password_manager::prefs::kCredentialsEnableService) {
    BOOL passwordsEnabled =
        _browserState->GetPrefs()->GetBoolean(preferenceName);
    NSString* passwordsDetail =
        passwordsEnabled ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
                         : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
    _passwordsDetailItem.detailText = passwordsDetail;
    [self reconfigureCellsForItems:@[ _passwordsDetailItem ]];
  }

  if (preferenceName == autofill::prefs::kAutofillProfileEnabled) {
    BOOL autofillProfileEnabled =
        autofill::prefs::IsAutofillProfileEnabled(_browserState->GetPrefs());
    NSString* detailText = autofillProfileEnabled
                               ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
                               : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
    _autoFillProfileDetailItem.detailText = detailText;
    [self reconfigureCellsForItems:@[ _autoFillProfileDetailItem ]];
  }

  if (preferenceName == autofill::prefs::kAutofillCreditCardEnabled) {
    BOOL autofillCreditCardEnabled =
        autofill::prefs::IsAutofillPaymentMethodsEnabled(
            _browserState->GetPrefs());
    NSString* detailText = autofillCreditCardEnabled
                               ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
                               : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
    _autoFillCreditCardDetailItem.detailText = detailText;
    [self reconfigureCellsForItems:@[ _autoFillCreditCardDetailItem ]];
  }

  if (preferenceName ==
      DefaultSearchManager::kDefaultSearchProviderDataPrefName) {
    // Reloading the data is needed because the item type and its class are
    // changing.
    [self reloadData];
  }
}

#pragma mark - GoogleServicesSettingsCoordinatorDelegate

- (void)googleServicesSettingsCoordinatorDidRemove:
    (GoogleServicesSettingsCoordinator*)coordinator {
  DCHECK_EQ(_googleServicesSettingsCoordinator, coordinator);
  [_googleServicesSettingsCoordinator stop];
  _googleServicesSettingsCoordinator.delegate = nil;
  _googleServicesSettingsCoordinator = nil;
}

#pragma mark - SafetyCheckCoordinatorDelegate

- (void)safetyCheckCoordinatorDidRemove:(SafetyCheckCoordinator*)coordinator {
  DCHECK_EQ(_safetyCheckCoordinator, coordinator);
  [_safetyCheckCoordinator stop];
  _safetyCheckCoordinator.delegate = nil;
  _safetyCheckCoordinator = nil;
}

#pragma mark - PasswordsCoordinatorDelegate

- (void)passwordsCoordinatorDidRemove:(PasswordsCoordinator*)coordinator {
  DCHECK_EQ(_passwordsCoordinator, coordinator);
  [_passwordsCoordinator stop];
  _passwordsCoordinator.delegate = nil;
  _passwordsCoordinator = nil;
}

#pragma mark - PasswordManagerReauthenticationDelegate

- (void)dismissPasswordManagerAfterFailedReauthentication {
  // Pop everything up to the Settings page.
  // When there is content presented, don't animate the dismissal of the view
  // controllers in the navigation controller to prevent revealing passwords
  // when the presented content is the one covered by the reauthentication UI.

  UINavigationController* navigationController = self.navigationController;
  UIViewController* topViewController = navigationController.topViewController;
  UIViewController* presentedViewController =
      topViewController.presentedViewController;

  [navigationController popToViewController:self
                                   animated:presentedViewController == nil];

  [presentedViewController.presentingViewController
      dismissViewControllerAnimated:YES
                         completion:nil];
}

#pragma mark - NotificationsCoordinatorDelegate

- (void)notificationsCoordinatorDidRemove:
    (NotificationsCoordinator*)coordinator {
  DCHECK_EQ(_notificationsCoordinator, coordinator);
  [_notificationsCoordinator stop];
  _notificationsCoordinator = nil;
}

#pragma mark - PrivacyCoordinatorDelegate

- (void)privacyCoordinatorViewControllerWasRemoved:
    (PrivacyCoordinator*)coordinator {
  DCHECK_EQ(_privacyCoordinator, coordinator);
  [_privacyCoordinator stop];
  _privacyCoordinator = nil;
}

#pragma mark - IdentityManagerObserverBridgeDelegate

// Notifies this controller that the sign in state has changed.
- (void)signinStateDidChange {
  // While the sign-in view is in progress, the TableView should not be
  // updated. Otherwise, it would lead to an UI glitch either while the sign
  // in UI is appearing or disappearing. The TableView will be reloaded once
  // the animation is finished.
  // See: -[SettingsTableViewController didFinishSignin:].
  if (self.isSigninInProgress)
    return;
  // Sign in state changes are rare. Just reload the entire table when
  // this happens.
  [self reloadData];
}

- (void)onPrimaryAccountChanged:
    (const signin::PrimaryAccountChangeEvent&)event {
  [self signinStateDidChange];
}

#pragma mark - UIAdaptivePresentationControllerDelegate

- (void)presentationControllerDidDismiss:
    (UIPresentationController*)presentationController {
  base::RecordAction(base::UserMetricsAction("IOSSettingsCloseWithSwipe"));
}

#pragma mark - PopoverLabelViewControllerDelegate

- (void)didTapLinkURL:(NSURL*)URL {
  [self view:nil didTapLinkURL:[[CrURL alloc] initWithNSURL:URL]];
}

#pragma mark - ManageSyncSettingsCoordinatorDelegate

- (void)manageSyncSettingsCoordinatorWasRemoved:
    (ManageSyncSettingsCoordinator*)coordinator {
  DCHECK_EQ(_manageSyncSettingsCoordinator, coordinator);
  [_manageSyncSettingsCoordinator stop];
  _manageSyncSettingsCoordinator = nil;
}

- (NSString*)manageSyncSettingsCoordinatorTitle {
  return l10n_util::GetNSString(IDS_IOS_GOOGLE_SYNC_SETTINGS_TITLE);
}

#pragma mark - NotificationsSettingsObserverDelegate

- (void)notificationsSettingsDidChangeForClient:
    (PushNotificationClientId)clientID {
  [self updateNotificationsDetailText];
}

#pragma mark - DownloadsSettingsCoordinatorDelegate

- (void)downloadsSettingsCoordinatorWasRemoved:
    (DownloadsSettingsCoordinator*)coordinator {
  [_downloadsSettingsCoordinator stop];
  _downloadsSettingsCoordinator = nil;
}

#pragma mark - EnhancedSafeBrowsingInlinePromoDelegate

- (void)dismissEnhancedSafeBrowsingInlinePromo {
  SettingsSectionIdentifier sectionID = SettingsSectionIdentifierESBPromo;
  if (![self.tableViewModel hasSectionForSectionIdentifier:sectionID]) {
    return;
  }

  NSUInteger index =
      [self.tableViewModel sectionForSectionIdentifier:sectionID];
  [self.tableViewModel removeItemWithType:SettingsItemTypeESBPromo
                fromSectionWithIdentifier:sectionID
                                  atIndex:0];
  [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:index]
                withRowAnimation:UITableViewRowAnimationFade];

  feature_engagement::Tracker* tracker =
      feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
  tracker->NotifyEvent(
      feature_engagement::events::kInlineEnhancedSafeBrowsingPromoClosed);
  base::RecordAction(base::UserMetricsAction(
      "MobileSettingsEnhancedSafeBrowsingInlinePromoDismiss"));
  [self removeEnhancedSafeBrowsingPromoFETDataIfNeeded];
}

- (void)showSafeBrowsingSettingsMenu {
  id<SettingsCommands> handler =
      HandlerForProtocol(_browser->GetCommandDispatcher(), SettingsCommands);
  [handler showSafeBrowsingSettingsFromPromoInteraction];
  base::RecordAction(base::UserMetricsAction(
      "MobileSettingsEnhancedSafeBrowsingInlinePromoProceed"));
}

@end