chromium/ios/chrome/browser/first_run/ui_bundled/signin/signin_screen_view_controller.mm

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/first_run/ui_bundled/signin/signin_screen_view_controller.h"

#import "base/feature_list.h"
#import "base/notreached.h"
#import "base/strings/sys_string_conversions.h"
#import "components/signin/public/base/signin_switches.h"
#import "ios/chrome/browser/shared/public/commands/tos_commands.h"
#import "ios/chrome/browser/shared/ui/elements/activity_overlay_view.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/ui/authentication/views/identity_button_control.h"
#import "ios/chrome/browser/first_run/ui_bundled/first_run_constants.h"
#import "ios/chrome/browser/ui/settings/elements/enterprise_info_popover_view_controller.h"
#import "ios/chrome/common/string_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/elements/popover_label_view_controller.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"

namespace {

// Top margin for the managed icon in the enteprised image view
constexpr CGFloat kTopMarginForManagedIcon = 16.;

// Enterprise icon in the bottom view.
NSString* const kEnterpriseIconName = @"enterprise_icon";

}  // namespace

@interface SigninScreenViewController ()

// Button controlling the display of the selected identity.
@property(nonatomic, strong) IdentityButtonControl* identityControl;
// The string to be displayed in the "Continue" button to personalize it.
// Usually the given name, or the email address if no given name.
@property(nonatomic, copy) NSString* personalizedButtonPrompt;
// Scrim displayed above the view when the UI is disabled.
@property(nonatomic, strong) ActivityOverlayView* overlay;

@end

@implementation SigninScreenViewController

@dynamic delegate;
@synthesize hasPlatformPolicies = _hasPlatformPolicies;
@synthesize screenIntent = _screenIntent;
@synthesize signinStatus = _signinStatus;
@synthesize syncEnabled = _syncEnabled;

#pragma mark - UIViewController

