chromium/ios/chrome/browser/ui/settings/password/passwords_in_other_apps/passwords_in_other_apps_view_controller.mm

// Copyright 2021 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/ui/settings/password/passwords_in_other_apps/passwords_in_other_apps_view_controller.h"

#import "base/ios/ios_util.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/elements/instruction_view.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/ui/settings/password/passwords_in_other_apps/constants.h"
#import "ios/chrome/browser/ui/settings/password/passwords_in_other_apps/passwords_in_other_apps_view_controller_delegate.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
#import "ios/chrome/browser/ui/settings/utils/password_auto_fill_status_manager.h"
#import "ios/chrome/common/string_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/elements/highlight_button.h"
#import "ios/chrome/common/ui/util/button_util.h"
#import "ios/chrome/common/ui/util/image_util.h"
#import "ios/chrome/common/ui/util/pointer_interaction_util.h"
#import "ios/chrome/common/ui/util/text_view_util.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/password_auto_fill/password_auto_fill_api.h"
#import "ui/base/device_form_factor.h"
#import "ui/base/l10n/l10n_util.h"

namespace {
CGFloat const kCaptionTextViewOffset = 16;
CGFloat const kDefaultMargin = 16;
CGFloat const kTitleTopMinimumMargin = 48;
CGFloat const kTitleHorizontalMargin = 18;
CGFloat const kDefaultBannerMultiplier = 0.25;
CGFloat const kContentWidthMultiplier = 0.65;
CGFloat const kBottomMargin = 10;
CGFloat const kButtonHorizontalMargin = 4;
CGFloat const kContentOptimalWidth = 327;
}  // namespace

@interface PasswordsInOtherAppsViewController ()

// Properties set on initialization.
@property(nonatomic, copy, readonly) NSString* titleText;
@property(nonatomic, copy, readonly) NSString* subtitleText;
// Whether banner is light or dark mode
@property(nonatomic, assign) UIUserInterfaceStyle bannerStyle;
@property(nonatomic, copy, readonly) NSString* bannerName;
@property(nonatomic, copy, readonly) NSString* actionString;

// Visible UI components.
@property(nonatomic, strong) UIImageView* imageView;
@property(nonatomic, strong) UILabel* titleLabel;
@property(nonatomic, strong) UILabel* subtitleLabel;
@property(nonatomic, strong) UIView* turnOnInstructionView;
@property(nonatomic, strong) UIView* turnOffInstructionView;
@property(nonatomic, strong) HighlightButton* actionButton;

@property(nonatomic, strong) UIActivityIndicatorView* spinner;
// Views that are used to format the layout of visible UI components.
@property(nonatomic, strong) UIScrollView* scrollView;
@property(nonatomic, strong) UIView* scrollContentView;
@property(nonatomic, strong) UIView* specificContentView;

// Helper properties.
@property(nonatomic, assign, readonly) BOOL useShortInstruction;
@property(nonatomic, strong, readonly) NSArray<NSString*>* steps;
@property(nonatomic, strong)
    NSArray<NSLayoutConstraint*>* turnOnInstructionViewConstraints;
@property(nonatomic, strong)
    NSArray<NSLayoutConstraint*>* turnOffInstructionViewConstraints;
@property(nonatomic, strong) UINavigationBar* navigationBar;
@property(nonatomic, strong) UINavigationBarAppearance* defaultAppearance;

// Whether the image is currently being calculated; used to prevent infinite
// recursions caused by `viewDidLayoutSubviews`.
@property(nonatomic, assign) BOOL calculatingImageSize;
@end

@interface PasswordsInOtherAppsViewController () <
    UIAdaptivePresentationControllerDelegate,
    UITextViewDelegate>

@end

@implementation PasswordsInOtherAppsViewController

