| Index: ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.mm
|
| diff --git a/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.mm b/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9f3a7130001ff03046e3f9c4d5af585993acf447
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.mm
|
| @@ -0,0 +1,393 @@
|
| +// Copyright 2016 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/authentication/signed_in_accounts_view_controller.h"
|
| +
|
| +#import "base/mac/foundation_util.h"
|
| +#include "components/signin/core/browser/account_tracker_service.h"
|
| +#include "components/signin/core/browser/profile_oauth2_token_service.h"
|
| +#include "components/signin/ios/browser/oauth2_token_service_observer_bridge.h"
|
| +#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
|
| +#include "ios/chrome/browser/signin/account_tracker_service_factory.h"
|
| +#include "ios/chrome/browser/signin/authentication_service.h"
|
| +#include "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"
|
| +#import "ios/chrome/browser/ui/collection_view/cells/collection_view_account_item.h"
|
| +#import "ios/chrome/browser/ui/collection_view/collection_view_controller.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/settings/utils/resized_avatar_cache.h"
|
| +#import "ios/chrome/browser/ui/uikit_ui_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"
|
| +#import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
|
| +#import "ios/public/provider/chrome/browser/signin/chrome_identity_service.h"
|
| +#include "ios/public/provider/chrome/browser/signin/signin_resources_provider.h"
|
| +#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h"
|
| +#import "ios/third_party/material_components_ios/src/components/Dialogs/src/MaterialDialogs.h"
|
| +#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
|
| +#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
|
| +#include "ui/base/l10n/l10n_util_mac.h"
|
| +
|
| +namespace {
|
| +
|
| +const int kMaxShownAccounts = 3;
|
| +const CGFloat kAccountsExtraBottomInset = 16;
|
| +const CGFloat kVerticalPadding = 24;
|
| +const CGFloat kButtonVerticalPadding = 16;
|
| +const CGFloat kHorizontalPadding = 24;
|
| +const CGFloat kAccountsHorizontalPadding = 8;
|
| +const CGFloat kButtonHorizontalPadding = 16;
|
| +const CGFloat kBetweenButtonsPadding = 8;
|
| +const CGFloat kMDCMinHorizontalPadding = 20;
|
| +const CGFloat kDialogMaxWidth = 328;
|
| +
|
| +typedef NS_ENUM(NSInteger, SectionIdentifier) {
|
| + SectionIdentifierAccounts = kSectionIdentifierEnumZero,
|
| +};
|
| +
|
| +typedef NS_ENUM(NSInteger, ItemType) {
|
| + ItemTypeAccount = kItemTypeEnumZero,
|
| +};
|
| +
|
| +// Whether the Signed In Accounts view is currently being shown.
|
| +BOOL gSignedInAccountsViewControllerIsShown = NO;
|
| +
|
| +} // namespace
|
| +
|
| +@interface SignedInAccountsCollectionViewController
|
| + : CollectionViewController<ChromeIdentityServiceObserver> {
|
| + ios::ChromeBrowserState* _browserState; // Weak.
|
| + std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver;
|
| + base::scoped_nsobject<ResizedAvatarCache> _avatarCache;
|
| +
|
| + // Enable lookup of item corresponding to a given identity GAIA ID string.
|
| + base::scoped_nsobject<NSDictionary<NSString*, CollectionViewItem*>>
|
| + _identityMap;
|
| +}
|
| +@end
|
| +
|
| +@implementation SignedInAccountsCollectionViewController
|
| +
|
| +- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState {
|
| + self = [super initWithStyle:CollectionViewControllerStyleDefault];
|
| + if (self) {
|
| + _browserState = browserState;
|
| + _avatarCache.reset([[ResizedAvatarCache alloc] init]);
|
| + _identityServiceObserver.reset(
|
| + new ChromeIdentityServiceObserverBridge(self));
|
| + [self loadModel];
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (void)viewDidLoad {
|
| + [super viewDidLoad];
|
| +
|
| + self.styler.shouldHideSeparators = YES;
|
| + self.collectionView.backgroundColor = [UIColor clearColor];
|
| +
|
| + // Add an inset at the bottom so the user can see whether it is possible to
|
| + // scroll to see additional accounts.
|
| + UIEdgeInsets contentInset = self.collectionView.contentInset;
|
| + contentInset.bottom += kAccountsExtraBottomInset;
|
| + self.collectionView.contentInset = contentInset;
|
| +}
|
| +
|
| +#pragma mark CollectionViewController
|
| +
|
| +- (void)loadModel {
|
| + [super loadModel];
|
| + CollectionViewModel* model = self.collectionViewModel;
|
| + NSMutableDictionary<NSString*, CollectionViewItem*>* mutableIdentityMap =
|
| + [[[NSMutableDictionary alloc] init] autorelease];
|
| +
|
| + [model addSectionWithIdentifier:SectionIdentifierAccounts];
|
| + ProfileOAuth2TokenService* oauth2_service =
|
| + OAuth2TokenServiceFactory::GetForBrowserState(_browserState);
|
| + AccountTrackerService* accountTracker =
|
| + ios::AccountTrackerServiceFactory::GetForBrowserState(_browserState);
|
| + 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]);
|
| +}
|
| +
|
| +#pragma mark Model objects
|
| +
|
| +- (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 userFullName];
|
| + item.detailText = [identity userEmail];
|
| + item.chromeIdentity = identity;
|
| +}
|
| +
|
| +#pragma mark MDCCollectionViewStylingDelegate
|
| +
|
| +- (BOOL)collectionView:(UICollectionView*)collectionView
|
| + shouldHideItemBackgroundAtIndexPath:(NSIndexPath*)indexPath {
|
| + return YES;
|
| +}
|
| +
|
| +- (CGFloat)collectionView:(UICollectionView*)collectionView
|
| + cellHeightAtIndexPath:(NSIndexPath*)indexPath {
|
| + return MDCCellDefaultOneLineWithAvatarHeight;
|
| +}
|
| +
|
| +- (BOOL)collectionView:(UICollectionView*)collectionView
|
| + hidesInkViewAtIndexPath:(NSIndexPath*)indexPath {
|
| + return YES;
|
| +}
|
| +
|
| +#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
|
| +
|
| +@interface SignedInAccountsViewController ()<
|
| + OAuth2TokenServiceObserverBridgeDelegate> {
|
| + ios::ChromeBrowserState* _browserState; // Weak.
|
| + std::unique_ptr<OAuth2TokenServiceObserverBridge> _tokenServiceObserver;
|
| + base::scoped_nsobject<MDCDialogTransitionController> _transitionController;
|
| +
|
| + base::scoped_nsobject<UILabel> _titleLabel;
|
| + base::scoped_nsobject<SignedInAccountsCollectionViewController>
|
| + _accountsCollection;
|
| + base::scoped_nsobject<UILabel> _infoLabel;
|
| + base::scoped_nsobject<MDCButton> _primaryButton;
|
| + base::scoped_nsobject<MDCButton> _secondaryButton;
|
| +}
|
| +@end
|
| +
|
| +@implementation SignedInAccountsViewController
|
| +
|
| ++ (BOOL)shouldBePresentedForBrowserState:
|
| + (ios::ChromeBrowserState*)browserState {
|
| + if (!browserState || browserState->IsOffTheRecord()) {
|
| + return NO;
|
| + }
|
| + AuthenticationService* authService =
|
| + AuthenticationServiceFactory::GetForBrowserState(browserState);
|
| + return !gSignedInAccountsViewControllerIsShown &&
|
| + authService->IsAuthenticated() && authService->HaveAccountsChanged();
|
| +}
|
| +
|
| +#pragma mark Initialization
|
| +
|
| +- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState {
|
| + self = [super initWithNibName:nil bundle:nil];
|
| + if (self) {
|
| + _browserState = browserState;
|
| + _tokenServiceObserver.reset(new OAuth2TokenServiceObserverBridge(
|
| + OAuth2TokenServiceFactory::GetForBrowserState(_browserState), self));
|
| + _transitionController.reset([[MDCDialogTransitionController alloc] init]);
|
| + self.modalPresentationStyle = UIModalPresentationCustom;
|
| + self.transitioningDelegate = _transitionController;
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (void)dismiss {
|
| + [self.presentingViewController dismissViewControllerAnimated:YES
|
| + completion:nil];
|
| +}
|
| +
|
| +- (void)dealloc {
|
| + [_primaryButton removeTarget:self
|
| + action:@selector(onPrimaryButtonPressed:)
|
| + forControlEvents:UIControlEventTouchDown];
|
| + [_secondaryButton removeTarget:self
|
| + action:@selector(onSecondaryButtonPressed:)
|
| + forControlEvents:UIControlEventTouchDown];
|
| + [super dealloc];
|
| +}
|
| +
|
| +#pragma mark UIViewController
|
| +
|
| +- (CGSize)preferredContentSize {
|
| + CGFloat width = MIN(kDialogMaxWidth,
|
| + self.presentingViewController.view.bounds.size.width -
|
| + 2 * kMDCMinHorizontalPadding);
|
| + OAuth2TokenService* token_service =
|
| + OAuth2TokenServiceFactory::GetForBrowserState(_browserState);
|
| + int shownAccounts =
|
| + MIN(kMaxShownAccounts, token_service->GetAccounts().size());
|
| + CGSize maxSize = CGSizeMake(width - 2 * kHorizontalPadding, CGFLOAT_MAX);
|
| + CGSize buttonSize = [_primaryButton sizeThatFits:maxSize];
|
| + CGSize infoSize = [_infoLabel sizeThatFits:maxSize];
|
| + CGSize titleSize = [_titleLabel sizeThatFits:maxSize];
|
| + CGFloat height = kVerticalPadding + titleSize.height + kVerticalPadding +
|
| + shownAccounts * MDCCellDefaultOneLineWithAvatarHeight +
|
| + kVerticalPadding + infoSize.height + kVerticalPadding +
|
| + buttonSize.height + kButtonVerticalPadding;
|
| + return CGSizeMake(width, height);
|
| +}
|
| +
|
| +- (void)viewDidLoad {
|
| + [super viewDidLoad];
|
| +
|
| + self.view.backgroundColor = [UIColor whiteColor];
|
| +
|
| + _titleLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]);
|
| + _titleLabel.get().text =
|
| + l10n_util::GetNSString(IDS_IOS_SIGNED_IN_ACCOUNTS_VIEW_TITLE);
|
| + _titleLabel.get().textColor = [[MDCPalette greyPalette] tint900];
|
| + _titleLabel.get().font = [MDCTypography headlineFont];
|
| + _titleLabel.get().translatesAutoresizingMaskIntoConstraints = NO;
|
| + [self.view addSubview:_titleLabel];
|
| +
|
| + _accountsCollection.reset([[SignedInAccountsCollectionViewController alloc]
|
| + initWithBrowserState:_browserState]);
|
| + _accountsCollection.get().view.translatesAutoresizingMaskIntoConstraints = NO;
|
| + [self addChildViewController:_accountsCollection];
|
| + [self.view addSubview:_accountsCollection.get().view];
|
| + [_accountsCollection didMoveToParentViewController:self];
|
| +
|
| + _infoLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]);
|
| + _infoLabel.get().text =
|
| + l10n_util::GetNSString(IDS_IOS_SIGNED_IN_ACCOUNTS_VIEW_INFO);
|
| + _infoLabel.get().numberOfLines = 0;
|
| + _infoLabel.get().textColor = [[MDCPalette greyPalette] tint700];
|
| + _infoLabel.get().font = [MDCTypography body1Font];
|
| + _infoLabel.get().translatesAutoresizingMaskIntoConstraints = NO;
|
| + [self.view addSubview:_infoLabel];
|
| +
|
| + _primaryButton.reset([[MDCFlatButton alloc] init]);
|
| + [_primaryButton addTarget:self
|
| + action:@selector(onPrimaryButtonPressed:)
|
| + forControlEvents:UIControlEventTouchUpInside];
|
| + [_primaryButton
|
| + setTitle:l10n_util::GetNSString(IDS_IOS_SIGNED_IN_ACCOUNTS_VIEW_OK_BUTTON)
|
| + forState:UIControlStateNormal];
|
| + [_primaryButton setBackgroundColor:[[MDCPalette cr_bluePalette] tint500]
|
| + forState:UIControlStateNormal];
|
| + _primaryButton.get().customTitleColor = [UIColor whiteColor];
|
| + _primaryButton.get().underlyingColorHint = [UIColor blackColor];
|
| + _primaryButton.get().inkColor = [UIColor colorWithWhite:1 alpha:0.2f];
|
| + _primaryButton.get().translatesAutoresizingMaskIntoConstraints = NO;
|
| + [self.view addSubview:_primaryButton];
|
| +
|
| + _secondaryButton.reset([[MDCFlatButton alloc] init]);
|
| + [_secondaryButton addTarget:self
|
| + action:@selector(onSecondaryButtonPressed:)
|
| + forControlEvents:UIControlEventTouchUpInside];
|
| + [_secondaryButton
|
| + setTitle:l10n_util::GetNSString(
|
| + IDS_IOS_SIGNED_IN_ACCOUNTS_VIEW_SETTINGS_BUTTON)
|
| + forState:UIControlStateNormal];
|
| + [_secondaryButton setBackgroundColor:[UIColor whiteColor]
|
| + forState:UIControlStateNormal];
|
| + _secondaryButton.get().customTitleColor =
|
| + [[MDCPalette cr_bluePalette] tint500];
|
| + _secondaryButton.get().underlyingColorHint = [UIColor whiteColor];
|
| + _secondaryButton.get().inkColor = [UIColor colorWithWhite:0 alpha:0.06f];
|
| + _secondaryButton.get().translatesAutoresizingMaskIntoConstraints = NO;
|
| + [self.view addSubview:_secondaryButton];
|
| +
|
| + NSDictionary* views = @{
|
| + @"title" : _titleLabel,
|
| + @"accounts" : _accountsCollection.get().view,
|
| + @"info" : _infoLabel,
|
| + @"primaryButton" : _primaryButton,
|
| + @"secondaryButton" : _secondaryButton,
|
| + };
|
| + NSDictionary* metrics = @{
|
| + @"verticalPadding" : @(kVerticalPadding),
|
| + @"accountsVerticalPadding" :
|
| + @(kVerticalPadding - kAccountsExtraBottomInset),
|
| + @"buttonVerticalPadding" : @(kButtonVerticalPadding),
|
| + @"horizontalPadding" : @(kHorizontalPadding),
|
| + @"accountsHorizontalPadding" : @(kAccountsHorizontalPadding),
|
| + @"buttonHorizontalPadding" : @(kButtonHorizontalPadding),
|
| + @"betweenButtonsPadding" : @(kBetweenButtonsPadding),
|
| + };
|
| + NSArray* constraints = @[
|
| + @"V:|-(verticalPadding)-[title]-(verticalPadding)-[accounts]",
|
| + @"V:[accounts]-(accountsVerticalPadding)-[info]",
|
| + @"V:[info]-(verticalPadding)-[primaryButton]-(buttonVerticalPadding)-|",
|
| + @"V:[info]-(verticalPadding)-[secondaryButton]-(buttonVerticalPadding)-|",
|
| + @"H:|-(horizontalPadding)-[title]-(horizontalPadding)-|",
|
| + @"H:|-(accountsHorizontalPadding)-[accounts]-(accountsHorizontalPadding)-|",
|
| + @"H:|-(horizontalPadding)-[info]-(horizontalPadding)-|",
|
| + @"H:[secondaryButton]-(betweenButtonsPadding)-[primaryButton]",
|
| + @"H:[primaryButton]-(buttonHorizontalPadding)-|",
|
| + ];
|
| +
|
| + ApplyVisualConstraintsWithMetrics(constraints, views, metrics);
|
| +}
|
| +
|
| +- (void)viewWillAppear:(BOOL)animated {
|
| + [super viewWillAppear:animated];
|
| + if ([self isBeingPresented] || [self isMovingToParentViewController]) {
|
| + gSignedInAccountsViewControllerIsShown = YES;
|
| + }
|
| +}
|
| +
|
| +- (void)viewWillDisappear:(BOOL)animated {
|
| + [super viewWillDisappear:animated];
|
| + if ([self isBeingDismissed] || [self isMovingFromParentViewController]) {
|
| + gSignedInAccountsViewControllerIsShown = NO;
|
| + }
|
| +}
|
| +
|
| +#pragma mark Events
|
| +
|
| +- (void)onPrimaryButtonPressed:(id)sender {
|
| + [self dismiss];
|
| +}
|
| +
|
| +- (void)onSecondaryButtonPressed:(id)sender {
|
| + [self dismiss];
|
| + base::scoped_nsobject<GenericChromeCommand> showAccountsSettingsCommand(
|
| + [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_ACCOUNTS_SETTINGS]);
|
| + [self chromeExecuteCommand:showAccountsSettingsCommand];
|
| +}
|
| +
|
| +#pragma mark OAuth2TokenServiceObserverBridgeDelegate
|
| +
|
| +- (void)onEndBatchChanges {
|
| + ProfileOAuth2TokenService* tokenService =
|
| + OAuth2TokenServiceFactory::GetForBrowserState(_browserState);
|
| + if (tokenService->GetAccounts().empty()) {
|
| + [self dismiss];
|
| + return;
|
| + }
|
| + [_accountsCollection loadModel];
|
| + [_accountsCollection.get().collectionView reloadData];
|
| +}
|
| +
|
| +@end
|
|
|