chromium/ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_selection_view_controller.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/authentication/card_unmask_authentication_selection_view_controller.h"

#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_icon_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_link_header_footer_item.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_selection_constants.h"
#import "ios/chrome/browser/autofill/ui_bundled/cells/card_unmask_header_item.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ui/base/l10n/l10n_util.h"

namespace {

// The reuse identifier for detail icon cells.
const NSString* kDetailIconCellReuseIdentifier =
    @"DetailIconCellReuseIdentifier";

}  // namespace

// The section id for the challenge options.
static NSString* kSectionIdChallengeOptions = @"SectionIdChallengeOptions";

@implementation CardUnmaskAuthenticationSelectionViewController {
  NSString* _headerTitle;
  NSString* _headerText;
  NSString* _footerText;
  NSArray<CardUnmaskChallengeOptionIOS*>* _challengeOptions;
  NSInteger _selectedChallengeOptionIndex;
  UITableViewDiffableDataSource<NSString*, NSNumber*>*
      _challengeOptionsDataSource;
}

- (instancetype)init {
  return [super initWithStyle:UITableViewStyleInsetGrouped];
}

- (void)viewDidLoad {
  self.title = l10n_util::GetNSString(
      IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE_VERIFICATION);
  // Configure the cancel button.
  UIBarButtonItem* cancelBarButtonItem = [[UIBarButtonItem alloc]
      initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
                           target:self
                           action:@selector(didTapCancelButton)];
  cancelBarButtonItem.accessibilityIdentifier =
      kCardUnmaskAuthenticationSelectionCancelButtonAccessibilityIdentifier;
  self.navigationItem.leftBarButtonItem = cancelBarButtonItem;

  // Configure the table items.
  self.tableView.allowsSelection = YES;
  RegisterTableViewCell<TableViewDetailIconCell>(self.tableView);
  RegisterTableViewHeaderFooter<CardUnmaskHeaderView>(self.tableView);
  RegisterTableViewHeaderFooter<TableViewLinkHeaderFooterView>(self.tableView);
  __weak __typeof(self) weakSelf = self;
  _challengeOptionsDataSource = [[UITableViewDiffableDataSource alloc]
      initWithTableView:self.tableView
           cellProvider:^(UITableView* tableView, NSIndexPath* indexPath,
                          NSNumber* itemId) {
             return [weakSelf provideCellForTableView:tableView
                                            indexPath:indexPath
                                 challengeOptionIndex:itemId.integerValue];
           }];
  self.tableView.dataSource = _challengeOptionsDataSource;
  NSDiffableDataSourceSnapshot<NSString*, NSNumber*>* snapshot =
      [_challengeOptionsDataSource snapshot];
  [snapshot appendSectionsWithIdentifiers:@[ kSectionIdChallengeOptions ]];
  [snapshot appendItemsWithIdentifiers:[self arrayOfChallangeOptionIndicies]
             intoSectionWithIdentifier:kSectionIdChallengeOptions];
  [_challengeOptionsDataSource applySnapshotUsingReloadData:snapshot];
}

#pragma mark - CardUnmaskAuthenticationSelectionConsumer

- (void)setHeaderTitle:(NSString*)headerTitle {
  if ([_headerTitle isEqualToString:headerTitle]) {
    return;
  }
  _headerTitle = headerTitle;
  [self reloadSection];
}

- (void)setHeaderText:(NSString*)headerText {
  if ([_headerText isEqualToString:headerText]) {
    return;
  }
  _headerText = headerText;
  [self reloadSection];
}

- (void)setCardUnmaskOptions:
    (NSArray<CardUnmaskChallengeOptionIOS*>*)cardUnmaskChallengeOptions {
  if ([_challengeOptions isEqual:cardUnmaskChallengeOptions]) {
    return;
  }
  _challengeOptions = cardUnmaskChallengeOptions;
  [self reloadSection];
}

- (void)setFooterText:(NSString*)footerText {
  if ([_footerText isEqualToString:footerText]) {
    return;
  }
  _footerText = footerText;
  [self reloadSection];
}

- (void)setChallengeAcceptanceLabel:(NSString*)challengeAcceptanceLabel {
  UIBarButtonItem* barButtonItem = [[UIBarButtonItem alloc]
      initWithTitle:challengeAcceptanceLabel
              style:UIBarButtonItemStylePlain
             target:self
             action:@selector(didTapChallengeAcceptanceButton)];
  barButtonItem.accessibilityIdentifier =
      kCardUnmaskAuthenticationSelectionAcceptanceButtonAccessibilityIdentifier;
  self.navigationItem.rightBarButtonItem = barButtonItem;
}

