Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(70)

Unified Diff: ios/chrome/browser/ui/authentication/chrome_signin_view_controller.mm

Issue 2586993002: Upstream Chrome on iOS source code [3/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698