OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 #include "ios/chrome/browser/ui/authentication/chrome_signin_view_controller.h" |
| 6 |
| 7 #include <stdint.h> |
| 8 #include <cmath> |
| 9 #include <memory> |
| 10 |
| 11 #import <CoreGraphics/CoreGraphics.h> |
| 12 #import <QuartzCore/QuartzCore.h> |
| 13 |
| 14 #import "base/ios/block_types.h" |
| 15 #import "base/ios/ios_util.h" |
| 16 #import "base/ios/weak_nsobject.h" |
| 17 #import "base/mac/bind_objc_block.h" |
| 18 #import "base/mac/scoped_nsobject.h" |
| 19 #include "base/metrics/user_metrics.h" |
| 20 #import "base/strings/sys_string_conversions.h" |
| 21 #include "base/timer/elapsed_timer.h" |
| 22 #include "base/timer/timer.h" |
| 23 #include "components/signin/core/browser/signin_metrics.h" |
| 24 #include "components/strings/grit/components_strings.h" |
| 25 #import "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 26 #include "ios/chrome/browser/chrome_url_constants.h" |
| 27 #import "ios/chrome/browser/signin/authentication_service.h" |
| 28 #import "ios/chrome/browser/signin/authentication_service_factory.h" |
| 29 #import "ios/chrome/browser/signin/chrome_identity_service_observer_bridge.h" |
| 30 #include "ios/chrome/browser/signin/signin_util.h" |
| 31 #import "ios/chrome/browser/sync/sync_setup_service.h" |
| 32 #import "ios/chrome/browser/sync/sync_setup_service_factory.h" |
| 33 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h" |
| 34 #import "ios/chrome/browser/ui/authentication/authentication_flow.h" |
| 35 #import "ios/chrome/browser/ui/authentication/authentication_ui_util.h" |
| 36 #include "ios/chrome/browser/ui/authentication/signin_account_selector_view_cont
roller.h" |
| 37 #include "ios/chrome/browser/ui/authentication/signin_confirmation_view_controll
er.h" |
| 38 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
| 39 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
| 40 #import "ios/chrome/browser/ui/commands/generic_chrome_command.h" |
| 41 #include "ios/chrome/browser/ui/commands/ios_command_ids.h" |
| 42 #import "ios/chrome/browser/ui/rtl_geometry.h" |
| 43 #import "ios/chrome/browser/ui/ui_util.h" |
| 44 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 45 #import "ios/chrome/browser/ui/util/label_link_controller.h" |
| 46 #include "ios/chrome/common/string_util.h" |
| 47 #include "ios/chrome/grit/ios_chromium_strings.h" |
| 48 #include "ios/chrome/grit/ios_strings.h" |
| 49 #import "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 50 #import "ios/public/provider/chrome/browser/signin/chrome_identity.h" |
| 51 #import "ios/public/provider/chrome/browser/signin/chrome_identity_interaction_m
anager.h" |
| 52 #import "ios/public/provider/chrome/browser/signin/chrome_identity_service.h" |
| 53 #import "ios/third_party/material_components_ios/src/components/ActivityIndicato
r/src/MaterialActivityIndicator.h" |
| 54 #import "ios/third_party/material_components_ios/src/components/Buttons/src/Mate
rialButtons.h" |
| 55 #import "ios/third_party/material_components_ios/src/components/Palettes/src/Mat
erialPalettes.h" |
| 56 #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoF
ontLoader.h" |
| 57 #import "ui/base/l10n/l10n_util.h" |
| 58 |
| 59 namespace { |
| 60 |
| 61 // Default animation duration. |
| 62 const CGFloat kAnimationDuration = 0.5f; |
| 63 |
| 64 enum LayoutType { |
| 65 LAYOUT_REGULAR, |
| 66 LAYOUT_COMPACT, |
| 67 }; |
| 68 |
| 69 // Alpha threshold upon which a view is considered hidden. |
| 70 const CGFloat kHiddenAlphaThreshold = 0.1; |
| 71 |
| 72 // Minimum duration of the pending state in milliseconds. |
| 73 const int64_t kMinimunPendingStateDurationMs = 300; |
| 74 |
| 75 // Internal padding between the title and image in the "More" button. |
| 76 const CGFloat kMoreButtonPadding = 5.0f; |
| 77 |
| 78 struct AuthenticationViewConstants { |
| 79 CGFloat PrimaryFontSize; |
| 80 CGFloat SecondaryFontSize; |
| 81 CGFloat GradientHeight; |
| 82 CGFloat ButtonHeight; |
| 83 CGFloat ButtonHorizontalPadding; |
| 84 CGFloat ButtonVerticalPadding; |
| 85 }; |
| 86 |
| 87 const AuthenticationViewConstants kCompactConstants = { |
| 88 24, // PrimaryFontSize |
| 89 14, // SecondaryFontSize |
| 90 40, // GradientHeight |
| 91 36, // ButtonHeight |
| 92 16, // ButtonHorizontalPadding |
| 93 16, // ButtonVerticalPadding |
| 94 }; |
| 95 |
| 96 const AuthenticationViewConstants kRegularConstants = { |
| 97 1.5 * kCompactConstants.PrimaryFontSize, |
| 98 1.5 * kCompactConstants.SecondaryFontSize, |
| 99 kCompactConstants.GradientHeight, |
| 100 1.5 * kCompactConstants.ButtonHeight, |
| 101 1.5 * kCompactConstants.ButtonHorizontalPadding, |
| 102 1.5 * kCompactConstants.ButtonVerticalPadding, |
| 103 }; |
| 104 |
| 105 enum AuthenticationState { |
| 106 NULL_STATE, |
| 107 IDENTITY_PICKER_STATE, |
| 108 SIGNIN_PENDING_STATE, |
| 109 IDENTITY_SELECTED_STATE, |
| 110 DONE_STATE, |
| 111 }; |
| 112 |
| 113 // Fades in |button| on screen if not already visible. |
| 114 void ShowButton(UIButton* button) { |
| 115 if (button.alpha > kHiddenAlphaThreshold) |
| 116 return; |
| 117 button.alpha = 1.0; |
| 118 } |
| 119 |
| 120 // Fades out |button| on screen if not already hidden. |
| 121 void HideButton(UIButton* button) { |
| 122 if (button.alpha < kHiddenAlphaThreshold) |
| 123 return; |
| 124 button.alpha = 0.0; |
| 125 } |
| 126 |
| 127 } // namespace |
| 128 |
| 129 @interface ChromeSigninViewController ()< |
| 130 ChromeIdentityInteractionManagerDelegate, |
| 131 ChromeIdentityServiceObserver, |
| 132 SigninAccountSelectorViewControllerDelegate, |
| 133 SigninConfirmationViewControllerDelegate, |
| 134 MDCActivityIndicatorDelegate> |
| 135 @property(nonatomic, retain) ChromeIdentity* selectedIdentity; |
| 136 |
| 137 @end |
| 138 |
| 139 @implementation ChromeSigninViewController { |
| 140 ios::ChromeBrowserState* _browserState; // weak |
| 141 base::WeakNSProtocol<id<ChromeSigninViewControllerDelegate>> _delegate; |
| 142 std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver; |
| 143 base::scoped_nsobject<ChromeIdentity> _selectedIdentity; |
| 144 |
| 145 // Authentication |
| 146 base::scoped_nsobject<AlertCoordinator> _alertCoordinator; |
| 147 base::scoped_nsobject<AuthenticationFlow> _authenticationFlow; |
| 148 BOOL _addedAccount; |
| 149 BOOL _autoSignIn; |
| 150 BOOL _didSignIn; |
| 151 BOOL _didAcceptSignIn; |
| 152 BOOL _didFinishSignIn; |
| 153 BOOL _isPresentedOnSettings; |
| 154 signin_metrics::AccessPoint _signInAccessPoint; |
| 155 base::scoped_nsobject<ChromeIdentityInteractionManager> _interactionManager; |
| 156 |
| 157 // Basic state. |
| 158 AuthenticationState _currentState; |
| 159 BOOL _ongoingStateChange; |
| 160 base::scoped_nsobject<MDCActivityIndicator> _activityIndicator; |
| 161 base::scoped_nsobject<MDCButton> _primaryButton; |
| 162 base::scoped_nsobject<MDCButton> _secondaryButton; |
| 163 base::scoped_nsobject<UIView> _gradientView; |
| 164 base::scoped_nsobject<CAGradientLayer> _gradientLayer; |
| 165 |
| 166 // Identity picker state. |
| 167 base::scoped_nsobject<SigninAccountSelectorViewController> _accountSelectorVC; |
| 168 |
| 169 // Signin pending state. |
| 170 AuthenticationState _activityIndicatorNextState; |
| 171 std::unique_ptr<base::ElapsedTimer> _pendingStateTimer; |
| 172 std::unique_ptr<base::Timer> _leavingPendingStateTimer; |
| 173 |
| 174 // Identity selected state. |
| 175 base::scoped_nsobject<SigninConfirmationViewController> _confirmationVC; |
| 176 BOOL _hasConfirmationScreenReachedBottom; |
| 177 } |
| 178 |
| 179 @synthesize shouldClearData = _shouldClearData; |
| 180 |
| 181 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState |
| 182 isPresentedOnSettings:(BOOL)isPresentedOnSettings |
| 183 signInAccessPoint:(signin_metrics::AccessPoint)accessPoint |
| 184 signInIdentity:(ChromeIdentity*)identity { |
| 185 self = [super init]; |
| 186 if (self) { |
| 187 _browserState = browserState; |
| 188 _isPresentedOnSettings = isPresentedOnSettings; |
| 189 _signInAccessPoint = accessPoint; |
| 190 |
| 191 if (identity) { |
| 192 _autoSignIn = YES; |
| 193 [self setSelectedIdentity:identity]; |
| 194 } |
| 195 _identityServiceObserver.reset( |
| 196 new ChromeIdentityServiceObserverBridge(self)); |
| 197 _currentState = NULL_STATE; |
| 198 } |
| 199 return self; |
| 200 } |
| 201 |
| 202 - (void)dealloc { |
| 203 // The call to -[UIControl addTarget:action:forControlEvents:] is made just |
| 204 // after the creation of those objects, so if the objects are not nil, then |
| 205 // it is safe to call -[UIControl removeTarget:action:forControlEvents:]. |
| 206 // If they are nil, then the call does nothing. |
| 207 [_primaryButton removeTarget:self |
| 208 action:@selector(onPrimaryButtonPressed:) |
| 209 forControlEvents:UIControlEventTouchDown]; |
| 210 [_secondaryButton removeTarget:self |
| 211 action:@selector(onSecondaryButtonPressed:) |
| 212 forControlEvents:UIControlEventTouchDown]; |
| 213 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 214 [super dealloc]; |
| 215 } |
| 216 |
| 217 - (void)cancel { |
| 218 if (_alertCoordinator) { |
| 219 DCHECK(!_authenticationFlow && !_interactionManager); |
| 220 [_alertCoordinator executeCancelHandler]; |
| 221 [_alertCoordinator stop]; |
| 222 } |
| 223 if (_interactionManager) { |
| 224 DCHECK(!_alertCoordinator && !_authenticationFlow); |
| 225 [_interactionManager cancelAndDismissAnimated:NO]; |
| 226 } |
| 227 if (_authenticationFlow) { |
| 228 DCHECK(!_alertCoordinator && !_interactionManager); |
| 229 [_authenticationFlow cancelAndDismiss]; |
| 230 } |
| 231 if (!_didAcceptSignIn && _didSignIn) { |
| 232 AuthenticationServiceFactory::GetForBrowserState(_browserState) |
| 233 ->SignOut(signin_metrics::ABORT_SIGNIN, nil); |
| 234 _didSignIn = NO; |
| 235 } |
| 236 if (!_didFinishSignIn) { |
| 237 _didFinishSignIn = YES; |
| 238 [_delegate didFailSignIn:self]; |
| 239 } |
| 240 } |
| 241 |
| 242 - (void)acceptSignInAndExecuteCommand:(GenericChromeCommand*)command { |
| 243 signin_metrics::LogSigninAccessPointCompleted(_signInAccessPoint); |
| 244 _didAcceptSignIn = YES; |
| 245 if (!_didFinishSignIn) { |
| 246 _didFinishSignIn = YES; |
| 247 [_delegate didAcceptSignIn:self executeCommand:command]; |
| 248 } |
| 249 } |
| 250 |
| 251 - (void)acceptSignInAndCommitSyncChanges { |
| 252 DCHECK(_didSignIn); |
| 253 SyncSetupServiceFactory::GetForBrowserState(_browserState)->CommitChanges(); |
| 254 [self acceptSignInAndExecuteCommand:nil]; |
| 255 } |
| 256 |
| 257 - (void)setPrimaryButtonStyling:(MDCButton*)button { |
| 258 [button setBackgroundColor:[[MDCPalette cr_bluePalette] tint500] |
| 259 forState:UIControlStateNormal]; |
| 260 [button setCustomTitleColor:[UIColor whiteColor]]; |
| 261 [button setUnderlyingColorHint:[UIColor blackColor]]; |
| 262 [button setInkColor:[UIColor colorWithWhite:1 alpha:0.2f]]; |
| 263 } |
| 264 |
| 265 - (void)setSecondaryButtonStyling:(MDCButton*)button { |
| 266 [button setBackgroundColor:self.backgroundColor |
| 267 forState:UIControlStateNormal]; |
| 268 [button setCustomTitleColor:[[MDCPalette cr_bluePalette] tint500]]; |
| 269 [button setUnderlyingColorHint:[UIColor whiteColor]]; |
| 270 [button setInkColor:[UIColor colorWithWhite:0 alpha:0.06f]]; |
| 271 } |
| 272 |
| 273 #pragma mark - Accessibility |
| 274 |
| 275 - (BOOL)accessibilityPerformEscape { |
| 276 // Simulate a press on the secondary button. |
| 277 [self onSecondaryButtonPressed:self]; |
| 278 return YES; |
| 279 } |
| 280 |
| 281 #pragma mark - Properties |
| 282 |
| 283 - (ios::ChromeBrowserState*)browserState { |
| 284 return _browserState; |
| 285 } |
| 286 |
| 287 - (id<ChromeSigninViewControllerDelegate>)delegate { |
| 288 return _delegate; |
| 289 } |
| 290 |
| 291 - (void)setDelegate:(id<ChromeSigninViewControllerDelegate>)delegate { |
| 292 _delegate.reset(delegate); |
| 293 } |
| 294 |
| 295 - (UIColor*)backgroundColor { |
| 296 return [[MDCPalette greyPalette] tint50]; |
| 297 } |
| 298 |
| 299 - (NSString*)identityPickerTitle { |
| 300 return l10n_util::GetNSString(IDS_IOS_ACCOUNT_CONSISTENCY_SETUP_TITLE); |
| 301 } |
| 302 |
| 303 - (NSString*)acceptSigninButtonTitle { |
| 304 return l10n_util::GetNSString( |
| 305 IDS_IOS_ACCOUNT_CONSISTENCY_CONFIRMATION_OK_BUTTON); |
| 306 } |
| 307 |
| 308 - (NSString*)skipSigninButtonTitle { |
| 309 return l10n_util::GetNSString(IDS_IOS_ACCOUNT_CONSISTENCY_SETUP_SKIP_BUTTON); |
| 310 } |
| 311 |
| 312 - (UIButton*)primaryButton { |
| 313 return _primaryButton; |
| 314 } |
| 315 |
| 316 - (UIButton*)secondaryButton { |
| 317 return _secondaryButton; |
| 318 } |
| 319 |
| 320 - (void)setSelectedIdentity:(ChromeIdentity*)identity { |
| 321 DCHECK(identity || (IDENTITY_PICKER_STATE == _currentState)); |
| 322 _selectedIdentity.reset([identity retain]); |
| 323 } |
| 324 |
| 325 - (ChromeIdentity*)selectedIdentity { |
| 326 return _selectedIdentity; |
| 327 } |
| 328 |
| 329 #pragma mark - Authentication |
| 330 |
| 331 - (void)handleAuthenticationError:(NSError*)error { |
| 332 // Filter out cancel and errors handled internally by ChromeIdentity. |
| 333 if (!ShouldHandleSigninError(error)) { |
| 334 return; |
| 335 } |
| 336 _alertCoordinator.reset( |
| 337 [ios_internal::ErrorCoordinator(error, nil, self) retain]); |
| 338 [_alertCoordinator start]; |
| 339 } |
| 340 |
| 341 - (void)signIntoIdentity:(ChromeIdentity*)identity { |
| 342 [_delegate willStartSignIn:self]; |
| 343 DCHECK(!_authenticationFlow); |
| 344 _authenticationFlow.reset([[AuthenticationFlow alloc] |
| 345 initWithBrowserState:_browserState |
| 346 identity:identity |
| 347 shouldClearData:_shouldClearData |
| 348 postSignInAction:POST_SIGNIN_ACTION_NONE |
| 349 presentingViewController:self]); |
| 350 base::WeakNSObject<ChromeSigninViewController> weakSelf(self); |
| 351 [_authenticationFlow startSignInWithCompletion:^(BOOL success) { |
| 352 [weakSelf onAccountSigninCompletion:success]; |
| 353 }]; |
| 354 } |
| 355 |
| 356 - (void)openAuthenticationDialogAddIdentity { |
| 357 DCHECK(!_interactionManager); |
| 358 _interactionManager = |
| 359 ios::GetChromeBrowserProvider() |
| 360 ->GetChromeIdentityService() |
| 361 ->NewChromeIdentityInteractionManager(_browserState, self); |
| 362 base::WeakNSObject<ChromeSigninViewController> weakSelf(self); |
| 363 SigninCompletionCallback completion = |
| 364 ^(ChromeIdentity* identity, NSError* error) { |
| 365 base::scoped_nsobject<ChromeSigninViewController> strongSelf( |
| 366 [weakSelf retain]); |
| 367 if (!strongSelf || !strongSelf.get()->_interactionManager) |
| 368 return; |
| 369 // The ChromeIdentityInteractionManager is not used anymore at this |
| 370 // point. |
| 371 strongSelf.get()->_interactionManager.reset(); |
| 372 |
| 373 if (error) { |
| 374 [strongSelf handleAuthenticationError:error]; |
| 375 return; |
| 376 } |
| 377 strongSelf.get()->_addedAccount = YES; |
| 378 [strongSelf onIdentityListChanged]; |
| 379 [strongSelf setSelectedIdentity:identity]; |
| 380 [strongSelf changeToState:SIGNIN_PENDING_STATE]; |
| 381 }; |
| 382 [_delegate willStartAddAccount:self]; |
| 383 [_interactionManager addAccountWithCompletion:completion]; |
| 384 } |
| 385 |
| 386 - (void)onAccountSigninCompletion:(BOOL)success { |
| 387 _authenticationFlow.reset(); |
| 388 if (success) { |
| 389 DCHECK(!_didSignIn); |
| 390 _didSignIn = YES; |
| 391 [_delegate didSignIn:self]; |
| 392 [self changeToState:IDENTITY_SELECTED_STATE]; |
| 393 } else { |
| 394 [self changeToState:IDENTITY_PICKER_STATE]; |
| 395 } |
| 396 } |
| 397 |
| 398 - (void)undoSignIn { |
| 399 if (_didSignIn) { |
| 400 AuthenticationServiceFactory::GetForBrowserState(_browserState) |
| 401 ->SignOut(signin_metrics::ABORT_SIGNIN, nil); |
| 402 [_delegate didUndoSignIn:self identity:self.selectedIdentity]; |
| 403 _didSignIn = NO; |
| 404 } |
| 405 if (_addedAccount) { |
| 406 // This is best effort. If the operation fails, the account will be left on |
| 407 // the device. The user will not be warned either as this call is |
| 408 // asynchronous (but undo is not), the application might be in an unknown |
| 409 // state when the forget identity operation finishes. |
| 410 ios::GetChromeBrowserProvider()->GetChromeIdentityService()->ForgetIdentity( |
| 411 self.selectedIdentity, nil); |
| 412 } |
| 413 _addedAccount = NO; |
| 414 } |
| 415 |
| 416 #pragma mark - State machine |
| 417 |
| 418 - (void)enterState:(AuthenticationState)state { |
| 419 _ongoingStateChange = NO; |
| 420 if (_didFinishSignIn) { |
| 421 // Stop the state machine when the sign-in is done. |
| 422 _currentState = DONE_STATE; |
| 423 return; |
| 424 } |
| 425 _currentState = state; |
| 426 switch (state) { |
| 427 case NULL_STATE: |
| 428 NOTREACHED(); |
| 429 break; |
| 430 case IDENTITY_PICKER_STATE: |
| 431 [self enterIdentityPickerState]; |
| 432 break; |
| 433 case SIGNIN_PENDING_STATE: |
| 434 [self enterSigninPendingState]; |
| 435 break; |
| 436 case IDENTITY_SELECTED_STATE: |
| 437 [self enterIdentitySelectedState]; |
| 438 break; |
| 439 case DONE_STATE: |
| 440 break; |
| 441 } |
| 442 } |
| 443 |
| 444 - (void)changeToState:(AuthenticationState)nextState { |
| 445 if (_currentState == nextState) |
| 446 return; |
| 447 _ongoingStateChange = YES; |
| 448 switch (_currentState) { |
| 449 case NULL_STATE: |
| 450 DCHECK_NE(IDENTITY_SELECTED_STATE, nextState); |
| 451 [self enterState:nextState]; |
| 452 return; |
| 453 case IDENTITY_PICKER_STATE: |
| 454 DCHECK_EQ(SIGNIN_PENDING_STATE, nextState); |
| 455 [self leaveIdentityPickerState:nextState]; |
| 456 return; |
| 457 case SIGNIN_PENDING_STATE: |
| 458 [self leaveSigninPendingState:nextState]; |
| 459 return; |
| 460 case IDENTITY_SELECTED_STATE: |
| 461 DCHECK_EQ(IDENTITY_PICKER_STATE, nextState); |
| 462 [self leaveIdentitySelectedState:nextState]; |
| 463 return; |
| 464 case DONE_STATE: |
| 465 // Ignored |
| 466 return; |
| 467 } |
| 468 NOTREACHED(); |
| 469 } |
| 470 |
| 471 #pragma mark - IdentityPickerState |
| 472 |
| 473 - (void)updatePrimaryButtonTitle { |
| 474 bool hasIdentities = ios::GetChromeBrowserProvider() |
| 475 ->GetChromeIdentityService() |
| 476 ->HasIdentities(); |
| 477 NSString* primaryButtonTitle = |
| 478 hasIdentities |
| 479 ? l10n_util::GetNSString( |
| 480 IDS_IOS_ACCOUNT_CONSISTENCY_SETUP_SIGNIN_BUTTON) |
| 481 : l10n_util::GetNSString( |
| 482 IDS_IOS_ACCOUNT_CONSISTENCY_SETUP_SIGNIN_NO_ACCOUNT_BUTTON); |
| 483 [_primaryButton setTitle:primaryButtonTitle forState:UIControlStateNormal]; |
| 484 [_primaryButton setImage:nil forState:UIControlStateNormal]; |
| 485 [self.view setNeedsLayout]; |
| 486 } |
| 487 |
| 488 - (void)enterIdentityPickerState { |
| 489 // Reset the selected identity. |
| 490 [self setSelectedIdentity:nil]; |
| 491 |
| 492 // Add the account selector view controller. |
| 493 _accountSelectorVC.reset([[SigninAccountSelectorViewController alloc] init]); |
| 494 _accountSelectorVC.get().delegate = self; |
| 495 [_accountSelectorVC willMoveToParentViewController:self]; |
| 496 [self addChildViewController:_accountSelectorVC]; |
| 497 _accountSelectorVC.get().view.frame = self.view.bounds; |
| 498 [self.view insertSubview:_accountSelectorVC.get().view |
| 499 belowSubview:_primaryButton]; |
| 500 [_accountSelectorVC didMoveToParentViewController:self]; |
| 501 |
| 502 // Update the button title. |
| 503 [self updatePrimaryButtonTitle]; |
| 504 [_secondaryButton setTitle:self.skipSigninButtonTitle |
| 505 forState:UIControlStateNormal]; |
| 506 [self.view setNeedsLayout]; |
| 507 |
| 508 HideButton(_primaryButton); |
| 509 HideButton(_secondaryButton); |
| 510 [UIView animateWithDuration:kAnimationDuration |
| 511 animations:^{ |
| 512 ShowButton(_primaryButton); |
| 513 ShowButton(_secondaryButton); |
| 514 }]; |
| 515 } |
| 516 |
| 517 - (void)reloadIdentityPickerState { |
| 518 // The account selector view controller reloads itself each time the list |
| 519 // of identities changes, thus there is no need to reload it. |
| 520 |
| 521 [self updatePrimaryButtonTitle]; |
| 522 } |
| 523 |
| 524 - (void)leaveIdentityPickerState:(AuthenticationState)nextState { |
| 525 [UIView animateWithDuration:kAnimationDuration |
| 526 animations:^{ |
| 527 HideButton(_primaryButton); |
| 528 HideButton(_secondaryButton); |
| 529 } |
| 530 completion:^(BOOL finished) { |
| 531 [_accountSelectorVC willMoveToParentViewController:nil]; |
| 532 [[_accountSelectorVC view] removeFromSuperview]; |
| 533 [_accountSelectorVC removeFromParentViewController]; |
| 534 _accountSelectorVC.reset(); |
| 535 [self enterState:nextState]; |
| 536 }]; |
| 537 } |
| 538 |
| 539 #pragma mark - SigninPendingState |
| 540 |
| 541 - (void)enterSigninPendingState { |
| 542 [_secondaryButton setTitle:l10n_util::GetNSString(IDS_CANCEL) |
| 543 forState:UIControlStateNormal]; |
| 544 [self.view setNeedsLayout]; |
| 545 |
| 546 _pendingStateTimer.reset(new base::ElapsedTimer()); |
| 547 ShowButton(_secondaryButton); |
| 548 [_activityIndicator startAnimating]; |
| 549 |
| 550 [self signIntoIdentity:self.selectedIdentity]; |
| 551 } |
| 552 |
| 553 - (void)reloadSigninPendingState { |
| 554 BOOL isSelectedIdentityValid = ios::GetChromeBrowserProvider() |
| 555 ->GetChromeIdentityService() |
| 556 ->IsValidIdentity(self.selectedIdentity); |
| 557 if (!isSelectedIdentityValid) { |
| 558 [_authenticationFlow cancelAndDismiss]; |
| 559 [self changeToState:IDENTITY_PICKER_STATE]; |
| 560 } |
| 561 } |
| 562 |
| 563 - (void)leaveSigninPendingState:(AuthenticationState)nextState { |
| 564 if (!_pendingStateTimer) { |
| 565 // The controller is already leaving the signin pending state, simply update |
| 566 // the new state to take into account the last request only. |
| 567 _activityIndicatorNextState = nextState; |
| 568 return; |
| 569 } |
| 570 |
| 571 _activityIndicatorNextState = nextState; |
| 572 _activityIndicator.get().delegate = self; |
| 573 |
| 574 base::TimeDelta remainingTime = |
| 575 base::TimeDelta::FromMilliseconds(kMinimunPendingStateDurationMs) - |
| 576 _pendingStateTimer->Elapsed(); |
| 577 _pendingStateTimer.reset(); |
| 578 |
| 579 if (remainingTime.InMilliseconds() < 0) { |
| 580 [_activityIndicator stopAnimating]; |
| 581 } else { |
| 582 // If the signin pending state is too fast, the screen will appear to |
| 583 // flicker. Make sure to animate for at least |
| 584 // |kMinimunPendingStateDurationMs| milliseconds. |
| 585 base::WeakNSObject<ChromeSigninViewController> weakSelf(self); |
| 586 ProceduralBlock completionBlock = ^{ |
| 587 base::scoped_nsobject<ChromeSigninViewController> strongSelf( |
| 588 [weakSelf retain]); |
| 589 if (!strongSelf) |
| 590 return; |
| 591 [strongSelf.get()->_activityIndicator stopAnimating]; |
| 592 strongSelf.get()->_leavingPendingStateTimer.reset(); |
| 593 }; |
| 594 _leavingPendingStateTimer.reset(new base::Timer(false, false)); |
| 595 _leavingPendingStateTimer->Start(FROM_HERE, remainingTime, |
| 596 base::BindBlock(completionBlock)); |
| 597 } |
| 598 } |
| 599 |
| 600 #pragma mark - IdentitySelectedState |
| 601 |
| 602 - (void)enterIdentitySelectedState { |
| 603 _confirmationVC.reset([[SigninConfirmationViewController alloc] |
| 604 initWithIdentity:self.selectedIdentity]); |
| 605 _confirmationVC.get().delegate = self; |
| 606 |
| 607 _hasConfirmationScreenReachedBottom = NO; |
| 608 [_confirmationVC willMoveToParentViewController:self]; |
| 609 [self addChildViewController:_confirmationVC]; |
| 610 _confirmationVC.get().view.frame = self.view.bounds; |
| 611 [self.view insertSubview:_confirmationVC.get().view |
| 612 belowSubview:_primaryButton]; |
| 613 [_confirmationVC didMoveToParentViewController:self]; |
| 614 |
| 615 [self setSecondaryButtonStyling:_primaryButton]; |
| 616 NSString* primaryButtonTitle = l10n_util::GetNSString( |
| 617 IDS_IOS_ACCOUNT_CONSISTENCY_CONFIRMATION_SCROLL_BUTTON); |
| 618 [_primaryButton setTitle:primaryButtonTitle forState:UIControlStateNormal]; |
| 619 [_primaryButton setImage:[UIImage imageNamed:@"signin_confirmation_more"] |
| 620 forState:UIControlStateNormal]; |
| 621 NSString* secondaryButtonTitle = l10n_util::GetNSString( |
| 622 IDS_IOS_ACCOUNT_CONSISTENCY_CONFIRMATION_UNDO_BUTTON); |
| 623 [_secondaryButton setTitle:secondaryButtonTitle |
| 624 forState:UIControlStateNormal]; |
| 625 [self.view setNeedsLayout]; |
| 626 |
| 627 HideButton(_primaryButton); |
| 628 HideButton(_secondaryButton); |
| 629 [UIView animateWithDuration:kAnimationDuration |
| 630 animations:^{ |
| 631 ShowButton(_primaryButton); |
| 632 ShowButton(_secondaryButton); |
| 633 } |
| 634 completion:nil]; |
| 635 } |
| 636 |
| 637 - (void)reloadIdentitySelectedState { |
| 638 BOOL isSelectedIdentityValid = ios::GetChromeBrowserProvider() |
| 639 ->GetChromeIdentityService() |
| 640 ->IsValidIdentity(self.selectedIdentity); |
| 641 if (!isSelectedIdentityValid) { |
| 642 [self changeToState:IDENTITY_PICKER_STATE]; |
| 643 return; |
| 644 } |
| 645 } |
| 646 |
| 647 - (void)leaveIdentitySelectedState:(AuthenticationState)nextState { |
| 648 [_confirmationVC willMoveToParentViewController:nil]; |
| 649 [[_confirmationVC view] removeFromSuperview]; |
| 650 [_confirmationVC removeFromParentViewController]; |
| 651 _confirmationVC.reset(); |
| 652 [self setPrimaryButtonStyling:_primaryButton]; |
| 653 HideButton(_primaryButton); |
| 654 HideButton(_secondaryButton); |
| 655 [self undoSignIn]; |
| 656 [self enterState:nextState]; |
| 657 } |
| 658 |
| 659 #pragma mark - UIViewController |
| 660 |
| 661 - (void)viewDidLoad { |
| 662 [super viewDidLoad]; |
| 663 self.view.backgroundColor = self.backgroundColor; |
| 664 |
| 665 _primaryButton.reset([[MDCFlatButton alloc] init]); |
| 666 [self setPrimaryButtonStyling:_primaryButton]; |
| 667 [_primaryButton addTarget:self |
| 668 action:@selector(onPrimaryButtonPressed:) |
| 669 forControlEvents:UIControlEventTouchUpInside]; |
| 670 HideButton(_primaryButton); |
| 671 [self.view addSubview:_primaryButton]; |
| 672 |
| 673 _secondaryButton.reset([[MDCFlatButton alloc] init]); |
| 674 [self setSecondaryButtonStyling:_secondaryButton]; |
| 675 [_secondaryButton addTarget:self |
| 676 action:@selector(onSecondaryButtonPressed:) |
| 677 forControlEvents:UIControlEventTouchUpInside]; |
| 678 [_secondaryButton setAccessibilityIdentifier:@"ic_close"]; |
| 679 HideButton(_secondaryButton); |
| 680 [self.view addSubview:_secondaryButton]; |
| 681 |
| 682 _activityIndicator.reset( |
| 683 [[MDCActivityIndicator alloc] initWithFrame:CGRectZero]); |
| 684 [_activityIndicator setDelegate:self]; |
| 685 [_activityIndicator setStrokeWidth:3]; |
| 686 [_activityIndicator |
| 687 setCycleColors:@[ [[MDCPalette cr_bluePalette] tint500] ]]; |
| 688 [self.view addSubview:_activityIndicator]; |
| 689 |
| 690 _gradientView.reset([[UIView alloc] initWithFrame:CGRectZero]); |
| 691 _gradientLayer.reset([[CAGradientLayer layer] retain]); |
| 692 _gradientLayer.get().colors = [NSArray |
| 693 arrayWithObjects:(id)[[UIColor colorWithWhite:1 alpha:0] CGColor], |
| 694 (id)[self.backgroundColor CGColor], nil]; |
| 695 [[_gradientView layer] insertSublayer:_gradientLayer atIndex:0]; |
| 696 [self.view addSubview:_gradientView]; |
| 697 } |
| 698 |
| 699 - (void)viewWillAppear:(BOOL)animated { |
| 700 [super viewWillAppear:animated]; |
| 701 |
| 702 if (_currentState != NULL_STATE) { |
| 703 return; |
| 704 } |
| 705 if (_autoSignIn) { |
| 706 [self enterState:SIGNIN_PENDING_STATE]; |
| 707 } else { |
| 708 [self enterState:IDENTITY_PICKER_STATE]; |
| 709 } |
| 710 } |
| 711 |
| 712 #pragma mark - Events |
| 713 |
| 714 - (void)onPrimaryButtonPressed:(id)sender { |
| 715 switch (_currentState) { |
| 716 case NULL_STATE: |
| 717 NOTREACHED(); |
| 718 return; |
| 719 case IDENTITY_PICKER_STATE: { |
| 720 if (_interactionManager) { |
| 721 // Adding an account is ongoing, ignore the button press. |
| 722 return; |
| 723 } |
| 724 ChromeIdentity* selectedIdentity = [_accountSelectorVC selectedIdentity]; |
| 725 [self setSelectedIdentity:selectedIdentity]; |
| 726 if (selectedIdentity) { |
| 727 [self changeToState:SIGNIN_PENDING_STATE]; |
| 728 } else { |
| 729 [self openAuthenticationDialogAddIdentity]; |
| 730 } |
| 731 return; |
| 732 } |
| 733 case SIGNIN_PENDING_STATE: |
| 734 NOTREACHED(); |
| 735 return; |
| 736 case IDENTITY_SELECTED_STATE: |
| 737 if (_hasConfirmationScreenReachedBottom) { |
| 738 [self acceptSignInAndCommitSyncChanges]; |
| 739 } else { |
| 740 [_confirmationVC scrollToBottom]; |
| 741 } |
| 742 return; |
| 743 case DONE_STATE: |
| 744 // Ignored |
| 745 return; |
| 746 } |
| 747 NOTREACHED(); |
| 748 } |
| 749 |
| 750 - (void)onSecondaryButtonPressed:(id)sender { |
| 751 switch (_currentState) { |
| 752 case NULL_STATE: |
| 753 NOTREACHED(); |
| 754 return; |
| 755 case IDENTITY_PICKER_STATE: |
| 756 if (!_didFinishSignIn) { |
| 757 _didFinishSignIn = YES; |
| 758 [_delegate didSkipSignIn:self]; |
| 759 } |
| 760 return; |
| 761 case SIGNIN_PENDING_STATE: |
| 762 base::RecordAction(base::UserMetricsAction("Signin_Undo_Signin")); |
| 763 [_authenticationFlow cancelAndDismiss]; |
| 764 [self undoSignIn]; |
| 765 [self changeToState:IDENTITY_PICKER_STATE]; |
| 766 return; |
| 767 case IDENTITY_SELECTED_STATE: |
| 768 base::RecordAction(base::UserMetricsAction("Signin_Undo_Signin")); |
| 769 [self changeToState:IDENTITY_PICKER_STATE]; |
| 770 return; |
| 771 case DONE_STATE: |
| 772 // Ignored |
| 773 return; |
| 774 } |
| 775 NOTREACHED(); |
| 776 } |
| 777 |
| 778 #pragma mark - ChromeIdentityServiceObserver |
| 779 |
| 780 - (void)onIdentityListChanged { |
| 781 switch (_currentState) { |
| 782 case NULL_STATE: |
| 783 case DONE_STATE: |
| 784 return; |
| 785 case IDENTITY_PICKER_STATE: |
| 786 [self reloadIdentityPickerState]; |
| 787 return; |
| 788 case SIGNIN_PENDING_STATE: |
| 789 [self reloadSigninPendingState]; |
| 790 return; |
| 791 case IDENTITY_SELECTED_STATE: |
| 792 [self reloadIdentitySelectedState]; |
| 793 return; |
| 794 } |
| 795 } |
| 796 |
| 797 - (void)onChromeIdentityServiceWillBeDestroyed { |
| 798 _identityServiceObserver.reset(); |
| 799 } |
| 800 |
| 801 #pragma mark - Layout |
| 802 |
| 803 - (void)viewDidLayoutSubviews { |
| 804 [super viewDidLayoutSubviews]; |
| 805 |
| 806 AuthenticationViewConstants constants; |
| 807 if ([self.traitCollection horizontalSizeClass] == |
| 808 UIUserInterfaceSizeClassRegular) { |
| 809 constants = kRegularConstants; |
| 810 } else { |
| 811 constants = kCompactConstants; |
| 812 } |
| 813 |
| 814 [self layoutButtons:constants]; |
| 815 |
| 816 CGSize viewSize = self.view.bounds.size; |
| 817 CGFloat collectionViewHeight = viewSize.height - |
| 818 _primaryButton.get().frame.size.height - |
| 819 constants.ButtonVerticalPadding; |
| 820 CGRect collectionViewFrame = |
| 821 CGRectMake(0, 0, viewSize.width, collectionViewHeight); |
| 822 [_accountSelectorVC.get().view setFrame:collectionViewFrame]; |
| 823 [_confirmationVC.get().view setFrame:collectionViewFrame]; |
| 824 |
| 825 // Layout the gradient view right above the buttons. |
| 826 CGFloat gradientOriginY = CGRectGetHeight(self.view.bounds) - |
| 827 constants.ButtonVerticalPadding - |
| 828 constants.ButtonHeight - constants.GradientHeight; |
| 829 [_gradientView setFrame:CGRectMake(0, gradientOriginY, viewSize.width, |
| 830 constants.GradientHeight)]; |
| 831 [_gradientLayer setFrame:[_gradientView bounds]]; |
| 832 |
| 833 // Layout the activity indicator in the center of the view. |
| 834 CGRect bounds = self.view.bounds; |
| 835 [_activityIndicator |
| 836 setCenter:CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds))]; |
| 837 } |
| 838 |
| 839 - (void)layoutButtons:(const AuthenticationViewConstants&)constants { |
| 840 [_primaryButton titleLabel].font = [[MDFRobotoFontLoader sharedInstance] |
| 841 mediumFontOfSize:constants.SecondaryFontSize]; |
| 842 [_secondaryButton titleLabel].font = [[MDFRobotoFontLoader sharedInstance] |
| 843 mediumFontOfSize:constants.SecondaryFontSize]; |
| 844 |
| 845 LayoutRect primaryButtonLayout = LayoutRectZero; |
| 846 primaryButtonLayout.boundingWidth = CGRectGetWidth(self.view.bounds); |
| 847 primaryButtonLayout.size = [_primaryButton |
| 848 sizeThatFits:CGSizeMake(CGFLOAT_MAX, constants.ButtonHeight)]; |
| 849 primaryButtonLayout.position.leading = primaryButtonLayout.boundingWidth - |
| 850 primaryButtonLayout.size.width - |
| 851 constants.ButtonHorizontalPadding; |
| 852 primaryButtonLayout.position.originY = CGRectGetHeight(self.view.bounds) - |
| 853 constants.ButtonVerticalPadding - |
| 854 constants.ButtonHeight; |
| 855 primaryButtonLayout.size.height = constants.ButtonHeight; |
| 856 [_primaryButton setFrame:LayoutRectGetRect(primaryButtonLayout)]; |
| 857 |
| 858 UIEdgeInsets imageInsets = UIEdgeInsetsZero; |
| 859 UIEdgeInsets titleInsets = UIEdgeInsetsZero; |
| 860 if ([_primaryButton imageForState:UIControlStateNormal]) { |
| 861 // Title label should be leading, followed by the image (with some padding). |
| 862 CGFloat paddedImageWidth = |
| 863 [_primaryButton imageView].frame.size.width + kMoreButtonPadding; |
| 864 CGFloat paddedTitleWidth = |
| 865 [_primaryButton titleLabel].frame.size.width + kMoreButtonPadding; |
| 866 imageInsets = UIEdgeInsetsMake(0, paddedTitleWidth, 0, -paddedTitleWidth); |
| 867 titleInsets = UIEdgeInsetsMake(0, -paddedImageWidth, 0, paddedImageWidth); |
| 868 } |
| 869 [_primaryButton setImageEdgeInsets:imageInsets]; |
| 870 [_primaryButton setTitleEdgeInsets:titleInsets]; |
| 871 |
| 872 LayoutRect secondaryButtonLayout = primaryButtonLayout; |
| 873 secondaryButtonLayout.size = [_secondaryButton |
| 874 sizeThatFits:CGSizeMake(CGFLOAT_MAX, constants.ButtonHeight)]; |
| 875 secondaryButtonLayout.position.leading = constants.ButtonHorizontalPadding; |
| 876 secondaryButtonLayout.size.height = constants.ButtonHeight; |
| 877 [_secondaryButton setFrame:LayoutRectGetRect(secondaryButtonLayout)]; |
| 878 } |
| 879 |
| 880 #pragma mark - MDCActivityIndicatorDelegate |
| 881 |
| 882 - (void)activityIndicatorAnimationDidFinish: |
| 883 (MDCActivityIndicator*)activityIndicator { |
| 884 DCHECK_EQ(SIGNIN_PENDING_STATE, _currentState); |
| 885 DCHECK_EQ(_activityIndicator, activityIndicator); |
| 886 |
| 887 // The activity indicator is only used in the signin pending state. Its |
| 888 // animation is stopped only when leaving the state. |
| 889 if (_activityIndicatorNextState != NULL_STATE) { |
| 890 [self enterState:_activityIndicatorNextState]; |
| 891 _activityIndicatorNextState = NULL_STATE; |
| 892 } |
| 893 } |
| 894 |
| 895 #pragma mark - ChromeIdentityInteractionManagerDelegate |
| 896 |
| 897 - (void)interactionManager:(ChromeIdentityInteractionManager*)interactionManager |
| 898 presentViewController:(UIViewController*)viewController |
| 899 animated:(BOOL)animated |
| 900 completion:(ProceduralBlock)completion { |
| 901 [self presentViewController:viewController |
| 902 animated:animated |
| 903 completion:completion]; |
| 904 } |
| 905 |
| 906 - (void)interactionManager:(ChromeIdentityInteractionManager*)interactionManager |
| 907 dismissViewControllerAnimated:(BOOL)animated |
| 908 completion:(ProceduralBlock)completion { |
| 909 [self dismissViewControllerAnimated:animated completion:completion]; |
| 910 } |
| 911 |
| 912 #pragma mark - SigninAccountSelectorViewControllerDelegate |
| 913 |
| 914 - (void)accountSelectorControllerDidSelectAddAccount: |
| 915 (SigninAccountSelectorViewController*)accountSelectorController { |
| 916 DCHECK_EQ(_accountSelectorVC, accountSelectorController); |
| 917 if (_ongoingStateChange) { |
| 918 return; |
| 919 } |
| 920 [self openAuthenticationDialogAddIdentity]; |
| 921 } |
| 922 |
| 923 #pragma mark - SigninConfirmationViewControllerDelegate |
| 924 |
| 925 // Callback for when a link in the label is pressed. |
| 926 - (void)signinConfirmationControllerDidTapSettingsLink: |
| 927 (SigninConfirmationViewController*)controller { |
| 928 DCHECK_EQ(_confirmationVC, controller); |
| 929 |
| 930 base::scoped_nsobject<GenericChromeCommand> command( |
| 931 [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_ACCOUNTS_SETTINGS]); |
| 932 [self acceptSignInAndExecuteCommand:command]; |
| 933 } |
| 934 |
| 935 - (void)signinConfirmationControllerDidReachBottom: |
| 936 (SigninConfirmationViewController*)controller { |
| 937 if (_hasConfirmationScreenReachedBottom) { |
| 938 return; |
| 939 } |
| 940 _hasConfirmationScreenReachedBottom = YES; |
| 941 [self setPrimaryButtonStyling:_primaryButton]; |
| 942 [_primaryButton setTitle:[self acceptSigninButtonTitle] |
| 943 forState:UIControlStateNormal]; |
| 944 [_primaryButton setImage:nil forState:UIControlStateNormal]; |
| 945 [self.view setNeedsLayout]; |
| 946 } |
| 947 |
| 948 @end |
OLD | NEW |