- (void)enterPendingState {
  self.tableView.allowsSelection = NO;
  UIActivityIndicatorView* activityIndicator = [[UIActivityIndicatorView alloc]
      initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
  [activityIndicator startAnimating];
  self.navigationItem.rightBarButtonItem =
      [[UIBarButtonItem alloc] initWithCustomView:activityIndicator];
}

#pragma mark - UITableViewDelegate

- (UITableViewCell*)tableView:(UITableView*)tableView
        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
  // Override the default selection style.
  UITableViewCell* cell = [super tableView:tableView
                     cellForRowAtIndexPath:indexPath];
  cell.selectionStyle = UITableViewCellSelectionStyleNone;
  return cell;
}

- (void)tableView:(UITableView*)tableView
    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  // Set a checkmark on the cell when being selected.
  CHECK((NSUInteger)indexPath.row < [_challengeOptions count]);
  [self setSelectedChallengeOptionIndex:indexPath.row];
  [self.mutator didSelectChallengeOption:_challengeOptions[indexPath.row]];
}

- (UIView*)tableView:(UITableView*)tableView
    viewForHeaderInSection:(NSInteger)section {
  CardUnmaskHeaderView* view =
      DequeueTableViewHeaderFooter<CardUnmaskHeaderView>(self.tableView);
  view.titleLabel.text = _headerTitle;
  view.instructionsLabel.text = _headerText;
  return view;
}

- (UIView*)tableView:(UITableView*)tableView
    viewForFooterInSection:(NSInteger)section {
  TableViewLinkHeaderFooterView* view =
      DequeueTableViewHeaderFooter<TableViewLinkHeaderFooterView>(
          self.tableView);
  [view setText:_footerText withColor:[UIColor colorNamed:kTextSecondaryColor]];
  [view setForceIndents:YES];
  return view;
}

#pragma mark - Private

// Deques and sets up a cell for the challenge option at index.
- (UITableViewCell*)provideCellForTableView:(UITableView*)tableView
                                  indexPath:(NSIndexPath*)indexPath
                       challengeOptionIndex:(NSInteger)challengeOptionIndex {
  TableViewDetailIconCell* cell =
      DequeueTableViewCell<TableViewDetailIconCell>(self.tableView);
  cell.textLabel.text = _challengeOptions[indexPath.row].modeLabel;
  [cell setDetailText:_challengeOptions[indexPath.row].challengeInfo];

  [cell setAccessoryType:_selectedChallengeOptionIndex == challengeOptionIndex
                             ? UITableViewCellAccessoryCheckmark
                             : UITableViewCellAccessoryNone];
  [cell setTextLayoutConstraintAxis:UILayoutConstraintAxisVertical];

  [cell setDetailTextNumberOfLines:0];  // Use as many lines as needed.
  [cell setIconCenteredVertically:YES];
  return cell;
}

// Reloads the challenge options section on the challenge options data source.
- (void)reloadSection {
  NSDiffableDataSourceSnapshot<NSString*, NSNumber*>* snapshot =
      [_challengeOptionsDataSource snapshot];
  [snapshot reloadSectionsWithIdentifiers:@[ kSectionIdChallengeOptions ]];
  [_challengeOptionsDataSource
      applySnapshotUsingReloadData:[_challengeOptionsDataSource snapshot]];
}

// Returns an array of challenge option indicies.
- (NSArray*)arrayOfChallangeOptionIndicies {
  NSMutableArray<NSNumber*>* identifiers =
      [NSMutableArray arrayWithCapacity:[_challengeOptions count]];
  for (NSUInteger i = 0; i < [_challengeOptions count]; i++) {
    [identifiers addObject:[NSNumber numberWithUnsignedInteger:i]];
  }
  return identifiers;
}

// Sets the selected challenge option, updating the circle/checkmark icon.
- (void)setSelectedChallengeOptionIndex:(NSInteger)selectedIndex {
  if (_selectedChallengeOptionIndex == selectedIndex) {
    return;
  }
  NSDiffableDataSourceSnapshot<NSString*, NSNumber*>* snapshot =
      [_challengeOptionsDataSource snapshot];
  [snapshot reloadItemsWithIdentifiers:@[ @(_selectedChallengeOptionIndex) ]];
  _selectedChallengeOptionIndex = selectedIndex;
  [snapshot reloadItemsWithIdentifiers:@[ @(_selectedChallengeOptionIndex) ]];
  [_challengeOptionsDataSource applySnapshotUsingReloadData:snapshot];
}

// This method is a callback for the right bar button item used to accept the
// selected challenge option.
- (void)didTapChallengeAcceptanceButton {
  [self.mutator didAcceptSelection];
}

// This method is a callback for the left bar button, a cancel button.
- (void)didTapCancelButton {
  [self.mutator didCancelSelection];
}

@end