OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller
.h" |
| 6 |
| 7 #import "base/mac/foundation_util.h" |
| 8 #include "components/signin/core/browser/account_tracker_service.h" |
| 9 #include "components/signin/core/browser/profile_oauth2_token_service.h" |
| 10 #include "components/signin/ios/browser/oauth2_token_service_observer_bridge.h" |
| 11 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 12 #include "ios/chrome/browser/signin/account_tracker_service_factory.h" |
| 13 #include "ios/chrome/browser/signin/authentication_service.h" |
| 14 #include "ios/chrome/browser/signin/authentication_service_factory.h" |
| 15 #include "ios/chrome/browser/signin/chrome_identity_service_observer_bridge.h" |
| 16 #include "ios/chrome/browser/signin/oauth2_token_service_factory.h" |
| 17 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_account_ite
m.h" |
| 18 #import "ios/chrome/browser/ui/collection_view/collection_view_controller.h" |
| 19 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h" |
| 20 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
| 21 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
| 22 #import "ios/chrome/browser/ui/commands/generic_chrome_command.h" |
| 23 #include "ios/chrome/browser/ui/commands/ios_command_ids.h" |
| 24 #import "ios/chrome/browser/ui/settings/utils/resized_avatar_cache.h" |
| 25 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 26 #include "ios/chrome/grit/ios_chromium_strings.h" |
| 27 #include "ios/chrome/grit/ios_strings.h" |
| 28 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 29 #import "ios/public/provider/chrome/browser/signin/chrome_identity.h" |
| 30 #import "ios/public/provider/chrome/browser/signin/chrome_identity_service.h" |
| 31 #include "ios/public/provider/chrome/browser/signin/signin_resources_provider.h" |
| 32 #import "ios/third_party/material_components_ios/src/components/Buttons/src/Mate
rialButtons.h" |
| 33 #import "ios/third_party/material_components_ios/src/components/Dialogs/src/Mate
rialDialogs.h" |
| 34 #import "ios/third_party/material_components_ios/src/components/Palettes/src/Mat
erialPalettes.h" |
| 35 #import "ios/third_party/material_components_ios/src/components/Typography/src/M
aterialTypography.h" |
| 36 #include "ui/base/l10n/l10n_util_mac.h" |
| 37 |
| 38 namespace { |
| 39 |
| 40 const int kMaxShownAccounts = 3; |
| 41 const CGFloat kAccountsExtraBottomInset = 16; |
| 42 const CGFloat kVerticalPadding = 24; |
| 43 const CGFloat kButtonVerticalPadding = 16; |
| 44 const CGFloat kHorizontalPadding = 24; |
| 45 const CGFloat kAccountsHorizontalPadding = 8; |
| 46 const CGFloat kButtonHorizontalPadding = 16; |
| 47 const CGFloat kBetweenButtonsPadding = 8; |
| 48 const CGFloat kMDCMinHorizontalPadding = 20; |
| 49 const CGFloat kDialogMaxWidth = 328; |
| 50 |
| 51 typedef NS_ENUM(NSInteger, SectionIdentifier) { |
| 52 SectionIdentifierAccounts = kSectionIdentifierEnumZero, |
| 53 }; |
| 54 |
| 55 typedef NS_ENUM(NSInteger, ItemType) { |
| 56 ItemTypeAccount = kItemTypeEnumZero, |
| 57 }; |
| 58 |
| 59 // Whether the Signed In Accounts view is currently being shown. |
| 60 BOOL gSignedInAccountsViewControllerIsShown = NO; |
| 61 |
| 62 } // namespace |
| 63 |
| 64 @interface SignedInAccountsCollectionViewController |
| 65 : CollectionViewController<ChromeIdentityServiceObserver> { |
| 66 ios::ChromeBrowserState* _browserState; // Weak. |
| 67 std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver; |
| 68 base::scoped_nsobject<ResizedAvatarCache> _avatarCache; |
| 69 |
| 70 // Enable lookup of item corresponding to a given identity GAIA ID string. |
| 71 base::scoped_nsobject<NSDictionary<NSString*, CollectionViewItem*>> |
| 72 _identityMap; |
| 73 } |
| 74 @end |
| 75 |
| 76 @implementation SignedInAccountsCollectionViewController |
| 77 |
| 78 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState { |
| 79 self = [super initWithStyle:CollectionViewControllerStyleDefault]; |
| 80 if (self) { |
| 81 _browserState = browserState; |
| 82 _avatarCache.reset([[ResizedAvatarCache alloc] init]); |
| 83 _identityServiceObserver.reset( |
| 84 new ChromeIdentityServiceObserverBridge(self)); |
| 85 [self loadModel]; |
| 86 } |
| 87 return self; |
| 88 } |
| 89 |
| 90 - (void)viewDidLoad { |
| 91 [super viewDidLoad]; |
| 92 |
| 93 self.styler.shouldHideSeparators = YES; |
| 94 self.collectionView.backgroundColor = [UIColor clearColor]; |
| 95 |
| 96 // Add an inset at the bottom so the user can see whether it is possible to |
| 97 // scroll to see additional accounts. |
| 98 UIEdgeInsets contentInset = self.collectionView.contentInset; |
| 99 contentInset.bottom += kAccountsExtraBottomInset; |
| 100 self.collectionView.contentInset = contentInset; |
| 101 } |
| 102 |
| 103 #pragma mark CollectionViewController |
| 104 |
| 105 - (void)loadModel { |
| 106 [super loadModel]; |
| 107 CollectionViewModel* model = self.collectionViewModel; |
| 108 NSMutableDictionary<NSString*, CollectionViewItem*>* mutableIdentityMap = |
| 109 [[[NSMutableDictionary alloc] init] autorelease]; |
| 110 |
| 111 [model addSectionWithIdentifier:SectionIdentifierAccounts]; |
| 112 ProfileOAuth2TokenService* oauth2_service = |
| 113 OAuth2TokenServiceFactory::GetForBrowserState(_browserState); |
| 114 AccountTrackerService* accountTracker = |
| 115 ios::AccountTrackerServiceFactory::GetForBrowserState(_browserState); |
| 116 for (const std::string& account_id : oauth2_service->GetAccounts()) { |
| 117 AccountInfo account = accountTracker->GetAccountInfo(account_id); |
| 118 ChromeIdentity* identity = ios::GetChromeBrowserProvider() |
| 119 ->GetChromeIdentityService() |
| 120 ->GetIdentityWithGaiaID(account.gaia); |
| 121 CollectionViewItem* item = [self accountItem:identity]; |
| 122 [model addItem:item toSectionWithIdentifier:SectionIdentifierAccounts]; |
| 123 [mutableIdentityMap setObject:item forKey:identity.gaiaID]; |
| 124 } |
| 125 _identityMap.reset([mutableIdentityMap retain]); |
| 126 } |
| 127 |
| 128 #pragma mark Model objects |
| 129 |
| 130 - (CollectionViewItem*)accountItem:(ChromeIdentity*)identity { |
| 131 CollectionViewAccountItem* item = [[[CollectionViewAccountItem alloc] |
| 132 initWithType:ItemTypeAccount] autorelease]; |
| 133 [self updateAccountItem:item withIdentity:identity]; |
| 134 return item; |
| 135 } |
| 136 |
| 137 - (void)updateAccountItem:(CollectionViewAccountItem*)item |
| 138 withIdentity:(ChromeIdentity*)identity { |
| 139 item.image = [_avatarCache resizedAvatarForIdentity:identity]; |
| 140 item.text = [identity userFullName]; |
| 141 item.detailText = [identity userEmail]; |
| 142 item.chromeIdentity = identity; |
| 143 } |
| 144 |
| 145 #pragma mark MDCCollectionViewStylingDelegate |
| 146 |
| 147 - (BOOL)collectionView:(UICollectionView*)collectionView |
| 148 shouldHideItemBackgroundAtIndexPath:(NSIndexPath*)indexPath { |
| 149 return YES; |
| 150 } |
| 151 |
| 152 - (CGFloat)collectionView:(UICollectionView*)collectionView |
| 153 cellHeightAtIndexPath:(NSIndexPath*)indexPath { |
| 154 return MDCCellDefaultOneLineWithAvatarHeight; |
| 155 } |
| 156 |
| 157 - (BOOL)collectionView:(UICollectionView*)collectionView |
| 158 hidesInkViewAtIndexPath:(NSIndexPath*)indexPath { |
| 159 return YES; |
| 160 } |
| 161 |
| 162 #pragma mark ChromeIdentityServiceObserver |
| 163 |
| 164 - (void)onProfileUpdate:(ChromeIdentity*)identity { |
| 165 CollectionViewAccountItem* item = |
| 166 base::mac::ObjCCastStrict<CollectionViewAccountItem>( |
| 167 [_identityMap objectForKey:identity.gaiaID]); |
| 168 [self updateAccountItem:item withIdentity:identity]; |
| 169 NSIndexPath* indexPath = |
| 170 [self.collectionViewModel indexPathForItem:item |
| 171 inSectionWithIdentifier:SectionIdentifierAccounts]; |
| 172 [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]]; |
| 173 } |
| 174 |
| 175 - (void)onChromeIdentityServiceWillBeDestroyed { |
| 176 _identityServiceObserver.reset(); |
| 177 } |
| 178 |
| 179 @end |
| 180 |
| 181 @interface SignedInAccountsViewController ()< |
| 182 OAuth2TokenServiceObserverBridgeDelegate> { |
| 183 ios::ChromeBrowserState* _browserState; // Weak. |
| 184 std::unique_ptr<OAuth2TokenServiceObserverBridge> _tokenServiceObserver; |
| 185 base::scoped_nsobject<MDCDialogTransitionController> _transitionController; |
| 186 |
| 187 base::scoped_nsobject<UILabel> _titleLabel; |
| 188 base::scoped_nsobject<SignedInAccountsCollectionViewController> |
| 189 _accountsCollection; |
| 190 base::scoped_nsobject<UILabel> _infoLabel; |
| 191 base::scoped_nsobject<MDCButton> _primaryButton; |
| 192 base::scoped_nsobject<MDCButton> _secondaryButton; |
| 193 } |
| 194 @end |
| 195 |
| 196 @implementation SignedInAccountsViewController |
| 197 |
| 198 + (BOOL)shouldBePresentedForBrowserState: |
| 199 (ios::ChromeBrowserState*)browserState { |
| 200 if (!browserState || browserState->IsOffTheRecord()) { |
| 201 return NO; |
| 202 } |
| 203 AuthenticationService* authService = |
| 204 AuthenticationServiceFactory::GetForBrowserState(browserState); |
| 205 return !gSignedInAccountsViewControllerIsShown && |
| 206 authService->IsAuthenticated() && authService->HaveAccountsChanged(); |
| 207 } |
| 208 |
| 209 #pragma mark Initialization |
| 210 |
| 211 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState { |
| 212 self = [super initWithNibName:nil bundle:nil]; |
| 213 if (self) { |
| 214 _browserState = browserState; |
| 215 _tokenServiceObserver.reset(new OAuth2TokenServiceObserverBridge( |
| 216 OAuth2TokenServiceFactory::GetForBrowserState(_browserState), self)); |
| 217 _transitionController.reset([[MDCDialogTransitionController alloc] init]); |
| 218 self.modalPresentationStyle = UIModalPresentationCustom; |
| 219 self.transitioningDelegate = _transitionController; |
| 220 } |
| 221 return self; |
| 222 } |
| 223 |
| 224 - (void)dismiss { |
| 225 [self.presentingViewController dismissViewControllerAnimated:YES |
| 226 completion:nil]; |
| 227 } |
| 228 |
| 229 - (void)dealloc { |
| 230 [_primaryButton removeTarget:self |
| 231 action:@selector(onPrimaryButtonPressed:) |
| 232 forControlEvents:UIControlEventTouchDown]; |
| 233 [_secondaryButton removeTarget:self |
| 234 action:@selector(onSecondaryButtonPressed:) |
| 235 forControlEvents:UIControlEventTouchDown]; |
| 236 [super dealloc]; |
| 237 } |
| 238 |
| 239 #pragma mark UIViewController |
| 240 |
| 241 - (CGSize)preferredContentSize { |
| 242 CGFloat width = MIN(kDialogMaxWidth, |
| 243 self.presentingViewController.view.bounds.size.width - |
| 244 2 * kMDCMinHorizontalPadding); |
| 245 OAuth2TokenService* token_service = |
| 246 OAuth2TokenServiceFactory::GetForBrowserState(_browserState); |
| 247 int shownAccounts = |
| 248 MIN(kMaxShownAccounts, token_service->GetAccounts().size()); |
| 249 CGSize maxSize = CGSizeMake(width - 2 * kHorizontalPadding, CGFLOAT_MAX); |
| 250 CGSize buttonSize = [_primaryButton sizeThatFits:maxSize]; |
| 251 CGSize infoSize = [_infoLabel sizeThatFits:maxSize]; |
| 252 CGSize titleSize = [_titleLabel sizeThatFits:maxSize]; |
| 253 CGFloat height = kVerticalPadding + titleSize.height + kVerticalPadding + |
| 254 shownAccounts * MDCCellDefaultOneLineWithAvatarHeight + |
| 255 kVerticalPadding + infoSize.height + kVerticalPadding + |
| 256 buttonSize.height + kButtonVerticalPadding; |
| 257 return CGSizeMake(width, height); |
| 258 } |
| 259 |
| 260 - (void)viewDidLoad { |
| 261 [super viewDidLoad]; |
| 262 |
| 263 self.view.backgroundColor = [UIColor whiteColor]; |
| 264 |
| 265 _titleLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
| 266 _titleLabel.get().text = |
| 267 l10n_util::GetNSString(IDS_IOS_SIGNED_IN_ACCOUNTS_VIEW_TITLE); |
| 268 _titleLabel.get().textColor = [[MDCPalette greyPalette] tint900]; |
| 269 _titleLabel.get().font = [MDCTypography headlineFont]; |
| 270 _titleLabel.get().translatesAutoresizingMaskIntoConstraints = NO; |
| 271 [self.view addSubview:_titleLabel]; |
| 272 |
| 273 _accountsCollection.reset([[SignedInAccountsCollectionViewController alloc] |
| 274 initWithBrowserState:_browserState]); |
| 275 _accountsCollection.get().view.translatesAutoresizingMaskIntoConstraints = NO; |
| 276 [self addChildViewController:_accountsCollection]; |
| 277 [self.view addSubview:_accountsCollection.get().view]; |
| 278 [_accountsCollection didMoveToParentViewController:self]; |
| 279 |
| 280 _infoLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
| 281 _infoLabel.get().text = |
| 282 l10n_util::GetNSString(IDS_IOS_SIGNED_IN_ACCOUNTS_VIEW_INFO); |
| 283 _infoLabel.get().numberOfLines = 0; |
| 284 _infoLabel.get().textColor = [[MDCPalette greyPalette] tint700]; |
| 285 _infoLabel.get().font = [MDCTypography body1Font]; |
| 286 _infoLabel.get().translatesAutoresizingMaskIntoConstraints = NO; |
| 287 [self.view addSubview:_infoLabel]; |
| 288 |
| 289 _primaryButton.reset([[MDCFlatButton alloc] init]); |
| 290 [_primaryButton addTarget:self |
| 291 action:@selector(onPrimaryButtonPressed:) |
| 292 forControlEvents:UIControlEventTouchUpInside]; |
| 293 [_primaryButton |
| 294 setTitle:l10n_util::GetNSString(IDS_IOS_SIGNED_IN_ACCOUNTS_VIEW_OK_BUTTON) |
| 295 forState:UIControlStateNormal]; |
| 296 [_primaryButton setBackgroundColor:[[MDCPalette cr_bluePalette] tint500] |
| 297 forState:UIControlStateNormal]; |
| 298 _primaryButton.get().customTitleColor = [UIColor whiteColor]; |
| 299 _primaryButton.get().underlyingColorHint = [UIColor blackColor]; |
| 300 _primaryButton.get().inkColor = [UIColor colorWithWhite:1 alpha:0.2f]; |
| 301 _primaryButton.get().translatesAutoresizingMaskIntoConstraints = NO; |
| 302 [self.view addSubview:_primaryButton]; |
| 303 |
| 304 _secondaryButton.reset([[MDCFlatButton alloc] init]); |
| 305 [_secondaryButton addTarget:self |
| 306 action:@selector(onSecondaryButtonPressed:) |
| 307 forControlEvents:UIControlEventTouchUpInside]; |
| 308 [_secondaryButton |
| 309 setTitle:l10n_util::GetNSString( |
| 310 IDS_IOS_SIGNED_IN_ACCOUNTS_VIEW_SETTINGS_BUTTON) |
| 311 forState:UIControlStateNormal]; |
| 312 [_secondaryButton setBackgroundColor:[UIColor whiteColor] |
| 313 forState:UIControlStateNormal]; |
| 314 _secondaryButton.get().customTitleColor = |
| 315 [[MDCPalette cr_bluePalette] tint500]; |
| 316 _secondaryButton.get().underlyingColorHint = [UIColor whiteColor]; |
| 317 _secondaryButton.get().inkColor = [UIColor colorWithWhite:0 alpha:0.06f]; |
| 318 _secondaryButton.get().translatesAutoresizingMaskIntoConstraints = NO; |
| 319 [self.view addSubview:_secondaryButton]; |
| 320 |
| 321 NSDictionary* views = @{ |
| 322 @"title" : _titleLabel, |
| 323 @"accounts" : _accountsCollection.get().view, |
| 324 @"info" : _infoLabel, |
| 325 @"primaryButton" : _primaryButton, |
| 326 @"secondaryButton" : _secondaryButton, |
| 327 }; |
| 328 NSDictionary* metrics = @{ |
| 329 @"verticalPadding" : @(kVerticalPadding), |
| 330 @"accountsVerticalPadding" : |
| 331 @(kVerticalPadding - kAccountsExtraBottomInset), |
| 332 @"buttonVerticalPadding" : @(kButtonVerticalPadding), |
| 333 @"horizontalPadding" : @(kHorizontalPadding), |
| 334 @"accountsHorizontalPadding" : @(kAccountsHorizontalPadding), |
| 335 @"buttonHorizontalPadding" : @(kButtonHorizontalPadding), |
| 336 @"betweenButtonsPadding" : @(kBetweenButtonsPadding), |
| 337 }; |
| 338 NSArray* constraints = @[ |
| 339 @"V:|-(verticalPadding)-[title]-(verticalPadding)-[accounts]", |
| 340 @"V:[accounts]-(accountsVerticalPadding)-[info]", |
| 341 @"V:[info]-(verticalPadding)-[primaryButton]-(buttonVerticalPadding)-|", |
| 342 @"V:[info]-(verticalPadding)-[secondaryButton]-(buttonVerticalPadding)-|", |
| 343 @"H:|-(horizontalPadding)-[title]-(horizontalPadding)-|", |
| 344 @"H:|-(accountsHorizontalPadding)-[accounts]-(accountsHorizontalPadding)-|", |
| 345 @"H:|-(horizontalPadding)-[info]-(horizontalPadding)-|", |
| 346 @"H:[secondaryButton]-(betweenButtonsPadding)-[primaryButton]", |
| 347 @"H:[primaryButton]-(buttonHorizontalPadding)-|", |
| 348 ]; |
| 349 |
| 350 ApplyVisualConstraintsWithMetrics(constraints, views, metrics); |
| 351 } |
| 352 |
| 353 - (void)viewWillAppear:(BOOL)animated { |
| 354 [super viewWillAppear:animated]; |
| 355 if ([self isBeingPresented] || [self isMovingToParentViewController]) { |
| 356 gSignedInAccountsViewControllerIsShown = YES; |
| 357 } |
| 358 } |
| 359 |
| 360 - (void)viewWillDisappear:(BOOL)animated { |
| 361 [super viewWillDisappear:animated]; |
| 362 if ([self isBeingDismissed] || [self isMovingFromParentViewController]) { |
| 363 gSignedInAccountsViewControllerIsShown = NO; |
| 364 } |
| 365 } |
| 366 |
| 367 #pragma mark Events |
| 368 |
| 369 - (void)onPrimaryButtonPressed:(id)sender { |
| 370 [self dismiss]; |
| 371 } |
| 372 |
| 373 - (void)onSecondaryButtonPressed:(id)sender { |
| 374 [self dismiss]; |
| 375 base::scoped_nsobject<GenericChromeCommand> showAccountsSettingsCommand( |
| 376 [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_ACCOUNTS_SETTINGS]); |
| 377 [self chromeExecuteCommand:showAccountsSettingsCommand]; |
| 378 } |
| 379 |
| 380 #pragma mark OAuth2TokenServiceObserverBridgeDelegate |
| 381 |
| 382 - (void)onEndBatchChanges { |
| 383 ProfileOAuth2TokenService* tokenService = |
| 384 OAuth2TokenServiceFactory::GetForBrowserState(_browserState); |
| 385 if (tokenService->GetAccounts().empty()) { |
| 386 [self dismiss]; |
| 387 return; |
| 388 } |
| 389 [_accountsCollection loadModel]; |
| 390 [_accountsCollection.get().collectionView reloadData]; |
| 391 } |
| 392 |
| 393 @end |
OLD | NEW |