- (instancetype)init {
  self = [super initWithNibName:nil bundle:nil];
  if (self) {
    _titleText =
        l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS);
    _actionString = l10n_util::GetNSString(IDS_IOS_OPEN_SETTINGS);

    UIUserInterfaceIdiom idiom = [[UIDevice currentDevice] userInterfaceIdiom];
    if (idiom == UIUserInterfaceIdiomPad) {
      _subtitleText = l10n_util::GetNSString(
          IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_SUBTITLE_IPAD);
    } else {
      _subtitleText = l10n_util::GetNSString(
          IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_SUBTITLE_IPHONE);
    }

#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
    _bannerName = kGoogleSettingsPasswordsInOtherAppsBannerImage;
#else
    _bannerName = kChromiumSettingsPasswordsInOtherAppsBannerImage;
#endif

    self.bannerStyle = UIUserInterfaceStyleUnspecified;
  }
  return self;
}

#pragma mark - UIViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  self.view.accessibilityIdentifier =
      kPasswordsInOtherAppsViewAccessibilityIdentifier;
  self.view.backgroundColor =
      [UIColor colorNamed:kGroupedPrimaryBackgroundColor];

  // Create a layout guide for the margin between the subtitle and the screen-
  // specific content. A layout guide is needed because the margin scales with
  // the view height.
  UILayoutGuide* subtitleMarginLayoutGuide = [[UILayoutGuide alloc] init];

  self.scrollContentView = [[UIView alloc] init];
  self.scrollContentView.translatesAutoresizingMaskIntoConstraints = NO;

  [self.scrollContentView addSubview:self.imageView];

  // Add the labels.
  [self.scrollContentView addSubview:self.titleLabel];
  [self.scrollContentView addSubview:self.subtitleLabel];
  [self.view addLayoutGuide:subtitleMarginLayoutGuide];
  [self.scrollContentView addSubview:self.specificContentView];

  // Wrap everything except the action buttons in a scroll view, to support
  // dynamic types.
  [self.scrollView addSubview:self.scrollContentView];
  [self.view addSubview:self.scrollView];

  [self updateInstructionsWithCurrentPasswordAutoFillStatus];

  // Create a layout guide to constrain the width of the content, while still
  // allowing the scroll view to take the full screen width.
  UILayoutGuide* widthLayoutGuide = [[UILayoutGuide alloc] init];
  [self.view addLayoutGuide:widthLayoutGuide];

  [NSLayoutConstraint activateConstraints:@[
    // Content width layout guide constraints. Constrain the width to both at
    // least 65% of the view width, and to the full view width with margins.
    // This is to accomodate the iPad layout, which cannot be isolated out using
    // the traitCollection because of the FormSheet presentation style
    // (iPad FormSheet is considered compact).
    [widthLayoutGuide.centerXAnchor
        constraintEqualToAnchor:self.view.safeAreaLayoutGuide.centerXAnchor],
    [widthLayoutGuide.widthAnchor
        constraintGreaterThanOrEqualToAnchor:self.view.safeAreaLayoutGuide
                                                 .widthAnchor
                                  multiplier:kContentWidthMultiplier],
    [widthLayoutGuide.widthAnchor
        constraintLessThanOrEqualToAnchor:self.view.safeAreaLayoutGuide
                                              .widthAnchor
                                 constant:-2 * kDefaultMargin],

    // Scroll view constraints.
    [self.scrollView.topAnchor
        constraintEqualToAnchor:self.view
                                    .topAnchor],  // banner image should overlap
                                                  // with navigation bar.
    [self.scrollView.leadingAnchor
        constraintEqualToAnchor:self.view.leadingAnchor],
    [self.scrollView.trailingAnchor
        constraintEqualToAnchor:self.view.trailingAnchor],
    [self.scrollView.bottomAnchor
        constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],

    // Scroll content view constraints. Constrain its height to at least the
    // scroll view height, so that derived VCs can pin UI elements just above
    // the buttons.
    [self.scrollContentView.topAnchor
        constraintEqualToAnchor:self.scrollView.topAnchor],
    [self.scrollContentView.leadingAnchor
        constraintEqualToAnchor:widthLayoutGuide.leadingAnchor],
    [self.scrollContentView.trailingAnchor
        constraintEqualToAnchor:widthLayoutGuide.trailingAnchor],
    [self.scrollContentView.bottomAnchor
        constraintEqualToAnchor:self.scrollView.bottomAnchor],
    [self.scrollContentView.heightAnchor
        constraintGreaterThanOrEqualToAnchor:self.scrollView.heightAnchor],

    // Banner image constraints. Scale the image vertically so its height takes
    // a certain % of the view height while maintaining its aspect ratio. Don't
    // constrain the width so that the image extends all the way to the edges of
    // the view, outside the scrollContentView.
    [self.imageView.topAnchor
        constraintEqualToAnchor:self.scrollContentView.topAnchor],
    [self.imageView.centerXAnchor
        constraintEqualToAnchor:self.view.centerXAnchor],

    // Labels contraints. Attach them to the top of the scroll content view, and
    // center them horizontally.
    [self.titleLabel.topAnchor
        constraintEqualToAnchor:self.imageView.bottomAnchor],
    [self.titleLabel.centerXAnchor
        constraintEqualToAnchor:self.scrollContentView.centerXAnchor],
    [self.titleLabel.widthAnchor
        constraintLessThanOrEqualToAnchor:self.scrollContentView.widthAnchor
                                 constant:-2 * kTitleHorizontalMargin],
    [self.subtitleLabel.topAnchor
        constraintEqualToAnchor:self.titleLabel.bottomAnchor
                       constant:kDefaultMargin],
    [self.subtitleLabel.centerXAnchor
        constraintEqualToAnchor:self.scrollContentView.centerXAnchor],
    [self.subtitleLabel.widthAnchor
        constraintLessThanOrEqualToAnchor:self.scrollContentView.widthAnchor],

    // Constraints for the screen-specific content view. It should take the
    // remaining scroll view area, with some margins on the top and sides.
    [subtitleMarginLayoutGuide.topAnchor
        constraintEqualToAnchor:self.subtitleLabel.bottomAnchor],
    [subtitleMarginLayoutGuide.heightAnchor
        constraintEqualToConstant:kDefaultMargin],
    [self.specificContentView.topAnchor
        constraintEqualToAnchor:subtitleMarginLayoutGuide.bottomAnchor],
    [self.specificContentView.leadingAnchor
        constraintEqualToAnchor:self.scrollContentView.leadingAnchor],
    [self.specificContentView.trailingAnchor
        constraintEqualToAnchor:self.scrollContentView.trailingAnchor],
    [self.specificContentView.bottomAnchor
        constraintEqualToAnchor:self.scrollContentView.bottomAnchor],
  ]];

  // This constraint is added to enforce that the content width should be as
  // close to the optimal width as possible, within the range already activated
  // for "widthLayoutGuide.widthAnchor" previously, with a higher priority.
  // In this case, the content width in iPad and iPhone landscape mode should be
  // the safe layout width multiplied by kContentWidthMultiplier, while the
  // content width for a iPhone portrait mode should be kContentOptimalWidth.
  NSLayoutConstraint* contentLayoutGuideWidthConstraint =
      [widthLayoutGuide.widthAnchor
          constraintEqualToConstant:kContentOptimalWidth];
  contentLayoutGuideWidthConstraint.priority = UILayoutPriorityRequired - 1;
  contentLayoutGuideWidthConstraint.active = YES;

  // In iPhone landscape mode, the top image is removed. In that case, we should
  // make sure there is enough distance between the title label and the top edge
  // of the iPhone.
  // We set to priority of this constraint to be lower than the imageView's
  // compression resistance priority so it could expand if the image height is
  // bigger than this.
  NSLayoutConstraint* imageHeightConstraint = [self.imageView.heightAnchor
      constraintEqualToConstant:kTitleTopMinimumMargin];
  imageHeightConstraint.priority = UILayoutPriorityDefaultHigh - 1;
  imageHeightConstraint.active = YES;
}

