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

// Copyright 2024 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_plus_address_cell.h"

#import "base/metrics/user_metrics.h"
#import "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_cell_utils.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_content_injector.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_plus_address.h"
#import "ios/chrome/browser/favicon/model/favicon_loader.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/list_model/list_model.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/favicon/favicon_container_view.h"
#import "ios/chrome/common/ui/favicon/favicon_view.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"
#import "ui/gfx/favicon_size.h"
#import "url/gurl.h"

namespace {

constexpr CGFloat kFaviconContainterViewSize = 30;

// Returns the size that the favicon should have.
CGFloat GetFaviconSize() {
  return IsKeyboardAccessoryUpgradeEnabled() ? kFaviconContainterViewSize
                                             : gfx::kFaviconSize;
}

}  // namespace

@interface ManualFillPlusAddressItem ()

// The plus address for this item.
@property(nonatomic, strong, readonly) ManualFillPlusAddress* plusAddress;

// The delegate for this item.
@property(nonatomic, weak, readonly) id<ManualFillContentInjector>
    contentInjector;

// The UIActions that should be available from the cell's overflow menu button.
@property(nonatomic, strong) NSArray<UIAction*>* menuActions;

// The part of the cell's accessibility label that is used to indicate the index
// at which the plus address represented by this item is positioned in the list
// of plus addresses to show.
@property(nonatomic, strong) NSString* cellIndexAccessibilityLabel;

@end

@implementation ManualFillPlusAddressItem

- (instancetype)initWithPlusAddress:(ManualFillPlusAddress*)plusAddress
                    contentInjector:
                        (id<ManualFillContentInjector>)contentInjector
                        menuActions:(NSArray<UIAction*>*)menuActions
        cellIndexAccessibilityLabel:(NSString*)cellIndexAccessibilityLabel {
  self = [super initWithType:kItemTypeEnumZero];
  if (self) {
    _plusAddress = plusAddress;
    _contentInjector = contentInjector;
    _menuActions = menuActions;
    _cellIndexAccessibilityLabel = cellIndexAccessibilityLabel;
    self.cellClass = [ManualFillPlusAddressCell class];
  }
  return self;
}

- (void)configureCell:(ManualFillPlusAddressCell*)cell
           withStyler:(ChromeTableViewStyler*)styler {
  [super configureCell:cell withStyler:styler];
  [cell setUpWithPlusAddress:self.plusAddress
                  contentInjector:self.contentInjector
                      menuActions:self.menuActions
      cellIndexAccessibilityLabel:_cellIndexAccessibilityLabel];
}

- (const GURL&)faviconURL {
  return self.plusAddress.URL;
}

- (NSString*)uniqueIdentifier {
  return base::SysUTF8ToNSString(self.plusAddress.URL.spec());
}

@end

@interface ManualFillPlusAddressCell ()

// The plus address this cell is showing.
@property(nonatomic, strong) ManualFillPlusAddress* plusAddress;

// The dynamic constraints for all the lines (i.e. not set in createView).
@property(nonatomic, strong)
    NSMutableArray<NSLayoutConstraint*>* dynamicConstraints;

// The constraints for the visible favicon.
@property(nonatomic, strong) NSArray<NSLayoutConstraint*>* faviconContraints;

// The view displayed at the top the cell containing the favicon, the site name
// and an overflow button.
@property(nonatomic, strong) UIView* headerView;

// The favicon for the plus address. Of type FaviconView when the Keyboard
// Accessory Upgrade is disabled, and FaviconContainerView when enabled.
@property(nonatomic, strong) UIView* faviconView;

// The label with the site name and host.
@property(nonatomic, strong) UILabel* siteNameLabel;

// The menu button displayed in the cell's header.
@property(nonatomic, strong) UIButton* overflowMenuButton;

// A button showing the plus address.
@property(nonatomic, strong) UIButton* plusAddressButton;

// Separator line. When the Keyboard Accessory Upgrade feature is enbaled, used
// to delimit the header from the rest of the cell. When disabled, used when
// needed to delimit cells.
@property(nonatomic, strong) UIView* grayLine;

// The delegate in charge of processing the user actions in this cell.
@property(nonatomic, weak) id<ManualFillContentInjector> contentInjector;

// Layout guide for the cell's content.
@property(nonatomic, strong) UILayoutGuide* layoutGuide;

@end

@implementation ManualFillPlusAddressCell

#pragma mark - Public

- (void)prepareForReuse {
  [super prepareForReuse];
  [NSLayoutConstraint deactivateConstraints:self.faviconContraints];
  self.faviconView.hidden = YES;

  [NSLayoutConstraint deactivateConstraints:self.dynamicConstraints];
  [self.dynamicConstraints removeAllObjects];

  self.siteNameLabel.text = @"";
  [self configureFaviconWithAttributes:nil];

  [self.plusAddressButton setTitle:@"" forState:UIControlStateNormal];
  self.plusAddressButton.enabled = YES;
  [self.plusAddressButton setTitleColor:[UIColor colorNamed:kTextPrimaryColor]
                               forState:UIControlStateNormal];

  self.plusAddress = nil;

  self.grayLine.hidden = NO;
}

