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

Side by Side Diff: ios/chrome/browser/ui/bookmarks/bookmark_folder_collection_view.mm

Issue 2921813002: Removes unnecessary subclassing of BookmarkCollectionView. (Closed)
Patch Set: Created 3 years, 6 months 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_collection_view.h"
6
7 #include "base/logging.h"
8 #include "base/strings/sys_string_conversions.h"
9 #include "components/bookmarks/browser/bookmark_model.h"
10 #include "ios/chrome/browser/bookmarks/bookmarks_utils.h"
11 #include "ios/chrome/browser/experimental_flags.h"
12 #import "ios/chrome/browser/ui/authentication/signin_promo_view.h"
13 #import "ios/chrome/browser/ui/authentication/signin_promo_view_configurator.h"
14 #import "ios/chrome/browser/ui/authentication/signin_promo_view_consumer.h"
15 #import "ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h"
16 #import "ios/chrome/browser/ui/bookmarks/bookmark_collection_cells.h"
17 #import "ios/chrome/browser/ui/bookmarks/bookmark_promo_cell.h"
18 #import "ios/chrome/browser/ui/bookmarks/bookmark_signin_promo_cell.h"
19 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
20
21 #if !defined(__has_feature) || !__has_feature(objc_arc)
22 #error "This file requires ARC support."
23 #endif
24
25 using bookmarks::BookmarkNode;
26
27 namespace {
28 // Computes the cell size based on width.
29 CGSize PreferredCellSizeForWidth(UICollectionViewCell* cell, CGFloat width) {
30 CGRect cellFrame = cell.frame;
31 cellFrame.size.width = width;
32 cellFrame.size.height = CGFLOAT_MAX;
33 cell.frame = cellFrame;
34 [cell setNeedsLayout];
35 [cell layoutIfNeeded];
36 CGSize result =
37 [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize
38 withHorizontalFittingPriority:UILayoutPriorityRequired
39 verticalFittingPriority:UILayoutPriorityDefaultLow];
40 cellFrame.size = result;
41 cell.frame = cellFrame;
42 return result;
43 }
44 }
45
46 @interface BookmarkFolderCollectionView ()<BookmarkPromoCellDelegate,
47 SigninPromoViewConsumer> {
48 // A vector of folders to display in the collection view.
49 std::vector<const BookmarkNode*> _subFolders;
50 // A vector of bookmark urls to display in the collection view.
51 std::vector<const BookmarkNode*> _subItems;
52
53 // True if the promo is visible.
54 BOOL _promoVisible;
55
56 // Mediator, helper for the sign-in promo view.
57 SigninPromoViewMediator* _signinPromoViewMediator;
58 }
59 @property(nonatomic, assign) const bookmarks::BookmarkNode* folder;
60
61 // Section indices.
62 @property(nonatomic, readonly, assign) NSInteger promoSection;
63 @property(nonatomic, readonly, assign) NSInteger folderSection;
64 @property(nonatomic, readonly, assign) NSInteger itemsSection;
65 @property(nonatomic, readonly, assign) NSInteger sectionCount;
66
67 @end
68
69 @implementation BookmarkFolderCollectionView
70 @synthesize delegate = _delegate;
71 @synthesize folder = _folder;
72
73 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
74 frame:(CGRect)frame {
75 self = [super initWithBrowserState:browserState frame:frame];
76 if (self) {
77 [self updateCollectionView];
78 }
79 return self;
80 }
81
82 - (void)setDelegate:(id<BookmarkFolderCollectionViewDelegate>)delegate {
83 _delegate = delegate;
84 [self promoStateChangedAnimated:NO];
85 }
86
87 - (NSInteger)promoSection {
88 return [self shouldShowPromoCell] ? 0 : -1;
89 }
90
91 - (NSInteger)folderSection {
92 return [self shouldShowPromoCell] ? 1 : 0;
93 }
94
95 - (NSInteger)itemsSection {
96 return [self shouldShowPromoCell] ? 2 : 1;
97 }
98
99 - (NSInteger)sectionCount {
100 return [self shouldShowPromoCell] ? 3 : 2;
101 }
102
103 - (void)updateCollectionView {
104 if (!self.bookmarkModel->loaded())
105 return;
106
107 // Regenerate the list of all bookmarks.
108 _subFolders = std::vector<const BookmarkNode*>();
109 _subItems = std::vector<const BookmarkNode*>();
110
111 if (self.folder) {
112 int childCount = self.folder->child_count();
113 for (int i = 0; i < childCount; ++i) {
114 const BookmarkNode* node = self.folder->GetChild(i);
115 if (node->is_folder())
116 _subFolders.push_back(node);
117 else
118 _subItems.push_back(node);
119 }
120
121 bookmark_utils_ios::SortFolders(&_subFolders);
122 }
123
124 [self cancelAllFaviconLoads];
125 [self.collectionView reloadData];
126 }
127
128 #pragma mark - BookmarkModelBridgeObserver Callbacks
129
130 - (void)bookmarkModelLoaded {
131 [self updateCollectionView];
132 }
133
134 - (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
135 // The base folder changed. Do nothing.
136 if (bookmarkNode == self.folder)
137 return;
138
139 // A specific cell changed. Reload that cell.
140 NSIndexPath* indexPath = [self indexPathForNode:bookmarkNode];
141
142 if (indexPath) {
143 // TODO(crbug.com/603661): Ideally, we would only reload the relevant index
144 // path. However, calling reloadItemsAtIndexPaths:(0,0) immediately after
145 // reloadData results in a exception: NSInternalInconsistencyException
146 // 'request for index path for global index 2147483645 ...'
147 // One solution would be to keep track of whether we've just called
148 // reloadData, but that requires experimentation to determine how long we
149 // have to wait before we can safely call reloadItemsAtIndexPaths.
150 [self updateCollectionView];
151 }
152 }
153
154 - (void)bookmarkNodeFaviconChanged:
155 (const bookmarks::BookmarkNode*)bookmarkNode {
156 // Only urls have favicons.
157 DCHECK(bookmarkNode->is_url());
158
159 // Update image of corresponding cell.
160 NSIndexPath* indexPath = [self indexPathForNode:bookmarkNode];
161
162 if (!indexPath)
163 return;
164
165 // Check that this cell is visible.
166 NSArray* visiblePaths = [self.collectionView indexPathsForVisibleItems];
167 if (![visiblePaths containsObject:indexPath])
168 return;
169
170 [self loadFaviconAtIndexPath:indexPath];
171 }
172
173 - (NSIndexPath*)indexPathForNode:(const bookmarks::BookmarkNode*)bookmarkNode {
174 NSIndexPath* indexPath = nil;
175 if (bookmarkNode->is_folder()) {
176 std::vector<const BookmarkNode*>::iterator it =
177 std::find(_subFolders.begin(), _subFolders.end(), bookmarkNode);
178 if (it != _subFolders.end()) {
179 ptrdiff_t index = std::distance(_subFolders.begin(), it);
180 indexPath =
181 [NSIndexPath indexPathForRow:index inSection:self.folderSection];
182 }
183 } else if (bookmarkNode->is_url()) {
184 std::vector<const BookmarkNode*>::iterator it =
185 std::find(_subItems.begin(), _subItems.end(), bookmarkNode);
186 if (it != _subItems.end()) {
187 ptrdiff_t index = std::distance(_subItems.begin(), it);
188 indexPath =
189 [NSIndexPath indexPathForRow:index inSection:self.itemsSection];
190 }
191 }
192 return indexPath;
193 }
194
195 - (void)bookmarkNodeDeleted:(const BookmarkNode*)node
196 fromFolder:(const BookmarkNode*)folder {
197 if (self.folder == node) {
198 self.folder = nil;
199 [self updateCollectionView];
200 }
201 }
202
203 - (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
204 // The base folder's children changed. Reload everything.
205 if (bookmarkNode == self.folder) {
206 [self updateCollectionView];
207 return;
208 }
209
210 // A subfolder's children changed. Reload that cell.
211 std::vector<const BookmarkNode*>::iterator it =
212 std::find(_subFolders.begin(), _subFolders.end(), bookmarkNode);
213 if (it != _subFolders.end()) {
214 // TODO(crbug.com/603661): Ideally, we would only reload the relevant index
215 // path. However, calling reloadItemsAtIndexPaths:(0,0) immediately after
216 // reloadData results in a exception: NSInternalInconsistencyException
217 // 'request for index path for global index 2147483645 ...'
218 // One solution would be to keep track of whether we've just called
219 // reloadData, but that requires experimentation to determine how long we
220 // have to wait before we can safely call reloadItemsAtIndexPaths.
221 [self updateCollectionView];
222 }
223 }
224
225 - (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
226 movedFromParent:(const BookmarkNode*)oldParent
227 toParent:(const BookmarkNode*)newParent {
228 if (oldParent == self.folder || newParent == self.folder) {
229 // A folder was added or removed from the base folder.
230 [self updateCollectionView];
231 }
232 }
233
234 - (void)bookmarkModelRemovedAllNodes {
235 self.folder = nil;
236 [self updateCollectionView];
237 }
238
239 #pragma mark - Parent class overrides that affect functionality
240
241 - (void)collectionView:(UICollectionView*)collectionView
242 willDisplayCell:(UICollectionViewCell*)cell
243 forItemAtIndexPath:(NSIndexPath*)indexPath {
244 if (indexPath.section == self.itemsSection) {
245 [self loadFaviconAtIndexPath:indexPath];
246 }
247 }
248
249 - (void)didAddCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
250 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
251 UICollectionViewCell* cell =
252 [self.collectionView cellForItemAtIndexPath:indexPath];
253 [self.delegate bookmarkCollectionView:self cell:cell addNodeForEditing:node];
254 }
255
256 - (void)didRemoveCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
257 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
258 UICollectionViewCell* cell =
259 [self.collectionView cellForItemAtIndexPath:indexPath];
260 [self.delegate bookmarkCollectionView:self
261 cell:cell
262 removeNodeForEditing:node];
263 }
264
265 - (void)didTapCellAtIndexPath:(NSIndexPath*)indexPath {
266 if (indexPath.section == self.promoSection) {
267 // User tapped inside promo cell but not on one of the buttons. Ignore it.
268 return;
269 }
270
271 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
272 DCHECK(node);
273
274 if (indexPath.section == self.folderSection) {
275 [self.delegate bookmarkFolderCollectionView:self
276 selectedFolderForNavigation:node];
277 } else {
278 RecordBookmarkLaunch(BOOKMARK_LAUNCH_LOCATION_FOLDER);
279 [self.delegate bookmarkCollectionView:self
280 selectedUrlForNavigation:node->url()];
281 }
282 }
283
284 - (void)didTapMenuButtonAtIndexPath:(NSIndexPath*)indexPath
285 onView:(UIView*)view
286 forCell:(BookmarkItemCell*)cell {
287 [self.delegate bookmarkCollectionView:self
288 wantsMenuForBookmark:[self nodeAtIndexPath:indexPath]
289 onView:view
290 forCell:cell];
291 }
292
293 - (bookmark_cell::ButtonType)buttonTypeForCellAtIndexPath:
294 (NSIndexPath*)indexPath {
295 return self.editing ? bookmark_cell::ButtonNone : bookmark_cell::ButtonMenu;
296 }
297
298 - (BOOL)allowLongPressForCellAtIndexPath:(NSIndexPath*)indexPath {
299 return !self.editing;
300 }
301
302 - (void)didLongPressCell:(UICollectionViewCell*)cell
303 atIndexPath:(NSIndexPath*)indexPath {
304 if (indexPath.section == self.promoSection) {
305 // User long-pressed inside promo cell. Ignore it.
306 return;
307 }
308
309 [self.delegate bookmarkCollectionView:self
310 didLongPressCell:cell
311 forBookmark:[self nodeAtIndexPath:indexPath]];
312 }
313
314 - (BOOL)shouldSelectCellAtIndexPath:(NSIndexPath*)indexPath {
315 return YES;
316 }
317
318 - (BOOL)cellIsSelectedForEditingAtIndexPath:(NSIndexPath*)indexPath {
319 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
320 const std::set<const BookmarkNode*>& editingNodes =
321 [self.delegate nodesBeingEdited];
322 return editingNodes.find(node) != editingNodes.end();
323 }
324
325 - (const BookmarkNode*)nodeAtIndexPath:(NSIndexPath*)indexPath {
326 if (indexPath.section == self.folderSection)
327 return _subFolders[indexPath.row];
328 if (indexPath.section == self.itemsSection)
329 return _subItems[indexPath.row];
330
331 NOTREACHED();
332 return nullptr;
333 }
334
335 - (void)resetFolder:(const BookmarkNode*)folder {
336 DCHECK(folder->is_folder());
337 self.folder = folder;
338 [self updateCollectionView];
339 }
340
341 - (CGFloat)interitemSpacingForSectionAtIndex:(NSInteger)section {
342 CGFloat interitemSpacing = 0;
343 SEL minimumInteritemSpacingSelector = @selector(collectionView:
344 layout:
345 minimumInteritemSpacingForSectionAtIndex:);
346 if ([self.collectionView.delegate
347 respondsToSelector:minimumInteritemSpacingSelector]) {
348 id collectionViewDelegate = static_cast<id>(self.collectionView.delegate);
349 interitemSpacing =
350 [collectionViewDelegate collectionView:self.collectionView
351 layout:self.collectionView
352 .collectionViewLayout
353 minimumInteritemSpacingForSectionAtIndex:section];
354 } else if ([self.collectionView.collectionViewLayout
355 isKindOfClass:[UICollectionViewFlowLayout class]]) {
356 UICollectionViewFlowLayout* flowLayout =
357 static_cast<UICollectionViewFlowLayout*>(
358 self.collectionView.collectionViewLayout);
359 interitemSpacing = flowLayout.minimumInteritemSpacing;
360 }
361 return interitemSpacing;
362 }
363
364 // Parent class override.
365 - (UIEdgeInsets)insetForSectionAtIndex:(NSInteger)section {
366 UIEdgeInsets insets = [super insetForSectionAtIndex:section];
367 if (section == self.folderSection)
368 insets.bottom = [self interitemSpacingForSectionAtIndex:section] / 2.;
369 else if (section == self.itemsSection)
370 insets.top = [self interitemSpacingForSectionAtIndex:section] / 2.;
371 else if (section == self.promoSection)
372 (void)0; // No insets to update.
373 else
374 NOTREACHED();
375 return insets;
376 }
377
378 // Parent class override.
379 - (CGSize)cellSizeForIndexPath:(NSIndexPath*)indexPath {
380 if ([self isPromoSection:indexPath.section]) {
381 UICollectionViewCell* cell =
382 [self.collectionView cellForItemAtIndexPath:indexPath];
383 if (!cell) {
384 // -[UICollectionView
385 // dequeueReusableCellWithReuseIdentifier:forIndexPath:] cannot be used
386 // here since this method is called by -[id<UICollectionViewDelegate>
387 // collectionView:layout:sizeForItemAtIndexPath:]. This would generate
388 // crash: SIGFPE, EXC_I386_DIV.
389 if (experimental_flags::IsSigninPromoEnabled()) {
390 DCHECK(_signinPromoViewMediator);
391 BookmarkSigninPromoCell* signinPromoCell =
392 [[BookmarkSigninPromoCell alloc]
393 initWithFrame:CGRectMake(0, 0, 1000, 1000)];
394 [[_signinPromoViewMediator createConfigurator]
395 configureSigninPromoView:signinPromoCell.signinPromoView];
396 cell = signinPromoCell;
397 } else {
398 cell = [[BookmarkPromoCell alloc] init];
399 }
400 }
401 return PreferredCellSizeForWidth(cell, CGRectGetWidth(self.bounds));
402 }
403 return [super cellSizeForIndexPath:indexPath];
404 }
405
406 // Parent class override.
407 - (UICollectionViewCell*)cellAtIndexPath:(NSIndexPath*)indexPath {
408 if (indexPath.section == self.promoSection) {
409 if (experimental_flags::IsSigninPromoEnabled()) {
410 BookmarkSigninPromoCell* signinPromoCell = [self.collectionView
411 dequeueReusableCellWithReuseIdentifier:[BookmarkSigninPromoCell
412 reuseIdentifier]
413 forIndexPath:indexPath];
414 signinPromoCell.signinPromoView.delegate = _signinPromoViewMediator;
415 [[_signinPromoViewMediator createConfigurator]
416 configureSigninPromoView:signinPromoCell.signinPromoView];
417 __weak BookmarkFolderCollectionView* weakSelf = self;
418 signinPromoCell.closeButtonAction = ^() {
419 [weakSelf.delegate bookmarkCollectionViewDismissPromo:self];
420 };
421 return signinPromoCell;
422 } else {
423 BookmarkPromoCell* promoCell = [self.collectionView
424 dequeueReusableCellWithReuseIdentifier:[BookmarkPromoCell
425 reuseIdentifier]
426 forIndexPath:indexPath];
427 promoCell.delegate = self;
428 return promoCell;
429 }
430 }
431 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
432
433 if (indexPath.section == self.folderSection)
434 return [self cellForFolder:node indexPath:indexPath];
435
436 BookmarkItemCell* cell = [self cellForBookmark:node indexPath:indexPath];
437 return cell;
438 }
439
440 - (BOOL)needsSectionHeaderForSection:(NSInteger)section {
441 // Only show header when there is at least one element in the previous
442 // section.
443 if (section == 0)
444 return NO;
445
446 if ([self numberOfItemsInSection:(section - 1)] == 0)
447 return NO;
448
449 return YES;
450 }
451
452 // Parent class override.
453 - (CGSize)headerSizeForSection:(NSInteger)section {
454 if ([self needsSectionHeaderForSection:section])
455 return CGSizeMake(self.bounds.size.width,
456 [BookmarkHeaderSeparatorView preferredHeight]);
457
458 return CGSizeZero;
459 }
460
461 // Parent class override.
462 - (UICollectionReusableView*)headerAtIndexPath:(NSIndexPath*)indexPath {
463 if (![self needsSectionHeaderForSection:indexPath.section])
464 return nil;
465
466 BookmarkHeaderSeparatorView* view = [self.collectionView
467 dequeueReusableSupplementaryViewOfKind:
468 UICollectionElementKindSectionHeader
469 withReuseIdentifier:[BookmarkHeaderSeparatorView
470 reuseIdentifier]
471 forIndexPath:indexPath];
472 view.backgroundColor = [UIColor colorWithWhite:1 alpha:1];
473 return view;
474 }
475
476 - (NSInteger)numberOfItemsInSection:(NSInteger)section {
477 if (section == self.folderSection)
478 return _subFolders.size();
479 if (section == self.itemsSection)
480 return _subItems.size();
481 if (section == self.promoSection)
482 return 1;
483
484 NOTREACHED();
485 return -1;
486 }
487
488 - (NSInteger)numberOfSections {
489 return self.sectionCount;
490 }
491
492 - (void)collectionViewScrolled {
493 [self.delegate bookmarkCollectionViewDidScroll:self];
494 }
495
496 - (void)setEditing:(BOOL)editing animated:(BOOL)animated {
497 [super setEditing:editing animated:animated];
498 [self promoStateChangedAnimated:animated];
499 }
500
501 - (void)promoStateChangedAnimated:(BOOL)animate {
502 BOOL newPromoState =
503 !self.editing && self.folder &&
504 self.folder->type() == BookmarkNode::MOBILE &&
505 [self.delegate bookmarkCollectionViewShouldShowPromoCell:self];
506 if (newPromoState != _promoVisible) {
507 // This is awful, but until the old code to do the refresh when switching
508 // in and out of edit mode is fixed, this is probably the cleanest thing to
509 // do.
510 _promoVisible = newPromoState;
511 if (experimental_flags::IsSigninPromoEnabled()) {
512 if (!_promoVisible) {
513 _signinPromoViewMediator.consumer = nil;
514 _signinPromoViewMediator = nil;
515 } else {
516 _signinPromoViewMediator = [[SigninPromoViewMediator alloc] init];
517 _signinPromoViewMediator.consumer = self;
518 _signinPromoViewMediator.accessPoint =
519 signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER;
520 }
521 }
522 [self.collectionView reloadData];
523 }
524 }
525
526 #pragma mark - BookmarkPromoCellDelegate
527
528 - (void)bookmarkPromoCellDidTapSignIn:(BookmarkPromoCell*)bookmarkPromoCell {
529 [self.delegate bookmarkCollectionViewShowSignIn:self];
530 }
531
532 - (void)bookmarkPromoCellDidTapDismiss:(BookmarkPromoCell*)bookmarkPromoCell {
533 [self.delegate bookmarkCollectionViewDismissPromo:self];
534 }
535
536 #pragma mark - Promo Cell
537
538 - (BOOL)shouldShowPromoCell {
539 return _promoVisible;
540 }
541
542 #pragma mark - SigninPromoViewConsumer
543
544 - (void)configureSigninPromoWithConfigurator:
545 (SigninPromoViewConfigurator*)configurator
546 identityChanged:(BOOL)identityChanged {
547 DCHECK(_signinPromoViewMediator);
548 NSIndexPath* indexPath =
549 [NSIndexPath indexPathForRow:0 inSection:self.promoSection];
550 BookmarkSigninPromoCell* signinPromoCell =
551 static_cast<BookmarkSigninPromoCell*>(
552 [self.collectionView cellForItemAtIndexPath:indexPath]);
553 if (!signinPromoCell)
554 return;
555 // Should always reconfigure the cell size even if it has to be reloaded.
556 // -[BookmarkFolderCollectionView cellSizeForIndexPath:] uses the current
557 // cell to compute its height.
558 [configurator configureSigninPromoView:signinPromoCell.signinPromoView];
559 if (identityChanged) {
560 // The section should be reload to update the cell height.
561 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:self.promoSection];
562 [self.collectionView reloadSections:indexSet];
563 }
564 }
565
566 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698