blob: afe60f993227c285041fa96ceb0950f9ee2a38d4 [file] [log] [blame]
// 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/authentication/ui_bundled/account_menu/account_menu_view_controller.h"
#import "base/apple/foundation_util.h"
#import "base/check.h"
#import "base/check_op.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_constants.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_data_source.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_mutator.h"
#import "ios/chrome/browser/authentication/ui_bundled/cells/central_account_view.h"
#import "ios/chrome/browser/authentication/ui_bundled/cells/table_view_account_item.h"
#import "ios/chrome/browser/keyboard/ui_bundled/UIKeyCommand+Chrome.h"
#import "ios/chrome/browser/policy/model/management_state.h"
#import "ios/chrome/browser/settings/model/sync/utils/account_error_ui_info.h"
#import "ios/chrome/browser/settings/ui_bundled/cells/settings_image_detail_text_cell.h"
#import "ios/chrome/browser/shared/ui/list_model/list_model.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.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/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/image_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"
const char kEditAccountListIdentifier[] = "kEditAccountListIdentifier";
const char kManageYourGoogleAccountIdentifier[] =
"kManageYourGoogleAccountIdentifier";
namespace {
// The margin between the cell and the sheet.
constexpr CGFloat kSideMargins = 16.;
// Size of the symbols.
constexpr CGFloat kErrorSymbolSize = 22.;
// Height and width of the buttons.
constexpr CGFloat kButtonSize = 30.;
// The height of the footer of sections, except for last section.
constexpr CGFloat kFooterHeight = 16.;
// The left separator inset between two secondary accounts.
constexpr CGFloat kSecondaryAccountsLeftSeparatorInset = 16.;
// The left separator inset between the last secondary account and Add Account.
constexpr CGFloat kLastSecondaryAccountLeftSeparatorInset = 60.;
// Per Apple guidelines, touch targets should be at least 44x44.
constexpr CGFloat kMinimumTouchTargetSize = 44.0;
// The corner radius of the half sheet.
constexpr CGFloat kHalfSheetCornerRadius = 10.0;
// Sections used in the account menu.
typedef NS_ENUM(NSUInteger, SectionIdentifier) {
// Sync errors.
SyncErrorsSectionIdentifier = kSectionIdentifierEnumZero,
// List of accounts.
AccountsSectionIdentifier,
// Manage accounts ans sign-out.
SignOutSectionIdentifier,
};
typedef NS_ENUM(NSUInteger, RowIdentifier) {
// Error section
RowIdentifierErrorExplanation = kItemTypeEnumZero,
RowIdentifierErrorButton,
// Signout section
RowIdentifierSignOut,
// Accounts section.
RowIdentifierAddAccount,
// The secondary account entries use the gaia ID as item identifier.
};
// Custom detent identifier for when the bottom sheet is minimized.
NSString* const kCustomMinimizedDetentIdentifier = @"customMinimizedDetent";
// Custom detent identifier for when the bottom sheet is expanded.
NSString* const kCustomExpandedDetentIdentifier = @"customExpandedDetent";
} // namespace
@interface AccountMenuViewController () <UITableViewDelegate>
@property(nonatomic, strong) UITableView* tableView;
@end
@implementation AccountMenuViewController {
UITableViewDiffableDataSource* _accountMenuDataSource;
UIButton* _closeButton;
UIButton* _ellipsisButton;
CentralAccountView* _identityAccountView;
// The index path of the cell on which the user tapped while account switching
// is in progress. It should be reset to nil before any table content occurs.
NSIndexPath* _selectedIndexPath;
// Set to true when the content is set and it is possible to compute the size
// of the popover.
// If preferredContentSize is set with different values in the same runloop,
// UIKit will pick the biggest or the first one (but not the last one).
BOOL _resizeReady;
}
#pragma mark - UIViewController
- (void)viewDidLoad {
[super viewDidLoad];
_resizeReady = NO;
self.tableView =
[[UITableView alloc] initWithFrame:CGRectZero
style:UITableViewStyleInsetGrouped];
self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.tableView];
[NSLayoutConstraint activateConstraints:@[
[self.view.topAnchor constraintEqualToAnchor:self.tableView.topAnchor],
[self.view.bottomAnchor
constraintEqualToAnchor:self.tableView.bottomAnchor],
[self.view.trailingAnchor
constraintEqualToAnchor:self.tableView.trailingAnchor],
[self.view.leadingAnchor
constraintEqualToAnchor:self.tableView.leadingAnchor],
]];
self.tableView.delegate = self;
self.tableView.accessibilityIdentifier = kAccountMenuTableViewId;
self.tableView.backgroundColor =
[UIColor colorNamed:kGroupedPrimaryBackgroundColor];
RegisterTableViewCell<TableViewAccountCell>(self.tableView);
RegisterTableViewCell<SettingsImageDetailTextCell>(self.tableView);
RegisterTableViewCell<TableViewTextCell>(self.tableView);
[self setUpTopButtons];
[self setUpTableContent];
[self updatePrimaryAccount];
self.tableView.tableFooterView = [[UIView alloc]
initWithFrame:CGRectMake(0, 0, CGFLOAT_EPSILON, CGFLOAT_EPSILON)];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// Update the bottom sheet height.
[self resize];
}
- (void)viewIsAppearing:(BOOL)animated {
[super viewIsAppearing:animated];
_resizeReady = YES;
}
#pragma mark - Private
// Sets the activity indicator.
- (void)setActivityIndicator:(TableViewAccountCell*)cell {
UIActivityIndicatorView* activityIndicatorView =
[[UIActivityIndicatorView alloc] init];
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO;
activityIndicatorView.color = [UIColor colorNamed:kTextSecondaryColor];
[cell setStatusView:activityIndicatorView];
[activityIndicatorView startAnimating];
}
// Returns the height of the navigation bar.
- (CGFloat)navigationBarHeight {
return self.navigationController.navigationBar.frame.size.height;
}
// Resizes the view for current content.
- (void)resize {
if (!_resizeReady) {
return;
}
// Update the bottom sheet height.
[self.sheetPresentationController invalidateDetents];
// Update the popover height.
[_identityAccountView updateTopPadding:[self navigationBarHeight]];
// Force the layout of the TableView to make sure that it has the right width
// before using its contentSize.
[self.tableView setNeedsLayout];
[self.tableView layoutIfNeeded];
CGFloat height = self.tableView.contentSize.height;
self.preferredContentSize = CGSize(self.preferredContentSize.width, height);
}
// Creates a button for the navigation bar.
- (UIButton*)addTopButtonWithSymbolName:(NSString*)symbolName
symbolConfiguration:
(UIImageSymbolConfiguration*)symbolConfiguration
isLeading:(BOOL)isLeading
accessibilityIdentifier:(NSString*)accessibilityIdentifier {
NSArray<UIColor*>* colors = @[
[UIColor colorNamed:kTextSecondaryColor],
[UIColor colorNamed:kUpdatedTertiaryBackgroundColor]
];
UIImage* image = SymbolWithPalette(
DefaultSymbolWithConfiguration(symbolName, symbolConfiguration), colors);
// Add padding on all sides of the button, to make it a 44x44 touch target.
CGFloat verticalInsets = (kMinimumTouchTargetSize - image.size.height) / 2.0;
CGFloat horizontalInsets = (kMinimumTouchTargetSize - image.size.width) / 2.0;
CGFloat distanceToSide = kSideMargins - horizontalInsets;
CGFloat distanceToTop = kSideMargins - verticalInsets;
UIButtonConfiguration* buttonConfiguration =
[UIButtonConfiguration plainButtonConfiguration];
buttonConfiguration.contentInsets = NSDirectionalEdgeInsetsMake(
verticalInsets, horizontalInsets, verticalInsets, horizontalInsets);
UIButton* button = [UIButton buttonWithConfiguration:buttonConfiguration
primaryAction:nil];
[button setImage:image forState:UIControlStateNormal];
button.translatesAutoresizingMaskIntoConstraints = NO;
button.accessibilityIdentifier = accessibilityIdentifier;
[self.navigationController.navigationBar addSubview:button];
if (isLeading) {
[button.leadingAnchor
constraintEqualToAnchor:self.navigationController.navigationBar
.leadingAnchor
constant:distanceToSide]
.active = YES;
} else {
[button.trailingAnchor
constraintEqualToAnchor:self.navigationController.navigationBar
.trailingAnchor
constant:-distanceToSide]
.active = YES;
}
[button.topAnchor
constraintEqualToAnchor:self.navigationController.navigationBar.topAnchor
constant:distanceToTop]
.active = YES;
return button;
}
// Sets up the buttons.
- (void)setUpTopButtons {
UIImageSymbolConfiguration* symbolConfiguration = [UIImageSymbolConfiguration
configurationWithPointSize:kButtonSize
weight:UIImageSymbolWeightRegular
scale:UIImageSymbolScaleMedium];
// Stop button
UIUserInterfaceIdiom idiom = [[UIDevice currentDevice] userInterfaceIdiom];
if (idiom != UIUserInterfaceIdiomPad) {
_closeButton = [self addTopButtonWithSymbolName:kXMarkCircleFillSymbol
symbolConfiguration:symbolConfiguration
isLeading:NO
accessibilityIdentifier:kAccountMenuCloseButtonId];
[_closeButton addTarget:self
action:@selector(userTappedOnClose)
forControlEvents:UIControlEventTouchUpInside];
}
// Ellipsis button
UIAction* manageYourAccountAction = [UIAction
actionWithTitle:
l10n_util::GetNSString(
IDS_IOS_GOOGLE_ACCOUNT_SETTINGS_MANAGE_GOOGLE_ACCOUNT_ITEM)
image:DefaultSymbolWithConfiguration(@"arrow.up.right.square",
symbolConfiguration)
identifier:base::SysUTF8ToNSString(
kManageYourGoogleAccountIdentifier)
handler:^(UIAction* action) {
base::RecordAction(base::UserMetricsAction(
"Signin_AccountMenu_ManageAccount"));
[self.mutator didTapManageYourGoogleAccount];
}];
manageYourAccountAction.subtitle = [self.dataSource primaryAccountEmail];
UIAction* editAccountListAction = [UIAction
actionWithTitle:l10n_util::GetNSString(
IDS_IOS_ACCOUNT_MENU_EDIT_ACCOUNT_LIST)
image:DefaultSymbolWithConfiguration(@"pencil",
symbolConfiguration)
identifier:base::SysUTF8ToNSString(kEditAccountListIdentifier)
handler:^(UIAction* action) {
base::RecordAction(base::UserMetricsAction(
"Signin_AccountMenu_EditAccountList"));
[self.mutator didTapManageAccounts];
}];
UIMenu* ellipsisMenu = [UIMenu
menuWithChildren:@[ manageYourAccountAction, editAccountListAction ]];
_ellipsisButton =
[self addTopButtonWithSymbolName:kEllipsisCircleFillSymbol
symbolConfiguration:symbolConfiguration
isLeading:YES
accessibilityIdentifier:kAccountMenuSecondaryActionMenuButtonId];
_ellipsisButton.menu = ellipsisMenu;
_ellipsisButton.showsMenuAsPrimaryAction = true;
_ellipsisButton.accessibilityLabel =
l10n_util::GetNSString(IDS_IOS_ICON_OPTION_MENU);
}
// Configures and returns a cell.
- (UITableViewCell*)cellForTableView:(UITableView*)tableView
indexPath:(NSIndexPath*)indexPath
itemIdentifier:(id)itemIdentifier {
NSString* gaiaID = base::apple::ObjCCast<NSString>(itemIdentifier);
if (gaiaID) {
// `itemIdentifier` is a gaia id.
TableViewAccountCell* cell =
DequeueTableViewCell<TableViewAccountCell>(tableView);
cell.accessibilityTraits = UIAccessibilityTraitButton;
cell.imageView.image = [self.dataSource imageForGaiaID:gaiaID];
cell.textLabel.text = [self.dataSource nameForGaiaID:gaiaID];
NSString* email = [self.dataSource emailForGaiaID:gaiaID];
cell.detailTextLabel.text = email;
cell.detailTextLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
cell.accessibilityLabel = l10n_util::GetNSStringF(
IDS_IOS_OPTIONS_ACCOUNTS_SIGNIN_ACCESSIBILITY_LABEL,
base::SysNSStringToUTF16(email));
cell.userInteractionEnabled = YES;
cell.accessibilityIdentifier = kAccountMenuSecondaryAccountButtonId;
if ([indexPath isEqual:_selectedIndexPath]) {
// In theory, this can occur if, during the account switch process, the
// user scrolls a lot, and scroll back.
[self setActivityIndicator:cell];
}
BOOL lastSecondaryIdentity =
(indexPath.row == [_accountMenuDataSource tableView:self.tableView
numberOfRowsInSection:indexPath.section] -
2);
cell.separatorInset = UIEdgeInsetsMake(
0., /*left=*/
(lastSecondaryIdentity) ? kSecondaryAccountsLeftSeparatorInset
: kLastSecondaryAccountLeftSeparatorInset,
0., 0.);
return cell;
}
// Otherwise `itemIdentifier` is a `RowIdentifier`.
RowIdentifier rowIdentifier = static_cast<RowIdentifier>(
base::apple::ObjCCastStrict<NSNumber>(itemIdentifier).integerValue);
NSString* label = nil;
NSString* accessibilityIdentifier = nil;
NSString* accessibilityLabel = nil;
switch (rowIdentifier) {
case RowIdentifierErrorExplanation: {
SettingsImageDetailTextCell* cell =
DequeueTableViewCell<SettingsImageDetailTextCell>(tableView);
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.accessibilityIdentifier = kAccountMenuErrorMessageId;
cell.accessibilityElementsHidden = YES;
cell.detailTextLabel.text =
l10n_util::GetNSString(self.dataSource.accountErrorUIInfo.messageID);
cell.image =
DefaultSymbolWithPointSize(kErrorCircleFillSymbol, kErrorSymbolSize);
cell.detailTextLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
[cell setImageViewTintColor:[UIColor colorNamed:kRed500Color]];
return cell;
}
case RowIdentifierErrorButton:
label = l10n_util::GetNSString(
self.dataSource.accountErrorUIInfo.buttonLabelID);
accessibilityLabel =
l10n_util::GetNSString(self.dataSource.accountErrorUIInfo.messageID);
accessibilityIdentifier = kAccountMenuErrorActionButtonId;
break;
case RowIdentifierAddAccount:
label =
l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_ADD_ACCOUNT_BUTTON);
accessibilityIdentifier = kAccountMenuAddAccountButtonId;
break;
case RowIdentifierSignOut:
label =
l10n_util::GetNSString(IDS_IOS_GOOGLE_ACCOUNT_SETTINGS_SIGN_OUT_ITEM);
accessibilityIdentifier = kAccountMenuSignoutButtonId;
break;
default:
NOTREACHED();
}
// If the function has not returned yet. This cell contains only text.
TableViewTextCell* cell = DequeueTableViewCell<TableViewTextCell>(tableView);
cell.accessibilityTraits = UIAccessibilityTraitButton;
cell.isAccessibilityElement = YES;
cell.textLabel.text = label;
cell.accessibilityLabel = accessibilityLabel ? accessibilityLabel : label;
cell.textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
cell.textLabel.textColor = [UIColor colorNamed:kBlueColor];
cell.userInteractionEnabled = YES;
cell.accessibilityIdentifier = accessibilityIdentifier;
return cell;
}
// Sets up bottom sheet presentation controller.
- (void)setUpBottomSheetPresentationController {
UISheetPresentationController* presentationController =
self.sheetPresentationController;
presentationController.prefersEdgeAttachedInCompactHeight = YES;
presentationController.widthFollowsPreferredContentSizeWhenEdgeAttached = YES;
presentationController.preferredCornerRadius = kHalfSheetCornerRadius;
__weak __typeof(self) weakSelf = self;
auto preferredHeightForSheetContent = ^CGFloat(
id<UISheetPresentationControllerDetentResolutionContext> context) {
return [weakSelf preferredHeightForSheetContent];
};
UISheetPresentationControllerDetent* customDetent =
[UISheetPresentationControllerDetent
customDetentWithIdentifier:kCustomMinimizedDetentIdentifier
resolver:preferredHeightForSheetContent];
presentationController.detents = @[ customDetent ];
presentationController.selectedDetentIdentifier =
kCustomMinimizedDetentIdentifier;
}
// Handles tapping on Close button.
- (void)userTappedOnClose {
base::RecordAction(base::UserMetricsAction("Signin_AccountMenu_Close"));
[self.mutator viewControllerWantsToBeClosed:self];
}
// Sets up table content.
- (void)setUpTableContent {
// Configure the table items.
__weak __typeof(self) weakSelf = self;
_accountMenuDataSource = [[UITableViewDiffableDataSource alloc]
initWithTableView:self.tableView
cellProvider:^UITableViewCell*(UITableView* tableView,
NSIndexPath* indexPath, id itemId) {
return [weakSelf cellForTableView:tableView
indexPath:indexPath
itemIdentifier:itemId];
}];
self.tableView.dataSource = _accountMenuDataSource;
NSDiffableDataSourceSnapshot* snapshot =
[[NSDiffableDataSourceSnapshot alloc] init];
AccountErrorUIInfo* error = self.dataSource.accountErrorUIInfo;
if (error) {
[snapshot appendSectionsWithIdentifiers:@[
@(SyncErrorsSectionIdentifier),
]];
[snapshot appendItemsWithIdentifiers:@[
@(RowIdentifierErrorExplanation), @(RowIdentifierErrorButton)
]
intoSectionWithIdentifier:@(SyncErrorsSectionIdentifier)];
}
[snapshot appendSectionsWithIdentifiers:@[ @(AccountsSectionIdentifier) ]];
NSMutableArray* accountsIdentifiers = [[NSMutableArray alloc] init];
NSArray<NSString*>* gaiaIDs = self.dataSource.secondaryAccountsGaiaIDs;
for (NSString* gaiaID in gaiaIDs) {
[accountsIdentifiers addObject:gaiaID];
}
[accountsIdentifiers addObject:@(RowIdentifierAddAccount)];
[snapshot appendItemsWithIdentifiers:accountsIdentifiers
intoSectionWithIdentifier:@(AccountsSectionIdentifier)];
[snapshot appendSectionsWithIdentifiers:@[ @(SignOutSectionIdentifier) ]];
[snapshot appendItemsWithIdentifiers:@[ @(RowIdentifierSignOut) ]
intoSectionWithIdentifier:@(SignOutSectionIdentifier)];
[_accountMenuDataSource applySnapshot:snapshot animatingDifferences:YES];
}
// Returns the sheet presentation controller if it exists.
- (UISheetPresentationController*)sheetPresentationController {
return self.navigationController.popoverPresentationController
.adaptiveSheetPresentationController;
}
// Returns preferred height according to the container view width.
- (CGFloat)preferredHeightForSheetContent {
// This is the size of the content of the table view and the navigation bar.
return self.tableView.contentSize.height + [self navigationBarHeight];
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
id itemIdentifier =
[_accountMenuDataSource itemIdentifierForIndexPath:indexPath];
NSString* gaiaID = base::apple::ObjCCast<NSString>(itemIdentifier);
if (gaiaID) {
// `itemIdentifier` is a gaiaID.
base::RecordAction(
base::UserMetricsAction("Signin_AccountMenu_SelectAccount"));
CGRect cellRect = [tableView rectForRowAtIndexPath:indexPath];
_selectedIndexPath = indexPath;
[self.mutator accountTappedWithGaiaID:gaiaID targetRect:cellRect];
} else {
// Otherwise `itemIdentifier` is a `RowIdentifier`.
RowIdentifier rowIdentifier = static_cast<RowIdentifier>(
base::apple::ObjCCastStrict<NSNumber>(itemIdentifier).integerValue);
switch (rowIdentifier) {
case RowIdentifierAddAccount:
base::RecordAction(
base::UserMetricsAction("Signin_AccountMenu_AddAccount"));
[self.mutator didTapAddAccount];
break;
case RowIdentifierErrorExplanation:
break;
case RowIdentifierErrorButton:
[self.mutator didTapErrorButton];
break;
case RowIdentifierSignOut:
base::RecordAction(
base::UserMetricsAction("Signin_AccountMenu_Signout"));
CGRect cellRect = [tableView rectForRowAtIndexPath:indexPath];
[self.mutator signOutFromTargetRect:cellRect];
break;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
}
- (CGFloat)tableView:(UITableView*)tableView
heightForHeaderInSection:(NSInteger)section {
return 0;
}
- (CGFloat)tableView:(UITableView*)tableView
heightForFooterInSection:(NSInteger)section {
if (section == [self.tableView numberOfSections] - 1) {
//  The last footer’s height is the margin between the table and the bottom
// of the popover/sheet.
return kSideMargins;
}
return kFooterHeight;
}
- (UIView*)tableView:(UITableView*)tableView
viewForHeaderInSection:(NSInteger)section {
return [[UIView alloc] initWithFrame:CGRectZero];
}
- (UIView*)tableView:(UITableView*)tableView
viewForFooterInSection:(NSInteger)section {
return [[UIView alloc] initWithFrame:CGRectZero];
}
#pragma mark - AccountMenuConsumer
- (void)setUserInteractionsEnabled:(BOOL)enabled {
self.tableView.allowsSelection = enabled;
_closeButton.enabled = enabled;
_ellipsisButton.enabled = enabled;
}
- (void)switchingStarted {
CHECK(_selectedIndexPath, base::NotFatalUntil::M135);
TableViewAccountCell* cell =
base::apple::ObjCCastStrict<TableViewAccountCell>(
[self.tableView cellForRowAtIndexPath:_selectedIndexPath]);
[self setActivityIndicator:cell];
self.modalInPresentation = YES;
_ellipsisButton.enabled = NO;
}
- (void)switchingStopped {
if (!_selectedIndexPath) {
return;
}
TableViewAccountCell* cell =
base::apple::ObjCCastStrict<TableViewAccountCell>(
[self.tableView cellForRowAtIndexPath:_selectedIndexPath]);
[cell setStatusView:nil];
[self.tableView deselectRowAtIndexPath:_selectedIndexPath animated:YES];
_selectedIndexPath = nil;
self.modalInPresentation = NO;
_ellipsisButton.enabled = YES;
}
- (void)updatePrimaryAccount {
_identityAccountView = [[CentralAccountView alloc]
initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 0)
avatarImage:self.dataSource.primaryAccountAvatar
name:self.dataSource.primaryAccountUserFullName
email:self.dataSource.primaryAccountEmail
managementState:self.dataSource.managementState
useLargeMargins:NO];
[_identityAccountView updateTopPadding:[self navigationBarHeight]];
self.tableView.tableHeaderView = _identityAccountView;
[self.tableView reloadData];
[self resize];
}
- (void)updateErrorSection:(AccountErrorUIInfo*)error {
CHECK(!_selectedIndexPath, base::NotFatalUntil::M135);
NSDiffableDataSourceSnapshot* snapshot = _accountMenuDataSource.snapshot;
if (error == nil) {
// The error disappeared.
CHECK_EQ([snapshot indexOfSectionIdentifier:@(SyncErrorsSectionIdentifier)],
0);
[snapshot
deleteSectionsWithIdentifiers:@[ @(SyncErrorsSectionIdentifier) ]];
} else if ([snapshot
indexOfSectionIdentifier:@(SyncErrorsSectionIdentifier)] ==
NSNotFound) {
// The error appeared.
[snapshot insertSectionsWithIdentifiers:@[ @(SyncErrorsSectionIdentifier) ]
beforeSectionWithIdentifier:@(AccountsSectionIdentifier)];
[snapshot appendItemsWithIdentifiers:@[
@(RowIdentifierErrorExplanation), @(RowIdentifierErrorButton)
]
intoSectionWithIdentifier:@(SyncErrorsSectionIdentifier)];
} else {
// The error changed. No need to change the sections, only their content.
}
[_accountMenuDataSource applySnapshot:snapshot animatingDifferences:YES];
[self resize];
}
- (void)updateAccountListWithGaiaIDsToAdd:(NSArray<NSString*>*)indicesToAdd
gaiaIDsToRemove:(NSArray<NSString*>*)gaiaIDsToRemove
gaiaIDsToKeep:(NSArray<NSString*>*)gaiaIDsToKeep {
CHECK(!_selectedIndexPath, base::NotFatalUntil::M135);
NSDiffableDataSourceSnapshot* snapshot = _accountMenuDataSource.snapshot;
NSMutableArray* accountsIdentifiersToAdd = [[NSMutableArray alloc] init];
for (NSString* gaiaID in indicesToAdd) {
[accountsIdentifiersToAdd addObject:gaiaID];
}
[snapshot insertItemsWithIdentifiers:accountsIdentifiersToAdd
beforeItemWithIdentifier:@(RowIdentifierAddAccount)];
NSMutableArray* accountsIdentifiersToRemove = [[NSMutableArray alloc] init];
for (NSString* gaiaID in gaiaIDsToRemove) {
[accountsIdentifiersToRemove addObject:gaiaID];
}
[snapshot deleteItemsWithIdentifiers:accountsIdentifiersToRemove];
[snapshot reconfigureItemsWithIdentifiers:gaiaIDsToKeep];
[_accountMenuDataSource applySnapshot:snapshot animatingDifferences:YES];
[self resize];
}
#pragma mark - UIResponder
// To always be able to register key commands via -keyCommands, the VC must be
// able to become first responder.
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (NSArray<UIKeyCommand*>*)keyCommands {
return @[ UIKeyCommand.cr_close ];
}
- (void)keyCommand_close {
base::RecordAction(base::UserMetricsAction("MobileKeyCommandClose"));
[self.mutator viewControllerWantsToBeClosed:self];
}
@end