blob: 57cba94be0eb6aca49cc1dea8e7d51a62a51585b [file] [log] [blame]
// Copyright 2014 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/bookmarks/bookmark_edit_view_controller.h"
#import <memory>
#import <set>
#import "base/auto_reset.h"
#import "base/check_op.h"
#import "base/ios/block_types.h"
#import "base/mac/foundation_util.h"
#import "base/mac/scoped_cftyperef.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "components/bookmarks/browser/bookmark_model.h"
#import "components/url_formatter/url_fixer.h"
#import "ios/chrome/browser/bookmarks/bookmark_model_bridge_observer.h"
#import "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/flags/system_flags.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_folder_view_controller.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_mediator.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_ui_constants.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
#import "ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.h"
#import "ios/chrome/browser/ui/bookmarks/cells/bookmark_text_field_item.h"
#import "ios/chrome/browser/ui/commands/snackbar_commands.h"
#import "ios/chrome/browser/ui/icons/chrome_icon.h"
#import "ios/chrome/browser/ui/image_util/image_util.h"
#import "ios/chrome/browser/ui/keyboard/UIKeyCommand+Chrome.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.h"
#import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
#import "ios/chrome/browser/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/ui/util/rtl_geometry.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util_mac.h"
#import "ui/gfx/image/image.h"
#import "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using bookmarks::BookmarkModel;
using bookmarks::BookmarkNode;
namespace {
// Converts NSString entered by the user to a GURL.
GURL ConvertUserDataToGURL(NSString* urlString) {
if (urlString) {
return url_formatter::FixupURL(base::SysNSStringToUTF8(urlString),
std::string());
} else {
return GURL();
}
}
typedef NS_ENUM(NSInteger, SectionIdentifier) {
SectionIdentifierInfo = kSectionIdentifierEnumZero,
};
typedef NS_ENUM(NSInteger, ItemType) {
ItemTypeName = kItemTypeEnumZero,
ItemTypeFolder,
ItemTypeURL,
ItemTypeInvalidURLFooter,
};
// Estimated Table Row height.
const CGFloat kEstimatedTableRowHeight = 50;
// Estimated TableSection Footer height.
const CGFloat kEstimatedTableSectionFooterHeight = 40;
} // namespace
@interface BookmarkEditViewController () <BookmarkFolderViewControllerDelegate,
BookmarkModelBridgeObserver,
BookmarkTextFieldItemDelegate> {
// Flag to ignore bookmark model changes notifications.
BOOL _ignoresBookmarkModelChanges;
std::unique_ptr<BookmarkModelBridge> _modelBridge;
}
// The bookmark this controller displays or edits.
// Redefined to be readwrite.
@property(nonatomic, assign) const BookmarkNode* bookmark;
// Reference to the bookmark model.
@property(nonatomic, assign) BookmarkModel* bookmarkModel;
// The parent of the bookmark. This may be different from `bookmark->parent()`
// if the changes have not been saved yet. `folder` then represents the
// candidate for the new parent of `bookmark`. This property is always a
// non-NULL, valid folder.
@property(nonatomic, assign) const BookmarkNode* folder;
// The folder picker view controller.
// Redefined to be readwrite.
@property(nonatomic, strong) BookmarkFolderViewController* folderViewController;
@property(nonatomic, assign) Browser* browser;
@property(nonatomic, assign) ChromeBrowserState* browserState;
// Cancel button item in navigation bar.
@property(nonatomic, strong) UIBarButtonItem* cancelItem;
// Done button item in navigation bar.
@property(nonatomic, strong) UIBarButtonItem* doneItem;
// CollectionViewItem-s from the collection.
@property(nonatomic, strong) BookmarkTextFieldItem* nameItem;
@property(nonatomic, strong) BookmarkParentFolderItem* folderItem;
@property(nonatomic, strong) BookmarkTextFieldItem* URLItem;
// YES if the URL item is displaying a valid URL.
@property(nonatomic, assign) BOOL displayingValidURL;
// The action sheet coordinator, if one is currently being shown.
@property(nonatomic, strong) ActionSheetCoordinator* actionSheetCoordinator;
// Reports the changes to the delegate, that has the responsibility to save the
// bookmark.
- (void)commitBookmarkChanges;
// Changes `self.folder` and updates the UI accordingly.
// The change is not committed until the user taps the Save button.
- (void)changeFolder:(const BookmarkNode*)folder;
// The Save button is disabled if the form values are deemed non-valid. This
// method updates the state of the Save button accordingly.
- (void)updateSaveButtonState;
// Reloads the folder label text.
- (void)updateFolderLabel;
// Populates the UI with information from the models.
- (void)updateUIFromBookmark;
// Called when the Delete button is pressed.
- (void)deleteBookmark;
// Called when the Folder button is pressed.
- (void)moveBookmark;
// Called when the Cancel button is pressed.
- (void)cancel;
// Called when the Done button is pressed.
- (void)save;
@end
#pragma mark
@implementation BookmarkEditViewController
@synthesize bookmark = _bookmark;
@synthesize bookmarkModel = _bookmarkModel;
@synthesize delegate = _delegate;
@synthesize displayingValidURL = _displayingValidURL;
@synthesize folder = _folder;
@synthesize folderViewController = _folderViewController;
@synthesize browser = _browser;
@synthesize browserState = _browserState;
@synthesize cancelItem = _cancelItem;
@synthesize doneItem = _doneItem;
@synthesize nameItem = _nameItem;
@synthesize folderItem = _folderItem;
@synthesize URLItem = _URLItem;
#pragma mark - Lifecycle
- (instancetype)initWithBookmark:(const BookmarkNode*)bookmark
browser:(Browser*)browser {
DCHECK(bookmark);
DCHECK(browser);
UITableViewStyle style = ChromeTableViewStyle();
self = [super initWithStyle:style];
if (self) {
DCHECK(!bookmark->is_folder());
// Browser may be OTR, which is why the original browser state is being
// explicitly requested.
_browser = browser;
_browserState = browser->GetBrowserState()->GetOriginalChromeBrowserState();
_bookmark = bookmark;
_bookmarkModel =
ios::BookmarkModelFactory::GetForBrowserState(_browserState);
_folder = _bookmark->parent();
// Set up the bookmark model oberver.
_modelBridge.reset(new BookmarkModelBridge(self, _bookmarkModel));
}
return self;
}
- (void)dealloc {
[self shutdown];
}
- (void)shutdown {
_folderViewController.delegate = nil;
}
#pragma mark View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.backgroundColor = self.styler.tableViewBackgroundColor;
self.tableView.estimatedRowHeight = kEstimatedTableRowHeight;
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.sectionHeaderHeight = 0;
self.tableView.sectionFooterHeight = UITableViewAutomaticDimension;
self.tableView.estimatedSectionFooterHeight =
kEstimatedTableSectionFooterHeight;
self.view.accessibilityIdentifier = kBookmarkEditViewContainerIdentifier;
[self.tableView
setSeparatorInset:UIEdgeInsetsMake(0, kBookmarkCellHorizontalLeadingInset,
0, 0)];
self.title = l10n_util::GetNSString(IDS_IOS_BOOKMARK_EDIT_SCREEN_TITLE);
self.navigationItem.hidesBackButton = YES;
UIBarButtonItem* cancelItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:@selector(cancel)];
cancelItem.accessibilityIdentifier = @"Cancel";
self.navigationItem.leftBarButtonItem = cancelItem;
self.cancelItem = cancelItem;
UIBarButtonItem* doneItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(save)];
doneItem.accessibilityIdentifier =
kBookmarkEditNavigationBarDoneButtonIdentifier;
self.navigationItem.rightBarButtonItem = doneItem;
self.doneItem = doneItem;
// Setup the bottom toolbar.
NSString* titleString = l10n_util::GetNSString(IDS_IOS_BOOKMARK_DELETE);
UIBarButtonItem* deleteButton =
[[UIBarButtonItem alloc] initWithTitle:titleString
style:UIBarButtonItemStylePlain
target:self
action:@selector(deleteBookmark)];
deleteButton.accessibilityIdentifier = kBookmarkEditDeleteButtonIdentifier;
UIBarButtonItem* spaceButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:nil];
deleteButton.tintColor = [UIColor colorNamed:kRedColor];
[self setToolbarItems:@[ spaceButton, deleteButton, spaceButton ]
animated:NO];
[self updateUIFromBookmark];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Whevener this VC is displayed the bottom toolbar will be shown.
self.navigationController.toolbarHidden = NO;
}
#pragma mark - Presentation controller integration
- (BOOL)shouldBeDismissedOnTouchOutside {
return NO;
}
#pragma mark - Accessibility
- (BOOL)accessibilityPerformEscape {
[self cancel];
return YES;
}
#pragma mark - Private
- (BOOL)inputURLIsValid {
return ConvertUserDataToGURL([self inputURLString]).is_valid();
}
// Retrieves input URL string from UI.
- (NSString*)inputURLString {
return self.URLItem.text;
}
// Retrieves input bookmark name string from UI.
- (NSString*)inputBookmarkName {
return self.nameItem.text;
}
- (void)commitBookmarkChanges {
// To stop getting recursive events from committed bookmark editing changes
// ignore bookmark model updates notifications.
base::AutoReset<BOOL> autoReset(&_ignoresBookmarkModelChanges, YES);
GURL url = ConvertUserDataToGURL([self inputURLString]);
// If the URL was not valid, the `save` message shouldn't have been sent.
DCHECK([self inputURLIsValid]);
// Tell delegate if bookmark name or title has been changed.
if (self.bookmark &&
(self.bookmark->GetTitle() !=
base::SysNSStringToUTF16([self inputBookmarkName]) ||
self.bookmark->url() != url)) {
[self.delegate bookmarkEditorWillCommitTitleOrUrlChange:self];
}
[self.snackbarCommandsHandler
showSnackbarMessage:
bookmark_utils_ios::CreateOrUpdateBookmarkWithUndoToast(
self.bookmark, [self inputBookmarkName], url, self.folder,
self.bookmarkModel, self.browserState)];
}
- (void)changeFolder:(const BookmarkNode*)folder {
DCHECK(folder->is_folder());
self.folder = folder;
[BookmarkMediator setFolderForNewBookmarks:self.folder
inBrowserState:self.browserState];
[self updateFolderLabel];
}
- (void)dismissBookmarkEditView {
[self.view endEditing:YES];
// Dismiss this controller.
[self.delegate bookmarkEditorWantsDismissal:self];
}
#pragma mark - Layout
- (void)updateSaveButtonState {
self.doneItem.enabled = [self inputURLIsValid];
}
- (void)updateFolderLabel {
NSIndexPath* indexPath =
[self.tableViewModel indexPathForItemType:ItemTypeFolder
sectionIdentifier:SectionIdentifierInfo];
if (!indexPath) {
return;
}
NSString* folderName = @"";
if (self.bookmark) {
folderName = bookmark_utils_ios::TitleForBookmarkNode(self.folder);
}
self.folderItem.title = folderName;
[self.tableView reloadRowsAtIndexPaths:@[ indexPath ]
withRowAnimation:UITableViewRowAnimationNone];
}
- (void)updateUIFromBookmark {
// If there is no current bookmark, don't update.
if (!self.bookmark) {
return;
}
[self loadModel];
TableViewModel* model = self.tableViewModel;
[model addSectionWithIdentifier:SectionIdentifierInfo];
self.nameItem = [[BookmarkTextFieldItem alloc] initWithType:ItemTypeName];
self.nameItem.accessibilityIdentifier = @"Title Field";
self.nameItem.placeholder =
l10n_util::GetNSString(IDS_IOS_BOOKMARK_NAME_FIELD_HEADER);
self.nameItem.text = bookmark_utils_ios::TitleForBookmarkNode(self.bookmark);
self.nameItem.delegate = self;
[model addItem:self.nameItem toSectionWithIdentifier:SectionIdentifierInfo];
self.folderItem =
[[BookmarkParentFolderItem alloc] initWithType:ItemTypeFolder];
self.folderItem.title = bookmark_utils_ios::TitleForBookmarkNode(self.folder);
[model addItem:self.folderItem toSectionWithIdentifier:SectionIdentifierInfo];
self.URLItem = [[BookmarkTextFieldItem alloc] initWithType:ItemTypeURL];
self.URLItem.accessibilityIdentifier = @"URL Field";
self.URLItem.placeholder =
l10n_util::GetNSString(IDS_IOS_BOOKMARK_URL_FIELD_HEADER);
self.URLItem.text = base::SysUTF8ToNSString(self.bookmark->url().spec());
self.URLItem.delegate = self;
[model addItem:self.URLItem toSectionWithIdentifier:SectionIdentifierInfo];
TableViewHeaderFooterItem* errorFooter =
[[TableViewHeaderFooterItem alloc] initWithType:ItemTypeInvalidURLFooter];
[model setFooter:errorFooter forSectionWithIdentifier:SectionIdentifierInfo];
self.displayingValidURL = YES;
// Save button state.
[self updateSaveButtonState];
}
#pragma mark - Actions
- (void)deleteBookmark {
if (self.bookmark && self.bookmarkModel->loaded()) {
// To stop getting recursive events from committed bookmark editing changes
// ignore bookmark model updates notifications.
base::AutoReset<BOOL> autoReset(&_ignoresBookmarkModelChanges, YES);
std::set<const BookmarkNode*> nodes;
if ([self.delegate bookmarkEditor:self
shoudDeleteAllOccurencesOfBookmark:self.bookmark]) {
// When launched from the star button, removing the current bookmark
// removes all matching nodes.
std::vector<const BookmarkNode*> nodesVector;
self.bookmarkModel->GetNodesByURL(self.bookmark->url(), &nodesVector);
for (const BookmarkNode* node : nodesVector) {
nodes.insert(node);
}
} else {
// When launched from the info button, removing the current bookmark only
// removes the current node.
nodes.insert(self.bookmark);
}
[self.snackbarCommandsHandler
showSnackbarMessage:bookmark_utils_ios::DeleteBookmarksWithUndoToast(
nodes, self.bookmarkModel, self.browserState)];
self.bookmark = nil;
}
[self.delegate bookmarkEditorWantsDismissal:self];
}
- (void)moveBookmark {
DCHECK(self.bookmarkModel);
DCHECK(!self.folderViewController);
std::set<const BookmarkNode*> editedNodes;
editedNodes.insert(self.bookmark);
BookmarkFolderViewController* folderViewController =
[[BookmarkFolderViewController alloc]
initWithBookmarkModel:self.bookmarkModel
allowsNewFolders:YES
editedNodes:editedNodes
allowsCancel:NO
selectedFolder:self.folder
browser:_browser];
folderViewController.delegate = self;
folderViewController.snackbarCommandsHandler = self.snackbarCommandsHandler;
self.folderViewController = folderViewController;
self.folderViewController.navigationItem.largeTitleDisplayMode =
UINavigationItemLargeTitleDisplayModeNever;
[self.navigationController pushViewController:self.folderViewController
animated:YES];
}
- (void)cancel {
[self dismissBookmarkEditView];
}
- (void)save {
[self commitBookmarkChanges];
[self dismissBookmarkEditView];
}
#pragma mark - BookmarkTextFieldItemDelegate
- (void)textDidChangeForItem:(BookmarkTextFieldItem*)item {
self.modalInPresentation = YES;
[self updateSaveButtonState];
if (self.displayingValidURL != [self inputURLIsValid]) {
self.displayingValidURL = [self inputURLIsValid];
UITableViewHeaderFooterView* footer = [self.tableView
footerViewForSection:[self.tableViewModel sectionForSectionIdentifier:
SectionIdentifierInfo]];
NSString* footerText =
[self inputURLIsValid]
? @""
: l10n_util::GetNSString(
IDS_IOS_BOOKMARK_URL_FIELD_VALIDATION_FAILED);
[self.tableView beginUpdates];
footer.textLabel.text = footerText;
[self.tableView endUpdates];
}
}
- (void)textFieldDidBeginEditing:(UITextField*)textField {
textField.textColor = [BookmarkTextFieldCell textColorForEditing:YES];
}
- (void)textFieldDidEndEditing:(UITextField*)textField {
textField.textColor = [BookmarkTextFieldCell textColorForEditing:NO];
}
- (BOOL)textFieldShouldReturn:(UITextField*)textField {
[textField resignFirstResponder];
return YES;
}
#pragma mark - UITableViewDataSource
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
DCHECK_EQ(tableView, self.tableView);
UITableViewCell* cell = [super tableView:tableView
cellForRowAtIndexPath:indexPath];
NSInteger type = [self.tableViewModel itemTypeForIndexPath:indexPath];
switch (type) {
case ItemTypeName:
cell.selectionStyle = UITableViewCellSelectionStyleNone;
break;
case ItemTypeURL:
case ItemTypeFolder:
break;
}
return cell;
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
DCHECK_EQ(tableView, self.tableView);
if ([self.tableViewModel itemTypeForIndexPath:indexPath] == ItemTypeFolder) {
[self moveBookmark];
}
}
- (UIView*)tableView:(UITableView*)tableView
viewForFooterInSection:(NSInteger)section {
UIView* footerView = [super tableView:tableView
viewForFooterInSection:section];
if (section ==
[self.tableViewModel sectionForSectionIdentifier:SectionIdentifierInfo]) {
UITableViewHeaderFooterView* headerFooterView =
base::mac::ObjCCastStrict<UITableViewHeaderFooterView>(footerView);
headerFooterView.textLabel.font =
[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1];
headerFooterView.textLabel.textColor = [UIColor colorNamed:kRedColor];
}
return footerView;
}
#pragma mark - BookmarkFolderViewControllerDelegate
- (void)folderPicker:(BookmarkFolderViewController*)folderPicker
didFinishWithFolder:(const BookmarkNode*)folder {
[self changeFolder:folder];
// This delegate method can be called on two occasions:
// - the user selected a folder in the folder picker. In that case, the folder
// picker should be popped;
// - the user created a new folder, in which case the navigation stack
// contains this bookmark editor (`self`), a folder picker and a folder
// creator. In such a case, both the folder picker and creator shoud be popped
// to reveal this bookmark editor. Thus the call to
// `popToViewController:animated:`.
[self.navigationController popToViewController:self animated:YES];
self.folderViewController.delegate = nil;
self.folderViewController = nil;
}
- (void)folderPickerDidCancel:(BookmarkFolderViewController*)folderPicker {
// This delegate method can only be called from the folder picker, which is
// the only view controller on top of this bookmark editor (`self`). Thus the
// call to `popViewControllerAnimated:`.
[self.navigationController popViewControllerAnimated:YES];
self.folderViewController.delegate = nil;
self.folderViewController = nil;
}
- (void)folderPickerDidDismiss:(BookmarkFolderViewController*)folderPicker {
self.folderViewController.delegate = nil;
self.folderViewController = nil;
[self dismissBookmarkEditView];
}
#pragma mark - BookmarkModelBridgeObserver
- (void)bookmarkModelLoaded {
// No-op.
}
- (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
if (_ignoresBookmarkModelChanges) {
return;
}
if (self.bookmark == bookmarkNode) {
[self updateUIFromBookmark];
}
}
- (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
if (_ignoresBookmarkModelChanges) {
return;
}
[self updateFolderLabel];
}
- (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
movedFromParent:(const BookmarkNode*)oldParent
toParent:(const BookmarkNode*)newParent {
if (_ignoresBookmarkModelChanges) {
return;
}
if (self.bookmark == bookmarkNode) {
[self.folderViewController changeSelectedFolder:newParent];
}
}
- (void)bookmarkNodeDeleted:(const BookmarkNode*)bookmarkNode
fromFolder:(const BookmarkNode*)folder {
if (_ignoresBookmarkModelChanges) {
return;
}
if (self.bookmark == bookmarkNode) {
self.bookmark = nil;
[self.delegate bookmarkEditorWantsDismissal:self];
} else if (self.folder == bookmarkNode) {
[self changeFolder:self.bookmarkModel->mobile_node()];
}
}
- (void)bookmarkModelRemovedAllNodes {
if (_ignoresBookmarkModelChanges) {
return;
}
self.bookmark = nil;
if (!self.bookmarkModel->is_permanent_node(self.folder)) {
[self changeFolder:self.bookmarkModel->mobile_node()];
}
[self.delegate bookmarkEditorWantsDismissal:self];
}
#pragma mark - UIAdaptivePresentationControllerDelegate
- (void)presentationControllerDidAttemptToDismiss:
(UIPresentationController*)presentationController {
self.actionSheetCoordinator = [[ActionSheetCoordinator alloc]
initWithBaseViewController:self
browser:_browser
title:nil
message:nil
barButtonItem:self.cancelItem];
__weak __typeof(self) weakSelf = self;
[self.actionSheetCoordinator
addItemWithTitle:l10n_util::GetNSString(
IDS_IOS_VIEW_CONTROLLER_DISMISS_SAVE_CHANGES)
action:^{
[weakSelf save];
}
style:UIAlertActionStyleDefault];
[self.actionSheetCoordinator
addItemWithTitle:l10n_util::GetNSString(
IDS_IOS_VIEW_CONTROLLER_DISMISS_DISCARD_CHANGES)
action:^{
[weakSelf cancel];
}
style:UIAlertActionStyleDestructive];
[self.actionSheetCoordinator
addItemWithTitle:l10n_util::GetNSString(
IDS_IOS_VIEW_CONTROLLER_DISMISS_CANCEL_CHANGES)
action:^{
weakSelf.navigationItem.leftBarButtonItem.enabled = YES;
weakSelf.navigationItem.rightBarButtonItem.enabled = YES;
}
style:UIAlertActionStyleCancel];
self.navigationItem.leftBarButtonItem.enabled = NO;
self.navigationItem.rightBarButtonItem.enabled = NO;
[self.actionSheetCoordinator start];
}
- (void)presentationControllerWillDismiss:
(UIPresentationController*)presentationController {
// Resign first responder if trying to dismiss the VC so the keyboard doesn't
// linger until the VC dismissal has completed.
[self.view endEditing:YES];
}
- (void)presentationControllerDidDismiss:
(UIPresentationController*)presentationController {
[self dismissBookmarkEditView];
}
#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 dismissBookmarkEditView];
}
@end