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

Side by Side Diff: ios/chrome/browser/ui/bookmarks/bookmark_all_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_all_collection_view.h"
6
7 #include "base/logging.h"
8 #include "base/mac/objc_property_releaser.h"
9 #include "base/mac/scoped_nsobject.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "components/bookmarks/browser/bookmark_model.h"
12 #include "ios/chrome/browser/bookmarks/bookmarks_utils.h"
13 #import "ios/chrome/browser/ui/bookmarks/bookmark_collection_cells.h"
14 #include "ios/chrome/browser/ui/bookmarks/bookmark_promo_cell.h"
15 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/base/models/tree_node_iterator.h"
18
19 using bookmarks::BookmarkNode;
20
21 namespace {
22 typedef std::vector<const BookmarkNode*> NodeVector;
23 // Each section of the collection view corresponds to a NodesSection object,
24 // sorted by creation date.
25 using bookmark_utils_ios::NodesSection;
26
27 // There is sometimes a flurry of events from the bookmark model. When this
28 // happens the data the collection view is displaying needs to be recalculated
29 // and the collection view needs to be reloaded. The recalculation of the data
30 // is expensive, so instead of doing the update over and over again the update
31 // is done once and all other updates received during the same runloop event are
32 // deferred to the next turn of the runloop (if everything is deferred this
33 // unfortunately triggers a bug in the UICollectionView sometimes).
34 // This enum tracks the state of the refresh. See -collectionViewNeedsUpdate
35 // for the state machine.
36 typedef enum { kNoUpdate = 0, kOneUpdateDone, kUpdateScheduled } UpdateState;
37 } // namespace
38
39 @interface BookmarkAllCollectionView ()<BookmarkPromoCellDelegate> {
40 // A vector of vectors. Url nodes are segregated by month of creation.
41 ScopedVector<NodesSection> _nodesSectionVector;
42 // To avoid refreshing the internal model too often.
43 UpdateState _updateScheduled;
44 base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkAllCollectionView;
45 }
46
47 // Keep a reference to the promo cell to deregister as delegate.
48 @property(nonatomic, retain) BookmarkPromoCell* promoCell;
49
50 // Triggers an update of the collection, but delayed in order to coallesce a lot
51 // of events into one update.
52 - (void)collectionViewNeedsUpdate;
53
54 @end
55
56 @implementation BookmarkAllCollectionView
57
58 @synthesize delegate = _delegate;
59 @synthesize promoCell = _promoCell;
60
61 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
62 frame:(CGRect)frame {
63 self = [super initWithBrowserState:browserState frame:frame];
64 if (self) {
65 _propertyReleaser_BookmarkAllCollectionView.Init(
66 self, [BookmarkAllCollectionView class]);
67 self.accessibilityIdentifier = @"bookmark_all_collection_view";
68 [self updateCollectionView];
69 }
70 return self;
71 }
72
73 - (void)dealloc {
74 _promoCell.delegate = nil;
75 [super dealloc];
76 }
77
78 - (void)updateCollectionView {
79 if (!self.bookmarkModel->loaded())
80 return;
81
82 // Regenerate the list of all bookmarks.
83 NodeVector allItems;
84 ui::TreeNodeIterator<const BookmarkNode> iterator(
85 self.bookmarkModel->root_node());
86
87 while (iterator.has_next()) {
88 const BookmarkNode* bookmark = iterator.Next();
89
90 if (bookmark->is_url())
91 allItems.push_back(bookmark);
92 }
93
94 // Perform segregation.
95 bookmark_utils_ios::segregateNodes(allItems, _nodesSectionVector);
96
97 [self cancelAllFaviconLoads];
98 [self.collectionView reloadData];
99 }
100
101 - (void)collectionViewNeedsUpdate {
102 switch (_updateScheduled) {
103 case kNoUpdate:
104 // If the collection view was not updated recently, update it now.
105 [self updateCollectionView];
106 _updateScheduled = kOneUpdateDone;
107 // And reset the state when going back to the main loop.
108 dispatch_async(dispatch_get_main_queue(), ^{
109 _updateScheduled = kNoUpdate;
110 });
111 break;
112 case kOneUpdateDone:
113 // An update was already done on this turn of the main loop, schedule the
114 // next update for later.
115 _updateScheduled = kUpdateScheduled;
116 dispatch_async(dispatch_get_main_queue(), ^{
117 if (_updateScheduled == kNoUpdate)
118 [self updateCollectionView];
119 });
120 break;
121 case kUpdateScheduled:
122 // Nothing to do.
123 break;
124 }
125 }
126
127 #pragma mark - BookmarkModelBridgeObserver Callbacks
128
129 - (void)bookmarkModelLoaded {
130 [self updateCollectionView];
131 }
132
133 - (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
134 if (bookmarkNode->is_folder())
135 return;
136
137 // TODO(crbug.com/603661): Ideally, we would only reload the relevant index
138 // path. However, calling reloadItemsAtIndexPaths:(0,0) immediately after
139 // reloadData results in a exception: NSInternalInconsistencyException
140 // 'request for index path for global index 2147483645 ...'
141 // One solution would be to keep track of whether we've just called
142 // reloadData, but that requires experimentation to determine how long we have
143 // to wait before we can safely call reloadItemsAtIndexPaths.
144 [self updateCollectionView];
145 }
146
147 - (void)bookmarkNodeFaviconChanged:
148 (const bookmarks::BookmarkNode*)bookmarkNode {
149 // Only urls have favicons.
150 DCHECK(bookmarkNode->is_url());
151
152 // Update image of corresponding cell.
153 NSIndexPath* indexPath = [self indexPathForNode:bookmarkNode];
154 if (!indexPath)
155 return;
156
157 // Check that this cell is visible.
158 NSArray* visiblePaths = [self.collectionView indexPathsForVisibleItems];
159 if (![visiblePaths containsObject:indexPath])
160 return;
161
162 [self loadFaviconAtIndexPath:indexPath];
163 }
164
165 - (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
166 [self collectionViewNeedsUpdate];
167 }
168
169 - (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
170 movedFromParent:(const BookmarkNode*)oldParent
171 toParent:(const BookmarkNode*)newParent {
172 [self updateCollectionView];
173 }
174
175 - (void)bookmarkNodeDeleted:(const BookmarkNode*)node
176 fromFolder:(const BookmarkNode*)folder {
177 // Only remove the node from the list of all nodes. Since we also receive a
178 // 'bookmarkNodeChildrenChanged' callback, the collection view will be updated
179 // there.
180 for (NodesSection* nodesSection : _nodesSectionVector) {
181 NodeVector nodeVector = nodesSection->vector;
182 // If the node was in _nodesSectionVector, it is now invalid. In that case,
183 // remove it from _nodesSectionVector.
184 auto it = std::find(nodeVector.begin(), nodeVector.end(), node);
185 if (it != nodeVector.end()) {
186 nodeVector.erase(it);
187 nodesSection->vector = nodeVector;
188 break;
189 }
190 }
191 }
192
193 - (void)bookmarkModelRemovedAllNodes {
194 [self updateCollectionView];
195 }
196
197 #pragma mark - Parent class overrides that affect functionality
198
199 - (void)collectionView:(UICollectionView*)collectionView
200 willDisplayCell:(UICollectionViewCell*)cell
201 forItemAtIndexPath:(NSIndexPath*)indexPath {
202 auto node = [self nodeAtIndexPath:indexPath];
203 if (node && node->type() == bookmarks::BookmarkNode::URL) {
204 [self loadFaviconAtIndexPath:indexPath];
205 }
206 }
207
208 - (void)didAddCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
209 DCHECK(![self isPromoSection:indexPath.section]);
210 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
211 UICollectionViewCell* cell =
212 [self.collectionView cellForItemAtIndexPath:indexPath];
213 [self.delegate bookmarkCollectionView:self cell:cell addNodeForEditing:node];
214 }
215
216 - (void)didRemoveCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
217 DCHECK(![self isPromoSection:indexPath.section]);
218 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
219 UICollectionViewCell* cell =
220 [self.collectionView cellForItemAtIndexPath:indexPath];
221 [self.delegate bookmarkCollectionView:self
222 cell:cell
223 removeNodeForEditing:node];
224 }
225
226 - (BOOL)shouldSelectCellAtIndexPath:(NSIndexPath*)indexPath {
227 return ![self isPromoSection:indexPath.section];
228 }
229
230 - (void)didTapCellAtIndexPath:(NSIndexPath*)indexPath {
231 DCHECK(![self isPromoSection:indexPath.section]);
232 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
233 DCHECK(node);
234 RecordBookmarkLaunch(BOOKMARK_LAUNCH_LOCATION_ALL_ITEMS);
235 [self.delegate bookmarkCollectionView:self
236 selectedUrlForNavigation:node->url()];
237 }
238
239 - (void)didTapMenuButtonAtIndexPath:(NSIndexPath*)indexPath
240 onView:(UIView*)view
241 forCell:(BookmarkItemCell*)cell {
242 DCHECK(![self isPromoSection:indexPath.section]);
243 [self.delegate bookmarkCollectionView:self
244 wantsMenuForBookmark:[self nodeAtIndexPath:indexPath]
245 onView:view
246 forCell:cell];
247 }
248
249 - (bookmark_cell::ButtonType)buttonTypeForCellAtIndexPath:
250 (NSIndexPath*)indexPath {
251 DCHECK(![self isPromoSection:indexPath.section]);
252 if (self.editing)
253 return bookmark_cell::ButtonNone;
254 return bookmark_cell::ButtonMenu;
255 }
256
257 - (BOOL)allowLongPressForCellAtIndexPath:(NSIndexPath*)indexPath {
258 return [self isPromoSection:indexPath.section] ? NO : !self.editing;
259 }
260
261 - (void)didLongPressCell:(UICollectionViewCell*)cell
262 atIndexPath:(NSIndexPath*)indexPath {
263 DCHECK(![self isPromoSection:indexPath.section]);
264 [self.delegate bookmarkCollectionView:self
265 didLongPressCell:cell
266 forBookmark:[self nodeAtIndexPath:indexPath]];
267 }
268
269 - (BOOL)cellIsSelectedForEditingAtIndexPath:(NSIndexPath*)indexPath {
270 DCHECK(![self isPromoSection:indexPath.section]);
271
272 const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
273 const std::set<const BookmarkNode*>& editingNodes =
274 [self.delegate nodesBeingEdited];
275 return editingNodes.find(node) != editingNodes.end();
276 }
277
278 - (const BookmarkNode*)nodeAtIndexPath:(NSIndexPath*)indexPath {
279 NSInteger section = indexPath.section;
280 if ([self isPromoSection:section])
281 return nullptr;
282
283 if ([self shouldShowPromoCell])
284 --section;
285 return _nodesSectionVector[section]->vector[indexPath.row];
286 }
287
288 - (NSIndexPath*)indexPathForNode:(const BookmarkNode*)bookmarkNode {
289 NSInteger section = 0;
290
291 // When showing promo cell, bookmarks start with section 1.
292 if ([self shouldShowPromoCell])
293 section = 1;
294
295 for (NodesSection* nodesSection : _nodesSectionVector) {
296 NodeVector nodeVector = nodesSection->vector;
297 NSInteger item = 0;
298 for (const BookmarkNode* node : nodeVector) {
299 if (bookmarkNode == node) {
300 return [NSIndexPath indexPathForItem:item inSection:section];
301 }
302 ++item;
303 }
304 ++section;
305 }
306
307 return nil;
308 }
309
310 #pragma mark - Parent class overrides that change UI
311
312 // Parent class override.
313 - (UIEdgeInsets)insetForSectionAtIndex:(NSInteger)section {
314 // Only return insets for non-empty sections.
315 NSInteger count = [self numberOfItemsInSection:section];
316 if (count == 0)
317 return UIEdgeInsetsZero;
318
319 // The last section needs special treatment.
320 UIEdgeInsets insets = [super insetForSectionAtIndex:section];
321 NSInteger sectionCount = [self.collectionView.dataSource
322 numberOfSectionsInCollectionView:self.collectionView];
323
324 if (section == sectionCount - 1) {
325 insets.top = 0;
326 return insets;
327 }
328
329 insets.top = 0;
330 insets.bottom = 0;
331 return insets;
332 }
333
334 // Parent class override.
335 - (UICollectionViewCell*)cellAtIndexPath:(NSIndexPath*)indexPath {
336 if ([self isPromoSection:indexPath.section]) {
337 self.promoCell = [self.collectionView
338 dequeueReusableCellWithReuseIdentifier:[BookmarkPromoCell
339 reuseIdentifier]
340 forIndexPath:indexPath];
341 self.promoCell.delegate = self;
342 return self.promoCell;
343 }
344
345 return [self cellForBookmark:[self nodeAtIndexPath:indexPath]
346 indexPath:indexPath];
347 }
348
349 // Parent class override.
350 - (CGSize)headerSizeForSection:(NSInteger)section {
351 if ([self isPromoSection:section])
352 return CGSizeZero;
353
354 return CGSizeMake(self.bounds.size.width, [BookmarkHeaderView handsetHeight]);
355 }
356
357 - (UICollectionReusableView*)headerAtIndexPath:(NSIndexPath*)indexPath {
358 NSInteger section = indexPath.section;
359 if ([self isPromoSection:section])
360 return nil;
361
362 if ([self shouldShowPromoCell])
363 --section;
364
365 BookmarkHeaderView* view = [self.collectionView
366 dequeueReusableSupplementaryViewOfKind:
367 UICollectionElementKindSectionHeader
368 withReuseIdentifier:[BookmarkHeaderView
369 reuseIdentifier]
370 forIndexPath:indexPath];
371
372 NSString* title =
373 base::SysUTF8ToNSString(_nodesSectionVector[section]->timeRepresentation);
374 [view setTitle:title];
375 return view;
376 }
377
378 - (NSInteger)numberOfItemsInSection:(NSInteger)section {
379 if ([self isPromoSection:section])
380 return 1;
381
382 if ([self shouldShowPromoCell])
383 --section;
384 return _nodesSectionVector[section]->vector.size();
385 }
386
387 - (NSInteger)numberOfSections {
388 const BOOL showPromo = [self shouldShowPromoCell];
389 const NSInteger nodeSectionsCount = _nodesSectionVector.size();
390 return showPromo ? nodeSectionsCount + 1 : nodeSectionsCount;
391 }
392
393 #pragma mark - BookmarkPromoCellDelegate
394
395 - (void)bookmarkPromoCellDidTapSignIn:(BookmarkPromoCell*)bookmarkPromoCell {
396 [self.delegate bookmarkCollectionViewShowSignIn:self];
397 }
398
399 - (void)bookmarkPromoCellDidTapDismiss:(BookmarkPromoCell*)bookmarkPromoCell {
400 [self.delegate bookmarkCollectionViewDismissPromo:self];
401 }
402
403 #pragma mark - Promo Cell
404
405 - (BOOL)isPromoActive {
406 return [self.delegate bookmarkCollectionViewShouldShowPromoCell:self];
407 }
408
409 - (BOOL)shouldShowPromoCell {
410 // The promo cell is not shown in edit mode.
411 return !self.editing && [self isPromoActive];
412 }
413
414 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698