- (void)viewDidDisappear:(BOOL)animated {
  if (self.navigationBar) {
    self.navigationItem.rightBarButtonItem = nil;
    [self.navigationBar setBackgroundImage:nil
                             forBarMetrics:UIBarMetricsDefault];
    self.navigationBar.shadowImage = nil;
    self.navigationBar.translucent = NO;
    self.navigationBar = nil;
  }
}

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];

  if (self.navigationController &&
      [self.navigationController
          isKindOfClass:[SettingsNavigationController class]]) {
    UIBarButtonItem* doneButton =
        [(SettingsNavigationController*)self.navigationController doneButton];
    self.navigationItem.rightBarButtonItem = doneButton;

    self.navigationBar = self.navigationController.navigationBar;
    [self.navigationBar setBackgroundImage:[[UIImage alloc] init]
                             forBarMetrics:UIBarMetricsDefault];
    self.navigationBar.shadowImage = [[UIImage alloc] init];
    self.navigationBar.translucent = YES;
  }
}

- (void)viewDidLayoutSubviews {
  [super viewDidLayoutSubviews];

  // Prevents potential recursive calls to `viewDidLayoutSubviews`.
  if (self.calculatingImageSize) {
    return;
  }
  // Rescale image here as on iPad the view height isn't correctly set before
  // subviews are laid out.
  self.calculatingImageSize = YES;
  self.imageView.image = [self createOrUpdateImage:self.imageView.image];
  self.calculatingImageSize = NO;
}

