// Copyright 2013 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_navigation_controller.h"
#import <MaterialComponents/MaterialSnackbar.h>
#import "base/apple/foundation_util.h"
#import "base/check.h"
#import "base/ios/ios_util.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "components/autofill/core/browser/personal_data_manager.h"
#import "components/password_manager/core/browser/ui/credential_ui_entry.h"
#import "components/password_manager/core/browser/ui/password_check_referrer.h"
#import "components/strings/grit/components_strings.h"
#import "components/sync/service/sync_service.h"
#import "ios/chrome/browser/autofill/model/personal_data_manager_factory.h"
#import "ios/chrome/browser/autofill/ui_bundled/autofill_credit_card_util.h"
#import "ios/chrome/browser/keyboard/ui_bundled/UIKeyCommand+Chrome.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/profile/profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/quick_delete_commands.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/ui/symbols/chrome_icon.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/sync/model/enterprise_utils.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/settings/autofill/autofill_credit_card_edit_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_credit_card_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_profile_edit_coordinator.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_coordinator.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/features.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/google_services/google_services_settings_coordinator.h"
#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.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/notifications/notifications_coordinator.h"
#import "ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.h"
#import "ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h"
#import "ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.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/privacy_safe_browsing_coordinator.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller_constants.h"
#import "ios/chrome/browser/ui/settings/settings_root_view_controlling.h"
#import "ios/chrome/browser/ui/settings/settings_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/tabs/inactive_tabs/inactive_tabs_settings_coordinator.h"
#import "ios/chrome/browser/ui/settings/utils/password_utils.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/reauthentication/reauthentication_module.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/user_feedback/user_feedback_api.h"
#import "ui/base/l10n/l10n_util_mac.h"
namespace {
// Helper function to configure handlers for child view controllers.
void ConfigureHandlers(id<SettingsRootViewControlling> controller,
CommandDispatcher* dispatcher) {
controller.applicationHandler =
HandlerForProtocol(dispatcher, ApplicationCommands);
controller.browserHandler = HandlerForProtocol(dispatcher, BrowserCommands);
controller.settingsHandler = HandlerForProtocol(dispatcher, SettingsCommands);
controller.snackbarHandler = HandlerForProtocol(dispatcher, SnackbarCommands);
}
} // namespace
NSString* const kSettingsDoneButtonId = @"kSettingsDoneButtonId";
@interface SettingsNavigationController () <
AutofillProfileEditCoordinatorDelegate,
ClearBrowsingDataCoordinatorDelegate,
GoogleServicesSettingsCoordinatorDelegate,
ManageSyncSettingsCoordinatorDelegate,
NotificationsCoordinatorDelegate,
PasswordDetailsCoordinatorDelegate,
PasswordsCoordinatorDelegate,
PrivacyCoordinatorDelegate,
PrivacySafeBrowsingCoordinatorDelegate,
SafetyCheckCoordinatorDelegate,
UIAdaptivePresentationControllerDelegate,
UINavigationControllerDelegate>
// Google services settings coordinator.
@property(nonatomic, strong)
GoogleServicesSettingsCoordinator* googleServicesSettingsCoordinator;
// Privacy settings coordinator.
@property(nonatomic, strong) PrivacyCoordinator* privacySettingsCoordinator;
// Sync settings coordinator.
@property(nonatomic, strong)
ManageSyncSettingsCoordinator* manageSyncSettingsCoordinator;
// Saved passwords settings coordinator.
@property(nonatomic, strong) PasswordsCoordinator* savedPasswordsCoordinator;
// Password details coordinator.
@property(nonatomic, strong)
PasswordDetailsCoordinator* passwordDetailsCoordinator;
// Autofill profile edit coordinator.
@property(nonatomic, strong)
AutofillProfileEditCoordinator* autofillProfileEditCoordinator;
// TODO(crbug.com/335387869): Delete this coordinator when Quick Delete is fully
// launched. The coordinator for the clear browsing data screen.
@property(nonatomic, strong)
ClearBrowsingDataCoordinator* clearBrowsingDataCoordinator;
// Safety Check coordinator.
@property(nonatomic, strong) SafetyCheckCoordinator* safetyCheckCoordinator;
// Privacy Safe Browsing coordinator.
@property(nonatomic, strong)
PrivacySafeBrowsingCoordinator* privacySafeBrowsingCoordinator;
// Coordinator for the inactive tabs settings.
@property(nonatomic, strong)
InactiveTabsSettingsCoordinator* inactiveTabsSettingsCoordinator;
// Coordinator for the Notifications settings.
@property(nonatomic, strong) NotificationsCoordinator* notificationsCoordinator;
// Accounts coordinator.
@property(nonatomic, strong) AccountsCoordinator* accountsCoordinator;
// Handler for Snackbar Commands.
@property(nonatomic, weak) id<SnackbarCommands> snackbarCommandsHandler;
// Current UIViewController being presented by this Navigation Controller.
// If nil it means the Navigation Controller is not presenting anything, or the
// VC being presented doesn't conform to
// UIAdaptivePresentationControllerDelegate.
@property(nonatomic, weak)
UIViewController<UIAdaptivePresentationControllerDelegate>*
currentPresentedViewController;
// The SettingsNavigationControllerDelegate for this NavigationController.
@property(nonatomic, weak) id<SettingsNavigationControllerDelegate>
settingsNavigationDelegate;
// The Browser instance this controller is configured with.
@property(nonatomic, assign) Browser* browser;
@end
@implementation SettingsNavigationController
#pragma mark - SettingsNavigationController methods.
+ (instancetype)
mainSettingsControllerForBrowser:(Browser*)browser
delegate:(id<SettingsNavigationControllerDelegate>)
delegate
hasDefaultBrowserBlueDot:(BOOL)hasDefaultBrowserBlueDot {
SettingsTableViewController* controller = [[SettingsTableViewController alloc]
initWithBrowser:browser
hasDefaultBrowserBlueDot:hasDefaultBrowserBlueDot];
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:controller
browser:browser
delegate:delegate];
[controller navigationItem].rightBarButtonItem =
[navigationController doneButton];
return navigationController;
}
+ (instancetype)
accountsControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)delegate {
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
navigationController.accountsCoordinator = [[AccountsCoordinator alloc]
initWithBaseNavigationController:navigationController
browser:browser
closeSettingsOnAddAccount:YES];
navigationController.accountsCoordinator.showSignoutButton = YES;
[navigationController.accountsCoordinator start];
return navigationController;
}
+ (instancetype)
googleServicesControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate {
// GoogleServicesSettings uses a coordinator to be presented, therefore the
// view controller is not accessible. Prefer creating a
// `SettingsNavigationController` with a nil root view controller and then
// use the coordinator to push the GoogleServicesSettings as the first
// root view controller.
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
[navigationController showGoogleServices];
return navigationController;
}
+ (instancetype)
syncSettingsControllerForBrowser:(Browser*)browser
delegate:(id<SettingsNavigationControllerDelegate>)
delegate {
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
[navigationController showSyncServices];
return navigationController;
}
// Creates a new SafetyCheckTableViewController and the chrome
// around it.
+ (instancetype)
safetyCheckControllerForBrowser:(Browser*)browser
delegate:(id<SettingsNavigationControllerDelegate>)
delegate
referrer:(password_manager::PasswordCheckReferrer)
referrer {
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
[navigationController showSafetyCheckAndStartSafetyCheck:referrer];
return navigationController;
}
+ (instancetype)
safeBrowsingControllerForBrowser:(Browser*)browser
delegate:(id<SettingsNavigationControllerDelegate>)
delegate {
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
[navigationController showSafeBrowsing];
return navigationController;
}
+ (instancetype)
syncPassphraseControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate {
SyncEncryptionPassphraseTableViewController* controller =
[[SyncEncryptionPassphraseTableViewController alloc]
initWithBrowser:browser];
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:controller
browser:browser
delegate:delegate];
[controller navigationItem].leftBarButtonItem =
[navigationController cancelButton];
return navigationController;
}
+ (instancetype)
savePasswordsControllerForBrowser:(Browser*)browser
delegate:(id<SettingsNavigationControllerDelegate>)
delegate
showCancelButton:(BOOL)showCancelButton {
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
[navigationController showSavedPasswordsAndShowCancelButton:showCancelButton];
return navigationController;
}
+ (instancetype)
passwordManagerSearchControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate {
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
[navigationController showPasswordManagerSearchPage];
return navigationController;
}
+ (instancetype)
passwordDetailsControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate
credential:
(password_manager::CredentialUIEntry)credential
inEditMode:(BOOL)editMode
showCancelButton:(BOOL)showCancelButton {
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
[navigationController showPasswordDetailsForCredential:credential
inEditMode:editMode
showCancelButton:showCancelButton];
return navigationController;
}
+ (instancetype)
userFeedbackControllerForBrowser:(Browser*)browser
delegate:(id<SettingsNavigationControllerDelegate>)
delegate
userFeedbackData:(UserFeedbackData*)userFeedbackData {
DCHECK(ios::provider::IsUserFeedbackSupported());
id<ApplicationCommands> applicationHandler =
HandlerForProtocol(browser->GetCommandDispatcher(), ApplicationCommands);
UserFeedbackConfiguration* configuration =
[[UserFeedbackConfiguration alloc] init];
configuration.data = userFeedbackData;
configuration.handler = applicationHandler;
configuration.singleSignOnService =
GetApplicationContext()->GetSingleSignOnService();
UIViewController* controller =
ios::provider::CreateUserFeedbackViewController(configuration);
DCHECK(controller);
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:controller
browser:browser
delegate:delegate];
// Fix for https://crbug.com/1042741 (hide the double header display).
navigationController.navigationBarHidden = YES;
// If the controller overrides overrideUserInterfaceStyle, respect that in the
// SettingsNavigationController.
navigationController.overrideUserInterfaceStyle =
controller.overrideUserInterfaceStyle;
return navigationController;
}
+ (instancetype)
privacyControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)delegate {
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
[navigationController showPrivacy];
return navigationController;
}
+ (instancetype)
addressDetailsControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate
address:(const autofill::AutofillProfile*)address
inEditMode:(BOOL)editMode
offerMigrateToAccount:(BOOL)offerMigrateToAccount {
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
[navigationController showAddressDetails:address
inEditMode:editMode
offerMigrateToAccount:offerMigrateToAccount];
return navigationController;
}
+ (instancetype)
autofillProfileControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate {
AutofillProfileTableViewController* controller =
[[AutofillProfileTableViewController alloc] initWithBrowser:browser];
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:controller
browser:browser
delegate:delegate];
// Make sure the cancel button is always present, as the Autofill screen
// isn't just shown from Settings.
[controller navigationItem].leftBarButtonItem =
[navigationController cancelButton];
return navigationController;
}
+ (instancetype)
autofillCreditCardControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate {
AutofillCreditCardTableViewController* controller =
[[AutofillCreditCardTableViewController alloc] initWithBrowser:browser];
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:controller
browser:browser
delegate:delegate];
// Make sure the cancel button is always present, as the Autofill screen
// isn't just shown from Settings.
[controller navigationItem].leftBarButtonItem =
[navigationController cancelButton];
return navigationController;
}
+ (instancetype)
autofillCreditCardEditControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate
creditCard:
(const autofill::CreditCard*)creditCard
inEditMode:(BOOL)editMode {
ChromeBrowserState* browserState =
browser->GetBrowserState()->GetOriginalChromeBrowserState();
autofill::PersonalDataManager* personalDataManager =
autofill::PersonalDataManagerFactory::GetForBrowserState(
browserState->GetOriginalChromeBrowserState());
AutofillCreditCardEditTableViewController* controller =
[[AutofillCreditCardEditTableViewController alloc]
initWithCreditCard:*creditCard
personalDataManager:personalDataManager];
if (editMode) {
// If `creditCard` needs to be edited from the Payments web page, then a
// command to open the Payments URL in a new tab should be dispatched. A
// SettingsNavigationController shouldn't be created in this case.
CHECK(
![AutofillCreditCardUtil shouldEditCardFromPaymentsWebPage:creditCard]);
[controller editButtonPressed];
}
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:controller
browser:browser
delegate:delegate];
// Make sure the cancel button is always present, as the Autofill screen isn't
// just shown from Settings.
[controller navigationItem].leftBarButtonItem =
[navigationController cancelButton];
return navigationController;
}
+ (instancetype)
defaultBrowserControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate
sourceForUMA:
(DefaultBrowserSettingsPageSource)source {
DefaultBrowserSettingsTableViewController* controller =
[[DefaultBrowserSettingsTableViewController alloc] init];
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:controller
browser:browser
delegate:delegate];
[controller navigationItem].leftBarButtonItem =
[navigationController cancelButton];
controller.source = source;
return navigationController;
}
+ (instancetype)
clearBrowsingDataControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate {
CHECK(!IsIosQuickDeleteEnabled());
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
navigationController.clearBrowsingDataCoordinator =
[[ClearBrowsingDataCoordinator alloc]
initWithBaseNavigationController:navigationController
browser:browser];
navigationController.clearBrowsingDataCoordinator.delegate =
navigationController;
[navigationController.clearBrowsingDataCoordinator start];
return navigationController;
}
+ (instancetype)
inactiveTabsControllerForBrowser:(Browser*)browser
delegate:(id<SettingsNavigationControllerDelegate>)
delegate {
CHECK(IsInactiveTabsAvailable());
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
navigationController.inactiveTabsSettingsCoordinator =
[[InactiveTabsSettingsCoordinator alloc]
initWithBaseNavigationController:navigationController
browser:browser];
[navigationController.inactiveTabsSettingsCoordinator start];
return navigationController;
}
+ (instancetype)
contentSettingsControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate {
ContentSettingsTableViewController* controller =
[[ContentSettingsTableViewController alloc] initWithBrowser:browser];
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:controller
browser:browser
delegate:delegate];
// Make sure the cancel button is always present, as the Contents Settings
// screen isn't just shown from Settings.
[controller navigationItem].leftBarButtonItem =
[navigationController cancelButton];
return navigationController;
}
+ (instancetype)
notificationsSettingsControllerForBrowser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate {
SettingsNavigationController* navigationController =
[[SettingsNavigationController alloc]
initWithRootViewController:nil
browser:browser
delegate:delegate];
[navigationController showNotificationsSettings];
return navigationController;
}
#pragma mark - Lifecycle
- (instancetype)initWithRootViewController:(UIViewController*)rootViewController
browser:(Browser*)browser
delegate:
(id<SettingsNavigationControllerDelegate>)
delegate {
CHECK(browser);
CHECK(!browser->GetBrowserState()->IsOffTheRecord());
self = [super initWithRootViewController:rootViewController];
if (self) {
_browser = browser;
_settingsNavigationDelegate = delegate;
// FIXME -- RTTI is bad.
if ([rootViewController
isKindOfClass:[SettingsRootTableViewController class]]) {
SettingsRootTableViewController* settingsRootViewController =
base::apple::ObjCCast<SettingsRootTableViewController>(
rootViewController);
// Set up handlers.
ConfigureHandlers(settingsRootViewController,
_browser->GetCommandDispatcher());
}
self.modalPresentationStyle = UIModalPresentationFormSheet;
// Set the presentationController delegate. This is used for swipe down to
// dismiss. This needs to be set after the modalPresentationStyle.
self.presentationController.delegate = self;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorNamed:kPrimaryBackgroundColor];
self.toolbar.translucent = NO;
self.navigationBar.barTintColor =
[UIColor colorNamed:kSecondaryBackgroundColor];
self.toolbar.barTintColor =
[UIColor colorNamed:kGroupedPrimaryBackgroundColor];
self.view.backgroundColor =
[UIColor colorNamed:kGroupedPrimaryBackgroundColor];
self.navigationBar.prefersLargeTitles = YES;
self.navigationBar.accessibilityIdentifier =
password_manager::kSettingsNavigationBarAccessibilityID;
// Set the NavigationController delegate.
self.delegate = self;
}
#pragma mark - Public
- (UIBarButtonItem*)cancelButton {
UIBarButtonItem* item = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:@selector(closeSettings)];
return item;
}
- (UIBarButtonItem*)doneButton {
if (self.presentingViewController == nil) {
// This can be called while being dismissed. In that case, return nil. See
// crbug.com/1346604.
return nil;
}
UIBarButtonItem* item = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(closeSettings)];
item.accessibilityIdentifier = kSettingsDoneButtonId;
return item;
}
- (void)cleanUpSettings {
// Notify all controllers of a Settings dismissal.
for (UIViewController* controller in [self viewControllers]) {
if ([controller respondsToSelector:@selector(settingsWillBeDismissed)]) {
[controller performSelector:@selector(settingsWillBeDismissed)];
}
}
// GoogleServicesSettingsCoordinator and PasswordsCoordinator must be stopped
// before dismissing the sync settings view.
[self.accountsCoordinator stop];
self.accountsCoordinator = nil;
[self stopSyncSettingsCoordinator];
[self stopGoogleServicesSettingsCoordinator];
[self stopPasswordsCoordinator];
[self stopSafetyCheckCoordinator];
[self stopClearBrowsingDataCoordinator];
[self stopPrivacySafeBrowsingCoordinator];
[self stopPrivacySettingsCoordinator];
[self stopInactiveTabSettingsCoordinator];
[self stopPasswordDetailsCoordinator];
[self stopAutofillProfileEditCoordinator];
[self stopNotificationsCoordinator];
// Reset the delegate to prevent any queued transitions from attempting to
// close the settings.
self.settingsNavigationDelegate = nil;
self.snackbarCommandsHandler = nil;
self.currentPresentedViewController = nil;
self.presentationController.delegate = nil;
_browser = nil;
}
- (void)closeSettings {
for (UIViewController* controller in [self viewControllers]) {
if ([controller conformsToProtocol:@protocol(SettingsControllerProtocol)]) {
[controller performSelector:@selector(reportDismissalUserAction)];
}
}
[self.settingsNavigationDelegate closeSettings];
}
- (void)popViewControllerOrCloseSettingsAnimated:(BOOL)animated {
if (self.viewControllers.count > 1) {
// Pop the top view controller to reveal the view controller underneath.
[self popViewControllerAnimated:animated];
} else {
// If there is only one view controller in the navigation stack,
// simply close settings.
[self closeSettings];
}
}
#pragma mark - Private
// Pushes a GoogleServicesSettingsViewController on this settings navigation
// controller. Does nothing id the top view controller is already of type
// `GoogleServicesSettingsViewController`.
- (void)showGoogleServices {
if ([self.topViewController
isKindOfClass:[GoogleServicesSettingsViewController class]]) {
// The top view controller is already the Google services settings panel.
// No need to open it.
return;
}
self.googleServicesSettingsCoordinator =
[[GoogleServicesSettingsCoordinator alloc]
initWithBaseNavigationController:self
browser:self.browser];
self.googleServicesSettingsCoordinator.delegate = self;
[self.googleServicesSettingsCoordinator start];
}
- (void)showPrivacy {
self.privacySettingsCoordinator = [[PrivacyCoordinator alloc]
initWithBaseNavigationController:self
browser:self.browser];
self.privacySettingsCoordinator.delegate = self;
[self.privacySettingsCoordinator start];
}
- (void)showSyncServices {
if ([self.topViewController
isKindOfClass:[ManageSyncSettingsCoordinator class]]) {
// The top view controller is already the Sync settings panel.
// No need to open it.
return;
}
DCHECK(!self.manageSyncSettingsCoordinator);
// TODO(crbug.com/40066949): Remove usage of HasSyncConsent() after kSync
// users migrated to kSignin in phase 3. See ConsentLevel::kSync
// documentation for details.
SyncSettingsAccountState accountState =
SyncServiceFactory::GetForBrowserState(self.browser->GetBrowserState())
->HasSyncConsent()
? SyncSettingsAccountState::kSyncing
: SyncSettingsAccountState::kSignedIn;
self.manageSyncSettingsCoordinator = [[ManageSyncSettingsCoordinator alloc]
initWithBaseNavigationController:self
browser:self.browser
accountState:accountState];
self.manageSyncSettingsCoordinator.delegate = self;
[self.manageSyncSettingsCoordinator start];
}
// Shows the Safety Check page and starts the Safety Check for `referrer`.
- (void)showSafetyCheckAndStartSafetyCheck:
(password_manager::PasswordCheckReferrer)referrer {
if ([self.topViewController isKindOfClass:[SafetyCheckCoordinator class]] ||
[self.safetyCheckCoordinator.baseViewController isBeingDismissed]) {
// Do not open the Safety Check panel if:
// [1] The top view controller is already the Safety Check panel, or
// [2] The Safety Check view controller is currently being dismissed.
return;
}
DCHECK(!self.safetyCheckCoordinator);
self.safetyCheckCoordinator = [[SafetyCheckCoordinator alloc]
initWithBaseNavigationController:self
browser:self.browser
referrer:referrer];
self.safetyCheckCoordinator.delegate = self;
[self.safetyCheckCoordinator start];
[self.safetyCheckCoordinator startCheckIfNotRunning];
}
- (void)showSafeBrowsing {
if ([self.topViewController
isKindOfClass:[PrivacySafeBrowsingCoordinator class]]) {
// The top view controller is already the Safe Browsing panel.
// No need to open it.
return;
}
DCHECK(!self.privacySafeBrowsingCoordinator);
self.privacySafeBrowsingCoordinator = [[PrivacySafeBrowsingCoordinator alloc]
initWithBaseNavigationController:self
browser:self.browser];
self.privacySafeBrowsingCoordinator.delegate = self;
[self.privacySafeBrowsingCoordinator start];
}
// Stops the underlying Google services settings coordinator if it exists.
- (void)stopGoogleServicesSettingsCoordinator {
[self.googleServicesSettingsCoordinator stop];
self.googleServicesSettingsCoordinator = nil;
}
// Stops the privacy settings coordinator if it exsists.
- (void)stopPrivacySettingsCoordinator {
[self.privacySettingsCoordinator stop];
self.privacySettingsCoordinator = nil;
}
// Stops the underlying Sync settings coordinator if it exists.
- (void)stopSyncSettingsCoordinator {
[self.manageSyncSettingsCoordinator stop];
self.manageSyncSettingsCoordinator = nil;
}
// Shows the saved passwords. If `showCancelButton` is true, adds a cancel
// button as the left navigation item.
- (void)showSavedPasswordsAndShowCancelButton:(BOOL)showCancelButton {
self.savedPasswordsCoordinator = [[PasswordsCoordinator alloc]
initWithBaseNavigationController:self
browser:self.browser];
self.savedPasswordsCoordinator.delegate = self;
[self.savedPasswordsCoordinator start];
if (showCancelButton) {
[self.savedPasswordsCoordinator.viewController navigationItem]
.leftBarButtonItem = [self cancelButton];
}
}
- (void)showPasswordManagerSearchPage {
self.savedPasswordsCoordinator = [[PasswordsCoordinator alloc]
initWithBaseNavigationController:self
browser:self.browser];
self.savedPasswordsCoordinator.delegate = self;
self.savedPasswordsCoordinator.openViewControllerForPasswordSearch = true;
[self.savedPasswordsCoordinator start];
}
- (void)showPasswordDetailsForCredential:
(password_manager::CredentialUIEntry)credential
inEditMode:(BOOL)editMode
showCancelButton:(BOOL)showCancelButton {
// TODO(crbug.com/40067451): Switch back to DCHECK if the number of reports is
// low.
DUMP_WILL_BE_CHECK(!self.passwordDetailsCoordinator);
self.passwordDetailsCoordinator = [[PasswordDetailsCoordinator alloc]
initWithBaseNavigationController:self
browser:self.browser
credential:credential
reauthModule:password_manager::
BuildReauthenticationModule()
context:DetailsContext::kOutsideSettings];
self.passwordDetailsCoordinator.delegate = self;
self.passwordDetailsCoordinator.openInEditMode = editMode;
self.passwordDetailsCoordinator.showCancelButton = showCancelButton;
[self.passwordDetailsCoordinator start];
}
// Stops the underlying passwords coordinator if it exists.
- (void)stopPasswordsCoordinator {
[self.savedPasswordsCoordinator stop];
self.savedPasswordsCoordinator.delegate = nil;
self.savedPasswordsCoordinator = nil;
}
// Stops the underlying clear browsing data coordinator if it exists.
- (void)stopClearBrowsingDataCoordinator {
[self.clearBrowsingDataCoordinator stop];
self.clearBrowsingDataCoordinator.delegate = nil;
self.clearBrowsingDataCoordinator = nil;
}
// Stops the underlying inactive tabs settings coordinator if it exists.
- (void)stopInactiveTabSettingsCoordinator {
[self.inactiveTabsSettingsCoordinator stop];
self.inactiveTabsSettingsCoordinator = nil;
}
// Stops the underlying SafetyCheck coordinator if it exists.
- (void)stopSafetyCheckCoordinator {
[self.safetyCheckCoordinator stop];
self.safetyCheckCoordinator.delegate = nil;
self.safetyCheckCoordinator = nil;
}
// Stops the underlying PrivacySafeBrowsing coordinator if it exists.
- (void)stopPrivacySafeBrowsingCoordinator {
[self.privacySafeBrowsingCoordinator stop];
self.privacySafeBrowsingCoordinator.delegate = nil;
self.privacySafeBrowsingCoordinator = nil;
}
// Stops the underlying PasswordDetailsCoordinator.
- (void)stopPasswordDetailsCoordinator {
[self.passwordDetailsCoordinator stop];
self.passwordDetailsCoordinator.delegate = nil;
self.passwordDetailsCoordinator = nil;
}
// Stops the underlying AutofillProfileEditCoordinator.
- (void)stopAutofillProfileEditCoordinator {
[self.autofillProfileEditCoordinator stop];
self.autofillProfileEditCoordinator.delegate = nil;
self.autofillProfileEditCoordinator = nil;
}
// Stops the underlying NotificationsCoordinator.
- (void)stopNotificationsCoordinator {
[self.notificationsCoordinator stop];
self.notificationsCoordinator.delegate = nil;
self.notificationsCoordinator = nil;
}
#pragma mark - GoogleServicesSettingsCoordinatorDelegate
- (void)googleServicesSettingsCoordinatorDidRemove:
(GoogleServicesSettingsCoordinator*)coordinator {
DCHECK_EQ(self.googleServicesSettingsCoordinator, coordinator);
[self stopGoogleServicesSettingsCoordinator];
}
#pragma mark - PrivacyCoordinatorDelegate
- (void)privacyCoordinatorViewControllerWasRemoved:
(PrivacyCoordinator*)coordinator {
DCHECK_EQ(self.privacySettingsCoordinator, coordinator);
[self stopPrivacySettingsCoordinator];
}
#pragma mark - ManageSyncSettingsCoordinatorDelegate
- (void)manageSyncSettingsCoordinatorWasRemoved:
(ManageSyncSettingsCoordinator*)coordinator {
DCHECK_EQ(self.manageSyncSettingsCoordinator, coordinator);
[self stopSyncSettingsCoordinator];
}
- (NSString*)manageSyncSettingsCoordinatorTitle {
return l10n_util::GetNSString(IDS_IOS_MANAGE_SYNC_SETTINGS_TITLE);
}
#pragma mark - PasswordsCoordinatorDelegate
- (void)passwordsCoordinatorDidRemove:(PasswordsCoordinator*)coordinator {
DCHECK_EQ(self.savedPasswordsCoordinator, coordinator);
[self stopPasswordsCoordinator];
}
#pragma mark - PasswordManagerReauthenticationDelegate
- (void)dismissPasswordManagerAfterFailedReauthentication {
id<ApplicationCommands> applicationHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), ApplicationCommands);
[applicationHandler closeSettingsUI];
}
#pragma mark PasswordDetailsCoordinatorDelegate
- (void)passwordDetailsCoordinatorDidRemove:
(PasswordDetailsCoordinator*)coordinator {
DCHECK_EQ(self.passwordDetailsCoordinator, coordinator);
[self stopPasswordDetailsCoordinator];
}
- (void)passwordDetailsCancelButtonWasTapped {
[self closeSettings];
}
#pragma mark - AutofillProfileEditCoordinatorDelegate
- (void)autofillProfileEditCoordinatorTableViewControllerDidFinish:
(AutofillProfileEditCoordinator*)coordinator {
DCHECK_EQ(self.autofillProfileEditCoordinator, coordinator);
[self stopAutofillProfileEditCoordinator];
}
#pragma mark - ClearBrowsingDataCoordinatorDelegate
- (void)clearBrowsingDataCoordinatorViewControllerWasRemoved:
(ClearBrowsingDataCoordinator*)coordinator {
DCHECK_EQ(self.clearBrowsingDataCoordinator, coordinator);
[self stopClearBrowsingDataCoordinator];
}
#pragma mark - SafetyCheckCoordinatorDelegate
- (void)safetyCheckCoordinatorDidRemove:(SafetyCheckCoordinator*)coordinator {
DCHECK_EQ(self.safetyCheckCoordinator, coordinator);
[self stopSafetyCheckCoordinator];
}
#pragma mark - PrivacySafeBrowsingCoordinatorDelegate
- (void)privacySafeBrowsingCoordinatorDidRemove:
(PrivacySafeBrowsingCoordinator*)coordinator {
DCHECK_EQ(self.privacySafeBrowsingCoordinator, coordinator);
[self stopPrivacySafeBrowsingCoordinator];
}
#pragma mark - NotificationsCoordinatorDelegate
- (void)notificationsCoordinatorDidRemove:
(NotificationsCoordinator*)coordinator {
DCHECK_EQ(self.notificationsCoordinator, coordinator);
[self stopNotificationsCoordinator];
}
#pragma mark - UIAdaptivePresentationControllerDelegate
- (BOOL)presentationControllerShouldDismiss:
(UIPresentationController*)presentationController {
if ([self.currentPresentedViewController
respondsToSelector:@selector(presentationControllerShouldDismiss:)]) {
return [self.currentPresentedViewController
presentationControllerShouldDismiss:presentationController];
}
return NO;
}
- (void)presentationControllerDidDismiss:
(UIPresentationController*)presentationController {
if ([self.currentPresentedViewController
respondsToSelector:@selector(presentationControllerDidDismiss:)]) {
[self.currentPresentedViewController
presentationControllerDidDismiss:presentationController];
}
// Call settingsWasDismissed to make sure any necessary cleanup is performed.
[self.settingsNavigationDelegate settingsWasDismissed];
}
- (void)presentationControllerWillDismiss:
(UIPresentationController*)presentationController {
if ([self.currentPresentedViewController
respondsToSelector:@selector(presentationControllerWillDismiss:)]) {
[self.currentPresentedViewController
presentationControllerWillDismiss:presentationController];
}
}
#pragma mark - Accessibility
- (BOOL)accessibilityPerformEscape {
UIViewController* poppedController = [self popViewControllerAnimated:YES];
if (!poppedController)
[self closeSettings];
return YES;
}
#pragma mark - UINavigationController
// Ensures that the keyboard is always dismissed during a navigation transition.
- (BOOL)disablesAutomaticKeyboardDismissal {
return NO;
}
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController*)navigationController
willShowViewController:(UIViewController*)viewController
animated:(BOOL)animated {
if ([viewController isMemberOfClass:[SettingsTableViewController class]] &&
![self.currentPresentedViewController
isMemberOfClass:[SettingsTableViewController class]] &&
[self.currentPresentedViewController
conformsToProtocol:@protocol(SettingsControllerProtocol)]) {
// Navigated back to root SettingsController from leaf SettingsController.
[self.currentPresentedViewController
performSelector:@selector(reportBackUserAction)];
}
self.currentPresentedViewController = base::apple::ObjCCast<
UIViewController<UIAdaptivePresentationControllerDelegate>>(
viewController);
}
#pragma mark - UIResponder
// To always be able to register key commands via -keyCommands, the VC must be
// able to become first responder.
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (NSArray<UIKeyCommand*>*)keyCommands {
if ([self presentedViewController]) {
return nil;
}
return @[ UIKeyCommand.cr_close ];
}
- (void)keyCommand_close {
base::RecordAction(base::UserMetricsAction("MobileKeyCommandClose"));
[self closeSettings];
}
#pragma mark - SettingsCommands
// TODO(crbug.com/41352590) : Do not pass `baseViewController` through
// dispatcher.
- (void)showAccountsSettingsFromViewController:
(UIViewController*)baseViewController
skipIfUINotAvailable:(BOOL)skipIfUINotAvailable {
// This command should only be triggered by the settinsg, therefore there is
// no issue for the UI to be available or not.
CHECK(!skipIfUINotAvailable);
AccountsCoordinator* accountsCoordinator =
[[AccountsCoordinator alloc] initWithBaseNavigationController:self
browser:self.browser
closeSettingsOnAddAccount:NO];
[accountsCoordinator start];
self.accountsCoordinator = accountsCoordinator;
}
// TODO(crbug.com/41352590) : Do not pass `baseViewController` through
// dispatcher.
- (void)showGoogleServicesSettingsFromViewController:
(UIViewController*)baseViewController {
[self showGoogleServices];
}
// TODO(crbug.com/41352590) : Do not pass `baseViewController` through
// dispatcher.
- (void)showSyncSettingsFromViewController:
(UIViewController*)baseViewController {
[self showSyncServices];
}
// TODO(crbug.com/41352590) : Do not pass `baseViewController` through
// dispatcher.
- (void)showSyncPassphraseSettingsFromViewController:
(UIViewController*)baseViewController {
SyncEncryptionPassphraseTableViewController* controller =
[[SyncEncryptionPassphraseTableViewController alloc]
initWithBrowser:self.browser];
ConfigureHandlers(controller, _browser->GetCommandDispatcher());
[self pushViewController:controller animated:YES];
}
// TODO(crbug.com/41352590) : Do not pass `baseViewController` through
// dispatcher.
- (void)showSavedPasswordsSettingsFromViewController:
(UIViewController*)baseViewController
showCancelButton:(BOOL)showCancelButton {
[self showSavedPasswordsAndShowCancelButton:showCancelButton];
}
- (void)showAddressDetails:(const autofill::AutofillProfile*)address
inEditMode:(BOOL)editMode
offerMigrateToAccount:(BOOL)offerMigrateToAccount {
self.autofillProfileEditCoordinator = [[AutofillProfileEditCoordinator alloc]
initWithBaseNavigationController:self
browser:self.browser
profile:*address
migrateToAccountButton:offerMigrateToAccount];
self.autofillProfileEditCoordinator.delegate = self;
self.autofillProfileEditCoordinator.openInEditMode = editMode;
[self.autofillProfileEditCoordinator start];
}
// TODO(crbug.com/41352590) : Do not pass `baseViewController` through
// dispatcher.
- (void)showProfileSettingsFromViewController:
(UIViewController*)baseViewController {
AutofillProfileTableViewController* controller =
[[AutofillProfileTableViewController alloc] initWithBrowser:self.browser];
ConfigureHandlers(controller, _browser->GetCommandDispatcher());
[self pushViewController:controller animated:YES];
}
- (void)showCreditCardSettings {
AutofillCreditCardTableViewController* controller =
[[AutofillCreditCardTableViewController alloc]
initWithBrowser:self.browser];
ConfigureHandlers(controller, _browser->GetCommandDispatcher());
[self pushViewController:controller animated:YES];
}
- (void)showCreditCardDetails:(const autofill::CreditCard*)creditCard
inEditMode:(BOOL)editMode {
ChromeBrowserState* browserState =
self.browser->GetBrowserState()->GetOriginalChromeBrowserState();
autofill::PersonalDataManager* personalDataManager =
autofill::PersonalDataManagerFactory::GetForBrowserState(
browserState->GetOriginalChromeBrowserState());
AutofillCreditCardEditTableViewController* controller =
[[AutofillCreditCardEditTableViewController alloc]
initWithCreditCard:*creditCard
personalDataManager:personalDataManager];
if (editMode) {
// If `creditCard` needs to be edited from the Payments web page, then a
// command to open the Payments URL in a new tab should be dispatched.
CHECK(
![AutofillCreditCardUtil shouldEditCardFromPaymentsWebPage:creditCard]);
[controller editButtonPressed];
}
ConfigureHandlers(controller, _browser->GetCommandDispatcher());
[self pushViewController:controller animated:YES];
}
- (void)showDefaultBrowserSettingsFromViewController:
(UIViewController*)baseViewController
sourceForUMA:
(DefaultBrowserSettingsPageSource)
source {
DefaultBrowserSettingsTableViewController* controller =
[[DefaultBrowserSettingsTableViewController alloc] init];
ConfigureHandlers(controller, _browser->GetCommandDispatcher());
controller.source = source;
[self pushViewController:controller animated:YES];
}
- (void)showClearBrowsingDataSettings {
CHECK(!IsIosQuickDeleteEnabled());
[self stopClearBrowsingDataCoordinator];
self.clearBrowsingDataCoordinator = [[ClearBrowsingDataCoordinator alloc]
initWithBaseNavigationController:self
browser:self.browser];
self.clearBrowsingDataCoordinator.delegate = self;
[self.clearBrowsingDataCoordinator start];
}
// Shows the Safety Check page and starts the Safety Check for `referrer`.
- (void)showAndStartSafetyCheckForReferrer:
(password_manager::PasswordCheckReferrer)referrer {
[self showSafetyCheckAndStartSafetyCheck:referrer];
}
- (void)showSafeBrowsingSettings {
[self showSafeBrowsing];
}
- (void)showSafeBrowsingSettingsFromPromoInteraction {
[self showSafeBrowsing];
self.privacySafeBrowsingCoordinator.openedFromPromoInteraction = YES;
}
- (void)showPasswordSearchPage {
[self showPasswordManagerSearchPage];
}
- (void)showContentsSettingsFromViewController:
(UIViewController*)baseViewController {
if ([self.topViewController
isKindOfClass:[ContentSettingsTableViewController class]]) {
// The top view controller is already the Contents Settings panel.
// No need to open it.
return;
}
ContentSettingsTableViewController* controller =
[[ContentSettingsTableViewController alloc] initWithBrowser:self.browser];
[self pushViewController:controller animated:YES];
}
- (void)showNotificationsSettings {
[self stopNotificationsCoordinator];
self.notificationsCoordinator = [[NotificationsCoordinator alloc]
initWithBaseNavigationController:self
browser:_browser];
self.notificationsCoordinator.delegate = self;
[self.notificationsCoordinator start];
}
@end