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

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

Powered by Google App Engine
This is Rietveld 408576698