- (void)viewDidLoad {
  self.view.accessibilityIdentifier =
      first_run::kFirstRunSignInScreenAccessibilityIdentifier;
  self.bannerSize = BannerImageSizeType::kStandard;
  self.scrollToEndMandatory = YES;
  self.readMoreString =
      l10n_util::GetNSString(IDS_IOS_FIRST_RUN_SCREEN_READ_MORE);

  // Set banner.
#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
  self.bannerName = kChromeSigninBannerImage;
#else
  self.bannerName = kChromiumSigninBannerImage;
#endif

  // Set `self.titleText` and `self.subtitleText`.
  switch (self.signinStatus) {
    case SigninScreenConsumerSigninStatusAvailable: {
      switch (self.screenIntent) {
        case SigninScreenConsumerScreenIntentSigninOnly:
          // Use in the context of the upgrade promo dialog.
          self.titleText =
              l10n_util::GetNSString(IDS_IOS_UNO_UPGRADE_PROMO_SIGNIN_TITLE);
          self.subtitleText =
              self.syncEnabled
                  ? l10n_util::GetNSString(
                        IDS_IOS_UNO_UPGRADE_PROMO_SIGNIN_SUBTITLE)
                  : l10n_util::GetNSString(
                        IDS_IOS_UNO_UPGRADE_PROMO_SIGNIN_SUBTITLE_SYNC_DISABLED);

          break;
        case SigninScreenConsumerScreenIntentWelcomeAndSignin:
        case SigninScreenConsumerScreenIntentWelcomeWithoutUMAAndSignin:
          // Use in the context of the FRE dialog.
          self.titleText =
              l10n_util::GetNSString(IDS_IOS_FIRST_RUN_SIGNIN_TITLE);
          self.subtitleText =
              self.syncEnabled
                  ? l10n_util::GetNSString(
                        IDS_IOS_FIRST_RUN_SIGNIN_BENEFITS_SUBTITLE_SHORT)
                  : l10n_util::GetNSString(
                        IDS_IOS_FIRST_RUN_SIGNIN_SUBTITLE_SHORT);
          break;
      }
      break;
    }
    case SigninScreenConsumerSigninStatusForced: {
      self.titleText =
          l10n_util::GetNSString(IDS_IOS_FIRST_RUN_SIGNIN_TITLE_SIGNIN_FORCED);
      self.subtitleText = l10n_util::GetNSString(
          IDS_IOS_FIRST_RUN_SIGNIN_SUBTITLE_SIGNIN_FORCED);
      break;
    }
    case SigninScreenConsumerSigninStatusDisabled: {
      UIUserInterfaceIdiom idiom =
          [[UIDevice currentDevice] userInterfaceIdiom];
      if (idiom == UIUserInterfaceIdiomPad) {
        self.titleText =
            l10n_util::GetNSString(IDS_IOS_FIRST_RUN_WELCOME_SCREEN_TITLE_IPAD);
      } else {
        self.titleText = l10n_util::GetNSString(
            IDS_IOS_FIRST_RUN_WELCOME_SCREEN_TITLE_IPHONE);
      }
      self.subtitleText =
          l10n_util::GetNSString(IDS_IOS_FIRST_RUN_WELCOME_SCREEN_SUBTITLE);
      break;
    }
  }
  [self generateDisclaimer];

  // Add `self.identityControl` if needed.
  if (self.signinStatus != SigninScreenConsumerSigninStatusDisabled) {
    [self.specificContentView addSubview:self.identityControl];

    [NSLayoutConstraint activateConstraints:@[
      [self.identityControl.topAnchor
          constraintEqualToAnchor:self.specificContentView.topAnchor],
      [self.identityControl.centerXAnchor
          constraintEqualToAnchor:self.specificContentView.centerXAnchor],
      [self.identityControl.widthAnchor
          constraintEqualToAnchor:self.specificContentView.widthAnchor],
      [self.identityControl.bottomAnchor
          constraintLessThanOrEqualToAnchor:self.specificContentView
                                                .bottomAnchor],
    ]];
  }

  // Add enterprise image view.
  if (self.hasPlatformPolicies) {
    NSLayoutYAxisAnchor* topAnchorForEnterpriseIcon =
        self.signinStatus == SigninScreenConsumerSigninStatusDisabled
            ? self.specificContentView.topAnchor
            : self.identityControl.bottomAnchor;
    UIImage* image = [UIImage imageNamed:kEnterpriseIconName];
    UIImageView* enterpriseImageView =
        [[UIImageView alloc] initWithImage:image];
    enterpriseImageView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.specificContentView addSubview:enterpriseImageView];
    [NSLayoutConstraint activateConstraints:@[
      [enterpriseImageView.topAnchor
          constraintGreaterThanOrEqualToAnchor:topAnchorForEnterpriseIcon
                                      constant:kTopMarginForManagedIcon],
      [enterpriseImageView.bottomAnchor
          constraintEqualToAnchor:self.specificContentView.bottomAnchor],
      [enterpriseImageView.centerXAnchor
          constraintEqualToAnchor:self.specificContentView.centerXAnchor],
      [enterpriseImageView.widthAnchor
          constraintLessThanOrEqualToAnchor:self.specificContentView
                                                .widthAnchor],
    ]];
  }

  // Set primary button if sign-in is disabled. For other cases, the primary
  // button is set with `setSelectedIdentityUserName:email:givenName:avatar:`
  // or `noIdentityAvailable`.
  DCHECK(self.primaryActionString ||
         self.signinStatus == SigninScreenConsumerSigninStatusDisabled);
  if (self.signinStatus == SigninScreenConsumerSigninStatusDisabled) {
    self.primaryActionString =
        l10n_util::GetNSString(IDS_IOS_FIRST_RUN_SIGNIN_CONTINUE);
  }
  // Set secondary button.
  if (self.signinStatus == SigninScreenConsumerSigninStatusAvailable) {
    self.secondaryActionString =
        l10n_util::GetNSString(IDS_IOS_FIRST_RUN_SIGNIN_DONT_SIGN_IN);
  }

  // Call super after setting up the strings and others, as required per super
  // class.
  [super viewDidLoad];
}

#pragma mark - Properties

- (IdentityButtonControl*)identityControl {
  if (!_identityControl) {
    _identityControl = [[IdentityButtonControl alloc] initWithFrame:CGRectZero];
    _identityControl.translatesAutoresizingMaskIntoConstraints = NO;
    [_identityControl addTarget:self
                         action:@selector(identityButtonControlTapped:forEvent:)
               forControlEvents:UIControlEventTouchUpInside];

    // Setting the content hugging priority isn't working, so creating a
    // low-priority constraint to make sure that the view is as small as
    // possible.
    NSLayoutConstraint* heightConstraint =
        [_identityControl.heightAnchor constraintEqualToConstant:0];
    heightConstraint.priority = UILayoutPriorityDefaultLow - 1;
    heightConstraint.active = YES;
  }
  return _identityControl;
}

