Index: ios/chrome/browser/ui/settings/accounts_collection_view_controller.mm |
diff --git a/ios/chrome/browser/ui/settings/accounts_collection_view_controller.mm b/ios/chrome/browser/ui/settings/accounts_collection_view_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a3e52cb4dc4ac163a39a33167702f669760f78e6 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/settings/accounts_collection_view_controller.mm |
@@ -0,0 +1,677 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// 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/accounts_collection_view_controller.h" |
+ |
+#import "base/ios/weak_nsobject.h" |
+#import "base/mac/foundation_util.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/metrics/user_metrics.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/strings/utf_string_conversions.h" |
+#include "components/browser_sync/profile_sync_service.h" |
+#include "components/signin/core/browser/account_tracker_service.h" |
+#include "components/signin/core/browser/profile_oauth2_token_service.h" |
+#include "components/signin/core/browser/signin_manager.h" |
+#include "components/signin/ios/browser/oauth2_token_service_observer_bridge.h" |
+#include "components/strings/grit/components_strings.h" |
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#include "ios/chrome/browser/signin/account_tracker_service_factory.h" |
+#import "ios/chrome/browser/signin/authentication_service.h" |
+#import "ios/chrome/browser/signin/authentication_service_factory.h" |
+#include "ios/chrome/browser/signin/chrome_identity_service_observer_bridge.h" |
+#include "ios/chrome/browser/signin/oauth2_token_service_factory.h" |
+#include "ios/chrome/browser/signin/signin_manager_factory.h" |
+#include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h" |
+#include "ios/chrome/browser/sync/sync_observer_bridge.h" |
+#include "ios/chrome/browser/sync/sync_setup_service.h" |
+#include "ios/chrome/browser/sync/sync_setup_service_factory.h" |
+#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h" |
+#import "ios/chrome/browser/ui/authentication/signin_interaction_controller.h" |
+#import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h" |
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_account_item.h" |
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h" |
+#import "ios/chrome/browser/ui/collection_view/collection_view_model.h" |
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
+#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
+#import "ios/chrome/browser/ui/commands/generic_chrome_command.h" |
+#include "ios/chrome/browser/ui/commands/ios_command_ids.h" |
+#import "ios/chrome/browser/ui/commands/open_url_command.h" |
+#import "ios/chrome/browser/ui/icons/chrome_icon.h" |
+#import "ios/chrome/browser/ui/settings/cells/account_control_item.h" |
+#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" |
+#import "ios/chrome/browser/ui/settings/sync_settings_collection_view_controller.h" |
+#import "ios/chrome/browser/ui/settings/utils/resized_avatar_cache.h" |
+#import "ios/chrome/browser/ui/sync/sync_util.h" |
+#include "ios/chrome/grit/ios_chromium_strings.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
+#include "ios/public/provider/chrome/browser/images/branded_image_provider.h" |
+#import "ios/public/provider/chrome/browser/signin/chrome_identity.h" |
+#import "ios/public/provider/chrome/browser/signin/chrome_identity_browser_opener.h" |
+#import "ios/public/provider/chrome/browser/signin/chrome_identity_service.h" |
+#import "net/base/mac/url_conversions.h" |
+#include "ui/base/l10n/l10n_util_mac.h" |
+ |
+NSString* const kSettingsAccountsId = @"kSettingsAccountsId"; |
+NSString* const kSettingsHeaderId = @"kSettingsHeaderId"; |
+NSString* const kSettingsAccountsSignoutCellId = |
+ @"kSettingsAccountsSignoutCellId"; |
+NSString* const kSettingsAccountsSyncCellId = @"kSettingsAccountsSyncCellId"; |
+ |
+namespace { |
+ |
+typedef NS_ENUM(NSInteger, SectionIdentifier) { |
+ SectionIdentifierAccounts = kSectionIdentifierEnumZero, |
+ SectionIdentifierSync, |
+ SectionIdentifierSignOut, |
+}; |
+ |
+typedef NS_ENUM(NSInteger, ItemType) { |
+ ItemTypeAccount = kItemTypeEnumZero, |
+ ItemTypeAddAccount, |
+ ItemTypeSync, |
+ ItemTypeGoogleActivityControls, |
+ ItemTypeSignOut, |
+ ItemTypeHeader, |
+}; |
+ |
+} // namespace |
+ |
+@interface AccountsCollectionViewController ()< |
+ ChromeIdentityServiceObserver, |
+ ChromeIdentityBrowserOpener, |
+ OAuth2TokenServiceObserverBridgeDelegate, |
+ SyncObserverModelBridge> { |
+ ios::ChromeBrowserState* _browserState; // weak |
+ BOOL _closeSettingsOnAddAccount; |
+ std::unique_ptr<SyncObserverBridge> _syncObserver; |
+ std::unique_ptr<OAuth2TokenServiceObserverBridge> _tokenServiceObserver; |
+ base::scoped_nsobject<SigninInteractionController> |
+ _signinInteractionController; |
+ // Modal alert for sign out. |
+ base::scoped_nsobject<AlertCoordinator> _alertCoordinator; |
+ // Whether an authentication operation is in progress (e.g switch accounts, |
+ // sign out). |
+ BOOL _authenticationOperationInProgress; |
+ // Whether the view controller is currently being dismissed and new dismiss |
+ // requests should be ignored. |
+ BOOL _isBeingDismissed; |
+ base::WeakNSObject<UIViewController> _settingsDetails; |
+ base::scoped_nsobject<ResizedAvatarCache> _avatarCache; |
+ std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver; |
+ |
+ // Enable lookup of item corresponding to a given identity GAIA ID string. |
+ base::scoped_nsobject<NSDictionary<NSString*, CollectionViewItem*>> |
+ _identityMap; |
+} |
+ |
+// Stops observing browser state services. This is required during the shutdown |
+// phase to avoid observing services for a browser state that is being killed. |
+- (void)stopBrowserStateServiceObservers; |
+ |
+@end |
+ |
+@implementation AccountsCollectionViewController |
+ |
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState |
+ closeSettingsOnAddAccount:(BOOL)closeSettingsOnAddAccount { |
+ DCHECK(browserState); |
+ DCHECK(!browserState->IsOffTheRecord()); |
+ self = [super initWithStyle:CollectionViewControllerStyleAppBar]; |
+ if (self) { |
+ _browserState = browserState; |
+ _closeSettingsOnAddAccount = closeSettingsOnAddAccount; |
+ browser_sync::ProfileSyncService* syncService = |
+ IOSChromeProfileSyncServiceFactory::GetForBrowserState(_browserState); |
+ _syncObserver.reset(new SyncObserverBridge(self, syncService)); |
+ _tokenServiceObserver.reset(new OAuth2TokenServiceObserverBridge( |
+ OAuth2TokenServiceFactory::GetForBrowserState(_browserState), self)); |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(willStartSwitchAccount) |
+ name:kSwitchAccountWillStartNotification |
+ object:nil]; |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(didFinishSwitchAccount) |
+ name:kSwitchAccountDidFinishNotification |
+ object:nil]; |
+ self.collectionViewAccessibilityIdentifier = kSettingsAccountsId; |
+ _avatarCache.reset([[ResizedAvatarCache alloc] init]); |
+ _identityServiceObserver.reset( |
+ new ChromeIdentityServiceObserverBridge(self)); |
+ [self loadModel]; |
+ } |
+ |
+ return self; |
+} |
+ |
+- (void)dealloc { |
+ [[NSNotificationCenter defaultCenter] removeObserver:self]; |
+ [super dealloc]; |
+} |
+ |
+- (void)stopBrowserStateServiceObservers { |
+ _tokenServiceObserver.reset(); |
+ _syncObserver.reset(); |
+} |
+ |
+#pragma mark - SettingsControllerProtocol |
+ |
+- (void)settingsWillBeDismissed { |
+ [_signinInteractionController cancel]; |
+ [_alertCoordinator stop]; |
+ [self stopBrowserStateServiceObservers]; |
+} |
+ |
+#pragma mark - SettingsRootCollectionViewController |
+ |
+- (void)reloadData { |
+ if (![self authService]->IsAuthenticated()) { |
+ // This accounts collection view will be popped or dismissed when the user |
+ // is signed out. Avoid reloading it in that case as that would lead to an |
+ // empty collection view. |
+ return; |
+ } |
+ [super reloadData]; |
+} |
+ |
+- (void)loadModel { |
+ // Update the title with the name with the currently signed-in account. |
+ ChromeIdentity* authenticatedIdentity = |
+ [self authService]->GetAuthenticatedIdentity(); |
+ NSString* title = nil; |
+ if (authenticatedIdentity) { |
+ title = [authenticatedIdentity userFullName]; |
+ if (!title) { |
+ // TODO(crbug.com/656994): Figure how to handle unnamed account. |
+ title = @"Unnamed account"; |
+ } |
+ } |
+ self.title = title; |
+ |
+ [super loadModel]; |
+ |
+ if (![self authService]->IsAuthenticated()) |
+ return; |
+ |
+ CollectionViewModel* model = self.collectionViewModel; |
+ |
+ NSMutableDictionary<NSString*, CollectionViewItem*>* mutableIdentityMap = |
+ [[[NSMutableDictionary alloc] init] autorelease]; |
+ |
+ // Account cells. |
+ ProfileOAuth2TokenService* oauth2_service = |
+ OAuth2TokenServiceFactory::GetForBrowserState(_browserState); |
+ AccountTrackerService* accountTracker = |
+ ios::AccountTrackerServiceFactory::GetForBrowserState(_browserState); |
+ [model addSectionWithIdentifier:SectionIdentifierAccounts]; |
+ [model setHeader:[self header] |
+ forSectionWithIdentifier:SectionIdentifierAccounts]; |
+ for (const std::string& account_id : oauth2_service->GetAccounts()) { |
+ AccountInfo account = accountTracker->GetAccountInfo(account_id); |
+ ChromeIdentity* identity = ios::GetChromeBrowserProvider() |
+ ->GetChromeIdentityService() |
+ ->GetIdentityWithGaiaID(account.gaia); |
+ CollectionViewItem* item = [self accountItem:identity]; |
+ [model addItem:item toSectionWithIdentifier:SectionIdentifierAccounts]; |
+ |
+ [mutableIdentityMap setObject:item forKey:identity.gaiaID]; |
+ } |
+ _identityMap.reset([mutableIdentityMap retain]); |
+ |
+ [model addItem:[self addAccountItem] |
+ toSectionWithIdentifier:SectionIdentifierAccounts]; |
+ |
+ // Sync and Google Activity section. |
+ [model addSectionWithIdentifier:SectionIdentifierSync]; |
+ [model addItem:[self syncItem] toSectionWithIdentifier:SectionIdentifierSync]; |
+ [model addItem:[self googleActivityControlsItem] |
+ toSectionWithIdentifier:SectionIdentifierSync]; |
+ |
+ // Sign out section. |
+ [model addSectionWithIdentifier:SectionIdentifierSignOut]; |
+ [model addItem:[self signOutItem] |
+ toSectionWithIdentifier:SectionIdentifierSignOut]; |
+} |
+ |
+#pragma mark - Model objects |
+ |
+- (CollectionViewItem*)header { |
+ CollectionViewTextItem* header = [ |
+ [[CollectionViewTextItem alloc] initWithType:ItemTypeHeader] autorelease]; |
+ header.text = l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_DESCRIPTION); |
+ header.accessibilityIdentifier = kSettingsHeaderId; |
+ return header; |
+} |
+ |
+- (CollectionViewItem*)accountItem:(ChromeIdentity*)identity { |
+ CollectionViewAccountItem* item = [[[CollectionViewAccountItem alloc] |
+ initWithType:ItemTypeAccount] autorelease]; |
+ [self updateAccountItem:item withIdentity:identity]; |
+ return item; |
+} |
+ |
+- (void)updateAccountItem:(CollectionViewAccountItem*)item |
+ withIdentity:(ChromeIdentity*)identity { |
+ item.image = [_avatarCache resizedAvatarForIdentity:identity]; |
+ item.text = identity.userEmail; |
+ item.chromeIdentity = identity; |
+ item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator; |
+} |
+ |
+- (CollectionViewItem*)addAccountItem { |
+ CollectionViewAccountItem* item = [[[CollectionViewAccountItem alloc] |
+ initWithType:ItemTypeAddAccount] autorelease]; |
+ item.text = |
+ l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_ADD_ACCOUNT_BUTTON); |
+ item.image = [UIImage imageNamed:@"settings_accounts_add_account"]; |
+ return item; |
+} |
+ |
+- (CollectionViewItem*)syncItem { |
+ AccountControlItem* item = |
+ [[[AccountControlItem alloc] initWithType:ItemTypeSync] autorelease]; |
+ item.text = l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_SYNC_TITLE); |
+ item.accessibilityIdentifier = kSettingsAccountsSyncCellId; |
+ [self updateSyncItem:item]; |
+ item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator; |
+ return item; |
+} |
+ |
+- (void)updateSyncItem:(AccountControlItem*)syncItem { |
+ SyncSetupService* syncSetupService = |
+ SyncSetupServiceFactory::GetForBrowserState(_browserState); |
+ if (!syncSetupService->HasFinishedInitialSetup()) { |
+ syncItem.image = [UIImage imageNamed:@"settings_sync"]; |
+ syncItem.detailText = |
+ l10n_util::GetNSString(IDS_IOS_SYNC_SETUP_IN_PROGRESS); |
+ syncItem.shouldDisplayError = NO; |
+ return; |
+ } |
+ |
+ ChromeIdentity* identity = [self authService]->GetAuthenticatedIdentity(); |
+ bool hasSyncError = !ios_internal::sync::IsTransientSyncError( |
+ syncSetupService->GetSyncServiceState()); |
+ bool hasMDMError = [self authService]->HasCachedMDMErrorForIdentity(identity); |
+ if (hasSyncError || hasMDMError) { |
+ syncItem.image = [UIImage imageNamed:@"settings_error"]; |
+ syncItem.detailText = |
+ ios_internal::sync::GetSyncErrorDescriptionForBrowserState( |
+ _browserState); |
+ syncItem.shouldDisplayError = YES; |
+ } else { |
+ syncItem.image = [UIImage imageNamed:@"settings_sync"]; |
+ syncItem.detailText = |
+ syncSetupService->IsSyncEnabled() |
+ ? l10n_util::GetNSStringF( |
+ IDS_IOS_SIGN_IN_TO_CHROME_SETTING_SYNCING, |
+ base::SysNSStringToUTF16([identity userEmail])) |
+ : l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_SYNC_IS_OFF); |
+ syncItem.shouldDisplayError = NO; |
+ } |
+} |
+ |
+- (CollectionViewItem*)googleActivityControlsItem { |
+ AccountControlItem* item = [[[AccountControlItem alloc] |
+ initWithType:ItemTypeGoogleActivityControls] autorelease]; |
+ item.text = l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_GOOGLE_TITLE); |
+ item.detailText = |
+ l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_GOOGLE_DESCRIPTION); |
+ item.image = ios::GetChromeBrowserProvider() |
+ ->GetBrandedImageProvider() |
+ ->GetAccountsListActivityControlsImage(); |
+ item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator; |
+ return item; |
+} |
+ |
+- (CollectionViewItem*)signOutItem { |
+ CollectionViewTextItem* item = [[[CollectionViewTextItem alloc] |
+ initWithType:ItemTypeSignOut] autorelease]; |
+ item.text = l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_SIGNOUT); |
+ item.accessibilityTraits |= UIAccessibilityTraitButton; |
+ item.accessibilityIdentifier = kSettingsAccountsSignoutCellId; |
+ return item; |
+} |
+ |
+#pragma mark - UICollectionViewDelegate |
+ |
+- (void)collectionView:(UICollectionView*)collectionView |
+ didSelectItemAtIndexPath:(NSIndexPath*)indexPath { |
+ [super collectionView:collectionView didSelectItemAtIndexPath:indexPath]; |
+ |
+ NSInteger itemType = |
+ [self.collectionViewModel itemTypeForIndexPath:indexPath]; |
+ |
+ switch (itemType) { |
+ case ItemTypeAccount: { |
+ CollectionViewAccountItem* item = |
+ base::mac::ObjCCastStrict<CollectionViewAccountItem>( |
+ [self.collectionViewModel itemAtIndexPath:indexPath]); |
+ DCHECK(item.chromeIdentity); |
+ [self showAccountDetails:item.chromeIdentity]; |
+ break; |
+ } |
+ case ItemTypeAddAccount: |
+ [self showAddAccount]; |
+ break; |
+ case ItemTypeSync: |
+ [self showSyncSettings]; |
+ break; |
+ case ItemTypeGoogleActivityControls: |
+ [self showGoogleActivitySettings]; |
+ break; |
+ case ItemTypeSignOut: |
+ [self showDisconnect]; |
+ break; |
+ default: |
+ break; |
+ } |
+} |
+ |
+#pragma mark - UICollectionViewDataSource |
+ |
+- (UICollectionReusableView*)collectionView:(UICollectionView*)collectionView |
+ viewForSupplementaryElementOfKind:(NSString*)kind |
+ atIndexPath:(NSIndexPath*)indexPath { |
+ UICollectionReusableView* view = [super collectionView:collectionView |
+ viewForSupplementaryElementOfKind:kind |
+ atIndexPath:indexPath]; |
+ MDCCollectionViewTextCell* textCell = |
+ base::mac::ObjCCast<MDCCollectionViewTextCell>(view); |
+ if (textCell) { |
+ textCell.textLabel.textColor = [[MDCPalette greyPalette] tint500]; |
+ } |
+ return view; |
+} |
+ |
+#pragma mark - MDCCollectionViewStylingDelegate |
+ |
+- (CGFloat)collectionView:(UICollectionView*)collectionView |
+ cellHeightAtIndexPath:(NSIndexPath*)indexPath { |
+ CollectionViewItem* item = |
+ [self.collectionViewModel itemAtIndexPath:indexPath]; |
+ if (item.type == ItemTypeGoogleActivityControls || |
+ item.type == ItemTypeSync) { |
+ return [MDCCollectionViewCell |
+ cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds) |
+ forItem:item]; |
+ } else if (item.type == ItemTypeSignOut) { |
+ return MDCCellDefaultOneLineHeight; |
+ } |
+ return MDCCellDefaultTwoLineHeight; |
+} |
+ |
+#pragma mark - SyncObserverModelBridge |
+ |
+- (void)onSyncStateChanged { |
+ if (![self authService]->IsAuthenticated()) { |
+ // Ignore sync state changed notification if signed out. |
+ return; |
+ } |
+ |
+ NSIndexPath* index = |
+ [self.collectionViewModel indexPathForItemType:ItemTypeSync |
+ sectionIdentifier:SectionIdentifierSync]; |
+ |
+ CollectionViewModel* model = self.collectionViewModel; |
+ if ([model numberOfSections] > index.section && |
+ [model numberOfItemsInSection:index.section] > index.row) { |
+ AccountControlItem* item = base::mac::ObjCCastStrict<AccountControlItem>( |
+ [model itemAtIndexPath:index]); |
+ [self updateSyncItem:item]; |
+ [self.collectionView reloadItemsAtIndexPaths:@[ index ]]; |
+ } |
+} |
+ |
+#pragma mark - OAuth2TokenServiceObserverBridgeDelegate |
+ |
+- (void)onEndBatchChanges { |
+ [self reloadData]; |
+ [self popViewIfSignedOut]; |
+ if (![self authService]->IsAuthenticated() && _settingsDetails) { |
+ [_settingsDetails dismissViewControllerAnimated:YES completion:nil]; |
+ _settingsDetails.reset(); |
+ } |
+} |
+ |
+#pragma mark - Sync and Activity Controls |
+ |
+- (void)showSyncSettings { |
+ if ([_alertCoordinator isVisible]) |
+ return; |
+ |
+ if ([self authService]->ShowMDMErrorDialogForIdentity( |
+ [self authService]->GetAuthenticatedIdentity())) { |
+ // If there is an MDM error for the synced identity, show it instead. |
+ return; |
+ } |
+ |
+ base::scoped_nsobject<UIViewController> controllerToPush( |
+ [[SyncSettingsCollectionViewController alloc] |
+ initWithBrowserState:_browserState |
+ allowSwitchSyncAccount:YES]); |
+ [self.navigationController pushViewController:controllerToPush animated:YES]; |
+} |
+ |
+- (void)showGoogleActivitySettings { |
+ if ([_alertCoordinator isVisible]) |
+ return; |
+ base::RecordAction(base::UserMetricsAction( |
+ "Signin_AccountSettings_GoogleActivityControlsClicked")); |
+ base::scoped_nsobject<UINavigationController> settingsDetails( |
+ ios::GetChromeBrowserProvider() |
+ ->GetChromeIdentityService() |
+ ->NewWebAndAppSettingDetails( |
+ [self authService]->GetAuthenticatedIdentity(), self)); |
+ UIImage* closeIcon = [ChromeIcon closeIcon]; |
+ SEL action = @selector(closeGoogleActivitySettings:); |
+ [settingsDetails.get().topViewController navigationItem].leftBarButtonItem = |
+ [ChromeIcon templateBarButtonItemWithImage:closeIcon |
+ target:self |
+ action:action]; |
+ [self presentViewController:settingsDetails animated:YES completion:nil]; |
+ |
+ // Keep a weak reference on the settings details, to be able to dismiss it |
+ // when the primary account is removed. |
+ _settingsDetails.reset(settingsDetails); |
+} |
+ |
+- (void)closeGoogleActivitySettings:(id)sender { |
+ DCHECK(_settingsDetails); |
+ [self dismissViewControllerAnimated:YES completion:nil]; |
+} |
+ |
+#pragma mark - Authentication operations |
+ |
+- (void)showAddAccount { |
+ if ([_alertCoordinator isVisible]) |
+ return; |
+ if (_signinInteractionController) { |
+ // Ignore this user action if there is already an add account operation |
+ // in-progress. |
+ return; |
+ } |
+ _signinInteractionController.reset([[SigninInteractionController alloc] |
+ initWithBrowserState:_browserState |
+ presentingViewController:self.navigationController |
+ isPresentedOnSettings:YES |
+ signInAccessPoint:signin_metrics::AccessPoint:: |
+ ACCESS_POINT_SETTINGS]); |
+ |
+ // |_authenticationOperationInProgress| is reset when the signin interaction |
+ // controller is dismissed. |
+ _authenticationOperationInProgress = YES; |
+ base::WeakNSObject<AccountsCollectionViewController> weakSelf(self); |
+ [_signinInteractionController addAccountWithCompletion:^(BOOL success) { |
+ [weakSelf handleDidAddAccount:success]; |
+ } |
+ viewController:self]; |
+} |
+ |
+- (void)handleDidAddAccount:(BOOL)success { |
+ _signinInteractionController.reset(); |
+ [self handleAuthenticationOperationDidFinish]; |
+ if (success && _closeSettingsOnAddAccount) { |
+ base::scoped_nsobject<GenericChromeCommand> closeSettingsCommand( |
+ [[GenericChromeCommand alloc] initWithTag:IDC_CLOSE_SETTINGS]); |
+ [self chromeExecuteCommand:closeSettingsCommand]; |
+ } |
+} |
+ |
+- (void)showAccountDetails:(ChromeIdentity*)identity { |
+ if ([_alertCoordinator isVisible]) |
+ return; |
+ base::scoped_nsobject<UIViewController> accountDetails( |
+ ios::GetChromeBrowserProvider() |
+ ->GetChromeIdentityService() |
+ ->NewAccountDetails(identity, self)); |
+ if (!accountDetails) { |
+ // Failed to create a new account details. Ignored. |
+ return; |
+ } |
+ [self presentViewController:accountDetails animated:YES completion:nil]; |
+ |
+ // Keep a weak reference on the account details, to be able to dismiss it |
+ // when the primary account is removed. |
+ _settingsDetails.reset(accountDetails); |
+} |
+ |
+- (void)showDisconnect { |
+ if (_authenticationOperationInProgress || [_alertCoordinator isVisible] || |
+ self != [self.navigationController topViewController]) { |
+ // An action is already in progress, ignore user's request. |
+ return; |
+ } |
+ |
+ NSString* title; |
+ NSString* message; |
+ NSString* continueButtonTitle; |
+ if ([self authService]->IsAuthenticatedIdentityManaged()) { |
+ std::string hosted_domain = |
+ ios::SigninManagerFactory::GetForBrowserState(_browserState) |
+ ->GetAuthenticatedAccountInfo() |
+ .hosted_domain; |
+ title = l10n_util::GetNSString(IDS_IOS_MANAGED_DISCONNECT_DIALOG_TITLE); |
+ message = l10n_util::GetNSStringF(IDS_IOS_MANAGED_DISCONNECT_DIALOG_INFO, |
+ base::UTF8ToUTF16(hosted_domain)); |
+ continueButtonTitle = |
+ l10n_util::GetNSString(IDS_IOS_MANAGED_DISCONNECT_DIALOG_ACCEPT); |
+ } else { |
+ title = l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_TITLE); |
+ message = l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_INFO_MOBILE); |
+ continueButtonTitle = l10n_util::GetNSString( |
+ IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE); |
+ } |
+ _alertCoordinator.reset([[AlertCoordinator alloc] |
+ initWithBaseViewController:self |
+ title:title |
+ message:message]); |
+ |
+ [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL) |
+ action:nil |
+ style:UIAlertActionStyleCancel]; |
+ base::WeakNSObject<AccountsCollectionViewController> weakSelf(self); |
+ [_alertCoordinator addItemWithTitle:continueButtonTitle |
+ action:^{ |
+ [weakSelf handleDisconnect]; |
+ } |
+ style:UIAlertActionStyleDefault]; |
+ [_alertCoordinator start]; |
+} |
+ |
+- (void)handleDisconnect { |
+ AuthenticationService* authService = [self authService]; |
+ if (authService->IsAuthenticated()) { |
+ _authenticationOperationInProgress = YES; |
+ [self preventUserInteraction]; |
+ authService->SignOut(signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, ^{ |
+ [self allowUserInteraction]; |
+ _authenticationOperationInProgress = NO; |
+ [base::mac::ObjCCastStrict<SettingsNavigationController>( |
+ self.navigationController) |
+ popViewControllerOrCloseSettingsAnimated:YES]; |
+ }); |
+ } |
+} |
+ |
+// Sets |_authenticationOperationInProgress| to NO and pops this accounts |
+// collection view controller if the user is signed out. |
+- (void)handleAuthenticationOperationDidFinish { |
+ DCHECK(_authenticationOperationInProgress); |
+ _authenticationOperationInProgress = NO; |
+ [self popViewIfSignedOut]; |
+} |
+ |
+- (void)popViewIfSignedOut { |
+ if ([self authService]->IsAuthenticated()) { |
+ return; |
+ } |
+ if (_authenticationOperationInProgress) { |
+ // The signed out state might be temporary (e.g. account switch, ...). |
+ // Don't pop this view based on intermediary values. |
+ return; |
+ } |
+ [self dismissSelfAnimated:NO]; |
+} |
+ |
+- (void)dismissSelfAnimated:(BOOL)animated { |
+ if (_isBeingDismissed) { |
+ return; |
+ } |
+ _isBeingDismissed = YES; |
+ [_signinInteractionController cancelAndDismiss]; |
+ [_alertCoordinator stop]; |
+ [self.navigationController popToViewController:self animated:NO]; |
+ [base::mac::ObjCCastStrict<SettingsNavigationController>( |
+ self.navigationController) |
+ popViewControllerOrCloseSettingsAnimated:animated]; |
+} |
+ |
+#pragma mark - Access to authentication service |
+ |
+- (AuthenticationService*)authService { |
+ return AuthenticationServiceFactory::GetForBrowserState(_browserState); |
+} |
+ |
+#pragma mark - Switch accounts notifications |
+ |
+- (void)willStartSwitchAccount { |
+ _authenticationOperationInProgress = YES; |
+} |
+ |
+- (void)didFinishSwitchAccount { |
+ [self handleAuthenticationOperationDidFinish]; |
+} |
+ |
+#pragma mark - ChromeIdentityBrowserOpener |
+ |
+- (void)openURL:(NSURL*)url |
+ view:(UIView*)view |
+ viewController:(UIViewController*)viewController { |
+ base::scoped_nsobject<OpenUrlCommand> command( |
+ [[OpenUrlCommand alloc] initWithURLFromChrome:net::GURLWithNSURL(url)]); |
+ [command setTag:IDC_CLOSE_SETTINGS_AND_OPEN_URL]; |
+ [self chromeExecuteCommand:command]; |
+} |
+ |
+#pragma mark - ChromeIdentityServiceObserver |
+ |
+- (void)onProfileUpdate:(ChromeIdentity*)identity { |
+ CollectionViewAccountItem* item = |
+ base::mac::ObjCCastStrict<CollectionViewAccountItem>( |
+ [_identityMap objectForKey:identity.gaiaID]); |
+ [self updateAccountItem:item withIdentity:identity]; |
+ NSIndexPath* indexPath = |
+ [self.collectionViewModel indexPathForItem:item |
+ inSectionWithIdentifier:SectionIdentifierAccounts]; |
+ [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]]; |
+} |
+ |
+- (void)onChromeIdentityServiceWillBeDestroyed { |
+ _identityServiceObserver.reset(); |
+} |
+ |
+@end |