chromium/ios/chrome/browser/ui/settings/google_services/manage_accounts/accounts_table_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/ui/settings/google_services/manage_accounts/accounts_table_view_controller.h"

#import "base/apple/foundation_util.h"
#import "base/metrics/user_metrics.h"
#import "base/notreached.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/signin/model/constants.h"
#import "ios/chrome/browser/ui/authentication/cells/table_view_account_item.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_accounts/accounts_model_identity_data_source.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_accounts/accounts_mutator.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_accounts/accounts_table_view_controller_constants.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_accounts/identity_view_item.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util_mac.h"

namespace {

typedef NS_ENUM(NSInteger, EditAccountListSectionIdentifier) {
  SectionIdentifierPrimaryAccount = kSectionIdentifierEnumZero,
  SectionIdentifierSecondaryAccount,
};

typedef NS_ENUM(NSInteger, EditAccountListItemType) {
  // Account item.
  ItemTypeAccount = kItemTypeEnumZero,
  // Remove account item.
  ItemTypeRemoveAccount,
  // Add account item.
  ItemTypeAddAccount,
  // Sign out item.
  ItemTypeSignOut,
};

}  // namespace

@interface AccountsTableViewController () {
  // Whether to close the view after adding an account.
  BOOL _closeSettingsOnAddAccount;

  // ApplicationCommands handler.
  id<ApplicationCommands> _applicationHandler;

  // Enable lookup of item corresponding to a given IdentityViewItem GAIA ID
  // string.
  NSDictionary<NSString*, TableViewItem*>* _identityMap;

  // If YES, offers the sign-out button on the UI.
  BOOL _offerSignout;
}

@end

@implementation AccountsTableViewController

@synthesize modelIdentityDataSource;

- (instancetype)initWithCloseSettingsOnAddAccount:
                    (BOOL)closeSettingsOnAddAccount
                       applicationCommandsHandler:
                           (id<ApplicationCommands>)applicationCommandsHandler
                                     offerSignout:(BOOL)offerSignout {
  self = [super initWithStyle:ChromeTableViewStyle()];
  if (self) {
    _closeSettingsOnAddAccount = closeSettingsOnAddAccount;
    _applicationHandler = applicationCommandsHandler;
    _offerSignout = offerSignout;
  }

  return self;
}

#pragma mark - UIViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  self.tableView.accessibilityIdentifier = kSettingsEditAccountListTableViewId;

  [self loadModel];
}

#pragma mark - SettingsRootTableViewController

- (void)loadModel {
  self.title = l10n_util::GetNSString(IDS_IOS_ACCOUNT_MENU_EDIT_ACCOUNT_LIST);

  [super loadModel];

  TableViewModel* model = self.tableViewModel;

  NSMutableDictionary<NSString*, TableViewItem*>* mutableIdentityMap =
      [[NSMutableDictionary alloc] init];

  NSString* authenticatedEmail =
      [self.modelIdentityDataSource primaryIdentityViewItem].userEmail;

  NSInteger nextSection = SectionIdentifierSecondaryAccount;

  for (const auto& identityViewItem :
       [self.modelIdentityDataSource identityViewItems]) {
    CHECK(identityViewItem);

    TableViewItem* accountItem =
        [self accountItemWithIdentityViewItem:identityViewItem];
    TableViewItem* removeAccountItem =
        [self removeAccountItemWithIdentityViewItem:identityViewItem];
    NSInteger sectionNumber;
    if ([identityViewItem.userEmail isEqualToString:authenticatedEmail]) {
      [model insertSectionWithIdentifier:SectionIdentifierPrimaryAccount
                                 atIndex:0];
      sectionNumber = SectionIdentifierPrimaryAccount;
    } else {
      [model addSectionWithIdentifier:nextSection];
      sectionNumber = nextSection++;
    }
    [model addItem:accountItem toSectionWithIdentifier:sectionNumber];
    [model addItem:removeAccountItem toSectionWithIdentifier:sectionNumber];
    [mutableIdentityMap setObject:accountItem forKey:identityViewItem.gaiaID];
  }

  TableViewItem* addAccountItem = [self addAccountItem];
  [model addSectionWithIdentifier:nextSection];
  [model addItem:addAccountItem toSectionWithIdentifier:nextSection++];

  if (_offerSignout) {
    TableViewItem* signOutItem = [self signOutItem];
    [model addSectionWithIdentifier:nextSection];
    [model addItem:signOutItem toSectionWithIdentifier:nextSection++];
  }

  _identityMap = mutableIdentityMap;
}

#pragma mark - Model objects

- (TableViewItem*)accountItemWithIdentityViewItem:
    (IdentityViewItem*)identityViewItem {
  TableViewAccountItem* item =
      [[TableViewAccountItem alloc] initWithType:ItemTypeAccount];
  [self updateAccountItem:item withIdentityViewItem:identityViewItem];
  return item;
}

