Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(572)

Unified Diff: ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm

Issue 2586993002: Upstream Chrome on iOS source code [3/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm
new file mode 100644
index 0000000000000000000000000000000000000000..1c01fe309b6d0f33f8d3f5b70d5761913c1ff796
--- /dev/null
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm
@@ -0,0 +1,595 @@
+// Copyright 2014 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/bookmarks/bookmark_edit_view_controller.h"
+
+#include <memory>
+#include <set>
+
+#include "base/auto_reset.h"
+#include "base/ios/block_types.h"
+#include "base/ios/weak_nsobject.h"
+#include "base/logging.h"
+#include "base/mac/bind_objc_block.h"
+#import "base/mac/foundation_util.h"
+#include "base/mac/objc_property_releaser.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/url_formatter/url_fixer.h"
+#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/ui/bookmarks/bookmark_elevated_toolbar.h"
+#import "ios/chrome/browser/ui/bookmarks/bookmark_extended_button.h"
+#import "ios/chrome/browser/ui/bookmarks/bookmark_folder_view_controller.h"
+#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
+#import "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.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/collection_view/collection_view_model.h"
+#import "ios/chrome/browser/ui/icons/chrome_icon.h"
+#import "ios/chrome/browser/ui/image_util.h"
+#import "ios/chrome/browser/ui/keyboard/UIKeyCommand+Chrome.h"
+#include "ios/chrome/browser/ui/rtl_geometry.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+#include "ios/chrome/grit/ios_strings.h"
+#import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#import "ios/public/provider/chrome/browser/ui/text_field_styling.h"
+#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
+#import "ios/third_party/material_components_ios/src/components/ShadowElevations/src/MaterialShadowElevations.h"
+#import "ios/third_party/material_components_ios/src/components/ShadowLayer/src/MaterialShadowLayer.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+#include "ui/gfx/image/image.h"
+#include "url/gurl.h"
+
+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,
+};
+} // namespace
+
+@interface BookmarkEditViewController ()<BookmarkFolderViewControllerDelegate,
+ BookmarkModelBridgeObserver,
+ BookmarkTextFieldItemDelegate,
+ TextFieldValidation> {
+ // Flag to ignore bookmark model changes notifications.
+ BOOL _ignoresBookmarkModelChanges;
+
+ std::unique_ptr<bookmarks::BookmarkModelBridge> _modelBridge;
+
+ base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkEditViewController;
+}
+
+// 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, retain) BookmarkFolderViewController* folderViewController;
+
+@property(nonatomic, assign) ios::ChromeBrowserState* browserState;
+
+// Cancel button item in navigation bar.
+@property(nonatomic, retain) UIBarButtonItem* cancelItem;
+
+// Done button item in navigation bar.
+@property(nonatomic, retain) UIBarButtonItem* doneItem;
+
+// CollectionViewItem-s from the collection.
+@property(nonatomic, retain) BookmarkTextFieldItem* nameItem;
+@property(nonatomic, retain) BookmarkParentFolderItem* folderItem;
+@property(nonatomic, retain) BookmarkTextFieldItem* URLItem;
+
+// 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 folder = _folder;
+@synthesize folderViewController = _folderViewController;
+@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
+ browserState:(ios::ChromeBrowserState*)browserState {
+ DCHECK(bookmark);
+ DCHECK(browserState);
+ self = [super initWithStyle:CollectionViewControllerStyleAppBar];
+ if (self) {
+ _propertyReleaser_BookmarkEditViewController.Init(
+ self, [BookmarkEditViewController class]);
+ DCHECK(!bookmark->is_folder());
+ DCHECK(!browserState->IsOffTheRecord());
+ _bookmark = bookmark;
+ _bookmarkModel =
+ ios::BookmarkModelFactory::GetForBrowserState(browserState);
+
+ _folder = bookmark->parent();
+
+ // Set up the bookmark model oberver.
+ _modelBridge.reset(
+ new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
+
+ _browserState = browserState;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ _folderViewController.delegate = nil;
+ [super dealloc];
+}
+
+#pragma mark View lifecycle
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.collectionView.backgroundColor = [UIColor whiteColor];
+ self.view.accessibilityIdentifier = @"Single Bookmark Editor";
+
+ self.title = l10n_util::GetNSString(IDS_IOS_BOOKMARK_EDIT_SCREEN_TITLE);
+
+ self.navigationItem.hidesBackButton = YES;
+
+ UIBarButtonItem* cancelItem =
+ [ChromeIcon templateBarButtonItemWithImage:[ChromeIcon closeIcon]
+ target:self
+ action:@selector(cancel)];
+ cancelItem.accessibilityIdentifier = @"Cancel";
+ self.navigationItem.leftBarButtonItem = cancelItem;
+ self.cancelItem = cancelItem;
+
+ base::scoped_nsobject<UIBarButtonItem> doneItem([[UIBarButtonItem alloc]
+ initWithTitle:l10n_util::GetNSString(IDS_IOS_BOOKMARK_DONE_BUTTON)
+ style:UIBarButtonItemStylePlain
+ target:self
+ action:@selector(save)]);
+ doneItem.get().accessibilityIdentifier = @"Done";
+ self.navigationItem.rightBarButtonItem = doneItem;
+ self.doneItem = doneItem;
+
+ base::scoped_nsobject<BookmarksElevatedToolbar> buttonBar(
+ [[BookmarksElevatedToolbar alloc] init]);
+ base::scoped_nsobject<UIBarButtonItem> deleteItem([[UIBarButtonItem alloc]
+ initWithTitle:l10n_util::GetNSString(IDS_IOS_BOOKMARK_DELETE)
+ style:UIBarButtonItemStylePlain
+ target:self
+ action:@selector(deleteBookmark)]);
+ deleteItem.get().accessibilityIdentifier = @"Delete_action";
+ [deleteItem setTitleTextAttributes:@{
+ NSForegroundColorAttributeName : [UIColor blackColor]
+ }
+ forState:UIControlStateNormal];
+ [buttonBar.get().layer
+ addSublayer:[[[MDCShadowLayer alloc] init] autorelease]];
+ buttonBar.get().shadowElevation = MDCShadowElevationSearchBarResting;
+ buttonBar.get().backgroundColor = [UIColor whiteColor];
+ buttonBar.get().items = @[ deleteItem ];
+ [self.view addSubview:buttonBar];
+
+ // Constraint |buttonBar| to be in bottom
+ buttonBar.get().translatesAutoresizingMaskIntoConstraints = NO;
+ [self.view addConstraints:
+ [NSLayoutConstraint
+ constraintsWithVisualFormat:@"H:|[buttonBar]|"
+ options:0
+ metrics:nil
+ views:NSDictionaryOfVariableBindings(
+ buttonBar)]];
+ [self.view addConstraint:[NSLayoutConstraint
+ constraintWithItem:buttonBar
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.view
+ attribute:NSLayoutAttributeBottom
+ multiplier:1.0
+ constant:0.0]];
+ [self.view
+ addConstraint:[NSLayoutConstraint
+ constraintWithItem:buttonBar
+ attribute:NSLayoutAttributeHeight
+ relatedBy:NSLayoutRelationEqual
+ toItem:nil
+ attribute:NSLayoutAttributeNotAnAttribute
+ multiplier:1.0
+ constant:48.0]];
+ [self updateUIFromBookmark];
+}
+
+#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]);
+ 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;
+ [BookmarkInteractionController setFolderForNewBookmarks:self.folder
+ inBrowserState:self.browserState];
+ [self updateFolderLabel];
+}
+
+- (void)dismiss {
+ [self.view resignFirstResponder];
+
+ // Dismiss this controller.
+ [self.delegate bookmarkEditorWantsDismissal:self];
+}
+
+#pragma mark - Layout
+
+- (void)updateSaveButtonState {
+ self.doneItem.enabled = [self inputURLIsValid];
+}
+
+- (void)updateFolderLabel {
+ NSIndexPath* indexPath =
+ [self.collectionViewModel indexPathForItemType:ItemTypeFolder
+ sectionIdentifier:SectionIdentifierInfo];
+ NSString* folderName = @"";
+ if (self.bookmark) {
+ folderName = bookmark_utils_ios::TitleForBookmarkNode(self.folder);
+ }
+
+ self.folderItem.title = folderName;
+ [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
+}
+
+- (void)updateUIFromBookmark {
+ // If there is no current bookmark, don't update.
+ if (!self.bookmark)
+ return;
+
+ [self loadModel];
+ CollectionViewModel* model = self.collectionViewModel;
+
+ [model addSectionWithIdentifier:SectionIdentifierInfo];
+
+ self.nameItem =
+ [[[BookmarkTextFieldItem alloc] initWithType:ItemTypeName] autorelease];
+ 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] autorelease];
+ 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];
+
+ // 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);
+ }
+ 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);
+ base::scoped_nsobject<BookmarkFolderViewController> folderViewController(
+ [[BookmarkFolderViewController alloc]
+ initWithBookmarkModel:self.bookmarkModel
+ allowsNewFolders:YES
+ editedNodes:editedNodes
+ allowsCancel:NO
+ selectedFolder:self.folder]);
+ folderViewController.get().delegate = self;
+ self.folderViewController = folderViewController;
+
+ [self.navigationController pushViewController:self.folderViewController
+ animated:YES];
+}
+
+- (void)cancel {
+ [self dismiss];
+}
+
+- (void)save {
+ [self commitBookmarkChanges];
+ [self dismiss];
+}
+
+#pragma mark - BookmarkTextFieldItemDelegate
+
+- (void)textDidChangeForItem:(BookmarkTextFieldItem*)item {
+ [self updateSaveButtonState];
+}
+
+- (BOOL)textFieldShouldReturn:(UITextField*)textField {
+ [textField resignFirstResponder];
+ return YES;
+}
+
+#pragma mark - TextFieldValidation
+
+- (NSString*)validationErrorForTextField:(id<TextFieldStyling>)field {
+ [self updateSaveButtonState];
+ if ([self inputURLIsValid]) {
+ return nil;
+ } else {
+ return l10n_util::GetNSString(IDS_IOS_BOOKMARK_URL_FIELD_VALIDATION_FAILED);
+ }
+}
+
+#pragma mark - UICollectionViewDataSource
+
+- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
+ cellForItemAtIndexPath:(NSIndexPath*)indexPath {
+ UICollectionViewCell* cell =
+ [super collectionView:collectionView cellForItemAtIndexPath:indexPath];
+ if ([self.collectionViewModel itemTypeForIndexPath:indexPath] ==
+ ItemTypeURL) {
+ BookmarkTextFieldCell* URLCell =
+ base::mac::ObjCCastStrict<BookmarkTextFieldCell>(cell);
+ URLCell.textField.textValidator = self;
+ }
+ return cell;
+}
+
+#pragma mark - UICollectionViewDelegate
+
+- (void)collectionView:(UICollectionView*)collectionView
+ didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
+ [super collectionView:collectionView didSelectItemAtIndexPath:indexPath];
+ if ([self.collectionViewModel itemTypeForIndexPath:indexPath] ==
+ ItemTypeFolder) {
+ [self moveBookmark];
+ }
+}
+
+#pragma mark - MDCCollectionViewStylingDelegate
+
+- (CGFloat)collectionView:(UICollectionView*)collectionView
+ cellHeightAtIndexPath:(NSIndexPath*)indexPath {
+ switch ([self.collectionViewModel itemTypeForIndexPath:indexPath]) {
+ case ItemTypeName:
+ case ItemTypeURL:
+ return 88;
+ case ItemTypeFolder:
+ return 50;
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+#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;
+}
+
+#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 - UIResponder
+
+- (NSArray*)keyCommands {
+ base::WeakNSObject<BookmarkEditViewController> weakSelf(self);
+ return @[ [UIKeyCommand cr_keyCommandWithInput:UIKeyInputEscape
+ modifierFlags:Cr_UIKeyModifierNone
+ title:nil
+ action:^{
+ [weakSelf dismiss];
+ }] ];
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698