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

Side by Side Diff: ios/chrome/browser/ui/bookmarks/bookmark_collection_view.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_collection_view.h"
6
7 #import <UIKit/UIGestureRecognizerSubclass.h>
8 #include <algorithm>
9 #include <map>
10 #include <memory>
11
12 #include "base/ios/weak_nsobject.h"
13 #include "base/mac/bind_objc_block.h"
14 #include "base/mac/foundation_util.h"
15 #include "base/mac/objc_property_releaser.h"
16 #include "base/mac/scoped_nsobject.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "components/bookmarks/browser/bookmark_model.h"
19 #include "components/bookmarks/browser/bookmark_model_observer.h"
20 #include "components/favicon/core/fallback_url_util.h"
21 #include "components/favicon/core/large_icon_service.h"
22 #include "components/favicon_base/fallback_icon_style.h"
23 #include "components/favicon_base/favicon_types.h"
24 #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
25 #include "ios/chrome/browser/bookmarks/bookmarks_utils.h"
26 #include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
27 #import "ios/chrome/browser/ui/bookmarks/bookmark_collection_cells.h"
28 #import "ios/chrome/browser/ui/bookmarks/bookmark_collection_view_background.h"
29 #import "ios/chrome/browser/ui/bookmarks/bookmark_promo_cell.h"
30 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
31 #include "ios/chrome/browser/ui/ui_util.h"
32 #import "ios/chrome/browser/ui/uikit_ui_util.h"
33 #include "ios/chrome/grit/ios_strings.h"
34 #include "skia/ext/skia_utils_ios.h"
35 #include "ui/base/l10n/l10n_util_mac.h"
36
37 using bookmarks::BookmarkNode;
38
39 namespace {
40
41 // Used to store a pair of NSIntegers when storing a NSIndexPath in C++
42 // collections.
43 using IntegerPair = std::pair<NSInteger, NSInteger>;
44
45 // The margin between the side of the view and the first and last tile.
46 CGFloat rowMarginTablet = 24.0;
47 CGFloat rowHeight = 48.0;
48 // Minimal acceptable favicon size, in points.
49 CGFloat minFaviconSizePt = 16;
50
51 // Delay in seconds to which the empty background view will be shown when the
52 // collection view is empty.
53 // This delay should not be too small to let enough time to load bookmarks
54 // from network.
55 const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
56
57 } // namespace
58
59 @interface BookmarkCollectionView ()<UICollectionViewDataSource,
60 UICollectionViewDelegateFlowLayout,
61 UIGestureRecognizerDelegate> {
62 std::unique_ptr<bookmarks::BookmarkModelBridge> _modelBridge;
63 ios::ChromeBrowserState* _browserState;
64
65 base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkCollectionView;
66
67 // Map of favicon load tasks for each index path. Used to keep track of
68 // pending favicon load operations so that they can be cancelled upon cell
69 // reuse. Keys are (section, item) pairs of cell index paths.
70 std::map<IntegerPair, base::CancelableTaskTracker::TaskId> _faviconLoadTasks;
71 // Task tracker used for async favicon loads.
72 base::CancelableTaskTracker _faviconTaskTracker;
73 }
74
75 // Redefined to be readwrite.
76 @property(nonatomic, assign) bookmarks::BookmarkModel* bookmarkModel;
77 // Redefined to be readwrite.
78 @property(nonatomic, retain) UICollectionView* collectionView;
79 // Redefined to be readwrite.
80 @property(nonatomic, assign) BOOL editing;
81 // Detects a long press on a cell.
82 @property(nonatomic, retain) UILongPressGestureRecognizer* longPressRecognizer;
83 // Background view of the collection view shown when there is no items.
84 @property(nonatomic, retain)
85 BookmarkCollectionViewBackground* emptyCollectionBackgroundView;
86 // Shadow to display over the content.
87 @property(nonatomic, retain) UIView* shadow;
88
89 // Updates the editing state for the cell.
90 - (void)updateEditingStateOfCell:(BookmarkCell*)cell
91 atIndexPath:(NSIndexPath*)indexPath
92 animateMenuVisibility:(BOOL)animateMenuVisibility
93 animateSelectedState:(BOOL)animateSelectedState;
94
95 // Callback received when the user taps the menu button on the cell.
96 - (void)didTapMenuButton:(BookmarkItemCell*)cell view:(UIView*)view;
97
98 // In landscape mode, there are 2 widths: 480pt and 568pt. Returns YES if the
99 // width is 568pt.
100 - (BOOL)wideLandscapeMode;
101
102 // Schedules showing or hiding the empty bookmarks background view if the
103 // collection view is empty by calling showEmptyBackgroundIfNeeded after
104 // kShowEmptyBookmarksBackgroundRefreshDelay.
105 // Multiple call to this method will cancel previous scheduled call to
106 // showEmptyBackgroundIfNeeded before scheduling a new one.
107 - (void)scheduleEmptyBackgroundVisibilityUpdate;
108 // Shows/hides empty bookmarks background view if the collections view is empty.
109 - (void)updateEmptyBackgroundVisibility;
110 // Shows/hides empty bookmarks background view with an animation.
111 - (void)setEmptyBackgroundVisible:(BOOL)visible;
112 @end
113
114 @implementation BookmarkCollectionView
115 @synthesize bookmarkModel = _bookmarkModel;
116 @synthesize collectionView = _collectionView;
117 @synthesize editing = _editing;
118 @synthesize emptyCollectionBackgroundView = _emptyCollectionBackgroundView;
119 @synthesize loader = _loader;
120 @synthesize longPressRecognizer = _longPressRecognizer;
121 @synthesize browserState = _browserState;
122 @synthesize shadow = _shadow;
123
124 #pragma mark - Initialization
125
126 - (id)init {
127 NOTREACHED();
128 return nil;
129 }
130
131 - (id)initWithFrame:(CGRect)frame {
132 NOTREACHED();
133 return nil;
134 }
135
136 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
137 frame:(CGRect)frame {
138 self = [super initWithFrame:frame];
139 if (self) {
140 _propertyReleaser_BookmarkCollectionView.Init(
141 self, [BookmarkCollectionView class]);
142
143 _browserState = browserState;
144
145 // Set up connection to the BookmarkModel.
146 _bookmarkModel =
147 ios::BookmarkModelFactory::GetForBrowserState(browserState);
148
149 // Set up observers.
150 _modelBridge.reset(
151 new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
152
153 [self setupViews];
154 }
155 return self;
156 }
157
158 - (void)dealloc {
159 _collectionView.dataSource = nil;
160 _collectionView.delegate = nil;
161 UIView* moi = _collectionView;
162 dispatch_async(dispatch_get_main_queue(), ^{
163 // A collection view with a layout that uses a dynamic animator (aka
164 // something that changes the layout over time) will crash if it is
165 // deallocated while the animation is currently playing.
166 // Apparently if a tick has been dispatched it will execute, invoking a
167 // method on the deallocated collection.
168 // The only purpose of this block is to retain the collection view for a
169 // while, giving the layout a chance to perform its last tick.
170 [moi self];
171 });
172 _faviconTaskTracker.TryCancelAll();
173 [super dealloc];
174 }
175
176 - (void)setupViews {
177 self.backgroundColor = bookmark_utils_ios::mainBackgroundColor();
178 base::scoped_nsobject<UICollectionViewFlowLayout> layout(
179 [[UICollectionViewFlowLayout alloc] init]);
180
181 base::scoped_nsobject<UICollectionView> collectionView(
182 [[UICollectionView alloc] initWithFrame:self.bounds
183 collectionViewLayout:layout]);
184 self.collectionView = collectionView;
185 self.collectionView.backgroundColor = [UIColor clearColor];
186 self.collectionView.autoresizingMask =
187 UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
188 self.collectionView.alwaysBounceVertical = YES;
189 self.collectionView.delegate = self;
190 self.collectionView.dataSource = self;
191 [self.collectionView registerClass:[BookmarkFolderCell class]
192 forCellWithReuseIdentifier:[BookmarkFolderCell reuseIdentifier]];
193 [self.collectionView registerClass:[BookmarkItemCell class]
194 forCellWithReuseIdentifier:[BookmarkItemCell reuseIdentifier]];
195 [self.collectionView registerClass:[BookmarkHeaderView class]
196 forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
197 withReuseIdentifier:[BookmarkHeaderView reuseIdentifier]];
198 [self.collectionView
199 registerClass:[BookmarkHeaderSeparatorView class]
200 forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
201 withReuseIdentifier:[BookmarkHeaderSeparatorView reuseIdentifier]];
202 [self.collectionView registerClass:[BookmarkPromoCell class]
203 forCellWithReuseIdentifier:[BookmarkPromoCell reuseIdentifier]];
204
205 [self addSubview:self.collectionView];
206
207 // Set up the background view shown when the collection is empty.
208 base::scoped_nsobject<BookmarkCollectionViewBackground>
209 emptyCollectionBackgroundView(
210 [[BookmarkCollectionViewBackground alloc] initWithFrame:CGRectZero]);
211 self.emptyCollectionBackgroundView = emptyCollectionBackgroundView;
212 self.emptyCollectionBackgroundView.autoresizingMask =
213 UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
214 self.emptyCollectionBackgroundView.alpha = 0;
215 self.emptyCollectionBackgroundView.text = [self textWhenCollectionIsEmpty];
216
217 self.emptyCollectionBackgroundView.frame = self.collectionView.bounds;
218 self.collectionView.backgroundView = self.emptyCollectionBackgroundView;
219
220 [self updateShadow];
221
222 self.longPressRecognizer =
223 base::scoped_nsobject<UILongPressGestureRecognizer>(
224 [[UILongPressGestureRecognizer alloc]
225 initWithTarget:self
226 action:@selector(longPress:)]);
227 self.longPressRecognizer.delegate = self;
228 [self.collectionView addGestureRecognizer:self.longPressRecognizer];
229 }
230
231 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
232 [self updateShadow];
233 }
234
235 - (void)updateShadow {
236 // Remove the current one, if any.
237 [self.shadow removeFromSuperview];
238
239 if (IsCompact(self)) {
240 self.shadow =
241 bookmark_utils_ios::dropShadowWithWidth(CGRectGetWidth(self.bounds));
242 } else {
243 self.shadow = [[[UIView alloc]
244 initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds),
245 1 / [[UIScreen mainScreen] scale])]
246 autorelease];
247 self.shadow.backgroundColor = [UIColor colorWithWhite:0.0 alpha:.12];
248 }
249
250 [self updateShadowFrame];
251 self.shadow.autoresizingMask = UIViewAutoresizingFlexibleWidth;
252 if (self.editing)
253 self.shadow.alpha = 0.0;
254
255 // Add the new shadow.
256 [self addSubview:self.shadow];
257 }
258
259 - (void)updateShadowFrame {
260 CGFloat shadowHeight = CGRectGetHeight(self.shadow.frame);
261 CGFloat y = std::min<CGFloat>(
262 0.0, self.collectionView.contentOffset.y - shadowHeight);
263 self.shadow.frame =
264 CGRectMake(0, y, CGRectGetWidth(self.bounds), shadowHeight);
265 }
266
267 #pragma mark - UIScrollViewDelegate
268
269 - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
270 [self updateShadowFrame];
271 [self collectionViewScrolled];
272 }
273
274 #pragma mark - empty background
275
276 - (void)scheduleEmptyBackgroundVisibilityUpdate {
277 [NSObject
278 cancelPreviousPerformRequestsWithTarget:self
279 selector:
280 @selector(
281 updateEmptyBackgroundVisibility)
282 object:nil];
283 [self performSelector:@selector(updateEmptyBackgroundVisibility)
284 withObject:nil
285 afterDelay:kShowEmptyBookmarksBackgroundRefreshDelay];
286 }
287
288 - (BOOL)isCollectionViewEmpty {
289 BOOL collectionViewIsEmpty = YES;
290 const NSInteger numberOfSections = [self numberOfSections];
291 NSInteger section = [self shouldShowPromoCell] ? 1 : 0;
292 for (; collectionViewIsEmpty && section < numberOfSections; ++section) {
293 const NSInteger numberOfItemsInSection =
294 [self numberOfItemsInSection:section];
295 collectionViewIsEmpty = numberOfItemsInSection == 0;
296 }
297 return collectionViewIsEmpty;
298 }
299
300 - (void)updateEmptyBackgroundVisibility {
301 const BOOL showEmptyBackground =
302 [self isCollectionViewEmpty] && ![self shouldShowPromoCell];
303 [self setEmptyBackgroundVisible:showEmptyBackground];
304 }
305
306 - (void)setEmptyBackgroundVisible:(BOOL)emptyBackgroundVisible {
307 [UIView beginAnimations:@"alpha" context:NULL];
308 self.emptyCollectionBackgroundView.alpha = emptyBackgroundVisible ? 1 : 0;
309 [UIView commitAnimations];
310 }
311
312 #pragma mark - UICollectionViewDataSource
313
314 - (NSInteger)collectionView:(UICollectionView*)collectionView
315 numberOfItemsInSection:(NSInteger)section {
316 const NSInteger numberOfItemsInSection =
317 [self numberOfItemsInSection:section];
318 const BOOL isCollectionViewEmpty = [self isCollectionViewEmpty];
319 self.collectionView.scrollEnabled = !isCollectionViewEmpty;
320 if (isCollectionViewEmpty) {
321 [self scheduleEmptyBackgroundVisibilityUpdate];
322 } else {
323 // Hide empty bookmarks now.
324 [self setEmptyBackgroundVisible:NO];
325 }
326 return numberOfItemsInSection;
327 }
328
329 - (NSInteger)numberOfSectionsInCollectionView:
330 (UICollectionView*)collectionView {
331 const NSInteger numberOfSections = [self numberOfSections];
332 const BOOL collectionViewIsEmpty = 0 == numberOfSections;
333 self.collectionView.scrollEnabled = !collectionViewIsEmpty;
334 if (collectionViewIsEmpty) {
335 [self scheduleEmptyBackgroundVisibilityUpdate];
336 } else {
337 // Hide empty bookmarks now.
338 [self setEmptyBackgroundVisible:NO];
339 }
340 return numberOfSections;
341 }
342
343 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
344 cellForItemAtIndexPath:(NSIndexPath*)indexPath {
345 return [self cellAtIndexPath:indexPath];
346 }
347
348 - (UICollectionReusableView*)collectionView:(UICollectionView*)collectionView
349 viewForSupplementaryElementOfKind:(NSString*)kind
350 atIndexPath:(NSIndexPath*)indexPath {
351 return [self headerAtIndexPath:indexPath];
352 }
353
354 - (BOOL)collectionView:(UICollectionView*)collectionView
355 shouldSelectItemAtIndexPath:(NSIndexPath*)indexPath {
356 return [self shouldSelectCellAtIndexPath:indexPath];
357 }
358
359 - (void)collectionView:(UICollectionView*)collectionView
360 didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
361 if (self.editing)
362 [self toggleSelectedForEditingAtIndexPath:indexPath];
363 else
364 [self didTapCellAtIndexPath:indexPath];
365 }
366
367 - (void)toggleSelectedForEditingAtIndexPath:(NSIndexPath*)indexPath {
368 BOOL selected = [self cellIsSelectedForEditingAtIndexPath:indexPath];
369 if (selected)
370 [self didRemoveCellForEditingAtIndexPath:indexPath];
371 else
372 [self didAddCellForEditingAtIndexPath:indexPath];
373
374 [self updateEditingStateOfCellAtIndexPath:indexPath
375 animateMenuVisibility:NO
376 animateSelectedState:YES];
377 }
378
379 #pragma mark - UICollectionViewDelegate
380
381 - (void)collectionView:(UICollectionView*)collectionView
382 didEndDisplayingCell:(UICollectionViewCell*)cell
383 forItemAtIndexPath:(NSIndexPath*)indexPath {
384 _faviconTaskTracker.TryCancel(
385 _faviconLoadTasks[IntegerPair(indexPath.section, indexPath.item)]);
386 }
387
388 #pragma mark - UICollectionViewDelegateFlowLayout
389
390 - (UIEdgeInsets)collectionView:(UICollectionView*)collectionView
391 layout:(UICollectionViewLayout*)collectionViewLayout
392 insetForSectionAtIndex:(NSInteger)section {
393 return [self insetForSectionAtIndex:section];
394 }
395
396 - (CGFloat)collectionView:(UICollectionView*)collectionView
397 layout:(UICollectionViewLayout*)
398 collectionViewLayout
399 minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
400 return [self minimumInteritemSpacingForSectionAtIndex:section];
401 }
402
403 - (CGFloat)collectionView:(UICollectionView*)collectionView
404 layout:(UICollectionViewLayout*)layout
405 minimumLineSpacingForSectionAtIndex:(NSInteger)section {
406 return [self minimumLineSpacingForSectionAtIndex:section];
407 }
408
409 - (CGSize)collectionView:(UICollectionView*)collectionView
410 layout:(UICollectionViewLayout*)collectionViewLayout
411 sizeForItemAtIndexPath:(NSIndexPath*)indexPath {
412 return [self cellSizeForIndexPath:indexPath];
413 }
414
415 - (CGSize)collectionView:(UICollectionView*)collectionView
416 layout:
417 (UICollectionViewLayout*)collectionViewLayout
418 referenceSizeForHeaderInSection:(NSInteger)section {
419 return [self headerSizeForSection:section];
420 }
421
422 #pragma mark - BookmarkItemCell callbacks
423
424 - (void)didTapMenuButton:(BookmarkItemCell*)cell view:(UIView*)view {
425 [self didTapMenuButtonAtIndexPath:[self.collectionView indexPathForCell:cell]
426 onView:view
427 forCell:cell];
428 }
429
430 #pragma mark - Convenience methods for subclasses
431
432 - (void)updateCellAtIndexPath:(NSIndexPath*)indexPath
433 withImage:(UIImage*)image
434 backgroundColor:(UIColor*)backgroundColor
435 textColor:(UIColor*)textColor
436 fallbackText:(NSString*)text {
437 BookmarkItemCell* cell = base::mac::ObjCCast<BookmarkItemCell>(
438 [self.collectionView cellForItemAtIndexPath:indexPath]);
439 if (!cell)
440 return;
441
442 if (image) {
443 [cell setImage:image];
444 } else {
445 [cell setPlaceholderText:text
446 textColor:textColor
447 backgroundColor:backgroundColor];
448 }
449 }
450
451 - (BookmarkItemCell*)cellForBookmark:(const BookmarkNode*)node
452 indexPath:(NSIndexPath*)indexPath {
453 DCHECK(![self isPromoSection:indexPath.section]);
454
455 BOOL selected = [self cellIsSelectedForEditingAtIndexPath:indexPath];
456
457 BookmarkItemCell* cell = [self.collectionView
458 dequeueReusableCellWithReuseIdentifier:[BookmarkItemCell reuseIdentifier]
459 forIndexPath:indexPath];
460
461 [cell updateWithTitle:bookmark_utils_ios::TitleForBookmarkNode(node)];
462 [cell setSelectedForEditing:selected animated:NO];
463 [cell setButtonTarget:self action:@selector(didTapMenuButton:view:)];
464
465 [self updateEditingStateOfCell:cell
466 atIndexPath:indexPath
467 animateMenuVisibility:NO
468 animateSelectedState:NO];
469
470 [self loadFaviconAtIndexPath:indexPath];
471
472 return cell;
473 }
474
475 - (void)cancelAllFaviconLoads {
476 _faviconTaskTracker.TryCancelAll();
477 }
478
479 - (void)cancelLoadingFaviconAtIndexPath:(NSIndexPath*)indexPath {
480 _faviconTaskTracker.TryCancel(
481 _faviconLoadTasks[IntegerPair(indexPath.section, indexPath.item)]);
482 }
483
484 - (void)loadFaviconAtIndexPath:(NSIndexPath*)indexPath {
485 // Cancel previous load attempts.
486 [self cancelLoadingFaviconAtIndexPath:indexPath];
487
488 // Start loading a favicon.
489 base::WeakNSObject<BookmarkCollectionView> weakSelf(self);
490 const bookmarks::BookmarkNode* node = [self nodeAtIndexPath:indexPath];
491 GURL blockURL(node->url());
492 void (^faviconBlock)(const favicon_base::LargeIconResult&) = ^(
493 const favicon_base::LargeIconResult& result) {
494 base::scoped_nsobject<BookmarkCollectionView> strongSelf([weakSelf retain]);
495 if (!strongSelf)
496 return;
497 UIImage* favIcon = nil;
498 UIColor* backgroundColor = nil;
499 UIColor* textColor = nil;
500 NSString* fallbackText = nil;
501 if (result.bitmap.is_valid()) {
502 scoped_refptr<base::RefCountedMemory> data =
503 result.bitmap.bitmap_data.get();
504 favIcon = [UIImage imageWithData:[NSData dataWithBytes:data->front()
505 length:data->size()]];
506 } else if (result.fallback_icon_style) {
507 backgroundColor = skia::UIColorFromSkColor(
508 result.fallback_icon_style->background_color);
509 textColor =
510 skia::UIColorFromSkColor(result.fallback_icon_style->text_color);
511
512 fallbackText =
513 base::SysUTF16ToNSString(favicon::GetFallbackIconText(blockURL));
514 }
515
516 [strongSelf updateCellAtIndexPath:indexPath
517 withImage:favIcon
518 backgroundColor:backgroundColor
519 textColor:textColor
520 fallbackText:fallbackText];
521 };
522
523 CGFloat scale = [UIScreen mainScreen].scale;
524 CGFloat preferredSize = scale * [BookmarkItemCell preferredImageSize];
525 CGFloat minSize = scale * minFaviconSizePt;
526
527 base::CancelableTaskTracker::TaskId taskId =
528 IOSChromeLargeIconServiceFactory::GetForBrowserState(self.browserState)
529 ->GetLargeIconOrFallbackStyle(node->url(), minSize, preferredSize,
530 base::BindBlock(faviconBlock),
531 &_faviconTaskTracker);
532 _faviconLoadTasks[IntegerPair(indexPath.section, indexPath.item)] = taskId;
533 }
534
535 - (BookmarkFolderCell*)cellForFolder:(const BookmarkNode*)node
536 indexPath:(NSIndexPath*)indexPath {
537 DCHECK(![self isPromoSection:indexPath.section]);
538 BookmarkFolderCell* cell = [self.collectionView
539 dequeueReusableCellWithReuseIdentifier:[BookmarkFolderCell
540 reuseIdentifier]
541 forIndexPath:indexPath];
542 [self updateEditingStateOfCell:cell
543 atIndexPath:indexPath
544 animateMenuVisibility:NO
545 animateSelectedState:NO];
546 [cell updateWithTitle:bookmark_utils_ios::TitleForBookmarkNode(node)];
547 [cell setButtonTarget:self action:@selector(didTapMenuButton:view:)];
548
549 return cell;
550 }
551
552 - (void)updateEditingStateOfCell:(BookmarkCell*)cell
553 atIndexPath:(NSIndexPath*)indexPath
554 animateMenuVisibility:(BOOL)animateMenuVisibility
555 animateSelectedState:(BOOL)animateSelectedState {
556 BOOL selected = [self cellIsSelectedForEditingAtIndexPath:indexPath];
557 [cell setSelectedForEditing:selected animated:animateSelectedState];
558 BookmarkItemCell* itemCell = static_cast<BookmarkItemCell*>(cell);
559 [itemCell showButtonOfType:[self buttonTypeForCellAtIndexPath:indexPath]
560 animated:animateMenuVisibility];
561 }
562
563 - (void)updateEditingStateOfCellAtIndexPath:(NSIndexPath*)indexPath
564 animateMenuVisibility:(BOOL)animateMenuVisibility
565 animateSelectedState:(BOOL)animateSelectedState {
566 BookmarkCell* cell = base::mac::ObjCCast<BookmarkCell>(
567 [self.collectionView cellForItemAtIndexPath:indexPath]);
568 if (!cell)
569 return;
570
571 [self updateEditingStateOfCell:cell
572 atIndexPath:indexPath
573 animateMenuVisibility:animateMenuVisibility
574 animateSelectedState:animateSelectedState];
575 }
576
577 #pragma mark - BookmarkModelObserver Callbacks
578
579 - (void)bookmarkModelLoaded {
580 NOTREACHED();
581 }
582
583 - (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
584 NOTREACHED();
585 }
586
587 - (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
588 NOTREACHED();
589 }
590
591 - (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
592 movedFromParent:(const BookmarkNode*)oldParent
593 toParent:(const BookmarkNode*)newParent {
594 NOTREACHED();
595 }
596
597 - (void)bookmarkNodeDeleted:(const BookmarkNode*)node
598 fromFolder:(const BookmarkNode*)folder {
599 NOTREACHED();
600 }
601
602 - (void)bookmarkModelRemovedAllNodes {
603 NOTREACHED();
604 }
605
606 #pragma mark - Public Methods That Must Be Overridden
607
608 - (BOOL)shouldSelectCellAtIndexPath:(NSIndexPath*)indexPath {
609 NOTREACHED();
610 return NO;
611 }
612
613 - (void)didTapCellAtIndexPath:(NSIndexPath*)indexPath {
614 NOTREACHED();
615 }
616
617 - (void)didAddCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
618 NOTREACHED();
619 }
620
621 - (void)didRemoveCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
622 NOTREACHED();
623 }
624
625 - (void)didTapMenuButtonAtIndexPath:(NSIndexPath*)indexPath
626 onView:(UIView*)view
627 forCell:(BookmarkItemCell*)cell {
628 NOTREACHED();
629 }
630
631 - (bookmark_cell::ButtonType)buttonTypeForCellAtIndexPath:
632 (NSIndexPath*)indexPath {
633 NOTREACHED();
634 return bookmark_cell::ButtonNone;
635 }
636
637 - (BOOL)allowLongPressForCellAtIndexPath:(NSIndexPath*)indexPath {
638 NOTREACHED();
639 return NO;
640 }
641
642 - (void)didLongPressCell:(UICollectionViewCell*)cell
643 atIndexPath:(NSIndexPath*)indexPath {
644 NOTREACHED();
645 }
646
647 - (BOOL)cellIsSelectedForEditingAtIndexPath:(NSIndexPath*)indexPath {
648 NOTREACHED();
649 return NO;
650 }
651
652 - (CGSize)headerSizeForSection:(NSInteger)section {
653 NOTREACHED();
654 return CGSizeZero;
655 }
656
657 - (UICollectionViewCell*)cellAtIndexPath:(NSIndexPath*)indexPath {
658 NOTREACHED();
659 return nil;
660 }
661
662 - (UICollectionReusableView*)headerAtIndexPath:(NSIndexPath*)indexPath {
663 NOTREACHED();
664 return nil;
665 }
666
667 - (NSInteger)numberOfItemsInSection:(NSInteger)section {
668 NOTREACHED();
669 return 0;
670 }
671 - (NSInteger)numberOfSections {
672 NOTREACHED();
673 return 0;
674 }
675
676 - (void)updateCollectionView {
677 NOTREACHED();
678 }
679
680 - (const bookmarks::BookmarkNode*)nodeAtIndexPath:(NSIndexPath*)indexPath {
681 NOTREACHED();
682 return nullptr;
683 }
684
685 #pragma mark - Methods that subclasses can override (UI)
686
687 - (UIEdgeInsets)insetForSectionAtIndex:(NSInteger)section {
688 if ([self isPromoSection:section])
689 return UIEdgeInsetsZero;
690
691 if (IsIPadIdiom()) {
692 return UIEdgeInsetsMake(10, rowMarginTablet, 0, rowMarginTablet);
693 } else {
694 return UIEdgeInsetsZero;
695 }
696 }
697
698 - (CGSize)cellSizeForIndexPath:(NSIndexPath*)indexPath {
699 if ([self isPromoSection:indexPath.section]) {
700 CGRect estimatedFrame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), 100);
701 UICollectionViewCell* cell =
702 [self.collectionView cellForItemAtIndexPath:indexPath];
703 if (!cell) {
704 cell = [[[BookmarkPromoCell alloc] initWithFrame:estimatedFrame]
705 autorelease];
706 }
707 cell.frame = estimatedFrame;
708 [cell layoutIfNeeded];
709 return [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
710 }
711
712 UIEdgeInsets insets = [self insetForSectionAtIndex:indexPath.section];
713 return CGSizeMake(self.bounds.size.width - (insets.right + insets.left),
714 rowHeight);
715 }
716
717 - (CGFloat)minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
718 return 0;
719 }
720
721 - (CGFloat)minimumLineSpacingForSectionAtIndex:(NSInteger)section {
722 return 0;
723 }
724
725 - (NSString*)textWhenCollectionIsEmpty {
726 return l10n_util::GetNSString(IDS_IOS_BOOKMARK_NO_BOOKMARKS_LABEL);
727 }
728
729 #pragma mark - Public Methods That Can Be Overridden
730
731 - (void)collectionViewScrolled {
732 }
733
734 #pragma mark - Editing
735
736 - (void)setEditing:(BOOL)editing animated:(BOOL)animated {
737 if (self.editing == editing)
738 return;
739
740 _editing = editing;
741 [UIView animateWithDuration:animated ? 0.2 : 0.0
742 delay:0
743 options:UIViewAnimationOptionBeginFromCurrentState
744 animations:^{
745 self.shadow.alpha = editing ? 0.0 : 1.0;
746 }
747 completion:nil];
748
749 // If the promo is active this means that it is removed and added as the edit
750 // mode changes, making reloading the data mandatory.
751 if ([self isPromoActive]) {
752 [self.collectionView reloadData];
753 [self.collectionView.collectionViewLayout invalidateLayout];
754 } else {
755 // Update the visual state of the bookmark cells without reloading the
756 // section.
757 // This prevents flickering of images that need to be asynchronously
758 // reloaded.
759 NSArray* indexPaths = [self.collectionView indexPathsForVisibleItems];
760 for (NSIndexPath* indexPath in indexPaths) {
761 [self updateEditingStateOfCellAtIndexPath:indexPath
762 animateMenuVisibility:animated
763 animateSelectedState:NO];
764 }
765 }
766 }
767
768 #pragma mark - Public Methods
769
770 - (void)changeOrientation:(UIInterfaceOrientation)orientation {
771 [self updateCollectionView];
772 }
773
774 - (CGFloat)contentPositionInPortraitOrientation {
775 if (IsPortrait())
776 return self.collectionView.contentOffset.y;
777
778 // In short landscape mode and portrait mode, there are 2 cells per row.
779 if ([self wideLandscapeMode])
780 return self.collectionView.contentOffset.y;
781
782 // In wide landscape mode, there are 3 cells per row.
783 return self.collectionView.contentOffset.y * 3 / 2.0;
784 }
785
786 - (void)applyContentPosition:(CGFloat)position {
787 if (IsLandscape() && [self wideLandscapeMode]) {
788 position = position * 2 / 3.0;
789 }
790
791 CGFloat y =
792 MIN(position,
793 [self.collectionView.collectionViewLayout collectionViewContentSize]
794 .height);
795 self.collectionView.contentOffset =
796 CGPointMake(self.collectionView.contentOffset.x, y);
797 }
798
799 - (void)setScrollsToTop:(BOOL)scrollsToTop {
800 self.collectionView.scrollsToTop = scrollsToTop;
801 }
802
803 #pragma mark - Private Methods
804
805 - (BOOL)wideLandscapeMode {
806 return self.frame.size.width > 567;
807 }
808
809 #pragma mark - UIGestureRecognizer Callbacks
810
811 - (void)longPress:(UILongPressGestureRecognizer*)recognizer {
812 if (self.longPressRecognizer.numberOfTouches != 1 || self.editing)
813 return;
814
815 if (self.longPressRecognizer.state == UIGestureRecognizerStateRecognized ||
816 self.longPressRecognizer.state == UIGestureRecognizerStateBegan) {
817 CGPoint point =
818 [self.longPressRecognizer locationOfTouch:0 inView:self.collectionView];
819 NSIndexPath* indexPath =
820 [self.collectionView indexPathForItemAtPoint:point];
821 if (!indexPath)
822 return;
823
824 UICollectionViewCell* cell =
825 [self.collectionView cellForItemAtIndexPath:indexPath];
826
827 // Notify the subclass that long press has been received.
828 if (cell)
829 [self didLongPressCell:cell atIndexPath:indexPath];
830 }
831 }
832
833 #pragma mark - UIGestureRecognizerDelegate
834
835 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
836 DCHECK(gestureRecognizer == self.longPressRecognizer);
837 CGPoint point =
838 [gestureRecognizer locationOfTouch:0 inView:self.collectionView];
839 NSIndexPath* indexPath = [self.collectionView indexPathForItemAtPoint:point];
840 if (!indexPath)
841 return NO;
842 return [self allowLongPressForCellAtIndexPath:indexPath];
843 }
844
845 #pragma mark - Promo Cell
846
847 - (BOOL)isPromoSection:(NSInteger)section {
848 return section == 0 && [self shouldShowPromoCell];
849 }
850
851 - (BOOL)shouldShowPromoCell {
852 return NO;
853 }
854
855 - (BOOL)isPromoActive {
856 return NO;
857 }
858
859 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698