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

Side by Side Diff: ios/chrome/browser/ui/bookmarks/bookmark_folder_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_folder_view_controller.h"
6
7 #include <memory>
8 #include <vector>
9
10 #import "base/ios/weak_nsobject.h"
11 #include "base/logging.h"
12 #include "base/mac/objc_property_releaser.h"
13 #include "base/mac/scoped_nsobject.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "components/bookmarks/browser/bookmark_model.h"
16 #import "ios/chrome/browser/ui/bookmarks/bookmark_folder_editor_view_controller. h"
17 #import "ios/chrome/browser/ui/bookmarks/bookmark_folder_table_view_cell.h"
18 #import "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
19 #import "ios/chrome/browser/ui/bookmarks/bookmark_navigation_controller.h"
20 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
21 #import "ios/chrome/browser/ui/icons/chrome_icon.h"
22 #import "ios/chrome/browser/ui/material_components/utils.h"
23 #include "ios/chrome/grit/ios_strings.h"
24 #import "ios/third_party/material_components_ios/src/components/AppBar/src/Mater ialAppBar.h"
25 #include "ui/base/l10n/l10n_util_mac.h"
26
27 using bookmarks::BookmarkNode;
28
29 namespace {
30
31 // The height of every folder cell.
32 const CGFloat kFolderCellHeight = 48.0;
33
34 // Height of section headers/footers.
35 const CGFloat kSectionHeaderHeight = 8.0;
36 const CGFloat kSectionFooterHeight = 8.0;
37
38 // Enum for the available sections.
39 // First section displays a cell to create a new folder.
40 // The second section displays as many folders as are available.
41 typedef enum {
42 BookmarkFolderSectionDefault = 0,
43 BookmarkFolderSectionFolders,
44 } BookmarkFolderSection;
45 const NSInteger BookmarkFolderSectionCount = 2;
46
47 } // namespace
48
49 @interface BookmarkFolderViewController ()<
50 BookmarkFolderEditorViewControllerDelegate,
51 BookmarkModelBridgeObserver,
52 UITableViewDataSource,
53 UITableViewDelegate> {
54 std::set<const BookmarkNode*> _editedNodes;
55 std::vector<const BookmarkNode*> _folders;
56 std::unique_ptr<bookmarks::BookmarkModelBridge> _modelBridge;
57 base::scoped_nsobject<MDCAppBar> _appBar;
58 base::mac::ObjCPropertyReleaser
59 _propertyReleaser_BookmarkFolderViewController;
60 }
61
62 // Should the controller setup Cancel and Done buttons instead of a back button.
63 @property(nonatomic, assign) BOOL allowsCancel;
64
65 // Should the controller setup a new-folder button.
66 @property(nonatomic, assign) BOOL allowsNewFolders;
67
68 // Reference to the main bookmark model.
69 @property(nonatomic, assign) bookmarks::BookmarkModel* bookmarkModel;
70
71 // The currently selected folder.
72 @property(nonatomic, readonly) const BookmarkNode* selectedFolder;
73
74 // The view controller to present when creating a new folder.
75 @property(nonatomic, retain)
76 BookmarkFolderEditorViewController* folderAddController;
77
78 // A linear list of folders.
79 @property(nonatomic, assign, readonly)
80 const std::vector<const BookmarkNode*>& folders;
81
82 // The table view that displays the options and folders.
83 @property(nonatomic, retain) UITableView* tableView;
84
85 // Returns the cell for the default section and the given |row|.
86 - (BookmarkFolderTableViewCell*)defaultSectionCellForRow:(NSInteger)row;
87
88 // Returns a folder cell for the folder at |row| in |self.folders|.
89 - (BookmarkFolderTableViewCell*)folderSectionCellForRow:(NSInteger)row;
90
91 // Reloads the folder list.
92 - (void)reloadFolders;
93
94 // Pushes on the navigation controller a view controller to create a new folder.
95 - (void)pushFolderAddViewController;
96
97 // Called when the user taps on a folder row. The cell is checked, the UI is
98 // locked so that the user can't interact with it, then the delegate is
99 // notified. Usual implementations of this delegate callback are to pop or
100 // dismiss this controller on selection. The delay is here to let the user get a
101 // visual feedback of the selection before this view disappears.
102 - (void)delayedNotifyDelegateOfSelection;
103
104 @end
105
106 @implementation BookmarkFolderViewController
107
108 @synthesize allowsCancel = _allowsCancel;
109 @synthesize allowsNewFolders = _allowsNewFolders;
110 @synthesize bookmarkModel = _bookmarkModel;
111 @synthesize editedNodes = _editedNodes;
112 @synthesize folderAddController = _folderAddController;
113 @synthesize delegate = _delegate;
114 @synthesize folders = _folders;
115 @synthesize tableView = _tableView;
116 @synthesize selectedFolder = _selectedFolder;
117
118 - (instancetype)initWithBookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel
119 allowsNewFolders:(BOOL)allowsNewFolders
120 editedNodes:
121 (const std::set<const BookmarkNode*>&)nodes
122 allowsCancel:(BOOL)allowsCancel
123 selectedFolder:(const BookmarkNode*)selectedFolder {
124 DCHECK(bookmarkModel);
125 DCHECK(bookmarkModel->loaded());
126 DCHECK(selectedFolder == NULL || selectedFolder->is_folder());
127 self = [super initWithNibName:nil bundle:nil];
128 if (self) {
129 _propertyReleaser_BookmarkFolderViewController.Init(
130 self, [BookmarkFolderViewController class]);
131 _allowsCancel = allowsCancel;
132 _allowsNewFolders = allowsNewFolders;
133 _bookmarkModel = bookmarkModel;
134 _editedNodes = nodes;
135 _selectedFolder = selectedFolder;
136
137 // Set up the bookmark model oberver.
138 _modelBridge.reset(
139 new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
140
141 _appBar.reset([[MDCAppBar alloc] init]);
142 [self addChildViewController:[_appBar headerViewController]];
143 }
144 return self;
145 }
146
147 - (void)changeSelectedFolder:(const BookmarkNode*)selectedFolder {
148 DCHECK(selectedFolder);
149 DCHECK(selectedFolder->is_folder());
150 _selectedFolder = selectedFolder;
151 [self.tableView reloadData];
152 }
153
154 - (void)dealloc {
155 _tableView.dataSource = nil;
156 _tableView.delegate = nil;
157 _folderAddController.delegate = nil;
158 [super dealloc];
159 }
160
161 - (UIStatusBarStyle)preferredStatusBarStyle {
162 return UIStatusBarStyleDefault;
163 }
164
165 #pragma mark - View lifecycle
166
167 - (void)viewDidLoad {
168 [super viewDidLoad];
169 if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) {
170 [self setEdgesForExtendedLayout:UIRectEdgeNone];
171 }
172 self.view.backgroundColor = [UIColor whiteColor];
173 self.view.accessibilityIdentifier = @"Folder Picker";
174
175 self.title = l10n_util::GetNSString(IDS_IOS_BOOKMARK_CHOOSE_GROUP_BUTTON);
176
177 base::scoped_nsobject<UIBarButtonItem> doneItem([[UIBarButtonItem alloc]
178 initWithTitle:l10n_util::GetNSString(
179 IDS_IOS_BOOKMARK_EDIT_MODE_EXIT_MOBILE)
180 style:UIBarButtonItemStylePlain
181 target:self
182 action:@selector(done:)]);
183 doneItem.get().accessibilityIdentifier = @"Done";
184 self.navigationItem.rightBarButtonItem = doneItem;
185
186 if (self.allowsCancel) {
187 UIBarButtonItem* cancelItem =
188 [ChromeIcon templateBarButtonItemWithImage:[ChromeIcon closeIcon]
189 target:self
190 action:@selector(cancel:)];
191 cancelItem.accessibilityLabel =
192 l10n_util::GetNSString(IDS_IOS_BOOKMARK_NEW_CANCEL_BUTTON_LABEL);
193 cancelItem.accessibilityIdentifier = @"Cancel";
194 self.navigationItem.leftBarButtonItem = cancelItem;
195 } else {
196 UIBarButtonItem* backItem =
197 [ChromeIcon templateBarButtonItemWithImage:[ChromeIcon backIcon]
198 target:self
199 action:@selector(back:)];
200 backItem.accessibilityLabel =
201 l10n_util::GetNSString(IDS_IOS_BOOKMARK_NEW_BACK_LABEL);
202 backItem.accessibilityIdentifier = @"Back";
203 self.navigationItem.leftBarButtonItem = backItem;
204 }
205
206 // The table view.
207 base::scoped_nsobject<UITableView> tableView([[UITableView alloc]
208 initWithFrame:self.view.bounds
209 style:UITableViewStylePlain]);
210 tableView.get().dataSource = self;
211 tableView.get().delegate = self;
212 tableView.get().autoresizingMask =
213 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
214 tableView.get().separatorStyle = UITableViewCellSeparatorStyleNone;
215 [self.view addSubview:tableView];
216 [self.view sendSubviewToBack:tableView];
217 self.tableView = tableView;
218
219 // Add the app bar to the view hierarchy. This must be done last, so that the
220 // app bar's views are the frontmost.
221 ConfigureAppBarWithCardStyle(_appBar);
222 [_appBar headerViewController].headerView.trackingScrollView = self.tableView;
223 [_appBar addSubviewsToParent];
224 }
225
226 - (void)viewWillAppear:(BOOL)animated {
227 [super viewWillAppear:animated];
228 [self reloadFolders];
229 }
230
231 - (UIViewController*)childViewControllerForStatusBarHidden {
232 return [_appBar headerViewController];
233 }
234
235 - (UIViewController*)childViewControllerForStatusBarStyle {
236 return [_appBar headerViewController];
237 }
238
239 #pragma mark - Accessibility
240
241 - (BOOL)accessibilityPerformEscape {
242 [self.delegate folderPickerDidCancel:self];
243 return YES;
244 }
245
246 #pragma mark - UIScrollViewDelegate
247
248 - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
249 MDCFlexibleHeaderView* headerView = [_appBar headerViewController].headerView;
250 if (scrollView == headerView.trackingScrollView) {
251 [headerView trackingScrollViewDidScroll];
252 }
253 }
254
255 - (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView {
256 MDCFlexibleHeaderView* headerView = [_appBar headerViewController].headerView;
257 if (scrollView == headerView.trackingScrollView) {
258 [headerView trackingScrollViewDidEndDecelerating];
259 }
260 }
261
262 - (void)scrollViewDidEndDragging:(UIScrollView*)scrollView
263 willDecelerate:(BOOL)decelerate {
264 MDCFlexibleHeaderView* headerView = [_appBar headerViewController].headerView;
265 if (scrollView == headerView.trackingScrollView) {
266 [headerView trackingScrollViewDidEndDraggingWillDecelerate:decelerate];
267 }
268 }
269
270 - (void)scrollViewWillEndDragging:(UIScrollView*)scrollView
271 withVelocity:(CGPoint)velocity
272 targetContentOffset:(inout CGPoint*)targetContentOffset {
273 MDCFlexibleHeaderView* headerView = [_appBar headerViewController].headerView;
274 if (scrollView == headerView.trackingScrollView) {
275 [headerView
276 trackingScrollViewWillEndDraggingWithVelocity:velocity
277 targetContentOffset:targetContentOffset];
278 }
279 }
280
281 #pragma mark - UITableViewDataSource
282
283 - (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
284 return BookmarkFolderSectionCount;
285 }
286
287 - (NSInteger)tableView:(UITableView*)tableView
288 numberOfRowsInSection:(NSInteger)section {
289 switch (static_cast<BookmarkFolderSection>(section)) {
290 case BookmarkFolderSectionDefault:
291 return [self shouldShowDefaultSection] ? 1 : 0;
292
293 case BookmarkFolderSectionFolders:
294 return self.folders.size();
295 }
296 NOTREACHED();
297 return 0;
298 }
299
300 - (UITableViewCell*)tableView:(UITableView*)tableView
301 cellForRowAtIndexPath:(NSIndexPath*)indexPath {
302 BookmarkFolderTableViewCell* cell = nil;
303 switch (static_cast<BookmarkFolderSection>(indexPath.section)) {
304 case BookmarkFolderSectionDefault:
305 cell = [self defaultSectionCellForRow:indexPath.row];
306 break;
307
308 case BookmarkFolderSectionFolders:
309 cell = [self folderSectionCellForRow:indexPath.row];
310 break;
311 }
312 return cell;
313 }
314
315 #pragma mark - UITableViewDelegate
316
317 - (CGFloat)tableView:(UITableView*)tableView
318 heightForRowAtIndexPath:(NSIndexPath*)indexPath {
319 return kFolderCellHeight;
320 }
321
322 - (CGFloat)tableView:(UITableView*)tableView
323 heightForHeaderInSection:(NSInteger)section {
324 switch (static_cast<BookmarkFolderSection>(section)) {
325 case BookmarkFolderSectionDefault:
326 return [self shouldShowDefaultSection] ? kSectionHeaderHeight : 0;
327
328 case BookmarkFolderSectionFolders:
329 return kSectionHeaderHeight;
330 }
331 NOTREACHED();
332 return 0;
333 }
334
335 - (UIView*)tableView:(UITableView*)tableView
336 viewForHeaderInSection:(NSInteger)section {
337 CGRect headerViewFrame =
338 CGRectMake(0, 0, CGRectGetWidth(tableView.frame),
339 [self tableView:tableView heightForHeaderInSection:section]);
340 UIView* headerView =
341 [[[UIView alloc] initWithFrame:headerViewFrame] autorelease];
342 if (section == BookmarkFolderSectionFolders &&
343 [self shouldShowDefaultSection]) {
344 CGRect separatorFrame =
345 CGRectMake(0, 0, CGRectGetWidth(headerView.bounds),
346 1.0 / [[UIScreen mainScreen] scale]); // 1-pixel divider.
347 base::scoped_nsobject<UIView> separator(
348 [[UIView alloc] initWithFrame:separatorFrame]);
349 separator.get().autoresizingMask = UIViewAutoresizingFlexibleBottomMargin |
350 UIViewAutoresizingFlexibleWidth;
351 separator.get().backgroundColor = bookmark_utils_ios::separatorColor();
352 [headerView addSubview:separator];
353 }
354 return headerView;
355 }
356
357 - (CGFloat)tableView:(UITableView*)tableView
358 heightForFooterInSection:(NSInteger)section {
359 switch (static_cast<BookmarkFolderSection>(section)) {
360 case BookmarkFolderSectionDefault:
361 return [self shouldShowDefaultSection] ? kSectionFooterHeight : 0;
362
363 case BookmarkFolderSectionFolders:
364 return kSectionFooterHeight;
365 }
366 NOTREACHED();
367 return 0;
368 }
369
370 - (UIView*)tableView:(UITableView*)tableView
371 viewForFooterInSection:(NSInteger)section {
372 return [[[UIView alloc] init] autorelease];
373 }
374
375 - (void)tableView:(UITableView*)tableView
376 didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
377 [tableView deselectRowAtIndexPath:indexPath animated:YES];
378 switch (static_cast<BookmarkFolderSection>(indexPath.section)) {
379 case BookmarkFolderSectionDefault:
380 [self pushFolderAddViewController];
381 break;
382
383 case BookmarkFolderSectionFolders: {
384 const BookmarkNode* folder = self.folders[indexPath.row];
385 [self changeSelectedFolder:folder];
386 [self delayedNotifyDelegateOfSelection];
387 break;
388 }
389 }
390 }
391
392 #pragma mark - BookmarkFolderEditorViewControllerDelegate
393
394 - (void)bookmarkFolderEditor:(BookmarkFolderEditorViewController*)folderEditor
395 didFinishEditingFolder:(const BookmarkNode*)folder {
396 DCHECK(folder);
397 [self reloadFolders];
398 [self changeSelectedFolder:folder];
399 [self delayedNotifyDelegateOfSelection];
400 }
401
402 - (void)bookmarkFolderEditorDidDeleteEditedFolder:
403 (BookmarkFolderEditorViewController*)folderEditor {
404 NOTREACHED();
405 }
406
407 - (void)bookmarkFolderEditorDidCancel:
408 (BookmarkFolderEditorViewController*)folderEditor {
409 [self.navigationController popViewControllerAnimated:YES];
410 self.folderAddController.delegate = nil;
411 self.folderAddController = nil;
412 }
413
414 #pragma mark - BookmarkModelBridgeObserver
415
416 - (void)bookmarkModelLoaded {
417 // The bookmark model is assumed to be loaded when this controller is created.
418 NOTREACHED();
419 }
420
421 - (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
422 if (!bookmarkNode->is_folder())
423 return;
424 [self reloadFolders];
425 }
426
427 - (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
428 [self reloadFolders];
429 }
430
431 - (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
432 movedFromParent:(const BookmarkNode*)oldParent
433 toParent:(const BookmarkNode*)newParent {
434 if (bookmarkNode->is_folder()) {
435 [self reloadFolders];
436 }
437 }
438
439 - (void)bookmarkNodeDeleted:(const BookmarkNode*)bookmarkNode
440 fromFolder:(const BookmarkNode*)folder {
441 if (!bookmarkNode->is_folder())
442 return;
443
444 if (bookmarkNode == self.selectedFolder) {
445 // The selected folder has been deleted. Fallback on the Mobile Bookmarks
446 // node.
447 [self changeSelectedFolder:self.bookmarkModel->mobile_node()];
448 }
449 [self reloadFolders];
450 }
451
452 - (void)bookmarkModelRemovedAllNodes {
453 // The selected folder is no longer valid. Fallback on the Mobile Bookmarks
454 // node.
455 [self changeSelectedFolder:self.bookmarkModel->mobile_node()];
456 [self reloadFolders];
457 }
458
459 #pragma mark - Actions
460
461 - (void)done:(id)sender {
462 [self.delegate folderPicker:self didFinishWithFolder:self.selectedFolder];
463 }
464
465 - (void)cancel:(id)sender {
466 [self.delegate folderPickerDidCancel:self];
467 }
468
469 - (void)back:(id)sender {
470 [self.delegate folderPickerDidCancel:self];
471 }
472
473 #pragma mark - Private
474
475 - (BOOL)shouldShowDefaultSection {
476 return self.allowsNewFolders;
477 }
478
479 - (BookmarkFolderTableViewCell*)defaultSectionCellForRow:(NSInteger)row {
480 DCHECK([self shouldShowDefaultSection]);
481 DCHECK_EQ(0, row);
482 BookmarkFolderTableViewCell* cell = [self.tableView
483 dequeueReusableCellWithIdentifier:[BookmarkFolderTableViewCell
484 folderCreationCellReuseIdentifier]];
485 if (!cell) {
486 cell = [BookmarkFolderTableViewCell folderCreationCell];
487 }
488 return cell;
489 }
490
491 - (BookmarkFolderTableViewCell*)folderSectionCellForRow:(NSInteger)row {
492 DCHECK(row <
493 [self.tableView numberOfRowsInSection:BookmarkFolderSectionFolders]);
494 BookmarkFolderTableViewCell* cell = [self.tableView
495 dequeueReusableCellWithIdentifier:[BookmarkFolderTableViewCell
496 folderCellReuseIdentifier]];
497 if (!cell) {
498 cell = [BookmarkFolderTableViewCell folderCell];
499 }
500 const BookmarkNode* folder = self.folders[row];
501 NSString* title = bookmark_utils_ios::TitleForBookmarkNode(folder);
502 cell.textLabel.text = title;
503 cell.accessibilityIdentifier = title;
504 cell.accessibilityLabel = title;
505 cell.checked = (self.selectedFolder == folder);
506
507 // Indentation level.
508 NSInteger level = 0;
509 const BookmarkNode* node = folder;
510 while (node && !(self.bookmarkModel->is_root_node(node))) {
511 ++level;
512 node = node->parent();
513 }
514 // The root node is not shown as a folder, so top level folders have a
515 // level strictly positive.
516 DCHECK(level > 0);
517 cell.indentationLevel = level - 1;
518
519 return cell;
520 }
521
522 - (void)reloadFolders {
523 _folders = bookmark_utils_ios::VisibleNonDescendantNodes(self.editedNodes,
524 self.bookmarkModel);
525 [self.tableView reloadData];
526 }
527
528 - (void)pushFolderAddViewController {
529 DCHECK(self.allowsNewFolders);
530 BookmarkFolderEditorViewController* folderCreator =
531 [BookmarkFolderEditorViewController
532 folderCreatorWithBookmarkModel:self.bookmarkModel
533 parentFolder:self.selectedFolder];
534 folderCreator.delegate = self;
535 [self.navigationController pushViewController:folderCreator animated:YES];
536 self.folderAddController = folderCreator;
537 }
538
539 - (void)delayedNotifyDelegateOfSelection {
540 self.view.userInteractionEnabled = NO;
541 base::WeakNSObject<BookmarkFolderViewController> weakSelf(self);
542 dispatch_after(
543 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)),
544 dispatch_get_main_queue(), ^{
545 base::scoped_nsobject<BookmarkFolderViewController> strongSelf(
546 [weakSelf retain]);
547 // Early return if the controller has been deallocated.
548 if (!strongSelf)
549 return;
550 strongSelf.get().view.userInteractionEnabled = YES;
551 [strongSelf done:nil];
552 });
553 }
554
555 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698