- (void)updateAccountItem:(TableViewAccountItem*)item
     withIdentityViewItem:(IdentityViewItem*)identityViewItem {
  item.image = identityViewItem.avatar;
  item.text = identityViewItem.userEmail;
  item.accessibilityIdentifier = identityViewItem.accessibilityIdentifier;
  item.mode = TableViewAccountModeNonTappable;
  item.accessoryType = UITableViewCellAccessoryNone;
}

- (TableViewItem*)removeAccountItemWithIdentityViewItem:
    (IdentityViewItem*)identityViewItem {
  TableViewTextItem* item =
      [[TableViewTextItem alloc] initWithType:ItemTypeRemoveAccount];
  item.text = l10n_util::GetNSString(IDS_IOS_REMOVE_GOOGLE_ACCOUNT_TITLE);
  item.accessibilityIdentifier =
      [kSettingsAccountsRemoveAccountButtonAccessibilityIdentifier
          stringByAppendingString:identityViewItem.accessibilityIdentifier];
  item.textColor = [UIColor colorNamed:kBlueColor];
  return item;
}

- (TableViewItem*)addAccountItem {
  TableViewTextItem* item =
      [[TableViewTextItem alloc] initWithType:ItemTypeAddAccount];
  item.text =
      l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_ADD_ACCOUNT_BUTTON);
  item.accessibilityIdentifier = kSettingsAccountsTableViewAddAccountCellId;
  item.textColor = [UIColor colorNamed:kBlueColor];
  return item;
}

- (TableViewItem*)signOutItem {
  TableViewTextItem* item =
      [[TableViewTextItem alloc] initWithType:ItemTypeSignOut];
  item.text =
      l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE);
  item.accessibilityIdentifier = kSettingsAccountsTableViewSignoutCellId;
  item.textColor = [UIColor colorNamed:kBlueColor];
  return item;
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView
    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  [super tableView:tableView didSelectRowAtIndexPath:indexPath];

  EditAccountListItemType itemType = static_cast<EditAccountListItemType>(
      [self.tableViewModel itemTypeForIndexPath:indexPath]);
  switch (itemType) {
    case ItemTypeAccount:
      return;
    case ItemTypeRemoveAccount: {
      NSIndexPath* accountItemIndexPath =
          [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
      TableViewAccountItem* accountItem =
          base::apple::ObjCCastStrict<TableViewAccountItem>(
              [self.tableViewModel itemAtIndexPath:accountItemIndexPath]);
      UIView* itemView =
          [[tableView cellForRowAtIndexPath:indexPath] contentView];
      [self.mutator
          requestRemoveIdentityWithGaiaID:[self
                                              gaiaIDWithAccountItem:accountItem]
                                 itemView:itemView];
      break;
    }
    case ItemTypeAddAccount:
      base::RecordAction(
          base::UserMetricsAction("Signin_AccountsTableView_AddAccount"));
      [self.mutator requestAddIdentityToDevice];
      break;
    case ItemTypeSignOut:
      base::RecordAction(
          base::UserMetricsAction("Signin_AccountsTableView_SignOut"));
      UIView* itemView =
          [[tableView cellForRowAtIndexPath:indexPath] contentView];
      [self.mutator requestSignOutWithItemView:itemView];
      break;
  }

  [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}

#pragma mark - AccountsConsumer

- (void)reloadAllItems {
  if (!self.tableViewModel) {
    // No need to reload since the model has not been loaded yet.
    return;
  }
  [self reloadData];
}

- (void)updateErrorSectionModelAndReloadViewIfNeeded:(BOOL)reloadViewIfNeeded {
  return;
}

- (void)updateIdentityViewItem:(IdentityViewItem*)identityViewItem {
  TableViewAccountItem* item =
      base::apple::ObjCCastStrict<TableViewAccountItem>(
          [_identityMap objectForKey:identityViewItem.gaiaID]);
  if (!item) {
    return;
  }
  [self updateAccountItem:item withIdentityViewItem:identityViewItem];
  NSIndexPath* indexPath = [self.tableViewModel indexPathForItem:item];
  [self.tableView reloadRowsAtIndexPaths:@[ indexPath ]
                        withRowAnimation:UITableViewRowAnimationAutomatic];
}

- (void)popView {
  // TODO (crbug.com/349071402): implement popView on sign-out.
}

#pragma mark - AccountsConsumer

- (NSString*)gaiaIDWithAccountItem:(TableViewItem*)accountItem {
  for (NSString* gaiaID in _identityMap) {
    if ([_identityMap[gaiaID] isEqual:accountItem]) {
      return gaiaID;
    }
  }
  return nil;
}

@end