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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.h"
6
7 #include <memory>
8 #include <set>
9
10 #include "base/auto_reset.h"
11 #include "base/ios/block_types.h"
12 #include "base/ios/weak_nsobject.h"
13 #include "base/logging.h"
14 #include "base/mac/bind_objc_block.h"
15 #import "base/mac/foundation_util.h"
16 #include "base/mac/objc_property_releaser.h"
17 #include "base/mac/scoped_cftyperef.h"
18 #include "base/mac/scoped_nsobject.h"
19 #include "base/strings/sys_string_conversions.h"
20 #include "components/bookmarks/browser/bookmark_model.h"
21 #include "components/url_formatter/url_fixer.h"
22 #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
23 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
24 #import "ios/chrome/browser/ui/bookmarks/bookmark_elevated_toolbar.h"
25 #import "ios/chrome/browser/ui/bookmarks/bookmark_extended_button.h"
26 #import "ios/chrome/browser/ui/bookmarks/bookmark_folder_view_controller.h"
27 #import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
28 #import "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
29 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
30 #import "ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.h"
31 #import "ios/chrome/browser/ui/bookmarks/cells/bookmark_text_field_item.h"
32 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
33 #import "ios/chrome/browser/ui/icons/chrome_icon.h"
34 #import "ios/chrome/browser/ui/image_util.h"
35 #import "ios/chrome/browser/ui/keyboard/UIKeyCommand+Chrome.h"
36 #include "ios/chrome/browser/ui/rtl_geometry.h"
37 #include "ios/chrome/browser/ui/ui_util.h"
38 #include "ios/chrome/grit/ios_strings.h"
39 #import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
40 #import "ios/public/provider/chrome/browser/ui/text_field_styling.h"
41 #import "ios/third_party/material_components_ios/src/components/Palettes/src/Mat erialPalettes.h"
42 #import "ios/third_party/material_components_ios/src/components/ShadowElevations /src/MaterialShadowElevations.h"
43 #import "ios/third_party/material_components_ios/src/components/ShadowLayer/src/ MaterialShadowLayer.h"
44 #include "ui/base/l10n/l10n_util_mac.h"
45 #include "ui/gfx/image/image.h"
46 #include "url/gurl.h"
47
48 using bookmarks::BookmarkModel;
49 using bookmarks::BookmarkNode;
50
51 namespace {
52 // Converts NSString entered by the user to a GURL.
53 GURL ConvertUserDataToGURL(NSString* urlString) {
54 if (urlString) {
55 return url_formatter::FixupURL(base::SysNSStringToUTF8(urlString),
56 std::string());
57 } else {
58 return GURL();
59 }
60 }
61
62 typedef NS_ENUM(NSInteger, SectionIdentifier) {
63 SectionIdentifierInfo = kSectionIdentifierEnumZero,
64 };
65
66 typedef NS_ENUM(NSInteger, ItemType) {
67 ItemTypeName = kItemTypeEnumZero,
68 ItemTypeFolder,
69 ItemTypeURL,
70 };
71 } // namespace
72
73 @interface BookmarkEditViewController ()<BookmarkFolderViewControllerDelegate,
74 BookmarkModelBridgeObserver,
75 BookmarkTextFieldItemDelegate,
76 TextFieldValidation> {
77 // Flag to ignore bookmark model changes notifications.
78 BOOL _ignoresBookmarkModelChanges;
79
80 std::unique_ptr<bookmarks::BookmarkModelBridge> _modelBridge;
81
82 base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkEditViewController;
83 }
84
85 // The bookmark this controller displays or edits.
86 // Redefined to be readwrite.
87 @property(nonatomic, assign) const BookmarkNode* bookmark;
88
89 // Reference to the bookmark model.
90 @property(nonatomic, assign) BookmarkModel* bookmarkModel;
91
92 // The parent of the bookmark. This may be different from |bookmark->parent()|
93 // if the changes have not been saved yet. |folder| then represents the
94 // candidate for the new parent of |bookmark|. This property is always a
95 // non-NULL, valid folder.
96 @property(nonatomic, assign) const BookmarkNode* folder;
97
98 // The folder picker view controller.
99 // Redefined to be readwrite.
100 @property(nonatomic, retain) BookmarkFolderViewController* folderViewController;
101
102 @property(nonatomic, assign) ios::ChromeBrowserState* browserState;
103
104 // Cancel button item in navigation bar.
105 @property(nonatomic, retain) UIBarButtonItem* cancelItem;
106
107 // Done button item in navigation bar.
108 @property(nonatomic, retain) UIBarButtonItem* doneItem;
109
110 // CollectionViewItem-s from the collection.
111 @property(nonatomic, retain) BookmarkTextFieldItem* nameItem;
112 @property(nonatomic, retain) BookmarkParentFolderItem* folderItem;
113 @property(nonatomic, retain) BookmarkTextFieldItem* URLItem;
114
115 // Reports the changes to the delegate, that has the responsibility to save the
116 // bookmark.
117 - (void)commitBookmarkChanges;
118
119 // Changes |self.folder| and updates the UI accordingly.
120 // The change is not committed until the user taps the Save button.
121 - (void)changeFolder:(const BookmarkNode*)folder;
122
123 // The Save button is disabled if the form values are deemed non-valid. This
124 // method updates the state of the Save button accordingly.
125 - (void)updateSaveButtonState;
126
127 // Reloads the folder label text.
128 - (void)updateFolderLabel;
129
130 // Populates the UI with information from the models.
131 - (void)updateUIFromBookmark;
132
133 // Called when the Delete button is pressed.
134 - (void)deleteBookmark;
135
136 // Called when the Folder button is pressed.
137 - (void)moveBookmark;
138
139 // Called when the Cancel button is pressed.
140 - (void)cancel;
141
142 // Called when the Done button is pressed.
143 - (void)save;
144
145 @end
146
147 #pragma mark
148
149 @implementation BookmarkEditViewController
150
151 @synthesize bookmark = _bookmark;
152 @synthesize bookmarkModel = _bookmarkModel;
153 @synthesize delegate = _delegate;
154 @synthesize folder = _folder;
155 @synthesize folderViewController = _folderViewController;
156 @synthesize browserState = _browserState;
157 @synthesize cancelItem = _cancelItem;
158 @synthesize doneItem = _doneItem;
159 @synthesize nameItem = _nameItem;
160 @synthesize folderItem = _folderItem;
161 @synthesize URLItem = _URLItem;
162
163 #pragma mark - Lifecycle
164
165 - (instancetype)initWithBookmark:(const BookmarkNode*)bookmark
166 browserState:(ios::ChromeBrowserState*)browserState {
167 DCHECK(bookmark);
168 DCHECK(browserState);
169 self = [super initWithStyle:CollectionViewControllerStyleAppBar];
170 if (self) {
171 _propertyReleaser_BookmarkEditViewController.Init(
172 self, [BookmarkEditViewController class]);
173 DCHECK(!bookmark->is_folder());
174 DCHECK(!browserState->IsOffTheRecord());
175 _bookmark = bookmark;
176 _bookmarkModel =
177 ios::BookmarkModelFactory::GetForBrowserState(browserState);
178
179 _folder = bookmark->parent();
180
181 // Set up the bookmark model oberver.
182 _modelBridge.reset(
183 new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
184
185 _browserState = browserState;
186 }
187 return self;
188 }
189
190 - (void)dealloc {
191 _folderViewController.delegate = nil;
192 [super dealloc];
193 }
194
195 #pragma mark View lifecycle
196
197 - (void)viewDidLoad {
198 [super viewDidLoad];
199 self.collectionView.backgroundColor = [UIColor whiteColor];
200 self.view.accessibilityIdentifier = @"Single Bookmark Editor";
201
202 self.title = l10n_util::GetNSString(IDS_IOS_BOOKMARK_EDIT_SCREEN_TITLE);
203
204 self.navigationItem.hidesBackButton = YES;
205
206 UIBarButtonItem* cancelItem =
207 [ChromeIcon templateBarButtonItemWithImage:[ChromeIcon closeIcon]
208 target:self
209 action:@selector(cancel)];
210 cancelItem.accessibilityIdentifier = @"Cancel";
211 self.navigationItem.leftBarButtonItem = cancelItem;
212 self.cancelItem = cancelItem;
213
214 base::scoped_nsobject<UIBarButtonItem> doneItem([[UIBarButtonItem alloc]
215 initWithTitle:l10n_util::GetNSString(IDS_IOS_BOOKMARK_DONE_BUTTON)
216 style:UIBarButtonItemStylePlain
217 target:self
218 action:@selector(save)]);
219 doneItem.get().accessibilityIdentifier = @"Done";
220 self.navigationItem.rightBarButtonItem = doneItem;
221 self.doneItem = doneItem;
222
223 base::scoped_nsobject<BookmarksElevatedToolbar> buttonBar(
224 [[BookmarksElevatedToolbar alloc] init]);
225 base::scoped_nsobject<UIBarButtonItem> deleteItem([[UIBarButtonItem alloc]
226 initWithTitle:l10n_util::GetNSString(IDS_IOS_BOOKMARK_DELETE)
227 style:UIBarButtonItemStylePlain
228 target:self
229 action:@selector(deleteBookmark)]);
230 deleteItem.get().accessibilityIdentifier = @"Delete_action";
231 [deleteItem setTitleTextAttributes:@{
232 NSForegroundColorAttributeName : [UIColor blackColor]
233 }
234 forState:UIControlStateNormal];
235 [buttonBar.get().layer
236 addSublayer:[[[MDCShadowLayer alloc] init] autorelease]];
237 buttonBar.get().shadowElevation = MDCShadowElevationSearchBarResting;
238 buttonBar.get().backgroundColor = [UIColor whiteColor];
239 buttonBar.get().items = @[ deleteItem ];
240 [self.view addSubview:buttonBar];
241
242 // Constraint |buttonBar| to be in bottom
243 buttonBar.get().translatesAutoresizingMaskIntoConstraints = NO;
244 [self.view addConstraints:
245 [NSLayoutConstraint
246 constraintsWithVisualFormat:@"H:|[buttonBar]|"
247 options:0
248 metrics:nil
249 views:NSDictionaryOfVariableBindings(
250 buttonBar)]];
251 [self.view addConstraint:[NSLayoutConstraint
252 constraintWithItem:buttonBar
253 attribute:NSLayoutAttributeBottom
254 relatedBy:NSLayoutRelationEqual
255 toItem:self.view
256 attribute:NSLayoutAttributeBottom
257 multiplier:1.0
258 constant:0.0]];
259 [self.view
260 addConstraint:[NSLayoutConstraint
261 constraintWithItem:buttonBar
262 attribute:NSLayoutAttributeHeight
263 relatedBy:NSLayoutRelationEqual
264 toItem:nil
265 attribute:NSLayoutAttributeNotAnAttribute
266 multiplier:1.0
267 constant:48.0]];
268 [self updateUIFromBookmark];
269 }
270
271 #pragma mark - Accessibility
272
273 - (BOOL)accessibilityPerformEscape {
274 [self cancel];
275 return YES;
276 }
277
278 #pragma mark - Private
279
280 - (BOOL)inputURLIsValid {
281 return ConvertUserDataToGURL([self inputURLString]).is_valid();
282 }
283
284 // Retrieves input URL string from UI.
285 - (NSString*)inputURLString {
286 return self.URLItem.text;
287 }
288
289 // Retrieves input bookmark name string from UI.
290 - (NSString*)inputBookmarkName {
291 return self.nameItem.text;
292 }
293
294 - (void)commitBookmarkChanges {
295 // To stop getting recursive events from committed bookmark editing changes
296 // ignore bookmark model updates notifications.
297 base::AutoReset<BOOL> autoReset(&_ignoresBookmarkModelChanges, YES);
298
299 GURL url = ConvertUserDataToGURL([self inputURLString]);
300 // If the URL was not valid, the |save| message shouldn't have been sent.
301 DCHECK([self inputURLIsValid]);
302 bookmark_utils_ios::CreateOrUpdateBookmarkWithUndoToast(
303 self.bookmark, [self inputBookmarkName], url, self.folder,
304 self.bookmarkModel, self.browserState);
305 }
306
307 - (void)changeFolder:(const BookmarkNode*)folder {
308 DCHECK(folder->is_folder());
309 self.folder = folder;
310 [BookmarkInteractionController setFolderForNewBookmarks:self.folder
311 inBrowserState:self.browserState];
312 [self updateFolderLabel];
313 }
314
315 - (void)dismiss {
316 [self.view resignFirstResponder];
317
318 // Dismiss this controller.
319 [self.delegate bookmarkEditorWantsDismissal:self];
320 }
321
322 #pragma mark - Layout
323
324 - (void)updateSaveButtonState {
325 self.doneItem.enabled = [self inputURLIsValid];
326 }
327
328 - (void)updateFolderLabel {
329 NSIndexPath* indexPath =
330 [self.collectionViewModel indexPathForItemType:ItemTypeFolder
331 sectionIdentifier:SectionIdentifierInfo];
332 NSString* folderName = @"";
333 if (self.bookmark) {
334 folderName = bookmark_utils_ios::TitleForBookmarkNode(self.folder);
335 }
336
337 self.folderItem.title = folderName;
338 [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
339 }
340
341 - (void)updateUIFromBookmark {
342 // If there is no current bookmark, don't update.
343 if (!self.bookmark)
344 return;
345
346 [self loadModel];
347 CollectionViewModel* model = self.collectionViewModel;
348
349 [model addSectionWithIdentifier:SectionIdentifierInfo];
350
351 self.nameItem =
352 [[[BookmarkTextFieldItem alloc] initWithType:ItemTypeName] autorelease];
353 self.nameItem.accessibilityIdentifier = @"Title Field";
354 self.nameItem.placeholder =
355 l10n_util::GetNSString(IDS_IOS_BOOKMARK_NAME_FIELD_HEADER);
356 self.nameItem.text = bookmark_utils_ios::TitleForBookmarkNode(self.bookmark);
357 self.nameItem.delegate = self;
358 [model addItem:self.nameItem toSectionWithIdentifier:SectionIdentifierInfo];
359
360 self.folderItem =
361 [[BookmarkParentFolderItem alloc] initWithType:ItemTypeFolder];
362 self.folderItem.title = bookmark_utils_ios::TitleForBookmarkNode(self.folder);
363 [model addItem:self.folderItem toSectionWithIdentifier:SectionIdentifierInfo];
364
365 self.URLItem =
366 [[[BookmarkTextFieldItem alloc] initWithType:ItemTypeURL] autorelease];
367 self.URLItem.accessibilityIdentifier = @"URL Field";
368 self.URLItem.placeholder =
369 l10n_util::GetNSString(IDS_IOS_BOOKMARK_URL_FIELD_HEADER);
370 self.URLItem.text = base::SysUTF8ToNSString(self.bookmark->url().spec());
371 self.URLItem.delegate = self;
372 [model addItem:self.URLItem toSectionWithIdentifier:SectionIdentifierInfo];
373
374 // Save button state.
375 [self updateSaveButtonState];
376 }
377
378 #pragma mark - Actions
379
380 - (void)deleteBookmark {
381 if (self.bookmark && self.bookmarkModel->loaded()) {
382 // To stop getting recursive events from committed bookmark editing changes
383 // ignore bookmark model updates notifications.
384 base::AutoReset<BOOL> autoReset(&_ignoresBookmarkModelChanges, YES);
385
386 std::set<const BookmarkNode*> nodes;
387 if ([self.delegate bookmarkEditor:self
388 shoudDeleteAllOccurencesOfBookmark:self.bookmark]) {
389 // When launched from the star button, removing the current bookmark
390 // removes all matching nodes.
391 std::vector<const BookmarkNode*> nodesVector;
392 self.bookmarkModel->GetNodesByURL(self.bookmark->url(), &nodesVector);
393 for (const BookmarkNode* node : nodesVector)
394 nodes.insert(node);
395 } else {
396 // When launched from the info button, removing the current bookmark only
397 // removes the current node.
398 nodes.insert(self.bookmark);
399 }
400 bookmark_utils_ios::DeleteBookmarksWithUndoToast(nodes, self.bookmarkModel,
401 self.browserState);
402 self.bookmark = nil;
403 }
404 [self.delegate bookmarkEditorWantsDismissal:self];
405 }
406
407 - (void)moveBookmark {
408 DCHECK(self.bookmarkModel);
409 DCHECK(!self.folderViewController);
410
411 std::set<const BookmarkNode*> editedNodes;
412 editedNodes.insert(self.bookmark);
413 base::scoped_nsobject<BookmarkFolderViewController> folderViewController(
414 [[BookmarkFolderViewController alloc]
415 initWithBookmarkModel:self.bookmarkModel
416 allowsNewFolders:YES
417 editedNodes:editedNodes
418 allowsCancel:NO
419 selectedFolder:self.folder]);
420 folderViewController.get().delegate = self;
421 self.folderViewController = folderViewController;
422
423 [self.navigationController pushViewController:self.folderViewController
424 animated:YES];
425 }
426
427 - (void)cancel {
428 [self dismiss];
429 }
430
431 - (void)save {
432 [self commitBookmarkChanges];
433 [self dismiss];
434 }
435
436 #pragma mark - BookmarkTextFieldItemDelegate
437
438 - (void)textDidChangeForItem:(BookmarkTextFieldItem*)item {
439 [self updateSaveButtonState];
440 }
441
442 - (BOOL)textFieldShouldReturn:(UITextField*)textField {
443 [textField resignFirstResponder];
444 return YES;
445 }
446
447 #pragma mark - TextFieldValidation
448
449 - (NSString*)validationErrorForTextField:(id<TextFieldStyling>)field {
450 [self updateSaveButtonState];
451 if ([self inputURLIsValid]) {
452 return nil;
453 } else {
454 return l10n_util::GetNSString(IDS_IOS_BOOKMARK_URL_FIELD_VALIDATION_FAILED);
455 }
456 }
457
458 #pragma mark - UICollectionViewDataSource
459
460 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
461 cellForItemAtIndexPath:(NSIndexPath*)indexPath {
462 UICollectionViewCell* cell =
463 [super collectionView:collectionView cellForItemAtIndexPath:indexPath];
464 if ([self.collectionViewModel itemTypeForIndexPath:indexPath] ==
465 ItemTypeURL) {
466 BookmarkTextFieldCell* URLCell =
467 base::mac::ObjCCastStrict<BookmarkTextFieldCell>(cell);
468 URLCell.textField.textValidator = self;
469 }
470 return cell;
471 }
472
473 #pragma mark - UICollectionViewDelegate
474
475 - (void)collectionView:(UICollectionView*)collectionView
476 didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
477 [super collectionView:collectionView didSelectItemAtIndexPath:indexPath];
478 if ([self.collectionViewModel itemTypeForIndexPath:indexPath] ==
479 ItemTypeFolder) {
480 [self moveBookmark];
481 }
482 }
483
484 #pragma mark - MDCCollectionViewStylingDelegate
485
486 - (CGFloat)collectionView:(UICollectionView*)collectionView
487 cellHeightAtIndexPath:(NSIndexPath*)indexPath {
488 switch ([self.collectionViewModel itemTypeForIndexPath:indexPath]) {
489 case ItemTypeName:
490 case ItemTypeURL:
491 return 88;
492 case ItemTypeFolder:
493 return 50;
494 default:
495 NOTREACHED();
496 return 0;
497 }
498 }
499
500 #pragma mark - BookmarkFolderViewControllerDelegate
501
502 - (void)folderPicker:(BookmarkFolderViewController*)folderPicker
503 didFinishWithFolder:(const BookmarkNode*)folder {
504 [self changeFolder:folder];
505 // This delegate method can be called on two occasions:
506 // - the user selected a folder in the folder picker. In that case, the folder
507 // picker should be popped;
508 // - the user created a new folder, in which case the navigation stack
509 // contains this bookmark editor (|self|), a folder picker and a folder
510 // creator. In such a case, both the folder picker and creator shoud be popped
511 // to reveal this bookmark editor. Thus the call to
512 // |popToViewController:animated:|.
513 [self.navigationController popToViewController:self animated:YES];
514 self.folderViewController.delegate = nil;
515 self.folderViewController = nil;
516 }
517
518 - (void)folderPickerDidCancel:(BookmarkFolderViewController*)folderPicker {
519 // This delegate method can only be called from the folder picker, which is
520 // the only view controller on top of this bookmark editor (|self|). Thus the
521 // call to |popViewControllerAnimated:|.
522 [self.navigationController popViewControllerAnimated:YES];
523 self.folderViewController.delegate = nil;
524 self.folderViewController = nil;
525 }
526
527 #pragma mark - BookmarkModelBridgeObserver
528
529 - (void)bookmarkModelLoaded {
530 // No-op.
531 }
532
533 - (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
534 if (_ignoresBookmarkModelChanges)
535 return;
536
537 if (self.bookmark == bookmarkNode)
538 [self updateUIFromBookmark];
539 }
540
541 - (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
542 if (_ignoresBookmarkModelChanges)
543 return;
544
545 [self updateFolderLabel];
546 }
547
548 - (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
549 movedFromParent:(const BookmarkNode*)oldParent
550 toParent:(const BookmarkNode*)newParent {
551 if (_ignoresBookmarkModelChanges)
552 return;
553
554 if (self.bookmark == bookmarkNode)
555 [self.folderViewController changeSelectedFolder:newParent];
556 }
557
558 - (void)bookmarkNodeDeleted:(const BookmarkNode*)bookmarkNode
559 fromFolder:(const BookmarkNode*)folder {
560 if (_ignoresBookmarkModelChanges)
561 return;
562
563 if (self.bookmark == bookmarkNode) {
564 self.bookmark = nil;
565 [self.delegate bookmarkEditorWantsDismissal:self];
566 } else if (self.folder == bookmarkNode) {
567 [self changeFolder:self.bookmarkModel->mobile_node()];
568 }
569 }
570
571 - (void)bookmarkModelRemovedAllNodes {
572 if (_ignoresBookmarkModelChanges)
573 return;
574
575 self.bookmark = nil;
576 if (!self.bookmarkModel->is_permanent_node(self.folder)) {
577 [self changeFolder:self.bookmarkModel->mobile_node()];
578 }
579
580 [self.delegate bookmarkEditorWantsDismissal:self];
581 }
582
583 #pragma mark - UIResponder
584
585 - (NSArray*)keyCommands {
586 base::WeakNSObject<BookmarkEditViewController> weakSelf(self);
587 return @[ [UIKeyCommand cr_keyCommandWithInput:UIKeyInputEscape
588 modifierFlags:Cr_UIKeyModifierNone
589 title:nil
590 action:^{
591 [weakSelf dismiss];
592 }] ];
593 }
594
595 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698