Index: ios/chrome/browser/ui/authentication/chrome_signin_view_controller.mm |
diff --git a/ios/chrome/browser/ui/authentication/chrome_signin_view_controller.mm b/ios/chrome/browser/ui/authentication/chrome_signin_view_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..85114102dc353d949e6bfbcd39b18293cdbf4f95 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/authentication/chrome_signin_view_controller.mm |
@@ -0,0 +1,948 @@ |
+// 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/ios/weak_nsobject.h" |
+#import "base/mac/bind_objc_block.h" |
+#import "base/mac/scoped_nsobject.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_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h" |
+#import "ui/base/l10n/l10n_util.h" |
+ |
+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 |
+ 16, // ButtonHorizontalPadding |
+ 16, // ButtonVerticalPadding |
+}; |
+ |
+const AuthenticationViewConstants kRegularConstants = { |
+ 1.5 * kCompactConstants.PrimaryFontSize, |
+ 1.5 * kCompactConstants.SecondaryFontSize, |
+ kCompactConstants.GradientHeight, |
+ 1.5 * kCompactConstants.ButtonHeight, |
+ 1.5 * kCompactConstants.ButtonHorizontalPadding, |
+ 1.5 * 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, retain) ChromeIdentity* selectedIdentity; |
+ |
+@end |
+ |
+@implementation ChromeSigninViewController { |
+ ios::ChromeBrowserState* _browserState; // weak |
+ base::WeakNSProtocol<id<ChromeSigninViewControllerDelegate>> _delegate; |
+ std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver; |
+ base::scoped_nsobject<ChromeIdentity> _selectedIdentity; |
+ |
+ // Authentication |
+ base::scoped_nsobject<AlertCoordinator> _alertCoordinator; |
+ base::scoped_nsobject<AuthenticationFlow> _authenticationFlow; |
+ BOOL _addedAccount; |
+ BOOL _autoSignIn; |
+ BOOL _didSignIn; |
+ BOOL _didAcceptSignIn; |
+ BOOL _didFinishSignIn; |
+ BOOL _isPresentedOnSettings; |
+ signin_metrics::AccessPoint _signInAccessPoint; |
+ base::scoped_nsobject<ChromeIdentityInteractionManager> _interactionManager; |
+ |
+ // Basic state. |
+ AuthenticationState _currentState; |
+ BOOL _ongoingStateChange; |
+ base::scoped_nsobject<MDCActivityIndicator> _activityIndicator; |
+ base::scoped_nsobject<MDCButton> _primaryButton; |
+ base::scoped_nsobject<MDCButton> _secondaryButton; |
+ base::scoped_nsobject<UIView> _gradientView; |
+ base::scoped_nsobject<CAGradientLayer> _gradientLayer; |
+ |
+ // Identity picker state. |
+ base::scoped_nsobject<SigninAccountSelectorViewController> _accountSelectorVC; |
+ |
+ // Signin pending state. |
+ AuthenticationState _activityIndicatorNextState; |
+ std::unique_ptr<base::ElapsedTimer> _pendingStateTimer; |
+ std::unique_ptr<base::Timer> _leavingPendingStateTimer; |
+ |
+ // Identity selected state. |
+ base::scoped_nsobject<SigninConfirmationViewController> _confirmationVC; |
+ BOOL _hasConfirmationScreenReachedBottom; |
+} |
+ |
+@synthesize shouldClearData = _shouldClearData; |
+ |
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState |
+ isPresentedOnSettings:(BOOL)isPresentedOnSettings |
+ signInAccessPoint:(signin_metrics::AccessPoint)accessPoint |
+ signInIdentity:(ChromeIdentity*)identity { |
+ self = [super init]; |
+ if (self) { |
+ _browserState = browserState; |
+ _isPresentedOnSettings = isPresentedOnSettings; |
+ _signInAccessPoint = accessPoint; |
+ |
+ 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]; |
+ [super dealloc]; |
+} |
+ |
+- (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(_signInAccessPoint); |
+ _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; |
+} |
+ |
+- (void)setDelegate:(id<ChromeSigninViewControllerDelegate>)delegate { |
+ _delegate.reset(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.reset([identity retain]); |
+} |
+ |
+- (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.reset( |
+ [ios_internal::ErrorCoordinator(error, nil, self) retain]); |
+ [_alertCoordinator start]; |
+} |
+ |
+- (void)signIntoIdentity:(ChromeIdentity*)identity { |
+ [_delegate willStartSignIn:self]; |
+ DCHECK(!_authenticationFlow); |
+ _authenticationFlow.reset([[AuthenticationFlow alloc] |
+ initWithBrowserState:_browserState |
+ identity:identity |
+ shouldClearData:_shouldClearData |
+ postSignInAction:POST_SIGNIN_ACTION_NONE |
+ presentingViewController:self]); |
+ base::WeakNSObject<ChromeSigninViewController> weakSelf(self); |
+ [_authenticationFlow startSignInWithCompletion:^(BOOL success) { |
+ [weakSelf onAccountSigninCompletion:success]; |
+ }]; |
+} |
+ |
+- (void)openAuthenticationDialogAddIdentity { |
+ DCHECK(!_interactionManager); |
+ _interactionManager = |
+ ios::GetChromeBrowserProvider() |
+ ->GetChromeIdentityService() |
+ ->NewChromeIdentityInteractionManager(_browserState, self); |
+ base::WeakNSObject<ChromeSigninViewController> weakSelf(self); |
+ SigninCompletionCallback completion = |
+ ^(ChromeIdentity* identity, NSError* error) { |
+ base::scoped_nsobject<ChromeSigninViewController> strongSelf( |
+ [weakSelf retain]); |
+ if (!strongSelf || !strongSelf.get()->_interactionManager) |
+ return; |
+ // The ChromeIdentityInteractionManager is not used anymore at this |
+ // point. |
+ strongSelf.get()->_interactionManager.reset(); |
+ |
+ if (error) { |
+ [strongSelf handleAuthenticationError:error]; |
+ return; |
+ } |
+ strongSelf.get()->_addedAccount = YES; |
+ [strongSelf onIdentityListChanged]; |
+ [strongSelf setSelectedIdentity:identity]; |
+ [strongSelf changeToState:SIGNIN_PENDING_STATE]; |
+ }; |
+ [_delegate willStartAddAccount:self]; |
+ [_interactionManager addAccountWithCompletion:completion]; |
+} |
+ |
+- (void)onAccountSigninCompletion:(BOOL)success { |
+ _authenticationFlow.reset(); |
+ 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.reset([[SigninAccountSelectorViewController alloc] init]); |
+ _accountSelectorVC.get().delegate = self; |
+ [_accountSelectorVC willMoveToParentViewController:self]; |
+ [self addChildViewController:_accountSelectorVC]; |
+ _accountSelectorVC.get().view.frame = self.view.bounds; |
+ [self.view insertSubview:_accountSelectorVC.get().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.reset(); |
+ [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.get().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. |
+ base::WeakNSObject<ChromeSigninViewController> weakSelf(self); |
+ ProceduralBlock completionBlock = ^{ |
+ base::scoped_nsobject<ChromeSigninViewController> strongSelf( |
+ [weakSelf retain]); |
+ if (!strongSelf) |
+ return; |
+ [strongSelf.get()->_activityIndicator stopAnimating]; |
+ strongSelf.get()->_leavingPendingStateTimer.reset(); |
+ }; |
+ _leavingPendingStateTimer.reset(new base::Timer(false, false)); |
+ _leavingPendingStateTimer->Start(FROM_HERE, remainingTime, |
+ base::BindBlock(completionBlock)); |
+ } |
+} |
+ |
+#pragma mark - IdentitySelectedState |
+ |
+- (void)enterIdentitySelectedState { |
+ _confirmationVC.reset([[SigninConfirmationViewController alloc] |
+ initWithIdentity:self.selectedIdentity]); |
+ _confirmationVC.get().delegate = self; |
+ |
+ _hasConfirmationScreenReachedBottom = NO; |
+ [_confirmationVC willMoveToParentViewController:self]; |
+ [self addChildViewController:_confirmationVC]; |
+ _confirmationVC.get().view.frame = self.view.bounds; |
+ [self.view insertSubview:_confirmationVC.get().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.reset(); |
+ [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.reset([[MDCFlatButton alloc] init]); |
+ [self setPrimaryButtonStyling:_primaryButton]; |
+ [_primaryButton addTarget:self |
+ action:@selector(onPrimaryButtonPressed:) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ HideButton(_primaryButton); |
+ [self.view addSubview:_primaryButton]; |
+ |
+ _secondaryButton.reset([[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.reset( |
+ [[MDCActivityIndicator alloc] initWithFrame:CGRectZero]); |
+ [_activityIndicator setDelegate:self]; |
+ [_activityIndicator setStrokeWidth:3]; |
+ [_activityIndicator |
+ setCycleColors:@[ [[MDCPalette cr_bluePalette] tint500] ]]; |
+ [self.view addSubview:_activityIndicator]; |
+ |
+ _gradientView.reset([[UIView alloc] initWithFrame:CGRectZero]); |
+ _gradientLayer.reset([[CAGradientLayer layer] retain]); |
+ _gradientLayer.get().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.get().frame.size.height - |
+ constants.ButtonVerticalPadding; |
+ CGRect collectionViewFrame = |
+ CGRectMake(0, 0, viewSize.width, collectionViewHeight); |
+ [_accountSelectorVC.get().view setFrame:collectionViewFrame]; |
+ [_confirmationVC.get().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 = [[MDFRobotoFontLoader sharedInstance] |
+ mediumFontOfSize:constants.SecondaryFontSize]; |
+ [_secondaryButton titleLabel].font = [[MDFRobotoFontLoader sharedInstance] |
+ 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); |
+ |
+ base::scoped_nsobject<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 |