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

Side by Side Diff: ios/chrome/browser/ui/bookmarks/bookmark_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
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "ios/chrome/browser/ui/bookmarks/bookmark_collection_view.h" 5 #import "ios/chrome/browser/ui/bookmarks/bookmark_collection_view.h"
6 6
7 #import <UIKit/UIGestureRecognizerSubclass.h> 7 #import <UIKit/UIGestureRecognizerSubclass.h>
8 #include <algorithm> 8 #include <algorithm>
9 #include <map> 9 #include <map>
10 #include <memory> 10 #include <memory>
11 11
12 #include "base/logging.h"
12 #include "base/mac/bind_objc_block.h" 13 #include "base/mac/bind_objc_block.h"
13 #include "base/mac/foundation_util.h" 14 #include "base/mac/foundation_util.h"
14 #include "base/strings/sys_string_conversions.h" 15 #include "base/strings/sys_string_conversions.h"
15 #include "components/bookmarks/browser/bookmark_model.h" 16 #include "components/bookmarks/browser/bookmark_model.h"
16 #include "components/bookmarks/browser/bookmark_model_observer.h" 17 #include "components/bookmarks/browser/bookmark_model_observer.h"
17 #include "components/favicon/core/fallback_url_util.h" 18 #include "components/favicon/core/fallback_url_util.h"
18 #include "components/favicon/core/large_icon_service.h" 19 #include "components/favicon/core/large_icon_service.h"
19 #include "components/favicon_base/fallback_icon_style.h" 20 #include "components/favicon_base/fallback_icon_style.h"
20 #include "components/favicon_base/favicon_types.h" 21 #include "components/favicon_base/favicon_types.h"
21 #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h" 22 #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
22 #include "ios/chrome/browser/bookmarks/bookmarks_utils.h" 23 #include "ios/chrome/browser/bookmarks/bookmarks_utils.h"
24 #include "ios/chrome/browser/experimental_flags.h"
23 #include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h" 25 #include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
26 #import "ios/chrome/browser/ui/authentication/signin_promo_view.h"
27 #import "ios/chrome/browser/ui/authentication/signin_promo_view_configurator.h"
28 #import "ios/chrome/browser/ui/authentication/signin_promo_view_consumer.h"
29 #import "ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h"
24 #import "ios/chrome/browser/ui/bookmarks/bookmark_collection_cells.h" 30 #import "ios/chrome/browser/ui/bookmarks/bookmark_collection_cells.h"
25 #import "ios/chrome/browser/ui/bookmarks/bookmark_collection_view_background.h" 31 #import "ios/chrome/browser/ui/bookmarks/bookmark_collection_view_background.h"
26 #import "ios/chrome/browser/ui/bookmarks/bookmark_promo_cell.h" 32 #import "ios/chrome/browser/ui/bookmarks/bookmark_promo_cell.h"
27 #import "ios/chrome/browser/ui/bookmarks/bookmark_signin_promo_cell.h" 33 #import "ios/chrome/browser/ui/bookmarks/bookmark_signin_promo_cell.h"
28 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h" 34 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
29 #include "ios/chrome/browser/ui/ui_util.h" 35 #include "ios/chrome/browser/ui/ui_util.h"
30 #import "ios/chrome/browser/ui/uikit_ui_util.h" 36 #import "ios/chrome/browser/ui/uikit_ui_util.h"
31 #include "ios/chrome/grit/ios_strings.h" 37 #include "ios/chrome/grit/ios_strings.h"
32 #include "skia/ext/skia_utils_ios.h" 38 #include "skia/ext/skia_utils_ios.h"
33 #include "ui/base/l10n/l10n_util_mac.h" 39 #include "ui/base/l10n/l10n_util_mac.h"
34 40
35 #if !defined(__has_feature) || !__has_feature(objc_arc) 41 #if !defined(__has_feature) || !__has_feature(objc_arc)
36 #error "This file requires ARC support." 42 #error "This file requires ARC support."
37 #endif 43 #endif
38 44
39 using bookmarks::BookmarkNode; 45 using bookmarks::BookmarkNode;
40 46
41 namespace { 47 namespace {
48 // Computes the cell size based on width.
49 CGSize PreferredCellSizeForWidth(UICollectionViewCell* cell, CGFloat width) {
50 CGRect cellFrame = cell.frame;
51 cellFrame.size.width = width;
52 cellFrame.size.height = CGFLOAT_MAX;
53 cell.frame = cellFrame;
54 [cell setNeedsLayout];
55 [cell layoutIfNeeded];
56 CGSize result =
57 [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize
58 withHorizontalFittingPriority:UILayoutPriorityRequired
59 verticalFittingPriority:UILayoutPriorityDefaultLow];
60 cellFrame.size = result;
61 cell.frame = cellFrame;
62 return result;
63 }
42 64
43 // Used to store a pair of NSIntegers when storing a NSIndexPath in C++ 65 // Used to store a pair of NSIntegers when storing a NSIndexPath in C++
44 // collections. 66 // collections.
45 using IntegerPair = std::pair<NSInteger, NSInteger>; 67 using IntegerPair = std::pair<NSInteger, NSInteger>;
46 68
47 // The margin between the side of the view and the first and last tile. 69 // The margin between the side of the view and the first and last tile.
48 CGFloat rowMarginTablet = 24.0; 70 CGFloat rowMarginTablet = 24.0;
49 CGFloat rowHeight = 48.0; 71 CGFloat rowHeight = 48.0;
50 // Minimal acceptable favicon size, in points. 72 // Minimal acceptable favicon size, in points.
51 CGFloat minFaviconSizePt = 16; 73 CGFloat minFaviconSizePt = 16;
52 74
53 // Delay in seconds to which the empty background view will be shown when the 75 // Delay in seconds to which the empty background view will be shown when the
54 // collection view is empty. 76 // collection view is empty.
55 // This delay should not be too small to let enough time to load bookmarks 77 // This delay should not be too small to let enough time to load bookmarks
56 // from network. 78 // from network.
57 const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0; 79 const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
80 }
58 81
59 } // namespace 82 @interface BookmarkCollectionView ()<BookmarkPromoCellDelegate,
60 83 SigninPromoViewConsumer,
61 @interface BookmarkCollectionView ()<UICollectionViewDataSource, 84 UICollectionViewDataSource,
62 UICollectionViewDelegateFlowLayout, 85 UICollectionViewDelegateFlowLayout,
63 UIGestureRecognizerDelegate> { 86 UIGestureRecognizerDelegate> {
87 // A vector of folders to display in the collection view.
88 std::vector<const BookmarkNode*> _subFolders;
89 // A vector of bookmark urls to display in the collection view.
90 std::vector<const BookmarkNode*> _subItems;
91
92 // True if the promo is visible.
93 BOOL _promoVisible;
94
95 // Mediator, helper for the sign-in promo view.
96 SigninPromoViewMediator* _signinPromoViewMediator;
97
64 std::unique_ptr<bookmarks::BookmarkModelBridge> _modelBridge; 98 std::unique_ptr<bookmarks::BookmarkModelBridge> _modelBridge;
65 ios::ChromeBrowserState* _browserState; 99 ios::ChromeBrowserState* _browserState;
66 100
67 // Map of favicon load tasks for each index path. Used to keep track of 101 // 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 102 // pending favicon load operations so that they can be cancelled upon cell
69 // reuse. Keys are (section, item) pairs of cell index paths. 103 // reuse. Keys are (section, item) pairs of cell index paths.
70 std::map<IntegerPair, base::CancelableTaskTracker::TaskId> _faviconLoadTasks; 104 std::map<IntegerPair, base::CancelableTaskTracker::TaskId> _faviconLoadTasks;
71 // Task tracker used for async favicon loads. 105 // Task tracker used for async favicon loads.
72 base::CancelableTaskTracker _faviconTaskTracker; 106 base::CancelableTaskTracker _faviconTaskTracker;
73 } 107 }
74 108
75 // Redefined to be readwrite. 109 // Redefined to be readwrite.
76 @property(nonatomic, assign) bookmarks::BookmarkModel* bookmarkModel; 110 @property(nonatomic, assign) bookmarks::BookmarkModel* bookmarkModel;
77 // Redefined to be readwrite. 111 // Redefined to be readwrite.
78 @property(nonatomic, strong) UICollectionView* collectionView; 112 @property(nonatomic, strong) UICollectionView* collectionView;
79 // Redefined to be readwrite. 113 // Redefined to be readwrite.
80 @property(nonatomic, assign) BOOL editing; 114 @property(nonatomic, assign) BOOL editing;
81 // Detects a long press on a cell. 115 // Detects a long press on a cell.
82 @property(nonatomic, strong) UILongPressGestureRecognizer* longPressRecognizer; 116 @property(nonatomic, strong) UILongPressGestureRecognizer* longPressRecognizer;
83 // Background view of the collection view shown when there is no items. 117 // Background view of the collection view shown when there is no items.
84 @property(nonatomic, strong) 118 @property(nonatomic, strong)
85 BookmarkCollectionViewBackground* emptyCollectionBackgroundView; 119 BookmarkCollectionViewBackground* emptyCollectionBackgroundView;
86 // Shadow to display over the content. 120 // Shadow to display over the content.
87 @property(nonatomic, strong) UIView* shadow; 121 @property(nonatomic, strong) UIView* shadow;
88 122
89 // Updates the editing state for the cell. 123 @property(nonatomic, assign) const bookmarks::BookmarkNode* folder;
90 - (void)updateEditingStateOfCell:(BookmarkCell*)cell
91 atIndexPath:(NSIndexPath*)indexPath
92 animateMenuVisibility:(BOOL)animateMenuVisibility
93 animateSelectedState:(BOOL)animateSelectedState;
94 124
95 // Callback received when the user taps the menu button on the cell. 125 // Section indices.
96 - (void)didTapMenuButton:(BookmarkItemCell*)cell view:(UIView*)view; 126 @property(nonatomic, readonly, assign) NSInteger promoSection;
127 @property(nonatomic, readonly, assign) NSInteger folderSection;
128 @property(nonatomic, readonly, assign) NSInteger itemsSection;
129 @property(nonatomic, readonly, assign) NSInteger sectionCount;
97 130
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 131 @end
113 132
114 @implementation BookmarkCollectionView 133 @implementation BookmarkCollectionView
134 @synthesize delegate = _delegate;
135 @synthesize folder = _folder;
115 @synthesize bookmarkModel = _bookmarkModel; 136 @synthesize bookmarkModel = _bookmarkModel;
116 @synthesize collectionView = _collectionView; 137 @synthesize collectionView = _collectionView;
117 @synthesize editing = _editing; 138 @synthesize editing = _editing;
118 @synthesize emptyCollectionBackgroundView = _emptyCollectionBackgroundView; 139 @synthesize emptyCollectionBackgroundView = _emptyCollectionBackgroundView;
119 @synthesize loader = _loader; 140 @synthesize loader = _loader;
120 @synthesize longPressRecognizer = _longPressRecognizer; 141 @synthesize longPressRecognizer = _longPressRecognizer;
121 @synthesize browserState = _browserState; 142 @synthesize browserState = _browserState;
122 @synthesize shadow = _shadow; 143 @synthesize shadow = _shadow;
123 144
124 #pragma mark - Initialization 145 #pragma mark - Initialization
125 146
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 _browserState = browserState;
141
142 // Set up connection to the BookmarkModel.
143 _bookmarkModel =
144 ios::BookmarkModelFactory::GetForBrowserState(browserState);
145
146 // Set up observers.
147 _modelBridge.reset(
148 new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
149
150 [self setupViews];
151 }
152 return self;
153 }
154
155 - (void)dealloc {
156 _collectionView.dataSource = nil;
157 _collectionView.delegate = nil;
158 UIView* moi = _collectionView;
159 dispatch_async(dispatch_get_main_queue(), ^{
160 // A collection view with a layout that uses a dynamic animator (aka
161 // something that changes the layout over time) will crash if it is
162 // deallocated while the animation is currently playing.
163 // Apparently if a tick has been dispatched it will execute, invoking a
164 // method on the deallocated collection.
165 // The only purpose of this block is to retain the collection view for a
166 // while, giving the layout a chance to perform its last tick.
167 [moi self];
168 });
169 _faviconTaskTracker.TryCancelAll();
170 }
171
172 - (void)setupViews { 147 - (void)setupViews {
173 self.backgroundColor = bookmark_utils_ios::mainBackgroundColor(); 148 self.backgroundColor = bookmark_utils_ios::mainBackgroundColor();
174 UICollectionViewFlowLayout* layout = 149 UICollectionViewFlowLayout* layout =
175 [[UICollectionViewFlowLayout alloc] init]; 150 [[UICollectionViewFlowLayout alloc] init];
176 151
177 UICollectionView* collectionView = 152 UICollectionView* collectionView =
178 [[UICollectionView alloc] initWithFrame:self.bounds 153 [[UICollectionView alloc] initWithFrame:self.bounds
179 collectionViewLayout:layout]; 154 collectionViewLayout:layout];
180 self.collectionView = collectionView; 155 self.collectionView = collectionView;
181 self.collectionView.backgroundColor = [UIColor clearColor]; 156 self.collectionView.backgroundColor = [UIColor clearColor];
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
216 191
217 [self updateShadow]; 192 [self updateShadow];
218 193
219 self.longPressRecognizer = [[UILongPressGestureRecognizer alloc] 194 self.longPressRecognizer = [[UILongPressGestureRecognizer alloc]
220 initWithTarget:self 195 initWithTarget:self
221 action:@selector(longPress:)]; 196 action:@selector(longPress:)];
222 self.longPressRecognizer.delegate = self; 197 self.longPressRecognizer.delegate = self;
223 [self.collectionView addGestureRecognizer:self.longPressRecognizer]; 198 [self.collectionView addGestureRecognizer:self.longPressRecognizer];
224 } 199 }
225 200
201 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
202 frame:(CGRect)frame {
203 self = [super initWithFrame:frame];
204 if (self) {
205 _browserState = browserState;
206
207 // Set up connection to the BookmarkModel.
208 _bookmarkModel =
209 ios::BookmarkModelFactory::GetForBrowserState(browserState);
210
211 // Set up observers.
212 _modelBridge.reset(
213 new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
214
215 [self setupViews];
216 [self updateCollectionView];
217 }
218 return self;
219 }
220
221 - (void)dealloc {
222 _collectionView.dataSource = nil;
223 _collectionView.delegate = nil;
224 UIView* moi = _collectionView;
225 dispatch_async(dispatch_get_main_queue(), ^{
226 // A collection view with a layout that uses a dynamic animator (aka
227 // something that changes the layout over time) will crash if it is
228 // deallocated while the animation is currently playing.
229 // Apparently if a tick has been dispatched it will execute, invoking a
230 // method on the deallocated collection.
231 // The only purpose of this block is to retain the collection view for a
232 // while, giving the layout a chance to perform its last tick.
233 [moi self];
234 });
235 _faviconTaskTracker.TryCancelAll();
236 }
237
238 #pragma mark - BookmarkHomePrimaryView
239
240 - (void)changeOrientation:(UIInterfaceOrientation)orientation {
241 [self updateCollectionView];
242 }
243
244 - (CGFloat)contentPositionInPortraitOrientation {
245 if (IsPortrait())
246 return self.collectionView.contentOffset.y;
247
248 // In short landscape mode and portrait mode, there are 2 cells per row.
249 if ([self wideLandscapeMode])
250 return self.collectionView.contentOffset.y;
251
252 // In wide landscape mode, there are 3 cells per row.
253 return self.collectionView.contentOffset.y * 3 / 2.0;
254 }
255
256 - (void)applyContentPosition:(CGFloat)position {
257 if (IsLandscape() && [self wideLandscapeMode]) {
258 position = position * 2 / 3.0;
259 }
260
261 CGFloat y =
262 MIN(position,
263 [self.collectionView.collectionViewLayout collectionViewContentSize]
264 .height);
265 self.collectionView.contentOffset =
266 CGPointMake(self.collectionView.contentOffset.x, y);
267 }
268
269 - (void)setEditing:(BOOL)editing animated:(BOOL)animated {
270 if (self.editing == editing)
271 return;
272
273 _editing = editing;
274 [UIView animateWithDuration:animated ? 0.2 : 0.0
275 delay:0
276 options:UIViewAnimationOptionBeginFromCurrentState
277 animations:^{
278 self.shadow.alpha = editing ? 0.0 : 1.0;
279 }
280 completion:nil];
281
282 // If the promo is active this means that it is removed and added as the edit
283 // mode changes, making reloading the data mandatory.
284 if ([self isPromoActive]) {
285 [self.collectionView reloadData];
286 [self.collectionView.collectionViewLayout invalidateLayout];
287 } else {
288 // Update the visual state of the bookmark cells without reloading the
289 // section.
290 // This prevents flickering of images that need to be asynchronously
291 // reloaded.
292 NSArray* indexPaths = [self.collectionView indexPathsForVisibleItems];
293 for (NSIndexPath* indexPath in indexPaths) {
294 [self updateEditingStateOfCellAtIndexPath:indexPath
295 animateMenuVisibility:animated
296 animateSelectedState:NO];
297 }
298 }
299 [self promoStateChangedAnimated:animated];
300 }
301
302 - (void)setScrollsToTop:(BOOL)scrollsToTop {
303 self.collectionView.scrollsToTop = scrollsToTop;
304 }
305
306 #pragma mark - Public
307
308 - (void)resetFolder:(const BookmarkNode*)folder {
309 DCHECK(folder->is_folder());
310 self.folder = folder;
311 [self updateCollectionView];
312 }
313
314 - (void)setDelegate:(id<BookmarkCollectionViewDelegate>)delegate {
315 _delegate = delegate;
316 [self promoStateChangedAnimated:NO];
317 }
318
319 - (void)collectionViewScrolled {
320 [self.delegate bookmarkCollectionViewDidScroll:self];
321 }
322
323 - (void)promoStateChangedAnimated:(BOOL)animate {
324 BOOL newPromoState =
325 !self.editing && self.folder &&
326 self.folder->type() == BookmarkNode::MOBILE &&
327 [self.delegate bookmarkCollectionViewShouldShowPromoCell:self];
328 if (newPromoState != _promoVisible) {
329 // This is awful, but until the old code to do the refresh when switching
330 // in and out of edit mode is fixed, this is probably the cleanest thing to
331 // do.
332 _promoVisible = newPromoState;
333 if (experimental_flags::IsSigninPromoEnabled()) {
334 if (!_promoVisible) {
335 _signinPromoViewMediator.consumer = nil;
336 _signinPromoViewMediator = nil;
337 } else {
338 _signinPromoViewMediator = [[SigninPromoViewMediator alloc] init];
339 _signinPromoViewMediator.consumer = self;
340 _signinPromoViewMediator.accessPoint =
341 signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER;
342 }
343 }
344 [self.collectionView reloadData];
345 }
346 }
347
348 #pragma mark - Sections
349
350 - (NSInteger)promoSection {
351 return [self shouldShowPromoCell] ? 0 : -1;
352 }
353
354 - (NSInteger)folderSection {
355 return [self shouldShowPromoCell] ? 1 : 0;
356 }
357
358 - (NSInteger)itemsSection {
359 return [self shouldShowPromoCell] ? 2 : 1;
360 }
361
362 - (NSInteger)sectionCount {
363 return [self shouldShowPromoCell] ? 3 : 2;
364 }
365
226 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { 366 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
227 [self updateShadow]; 367 [self updateShadow];
228 } 368 }
229 369
230 - (void)updateShadow { 370 - (void)updateShadow {
231 // Remove the current one, if any. 371 // Remove the current one, if any.
232 [self.shadow removeFromSuperview]; 372 [self.shadow removeFromSuperview];
233 373
234 if (IsCompact(self)) { 374 if (IsCompact(self)) {
235 self.shadow = 375 self.shadow =
(...skipping 15 matching lines...) Expand all
251 } 391 }
252 392
253 - (void)updateShadowFrame { 393 - (void)updateShadowFrame {
254 CGFloat shadowHeight = CGRectGetHeight(self.shadow.frame); 394 CGFloat shadowHeight = CGRectGetHeight(self.shadow.frame);
255 CGFloat y = std::min<CGFloat>( 395 CGFloat y = std::min<CGFloat>(
256 0.0, self.collectionView.contentOffset.y - shadowHeight); 396 0.0, self.collectionView.contentOffset.y - shadowHeight);
257 self.shadow.frame = 397 self.shadow.frame =
258 CGRectMake(0, y, CGRectGetWidth(self.bounds), shadowHeight); 398 CGRectMake(0, y, CGRectGetWidth(self.bounds), shadowHeight);
259 } 399 }
260 400
401 #pragma mark - BookmarkItemCell callbacks
402
403 // Callback received when the user taps the menu button on the cell.
404 - (void)didTapMenuButton:(BookmarkItemCell*)cell view:(UIView*)view {
405 [self didTapMenuButtonAtIndexPath:[self.collectionView indexPathForCell:cell]
406 onView:view
407 forCell:cell];
408 }
409
410 #pragma mark - BookmarkModelBridgeObserver Callbacks
411 // BookmarkModelBridgeObserver Callbacks
412 // Instances of this class automatically observe the bookmark model.
413 // The bookmark model has loaded.
414 - (void)bookmarkModelLoaded {
415 [self updateCollectionView];
416 }
417
418 // The node has changed, but not its children.
419 - (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
420 // The base folder changed. Do nothing.
421 if (bookmarkNode == self.folder)
422 return;
423
424 // A specific cell changed. Reload that cell.
425 NSIndexPath* indexPath = [self indexPathForNode:bookmarkNode];
426
427 if (indexPath) {
428 // TODO(crbug.com/603661): Ideally, we would only reload the relevant index
429 // path. However, calling reloadItemsAtIndexPaths:(0,0) immediately after
430 // reloadData results in a exception: NSInternalInconsistencyException
431 // 'request for index path for global index 2147483645 ...'
432 // One solution would be to keep track of whether we've just called
433 // reloadData, but that requires experimentation to determine how long we
434 // have to wait before we can safely call reloadItemsAtIndexPaths.
435 [self updateCollectionView];
436 }
437 }
438
439 // The node has not changed, but its children have.
440 - (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
441 // The base folder's children changed. Reload everything.
442 if (bookmarkNode == self.folder) {
443 [self updateCollectionView];
444 return;
445 }
446
447 // A subfolder's children changed. Reload that cell.
448 std::vector<const BookmarkNode*>::iterator it =
449 std::find(_subFolders.begin(), _subFolders.end(), bookmarkNode);
450 if (it != _subFolders.end()) {
451 // TODO(crbug.com/603661): Ideally, we would only reload the relevant index
452 // path. However, calling reloadItemsAtIndexPaths:(0,0) immediately after
453 // reloadData results in a exception: NSInternalInconsistencyException
454 // 'request for index path for global index 2147483645 ...'
455 // One solution would be to keep track of whether we've just called
456 // reloadData, but that requires experimentation to determine how long we
457 // have to wait before we can safely call reloadItemsAtIndexPaths.
458 [self updateCollectionView];
459 }
460 }
461
462 // The node has moved to a new parent folder.
463 - (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
464 movedFromParent:(const BookmarkNode*)oldParent
465 toParent:(const BookmarkNode*)newParent {
466 if (oldParent == self.folder || newParent == self.folder) {
467 // A folder was added or removed from the base folder.
468 [self updateCollectionView];
469 }
470 }
471
472 // |node| was deleted from |folder|.
473 - (void)bookmarkNodeDeleted:(const BookmarkNode*)node
474 fromFolder:(const BookmarkNode*)folder {
475 if (self.folder == node) {
476 self.folder = nil;
477 [self updateCollectionView];
478 }
479 }
480
481 // All non-permanent nodes have been removed.
482 - (void)bookmarkModelRemovedAllNodes {
483 self.folder = nil;
484 [self updateCollectionView];
485 }
486
487 - (void)bookmarkNodeFaviconChanged:
488 (const bookmarks::BookmarkNode*)bookmarkNode {
489 // Only urls have favicons.
490 DCHECK(bookmarkNode->is_url());
491
492 // Update image of corresponding cell.
493 NSIndexPath* indexPath = [self indexPathForNode:bookmarkNode];
494
495 if (!indexPath)
496 return;
497
498 // Check that this cell is visible.
499 NSArray* visiblePaths = [self.collectionView indexPathsForVisibleItems];
500 if (![visiblePaths containsObject:indexPath])
501 return;
502
503 [self loadFaviconAtIndexPath:indexPath];
504 }
505
506 #pragma mark - non-UI
507
508 // Called when a user is attempting to select a cell.
509 // Returning NO prevents the cell from being selected.
510 - (BOOL)shouldSelectCellAtIndexPath:(NSIndexPath*)indexPath {
511 return YES;
512 }
513
514 // Called when a cell is tapped outside of editing mode.
515 - (void)didTapCellAtIndexPath:(NSIndexPath*)indexPath {
516 if (indexPath.section == self.promoSection) {
517 // User tapped inside promo cell but not on one of the buttons. Ignore it.
518 return;
519 }
520
521 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
522 DCHECK(node);
523
524 if (indexPath.section == self.folderSection) {
525 [self.delegate bookmarkCollectionView:self
526 selectedFolderForNavigation:node];
527 } else {
528 RecordBookmarkLaunch(BOOKMARK_LAUNCH_LOCATION_FOLDER);
529 [self.delegate bookmarkCollectionView:self
530 selectedUrlForNavigation:node->url()];
531 }
532 }
533
534 // Called when a user selected a cell in the editing state.
535 - (void)didAddCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
536 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
537 UICollectionViewCell* cell =
538 [self.collectionView cellForItemAtIndexPath:indexPath];
539 [self.delegate bookmarkCollectionView:self cell:cell addNodeForEditing:node];
540 }
541
542 - (void)didRemoveCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
543 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
544 UICollectionViewCell* cell =
545 [self.collectionView cellForItemAtIndexPath:indexPath];
546 [self.delegate bookmarkCollectionView:self
547 cell:cell
548 removeNodeForEditing:node];
549 }
550
551 // Called when a user taps the menu button on a cell.
552 - (void)didTapMenuButtonAtIndexPath:(NSIndexPath*)indexPath
553 onView:(UIView*)view
554 forCell:(BookmarkItemCell*)cell {
555 [self.delegate bookmarkCollectionView:self
556 wantsMenuForBookmark:[self nodeAtIndexPath:indexPath]
557 onView:view
558 forCell:cell];
559 }
560
561 // Whether a cell should show a button and of which type.
562 - (bookmark_cell::ButtonType)buttonTypeForCellAtIndexPath:
563 (NSIndexPath*)indexPath {
564 return self.editing ? bookmark_cell::ButtonNone : bookmark_cell::ButtonMenu;
565 }
566
567 // Whether a long press at the cell at |indexPath| should be allowed.
568 - (BOOL)allowLongPressForCellAtIndexPath:(NSIndexPath*)indexPath {
569 return !self.editing;
570 }
571
572 // The |cell| at |indexPath| received a long press.
573 - (void)didLongPressCell:(UICollectionViewCell*)cell
574 atIndexPath:(NSIndexPath*)indexPath {
575 if (indexPath.section == self.promoSection) {
576 // User long-pressed inside promo cell. Ignore it.
577 return;
578 }
579
580 [self.delegate bookmarkCollectionView:self
581 didLongPressCell:cell
582 forBookmark:[self nodeAtIndexPath:indexPath]];
583 }
584
585 // Whether the cell has been selected in editing mode.
586 - (BOOL)cellIsSelectedForEditingAtIndexPath:(NSIndexPath*)indexPath {
587 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
588 const std::set<const BookmarkNode*>& editingNodes =
589 [self.delegate nodesBeingEdited];
590 return editingNodes.find(node) != editingNodes.end();
591 }
592
593 // Updates the collection view based on the current state of all models and
594 // contextual parameters, such as the interface orientation.
595 - (void)updateCollectionView {
596 if (!self.bookmarkModel->loaded())
597 return;
598
599 // Regenerate the list of all bookmarks.
600 _subFolders = std::vector<const BookmarkNode*>();
601 _subItems = std::vector<const BookmarkNode*>();
602
603 if (self.folder) {
604 int childCount = self.folder->child_count();
605 for (int i = 0; i < childCount; ++i) {
606 const BookmarkNode* node = self.folder->GetChild(i);
607 if (node->is_folder())
608 _subFolders.push_back(node);
609 else
610 _subItems.push_back(node);
611 }
612
613 bookmark_utils_ios::SortFolders(&_subFolders);
614 }
615
616 [self cancelAllFaviconLoads];
617 [self.collectionView reloadData];
618 }
619
620 // Returns the bookmark node associated with |indexPath|.
621 - (const BookmarkNode*)nodeAtIndexPath:(NSIndexPath*)indexPath {
622 if (indexPath.section == self.folderSection)
623 return _subFolders[indexPath.row];
624 if (indexPath.section == self.itemsSection)
625 return _subItems[indexPath.row];
626
627 NOTREACHED();
628 return nullptr;
629 }
630
631 #pragma mark -
632
633 - (NSIndexPath*)indexPathForNode:(const bookmarks::BookmarkNode*)bookmarkNode {
634 NSIndexPath* indexPath = nil;
635 if (bookmarkNode->is_folder()) {
636 std::vector<const BookmarkNode*>::iterator it =
637 std::find(_subFolders.begin(), _subFolders.end(), bookmarkNode);
638 if (it != _subFolders.end()) {
639 ptrdiff_t index = std::distance(_subFolders.begin(), it);
640 indexPath =
641 [NSIndexPath indexPathForRow:index inSection:self.folderSection];
642 }
643 } else if (bookmarkNode->is_url()) {
644 std::vector<const BookmarkNode*>::iterator it =
645 std::find(_subItems.begin(), _subItems.end(), bookmarkNode);
646 if (it != _subItems.end()) {
647 ptrdiff_t index = std::distance(_subItems.begin(), it);
648 indexPath =
649 [NSIndexPath indexPathForRow:index inSection:self.itemsSection];
650 }
651 }
652 return indexPath;
653 }
654
655 - (void)collectionView:(UICollectionView*)collectionView
656 willDisplayCell:(UICollectionViewCell*)cell
657 forItemAtIndexPath:(NSIndexPath*)indexPath {
658 if (indexPath.section == self.itemsSection) {
659 [self loadFaviconAtIndexPath:indexPath];
660 }
661 }
662
663 - (CGFloat)interitemSpacingForSectionAtIndex:(NSInteger)section {
664 CGFloat interitemSpacing = 0;
665 SEL minimumInteritemSpacingSelector = @selector
666 (collectionView:layout:minimumInteritemSpacingForSectionAtIndex:);
667 if ([self.collectionView.delegate
668 respondsToSelector:minimumInteritemSpacingSelector]) {
669 id collectionViewDelegate = static_cast<id>(self.collectionView.delegate);
670 interitemSpacing =
671 [collectionViewDelegate collectionView:self.collectionView
672 layout:self.collectionView
673 .collectionViewLayout
674 minimumInteritemSpacingForSectionAtIndex:section];
675 } else if ([self.collectionView.collectionViewLayout
676 isKindOfClass:[UICollectionViewFlowLayout class]]) {
677 UICollectionViewFlowLayout* flowLayout =
678 static_cast<UICollectionViewFlowLayout*>(
679 self.collectionView.collectionViewLayout);
680 interitemSpacing = flowLayout.minimumInteritemSpacing;
681 }
682 return interitemSpacing;
683 }
684
685 // The inset of the section.
686 - (UIEdgeInsets)insetForSectionAtIndex:(NSInteger)section {
687 UIEdgeInsets insets = UIEdgeInsetsZero;
688 if ([self isPromoSection:section])
689 insets = UIEdgeInsetsZero;
690
691 if (IsIPadIdiom()) {
692 insets = UIEdgeInsetsMake(10, rowMarginTablet, 0, rowMarginTablet);
693 } else {
694 insets = UIEdgeInsetsZero;
695 }
696
697 if (section == self.folderSection)
698 insets.bottom = [self interitemSpacingForSectionAtIndex:section] / 2.;
699 else if (section == self.itemsSection)
700 insets.top = [self interitemSpacingForSectionAtIndex:section] / 2.;
701 else if (section == self.promoSection)
702 (void)0; // No insets to update.
703 else
704 NOTREACHED();
705 return insets;
706 }
707
708 // The size of the cell at |indexPath|.
709 - (CGSize)cellSizeForIndexPath:(NSIndexPath*)indexPath {
710 if ([self isPromoSection:indexPath.section]) {
711 UICollectionViewCell* cell =
712 [self.collectionView cellForItemAtIndexPath:indexPath];
713 if (!cell) {
714 // -[UICollectionView
715 // dequeueReusableCellWithReuseIdentifier:forIndexPath:] cannot be used
716 // here since this method is called by -[id<UICollectionViewDelegate>
717 // collectionView:layout:sizeForItemAtIndexPath:]. This would generate
718 // crash: SIGFPE, EXC_I386_DIV.
719 if (experimental_flags::IsSigninPromoEnabled()) {
720 DCHECK(_signinPromoViewMediator);
721 BookmarkSigninPromoCell* signinPromoCell =
722 [[BookmarkSigninPromoCell alloc]
723 initWithFrame:CGRectMake(0, 0, 1000, 1000)];
724 [[_signinPromoViewMediator createConfigurator]
725 configureSigninPromoView:signinPromoCell.signinPromoView];
726 cell = signinPromoCell;
727 } else {
728 cell = [[BookmarkPromoCell alloc] init];
729 }
730 }
731 return PreferredCellSizeForWidth(cell, CGRectGetWidth(self.bounds));
732 }
733 DCHECK(![self isPromoSection:indexPath.section]);
734 UIEdgeInsets insets = [self insetForSectionAtIndex:indexPath.section];
735 return CGSizeMake(self.bounds.size.width - (insets.right + insets.left),
736 rowHeight);
737 }
738
739 - (BOOL)needsSectionHeaderForSection:(NSInteger)section {
740 // Only show header when there is at least one element in the previous
741 // section.
742 if (section == 0)
743 return NO;
744
745 if ([self numberOfItemsInSection:(section - 1)] == 0)
746 return NO;
747
748 return YES;
749 }
750
751 #pragma mark - UI
752
753 // The size of the header for |section|. A return value of CGSizeZero prevents
754 // a header from showing.
755 - (CGSize)headerSizeForSection:(NSInteger)section {
756 if ([self needsSectionHeaderForSection:section])
757 return CGSizeMake(self.bounds.size.width,
758 [BookmarkHeaderSeparatorView preferredHeight]);
759
760 return CGSizeZero;
761 }
762
763 // Create a cell for display at |indexPath|.
764 - (UICollectionViewCell*)cellAtIndexPath:(NSIndexPath*)indexPath {
765 if (indexPath.section == self.promoSection) {
766 if (experimental_flags::IsSigninPromoEnabled()) {
767 BookmarkSigninPromoCell* signinPromoCell = [self.collectionView
768 dequeueReusableCellWithReuseIdentifier:[BookmarkSigninPromoCell
769 reuseIdentifier]
770 forIndexPath:indexPath];
771 signinPromoCell.signinPromoView.delegate = _signinPromoViewMediator;
772 [[_signinPromoViewMediator createConfigurator]
773 configureSigninPromoView:signinPromoCell.signinPromoView];
774 __weak BookmarkCollectionView* weakSelf = self;
775 signinPromoCell.closeButtonAction = ^() {
776 [weakSelf.delegate bookmarkCollectionViewDismissPromo:self];
777 };
778 return signinPromoCell;
779 } else {
780 BookmarkPromoCell* promoCell = [self.collectionView
781 dequeueReusableCellWithReuseIdentifier:[BookmarkPromoCell
782 reuseIdentifier]
783 forIndexPath:indexPath];
784 promoCell.delegate = self;
785 return promoCell;
786 }
787 }
788 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
789
790 if (indexPath.section == self.folderSection)
791 return [self cellForFolder:node indexPath:indexPath];
792
793 BookmarkItemCell* cell = [self cellForBookmark:node indexPath:indexPath];
794 return cell;
795 }
796
797 // Create a header view for the element at |indexPath|.
798 - (UICollectionReusableView*)headerAtIndexPath:(NSIndexPath*)indexPath {
799 if (![self needsSectionHeaderForSection:indexPath.section])
800 return nil;
801
802 BookmarkHeaderSeparatorView* view = [self.collectionView
803 dequeueReusableSupplementaryViewOfKind:
804 UICollectionElementKindSectionHeader
805 withReuseIdentifier:[BookmarkHeaderSeparatorView
806 reuseIdentifier]
807 forIndexPath:indexPath];
808 view.backgroundColor = [UIColor colorWithWhite:1 alpha:1];
809 return view;
810 }
811
812 - (NSInteger)numberOfItemsInSection:(NSInteger)section {
813 if (section == self.folderSection)
814 return _subFolders.size();
815 if (section == self.itemsSection)
816 return _subItems.size();
817 if (section == self.promoSection)
818 return 1;
819
820 NOTREACHED();
821 return -1;
822 }
823
824 - (NSInteger)numberOfSections {
825 return self.sectionCount;
826 }
827
828 #pragma mark - BookmarkPromoCellDelegate
829
830 - (void)bookmarkPromoCellDidTapSignIn:(BookmarkPromoCell*)bookmarkPromoCell {
831 [self.delegate bookmarkCollectionViewShowSignIn:self];
832 }
833
834 - (void)bookmarkPromoCellDidTapDismiss:(BookmarkPromoCell*)bookmarkPromoCell {
835 [self.delegate bookmarkCollectionViewDismissPromo:self];
836 }
837
838 #pragma mark - SigninPromoViewConsumer
839
840 - (void)configureSigninPromoWithConfigurator:
841 (SigninPromoViewConfigurator*)configurator
842 identityChanged:(BOOL)identityChanged {
843 DCHECK(_signinPromoViewMediator);
844 NSIndexPath* indexPath =
845 [NSIndexPath indexPathForRow:0 inSection:self.promoSection];
846 BookmarkSigninPromoCell* signinPromoCell =
847 static_cast<BookmarkSigninPromoCell*>(
848 [self.collectionView cellForItemAtIndexPath:indexPath]);
849 if (!signinPromoCell)
850 return;
851 // Should always reconfigure the cell size even if it has to be reloaded.
852 // -[BookmarkCollectionView cellSizeForIndexPath:] uses the current
853 // cell to compute its height.
854 [configurator configureSigninPromoView:signinPromoCell.signinPromoView];
855 if (identityChanged) {
856 // The section should be reload to update the cell height.
857 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:self.promoSection];
858 [self.collectionView reloadSections:indexSet];
859 }
860 }
861
261 #pragma mark - UIScrollViewDelegate 862 #pragma mark - UIScrollViewDelegate
262 863
263 - (void)scrollViewDidScroll:(UIScrollView*)scrollView { 864 - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
264 [self updateShadowFrame]; 865 [self updateShadowFrame];
265 [self collectionViewScrolled]; 866 [self collectionViewScrolled];
266 } 867 }
267 868
268 #pragma mark - empty background 869 #pragma mark - empty background
269 870
871 // Schedules showing or hiding the empty bookmarks background view if the
872 // collection view is empty by calling showEmptyBackgroundIfNeeded after
873 // kShowEmptyBookmarksBackgroundRefreshDelay.
874 // Multiple call to this method will cancel previous scheduled call to
875 // showEmptyBackgroundIfNeeded before scheduling a new one.
270 - (void)scheduleEmptyBackgroundVisibilityUpdate { 876 - (void)scheduleEmptyBackgroundVisibilityUpdate {
271 [NSObject 877 [NSObject cancelPreviousPerformRequestsWithTarget:self
272 cancelPreviousPerformRequestsWithTarget:self 878 selector:@selector
273 selector: 879 (updateEmptyBackgroundVisibility)
274 @selector( 880 object:nil];
275 updateEmptyBackgroundVisibility)
276 object:nil];
277 [self performSelector:@selector(updateEmptyBackgroundVisibility) 881 [self performSelector:@selector(updateEmptyBackgroundVisibility)
278 withObject:nil 882 withObject:nil
279 afterDelay:kShowEmptyBookmarksBackgroundRefreshDelay]; 883 afterDelay:kShowEmptyBookmarksBackgroundRefreshDelay];
280 } 884 }
281 885
282 - (BOOL)isCollectionViewEmpty { 886 - (BOOL)isCollectionViewEmpty {
283 BOOL collectionViewIsEmpty = YES; 887 BOOL collectionViewIsEmpty = YES;
284 const NSInteger numberOfSections = [self numberOfSections]; 888 const NSInteger numberOfSections = [self numberOfSections];
285 NSInteger section = [self shouldShowPromoCell] ? 1 : 0; 889 NSInteger section = [self shouldShowPromoCell] ? 1 : 0;
286 for (; collectionViewIsEmpty && section < numberOfSections; ++section) { 890 for (; collectionViewIsEmpty && section < numberOfSections; ++section) {
287 const NSInteger numberOfItemsInSection = 891 const NSInteger numberOfItemsInSection =
288 [self numberOfItemsInSection:section]; 892 [self numberOfItemsInSection:section];
289 collectionViewIsEmpty = numberOfItemsInSection == 0; 893 collectionViewIsEmpty = numberOfItemsInSection == 0;
290 } 894 }
291 return collectionViewIsEmpty; 895 return collectionViewIsEmpty;
292 } 896 }
293 897
898 // Shows/hides empty bookmarks background view if the collections view is empty.
294 - (void)updateEmptyBackgroundVisibility { 899 - (void)updateEmptyBackgroundVisibility {
295 const BOOL showEmptyBackground = 900 const BOOL showEmptyBackground =
296 [self isCollectionViewEmpty] && ![self shouldShowPromoCell]; 901 [self isCollectionViewEmpty] && ![self shouldShowPromoCell];
297 [self setEmptyBackgroundVisible:showEmptyBackground]; 902 [self setEmptyBackgroundVisible:showEmptyBackground];
298 } 903 }
299 904
905 // Shows/hides empty bookmarks background view with an animation.
300 - (void)setEmptyBackgroundVisible:(BOOL)emptyBackgroundVisible { 906 - (void)setEmptyBackgroundVisible:(BOOL)emptyBackgroundVisible {
301 [UIView beginAnimations:@"alpha" context:NULL]; 907 [UIView beginAnimations:@"alpha" context:NULL];
302 self.emptyCollectionBackgroundView.alpha = emptyBackgroundVisible ? 1 : 0; 908 self.emptyCollectionBackgroundView.alpha = emptyBackgroundVisible ? 1 : 0;
303 [UIView commitAnimations]; 909 [UIView commitAnimations];
304 } 910 }
305 911
306 #pragma mark - UICollectionViewDataSource 912 #pragma mark - UICollectionViewDataSource
307 913
308 - (NSInteger)collectionView:(UICollectionView*)collectionView 914 - (NSInteger)collectionView:(UICollectionView*)collectionView
309 numberOfItemsInSection:(NSInteger)section { 915 numberOfItemsInSection:(NSInteger)section {
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
406 return [self cellSizeForIndexPath:indexPath]; 1012 return [self cellSizeForIndexPath:indexPath];
407 } 1013 }
408 1014
409 - (CGSize)collectionView:(UICollectionView*)collectionView 1015 - (CGSize)collectionView:(UICollectionView*)collectionView
410 layout: 1016 layout:
411 (UICollectionViewLayout*)collectionViewLayout 1017 (UICollectionViewLayout*)collectionViewLayout
412 referenceSizeForHeaderInSection:(NSInteger)section { 1018 referenceSizeForHeaderInSection:(NSInteger)section {
413 return [self headerSizeForSection:section]; 1019 return [self headerSizeForSection:section];
414 } 1020 }
415 1021
416 #pragma mark - BookmarkItemCell callbacks 1022 #pragma mark - UIGestureRecognizer Callbacks
417 1023
418 - (void)didTapMenuButton:(BookmarkItemCell*)cell view:(UIView*)view { 1024 - (void)longPress:(UILongPressGestureRecognizer*)recognizer {
419 [self didTapMenuButtonAtIndexPath:[self.collectionView indexPathForCell:cell] 1025 if (self.longPressRecognizer.numberOfTouches != 1 || self.editing)
420 onView:view 1026 return;
421 forCell:cell]; 1027
1028 if (self.longPressRecognizer.state == UIGestureRecognizerStateRecognized ||
1029 self.longPressRecognizer.state == UIGestureRecognizerStateBegan) {
1030 CGPoint point =
1031 [self.longPressRecognizer locationOfTouch:0 inView:self.collectionView];
1032 NSIndexPath* indexPath =
1033 [self.collectionView indexPathForItemAtPoint:point];
1034 if (!indexPath)
1035 return;
1036
1037 UICollectionViewCell* cell =
1038 [self.collectionView cellForItemAtIndexPath:indexPath];
1039
1040 // Notify the subclass that long press has been received.
1041 if (cell)
1042 [self didLongPressCell:cell atIndexPath:indexPath];
1043 }
422 } 1044 }
423 1045
424 #pragma mark - Convenience methods for subclasses 1046 #pragma mark - UIGestureRecognizerDelegate
1047
1048 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
1049 DCHECK(gestureRecognizer == self.longPressRecognizer);
1050 CGPoint point =
1051 [gestureRecognizer locationOfTouch:0 inView:self.collectionView];
1052 NSIndexPath* indexPath = [self.collectionView indexPathForItemAtPoint:point];
1053 if (!indexPath)
1054 return NO;
1055 return [self allowLongPressForCellAtIndexPath:indexPath];
1056 }
1057
1058 #pragma mark - Convenience methods
425 1059
426 - (void)updateCellAtIndexPath:(NSIndexPath*)indexPath 1060 - (void)updateCellAtIndexPath:(NSIndexPath*)indexPath
427 withImage:(UIImage*)image 1061 withImage:(UIImage*)image
428 backgroundColor:(UIColor*)backgroundColor 1062 backgroundColor:(UIColor*)backgroundColor
429 textColor:(UIColor*)textColor 1063 textColor:(UIColor*)textColor
430 fallbackText:(NSString*)text { 1064 fallbackText:(NSString*)text {
431 BookmarkItemCell* cell = base::mac::ObjCCast<BookmarkItemCell>( 1065 BookmarkItemCell* cell = base::mac::ObjCCast<BookmarkItemCell>(
432 [self.collectionView cellForItemAtIndexPath:indexPath]); 1066 [self.collectionView cellForItemAtIndexPath:indexPath]);
433 if (!cell) 1067 if (!cell)
434 return; 1068 return;
(...skipping 24 matching lines...) Expand all
459 [self updateEditingStateOfCell:cell 1093 [self updateEditingStateOfCell:cell
460 atIndexPath:indexPath 1094 atIndexPath:indexPath
461 animateMenuVisibility:NO 1095 animateMenuVisibility:NO
462 animateSelectedState:NO]; 1096 animateSelectedState:NO];
463 1097
464 [self loadFaviconAtIndexPath:indexPath]; 1098 [self loadFaviconAtIndexPath:indexPath];
465 1099
466 return cell; 1100 return cell;
467 } 1101 }
468 1102
1103 // Cancels all async loads of favicons. Subclasses should call this method when
1104 // the bookmark model is going through significant changes, then manually call
1105 // loadFaviconAtIndexPath: for everything that needs to be loaded; or
1106 // just reload relevant cells.
469 - (void)cancelAllFaviconLoads { 1107 - (void)cancelAllFaviconLoads {
470 _faviconTaskTracker.TryCancelAll(); 1108 _faviconTaskTracker.TryCancelAll();
471 } 1109 }
472 1110
473 - (void)cancelLoadingFaviconAtIndexPath:(NSIndexPath*)indexPath { 1111 - (void)cancelLoadingFaviconAtIndexPath:(NSIndexPath*)indexPath {
474 _faviconTaskTracker.TryCancel( 1112 _faviconTaskTracker.TryCancel(
475 _faviconLoadTasks[IntegerPair(indexPath.section, indexPath.item)]); 1113 _faviconLoadTasks[IntegerPair(indexPath.section, indexPath.item)]);
476 } 1114 }
477 1115
1116 // Asynchronously loads favicon for given index path. The loads are cancelled
1117 // upon cell reuse automatically.
478 - (void)loadFaviconAtIndexPath:(NSIndexPath*)indexPath { 1118 - (void)loadFaviconAtIndexPath:(NSIndexPath*)indexPath {
479 // Cancel previous load attempts. 1119 // Cancel previous load attempts.
480 [self cancelLoadingFaviconAtIndexPath:indexPath]; 1120 [self cancelLoadingFaviconAtIndexPath:indexPath];
481 1121
482 // Start loading a favicon. 1122 // Start loading a favicon.
483 __weak BookmarkCollectionView* weakSelf = self; 1123 __weak BookmarkCollectionView* weakSelf = self;
484 const bookmarks::BookmarkNode* node = [self nodeAtIndexPath:indexPath]; 1124 const bookmarks::BookmarkNode* node = [self nodeAtIndexPath:indexPath];
485 GURL blockURL(node->url()); 1125 GURL blockURL(node->url());
486 void (^faviconBlock)(const favicon_base::LargeIconResult&) = ^( 1126 void (^faviconBlock)(const favicon_base::LargeIconResult&) =
487 const favicon_base::LargeIconResult& result) { 1127 ^(const favicon_base::LargeIconResult& result) {
488 BookmarkCollectionView* strongSelf = weakSelf; 1128 BookmarkCollectionView* strongSelf = weakSelf;
489 if (!strongSelf) 1129 if (!strongSelf)
490 return; 1130 return;
491 UIImage* favIcon = nil; 1131 UIImage* favIcon = nil;
492 UIColor* backgroundColor = nil; 1132 UIColor* backgroundColor = nil;
493 UIColor* textColor = nil; 1133 UIColor* textColor = nil;
494 NSString* fallbackText = nil; 1134 NSString* fallbackText = nil;
495 if (result.bitmap.is_valid()) { 1135 if (result.bitmap.is_valid()) {
496 scoped_refptr<base::RefCountedMemory> data = result.bitmap.bitmap_data; 1136 scoped_refptr<base::RefCountedMemory> data =
497 favIcon = [UIImage imageWithData:[NSData dataWithBytes:data->front() 1137 result.bitmap.bitmap_data;
498 length:data->size()]]; 1138 favIcon = [UIImage imageWithData:[NSData dataWithBytes:data->front()
499 } else if (result.fallback_icon_style) { 1139 length:data->size()]];
500 backgroundColor = skia::UIColorFromSkColor( 1140 } else if (result.fallback_icon_style) {
501 result.fallback_icon_style->background_color); 1141 backgroundColor = skia::UIColorFromSkColor(
502 textColor = 1142 result.fallback_icon_style->background_color);
503 skia::UIColorFromSkColor(result.fallback_icon_style->text_color); 1143 textColor =
1144 skia::UIColorFromSkColor(result.fallback_icon_style->text_color);
504 1145
505 fallbackText = 1146 fallbackText =
506 base::SysUTF16ToNSString(favicon::GetFallbackIconText(blockURL)); 1147 base::SysUTF16ToNSString(favicon::GetFallbackIconText(blockURL));
507 } 1148 }
508 1149
509 [strongSelf updateCellAtIndexPath:indexPath 1150 [strongSelf updateCellAtIndexPath:indexPath
510 withImage:favIcon 1151 withImage:favIcon
511 backgroundColor:backgroundColor 1152 backgroundColor:backgroundColor
512 textColor:textColor 1153 textColor:textColor
513 fallbackText:fallbackText]; 1154 fallbackText:fallbackText];
514 }; 1155 };
515 1156
516 CGFloat scale = [UIScreen mainScreen].scale; 1157 CGFloat scale = [UIScreen mainScreen].scale;
517 CGFloat preferredSize = scale * [BookmarkItemCell preferredImageSize]; 1158 CGFloat preferredSize = scale * [BookmarkItemCell preferredImageSize];
518 CGFloat minSize = scale * minFaviconSizePt; 1159 CGFloat minSize = scale * minFaviconSizePt;
519 1160
520 base::CancelableTaskTracker::TaskId taskId = 1161 base::CancelableTaskTracker::TaskId taskId =
521 IOSChromeLargeIconServiceFactory::GetForBrowserState(self.browserState) 1162 IOSChromeLargeIconServiceFactory::GetForBrowserState(self.browserState)
522 ->GetLargeIconOrFallbackStyle(node->url(), minSize, preferredSize, 1163 ->GetLargeIconOrFallbackStyle(node->url(), minSize, preferredSize,
523 base::BindBlockArc(faviconBlock), 1164 base::BindBlockArc(faviconBlock),
524 &_faviconTaskTracker); 1165 &_faviconTaskTracker);
(...skipping 10 matching lines...) Expand all
535 [self updateEditingStateOfCell:cell 1176 [self updateEditingStateOfCell:cell
536 atIndexPath:indexPath 1177 atIndexPath:indexPath
537 animateMenuVisibility:NO 1178 animateMenuVisibility:NO
538 animateSelectedState:NO]; 1179 animateSelectedState:NO];
539 [cell updateWithTitle:bookmark_utils_ios::TitleForBookmarkNode(node)]; 1180 [cell updateWithTitle:bookmark_utils_ios::TitleForBookmarkNode(node)];
540 [cell setButtonTarget:self action:@selector(didTapMenuButton:view:)]; 1181 [cell setButtonTarget:self action:@selector(didTapMenuButton:view:)];
541 1182
542 return cell; 1183 return cell;
543 } 1184 }
544 1185
1186 // Updates the editing state for the cell.
545 - (void)updateEditingStateOfCell:(BookmarkCell*)cell 1187 - (void)updateEditingStateOfCell:(BookmarkCell*)cell
546 atIndexPath:(NSIndexPath*)indexPath 1188 atIndexPath:(NSIndexPath*)indexPath
547 animateMenuVisibility:(BOOL)animateMenuVisibility 1189 animateMenuVisibility:(BOOL)animateMenuVisibility
548 animateSelectedState:(BOOL)animateSelectedState { 1190 animateSelectedState:(BOOL)animateSelectedState {
549 BOOL selected = [self cellIsSelectedForEditingAtIndexPath:indexPath]; 1191 BOOL selected = [self cellIsSelectedForEditingAtIndexPath:indexPath];
550 [cell setSelectedForEditing:selected animated:animateSelectedState]; 1192 [cell setSelectedForEditing:selected animated:animateSelectedState];
551 BookmarkItemCell* itemCell = static_cast<BookmarkItemCell*>(cell); 1193 BookmarkItemCell* itemCell = static_cast<BookmarkItemCell*>(cell);
552 [itemCell showButtonOfType:[self buttonTypeForCellAtIndexPath:indexPath] 1194 [itemCell showButtonOfType:[self buttonTypeForCellAtIndexPath:indexPath]
553 animated:animateMenuVisibility]; 1195 animated:animateMenuVisibility];
554 } 1196 }
555 1197
1198 // |animateMenuVisibility| refers to whether the change in the visibility of the
1199 // menu button is animated.
1200 // |animateSelectedState| refers to whether the change in the selected state (in
1201 // editing mode) of the cell is animated.
1202 // This method updates the visibility of the menu button.
1203 // This method updates the selected state of the cell (in editing mode).
556 - (void)updateEditingStateOfCellAtIndexPath:(NSIndexPath*)indexPath 1204 - (void)updateEditingStateOfCellAtIndexPath:(NSIndexPath*)indexPath
557 animateMenuVisibility:(BOOL)animateMenuVisibility 1205 animateMenuVisibility:(BOOL)animateMenuVisibility
558 animateSelectedState:(BOOL)animateSelectedState { 1206 animateSelectedState:(BOOL)animateSelectedState {
559 BookmarkCell* cell = base::mac::ObjCCast<BookmarkCell>( 1207 BookmarkCell* cell = base::mac::ObjCCast<BookmarkCell>(
560 [self.collectionView cellForItemAtIndexPath:indexPath]); 1208 [self.collectionView cellForItemAtIndexPath:indexPath]);
561 if (!cell) 1209 if (!cell)
562 return; 1210 return;
563 1211
564 [self updateEditingStateOfCell:cell 1212 [self updateEditingStateOfCell:cell
565 atIndexPath:indexPath 1213 atIndexPath:indexPath
566 animateMenuVisibility:animateMenuVisibility 1214 animateMenuVisibility:animateMenuVisibility
567 animateSelectedState:animateSelectedState]; 1215 animateSelectedState:animateSelectedState];
568 } 1216 }
569 1217
570 #pragma mark - BookmarkModelObserver Callbacks 1218 // The minimal horizontal space between items to respect between cells in
571 1219 // |section|.
572 - (void)bookmarkModelLoaded {
573 NOTREACHED();
574 }
575
576 - (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
577 NOTREACHED();
578 }
579
580 - (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
581 NOTREACHED();
582 }
583
584 - (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
585 movedFromParent:(const BookmarkNode*)oldParent
586 toParent:(const BookmarkNode*)newParent {
587 NOTREACHED();
588 }
589
590 - (void)bookmarkNodeDeleted:(const BookmarkNode*)node
591 fromFolder:(const BookmarkNode*)folder {
592 NOTREACHED();
593 }
594
595 - (void)bookmarkModelRemovedAllNodes {
596 NOTREACHED();
597 }
598
599 #pragma mark - Public Methods That Must Be Overridden
600
601 - (BOOL)shouldSelectCellAtIndexPath:(NSIndexPath*)indexPath {
602 NOTREACHED();
603 return NO;
604 }
605
606 - (void)didTapCellAtIndexPath:(NSIndexPath*)indexPath {
607 NOTREACHED();
608 }
609
610 - (void)didAddCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
611 NOTREACHED();
612 }
613
614 - (void)didRemoveCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
615 NOTREACHED();
616 }
617
618 - (void)didTapMenuButtonAtIndexPath:(NSIndexPath*)indexPath
619 onView:(UIView*)view
620 forCell:(BookmarkItemCell*)cell {
621 NOTREACHED();
622 }
623
624 - (bookmark_cell::ButtonType)buttonTypeForCellAtIndexPath:
625 (NSIndexPath*)indexPath {
626 NOTREACHED();
627 return bookmark_cell::ButtonNone;
628 }
629
630 - (BOOL)allowLongPressForCellAtIndexPath:(NSIndexPath*)indexPath {
631 NOTREACHED();
632 return NO;
633 }
634
635 - (void)didLongPressCell:(UICollectionViewCell*)cell
636 atIndexPath:(NSIndexPath*)indexPath {
637 NOTREACHED();
638 }
639
640 - (BOOL)cellIsSelectedForEditingAtIndexPath:(NSIndexPath*)indexPath {
641 NOTREACHED();
642 return NO;
643 }
644
645 - (CGSize)headerSizeForSection:(NSInteger)section {
646 NOTREACHED();
647 return CGSizeZero;
648 }
649
650 - (UICollectionViewCell*)cellAtIndexPath:(NSIndexPath*)indexPath {
651 NOTREACHED();
652 return nil;
653 }
654
655 - (UICollectionReusableView*)headerAtIndexPath:(NSIndexPath*)indexPath {
656 NOTREACHED();
657 return nil;
658 }
659
660 - (NSInteger)numberOfItemsInSection:(NSInteger)section {
661 NOTREACHED();
662 return 0;
663 }
664 - (NSInteger)numberOfSections {
665 NOTREACHED();
666 return 0;
667 }
668
669 - (void)updateCollectionView {
670 NOTREACHED();
671 }
672
673 - (const bookmarks::BookmarkNode*)nodeAtIndexPath:(NSIndexPath*)indexPath {
674 NOTREACHED();
675 return nullptr;
676 }
677
678 #pragma mark - Methods that subclasses can override (UI)
679
680 - (UIEdgeInsets)insetForSectionAtIndex:(NSInteger)section {
681 if ([self isPromoSection:section])
682 return UIEdgeInsetsZero;
683
684 if (IsIPadIdiom()) {
685 return UIEdgeInsetsMake(10, rowMarginTablet, 0, rowMarginTablet);
686 } else {
687 return UIEdgeInsetsZero;
688 }
689 }
690
691 - (CGSize)cellSizeForIndexPath:(NSIndexPath*)indexPath {
692 DCHECK(![self isPromoSection:indexPath.section]);
693 UIEdgeInsets insets = [self insetForSectionAtIndex:indexPath.section];
694 return CGSizeMake(self.bounds.size.width - (insets.right + insets.left),
695 rowHeight);
696 }
697
698 - (CGFloat)minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { 1220 - (CGFloat)minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
699 return 0; 1221 return 0;
700 } 1222 }
701 1223
1224 // The minimal vertical space between items to respect between cells in
1225 // |section|.
702 - (CGFloat)minimumLineSpacingForSectionAtIndex:(NSInteger)section { 1226 - (CGFloat)minimumLineSpacingForSectionAtIndex:(NSInteger)section {
703 return 0; 1227 return 0;
704 } 1228 }
705 1229
1230 // The text to display when there are no items in the collection. Default is
1231 // |IDS_IOS_BOOKMARK_NO_BOOKMARKS_LABEL|.
706 - (NSString*)textWhenCollectionIsEmpty { 1232 - (NSString*)textWhenCollectionIsEmpty {
707 return l10n_util::GetNSString(IDS_IOS_BOOKMARK_NO_BOOKMARKS_LABEL); 1233 return l10n_util::GetNSString(IDS_IOS_BOOKMARK_NO_BOOKMARKS_LABEL);
708 } 1234 }
709 1235
710 #pragma mark - Public Methods That Can Be Overridden 1236 // In landscape mode, there are 2 widths: 480pt and 568pt. Returns YES if the
711 1237 // width is 568pt.
712 - (void)collectionViewScrolled {
713 }
714
715 #pragma mark - Editing
716
717 - (void)setEditing:(BOOL)editing animated:(BOOL)animated {
718 if (self.editing == editing)
719 return;
720
721 _editing = editing;
722 [UIView animateWithDuration:animated ? 0.2 : 0.0
723 delay:0
724 options:UIViewAnimationOptionBeginFromCurrentState
725 animations:^{
726 self.shadow.alpha = editing ? 0.0 : 1.0;
727 }
728 completion:nil];
729
730 // If the promo is active this means that it is removed and added as the edit
731 // mode changes, making reloading the data mandatory.
732 if ([self isPromoActive]) {
733 [self.collectionView reloadData];
734 [self.collectionView.collectionViewLayout invalidateLayout];
735 } else {
736 // Update the visual state of the bookmark cells without reloading the
737 // section.
738 // This prevents flickering of images that need to be asynchronously
739 // reloaded.
740 NSArray* indexPaths = [self.collectionView indexPathsForVisibleItems];
741 for (NSIndexPath* indexPath in indexPaths) {
742 [self updateEditingStateOfCellAtIndexPath:indexPath
743 animateMenuVisibility:animated
744 animateSelectedState:NO];
745 }
746 }
747 }
748
749 #pragma mark - Public Methods
750
751 - (void)changeOrientation:(UIInterfaceOrientation)orientation {
752 [self updateCollectionView];
753 }
754
755 - (CGFloat)contentPositionInPortraitOrientation {
756 if (IsPortrait())
757 return self.collectionView.contentOffset.y;
758
759 // In short landscape mode and portrait mode, there are 2 cells per row.
760 if ([self wideLandscapeMode])
761 return self.collectionView.contentOffset.y;
762
763 // In wide landscape mode, there are 3 cells per row.
764 return self.collectionView.contentOffset.y * 3 / 2.0;
765 }
766
767 - (void)applyContentPosition:(CGFloat)position {
768 if (IsLandscape() && [self wideLandscapeMode]) {
769 position = position * 2 / 3.0;
770 }
771
772 CGFloat y =
773 MIN(position,
774 [self.collectionView.collectionViewLayout collectionViewContentSize]
775 .height);
776 self.collectionView.contentOffset =
777 CGPointMake(self.collectionView.contentOffset.x, y);
778 }
779
780 - (void)setScrollsToTop:(BOOL)scrollsToTop {
781 self.collectionView.scrollsToTop = scrollsToTop;
782 }
783
784 #pragma mark - Private Methods
785
786 - (BOOL)wideLandscapeMode { 1238 - (BOOL)wideLandscapeMode {
787 return self.frame.size.width > 567; 1239 return self.frame.size.width > 567;
788 } 1240 }
789 1241
790 #pragma mark - UIGestureRecognizer Callbacks
791
792 - (void)longPress:(UILongPressGestureRecognizer*)recognizer {
793 if (self.longPressRecognizer.numberOfTouches != 1 || self.editing)
794 return;
795
796 if (self.longPressRecognizer.state == UIGestureRecognizerStateRecognized ||
797 self.longPressRecognizer.state == UIGestureRecognizerStateBegan) {
798 CGPoint point =
799 [self.longPressRecognizer locationOfTouch:0 inView:self.collectionView];
800 NSIndexPath* indexPath =
801 [self.collectionView indexPathForItemAtPoint:point];
802 if (!indexPath)
803 return;
804
805 UICollectionViewCell* cell =
806 [self.collectionView cellForItemAtIndexPath:indexPath];
807
808 // Notify the subclass that long press has been received.
809 if (cell)
810 [self didLongPressCell:cell atIndexPath:indexPath];
811 }
812 }
813
814 #pragma mark - UIGestureRecognizerDelegate
815
816 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
817 DCHECK(gestureRecognizer == self.longPressRecognizer);
818 CGPoint point =
819 [gestureRecognizer locationOfTouch:0 inView:self.collectionView];
820 NSIndexPath* indexPath = [self.collectionView indexPathForItemAtPoint:point];
821 if (!indexPath)
822 return NO;
823 return [self allowLongPressForCellAtIndexPath:indexPath];
824 }
825
826 #pragma mark - Promo Cell 1242 #pragma mark - Promo Cell
827 1243
828 - (BOOL)isPromoSection:(NSInteger)section { 1244 - (BOOL)isPromoSection:(NSInteger)section {
829 return section == 0 && [self shouldShowPromoCell]; 1245 return section == 0 && [self shouldShowPromoCell];
830 } 1246 }
831 1247
832 - (BOOL)shouldShowPromoCell { 1248 - (BOOL)shouldShowPromoCell {
833 return NO; 1249 return _promoVisible;
834 } 1250 }
835 1251
836 - (BOOL)isPromoActive { 1252 - (BOOL)isPromoActive {
837 return NO; 1253 return NO;
838 } 1254 }
839 1255
840 @end 1256 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698