| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // 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/settings_root_table_view_controller.h" |
| |
| #import "base/mac/foundation_util.h" |
| #import "ios/chrome/browser/ui/commands/application_commands.h" |
| #import "ios/chrome/browser/ui/commands/open_new_tab_command.h" |
| #import "ios/chrome/browser/ui/material_components/utils.h" |
| #import "ios/chrome/browser/ui/settings/bar_button_activity_indicator.h" |
| #import "ios/chrome/browser/ui/settings/cells/settings_cells_constants.h" |
| #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" |
| #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h" |
| #import "ios/chrome/browser/ui/util/uikit_ui_util.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| // Height of the space used by header/footer when none is set. Default is |
| // |estimatedSection{Header|Footer}Height|. |
| const CGFloat kDefaultHeaderFooterHeight = 10; |
| // Estimated height of the header/footer, used to speed the constraints. |
| const CGFloat kEstimatedHeaderFooterHeight = 35; |
| |
| enum SavedBarButtomItemPositionEnum { |
| kUndefinedBarButtonItemPosition, |
| kLeftBarButtonItemPosition, |
| kRightBarButtonItemPosition |
| }; |
| |
| // Dimension of the authentication operation activity indicator frame. |
| const CGFloat kActivityIndicatorDimensionIPad = 64; |
| const CGFloat kActivityIndicatorDimensionIPhone = 56; |
| } // namespace |
| |
| NSString* const kSettingsToolbarDeleteButtonId = |
| @"PasswordsToolbarDeleteButtonId"; |
| |
| @interface SettingsRootTableViewController () |
| |
| // Delete button for the toolbar. |
| @property(nonatomic, strong) UIBarButtonItem* deleteButton; |
| |
| // Item displayed before the user interactions are prevented. This is used to |
| // store the item while the interaction is prevented. |
| @property(nonatomic, strong) UIBarButtonItem* savedBarButtonItem; |
| |
| // Veil preventing interactions with the TableView. |
| @property(nonatomic, strong) UIView* veil; |
| |
| // Position of the saved button. |
| @property(nonatomic, assign) |
| SavedBarButtomItemPositionEnum savedBarButtonItemPosition; |
| |
| @end |
| |
| @implementation SettingsRootTableViewController |
| |
| @synthesize dispatcher = _dispatcher; |
| |
| #pragma mark - Public |
| |
| - (void)updateEditButton { |
| if (self.tableView.editing) { |
| self.navigationItem.rightBarButtonItem = [self createEditModeDoneButton]; |
| return; |
| } |
| |
| [self.navigationController setToolbarHidden:YES animated:YES]; |
| if (self.shouldShowEditButton) { |
| self.navigationItem.rightBarButtonItem = [self createEditButton]; |
| } else { |
| self.navigationItem.rightBarButtonItem = [self doneButtonIfNeeded]; |
| } |
| } |
| |
| - (void)reloadData { |
| [self loadModel]; |
| [self.tableView reloadData]; |
| } |
| |
| #pragma mark - Property |
| |
| - (UIBarButtonItem*)deleteButton { |
| if (!_deleteButton) { |
| _deleteButton = [[UIBarButtonItem alloc] |
| initWithTitle:l10n_util::GetNSString(IDS_IOS_SETTINGS_TOOLBAR_DELETE) |
| style:UIBarButtonItemStylePlain |
| target:self |
| action:@selector(deleteButtonCallback)]; |
| _deleteButton.accessibilityIdentifier = kSettingsToolbarDeleteButtonId; |
| _deleteButton.tintColor = [UIColor redColor]; |
| } |
| return _deleteButton; |
| } |
| |
| #pragma mark - UIViewController |
| |
| - (NSArray<UIBarButtonItem*>*)toolbarItems { |
| UIBarButtonItem* flexibleSpace = [[UIBarButtonItem alloc] |
| initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace |
| target:nil |
| action:nil]; |
| return @[ flexibleSpace, self.deleteButton, flexibleSpace ]; |
| } |
| |
| - (void)viewDidLoad { |
| self.styler.tableViewBackgroundColor = |
| [UIColor groupTableViewBackgroundColor]; |
| self.styler.tableViewSectionHeaderBlurEffect = nil; |
| [super viewDidLoad]; |
| self.styler.cellBackgroundColor = [UIColor whiteColor]; |
| self.styler.cellTitleColor = [UIColor blackColor]; |
| self.tableView.estimatedSectionHeaderHeight = kEstimatedHeaderFooterHeight; |
| self.tableView.estimatedRowHeight = kSettingsCellDefaultHeight; |
| self.tableView.estimatedSectionFooterHeight = kEstimatedHeaderFooterHeight; |
| self.tableView.separatorInset = |
| UIEdgeInsetsMake(0, kTableViewSeparatorInset, 0, 0); |
| // Do not set the estimated height of the footer/header as if there is no |
| // header/footer, there is an empty space. |
| } |
| |
| - (void)viewWillAppear:(BOOL)animated { |
| [super viewWillAppear:animated]; |
| UIBarButtonItem* doneButton = [self doneButtonIfNeeded]; |
| if (!self.navigationItem.rightBarButtonItem && doneButton) { |
| self.navigationItem.rightBarButtonItem = doneButton; |
| } |
| } |
| |
| - (void)viewWillDisappear:(BOOL)animated { |
| [super viewWillDisappear:animated]; |
| [self.navigationController setToolbarHidden:YES animated:YES]; |
| } |
| |
| - (void)setEditing:(BOOL)editing animated:(BOOL)animated { |
| [super setEditing:editing animated:animated]; |
| if (!editing) |
| [self.navigationController setToolbarHidden:YES animated:YES]; |
| } |
| |
| #pragma mark - UITableViewDelegate |
| |
| - (void)tableView:(UITableView*)tableView |
| didSelectRowAtIndexPath:(NSIndexPath*)indexPath { |
| if (!self.tableView.editing) |
| return; |
| |
| if (self.navigationController.toolbarHidden) |
| [self.navigationController setToolbarHidden:NO animated:YES]; |
| } |
| |
| - (void)tableView:(UITableView*)tableView |
| didDeselectRowAtIndexPath:(NSIndexPath*)indexPath { |
| if (!self.tableView.editing) |
| return; |
| |
| if (self.tableView.indexPathsForSelectedRows.count == 0) |
| [self.navigationController setToolbarHidden:YES animated:YES]; |
| } |
| |
| - (CGFloat)tableView:(UITableView*)tableView |
| heightForHeaderInSection:(NSInteger)section { |
| if ([self.tableViewModel headerForSection:section]) |
| return UITableViewAutomaticDimension; |
| return kDefaultHeaderFooterHeight; |
| } |
| |
| - (CGFloat)tableView:(UITableView*)tableView |
| heightForFooterInSection:(NSInteger)section { |
| if ([self.tableViewModel footerForSection:section]) |
| return UITableViewAutomaticDimension; |
| return kDefaultHeaderFooterHeight; |
| } |
| |
| #pragma mark - TableViewLinkHeaderFooterItemDelegate |
| |
| - (void)view:(TableViewLinkHeaderFooterView*)view didTapLinkURL:(GURL)URL { |
| // Subclass must have a valid dispatcher assigned. |
| DCHECK(self.dispatcher); |
| OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:URL]; |
| [self.dispatcher closeSettingsUIAndOpenURL:command]; |
| } |
| |
| #pragma mark - Private |
| |
| - (void)deleteButtonCallback { |
| [self deleteItems:self.tableView.indexPathsForSelectedRows]; |
| } |
| |
| - (UIBarButtonItem*)doneButtonIfNeeded { |
| if (self.shouldHideDoneButton) { |
| return nil; |
| } |
| SettingsNavigationController* navigationController = |
| base::mac::ObjCCast<SettingsNavigationController>( |
| self.navigationController); |
| return [navigationController doneButton]; |
| } |
| |
| - (UIBarButtonItem*)createEditButton { |
| // Create a custom Edit bar button item, as Material Navigation Bar does not |
| // handle a system UIBarButtonSystemItemEdit item. |
| UIBarButtonItem* button = [[UIBarButtonItem alloc] |
| initWithTitle:l10n_util::GetNSString(IDS_IOS_NAVIGATION_BAR_EDIT_BUTTON) |
| style:UIBarButtonItemStyleDone |
| target:self |
| action:@selector(editButtonPressed)]; |
| [button setEnabled:[self editButtonEnabled]]; |
| return button; |
| } |
| |
| - (UIBarButtonItem*)createEditModeDoneButton { |
| // Create a custom Done bar button item, as Material Navigation Bar does not |
| // handle a system UIBarButtonSystemItemDone item. |
| return [[UIBarButtonItem alloc] |
| initWithTitle:l10n_util::GetNSString(IDS_IOS_NAVIGATION_BAR_DONE_BUTTON) |
| style:UIBarButtonItemStyleDone |
| target:self |
| action:@selector(editButtonPressed)]; |
| } |
| |
| #pragma mark - Subclassing |
| |
| - (BOOL)shouldShowEditButton { |
| return NO; |
| } |
| |
| - (BOOL)editButtonEnabled { |
| return NO; |
| } |
| |
| - (void)editButtonPressed { |
| [self setEditing:!self.tableView.editing animated:YES]; |
| [self updateEditButton]; |
| } |
| |
| - (void)deleteItems:(NSArray<NSIndexPath*>*)indexPaths { |
| [self.tableView |
| performBatchUpdates:^{ |
| [self removeFromModelItemAtIndexPaths:indexPaths]; |
| [self.tableView |
| deleteRowsAtIndexPaths:indexPaths |
| withRowAnimation:UITableViewRowAnimationAutomatic]; |
| } |
| completion:nil]; |
| } |
| |
| - (void)preventUserInteraction { |
| DCHECK(!self.savedBarButtonItem); |
| DCHECK_EQ(kUndefinedBarButtonItemPosition, self.savedBarButtonItemPosition); |
| |
| // Create |waitButton|. |
| BOOL displayActivityIndicatorOnTheRight = |
| self.navigationItem.rightBarButtonItem != nil; |
| CGFloat activityIndicatorDimension = IsIPadIdiom() |
| ? kActivityIndicatorDimensionIPad |
| : kActivityIndicatorDimensionIPhone; |
| BarButtonActivityIndicator* indicator = [[BarButtonActivityIndicator alloc] |
| initWithFrame:CGRectMake(0.0, 0.0, activityIndicatorDimension, |
| activityIndicatorDimension)]; |
| UIBarButtonItem* waitButton = |
| [[UIBarButtonItem alloc] initWithCustomView:indicator]; |
| |
| if (displayActivityIndicatorOnTheRight) { |
| // If there is a right bar button item, then it is the "Done" button. |
| self.savedBarButtonItem = self.navigationItem.rightBarButtonItem; |
| self.savedBarButtonItemPosition = kRightBarButtonItemPosition; |
| self.navigationItem.rightBarButtonItem = waitButton; |
| [self.navigationItem.leftBarButtonItem setEnabled:NO]; |
| } else { |
| self.savedBarButtonItem = self.navigationItem.leftBarButtonItem; |
| self.savedBarButtonItemPosition = kLeftBarButtonItemPosition; |
| self.navigationItem.leftBarButtonItem = waitButton; |
| } |
| |
| // Adds a veil that covers the collection view and prevents user interaction. |
| DCHECK(self.view); |
| DCHECK(!self.veil); |
| self.veil = [[UIView alloc] initWithFrame:self.view.bounds]; |
| [self.veil setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | |
| UIViewAutoresizingFlexibleHeight)]; |
| [self.veil setBackgroundColor:[UIColor colorWithWhite:1.0 alpha:0.5]]; |
| [self.view addSubview:self.veil]; |
| |
| // Disable user interaction for the navigation controller view to ensure |
| // that the user cannot go back by swipping the navigation's top view |
| // controller |
| [self.navigationController.view setUserInteractionEnabled:NO]; |
| } |
| |
| - (void)allowUserInteraction { |
| DCHECK(self.navigationController) |
| << "|allowUserInteraction| should always be called before this settings" |
| " controller is popped or dismissed."; |
| [self.navigationController.view setUserInteractionEnabled:YES]; |
| |
| // Removes the veil that prevents user interaction. |
| DCHECK(self.veil); |
| [UIView animateWithDuration:0.3 |
| animations:^{ |
| [self.veil removeFromSuperview]; |
| } |
| completion:^(BOOL finished) { |
| self.veil = nil; |
| }]; |
| |
| DCHECK(self.savedBarButtonItem); |
| switch (self.savedBarButtonItemPosition) { |
| case kLeftBarButtonItemPosition: |
| self.navigationItem.leftBarButtonItem = self.savedBarButtonItem; |
| break; |
| case kRightBarButtonItemPosition: |
| self.navigationItem.rightBarButtonItem = self.savedBarButtonItem; |
| [self.navigationItem.leftBarButtonItem setEnabled:YES]; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| self.savedBarButtonItem = nil; |
| self.savedBarButtonItemPosition = kUndefinedBarButtonItemPosition; |
| } |
| |
| @end |