- (ActivityOverlayView*)overlay {
  if (!_overlay) {
    _overlay = [[ActivityOverlayView alloc] init];
    _overlay.translatesAutoresizingMaskIntoConstraints = NO;
  }
  return _overlay;
}

#pragma mark - Private

// Generates the footer string.
- (void)generateDisclaimer {
  NSMutableArray<NSString*>* array = [NSMutableArray array];
  NSMutableArray<NSURL*>* urls = [NSMutableArray array];
  if (self.hasPlatformPolicies) {
    [array addObject:l10n_util::GetNSString(
                         IDS_IOS_FIRST_RUN_WELCOME_SCREEN_BROWSER_MANAGED)];
  }
  switch (self.screenIntent) {
    case SigninScreenConsumerScreenIntentSigninOnly: {
      break;
    }
    case SigninScreenConsumerScreenIntentWelcomeAndSignin: {
      [array addObject:l10n_util::GetNSString(
                           IDS_IOS_FIRST_RUN_WELCOME_SCREEN_TERMS_OF_SERVICE)];
      [urls addObject:[NSURL URLWithString:first_run::kTermsOfServiceURL]];
      [array addObject:l10n_util::GetNSString(
                           IDS_IOS_FIRST_RUN_WELCOME_SCREEN_METRIC_REPORTING)];
      [urls addObject:[NSURL URLWithString:first_run::kMetricReportingURL]];
      break;
    }
    case SigninScreenConsumerScreenIntentWelcomeWithoutUMAAndSignin: {
      [array addObject:l10n_util::GetNSString(
                           IDS_IOS_FIRST_RUN_WELCOME_SCREEN_TERMS_OF_SERVICE)];
      [urls addObject:[NSURL URLWithString:first_run::kTermsOfServiceURL]];
      break;
    }
  }
  self.disclaimerText = [array componentsJoinedByString:@" "];
  self.disclaimerURLs = urls;
}

// Callback for `identityControl`.
- (void)identityButtonControlTapped:(id)sender forEvent:(UIEvent*)event {
  UITouch* touch = event.allTouches.anyObject;
  [self.delegate showAccountPickerFromPoint:[touch locationInView:nil]];
}

// Updates the UI to adapt for `identityAvailable` or not.
- (void)updateUIForIdentityAvailable:(BOOL)identityAvailable {
  self.identityControl.hidden = !identityAvailable;
  if (identityAvailable) {
    self.primaryActionString = l10n_util::GetNSStringF(
        IDS_IOS_FIRST_RUN_SIGNIN_CONTINUE_AS,
        base::SysNSStringToUTF16(self.personalizedButtonPrompt));
  } else {
    self.primaryActionString =
        l10n_util::GetNSString(IDS_IOS_FIRST_RUN_SIGNIN_SIGN_IN_ACTION);
  }
}

#pragma mark - SigninScreenConsumer

- (void)setSelectedIdentityUserName:(NSString*)userName
                              email:(NSString*)email
                          givenName:(NSString*)givenName
                             avatar:(UIImage*)avatar {
  DCHECK_NE(self.signinStatus, SigninScreenConsumerSigninStatusDisabled);
  DCHECK(email);
  DCHECK(avatar);
  self.personalizedButtonPrompt = givenName ? givenName : email;
  [self updateUIForIdentityAvailable:YES];
  [self.identityControl setIdentityName:userName email:email];
  [self.identityControl setIdentityAvatar:avatar];
}

- (void)noIdentityAvailable {
  DCHECK_NE(self.signinStatus, SigninScreenConsumerSigninStatusDisabled);
  [self updateUIForIdentityAvailable:NO];
}

- (void)setUIEnabled:(BOOL)UIEnabled {
  if (base::FeatureList::IsEnabled(
          switches::kMinorModeRestrictionsForHistorySyncOptIn)) {
    // For the history sync minor mode experiment, we do not use the spinner
    // overlay. The disabled UI has a spinner in the primary button.
    self.primaryButtonSpinnerEnabled = !UIEnabled;
    self.view.userInteractionEnabled = UIEnabled;
  } else {
    // Use the spinner overlay to disable the view.
    if (UIEnabled) {
      [self.overlay removeFromSuperview];
    } else {
      [self.view addSubview:self.overlay];
      AddSameConstraints(self.view, self.overlay);
      [self.overlay.indicator startAnimating];
    }
  }
}

@end