chromium/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_cell_utils.mm

// Copyright 2018 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/autofill/ui_bundled/manual_fill/manual_fill_cell_utils.h"

#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/chip_button.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_constants.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_labeled_chip.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/elements/extended_touch_target_button.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_cell.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/button_util.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util_mac.h"

namespace {

// Vertical insets of the "Autofill Form" button.
constexpr CGFloat kAutofillFormButtonVerticalInsets = 11;

// Minimum height of the "Autofill Form" button.
constexpr CGFloat kAutofillFormButtonMinHeight = 44;

// Bottom margin for the cell content. Used when the Keyboard Accessory Upgrade
// feature is disabled.
constexpr CGFloat kCellBottomMargin = 18;

// Line spacing for the cell's header title.
constexpr CGFloat kHeaderAttributedStringLineSpacing = 2;

// Font size for the cell's header title.
constexpr CGFloat kHeaderAttributedStringTitleFontSize = 15;

// Top and bottom padding for the header view label.
constexpr CGFloat kHeaderViewLabelVerticalPadding = 6;

// Minimum height for the header view.
constexpr CGFloat kHeaderViewMinHeight = 44;

// Height of the grey separator.
constexpr CGFloat kSeparatorHeight = 1;

// Horizontal spacing between views. Used when the Keyboard Accessory Upgrade
// feature is disabled.
constexpr CGFloat kHorizontalSpacing = 16;

// Vertical spacing between views. Used when the Keyboard Accessory Upgrade
// feature is disabled.
constexpr CGFloat kVerticalSpacing = 8;

// Generic vertical spacing between views. Delimits the different parts of the
// cell and the chip groups.
constexpr CGFloat kGenericVerticalSpacingBetweenViews = 16;

// Small vertical and horizontal spacing between views. Used to visually group
// chips together and as vertical padding for the cell's header.
constexpr CGFloat kSmallSpacingBetweenViews = 4;

// Vertical spacing between two labeled chip buttons.
constexpr CGFloat kVerticalSpacingBetweenLabeledChips = 8;

// Height and width of the overflow menu button displayed in the cell's header.
constexpr CGFloat kOverflowMenuButtonSize = 24;

// Minimum spacing that a cell's trailing view should have with the view on its
// left.
constexpr CGFloat kTrailingViewMinLeadingSpacing = 8;

// Top and bottom padding for the virtual card instruction view.
constexpr CGFloat kVirtualCardInstructionsVerticalPadding = 8;

// Adds all baseline anchor constraints for the given `views` to match the first
// one. Constraints are not activated.
void AppendEqualBaselinesConstraints(
    NSMutableArray<NSLayoutConstraint*>* constraints,
    NSArray<UIView*>* views) {
  UIView* leadingView = views.firstObject;
  for (UIView* view in views) {
    DCHECK([view isKindOfClass:[UIButton class]] ||
           [view isKindOfClass:[UILabel class]]);
    if (view == leadingView) {
      continue;
    }

    if (IsKeyboardAccessoryUpgradeEnabled()) {
      [constraints
          addObject:[view.centerYAnchor
                        constraintEqualToAnchor:leadingView.centerYAnchor]];
    } else {
      [constraints
          addObject:[view.lastBaselineAnchor
                        constraintEqualToAnchor:leadingView
                                                    .lastBaselineAnchor]];
    }
  }
}

// Returns the vertical spacing that should be added above a UI element of given
// `type`.
CGFloat GetVerticalSpacingForElementType(
    ManualFillCellView::ElementType element_type) {
  if (!IsKeyboardAccessoryUpgradeEnabled()) {
    return kVerticalSpacing;
  }

  switch (element_type) {
    case ManualFillCellView::ElementType::kFirstChipButtonOfGroup:
    case ManualFillCellView::ElementType::kOther:
      return kGenericVerticalSpacingBetweenViews;
    case ManualFillCellView::ElementType::kLabeledChipButton:
      return kVerticalSpacingBetweenLabeledChips;
    case ManualFillCellView::ElementType::kHeaderSeparator:
    case ManualFillCellView::ElementType::kOtherChipButton:
      return kSmallSpacingBetweenViews;
    case ManualFillCellView::ElementType::kVirtualCardInstructions:
    case ManualFillCellView::ElementType::kVirtualCardInstructionsSeparator:
      return kVirtualCardInstructionsVerticalPadding;
  }
}

// Returns the width of the given `layout_guide`.
CGFloat GetLayoutGuideWidth(UILayoutGuide* layout_guide) {
  return layout_guide.layoutFrame.size.width;
}

// Returns the width of the given `view`.
CGFloat GetViewWidth(UIView* view) {
  return view.intrinsicContentSize.width;
}

// Returns the font for the cell's header title.
UIFont* TitleFont() {
  if (IsKeyboardAccessoryUpgradeEnabled()) {
    UIFont* font = [UIFont systemFontOfSize:kHeaderAttributedStringTitleFontSize
                                     weight:UIFontWeightMedium];
    return [[UIFontMetrics defaultMetrics] scaledFontForFont:font];
  } else {
    return [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
  }
}

// Creates and adds constraints to `constraints`, so as to horizontally lay out
// the given `views`, while taking the `trailing_view` into account. Then, adds
// the first view to `vertical_lead_views` to mark the start of a new row of
// views.
void LayViewsHorizontally(NSArray<UIView*>* views,
                          UILayoutGuide* guide,
                          NSMutableArray<NSLayoutConstraint*>* constraints,
                          NSMutableArray<UIView*>* vertical_lead_views,
                          UIView* trailing_view) {
  AppendHorizontalConstraintsForViews(
      constraints, views, guide, 0,
      AppendConstraintsHorizontalSyncBaselines |
          AppendConstraintsHorizontalEqualOrSmallerThanGuide,
      trailing_view);
  [vertical_lead_views addObject:views.firstObject];
}

}  // namespace

const CGFloat kCellMargin = 16;
const CGFloat kChipsHorizontalMargin = -1;

CGFloat GetHorizontalSpacingBetweenChips() {
  return IsKeyboardAccessoryUpgradeEnabled() ? kSmallSpacingBetweenViews
                                             : kHorizontalSpacing;
}

UIButton* CreateChipWithSelectorAndTarget(SEL action, id target) {
  UIButton* button = [ChipButton buttonWithType:UIButtonTypeCustom];
  button.translatesAutoresizingMaskIntoConstraints = NO;
  button.titleLabel.adjustsFontForContentSizeCategory = YES;
  [button addTarget:target
                action:action
      forControlEvents:UIControlEventTouchUpInside];
  return button;
}

void AppendVerticalConstraintsSpacingForViews(
    NSMutableArray<NSLayoutConstraint*>* constraints,
    const std::vector<ManualFillCellView>& manual_fill_cell_views,
    UILayoutGuide* layout_guide) {
  AppendVerticalConstraintsSpacingForViews(constraints, manual_fill_cell_views,
                                           layout_guide, 0);
}

void AppendVerticalConstraintsSpacingForViews(
    NSMutableArray<NSLayoutConstraint*>* constraints,
    const std::vector<ManualFillCellView>& manual_fill_cell_views,
    UILayoutGuide* layout_guide,
    CGFloat offset) {
  NSLayoutYAxisAnchor* previous_anchor = layout_guide.topAnchor;
  for (const ManualFillCellView& manual_fill_cell_view :
       manual_fill_cell_views) {
    CGFloat spacing =
        manual_fill_cell_view == manual_fill_cell_views.front()
            ? offset
            : GetVerticalSpacingForElementType(manual_fill_cell_view.type);

    UIView* view = manual_fill_cell_view.view;
    [constraints
        addObject:[view.topAnchor constraintEqualToAnchor:previous_anchor
                                                 constant:spacing]];
    previous_anchor = view.bottomAnchor;
  }

  [constraints
      addObject:[previous_anchor
                    constraintEqualToAnchor:layout_guide.bottomAnchor]];
}

void AddChipGroupsToVerticalLeadViews(
    NSArray<NSArray<UIView*>*>* chip_groups,
    std::vector<ManualFillCellView>& vertical_lead_views) {
  for (NSArray* chip_group in chip_groups) {
    for (UIView* chip in chip_group) {
      ManualFillCellView::ElementType element_type;
      if ([chip isEqual:[chip_group firstObject]]) {
        element_type = ManualFillCellView::ElementType::kFirstChipButtonOfGroup;
      } else if ([chip isKindOfClass:[ManualFillLabeledChip class]]) {
        element_type = ManualFillCellView::ElementType::kLabeledChipButton;
      } else {
        element_type = ManualFillCellView::ElementType::kOtherChipButton;
      }

      AddViewToVerticalLeadViews(chip, element_type, vertical_lead_views);
    }
  }
}

void AddViewToVerticalLeadViews(
    UIView* view,
    ManualFillCellView::ElementType type,
    std::vector<ManualFillCellView>& vertical_lead_views) {
  ManualFillCellView manual_fill_cell_view = {view, type};
  vertical_lead_views.push_back(manual_fill_cell_view);
}

void AppendHorizontalConstraintsForViews(
    NSMutableArray<NSLayoutConstraint*>* constraints,
    NSArray<UIView*>* views,
    UILayoutGuide* layout_guide) {
  AppendHorizontalConstraintsForViews(constraints, views, layout_guide, 0);
}

void AppendHorizontalConstraintsForViews(
    NSMutableArray<NSLayoutConstraint*>* constraints,
    NSArray<UIView*>* views,
    UILayoutGuide* layout_guide,
    CGFloat margin) {
  AppendHorizontalConstraintsForViews(constraints, views, layout_guide, margin,
                                      0);
}

void AppendHorizontalConstraintsForViews(
    NSMutableArray<NSLayoutConstraint*>* constraints,
    NSArray<UIView*>* views,
    UILayoutGuide* layout_guide,
    CGFloat margin,
    AppendConstraints options) {
  AppendHorizontalConstraintsForViews(constraints, views, layout_guide, margin,
                                      options, nil);
}

void AppendHorizontalConstraintsForViews(
    NSMutableArray<NSLayoutConstraint*>* constraints,
    NSArray<UIView*>* views,
    UILayoutGuide* layout_guide,
    CGFloat margin,
    AppendConstraints options,
    UIView* trailing_view) {
  if (views.count == 0) {
    return;
  }

  NSLayoutXAxisAnchor* previous_anchor = layout_guide.leadingAnchor;

  BOOL is_first_view = YES;
  for (UIView* view in views) {
    CGFloat spacing =
        is_first_view ? margin : GetHorizontalSpacingBetweenChips();

    NSLayoutConstraint* constraint =
        [view.leadingAnchor constraintEqualToAnchor:previous_anchor
                                           constant:spacing];

    if (!is_first_view && IsKeyboardAccessoryUpgradeEnabled()) {
      // Set the in-between view constraints to a low priority so that the views
      // can be easily reorganized when the width of the `layout_guide` changes.
      constraint.priority = UILayoutPriorityDefaultLow;
    }
    [constraints addObject:constraint];

    if (!IsKeyboardAccessoryUpgradeEnabled()) {
      [view
          setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
                                          forAxis:
                                              UILayoutConstraintAxisHorizontal];
      [view setContentHuggingPriority:UILayoutPriorityDefaultHigh
                              forAxis:UILayoutConstraintAxisHorizontal];
    }

    previous_anchor = view.trailingAnchor;
    is_first_view = NO;
  }

  // If there's a `trailing_view`, constraint the trailing anchor of the last
  // view to `trailing_view`'s leading anchor. Otherwise constraint the last
  // view's trailing anchor to the `layout_guide`'s trailing anchor.
  UIView* last_view = views.lastObject;
  if (trailing_view) {
    [constraints
        addObject:
            [last_view.trailingAnchor
                constraintLessThanOrEqualToAnchor:trailing_view.leadingAnchor
                                         constant:
                                             -kTrailingViewMinLeadingSpacing]];
    [constraints
        addObject:[trailing_view.trailingAnchor
                      constraintEqualToAnchor:layout_guide.trailingAnchor]];

  } else if (options & AppendConstraintsHorizontalEqualOrSmallerThanGuide) {
    [constraints
        addObject:[last_view.trailingAnchor
                      constraintLessThanOrEqualToAnchor:layout_guide
                                                            .trailingAnchor
                                               constant:-margin]];

  } else {
    [constraints
        addObject:[last_view.trailingAnchor
                      constraintEqualToAnchor:layout_guide.trailingAnchor
                                     constant:-margin]];
    if (!IsKeyboardAccessoryUpgradeEnabled()) {
      // Give all remaining space to the last button, minus margin, as per UX.
      [last_view
          setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh
                                          forAxis:
                                              UILayoutConstraintAxisHorizontal];
      [last_view setContentHuggingPriority:UILayoutPriorityDefaultLow
                                   forAxis:UILayoutConstraintAxisHorizontal];
    }
  }

  if (options & AppendConstraintsHorizontalSyncBaselines) {
    AppendEqualBaselinesConstraints(constraints, views);
  }
}

void LayViewsHorizontallyWhenPossible(
    NSArray<UIView*>* views,
    UILayoutGuide* guide,
    NSMutableArray<NSLayoutConstraint*>* constraints,
    NSMutableArray<UIView*>* vertical_lead_views) {
  LayViewsHorizontallyWhenPossible(views, guide, constraints,
                                   vertical_lead_views, nil);
}

void LayViewsHorizontallyWhenPossible(
    NSArray<UIView*>* views,
    UILayoutGuide* guide,
    NSMutableArray<NSLayoutConstraint*>* constraints,
    NSMutableArray<UIView*>* vertical_lead_views,
    UIView* first_row_trailing_view) {
  if (!views || !views.count) {
    return;
  }

  // `first_row_trailing_view` should only be considered for the first row.
  BOOL should_consider_trailing_view = first_row_trailing_view;
  CGFloat available_width = GetLayoutGuideWidth(guide);
  if (should_consider_trailing_view) {
    // Remove the width of the `first_row_trailing_view` and its leading spacing
    // from the `available_width`.
    available_width -= (GetViewWidth(first_row_trailing_view) +
                        kTrailingViewMinLeadingSpacing);
  }
  NSMutableArray<UIView*>* horizontal_views = [[NSMutableArray alloc] init];

  for (UIView* view in views) {
    CGFloat view_width = GetViewWidth(view);
    BOOL fits_horizontally =
        !horizontal_views.count || available_width - view_width >= 0;

    if (fits_horizontally) {
      [horizontal_views addObject:view];
      available_width -= (view_width + GetHorizontalSpacingBetweenChips());
    } else {
      LayViewsHorizontally(
          horizontal_views, guide, constraints, vertical_lead_views,
          should_consider_trailing_view ? first_row_trailing_view : nil);
      should_consider_trailing_view = NO;

      // Start new row of views.
      [horizontal_views removeAllObjects];
      [horizontal_views addObject:view];
      available_width = GetLayoutGuideWidth(guide) - view_width -
                        GetHorizontalSpacingBetweenChips();
    }
  }

  LayViewsHorizontally(
      horizontal_views, guide, constraints, vertical_lead_views,
      should_consider_trailing_view ? first_row_trailing_view : nil);
}

UILabel* CreateLabel() {
  UILabel* label = [[UILabel alloc] init];
  label.translatesAutoresizingMaskIntoConstraints = NO;
  label.adjustsFontForContentSizeCategory = YES;
  label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
  return label;
}

NSMutableAttributedString* CreateHeaderAttributedString(NSString* title,
                                                        NSString* subtitle) {
  NSMutableAttributedString* attributed_title =
      [[NSMutableAttributedString alloc]
          initWithString:title
              attributes:@{
                NSForegroundColorAttributeName :
                    [UIColor colorNamed:kTextPrimaryColor],
                NSFontAttributeName : TitleFont(),
              }];

  if (IsKeyboardAccessoryUpgradeEnabled()) {
    NSMutableParagraphStyle* title_paragraph_style =
        [[NSMutableParagraphStyle alloc] init];
    title_paragraph_style.lineSpacing = kHeaderAttributedStringLineSpacing;
    title_paragraph_style.lineBreakMode = NSLineBreakByWordWrapping;
    [attributed_title
        addAttributes:@{NSParagraphStyleAttributeName : title_paragraph_style}
                range:NSMakeRange(0, attributed_title.string.length)];
  }

  if (subtitle && subtitle.length) {
    NSMutableAttributedString* attributed_subtitle = [[NSMutableAttributedString
        alloc]
        initWithString:[NSString stringWithFormat:@"\n%@", subtitle]
            attributes:@{
              NSForegroundColorAttributeName :
                  [UIColor colorNamed:kTextSecondaryColor],
              NSFontAttributeName : [UIFont
                  preferredFontForTextStyle:IsKeyboardAccessoryUpgradeEnabled()
                                                ? UIFontTextStyleCaption1
                                                : UIFontTextStyleFootnote],
            }];

    if (IsKeyboardAccessoryUpgradeEnabled()) {
      NSMutableParagraphStyle* subtitle_paragraph_style =
          [[NSMutableParagraphStyle alloc] init];
      subtitle_paragraph_style.lineBreakMode = NSLineBreakByWordWrapping;
      [attributed_subtitle
          addAttributes:@{
            NSParagraphStyleAttributeName : subtitle_paragraph_style
          }
                  range:NSMakeRange(0, attributed_subtitle.string.length)];
    }

    [attributed_title appendAttributedString:attributed_subtitle];
  }

  return attributed_title;
}

UIStackView* CreateHeaderView(UIView* icon,
                              UILabel* label,
                              UIButton* overflow_menu_button) {
  NSMutableArray<UIView*>* subviews = [[NSMutableArray alloc] init];
  if (icon) {
    [subviews addObject:icon];
  }
  CHECK(label);
  [subviews addObject:label];
  if (overflow_menu_button) {
    [subviews addObject:overflow_menu_button];
  }

  UIStackView* header_view =
      [[UIStackView alloc] initWithArrangedSubviews:subviews];
  header_view.translatesAutoresizingMaskIntoConstraints = NO;
  header_view.spacing = UIStackViewSpacingUseSystem;  // Spacing of 8px.
  header_view.alignment = UIStackViewAlignmentCenter;

  if (IsKeyboardAccessoryUpgradeEnabled()) {
    [NSLayoutConstraint activateConstraints:@[
      [header_view.heightAnchor
          constraintGreaterThanOrEqualToConstant:kHeaderViewMinHeight],
      [label.topAnchor constraintEqualToAnchor:header_view.topAnchor
                                      constant:kHeaderViewLabelVerticalPadding],
      [label.bottomAnchor
          constraintEqualToAnchor:header_view.bottomAnchor
                         constant:-kHeaderViewLabelVerticalPadding],
    ]];
  }

  return header_view;
}

UIButton* CreateOverflowMenuButton() {
  ExtendedTouchTargetButton* menu_button =
      [ExtendedTouchTargetButton buttonWithType:UIButtonTypeSystem];
  menu_button.translatesAutoresizingMaskIntoConstraints = NO;
  menu_button.contentMode = UIViewContentModeCenter;
  menu_button.accessibilityIdentifier =
      manual_fill::kExpandedManualFillOverflowMenuID;

  UIImage* menu_image = SymbolWithPalette(
      DefaultSymbolWithPointSize(kEllipsisCircleFillSymbol,
                                 kOverflowMenuButtonSize),
      @[
        [UIColor colorNamed:kBlue600Color], [UIColor tertiarySystemFillColor]
      ]);
  [menu_button setImage:menu_image forState:UIControlStateNormal];

  [menu_button setContentHuggingPriority:UILayoutPriorityRequired
                                 forAxis:UILayoutConstraintAxisHorizontal];
  [menu_button
      setContentCompressionResistancePriority:UILayoutPriorityRequired
                                      forAxis:UILayoutConstraintAxisHorizontal];

  menu_button.showsMenuAsPrimaryAction = YES;

  return menu_button;
}

UIView* CreateGraySeparatorForContainer(UIView* container) {
  UIView* gray_line = [[UIView alloc] init];
  gray_line.backgroundColor = [UIColor colorNamed:kSeparatorColor];
  gray_line.translatesAutoresizingMaskIntoConstraints = NO;
  [container addSubview:gray_line];

  id<LayoutGuideProvider> safe_area = container.safeAreaLayoutGuide;
  if (IsKeyboardAccessoryUpgradeEnabled()) {
    [NSLayoutConstraint activateConstraints:@[
      // Vertical constraints.
      [gray_line.heightAnchor constraintEqualToConstant:kSeparatorHeight],
      // Horizontal constraints.
      [gray_line.leadingAnchor constraintEqualToAnchor:safe_area.leadingAnchor
                                              constant:kCellMargin],
      [safe_area.trailingAnchor
          constraintEqualToAnchor:gray_line.trailingAnchor],
    ]];
  } else {
    [NSLayoutConstraint activateConstraints:@[
      // Vertical constraints.
      [gray_line.bottomAnchor constraintEqualToAnchor:container.bottomAnchor],
      [gray_line.heightAnchor constraintEqualToConstant:kSeparatorHeight],
      // Horizontal constraints.
      [gray_line.leadingAnchor constraintEqualToAnchor:safe_area.leadingAnchor
                                              constant:kCellMargin],
      [safe_area.trailingAnchor constraintEqualToAnchor:gray_line.trailingAnchor
                                               constant:kCellMargin],
    ]];
  }

  return gray_line;
}

UIButton* CreateAutofillFormButton() {
  UIButton* button = PrimaryActionButton(/*pointer_interaction_enabled=*/YES);
  button.accessibilityIdentifier =
      manual_fill::kExpandedManualFillAutofillFormButtonID;
  UIButtonConfiguration* buttonConfiguration = button.configuration;
  buttonConfiguration.contentInsets =
      NSDirectionalEdgeInsetsMake(kAutofillFormButtonVerticalInsets, 0,
                                  kAutofillFormButtonVerticalInsets, 0);
  button.configuration = buttonConfiguration;

  [button.heightAnchor
      constraintGreaterThanOrEqualToConstant:kAutofillFormButtonMinHeight]
      .active = YES;

  SetConfigurationTitle(
      button, l10n_util::GetNSString(
                  IDS_IOS_MANUAL_FALLBACK_AUTOFILL_FORM_BUTTON_TITLE));

  return button;
}

UILayoutGuide* AddLayoutGuideToContentView(UIView* content_view,
                                           BOOL cell_has_header) {
  UILayoutGuide* layout_guide = [[UILayoutGuide alloc] init];
  [content_view addLayoutGuide:layout_guide];

  id<LayoutGuideProvider> safe_area = content_view.safeAreaLayoutGuide;
  CGFloat top_margin = cell_has_header && IsKeyboardAccessoryUpgradeEnabled()
                           ? kSmallSpacingBetweenViews
                           : kCellMargin;
  CGFloat bottom_margin =
      IsKeyboardAccessoryUpgradeEnabled() ? kCellMargin : kCellBottomMargin;
  [NSLayoutConstraint activateConstraints:@[
    [layout_guide.topAnchor constraintEqualToAnchor:content_view.topAnchor
                                           constant:top_margin],
    [layout_guide.bottomAnchor constraintEqualToAnchor:content_view.bottomAnchor
                                              constant:-bottom_margin],
    [layout_guide.leadingAnchor constraintEqualToAnchor:safe_area.leadingAnchor
                                               constant:kCellMargin],
    [layout_guide.trailingAnchor
        constraintEqualToAnchor:safe_area.trailingAnchor
                       constant:-kCellMargin],
  ]];

  return layout_guide;
}

NSMutableAttributedString* CreateSiteNameLabelAttributedText(
    ManualFillSiteInfo* siteInfo) {
  NSString* siteName = siteInfo.siteName ? siteInfo.siteName : @"";
  NSString* host;
  NSMutableAttributedString* attributedString;

  BOOL shouldShowHost = siteInfo.host && siteInfo.host.length &&
                        ![siteInfo.host isEqualToString:siteInfo.siteName];
  if (shouldShowHost) {
    if (IsKeyboardAccessoryUpgradeEnabled()) {
      host = siteInfo.host;
    }

    // If the Keyboard Accessory Upgrade feature is disabled, `host` will be
    // `nil` here, and so it won't be added to `attributedString` right away.
    attributedString = CreateHeaderAttributedString(siteName, host);

    if (!IsKeyboardAccessoryUpgradeEnabled()) {
      host = [NSString stringWithFormat:@" –– %@", siteInfo.host];
      NSDictionary* attributes = @{
        NSForegroundColorAttributeName :
            [UIColor colorNamed:kTextSecondaryColor],
        NSFontAttributeName :
            [UIFont preferredFontForTextStyle:UIFontTextStyleBody]
      };
      NSAttributedString* hostAttributedString =
          [[NSAttributedString alloc] initWithString:host
                                          attributes:attributes];
      [attributedString appendAttributedString:hostAttributedString];
    }
  } else {
    attributedString = CreateHeaderAttributedString(siteName, nil);
  }

  return attributedString;
}

void GiveAccessibilityContextToCellAndButton(UIView* cell_container,
                                             UIButton* overflow_menu_button,
                                             UIButton* autofill_form_button,
                                             NSString* accessibility_context) {
  CHECK(cell_container);
  CHECK(overflow_menu_button);
  CHECK(autofill_form_button);

  cell_container.accessibilityLabel = accessibility_context;
  overflow_menu_button.accessibilityLabel = l10n_util::GetNSStringF(
      IDS_IOS_MANUAL_FALLBACK_THREE_DOT_MENU_BUTTON_ACCESSIBILITY_LABEL,
      base::SysNSStringToUTF16(accessibility_context));
  autofill_form_button.accessibilityLabel = l10n_util::GetNSStringF(
      IDS_IOS_MANUAL_FALLBACK_AUTOFILL_FORM_BUTTON_ACCESSIBILITY_LABEL,
      base::SysNSStringToUTF16(accessibility_context));
}

void SetUpCellAccessibilityElements(TableViewCell* cell,
                                    NSArray<UIView*>* accessibilityElements) {
  // If the Keyboard Accessory Upgrade feature is disabled, keep the default
  // accessibility behaviour.
  if (!IsKeyboardAccessoryUpgradeEnabled()) {
    return;
  }

  // The following two lines are needed to make the cell as a container, as well
  // as its content, accessible.
  cell.isAccessibilityElement = NO;
  cell.contentView.isAccessibilityElement = YES;

  cell.accessibilityElements = accessibilityElements;
}