- (void)didMoveToParentViewController:(UIViewController*)parent {
  [super didMoveToParentViewController:parent];
  if (!parent) {
    [self.presenter passwordsInOtherAppsViewControllerDidDismiss];
  }
}

- (void)willMoveToParentViewController:(UIViewController*)parent {
  [super willMoveToParentViewController:parent];
  [self.navigationController setToolbarHidden:YES animated:YES];
}

- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
  [super traitCollectionDidChange:previousTraitCollection];
  if (self.traitCollection.verticalSizeClass !=
      previousTraitCollection.verticalSizeClass) {
    self.imageView.image = [self createOrUpdateImage:self.imageView.image];
  }
}

#pragma mark - Accessors

- (UIScrollView*)scrollView {
  if (!_scrollView) {
    _scrollView = [[UIScrollView alloc] init];
    _scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    _scrollView.contentInsetAdjustmentBehavior =
        UIScrollViewContentInsetAdjustmentNever;
    _scrollView.accessibilityIdentifier =
        kPasswordsInOtherAppsScrollViewAccessibilityIdentifier;
  }
  return _scrollView;
}

- (UIImageView*)imageView {
  if (!_imageView) {
    _imageView =
        [[UIImageView alloc] initWithImage:[self createOrUpdateImage:nil]];
    _imageView.clipsToBounds = YES;
    _imageView.translatesAutoresizingMaskIntoConstraints = NO;
    _imageView.accessibilityIdentifier =
        kPasswordsInOtherAppsImageAccessibilityIdentifier;
  }
  return _imageView;
}

- (UILabel*)titleLabel {
  if (!_titleLabel) {
    _titleLabel = [[UILabel alloc] init];
    _titleLabel.numberOfLines = 0;

    UIFontTextStyle textStyle = UIFontTextStyleTitle2;
    UIFontDescriptor* descriptor =
        [UIFontDescriptor preferredFontDescriptorWithTextStyle:textStyle];
    UIFont* font = [UIFont systemFontOfSize:descriptor.pointSize
                                     weight:UIFontWeightBold];
    UIFontMetrics* fontMetrics = [UIFontMetrics metricsForTextStyle:textStyle];

    _titleLabel.font = [fontMetrics scaledFontForFont:font];
    _titleLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
    _titleLabel.text = self.titleText;
    _titleLabel.textAlignment = NSTextAlignmentCenter;
    _titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
    _titleLabel.adjustsFontForContentSizeCategory = YES;
    _titleLabel.accessibilityIdentifier =
        kPasswordsInOtherAppsTitleAccessibilityIdentifier;
    _titleLabel.accessibilityTraits |= UIAccessibilityTraitHeader;
  }
  return _titleLabel;
}

