blob: d0badafdfbde51bbb19d770b6bbf293379ebf7fd [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_coordinator.h"
#import <MaterialComponents/MaterialSnackbar.h>
#import "base/check.h"
#import "base/functional/callback_helpers.h"
#import "base/memory/raw_ptr.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "components/signin/public/base/signin_metrics.h"
#import "components/sync/service/sync_service.h"
#import "components/sync/service/sync_service_utils.h"
#import "components/sync/service/sync_user_settings.h"
#import "components/trusted_vault/trusted_vault_server_constants.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/app/change_profile_commands.h"
#import "ios/chrome/app/profile/profile_state.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_constants.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_mediator.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_mediator_delegate.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_view_controller.h"
#import "ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/add_account_signin/add_account_signin_coordinator.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/interruptible_chrome_coordinator.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_constants.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_coordinator+protected.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_coordinator.h"
#import "ios/chrome/browser/authentication/ui_bundled/signout_action_sheet/signout_action_sheet_coordinator.h"
#import "ios/chrome/browser/policy/model/management_state.h"
#import "ios/chrome/browser/policy/ui_bundled/management_util.h"
#import "ios/chrome/browser/push_notification/model/push_notification_service.h"
#import "ios/chrome/browser/scoped_ui_blocker/ui_bundled/scoped_ui_blocker.h"
#import "ios/chrome/browser/settings/ui_bundled/google_services/manage_accounts/manage_accounts_coordinator.h"
#import "ios/chrome/browser/settings/ui_bundled/google_services/manage_accounts/manage_accounts_coordinator_delegate.h"
#import "ios/chrome/browser/settings/ui_bundled/settings_controller_protocol.h"
#import "ios/chrome/browser/settings/ui_bundled/settings_root_view_controlling.h"
#import "ios/chrome/browser/settings/ui_bundled/sync/sync_encryption_passphrase_table_view_controller.h"
#import "ios/chrome/browser/settings/ui_bundled/sync/sync_encryption_table_view_controller.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/browser/browser_provider.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.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/settings_commands.h"
#import "ios/chrome/browser/shared/public/commands/show_signin_command.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/shared/ui/util/identity_snackbar/identity_snackbar_message.h"
#import "ios/chrome/browser/shared/ui/util/snackbar_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_factory.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/signin/model/system_identity_manager.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"
namespace {
// First part of a switch-account-after-switching-profile continuation: Sign out
// the current account if it's different from the desired one.
void ChangeProfileSignOutIfMismatchContinuation(
id<SystemIdentity> expected_identity,
SceneState* scene_state,
base::OnceClosure closure) {
Browser* browser =
scene_state.browserProviderInterface.mainBrowserProvider.browser;
AuthenticationService* authentication_service =
AuthenticationServiceFactory::GetForProfile(browser->GetProfile());
id<SystemIdentity> existing_identity =
authentication_service->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
if (!existing_identity || existing_identity == expected_identity) {
// No need to sign-out as either the correct identity is signed-in or
// no identity is signed-in.
std::move(closure).Run();
return;
}
// Signing out is only allowed in the personal profile. Phrased another way,
// we shouldn't try to a work profile with a non-matching account.
// TODO(crbug.com/375605174): AuthenticationService should probably enforce
// this internally.
CHECK_EQ(browser->GetProfile()->GetProfileName(),
GetApplicationContext()
->GetAccountProfileMapper()
->GetPersonalProfileName());
authentication_service->SignOut(
signin_metrics::ProfileSignout::kChangeAccountInAccountMenu,
/*force_clear_browsing_data=*/false,
base::CallbackToBlock(std::move(closure)));
}
// Second part of a switch-account-after-switching-profile continuation: Sign in
// the desired account if it's not already signed in.
void ChangeProfileSignInContinuation(id<SystemIdentity> identity,
SceneState* scene_state,
base::OnceClosure closure) {
Browser* browser =
scene_state.browserProviderInterface.mainBrowserProvider.browser;
// TODO(crbug.com/375604649): This should probably go through
// AuthenticationFlow rather than using AuthenticationService directly, so
// that the snackbar gets shown, and also the enterprise onboarding screen if
// necessary.
AuthenticationService* authentication_service =
AuthenticationServiceFactory::GetForProfile(browser->GetProfile());
authentication_service->SignIn(
identity, signin_metrics::AccessPoint::ACCESS_POINT_ACCOUNT_MENU);
std::move(closure).Run();
}
} // anonymous namespace
@interface AccountMenuCoordinator () <AccountMenuMediatorDelegate,
ManageAccountsCoordinatorDelegate,
UIAdaptivePresentationControllerDelegate>
// The view controller.
@property(nonatomic, strong) AccountMenuViewController* viewController;
// The mediator.
@property(nonatomic, strong) AccountMenuMediator* mediator;
@end
@implementation AccountMenuCoordinator {
UINavigationController* _navigationController;
raw_ptr<AuthenticationService> _authenticationService;
raw_ptr<signin::IdentityManager> _identityManager;
raw_ptr<PrefService> _prefService;
// Dismiss callback for account details view.
SystemIdentityManager::DismissViewCallback
_accountDetailsControllerDismissCallback;
// The coordinators for the "Edit account list"
ManageAccountsCoordinator* _manageAccountsCoordinator;
// The coordinator for the action sheet to sign out.
SignoutActionSheetCoordinator* _signoutActionSheetCoordinator;
raw_ptr<syncer::SyncService> _syncService;
SyncEncryptionTableViewController* _syncEncryptionTableViewController;
SyncEncryptionPassphraseTableViewController*
_syncEncryptionPassphraseTableViewController;
id<ApplicationCommands> _applicationHandler;
id<ChangeProfileCommands> _changeProfileHandler;
raw_ptr<ChromeAccountManagerService> _accountManagerService;
// Callback to hide the activity overlay.
base::ScopedClosureRunner _activityOverlayCallback;
// The child signin coordinator if it’s open. It may be presented by the
// Manage Account’s coordinator view controller.
SigninCoordinator* _signinCoordinator;
// Block the UI when the identity removal or switch is in progress.
std::unique_ptr<ScopedUIBlocker> _UIBlocker;
}
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browser:(Browser*)browser {
return [super initWithBaseViewController:viewController
browser:browser
accessPoint:signin_metrics::AccessPoint::
ACCESS_POINT_ACCOUNT_MENU];
}
- (void)dealloc {
DCHECK(!_mediator);
}
- (void)start {
[super start];
ProfileIOS* profile = self.browser->GetProfile();
_syncService = SyncServiceFactory::GetForProfile(profile);
_authenticationService = AuthenticationServiceFactory::GetForProfile(profile);
_accountManagerService =
ChromeAccountManagerServiceFactory::GetForProfile(profile);
_identityManager = IdentityManagerFactory::GetForProfile(profile);
_prefService = profile->GetPrefs();
_applicationHandler = HandlerForProtocol(self.browser->GetCommandDispatcher(),
ApplicationCommands);
_changeProfileHandler = HandlerForProtocol(
self.browser->GetSceneState().profileState.appState.appCommandDispatcher,
ChangeProfileCommands);
_viewController = [[AccountMenuViewController alloc] init];
_navigationController = [[UINavigationController alloc]
initWithRootViewController:_viewController];
if (self.anchorView) {
_navigationController.modalPresentationStyle = UIModalPresentationPopover;
_navigationController.popoverPresentationController.sourceView =
self.anchorView;
_navigationController.popoverPresentationController
.permittedArrowDirections = UIPopoverArrowDirectionUp;
} else {
// If no anchor view was provided, fall back to a form sheet. For narrow
// width devices (i.e. iPhone) it's the same thing as a popover anyway, and
// for regular width (i.e. iPad) it's a dialog centered in the window.
_navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
}
_navigationController.presentationController.delegate = self;
PrefService* prefs = profile->GetPrefs();
_mediator =
[[AccountMenuMediator alloc] initWithSyncService:_syncService
accountManagerService:_accountManagerService
authService:_authenticationService
identityManager:_identityManager
prefs:prefs];
_mediator.delegate = self;
_mediator.consumer = _viewController;
_viewController.mutator = _mediator;
_viewController.dataSource = _mediator;
[_viewController setUpBottomSheetPresentationController];
[self.baseViewController presentViewController:_navigationController
animated:YES
completion:nil];
}
- (void)stop {
// TODO(crbug.com/336719423): Change condition to CHECK(_mediator). But
// first inform the parent coordinator at didTapClose that this view was
// dismissed.
if (!_mediator) {
return;
}
[_syncEncryptionPassphraseTableViewController settingsWillBeDismissed];
_syncEncryptionPassphraseTableViewController = nil;
[_syncEncryptionTableViewController settingsWillBeDismissed];
_syncEncryptionTableViewController = nil;
// Sets to nil the account menu objects.
[_mediator disconnect];
_mediator.delegate = nil;
_mediator = nil;
// Sets the service to nil.
_authenticationService = nil;
_identityManager = nil;
_prefService = nil;
_applicationHandler = nil;
_syncService = nullptr;
_accountManagerService = nullptr;
[self unblockOtherScenes];
[super stop];
}
#pragma mark - UIAdaptivePresentationControllerDelegate
- (void)presentationControllerDidDismiss:
(UIPresentationController*)presentationController {
base::RecordAction(
base::UserMetricsAction("Signin_AccountMenu_Dismissed_By_User"));
// We assume the dismiss was done by the user.
self.mediator.signinCoordinatorResult = SigninCoordinatorResultCanceledByUser;
// UIShutdownNoDismiss because the UI is already dismissed.
[self interruptWithAction:SynchronousStopAction() completion:nil];
}
#pragma mark - AccountMenuMediatorDelegate
- (void)didTapManageYourGoogleAccount {
__weak __typeof(self) weakSelf = self;
_accountDetailsControllerDismissCallback =
GetApplicationContext()
->GetSystemIdentityManager()
->PresentAccountDetailsController(
_authenticationService->GetPrimaryIdentity(
signin::ConsentLevel::kSignin),
_viewController,
/*animated=*/YES,
base::BindOnce(
[](__typeof(self) strongSelf) {
[strongSelf resetAccountDetailsControllerDismissCallback];
},
weakSelf));
}
- (void)didTapManageAccounts {
CHECK(!_manageAccountsCoordinator, base::NotFatalUntil::M133);
_manageAccountsCoordinator = [[ManageAccountsCoordinator alloc]
initWithBaseViewController:_navigationController
browser:self.browser
closeSettingsOnAddAccount:NO];
_manageAccountsCoordinator.delegate = self;
_manageAccountsCoordinator.signoutDismissalByParentCoordinator = YES;
[_manageAccountsCoordinator start];
}
- (void)signOutFromTargetRect:(CGRect)targetRect
forSwitch:(BOOL)forSwitch
completion:(void (^)(BOOL))completion {
if (!_authenticationService->HasPrimaryIdentity(
signin::ConsentLevel::kSignin)) {
// This could happen in very rare cases, if the account somehow got removed
// after the accounts menu was created.
return;
}
signin_metrics::ProfileSignout metricSignOut =
forSwitch
? signin_metrics::ProfileSignout::kChangeAccountInAccountMenu
: signin_metrics::ProfileSignout::kUserClickedSignoutInAccountMenu;
_signoutActionSheetCoordinator = [[SignoutActionSheetCoordinator alloc]
initWithBaseViewController:_viewController
browser:self.browser
rect:targetRect
view:_viewController.view
forceSnackbarOverToolbar:YES
withSource:metricSignOut];
_signoutActionSheetCoordinator.accountSwitch = forSwitch;
__weak __typeof(self) weakSelf = self;
_signoutActionSheetCoordinator.signoutCompletion = ^(BOOL success) {
[weakSelf stopSignoutActionSheetCoordinator];
if (completion) {
completion(success);
}
};
[_signoutActionSheetCoordinator start];
}
- (void)triggerProfileSwitchToProfileNamed:(std::string_view)profileName
andSigninWithSystemIdentity:(id<SystemIdentity>)identity {
CHECK(AreSeparateProfilesForManagedAccountsEnabled());
SceneState* sceneState = self.browser->GetSceneState();
ChangeProfileContinuation continuation = ChainChangeProfileContinuations(
base::BindOnce(&ChangeProfileSignOutIfMismatchContinuation, identity),
base::BindOnce(&ChangeProfileSignInContinuation, identity));
[_changeProfileHandler changeProfile:profileName
forScene:sceneState
continuation:std::move(continuation)];
}
- (void)didTapAddAccountWithCompletion:
(SigninCoordinatorCompletionCallback)completion {
[self openAddAccountWithBaseViewController:_navigationController
completion:completion];
}
- (void)mediatorWantsToBeDismissed:(AccountMenuMediator*)mediator {
CHECK_EQ(mediator, _mediator);
[self interruptWithAction:SigninCoordinatorInterrupt::DismissWithAnimation
completion:nil];
}
- (AuthenticationFlow*)
triggerSigninWithSystemIdentity:(id<SystemIdentity>)identity
completion:
(signin_ui::SigninCompletionCallback)completion {
AuthenticationFlow* authenticationFlow = [[AuthenticationFlow alloc]
initWithBrowser:self.browser
identity:identity
accessPoint:signin_metrics::AccessPoint::
ACCESS_POINT_ACCOUNT_MENU
postSignInActions:PostSignInActionSet({PostSignInAction::kNone})
presentingViewController:_navigationController];
[authenticationFlow
startSignInWithCompletion:^(SigninCoordinatorResult result) {
if (completion) {
completion(result);
}
}];
return authenticationFlow;
}
- (void)triggerAccountSwitchSnackbarWithIdentity:
(id<SystemIdentity>)systemIdentity {
UIImage* avatar = _accountManagerService->GetIdentityAvatarWithIdentity(
systemIdentity, IdentityAvatarSize::Regular);
ManagementState managementState = GetManagementState(
_identityManager, _authenticationService, _prefService);
MDCSnackbarMessage* snackbarTitle = [[IdentitySnackbarMessage alloc]
initWithName:systemIdentity.userGivenName
email:systemIdentity.userEmail
avatar:avatar
managed:managementState.is_profile_managed()];
CommandDispatcher* dispatcher = self.browser->GetCommandDispatcher();
id<SnackbarCommands> snackbarCommandsHandler =
HandlerForProtocol(dispatcher, SnackbarCommands);
[snackbarCommandsHandler showSnackbarMessageOverBrowserToolbar:snackbarTitle];
}
- (BOOL)blockOtherScenesIfPossible {
SceneState* sceneState = self.browser->GetSceneState();
if (sceneState.isUIBlocked) {
// This could occur due to race condition with multiple windows and
// simultaneous taps. See crbug.com/368310663.
return NO;
}
_UIBlocker = std::make_unique<ScopedUIBlocker>(sceneState);
return YES;
}
- (void)unblockOtherScenes {
_UIBlocker.reset();
}
#pragma mark - SyncErrorSettingsCommandHandler
- (void)openPassphraseDialogWithModalPresentation:(BOOL)presentModally {
CHECK(presentModally);
SceneState* sceneState = self.browser->GetSceneState();
if (sceneState.isUIBlocked) {
// This could occur due to race condition with multiple windows and
// simultaneous taps. See crbug.com/368310663.
return;
}
_syncEncryptionPassphraseTableViewController =
[[SyncEncryptionPassphraseTableViewController alloc]
initWithBrowser:self.browser];
_syncEncryptionPassphraseTableViewController.presentModally = YES;
UINavigationController* navigationController = [[UINavigationController alloc]
initWithRootViewController:_syncEncryptionPassphraseTableViewController];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self configureHandlersForRootViewController:
_syncEncryptionPassphraseTableViewController];
[_navigationController presentViewController:navigationController
animated:YES
completion:nil];
}
- (void)openTrustedVaultReauthForFetchKeys {
trusted_vault::SecurityDomainId securityDomainID =
trusted_vault::SecurityDomainId::kChromeSync;
syncer::TrustedVaultUserActionTriggerForUMA trigger =
syncer::TrustedVaultUserActionTriggerForUMA::kAccountMenu;
signin_metrics::AccessPoint accessPoint =
signin_metrics::AccessPoint::ACCESS_POINT_ACCOUNT_MENU;
SigninTrustedVaultDialogIntent intent =
SigninTrustedVaultDialogIntentFetchKeys;
_signinCoordinator = [SigninCoordinator
trustedVaultReAuthenticationCoordinatorWithBaseViewController:
_navigationController
browser:self.browser
intent:intent
securityDomainID:
securityDomainID
trigger:trigger
accessPoint:
accessPoint];
[self startSigninCoordinatorWithCompletion:nil];
}
- (void)openTrustedVaultReauthForDegradedRecoverability {
trusted_vault::SecurityDomainId securityDomainID =
trusted_vault::SecurityDomainId::kChromeSync;
syncer::TrustedVaultUserActionTriggerForUMA trigger =
syncer::TrustedVaultUserActionTriggerForUMA::kAccountMenu;
signin_metrics::AccessPoint accessPoint =
signin_metrics::AccessPoint::ACCESS_POINT_ACCOUNT_MENU;
SigninTrustedVaultDialogIntent intent =
SigninTrustedVaultDialogIntentDegradedRecoverability;
_signinCoordinator = [SigninCoordinator
trustedVaultReAuthenticationCoordinatorWithBaseViewController:
_navigationController
browser:self.browser
intent:intent
securityDomainID:
securityDomainID
trigger:trigger
accessPoint:
accessPoint];
[self startSigninCoordinatorWithCompletion:nil];
}
- (void)openMDMErrodDialogWithSystemIdentity:(id<SystemIdentity>)identity {
_authenticationService->ShowMDMErrorDialogForIdentity(identity);
}
- (void)openPrimaryAccountReauthDialog {
signin_metrics::AccessPoint accessPoint =
signin_metrics::AccessPoint::ACCESS_POINT_ACCOUNT_MENU;
signin_metrics::PromoAction promoAction =
signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO;
_signinCoordinator = [SigninCoordinator
primaryAccountReauthCoordinatorWithBaseViewController:
_navigationController
browser:self.browser
accessPoint:accessPoint
promoAction:promoAction];
[self startSigninCoordinatorWithCompletion:nil];
}
#pragma mark - SigninCoordinator
- (void)interruptWithAction:(SigninCoordinatorInterrupt)action
completion:(ProceduralBlock)completion {
__weak __typeof(self) weakSelf = self;
ProceduralBlock childrenCompletion = ^() {
[weakSelf
runCompletionWithSigninResult:weakSelf.mediator.signinCoordinatorResult
completionIdentity:weakSelf.mediator
.signinCompletionIdentity];
if (completion) {
completion();
}
};
if (IsInterruptibleCoordinatorStoppedSynchronouslyEnabled()) {
[self stopChildrenAndViewControllerWithAction:action completion:nil];
childrenCompletion();
} else {
[self stopChildrenAndViewControllerWithAction:action
completion:childrenCompletion];
}
}
#pragma mark - ManageAccountsCoordinatorDelegate
- (void)manageAccountsCoordinatorWantsToBeStopped:
(ManageAccountsCoordinator*)coordinator {
CHECK_EQ(coordinator, _manageAccountsCoordinator, base::NotFatalUntil::M133);
[self stopManageAccountsCoordinator];
}
- (void)manageAccountsCoordinator:
(ManageAccountsCoordinator*)manageAccountsCoordinator
didRequestAddAccountWithBaseViewController:(UIViewController*)viewController
completion:
(SigninCoordinatorCompletionCallback)
completion {
CHECK_EQ(manageAccountsCoordinator, _manageAccountsCoordinator);
[self openAddAccountWithBaseViewController:viewController
completion:completion];
}
#pragma mark - Private
- (void)stopSigninCoordinator {
[_signinCoordinator stop];
_signinCoordinator = nil;
}
- (void)startSigninCoordinatorWithCompletion:
(SigninCoordinatorCompletionCallback)completion {
CHECK(_signinCoordinator);
__weak __typeof(self) weakSelf = self;
_signinCoordinator.signinCompletion =
^(SigninCoordinatorResult signinResult,
id<SystemIdentity> signinCompletionIdentity) {
[weakSelf
signinCoordinatorCompletionWithSigninResult:signinResult
completionIdentity:signinCompletionIdentity
completion:completion];
};
[_signinCoordinator start];
}
// Opens the add account coordinator on top of `baseViewController`.
- (void)openAddAccountWithBaseViewController:baseViewController
completion:
(SigninCoordinatorCompletionCallback)
completion {
_signinCoordinator = [SigninCoordinator
addAccountCoordinatorWithBaseViewController:baseViewController
browser:self.browser
accessPoint:self.accessPoint];
[self startSigninCoordinatorWithCompletion:completion];
}
// Clean up the add account coordinator.
- (void)
signinCoordinatorCompletionWithSigninResult:
(SigninCoordinatorResult)signinResult
completionIdentity:
(id<SystemIdentity>)completionIdentity
completion:
(SigninCoordinatorCompletionCallback)
completion {
[self stopSigninCoordinator];
if (completion) {
completion(signinResult, completionIdentity);
}
}
- (void)stopManageAccountsCoordinator {
[_manageAccountsCoordinator stop];
_manageAccountsCoordinator.delegate = nil;
_manageAccountsCoordinator = nil;
}
- (void)resetAccountDetailsControllerDismissCallback {
_accountDetailsControllerDismissCallback.Reset();
}
- (void)configureHandlersForRootViewController:
(id<SettingsRootViewControlling>)controller {
CommandDispatcher* dispatcher = self.browser->GetCommandDispatcher();
controller.applicationHandler =
HandlerForProtocol(dispatcher, ApplicationCommands);
controller.browserHandler = HandlerForProtocol(dispatcher, BrowserCommands);
controller.settingsHandler = HandlerForProtocol(dispatcher, SettingsCommands);
controller.snackbarHandler = HandlerForProtocol(dispatcher, SnackbarCommands);
}
- (void)stopSignoutActionSheetCoordinator {
[_signoutActionSheetCoordinator stop];
_signoutActionSheetCoordinator = nil;
}
// Stops all children, then dismiss the view controller. Executes
// `completion` synchronously.
- (void)stopChildrenAndViewControllerWithAction:
(SigninCoordinatorInterrupt)action
completion:(ProceduralBlock)completion {
// Stopping all potentially open children views.
if (!_accountDetailsControllerDismissCallback.is_null()) {
std::move(_accountDetailsControllerDismissCallback).Run(/*animated=*/false);
}
[self stopSignoutActionSheetCoordinator];
__weak __typeof(self) weakSelf = self;
ProceduralBlock dismissAndCompletion = ^() {
// Add Account coordinator should be stopped before the Manage Accounts
// Coordinator, as the former may be presented by the latter.
[weakSelf stopManageAccountsCoordinator];
[weakSelf dismissViewControllerAction:action completion:completion];
};
if (_signinCoordinator) {
SigninCoordinatorInterrupt subviewAction =
(action == SigninCoordinatorInterrupt::UIShutdownNoDismiss)
? SigninCoordinatorInterrupt::UIShutdownNoDismiss
: SigninCoordinatorInterrupt::DismissWithoutAnimation;
[_signinCoordinator interruptWithAction:subviewAction
completion:dismissAndCompletion];
} else {
dismissAndCompletion();
}
}
// Unplugs the view and navigation controller. Dismisses the navigation
// controller as specified by the action.
- (void)dismissViewControllerAction:(SigninCoordinatorInterrupt)action
completion:(void (^)())completion {
if (!_navigationController) {
// The view controller was already dismissed. We can directly call
// completion.
if (completion) {
completion();
}
return;
}
_activityOverlayCallback.RunAndReset();
_mediator.consumer = nil;
_viewController.dataSource = nil;
_viewController.mutator = nil;
UINavigationController* navigationController = _navigationController;
_navigationController = nil;
_viewController = nil;
switch (action) {
case SigninCoordinatorInterrupt::UIShutdownNoDismiss: {
CHECK(!IsInterruptibleCoordinatorAlwaysDismissedEnabled(),
base::NotFatalUntil::M136);
if (completion) {
completion();
}
break;
}
case SigninCoordinatorInterrupt::DismissWithoutAnimation: {
[navigationController.presentingViewController
dismissViewControllerAnimated:NO
completion:completion];
break;
}
case SigninCoordinatorInterrupt::DismissWithAnimation: {
[navigationController.presentingViewController
dismissViewControllerAnimated:YES
completion:completion];
break;
}
}
}
@end