chromium/ios/chrome/browser/ui/settings/password/password_details/cells/table_view_stacked_details_item.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/ui/settings/password/password_details/cells/table_view_stacked_details_item.h"

#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"

@implementation TableViewStackedDetailsItem

- (instancetype)initWithType:(NSInteger)type {
  self = [super initWithType:type];
  if (self) {
    self.cellClass = [TableViewStackedDetailsCell class];
  }
  return self;
}

- (void)configureCell:(TableViewStackedDetailsCell*)cell
           withStyler:(ChromeTableViewStyler*)styler {
  [super configureCell:cell withStyler:styler];

  cell.titleLabel.text = self.titleText;

  [cell setDetails:self.detailTexts];
  [cell setDetailTextColor:self.detailTextColor
                               ? self.detailTextColor
                               : [UIColor colorNamed:kTextPrimaryColor]];

  cell.accessibilityLabel = [self accessibilityLabelForCell];
}

#pragma mark - Private

// Builds the accessibility label of the cell, concatenating the title and
// details texts.
- (NSString*)accessibilityLabelForCell {
  NSMutableString* accessibilityLabelText =
      [NSMutableString stringWithString:self.titleText];

  for (NSString* detailsText in self.detailTexts) {
    [accessibilityLabelText appendFormat:@", %@", detailsText];
  }

  return accessibilityLabelText;
}

@end

@interface TableViewStackedDetailsCell () {
  UIStackView* _columnsStackView;
  UIStackView* _detailsStackView;
  NSMutableArray<UILabel*>* _detailLabels;
}

@end

@implementation TableViewStackedDetailsCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style
              reuseIdentifier:(NSString*)reuseIdentifier {
  self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];

  if (self) {
    self.isAccessibilityElement = YES;
    _detailLabels = [[NSMutableArray<UILabel*> alloc] init];
    [self addStaticViews];
    [self setTitleStyling];
  }

  return self;
}

#pragma mark - Public

- (void)setDetails:(NSArray<NSString*>*)detailTexts {
  [self createLabelsForDetails:detailTexts];
  [self setDetailsStyling];
  [self updateForAccessibilityContentSizeCategory:
            UIContentSizeCategoryIsAccessibilityCategory(
                self.traitCollection.preferredContentSizeCategory)];
}

- (void)setDetailTextColor:(UIColor*)detailTextColor {
  for (UILabel* detailsLabel in self.detailLabels) {
    detailsLabel.textColor = detailTextColor;
  }
}

#pragma mark - UITableViewCell

- (void)prepareForReuse {
  [super prepareForReuse];

  _titleLabel.text = nil;

  // Discard existing details labels.
  for (UILabel* detailLabel in self.detailLabels) {
    [_detailsStackView removeArrangedSubview:detailLabel];
    [detailLabel removeFromSuperview];
  }
  [_detailLabels removeAllObjects];
}

- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
  [super traitCollectionDidChange:previousTraitCollection];
  BOOL isCurrentCategoryAccessibility =
      UIContentSizeCategoryIsAccessibilityCategory(
          self.traitCollection.preferredContentSizeCategory);
  if (isCurrentCategoryAccessibility !=
      UIContentSizeCategoryIsAccessibilityCategory(
          previousTraitCollection.preferredContentSizeCategory)) {
    [self updateForAccessibilityContentSizeCategory:
              isCurrentCategoryAccessibility];
  }
}

#pragma mark - Private

// Adds the title and container views to the view hierarchy.
- (void)addStaticViews {
  _titleLabel = [[UILabel alloc] init];
  // Let the title take up any extra horizontal space.
  [_titleLabel setContentHuggingPriority:UILayoutPriorityDefaultLow
                                 forAxis:UILayoutConstraintAxisHorizontal];

  // Make sure the title is always displayed. Details will be clipped if there
  // is not enough width.
  [_titleLabel
      setContentCompressionResistancePriority:UILayoutPriorityRequired
                                      forAxis:UILayoutConstraintAxisHorizontal];

  _detailsStackView = [[UIStackView alloc] init];
  _detailsStackView.translatesAutoresizingMaskIntoConstraints = NO;
  _detailsStackView.axis = UILayoutConstraintAxisVertical;

  _columnsStackView = [[UIStackView alloc]
      initWithArrangedSubviews:@[ _titleLabel, _detailsStackView ]];
  _columnsStackView.translatesAutoresizingMaskIntoConstraints = NO;

  UIView* contentView = self.contentView;
  [contentView addSubview:_columnsStackView];

  NSDirectionalEdgeInsets columnsStackViewInsets = NSDirectionalEdgeInsetsMake(
      kTableViewOneLabelCellVerticalSpacing, kTableViewHorizontalSpacing,
      kTableViewOneLabelCellVerticalSpacing, kTableViewHorizontalSpacing);

  AddSameConstraintsWithInsets(_columnsStackView, contentView,
                               columnsStackViewInsets);
}

// Set styling for title label.
- (void)setTitleStyling {
  _titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
  _titleLabel.adjustsFontForContentSizeCategory = YES;
  _titleLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
}

// Set styling for details labels.
- (void)setDetailsStyling {
  for (UILabel* detailsLabel in _detailLabels) {
    detailsLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    detailsLabel.adjustsFontForContentSizeCategory = YES;
  }
}

// Creates details labels and adds them to the view hierarchy.
- (void)createLabelsForDetails:(NSArray<NSString*>*)detailTexts {
  NSInteger missingLabels = detailTexts.count - _detailLabels.count;

  // Make sure there is one label per detail text.
  while (missingLabels > 0) {
    // Add enough labels for each details text.
    UILabel* newLabel = [[UILabel alloc] init];
    [newLabel
        setContentCompressionResistancePriority:UILayoutPriorityRequired
                                        forAxis:UILayoutConstraintAxisVertical];
    newLabel.textAlignment = NSTextAlignmentLeft;
    [_detailLabels addObject:newLabel];
    [_detailsStackView addArrangedSubview:newLabel];

    missingLabels--;
  }

  // If there are too many labels, remove them until they match the texts to
  // display.
  while (missingLabels < 0) {
    UILabel* lastLabel = _detailLabels.lastObject;
    [lastLabel removeFromSuperview];
    [_detailsStackView removeArrangedSubview:lastLabel];
    [_detailLabels removeLastObject];

    missingLabels++;
  }

  for (NSUInteger index = 0; index < detailTexts.count; index++) {
    _detailLabels[index].text = detailTexts[index];
  }
}

// Updates the cell such as it is laid out correctly with regard to the
// preferred content size category, if it is an
// `accessibilityContentSizeCategory` or not.
- (void)updateForAccessibilityContentSizeCategory:
    (BOOL)accessibilityContentSizeCategory {
  if (accessibilityContentSizeCategory) {
    _columnsStackView.axis = UILayoutConstraintAxisVertical;
    _columnsStackView.alignment = UIStackViewAlignmentLeading;
    _detailsStackView.alignment = UIStackViewAlignmentLeading;
    _columnsStackView.spacing = kTableViewVerticalSpacing;

  } else {
    _columnsStackView.axis = UILayoutConstraintAxisHorizontal;
    _columnsStackView.alignment = _detailLabels.count == 1
                                      ? UIStackViewAlignmentCenter
                                      : UIStackViewAlignmentTop;
    _detailsStackView.alignment = UIStackViewAlignmentTrailing;
    _columnsStackView.spacing = kTableViewSubViewHorizontalSpacing;
  }
}

@end