- (UILabel*)subtitleLabel {
  if (!_subtitleLabel) {
    _subtitleLabel = [[UILabel alloc] init];
    _subtitleLabel.font =
        [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    _subtitleLabel.numberOfLines = 0;
    _subtitleLabel.textColor = [UIColor colorNamed:kGrey800Color];
    _subtitleLabel.text = self.subtitleText;
    _subtitleLabel.textAlignment = NSTextAlignmentCenter;
    _subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
    _subtitleLabel.adjustsFontForContentSizeCategory = YES;
    _subtitleLabel.accessibilityIdentifier =
        kPasswordsInOtherAppsSubtitleAccessibilityIdentifier;
  }
  return _subtitleLabel;
}

- (UIActivityIndicatorView*)spinner {
  if (!_spinner) {
    _spinner = GetMediumUIActivityIndicatorView();
    _spinner.hidesWhenStopped = YES;
    _spinner.translatesAutoresizingMaskIntoConstraints = NO;
  }
  return _spinner;
}

- (UIView*)specificContentView {
  if (!_specificContentView) {
    _specificContentView = [[UIView alloc] init];
    _specificContentView.translatesAutoresizingMaskIntoConstraints = NO;
  }
  return _specificContentView;
}

- (UIButton*)actionButton {
  if (!_actionButton) {
    _actionButton = [[HighlightButton alloc] initWithFrame:CGRectZero];

    UIButtonConfiguration* buttonConfiguration =
        [UIButtonConfiguration plainButtonConfiguration];
    buttonConfiguration.contentInsets = NSDirectionalEdgeInsetsMake(
        kButtonVerticalInsets, kButtonHorizontalMargin, kButtonVerticalInsets,
        kButtonHorizontalMargin);
    buttonConfiguration.background.backgroundColor =
        [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
    buttonConfiguration.baseForegroundColor = [UIColor colorNamed:kBlueColor];

    UIFont* font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
    NSAttributedString* attributedTitle = [[NSAttributedString alloc]
        initWithString:self.actionString
            attributes:@{NSFontAttributeName : font}];
    buttonConfiguration.attributedTitle = attributedTitle;
    buttonConfiguration.titleLineBreakMode = NSLineBreakByTruncatingTail;
    _actionButton.configuration = buttonConfiguration;

    _actionButton.layer.cornerRadius = kPrimaryButtonCornerRadius;
    _actionButton.translatesAutoresizingMaskIntoConstraints = NO;
    _actionButton.pointerInteractionEnabled = YES;
    _actionButton.pointerStyleProvider =
        CreateOpaqueButtonPointerStyleProvider();
    _actionButton.accessibilityIdentifier =
        kPasswordsInOtherAppsActionAccessibilityIdentifier;

    [_actionButton addTarget:self
                      action:@selector(didTapActionButton)
            forControlEvents:UIControlEventTouchUpInside];
  }
  return _actionButton;
}

- (UIView*)turnOnInstructionView {
  if (!_turnOnInstructionView) {
    UIImage* icon = [UIImage imageNamed:@"settings"];
    NSArray<UIImage*>* icons =
        self.useShortInstruction ? nil : @[ icon, icon, icon, icon ];
    InstructionView* instruction =
        [[InstructionView alloc] initWithList:self.steps
                                        style:InstructionViewStyleGrayscale
                                        icons:icons];
    instruction.translatesAutoresizingMaskIntoConstraints = NO;
    UILayoutGuide* instructionLayoutGuide = [[UILayoutGuide alloc] init];

    _turnOnInstructionView = [[UIView alloc] init];
    _turnOnInstructionView.translatesAutoresizingMaskIntoConstraints = NO;
    [_turnOnInstructionView addSubview:self.spinner];
    [_turnOnInstructionView addLayoutGuide:instructionLayoutGuide];
    [_turnOnInstructionView addSubview:instruction];

    // Set constraints for top, leading, trailing edges and width.
    NSMutableArray<NSLayoutConstraint*>* constraints =
        [NSMutableArray arrayWithArray:@[
          [self.spinner.topAnchor
              constraintEqualToAnchor:_turnOnInstructionView.topAnchor],
          [self.spinner.centerXAnchor
              constraintEqualToAnchor:_turnOnInstructionView.centerXAnchor],
          [self.spinner.widthAnchor
              constraintEqualToAnchor:self.spinner.heightAnchor],
          [self.spinner.bottomAnchor
              constraintLessThanOrEqualToAnchor:_turnOnInstructionView
                                                    .bottomAnchor],
          [instructionLayoutGuide.topAnchor
              constraintEqualToAnchor:self.spinner.bottomAnchor
                             constant:kDefaultMargin],
          [instructionLayoutGuide.widthAnchor
              constraintEqualToAnchor:_turnOnInstructionView.widthAnchor],
          [instructionLayoutGuide.centerXAnchor
              constraintEqualToAnchor:_turnOnInstructionView.centerXAnchor],
          [instruction.topAnchor
              constraintGreaterThanOrEqualToAnchor:instructionLayoutGuide
                                                       .topAnchor],
          [instruction.bottomAnchor
              constraintEqualToAnchor:instructionLayoutGuide.bottomAnchor],
          [instruction.widthAnchor
              constraintEqualToAnchor:instructionLayoutGuide.widthAnchor],
          [instruction.centerXAnchor
              constraintEqualToAnchor:instructionLayoutGuide.centerXAnchor],
          [_turnOnInstructionView.topAnchor
              constraintEqualToAnchor:self.specificContentView.topAnchor],
          [_turnOnInstructionView.bottomAnchor
              constraintEqualToAnchor:self.specificContentView.bottomAnchor
                             constant:-kBottomMargin],
          [_turnOnInstructionView.centerXAnchor
              constraintEqualToAnchor:self.specificContentView.centerXAnchor],
          [_turnOnInstructionView.widthAnchor
              constraintEqualToAnchor:self.specificContentView.widthAnchor]
        ]];

    // Set constraints for bottom edge:
    // if the view will contain an action button, place action button at the
    // bottom and instruction right above it; otherwise, place the instruction
    // at the bottom.
    if (self.useShortInstruction) {
      [_turnOnInstructionView addSubview:self.actionButton];
      [constraints addObjectsFromArray:@[
        [self.actionButton.widthAnchor
            constraintEqualToAnchor:_turnOnInstructionView.widthAnchor],
        [self.actionButton.centerXAnchor
            constraintEqualToAnchor:_turnOnInstructionView.centerXAnchor],
        [self.actionButton.bottomAnchor
            constraintEqualToAnchor:_turnOnInstructionView.bottomAnchor
                           constant:-kBottomMargin],
        [instructionLayoutGuide.bottomAnchor
            constraintEqualToAnchor:self.actionButton.topAnchor
                           constant:-kDefaultMargin],
      ]];
    } else {
      [constraints addObjectsFromArray:@[
        [instructionLayoutGuide.bottomAnchor
            constraintEqualToAnchor:_turnOnInstructionView.bottomAnchor],
      ]];
    }
    _turnOnInstructionViewConstraints = constraints;
  }
  return _turnOnInstructionView;
}

- (UIView*)turnOffInstructionView {
  if (!_turnOffInstructionView) {
    UITextView* captionTextView = [self drawCaptionTextView];
    NSLog(@"%@", captionTextView.text);
    UIImage* checkmark = [[UIImage imageNamed:@"settings_safe_state"]
        imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    UIImageView* checkmarkView = [[UIImageView alloc] initWithImage:checkmark];
    checkmarkView.tintColor = [UIColor colorNamed:kGreenColor];
    checkmarkView.translatesAutoresizingMaskIntoConstraints = NO;

    _turnOffInstructionView = [[UIView alloc] init];
    _turnOffInstructionView.translatesAutoresizingMaskIntoConstraints = NO;
    [_turnOffInstructionView addSubview:captionTextView];
    [_turnOffInstructionView addSubview:checkmarkView];

    // Set constraints.
    self.turnOffInstructionViewConstraints = @[
      [captionTextView.topAnchor
          constraintEqualToAnchor:_turnOffInstructionView.topAnchor],
      [captionTextView.centerXAnchor
          constraintEqualToAnchor:_turnOffInstructionView.centerXAnchor],
      [captionTextView.widthAnchor
          constraintLessThanOrEqualToAnchor:_turnOffInstructionView
                                                .widthAnchor],
      [captionTextView.bottomAnchor
          constraintLessThanOrEqualToAnchor:_turnOffInstructionView
                                                .bottomAnchor],
      [checkmarkView.topAnchor
          constraintEqualToAnchor:captionTextView.bottomAnchor
                         constant:kCaptionTextViewOffset],
      [checkmarkView.centerXAnchor
          constraintEqualToAnchor:_turnOffInstructionView.centerXAnchor],
      [checkmarkView.widthAnchor
          constraintEqualToAnchor:checkmarkView.heightAnchor],
      [checkmarkView.bottomAnchor
          constraintLessThanOrEqualToAnchor:_turnOffInstructionView
                                                .bottomAnchor],
      [_turnOffInstructionView.topAnchor
          constraintEqualToAnchor:self.specificContentView.topAnchor],
      [_turnOffInstructionView.centerXAnchor
          constraintEqualToAnchor:self.specificContentView.centerXAnchor],
      [_turnOffInstructionView.widthAnchor
          constraintEqualToAnchor:self.specificContentView.widthAnchor],
      [_turnOffInstructionView.bottomAnchor
          constraintEqualToAnchor:self.specificContentView.bottomAnchor
                         constant:-kDefaultMargin]
    ];
  }
  return _turnOffInstructionView;
}

#pragma mark - PasswordsInOtherAppsConsumer

- (void)updateInstructionsWithCurrentPasswordAutoFillStatus {
  // Show instructions to turn off autoFill if auto-fill is enabled; show
  // instructions to turn on otherwise;
  PasswordAutoFillStatusManager* sharedManager =
      [PasswordAutoFillStatusManager sharedManager];
  BOOL shouldShowTurnOffInstructions =
      sharedManager.ready && sharedManager.autoFillEnabled;

  UIView* viewToRemove = shouldShowTurnOffInstructions
                             ? _turnOnInstructionView
                             : _turnOffInstructionView;
  if (viewToRemove != nil) {
    [viewToRemove removeFromSuperview];
  }
  [self.specificContentView addSubview:shouldShowTurnOffInstructions
                                           ? self.turnOffInstructionView
                                           : self.turnOnInstructionView];

  [NSLayoutConstraint
      activateConstraints:shouldShowTurnOffInstructions
                              ? self.turnOffInstructionViewConstraints
                              : self.turnOnInstructionViewConstraints];

  if (_spinner) {
    sharedManager.ready ? [_spinner stopAnimating] : [_spinner startAnimating];
  }
}

- (BOOL)useShortInstruction {
  return ios::provider::SupportShortenedInstructionForPasswordAutoFill() &&
         base::FeatureList::IsEnabled(
             kEnableShortenedPasswordAutoFillInstruction);
}

- (NSArray<NSString*>*)steps {
  BOOL isIOS16AndAbove = NO;
  if (@available(iOS 16, *)) {
    isIOS16AndAbove = YES;
  }
  if (self.useShortInstruction) {
    return @[
      l10n_util::GetNSString(
          isIOS16AndAbove
              ? IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_SHORTENED_STEP_1_IOS16
              : IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_SHORTENED_STEP_1),
      l10n_util::GetNSString(
          IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_SHORTENED_STEP_2)
    ];
  }
  return @[
    ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET
        ? l10n_util::GetNSString(
              IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_1_IPAD)
        : l10n_util::GetNSString(
              IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_1_IPHONE),
    l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_2),
    l10n_util::GetNSString(
        isIOS16AndAbove ? IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_3_IOS16
                        : IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_3),
    l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_4)
  ];
}

#pragma mark - UIAdaptivePresentationControllerDelegate

- (BOOL)presentationControllerShouldDismiss:
    (UIPresentationController*)presentationController {
  return YES;
}

#pragma mark - UITextViewDelegate

- (BOOL)textView:(UITextView*)textView
    shouldInteractWithURL:(NSURL*)URL
                  inRange:(NSRange)characterRange
              interaction:(UITextItemInteraction)interaction {
  [self didTapActionButton];
  return YES;
}

#pragma mark - Private

// Returns caption text that shows below the subtitle in turnOffInstructions.
- (UITextView*)drawCaptionTextView {
  NSString* text;
  if (@available(iOS 16, *)) {
    text = l10n_util::GetNSString(
        IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_CAPTION_IOS16);
  } else {
    text = l10n_util::GetNSString(
        IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_CAPTION);
  }
  NSDictionary* textAttributes = @{
    NSForegroundColorAttributeName : [UIColor colorNamed:kGrey600Color],
    NSFontAttributeName :
        [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]
  };

  NSDictionary* linkAttributes = @{
    NSFontAttributeName :
        [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote],
    NSUnderlineStyleAttributeName : @(NSUnderlineStyleNone),
    NSForegroundColorAttributeName : [UIColor colorNamed:kRedColor],
    NSLinkAttributeName : l10n_util::GetNSString(IDS_IOS_OPEN_SETTINGS),
  };

  UITextView* captionTextView = CreateUITextViewWithTextKit1();
  captionTextView.attributedText =
      AttributedStringFromStringWithLink(text, textAttributes, linkAttributes);
  captionTextView.editable = NO;
  captionTextView.scrollEnabled = NO;
  captionTextView.backgroundColor = self.view.backgroundColor;
  captionTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);
  captionTextView.textAlignment = NSTextAlignmentCenter;
  captionTextView.translatesAutoresizingMaskIntoConstraints = NO;
  captionTextView.adjustsFontForContentSizeCategory = YES;

  captionTextView.delegate = self;

  return captionTextView;
}

