chromium/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller.mm

// Copyright 2023 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_sharing/password_picker_view_controller.h"

#import "base/strings/sys_string_conversions.h"
#import "components/password_manager/core/browser/password_ui_utils.h"
#import "components/password_manager/core/browser/ui/credential_ui_entry.h"
#import "ios/chrome/browser/net/model/crurl.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_url_item.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_favicon_data_source.h"
#import "ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller_presentation_delegate.h"
#import "ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_constants.h"
#import "ios/chrome/common/ui/favicon/favicon_view.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util_mac.h"

namespace {

// Alpha for the disabled passkey cell.
const CGFloat kBackgroundDisabledAlpha = 0.4;

typedef NS_ENUM(NSInteger, SectionIdentifier) {
  SectionIdentifierCredentials = kSectionIdentifierEnumZero,
};

typedef NS_ENUM(NSInteger, ItemType) {
  ItemTypeCredential = kItemTypeEnumZero,
};

// Orders passwords before passkeys. For the same type of credentials defaults
// to the existing comparator.
bool CompareCredentialsByType(const password_manager::CredentialUIEntry& lhs,
                              const password_manager::CredentialUIEntry& rhs) {
  bool is_lhs_passkey = !lhs.passkey_credential_id.empty();
  bool is_rhs_passkey = !rhs.passkey_credential_id.empty();
  if (is_lhs_passkey != is_rhs_passkey) {
    return is_lhs_passkey < is_rhs_passkey;
  }
  return lhs < rhs;
}

}  // namespace

@interface PasswordPickerViewController () {
  std::vector<password_manager::CredentialUIEntry> _credentials;
}

@end

@implementation PasswordPickerViewController

#pragma mark - UIViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]
      initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
                           target:self
                           action:@selector(cancelButtonTapped)];
  self.navigationItem.leftBarButtonItem.accessibilityIdentifier =
      kPasswordPickerCancelButtonID;
  self.navigationItem.title =
      l10n_util::GetNSString(IDS_IOS_PASSWORD_SHARING_TITLE);
  UIBarButtonItem* nextButton = [[UIBarButtonItem alloc]
      initWithTitle:l10n_util::GetNSString(
                        IDS_IOS_PASSWORD_SHARING_PASSWORD_PICKER_NEXT_BUTTON)
              style:UIBarButtonItemStylePlain
             target:self
             action:@selector(nextButtonTapped)];
  self.navigationItem.rightBarButtonItem = nextButton;
  self.navigationItem.rightBarButtonItem.accessibilityIdentifier =
      kPasswordPickerNextButtonID;
  self.view.accessibilityIdentifier = kPasswordPickerViewID;

  [self loadModel];
}

- (void)loadModel {
  [super loadModel];

  TableViewModel* model = self.tableViewModel;
  [model addSectionWithIdentifier:SectionIdentifierCredentials];
  for (const password_manager::CredentialUIEntry& credential : _credentials) {
    [model addItem:[self credentialItem:credential]
        toSectionWithIdentifier:SectionIdentifierCredentials];
  }
}

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

  // Select first row by default.
  NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
  [self.tableView selectRowAtIndexPath:indexPath
                              animated:NO
                        scrollPosition:UITableViewScrollPositionNone];
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView
    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  [tableView cellForRowAtIndexPath:indexPath].accessoryType =
      UITableViewCellAccessoryCheckmark;
}

- (void)tableView:(UITableView*)tableView
    didDeselectRowAtIndexPath:(NSIndexPath*)indexPath {
  [tableView cellForRowAtIndexPath:indexPath].accessoryType =
      UITableViewCellAccessoryNone;
}

#pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView*)tableView
    numberOfRowsInSection:(NSInteger)section {
  return _credentials.size();
}

- (NSInteger)numberOfSectionsInTableView:(UITableView*)theTableView {
  return 1;
}

- (UITableViewCell*)tableView:(UITableView*)tableView
        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
  BOOL isPassword = _credentials[indexPath.row].passkey_credential_id.empty();
  UITableViewCell* cell = [super tableView:tableView
                     cellForRowAtIndexPath:indexPath];

  cell.selectionStyle = UITableViewCellSelectionStyleNone;
  cell.userInteractionEnabled = isPassword;
  cell.textLabel.numberOfLines = 1;
  cell.detailTextLabel.numberOfLines = 1;
  if (indexPath.row == tableView.indexPathForSelectedRow.row) {
    cell.accessoryType = UITableViewCellAccessoryCheckmark;
  } else {
    cell.accessoryType = UITableViewCellAccessoryNone;
  }
  if (!isPassword) {
    cell.contentView.alpha = kBackgroundDisabledAlpha;
  }

  TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
  TableViewURLItem* URLItem =
      base::apple::ObjCCastStrict<TableViewURLItem>(item);
  TableViewURLCell* URLCell =
      base::apple::ObjCCastStrict<TableViewURLCell>(cell);
  [self.imageDataSource
      faviconForPageURL:URLItem.URL
             completion:^(FaviconAttributes* attributes) {
               [URLCell.faviconView configureWithAttributes:attributes];
             }];

  return cell;
}

#pragma mark - PasswordPickerConsumer

- (void)setCredentials:
    (const std::vector<password_manager::CredentialUIEntry>&)credentials {
  _credentials = credentials;

  // Ensure that passkeys are at the end since they cannot be shared currently.
  std::sort(_credentials.begin(), _credentials.end(), CompareCredentialsByType);

  [self loadModel];
  [self.tableView reloadData];
}

#pragma mark - Items

- (TableViewURLItem*)credentialItem:
    (const password_manager::CredentialUIEntry&)credential {
  TableViewURLItem* item =
      [[TableViewURLItem alloc] initWithType:ItemTypeCredential];
  item.title = base::SysUTF16ToNSString(credential.username);
  item.URL = [[CrURL alloc] initWithGURL:GURL(credential.GetURL())];
  if (!credential.passkey_credential_id.empty()) {
    item.detailText = l10n_util::GetNSString(
        IDS_IOS_PASSWORD_SHARING_PASSWORD_PICKER_PASSKEY_INFO);
  }
  return item;
}

#pragma mark - Private

- (void)cancelButtonTapped {
  [self.delegate passwordPickerWasDismissed:self];
}

- (void)nextButtonTapped {
  [self.delegate
        passwordPickerClosed:self
      withSelectedCredential:_credentials[self.tableView.indexPathForSelectedRow
                                              .row]];
}

@end