| // 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. |
| |
| #include "ios/chrome/browser/ui/authentication/chrome_signin_view_controller.h" |
| |
| #include <stdint.h> |
| #include <cmath> |
| #include <memory> |
| |
| #import <CoreGraphics/CoreGraphics.h> |
| #import <QuartzCore/QuartzCore.h> |
| |
| #import "base/ios/block_types.h" |
| #import "base/ios/ios_util.h" |
| #import "base/mac/bind_objc_block.h" |
| #include "base/metrics/user_metrics.h" |
| #import "base/strings/sys_string_conversions.h" |
| #include "base/timer/elapsed_timer.h" |
| #include "base/timer/timer.h" |
| #include "components/signin/core/browser/signin_metrics.h" |
| #include "components/strings/grit/components_strings.h" |
| #import "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #include "ios/chrome/browser/chrome_url_constants.h" |
| #import "ios/chrome/browser/signin/authentication_service.h" |
| #import "ios/chrome/browser/signin/authentication_service_factory.h" |
| #import "ios/chrome/browser/signin/chrome_identity_service_observer_bridge.h" |
| #include "ios/chrome/browser/signin/signin_util.h" |
| #import "ios/chrome/browser/sync/sync_setup_service.h" |
| #import "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/authentication_flow.h" |
| #import "ios/chrome/browser/ui/authentication/authentication_ui_util.h" |
| #include "ios/chrome/browser/ui/authentication/signin_account_selector_view_controller.h" |
| #include "ios/chrome/browser/ui/authentication/signin_confirmation_view_controller.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/rtl_geometry.h" |
| #import "ios/chrome/browser/ui/ui_util.h" |
| #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| #import "ios/chrome/browser/ui/util/label_link_controller.h" |
| #include "ios/chrome/common/string_util.h" |
| #include "ios/chrome/grit/ios_chromium_strings.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #import "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_interaction_manager.h" |
| #import "ios/public/provider/chrome/browser/signin/chrome_identity_service.h" |
| #import "ios/third_party/material_components_ios/src/components/ActivityIndicator/src/MaterialActivityIndicator.h" |
| #import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.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" |
| #import "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| |
| // Default animation duration. |
| const CGFloat kAnimationDuration = 0.5f; |
| |
| enum LayoutType { |
| LAYOUT_REGULAR, |
| LAYOUT_COMPACT, |
| }; |
| |
| // Alpha threshold upon which a view is considered hidden. |
| const CGFloat kHiddenAlphaThreshold = 0.1; |
| |
| // Minimum duration of the pending state in milliseconds. |
| const int64_t kMinimunPendingStateDurationMs = 300; |
| |
| // Internal padding between the title and image in the "More" button. |
| const CGFloat kMoreButtonPadding = 5.0f; |
| |
| struct AuthenticationViewConstants { |
| CGFloat PrimaryFontSize; |
| CGFloat SecondaryFontSize; |
| CGFloat GradientHeight; |
| CGFloat ButtonHeight; |
| CGFloat ButtonHorizontalPadding; |
| CGFloat ButtonVerticalPadding; |
| }; |
| |
| const AuthenticationViewConstants kCompactConstants = { |
| 24, // PrimaryFontSize |
| 14, // SecondaryFontSize |
| 40, // GradientHeight |
| 36, // ButtonHeight |
| 32, // ButtonHorizontalPadding |
| 32, // ButtonVerticalPadding |
| }; |
| |
| const AuthenticationViewConstants kRegularConstants = { |
| 1.5 * kCompactConstants.PrimaryFontSize, |
| 1.5 * kCompactConstants.SecondaryFontSize, |
| kCompactConstants.GradientHeight, |
| 1.5 * kCompactConstants.ButtonHeight, |
| kCompactConstants.ButtonHorizontalPadding, |
| kCompactConstants.ButtonVerticalPadding, |
| }; |
| |
| enum AuthenticationState { |
| NULL_STATE, |
| IDENTITY_PICKER_STATE, |
| SIGNIN_PENDING_STATE, |
| IDENTITY_SELECTED_STATE, |
| DONE_STATE, |
| }; |
| |
| // Fades in |button| on screen if not already visible. |
| void ShowButton(UIButton* button) { |
| if (button.alpha > kHiddenAlphaThreshold) |
| return; |
| button.alpha = 1.0; |
| } |
| |
| // Fades out |button| on screen if not already hidden. |
| void HideButton(UIButton* button) { |
| if (button.alpha < kHiddenAlphaThreshold) |
| return; |
| button.alpha = 0.0; |
| } |
| |
| } // namespace |
| |
| @interface ChromeSigninViewController ()< |
| ChromeIdentityInteractionManagerDelegate, |
| ChromeIdentityServiceObserver, |
| SigninAccountSelectorViewControllerDelegate, |
| SigninConfirmationViewControllerDelegate, |
| MDCActivityIndicatorDelegate> |
| @property(nonatomic, strong) ChromeIdentity* selectedIdentity; |
| |
| @end |
| |
| @implementation ChromeSigninViewController { |
| ios::ChromeBrowserState* _browserState; // weak |
| __weak id<ChromeSigninViewControllerDelegate> _delegate; |
| std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver; |
| ChromeIdentity* _selectedIdentity; |
| |
| // Authentication |
| AlertCoordinator* _alertCoordinator; |
| AuthenticationFlow* _authenticationFlow; |
| BOOL _addedAccount; |
| BOOL _autoSignIn; |
| BOOL _didSignIn; |
| BOOL _didAcceptSignIn; |
| BOOL _didFinishSignIn; |
| BOOL _isPresentedOnSettings; |
| signin_metrics::AccessPoint _accessPoint; |
| signin_metrics::PromoAction _promoAction; |
| ChromeIdentityInteractionManager* _interactionManager; |
| |
| // Basic state. |
| AuthenticationState _currentState; |
| BOOL _ongoingStateChange; |
| MDCActivityIndicator* _activityIndicator; |
| MDCButton* _primaryButton; |
| MDCButton* _secondaryButton; |
| UIView* _gradientView; |
| CAGradientLayer* _gradientLayer; |
| |
| // Identity picker state. |
| SigninAccountSelectorViewController* _accountSelectorVC; |
| |
| // Signin pending state. |
| AuthenticationState _activityIndicatorNextState; |
| std::unique_ptr<base::ElapsedTimer> _pendingStateTimer; |
| std::unique_ptr<base::Timer> _leavingPendingStateTimer; |
| |
| // Identity selected state. |
| SigninConfirmationViewController* _confirmationVC; |
| BOOL _hasConfirmationScreenReachedBottom; |
| } |
| |
| @synthesize delegate = _delegate; |
| @synthesize shouldClearData = _shouldClearData; |
| |
| - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState |
| isPresentedOnSettings:(BOOL)isPresentedOnSettings |
| accessPoint:(signin_metrics::AccessPoint)accessPoint |
| promoAction:(signin_metrics::PromoAction)promoAction |
| signInIdentity:(ChromeIdentity*)identity { |
| self = [super init]; |
| if (self) { |
| _browserState = browserState; |
| _isPresentedOnSettings = isPresentedOnSettings; |
| _accessPoint = accessPoint; |
| _promoAction = promoAction; |
| |
| if (identity) { |
| _autoSignIn = YES; |
| [self setSelectedIdentity:identity]; |
| } |
| _identityServiceObserver.reset( |
| new ChromeIdentityServiceObserverBridge(self)); |
| _currentState = NULL_STATE; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| // The call to -[UIControl addTarget:action:forControlEvents:] is made just |
| // after the creation of those objects, so if the objects are not nil, then |
| // it is safe to call -[UIControl removeTarget:action:forControlEvents:]. |
| // If they are nil, then the call does nothing. |
| [_primaryButton removeTarget:self |
| action:@selector(onPrimaryButtonPressed:) |
| forControlEvents:UIControlEventTouchDown]; |
| [_secondaryButton removeTarget:self |
| action:@selector(onSecondaryButtonPressed:) |
| forControlEvents:UIControlEventTouchDown]; |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| } |
| |
| - (void)cancel { |
| if (_alertCoordinator) { |
| DCHECK(!_authenticationFlow && !_interactionManager); |
| [_alertCoordinator executeCancelHandler]; |
| [_alertCoordinator stop]; |
| } |
| if (_interactionManager) { |
| DCHECK(!_alertCoordinator && !_authenticationFlow); |
| [_interactionManager cancelAndDismissAnimated:NO]; |
| } |
| if (_authenticationFlow) { |
| DCHECK(!_alertCoordinator && !_interactionManager); |
| [_authenticationFlow cancelAndDismiss]; |
| } |
| if (!_didAcceptSignIn && _didSignIn) { |
| AuthenticationServiceFactory::GetForBrowserState(_browserState) |
| ->SignOut(signin_metrics::ABORT_SIGNIN, nil); |
| _didSignIn = NO; |
| } |
| if (!_didFinishSignIn) { |
| _didFinishSignIn = YES; |
| [_delegate didFailSignIn:self]; |
| } |
| } |
| |
| - (void)acceptSignInAndExecuteCommand:(GenericChromeCommand*)command { |
| signin_metrics::LogSigninAccessPointCompleted(_accessPoint, _promoAction); |
| _didAcceptSignIn = YES; |
| if (!_didFinishSignIn) { |
| _didFinishSignIn = YES; |
| [_delegate didAcceptSignIn:self executeCommand:command]; |
| } |
| } |
| |
| - (void)acceptSignInAndCommitSyncChanges { |
| DCHECK(_didSignIn); |
| SyncSetupServiceFactory::GetForBrowserState(_browserState)->CommitChanges(); |
| [self acceptSignInAndExecuteCommand:nil]; |
| } |
| |
| - (void)setPrimaryButtonStyling:(MDCButton*)button { |
| [button setBackgroundColor:[[MDCPalette cr_bluePalette] tint500] |
| forState:UIControlStateNormal]; |
| [button setCustomTitleColor:[UIColor whiteColor]]; |
| [button setUnderlyingColorHint:[UIColor blackColor]]; |
| [button setInkColor:[UIColor colorWithWhite:1 alpha:0.2f]]; |
| } |
| |
| - (void)setSecondaryButtonStyling:(MDCButton*)button { |
| [button setBackgroundColor:self.backgroundColor |
| forState:UIControlStateNormal]; |
| [button setCustomTitleColor:[[MDCPalette cr_bluePalette] tint500]]; |
| [button setUnderlyingColorHint:[UIColor whiteColor]]; |
| [button setInkColor:[UIColor colorWithWhite:0 alpha:0.06f]]; |
| } |
| |
| #pragma mark - Accessibility |
| |
| - (BOOL)accessibilityPerformEscape { |
| // Simulate a press on the secondary button. |
| [self onSecondaryButtonPressed:self]; |
| return YES; |
| } |
| |
| #pragma mark - Properties |
| |
| - (ios::ChromeBrowserState*)browserState { |
| return _browserState; |
| } |
| |
| - (id<ChromeSigninViewControllerDelegate>)delegate { |
| return _delegate; |
| } |
| |
| - (UIColor*)backgroundColor { |
| return [[MDCPalette greyPalette] tint50]; |
| } |
| |
| - (NSString*)identityPickerTitle { |
| return l10n_util::GetNSString(IDS_IOS_ACCOUNT_CONSISTENCY_SETUP_TITLE); |
| } |
| |
| - (NSString*)acceptSigninButtonTitle { |
| return l10n_util::GetNSString( |
| IDS_IOS_ACCOUNT_CONSISTENCY_CONFIRMATION_OK_BUTTON); |
| } |
| |
| - (NSString*)skipSigninButtonTitle { |
| return l10n_util::GetNSString(IDS_IOS_ACCOUNT_CONSISTENCY_SETUP_SKIP_BUTTON); |
| } |
| |
| - (UIButton*)primaryButton { |
| return _primaryButton; |
| } |
| |
| - (UIButton*)secondaryButton { |
| return _secondaryButton; |
| } |
| |
| - (void)setSelectedIdentity:(ChromeIdentity*)identity { |
| DCHECK(identity || (IDENTITY_PICKER_STATE == _currentState)); |
| _selectedIdentity = identity; |
| } |
| |
| - (ChromeIdentity*)selectedIdentity { |
| return _selectedIdentity; |
| } |
| |
| #pragma mark - Authentication |
| |
| - (void)handleAuthenticationError:(NSError*)error { |
| // Filter out cancel and errors handled internally by ChromeIdentity. |
| if (!ShouldHandleSigninError(error)) { |
| return; |
| } |
| _alertCoordinator = ios_internal::ErrorCoordinator(error, nil, self); |
| [_alertCoordinator start]; |
| } |
| |
| - (void)signIntoIdentity:(ChromeIdentity*)identity { |
| [_delegate willStartSignIn:self]; |
| DCHECK(!_authenticationFlow); |
| _authenticationFlow = |
| [[AuthenticationFlow alloc] initWithBrowserState:_browserState |
| identity:identity |
| shouldClearData:_shouldClearData |
| postSignInAction:POST_SIGNIN_ACTION_NONE |
| presentingViewController:self]; |
| __weak ChromeSigninViewController* weakSelf = self; |
| [_authenticationFlow startSignInWithCompletion:^(BOOL success) { |
| [weakSelf onAccountSigninCompletion:success]; |
| }]; |
| } |
| |
| - (void)openAuthenticationDialogAddIdentity { |
| DCHECK(!_interactionManager); |
| _interactionManager = |
| ios::GetChromeBrowserProvider() |
| ->GetChromeIdentityService() |
| ->NewChromeIdentityInteractionManager(_browserState, self); |
| __weak ChromeSigninViewController* weakSelf = self; |
| SigninCompletionCallback completion = |
| ^(ChromeIdentity* identity, NSError* error) { |
| ChromeSigninViewController* strongSelf = weakSelf; |
| if (!strongSelf || !strongSelf->_interactionManager) |
| return; |
| // The ChromeIdentityInteractionManager is not used anymore at this |
| // point. |
| strongSelf->_interactionManager = nil; |
| |
| if (error) { |
| [strongSelf handleAuthenticationError:error]; |
| return; |
| } |
| strongSelf->_addedAccount = YES; |
| [strongSelf onIdentityListChanged]; |
| [strongSelf setSelectedIdentity:identity]; |
| [strongSelf changeToState:SIGNIN_PENDING_STATE]; |
| }; |
| [_delegate willStartAddAccount:self]; |
| [_interactionManager addAccountWithCompletion:completion]; |
| } |
| |
| - (void)onAccountSigninCompletion:(BOOL)success { |
| _authenticationFlow = nil; |
| if (success) { |
| DCHECK(!_didSignIn); |
| _didSignIn = YES; |
| [_delegate didSignIn:self]; |
| [self changeToState:IDENTITY_SELECTED_STATE]; |
| } else { |
| [self changeToState:IDENTITY_PICKER_STATE]; |
| } |
| } |
| |
| - (void)undoSignIn { |
| if (_didSignIn) { |
| AuthenticationServiceFactory::GetForBrowserState(_browserState) |
| ->SignOut(signin_metrics::ABORT_SIGNIN, nil); |
| [_delegate didUndoSignIn:self identity:self.selectedIdentity]; |
| _didSignIn = NO; |
| } |
| if (_addedAccount) { |
| // This is best effort. If the operation fails, the account will be left on |
| // the device. The user will not be warned either as this call is |
| // asynchronous (but undo is not), the application might be in an unknown |
| // state when the forget identity operation finishes. |
| ios::GetChromeBrowserProvider()->GetChromeIdentityService()->ForgetIdentity( |
| self.selectedIdentity, nil); |
| } |
| _addedAccount = NO; |
| } |
| |
| #pragma mark - State machine |
| |
| - (void)enterState:(AuthenticationState)state { |
| _ongoingStateChange = NO; |
| if (_didFinishSignIn) { |
| // Stop the state machine when the sign-in is done. |
| _currentState = DONE_STATE; |
| return; |
| } |
| _currentState = state; |
| switch (state) { |
| case NULL_STATE: |
| NOTREACHED(); |
| break; |
| case IDENTITY_PICKER_STATE: |
| [self enterIdentityPickerState]; |
| break; |
| case SIGNIN_PENDING_STATE: |
| [self enterSigninPendingState]; |
| break; |
| case IDENTITY_SELECTED_STATE: |
| [self enterIdentitySelectedState]; |
| break; |
| case DONE_STATE: |
| break; |
| } |
| } |
| |
| - (void)changeToState:(AuthenticationState)nextState { |
| if (_currentState == nextState) |
| return; |
| _ongoingStateChange = YES; |
| switch (_currentState) { |
| case NULL_STATE: |
| DCHECK_NE(IDENTITY_SELECTED_STATE, nextState); |
| [self enterState:nextState]; |
| return; |
| case IDENTITY_PICKER_STATE: |
| DCHECK_EQ(SIGNIN_PENDING_STATE, nextState); |
| [self leaveIdentityPickerState:nextState]; |
| return; |
| case SIGNIN_PENDING_STATE: |
| [self leaveSigninPendingState:nextState]; |
| return; |
| case IDENTITY_SELECTED_STATE: |
| DCHECK_EQ(IDENTITY_PICKER_STATE, nextState); |
| [self leaveIdentitySelectedState:nextState]; |
| return; |
| case DONE_STATE: |
| // Ignored |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| #pragma mark - IdentityPickerState |
| |
| - (void)updatePrimaryButtonTitle { |
| bool hasIdentities = ios::GetChromeBrowserProvider() |
| ->GetChromeIdentityService() |
| ->HasIdentities(); |
| NSString* primaryButtonTitle = |
| hasIdentities |
| ? l10n_util::GetNSString( |
| IDS_IOS_ACCOUNT_CONSISTENCY_SETUP_SIGNIN_BUTTON) |
| : l10n_util::GetNSString( |
| IDS_IOS_ACCOUNT_CONSISTENCY_SETUP_SIGNIN_NO_ACCOUNT_BUTTON); |
| [_primaryButton setTitle:primaryButtonTitle forState:UIControlStateNormal]; |
| [_primaryButton setImage:nil forState:UIControlStateNormal]; |
| [self.view setNeedsLayout]; |
| } |
| |
| - (void)enterIdentityPickerState { |
| // Reset the selected identity. |
| [self setSelectedIdentity:nil]; |
| |
| // Add the account selector view controller. |
| _accountSelectorVC = [[SigninAccountSelectorViewController alloc] init]; |
| _accountSelectorVC.delegate = self; |
| [_accountSelectorVC willMoveToParentViewController:self]; |
| [self addChildViewController:_accountSelectorVC]; |
| _accountSelectorVC.view.frame = self.view.bounds; |
| [self.view insertSubview:_accountSelectorVC.view belowSubview:_primaryButton]; |
| [_accountSelectorVC didMoveToParentViewController:self]; |
| |
| // Update the button title. |
| [self updatePrimaryButtonTitle]; |
| [_secondaryButton setTitle:self.skipSigninButtonTitle |
| forState:UIControlStateNormal]; |
| [self.view setNeedsLayout]; |
| |
| HideButton(_primaryButton); |
| HideButton(_secondaryButton); |
| [UIView animateWithDuration:kAnimationDuration |
| animations:^{ |
| ShowButton(_primaryButton); |
| ShowButton(_secondaryButton); |
| }]; |
| } |
| |
| - (void)reloadIdentityPickerState { |
| // The account selector view controller reloads itself each time the list |
| // of identities changes, thus there is no need to reload it. |
| |
| [self updatePrimaryButtonTitle]; |
| } |
| |
| - (void)leaveIdentityPickerState:(AuthenticationState)nextState { |
| [UIView animateWithDuration:kAnimationDuration |
| animations:^{ |
| HideButton(_primaryButton); |
| HideButton(_secondaryButton); |
| } |
| completion:^(BOOL finished) { |
| [_accountSelectorVC willMoveToParentViewController:nil]; |
| [[_accountSelectorVC view] removeFromSuperview]; |
| [_accountSelectorVC removeFromParentViewController]; |
| _accountSelectorVC = nil; |
| [self enterState:nextState]; |
| }]; |
| } |
| |
| #pragma mark - SigninPendingState |
| |
| - (void)enterSigninPendingState { |
| [_secondaryButton setTitle:l10n_util::GetNSString(IDS_CANCEL) |
| forState:UIControlStateNormal]; |
| [self.view setNeedsLayout]; |
| |
| _pendingStateTimer.reset(new base::ElapsedTimer()); |
| ShowButton(_secondaryButton); |
| [_activityIndicator startAnimating]; |
| |
| [self signIntoIdentity:self.selectedIdentity]; |
| } |
| |
| - (void)reloadSigninPendingState { |
| BOOL isSelectedIdentityValid = ios::GetChromeBrowserProvider() |
| ->GetChromeIdentityService() |
| ->IsValidIdentity(self.selectedIdentity); |
| if (!isSelectedIdentityValid) { |
| [_authenticationFlow cancelAndDismiss]; |
| [self changeToState:IDENTITY_PICKER_STATE]; |
| } |
| } |
| |
| - (void)leaveSigninPendingState:(AuthenticationState)nextState { |
| if (!_pendingStateTimer) { |
| // The controller is already leaving the signin pending state, simply update |
| // the new state to take into account the last request only. |
| _activityIndicatorNextState = nextState; |
| return; |
| } |
| |
| _activityIndicatorNextState = nextState; |
| _activityIndicator.delegate = self; |
| |
| base::TimeDelta remainingTime = |
| base::TimeDelta::FromMilliseconds(kMinimunPendingStateDurationMs) - |
| _pendingStateTimer->Elapsed(); |
| _pendingStateTimer.reset(); |
| |
| if (remainingTime.InMilliseconds() < 0) { |
| [_activityIndicator stopAnimating]; |
| } else { |
| // If the signin pending state is too fast, the screen will appear to |
| // flicker. Make sure to animate for at least |
| // |kMinimunPendingStateDurationMs| milliseconds. |
| __weak ChromeSigninViewController* weakSelf = self; |
| ProceduralBlock completionBlock = ^{ |
| ChromeSigninViewController* strongSelf = weakSelf; |
| if (!strongSelf) |
| return; |
| [strongSelf->_activityIndicator stopAnimating]; |
| strongSelf->_leavingPendingStateTimer.reset(); |
| }; |
| _leavingPendingStateTimer.reset(new base::Timer(false, false)); |
| _leavingPendingStateTimer->Start(FROM_HERE, remainingTime, |
| base::BindBlockArc(completionBlock)); |
| } |
| } |
| |
| #pragma mark - IdentitySelectedState |
| |
| - (void)enterIdentitySelectedState { |
| _confirmationVC = [[SigninConfirmationViewController alloc] |
| initWithIdentity:self.selectedIdentity]; |
| _confirmationVC.delegate = self; |
| |
| _hasConfirmationScreenReachedBottom = NO; |
| [_confirmationVC willMoveToParentViewController:self]; |
| [self addChildViewController:_confirmationVC]; |
| _confirmationVC.view.frame = self.view.bounds; |
| [self.view insertSubview:_confirmationVC.view belowSubview:_primaryButton]; |
| [_confirmationVC didMoveToParentViewController:self]; |
| |
| [self setSecondaryButtonStyling:_primaryButton]; |
| NSString* primaryButtonTitle = l10n_util::GetNSString( |
| IDS_IOS_ACCOUNT_CONSISTENCY_CONFIRMATION_SCROLL_BUTTON); |
| [_primaryButton setTitle:primaryButtonTitle forState:UIControlStateNormal]; |
| [_primaryButton setImage:[UIImage imageNamed:@"signin_confirmation_more"] |
| forState:UIControlStateNormal]; |
| NSString* secondaryButtonTitle = l10n_util::GetNSString( |
| IDS_IOS_ACCOUNT_CONSISTENCY_CONFIRMATION_UNDO_BUTTON); |
| [_secondaryButton setTitle:secondaryButtonTitle |
| forState:UIControlStateNormal]; |
| [self.view setNeedsLayout]; |
| |
| HideButton(_primaryButton); |
| HideButton(_secondaryButton); |
| [UIView animateWithDuration:kAnimationDuration |
| animations:^{ |
| ShowButton(_primaryButton); |
| ShowButton(_secondaryButton); |
| } |
| completion:nil]; |
| } |
| |
| - (void)reloadIdentitySelectedState { |
| BOOL isSelectedIdentityValid = ios::GetChromeBrowserProvider() |
| ->GetChromeIdentityService() |
| ->IsValidIdentity(self.selectedIdentity); |
| if (!isSelectedIdentityValid) { |
| [self changeToState:IDENTITY_PICKER_STATE]; |
| return; |
| } |
| } |
| |
| - (void)leaveIdentitySelectedState:(AuthenticationState)nextState { |
| [_confirmationVC willMoveToParentViewController:nil]; |
| [[_confirmationVC view] removeFromSuperview]; |
| [_confirmationVC removeFromParentViewController]; |
| _confirmationVC = nil; |
| [self setPrimaryButtonStyling:_primaryButton]; |
| HideButton(_primaryButton); |
| HideButton(_secondaryButton); |
| [self undoSignIn]; |
| [self enterState:nextState]; |
| } |
| |
| #pragma mark - UIViewController |
| |
| - (void)viewDidLoad { |
| [super viewDidLoad]; |
| self.view.backgroundColor = self.backgroundColor; |
| |
| _primaryButton = [[MDCFlatButton alloc] init]; |
| [self setPrimaryButtonStyling:_primaryButton]; |
| [_primaryButton addTarget:self |
| action:@selector(onPrimaryButtonPressed:) |
| forControlEvents:UIControlEventTouchUpInside]; |
| HideButton(_primaryButton); |
| [self.view addSubview:_primaryButton]; |
| |
| _secondaryButton = [[MDCFlatButton alloc] init]; |
| [self setSecondaryButtonStyling:_secondaryButton]; |
| [_secondaryButton addTarget:self |
| action:@selector(onSecondaryButtonPressed:) |
| forControlEvents:UIControlEventTouchUpInside]; |
| [_secondaryButton setAccessibilityIdentifier:@"ic_close"]; |
| HideButton(_secondaryButton); |
| [self.view addSubview:_secondaryButton]; |
| |
| _activityIndicator = [[MDCActivityIndicator alloc] initWithFrame:CGRectZero]; |
| [_activityIndicator setDelegate:self]; |
| [_activityIndicator setStrokeWidth:3]; |
| [_activityIndicator |
| setCycleColors:@[ [[MDCPalette cr_bluePalette] tint500] ]]; |
| [self.view addSubview:_activityIndicator]; |
| |
| _gradientView = [[UIView alloc] initWithFrame:CGRectZero]; |
| _gradientLayer = [CAGradientLayer layer]; |
| [_gradientView setUserInteractionEnabled:NO]; |
| _gradientLayer.colors = [NSArray |
| arrayWithObjects:(id)[[UIColor colorWithWhite:1 alpha:0] CGColor], |
| (id)[self.backgroundColor CGColor], nil]; |
| [[_gradientView layer] insertSublayer:_gradientLayer atIndex:0]; |
| [self.view addSubview:_gradientView]; |
| } |
| |
| - (void)viewWillAppear:(BOOL)animated { |
| [super viewWillAppear:animated]; |
| |
| if (_currentState != NULL_STATE) { |
| return; |
| } |
| if (_autoSignIn) { |
| [self enterState:SIGNIN_PENDING_STATE]; |
| } else { |
| [self enterState:IDENTITY_PICKER_STATE]; |
| } |
| } |
| |
| #pragma mark - Events |
| |
| - (void)onPrimaryButtonPressed:(id)sender { |
| switch (_currentState) { |
| case NULL_STATE: |
| NOTREACHED(); |
| return; |
| case IDENTITY_PICKER_STATE: { |
| if (_interactionManager) { |
| // Adding an account is ongoing, ignore the button press. |
| return; |
| } |
| ChromeIdentity* selectedIdentity = [_accountSelectorVC selectedIdentity]; |
| [self setSelectedIdentity:selectedIdentity]; |
| if (selectedIdentity) { |
| [self changeToState:SIGNIN_PENDING_STATE]; |
| } else { |
| [self openAuthenticationDialogAddIdentity]; |
| } |
| return; |
| } |
| case SIGNIN_PENDING_STATE: |
| NOTREACHED(); |
| return; |
| case IDENTITY_SELECTED_STATE: |
| if (_hasConfirmationScreenReachedBottom) { |
| [self acceptSignInAndCommitSyncChanges]; |
| } else { |
| [_confirmationVC scrollToBottom]; |
| } |
| return; |
| case DONE_STATE: |
| // Ignored |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| - (void)onSecondaryButtonPressed:(id)sender { |
| switch (_currentState) { |
| case NULL_STATE: |
| NOTREACHED(); |
| return; |
| case IDENTITY_PICKER_STATE: |
| if (!_didFinishSignIn) { |
| _didFinishSignIn = YES; |
| [_delegate didSkipSignIn:self]; |
| } |
| return; |
| case SIGNIN_PENDING_STATE: |
| base::RecordAction(base::UserMetricsAction("Signin_Undo_Signin")); |
| [_authenticationFlow cancelAndDismiss]; |
| [self undoSignIn]; |
| [self changeToState:IDENTITY_PICKER_STATE]; |
| return; |
| case IDENTITY_SELECTED_STATE: |
| base::RecordAction(base::UserMetricsAction("Signin_Undo_Signin")); |
| [self changeToState:IDENTITY_PICKER_STATE]; |
| return; |
| case DONE_STATE: |
| // Ignored |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| #pragma mark - ChromeIdentityServiceObserver |
| |
| - (void)onIdentityListChanged { |
| switch (_currentState) { |
| case NULL_STATE: |
| case DONE_STATE: |
| return; |
| case IDENTITY_PICKER_STATE: |
| [self reloadIdentityPickerState]; |
| return; |
| case SIGNIN_PENDING_STATE: |
| [self reloadSigninPendingState]; |
| return; |
| case IDENTITY_SELECTED_STATE: |
| [self reloadIdentitySelectedState]; |
| return; |
| } |
| } |
| |
| - (void)onChromeIdentityServiceWillBeDestroyed { |
| _identityServiceObserver.reset(); |
| } |
| |
| #pragma mark - Layout |
| |
| - (void)viewDidLayoutSubviews { |
| [super viewDidLayoutSubviews]; |
| |
| AuthenticationViewConstants constants; |
| if ([self.traitCollection horizontalSizeClass] == |
| UIUserInterfaceSizeClassRegular) { |
| constants = kRegularConstants; |
| } else { |
| constants = kCompactConstants; |
| } |
| |
| [self layoutButtons:constants]; |
| |
| CGSize viewSize = self.view.bounds.size; |
| CGFloat collectionViewHeight = viewSize.height - |
| _primaryButton.frame.size.height - |
| constants.ButtonVerticalPadding; |
| CGRect collectionViewFrame = |
| CGRectMake(0, 0, viewSize.width, collectionViewHeight); |
| [_accountSelectorVC.view setFrame:collectionViewFrame]; |
| [_confirmationVC.view setFrame:collectionViewFrame]; |
| |
| // Layout the gradient view right above the buttons. |
| CGFloat gradientOriginY = CGRectGetHeight(self.view.bounds) - |
| constants.ButtonVerticalPadding - |
| constants.ButtonHeight - constants.GradientHeight; |
| [_gradientView setFrame:CGRectMake(0, gradientOriginY, viewSize.width, |
| constants.GradientHeight)]; |
| [_gradientLayer setFrame:[_gradientView bounds]]; |
| |
| // Layout the activity indicator in the center of the view. |
| CGRect bounds = self.view.bounds; |
| [_activityIndicator |
| setCenter:CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds))]; |
| } |
| |
| - (void)layoutButtons:(const AuthenticationViewConstants&)constants { |
| [_primaryButton titleLabel].font = |
| [[MDCTypography fontLoader] mediumFontOfSize:constants.SecondaryFontSize]; |
| [_secondaryButton titleLabel].font = |
| [[MDCTypography fontLoader] mediumFontOfSize:constants.SecondaryFontSize]; |
| |
| LayoutRect primaryButtonLayout = LayoutRectZero; |
| primaryButtonLayout.boundingWidth = CGRectGetWidth(self.view.bounds); |
| primaryButtonLayout.size = [_primaryButton |
| sizeThatFits:CGSizeMake(CGFLOAT_MAX, constants.ButtonHeight)]; |
| primaryButtonLayout.position.leading = primaryButtonLayout.boundingWidth - |
| primaryButtonLayout.size.width - |
| constants.ButtonHorizontalPadding; |
| primaryButtonLayout.position.originY = CGRectGetHeight(self.view.bounds) - |
| constants.ButtonVerticalPadding - |
| constants.ButtonHeight; |
| primaryButtonLayout.size.height = constants.ButtonHeight; |
| [_primaryButton setFrame:LayoutRectGetRect(primaryButtonLayout)]; |
| |
| UIEdgeInsets imageInsets = UIEdgeInsetsZero; |
| UIEdgeInsets titleInsets = UIEdgeInsetsZero; |
| if ([_primaryButton imageForState:UIControlStateNormal]) { |
| // Title label should be leading, followed by the image (with some padding). |
| CGFloat paddedImageWidth = |
| [_primaryButton imageView].frame.size.width + kMoreButtonPadding; |
| CGFloat paddedTitleWidth = |
| [_primaryButton titleLabel].frame.size.width + kMoreButtonPadding; |
| imageInsets = UIEdgeInsetsMake(0, paddedTitleWidth, 0, -paddedTitleWidth); |
| titleInsets = UIEdgeInsetsMake(0, -paddedImageWidth, 0, paddedImageWidth); |
| } |
| [_primaryButton setImageEdgeInsets:imageInsets]; |
| [_primaryButton setTitleEdgeInsets:titleInsets]; |
| |
| LayoutRect secondaryButtonLayout = primaryButtonLayout; |
| secondaryButtonLayout.size = [_secondaryButton |
| sizeThatFits:CGSizeMake(CGFLOAT_MAX, constants.ButtonHeight)]; |
| secondaryButtonLayout.position.leading = constants.ButtonHorizontalPadding; |
| secondaryButtonLayout.size.height = constants.ButtonHeight; |
| [_secondaryButton setFrame:LayoutRectGetRect(secondaryButtonLayout)]; |
| } |
| |
| #pragma mark - MDCActivityIndicatorDelegate |
| |
| - (void)activityIndicatorAnimationDidFinish: |
| (MDCActivityIndicator*)activityIndicator { |
| DCHECK_EQ(SIGNIN_PENDING_STATE, _currentState); |
| DCHECK_EQ(_activityIndicator, activityIndicator); |
| |
| // The activity indicator is only used in the signin pending state. Its |
| // animation is stopped only when leaving the state. |
| if (_activityIndicatorNextState != NULL_STATE) { |
| [self enterState:_activityIndicatorNextState]; |
| _activityIndicatorNextState = NULL_STATE; |
| } |
| } |
| |
| #pragma mark - ChromeIdentityInteractionManagerDelegate |
| |
| - (void)interactionManager:(ChromeIdentityInteractionManager*)interactionManager |
| presentViewController:(UIViewController*)viewController |
| animated:(BOOL)animated |
| completion:(ProceduralBlock)completion { |
| [self presentViewController:viewController |
| animated:animated |
| completion:completion]; |
| } |
| |
| - (void)interactionManager:(ChromeIdentityInteractionManager*)interactionManager |
| dismissViewControllerAnimated:(BOOL)animated |
| completion:(ProceduralBlock)completion { |
| [self dismissViewControllerAnimated:animated completion:completion]; |
| } |
| |
| #pragma mark - SigninAccountSelectorViewControllerDelegate |
| |
| - (void)accountSelectorControllerDidSelectAddAccount: |
| (SigninAccountSelectorViewController*)accountSelectorController { |
| DCHECK_EQ(_accountSelectorVC, accountSelectorController); |
| if (_ongoingStateChange) { |
| return; |
| } |
| [self openAuthenticationDialogAddIdentity]; |
| } |
| |
| #pragma mark - SigninConfirmationViewControllerDelegate |
| |
| // Callback for when a link in the label is pressed. |
| - (void)signinConfirmationControllerDidTapSettingsLink: |
| (SigninConfirmationViewController*)controller { |
| DCHECK_EQ(_confirmationVC, controller); |
| |
| GenericChromeCommand* command = |
| [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_ACCOUNTS_SETTINGS]; |
| [self acceptSignInAndExecuteCommand:command]; |
| } |
| |
| - (void)signinConfirmationControllerDidReachBottom: |
| (SigninConfirmationViewController*)controller { |
| if (_hasConfirmationScreenReachedBottom) { |
| return; |
| } |
| _hasConfirmationScreenReachedBottom = YES; |
| [self setPrimaryButtonStyling:_primaryButton]; |
| [_primaryButton setTitle:[self acceptSigninButtonTitle] |
| forState:UIControlStateNormal]; |
| [_primaryButton setImage:nil forState:UIControlStateNormal]; |
| [self.view setNeedsLayout]; |
| } |
| |
| @end |