// Returns a new UIImage which is `sourceImage` resized to `newSize`.
// Returns `currentImage` if it is already at the correct size.
// Returns nil when the view should not show an image (iPhone landscape mode).
- (UIImage*)createOrUpdateImage:(UIImage*)currentImage {
  if (IsCompactHeight(self)) {
    return nil;
  }
  UIUserInterfaceStyle currentStyle =
      UITraitCollection.currentTraitCollection.userInterfaceStyle;
  CGSize newSize = [self computeBannerImageSize];
  if (CGSizeEqualToSize(newSize, currentImage.size) &&
      self.bannerStyle == currentStyle) {
    return currentImage;
  }
  self.bannerStyle = currentStyle;
  return ResizeImage([self bannerImage], newSize, ProjectionMode::kAspectFit);
}

// The banner image
- (UIImage*)bannerImage {
  return [UIImage imageNamed:self.bannerName];
}

// Computes banner's image size.
- (CGSize)computeBannerImageSize {
  CGFloat destinationHeight =
      roundf(self.view.bounds.size.height * kDefaultBannerMultiplier);
  CGFloat destinationWidth =
      roundf([self bannerImage].size.width / [self bannerImage].size.height *
             destinationHeight);
  CGSize newSize = CGSizeMake(destinationWidth, destinationHeight);
  return newSize;
}

// Selector of self.actionButton and link in caption text view.
- (void)didTapActionButton {
  [self.delegate openApplicationSettings];
}

@end