- (void)setUpWithPlusAddress:(ManualFillPlusAddress*)plusAddress
                contentInjector:(id<ManualFillContentInjector>)contentInjector
                    menuActions:(NSArray<UIAction*>*)menuActions
    cellIndexAccessibilityLabel:(NSString*)cellIndexAccessibilityLabel {
  if (self.contentView.subviews.count == 0) {
    [self createViewHierarchy];
  }
  self.contentInjector = contentInjector;
  self.plusAddress = plusAddress;

  // Holds the views whose leading anchor is constrained relative to the cell's
  // leading anchor.
  std::vector<ManualFillCellView> verticalLeadViews;

  // Header.
  NSAttributedString* attributedText =
      CreateSiteNameLabelAttributedText(plusAddress);
  self.siteNameLabel.attributedText = attributedText;
  if (IsKeyboardAccessoryUpgradeEnabled()) {
    self.siteNameLabel.numberOfLines = 0;
    self.accessibilityLabel =
        [NSString stringWithFormat:@"%@, %@", cellIndexAccessibilityLabel,
                                   attributedText.string];
  }
  self.siteNameLabel.hidden = NO;
  self.faviconView.hidden = NO;
  AddViewToVerticalLeadViews(self.headerView,
                             ManualFillCellView::ElementType::kOther,
                             verticalLeadViews);
  if (IsKeyboardAccessoryUpgradeEnabled()) {
    AddViewToVerticalLeadViews(
        self.grayLine, ManualFillCellView::ElementType::kHeaderSeparator,
        verticalLeadViews);
  }
  if (menuActions && menuActions.count) {
    self.overflowMenuButton.menu = [UIMenu menuWithChildren:menuActions];
    self.overflowMenuButton.hidden = NO;
  } else {
    self.overflowMenuButton.hidden = YES;
  }

  // Holds the chip buttons related to the plus address that are vertical leads.
  NSMutableArray<UIView*>* plusAddressGroupVerticalLeadChips =
      [[NSMutableArray alloc] init];

  // Plus Address chip button.
  [self.plusAddressButton setTitle:plusAddress.plusAddress
                          forState:UIControlStateNormal];
  if (IsKeyboardAccessoryUpgradeEnabled()) {
    self.plusAddressButton.accessibilityLabel = l10n_util::GetNSStringF(
        IDS_IOS_MANUAL_FALLBACK_CHIP_ACCESSIBILITY_LABEL,
        base::SysNSStringToUTF16(plusAddress.plusAddress));
  }
  [plusAddressGroupVerticalLeadChips addObject:self.plusAddressButton];

  AddChipGroupsToVerticalLeadViews(@[ plusAddressGroupVerticalLeadChips ],
                                   verticalLeadViews);

  // Set and activate constraints.
  self.dynamicConstraints = [[NSMutableArray alloc] init];
  AppendVerticalConstraintsSpacingForViews(self.dynamicConstraints,
                                           verticalLeadViews, self.layoutGuide,
                                           /*offset=*/0);
  [NSLayoutConstraint activateConstraints:self.dynamicConstraints];
}

- (NSString*)uniqueIdentifier {
  return base::SysUTF8ToNSString(self.plusAddress.URL.spec());
}

- (void)configureWithFaviconAttributes:(FaviconAttributes*)attributes {
  if (attributes.faviconImage) {
    self.faviconView.hidden = NO;
    [NSLayoutConstraint activateConstraints:self.faviconContraints];
    [self configureFaviconWithAttributes:attributes];
    return;
  }
  [NSLayoutConstraint deactivateConstraints:self.faviconContraints];
  self.faviconView.hidden = YES;
}

#pragma mark - Private

// Creates and sets up the view hierarchy.
- (void)createViewHierarchy {
  self.layoutGuide =
      AddLayoutGuideToContentView(self.contentView, /*cell_has_header=*/YES);

  self.selectionStyle = UITableViewCellSelectionStyleNone;

  NSMutableArray<NSLayoutConstraint*>* staticConstraints =
      [[NSMutableArray alloc] init];

  self.faviconView = IsKeyboardAccessoryUpgradeEnabled()
                         ? [[FaviconContainerView alloc] init]
                         : [[FaviconView alloc] init];
  self.faviconView.translatesAutoresizingMaskIntoConstraints = NO;
  self.faviconView.clipsToBounds = YES;
  self.faviconView.hidden = YES;
  self.faviconContraints = @[
    [self.faviconView.widthAnchor constraintEqualToConstant:GetFaviconSize()],
    [self.faviconView.heightAnchor
        constraintEqualToAnchor:self.faviconView.widthAnchor],
  ];

  self.siteNameLabel = CreateLabel();
  self.overflowMenuButton = CreateOverflowMenuButton();
  self.headerView = CreateHeaderView(self.faviconView, self.siteNameLabel,
                                     self.overflowMenuButton);
  [self.contentView addSubview:self.headerView];
  AppendHorizontalConstraintsForViews(staticConstraints, @[ self.headerView ],
                                      self.layoutGuide);

  self.grayLine = CreateGraySeparatorForContainer(self.contentView);

  self.plusAddressButton = CreateChipWithSelectorAndTarget(
      @selector(userDidTapPlusAddressButton:), self);
  [self.contentView addSubview:self.plusAddressButton];
  AppendHorizontalConstraintsForViews(
      staticConstraints, @[ self.plusAddressButton ], self.layoutGuide,
      kChipsHorizontalMargin,
      AppendConstraintsHorizontalEqualOrSmallerThanGuide);

  [NSLayoutConstraint activateConstraints:staticConstraints];
}

- (void)userDidTapPlusAddressButton:(UIButton*)button {
  // TODO(crbug.com/327838014): Implement.
}

// Configure the favicon with the given `attributes`.
- (void)configureFaviconWithAttributes:(FaviconAttributes*)attributes {
  FaviconView* favicon;
  if (IsKeyboardAccessoryUpgradeEnabled()) {
    FaviconContainerView* faviconContainerView =
        static_cast<FaviconContainerView*>(self.faviconView);
    favicon = faviconContainerView.faviconView;
  } else {
    favicon = static_cast<FaviconView*>(self.faviconView);
  }
  [favicon configureWithAttributes:attributes];
}

@end