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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/bookmarks/bookmark_collection_view.mm
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_collection_view.mm b/ios/chrome/browser/ui/bookmarks/bookmark_collection_view.mm
index 898a2bbeccdb6cfd8a5c5366818832d18de44059..28cc755dbb44cc7bc27c3d588966e6491437fb92 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_collection_view.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_collection_view.mm
@@ -9,6 +9,7 @@
#include <map>
#include <memory>
+#include "base/logging.h"
#include "base/mac/bind_objc_block.h"
#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h"
@@ -20,7 +21,12 @@
#include "components/favicon_base/favicon_types.h"
#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
#include "ios/chrome/browser/bookmarks/bookmarks_utils.h"
+#include "ios/chrome/browser/experimental_flags.h"
#include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
+#import "ios/chrome/browser/ui/authentication/signin_promo_view.h"
+#import "ios/chrome/browser/ui/authentication/signin_promo_view_configurator.h"
+#import "ios/chrome/browser/ui/authentication/signin_promo_view_consumer.h"
+#import "ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_collection_cells.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_collection_view_background.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_promo_cell.h"
@@ -39,6 +45,22 @@
using bookmarks::BookmarkNode;
namespace {
+// Computes the cell size based on width.
+CGSize PreferredCellSizeForWidth(UICollectionViewCell* cell, CGFloat width) {
+ CGRect cellFrame = cell.frame;
+ cellFrame.size.width = width;
+ cellFrame.size.height = CGFLOAT_MAX;
+ cell.frame = cellFrame;
+ [cell setNeedsLayout];
+ [cell layoutIfNeeded];
+ CGSize result =
+ [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize
+ withHorizontalFittingPriority:UILayoutPriorityRequired
+ verticalFittingPriority:UILayoutPriorityDefaultLow];
+ cellFrame.size = result;
+ cell.frame = cellFrame;
+ return result;
+}
// Used to store a pair of NSIntegers when storing a NSIndexPath in C++
// collections.
@@ -55,12 +77,24 @@ CGFloat minFaviconSizePt = 16;
// This delay should not be too small to let enough time to load bookmarks
// from network.
const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
+}
-} // namespace
-
-@interface BookmarkCollectionView ()<UICollectionViewDataSource,
+@interface BookmarkCollectionView ()<BookmarkPromoCellDelegate,
+ SigninPromoViewConsumer,
+ UICollectionViewDataSource,
UICollectionViewDelegateFlowLayout,
UIGestureRecognizerDelegate> {
+ // A vector of folders to display in the collection view.
+ std::vector<const BookmarkNode*> _subFolders;
+ // A vector of bookmark urls to display in the collection view.
+ std::vector<const BookmarkNode*> _subItems;
+
+ // True if the promo is visible.
+ BOOL _promoVisible;
+
+ // Mediator, helper for the sign-in promo view.
+ SigninPromoViewMediator* _signinPromoViewMediator;
+
std::unique_ptr<bookmarks::BookmarkModelBridge> _modelBridge;
ios::ChromeBrowserState* _browserState;
@@ -86,32 +120,19 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
// Shadow to display over the content.
@property(nonatomic, strong) UIView* shadow;
-// Updates the editing state for the cell.
-- (void)updateEditingStateOfCell:(BookmarkCell*)cell
- atIndexPath:(NSIndexPath*)indexPath
- animateMenuVisibility:(BOOL)animateMenuVisibility
- animateSelectedState:(BOOL)animateSelectedState;
-
-// Callback received when the user taps the menu button on the cell.
-- (void)didTapMenuButton:(BookmarkItemCell*)cell view:(UIView*)view;
+@property(nonatomic, assign) const bookmarks::BookmarkNode* folder;
-// In landscape mode, there are 2 widths: 480pt and 568pt. Returns YES if the
-// width is 568pt.
-- (BOOL)wideLandscapeMode;
+// Section indices.
+@property(nonatomic, readonly, assign) NSInteger promoSection;
+@property(nonatomic, readonly, assign) NSInteger folderSection;
+@property(nonatomic, readonly, assign) NSInteger itemsSection;
+@property(nonatomic, readonly, assign) NSInteger sectionCount;
-// Schedules showing or hiding the empty bookmarks background view if the
-// collection view is empty by calling showEmptyBackgroundIfNeeded after
-// kShowEmptyBookmarksBackgroundRefreshDelay.
-// Multiple call to this method will cancel previous scheduled call to
-// showEmptyBackgroundIfNeeded before scheduling a new one.
-- (void)scheduleEmptyBackgroundVisibilityUpdate;
-// Shows/hides empty bookmarks background view if the collections view is empty.
-- (void)updateEmptyBackgroundVisibility;
-// Shows/hides empty bookmarks background view with an animation.
-- (void)setEmptyBackgroundVisible:(BOOL)visible;
@end
@implementation BookmarkCollectionView
+@synthesize delegate = _delegate;
+@synthesize folder = _folder;
@synthesize bookmarkModel = _bookmarkModel;
@synthesize collectionView = _collectionView;
@synthesize editing = _editing;
@@ -123,52 +144,6 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
#pragma mark - Initialization
-- (id)init {
- NOTREACHED();
- return nil;
-}
-
-- (id)initWithFrame:(CGRect)frame {
- NOTREACHED();
- return nil;
-}
-
-- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
- frame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- _browserState = browserState;
-
- // Set up connection to the BookmarkModel.
- _bookmarkModel =
- ios::BookmarkModelFactory::GetForBrowserState(browserState);
-
- // Set up observers.
- _modelBridge.reset(
- new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
-
- [self setupViews];
- }
- return self;
-}
-
-- (void)dealloc {
- _collectionView.dataSource = nil;
- _collectionView.delegate = nil;
- UIView* moi = _collectionView;
- dispatch_async(dispatch_get_main_queue(), ^{
- // A collection view with a layout that uses a dynamic animator (aka
- // something that changes the layout over time) will crash if it is
- // deallocated while the animation is currently playing.
- // Apparently if a tick has been dispatched it will execute, invoking a
- // method on the deallocated collection.
- // The only purpose of this block is to retain the collection view for a
- // while, giving the layout a chance to perform its last tick.
- [moi self];
- });
- _faviconTaskTracker.TryCancelAll();
-}
-
- (void)setupViews {
self.backgroundColor = bookmark_utils_ios::mainBackgroundColor();
UICollectionViewFlowLayout* layout =
@@ -223,6 +198,171 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
[self.collectionView addGestureRecognizer:self.longPressRecognizer];
}
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
+ frame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+ if (self) {
+ _browserState = browserState;
+
+ // Set up connection to the BookmarkModel.
+ _bookmarkModel =
+ ios::BookmarkModelFactory::GetForBrowserState(browserState);
+
+ // Set up observers.
+ _modelBridge.reset(
+ new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
+
+ [self setupViews];
+ [self updateCollectionView];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ _collectionView.dataSource = nil;
+ _collectionView.delegate = nil;
+ UIView* moi = _collectionView;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ // A collection view with a layout that uses a dynamic animator (aka
+ // something that changes the layout over time) will crash if it is
+ // deallocated while the animation is currently playing.
+ // Apparently if a tick has been dispatched it will execute, invoking a
+ // method on the deallocated collection.
+ // The only purpose of this block is to retain the collection view for a
+ // while, giving the layout a chance to perform its last tick.
+ [moi self];
+ });
+ _faviconTaskTracker.TryCancelAll();
+}
+
+#pragma mark - BookmarkHomePrimaryView
+
+- (void)changeOrientation:(UIInterfaceOrientation)orientation {
+ [self updateCollectionView];
+}
+
+- (CGFloat)contentPositionInPortraitOrientation {
+ if (IsPortrait())
+ return self.collectionView.contentOffset.y;
+
+ // In short landscape mode and portrait mode, there are 2 cells per row.
+ if ([self wideLandscapeMode])
+ return self.collectionView.contentOffset.y;
+
+ // In wide landscape mode, there are 3 cells per row.
+ return self.collectionView.contentOffset.y * 3 / 2.0;
+}
+
+- (void)applyContentPosition:(CGFloat)position {
+ if (IsLandscape() && [self wideLandscapeMode]) {
+ position = position * 2 / 3.0;
+ }
+
+ CGFloat y =
+ MIN(position,
+ [self.collectionView.collectionViewLayout collectionViewContentSize]
+ .height);
+ self.collectionView.contentOffset =
+ CGPointMake(self.collectionView.contentOffset.x, y);
+}
+
+- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
+ if (self.editing == editing)
+ return;
+
+ _editing = editing;
+ [UIView animateWithDuration:animated ? 0.2 : 0.0
+ delay:0
+ options:UIViewAnimationOptionBeginFromCurrentState
+ animations:^{
+ self.shadow.alpha = editing ? 0.0 : 1.0;
+ }
+ completion:nil];
+
+ // If the promo is active this means that it is removed and added as the edit
+ // mode changes, making reloading the data mandatory.
+ if ([self isPromoActive]) {
+ [self.collectionView reloadData];
+ [self.collectionView.collectionViewLayout invalidateLayout];
+ } else {
+ // Update the visual state of the bookmark cells without reloading the
+ // section.
+ // This prevents flickering of images that need to be asynchronously
+ // reloaded.
+ NSArray* indexPaths = [self.collectionView indexPathsForVisibleItems];
+ for (NSIndexPath* indexPath in indexPaths) {
+ [self updateEditingStateOfCellAtIndexPath:indexPath
+ animateMenuVisibility:animated
+ animateSelectedState:NO];
+ }
+ }
+ [self promoStateChangedAnimated:animated];
+}
+
+- (void)setScrollsToTop:(BOOL)scrollsToTop {
+ self.collectionView.scrollsToTop = scrollsToTop;
+}
+
+#pragma mark - Public
+
+- (void)resetFolder:(const BookmarkNode*)folder {
+ DCHECK(folder->is_folder());
+ self.folder = folder;
+ [self updateCollectionView];
+}
+
+- (void)setDelegate:(id<BookmarkCollectionViewDelegate>)delegate {
+ _delegate = delegate;
+ [self promoStateChangedAnimated:NO];
+}
+
+- (void)collectionViewScrolled {
+ [self.delegate bookmarkCollectionViewDidScroll:self];
+}
+
+- (void)promoStateChangedAnimated:(BOOL)animate {
+ BOOL newPromoState =
+ !self.editing && self.folder &&
+ self.folder->type() == BookmarkNode::MOBILE &&
+ [self.delegate bookmarkCollectionViewShouldShowPromoCell:self];
+ if (newPromoState != _promoVisible) {
+ // This is awful, but until the old code to do the refresh when switching
+ // in and out of edit mode is fixed, this is probably the cleanest thing to
+ // do.
+ _promoVisible = newPromoState;
+ if (experimental_flags::IsSigninPromoEnabled()) {
+ if (!_promoVisible) {
+ _signinPromoViewMediator.consumer = nil;
+ _signinPromoViewMediator = nil;
+ } else {
+ _signinPromoViewMediator = [[SigninPromoViewMediator alloc] init];
+ _signinPromoViewMediator.consumer = self;
+ _signinPromoViewMediator.accessPoint =
+ signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER;
+ }
+ }
+ [self.collectionView reloadData];
+ }
+}
+
+#pragma mark - Sections
+
+- (NSInteger)promoSection {
+ return [self shouldShowPromoCell] ? 0 : -1;
+}
+
+- (NSInteger)folderSection {
+ return [self shouldShowPromoCell] ? 1 : 0;
+}
+
+- (NSInteger)itemsSection {
+ return [self shouldShowPromoCell] ? 2 : 1;
+}
+
+- (NSInteger)sectionCount {
+ return [self shouldShowPromoCell] ? 3 : 2;
+}
+
- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
[self updateShadow];
}
@@ -258,75 +398,541 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
CGRectMake(0, y, CGRectGetWidth(self.bounds), shadowHeight);
}
-#pragma mark - UIScrollViewDelegate
+#pragma mark - BookmarkItemCell callbacks
-- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
- [self updateShadowFrame];
- [self collectionViewScrolled];
+// Callback received when the user taps the menu button on the cell.
+- (void)didTapMenuButton:(BookmarkItemCell*)cell view:(UIView*)view {
+ [self didTapMenuButtonAtIndexPath:[self.collectionView indexPathForCell:cell]
+ onView:view
+ forCell:cell];
}
-#pragma mark - empty background
-
-- (void)scheduleEmptyBackgroundVisibilityUpdate {
- [NSObject
- cancelPreviousPerformRequestsWithTarget:self
- selector:
- @selector(
- updateEmptyBackgroundVisibility)
- object:nil];
- [self performSelector:@selector(updateEmptyBackgroundVisibility)
- withObject:nil
- afterDelay:kShowEmptyBookmarksBackgroundRefreshDelay];
+#pragma mark - BookmarkModelBridgeObserver Callbacks
+// BookmarkModelBridgeObserver Callbacks
+// Instances of this class automatically observe the bookmark model.
+// The bookmark model has loaded.
+- (void)bookmarkModelLoaded {
+ [self updateCollectionView];
}
-- (BOOL)isCollectionViewEmpty {
- BOOL collectionViewIsEmpty = YES;
- const NSInteger numberOfSections = [self numberOfSections];
- NSInteger section = [self shouldShowPromoCell] ? 1 : 0;
- for (; collectionViewIsEmpty && section < numberOfSections; ++section) {
- const NSInteger numberOfItemsInSection =
- [self numberOfItemsInSection:section];
- collectionViewIsEmpty = numberOfItemsInSection == 0;
+// The node has changed, but not its children.
+- (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
+ // The base folder changed. Do nothing.
+ if (bookmarkNode == self.folder)
+ return;
+
+ // A specific cell changed. Reload that cell.
+ NSIndexPath* indexPath = [self indexPathForNode:bookmarkNode];
+
+ if (indexPath) {
+ // TODO(crbug.com/603661): Ideally, we would only reload the relevant index
+ // path. However, calling reloadItemsAtIndexPaths:(0,0) immediately after
+ // reloadData results in a exception: NSInternalInconsistencyException
+ // 'request for index path for global index 2147483645 ...'
+ // One solution would be to keep track of whether we've just called
+ // reloadData, but that requires experimentation to determine how long we
+ // have to wait before we can safely call reloadItemsAtIndexPaths.
+ [self updateCollectionView];
}
- return collectionViewIsEmpty;
}
-- (void)updateEmptyBackgroundVisibility {
- const BOOL showEmptyBackground =
- [self isCollectionViewEmpty] && ![self shouldShowPromoCell];
- [self setEmptyBackgroundVisible:showEmptyBackground];
-}
+// The node has not changed, but its children have.
+- (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
+ // The base folder's children changed. Reload everything.
+ if (bookmarkNode == self.folder) {
+ [self updateCollectionView];
+ return;
+ }
-- (void)setEmptyBackgroundVisible:(BOOL)emptyBackgroundVisible {
- [UIView beginAnimations:@"alpha" context:NULL];
- self.emptyCollectionBackgroundView.alpha = emptyBackgroundVisible ? 1 : 0;
- [UIView commitAnimations];
+ // A subfolder's children changed. Reload that cell.
+ std::vector<const BookmarkNode*>::iterator it =
+ std::find(_subFolders.begin(), _subFolders.end(), bookmarkNode);
+ if (it != _subFolders.end()) {
+ // TODO(crbug.com/603661): Ideally, we would only reload the relevant index
+ // path. However, calling reloadItemsAtIndexPaths:(0,0) immediately after
+ // reloadData results in a exception: NSInternalInconsistencyException
+ // 'request for index path for global index 2147483645 ...'
+ // One solution would be to keep track of whether we've just called
+ // reloadData, but that requires experimentation to determine how long we
+ // have to wait before we can safely call reloadItemsAtIndexPaths.
+ [self updateCollectionView];
+ }
}
-#pragma mark - UICollectionViewDataSource
+// The node has moved to a new parent folder.
+- (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
+ movedFromParent:(const BookmarkNode*)oldParent
+ toParent:(const BookmarkNode*)newParent {
+ if (oldParent == self.folder || newParent == self.folder) {
+ // A folder was added or removed from the base folder.
+ [self updateCollectionView];
+ }
+}
-- (NSInteger)collectionView:(UICollectionView*)collectionView
- numberOfItemsInSection:(NSInteger)section {
- const NSInteger numberOfItemsInSection =
- [self numberOfItemsInSection:section];
- const BOOL isCollectionViewEmpty = [self isCollectionViewEmpty];
- self.collectionView.scrollEnabled = !isCollectionViewEmpty;
- if (isCollectionViewEmpty) {
- [self scheduleEmptyBackgroundVisibilityUpdate];
- } else {
- // Hide empty bookmarks now.
- [self setEmptyBackgroundVisible:NO];
+// |node| was deleted from |folder|.
+- (void)bookmarkNodeDeleted:(const BookmarkNode*)node
+ fromFolder:(const BookmarkNode*)folder {
+ if (self.folder == node) {
+ self.folder = nil;
+ [self updateCollectionView];
}
- return numberOfItemsInSection;
}
-- (NSInteger)numberOfSectionsInCollectionView:
- (UICollectionView*)collectionView {
- const NSInteger numberOfSections = [self numberOfSections];
- const BOOL collectionViewIsEmpty = 0 == numberOfSections;
- self.collectionView.scrollEnabled = !collectionViewIsEmpty;
- if (collectionViewIsEmpty) {
- [self scheduleEmptyBackgroundVisibilityUpdate];
+// All non-permanent nodes have been removed.
+- (void)bookmarkModelRemovedAllNodes {
+ self.folder = nil;
+ [self updateCollectionView];
+}
+
+- (void)bookmarkNodeFaviconChanged:
+ (const bookmarks::BookmarkNode*)bookmarkNode {
+ // Only urls have favicons.
+ DCHECK(bookmarkNode->is_url());
+
+ // Update image of corresponding cell.
+ NSIndexPath* indexPath = [self indexPathForNode:bookmarkNode];
+
+ if (!indexPath)
+ return;
+
+ // Check that this cell is visible.
+ NSArray* visiblePaths = [self.collectionView indexPathsForVisibleItems];
+ if (![visiblePaths containsObject:indexPath])
+ return;
+
+ [self loadFaviconAtIndexPath:indexPath];
+}
+
+#pragma mark - non-UI
+
+// Called when a user is attempting to select a cell.
+// Returning NO prevents the cell from being selected.
+- (BOOL)shouldSelectCellAtIndexPath:(NSIndexPath*)indexPath {
+ return YES;
+}
+
+// Called when a cell is tapped outside of editing mode.
+- (void)didTapCellAtIndexPath:(NSIndexPath*)indexPath {
+ if (indexPath.section == self.promoSection) {
+ // User tapped inside promo cell but not on one of the buttons. Ignore it.
+ return;
+ }
+
+ const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
+ DCHECK(node);
+
+ if (indexPath.section == self.folderSection) {
+ [self.delegate bookmarkCollectionView:self
+ selectedFolderForNavigation:node];
+ } else {
+ RecordBookmarkLaunch(BOOKMARK_LAUNCH_LOCATION_FOLDER);
+ [self.delegate bookmarkCollectionView:self
+ selectedUrlForNavigation:node->url()];
+ }
+}
+
+// Called when a user selected a cell in the editing state.
+- (void)didAddCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
+ const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
+ UICollectionViewCell* cell =
+ [self.collectionView cellForItemAtIndexPath:indexPath];
+ [self.delegate bookmarkCollectionView:self cell:cell addNodeForEditing:node];
+}
+
+- (void)didRemoveCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
+ const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
+ UICollectionViewCell* cell =
+ [self.collectionView cellForItemAtIndexPath:indexPath];
+ [self.delegate bookmarkCollectionView:self
+ cell:cell
+ removeNodeForEditing:node];
+}
+
+// Called when a user taps the menu button on a cell.
+- (void)didTapMenuButtonAtIndexPath:(NSIndexPath*)indexPath
+ onView:(UIView*)view
+ forCell:(BookmarkItemCell*)cell {
+ [self.delegate bookmarkCollectionView:self
+ wantsMenuForBookmark:[self nodeAtIndexPath:indexPath]
+ onView:view
+ forCell:cell];
+}
+
+// Whether a cell should show a button and of which type.
+- (bookmark_cell::ButtonType)buttonTypeForCellAtIndexPath:
+ (NSIndexPath*)indexPath {
+ return self.editing ? bookmark_cell::ButtonNone : bookmark_cell::ButtonMenu;
+}
+
+// Whether a long press at the cell at |indexPath| should be allowed.
+- (BOOL)allowLongPressForCellAtIndexPath:(NSIndexPath*)indexPath {
+ return !self.editing;
+}
+
+// The |cell| at |indexPath| received a long press.
+- (void)didLongPressCell:(UICollectionViewCell*)cell
+ atIndexPath:(NSIndexPath*)indexPath {
+ if (indexPath.section == self.promoSection) {
+ // User long-pressed inside promo cell. Ignore it.
+ return;
+ }
+
+ [self.delegate bookmarkCollectionView:self
+ didLongPressCell:cell
+ forBookmark:[self nodeAtIndexPath:indexPath]];
+}
+
+// Whether the cell has been selected in editing mode.
+- (BOOL)cellIsSelectedForEditingAtIndexPath:(NSIndexPath*)indexPath {
+ const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
+ const std::set<const BookmarkNode*>& editingNodes =
+ [self.delegate nodesBeingEdited];
+ return editingNodes.find(node) != editingNodes.end();
+}
+
+// Updates the collection view based on the current state of all models and
+// contextual parameters, such as the interface orientation.
+- (void)updateCollectionView {
+ if (!self.bookmarkModel->loaded())
+ return;
+
+ // Regenerate the list of all bookmarks.
+ _subFolders = std::vector<const BookmarkNode*>();
+ _subItems = std::vector<const BookmarkNode*>();
+
+ if (self.folder) {
+ int childCount = self.folder->child_count();
+ for (int i = 0; i < childCount; ++i) {
+ const BookmarkNode* node = self.folder->GetChild(i);
+ if (node->is_folder())
+ _subFolders.push_back(node);
+ else
+ _subItems.push_back(node);
+ }
+
+ bookmark_utils_ios::SortFolders(&_subFolders);
+ }
+
+ [self cancelAllFaviconLoads];
+ [self.collectionView reloadData];
+}
+
+// Returns the bookmark node associated with |indexPath|.
+- (const BookmarkNode*)nodeAtIndexPath:(NSIndexPath*)indexPath {
+ if (indexPath.section == self.folderSection)
+ return _subFolders[indexPath.row];
+ if (indexPath.section == self.itemsSection)
+ return _subItems[indexPath.row];
+
+ NOTREACHED();
+ return nullptr;
+}
+
+#pragma mark -
+
+- (NSIndexPath*)indexPathForNode:(const bookmarks::BookmarkNode*)bookmarkNode {
+ NSIndexPath* indexPath = nil;
+ if (bookmarkNode->is_folder()) {
+ std::vector<const BookmarkNode*>::iterator it =
+ std::find(_subFolders.begin(), _subFolders.end(), bookmarkNode);
+ if (it != _subFolders.end()) {
+ ptrdiff_t index = std::distance(_subFolders.begin(), it);
+ indexPath =
+ [NSIndexPath indexPathForRow:index inSection:self.folderSection];
+ }
+ } else if (bookmarkNode->is_url()) {
+ std::vector<const BookmarkNode*>::iterator it =
+ std::find(_subItems.begin(), _subItems.end(), bookmarkNode);
+ if (it != _subItems.end()) {
+ ptrdiff_t index = std::distance(_subItems.begin(), it);
+ indexPath =
+ [NSIndexPath indexPathForRow:index inSection:self.itemsSection];
+ }
+ }
+ return indexPath;
+}
+
+- (void)collectionView:(UICollectionView*)collectionView
+ willDisplayCell:(UICollectionViewCell*)cell
+ forItemAtIndexPath:(NSIndexPath*)indexPath {
+ if (indexPath.section == self.itemsSection) {
+ [self loadFaviconAtIndexPath:indexPath];
+ }
+}
+
+- (CGFloat)interitemSpacingForSectionAtIndex:(NSInteger)section {
+ CGFloat interitemSpacing = 0;
+ SEL minimumInteritemSpacingSelector = @selector
+ (collectionView:layout:minimumInteritemSpacingForSectionAtIndex:);
+ if ([self.collectionView.delegate
+ respondsToSelector:minimumInteritemSpacingSelector]) {
+ id collectionViewDelegate = static_cast<id>(self.collectionView.delegate);
+ interitemSpacing =
+ [collectionViewDelegate collectionView:self.collectionView
+ layout:self.collectionView
+ .collectionViewLayout
+ minimumInteritemSpacingForSectionAtIndex:section];
+ } else if ([self.collectionView.collectionViewLayout
+ isKindOfClass:[UICollectionViewFlowLayout class]]) {
+ UICollectionViewFlowLayout* flowLayout =
+ static_cast<UICollectionViewFlowLayout*>(
+ self.collectionView.collectionViewLayout);
+ interitemSpacing = flowLayout.minimumInteritemSpacing;
+ }
+ return interitemSpacing;
+}
+
+// The inset of the section.
+- (UIEdgeInsets)insetForSectionAtIndex:(NSInteger)section {
+ UIEdgeInsets insets = UIEdgeInsetsZero;
+ if ([self isPromoSection:section])
+ insets = UIEdgeInsetsZero;
+
+ if (IsIPadIdiom()) {
+ insets = UIEdgeInsetsMake(10, rowMarginTablet, 0, rowMarginTablet);
+ } else {
+ insets = UIEdgeInsetsZero;
+ }
+
+ if (section == self.folderSection)
+ insets.bottom = [self interitemSpacingForSectionAtIndex:section] / 2.;
+ else if (section == self.itemsSection)
+ insets.top = [self interitemSpacingForSectionAtIndex:section] / 2.;
+ else if (section == self.promoSection)
+ (void)0; // No insets to update.
+ else
+ NOTREACHED();
+ return insets;
+}
+
+// The size of the cell at |indexPath|.
+- (CGSize)cellSizeForIndexPath:(NSIndexPath*)indexPath {
+ if ([self isPromoSection:indexPath.section]) {
+ UICollectionViewCell* cell =
+ [self.collectionView cellForItemAtIndexPath:indexPath];
+ if (!cell) {
+ // -[UICollectionView
+ // dequeueReusableCellWithReuseIdentifier:forIndexPath:] cannot be used
+ // here since this method is called by -[id<UICollectionViewDelegate>
+ // collectionView:layout:sizeForItemAtIndexPath:]. This would generate
+ // crash: SIGFPE, EXC_I386_DIV.
+ if (experimental_flags::IsSigninPromoEnabled()) {
+ DCHECK(_signinPromoViewMediator);
+ BookmarkSigninPromoCell* signinPromoCell =
+ [[BookmarkSigninPromoCell alloc]
+ initWithFrame:CGRectMake(0, 0, 1000, 1000)];
+ [[_signinPromoViewMediator createConfigurator]
+ configureSigninPromoView:signinPromoCell.signinPromoView];
+ cell = signinPromoCell;
+ } else {
+ cell = [[BookmarkPromoCell alloc] init];
+ }
+ }
+ return PreferredCellSizeForWidth(cell, CGRectGetWidth(self.bounds));
+ }
+ DCHECK(![self isPromoSection:indexPath.section]);
+ UIEdgeInsets insets = [self insetForSectionAtIndex:indexPath.section];
+ return CGSizeMake(self.bounds.size.width - (insets.right + insets.left),
+ rowHeight);
+}
+
+- (BOOL)needsSectionHeaderForSection:(NSInteger)section {
+ // Only show header when there is at least one element in the previous
+ // section.
+ if (section == 0)
+ return NO;
+
+ if ([self numberOfItemsInSection:(section - 1)] == 0)
+ return NO;
+
+ return YES;
+}
+
+#pragma mark - UI
+
+// The size of the header for |section|. A return value of CGSizeZero prevents
+// a header from showing.
+- (CGSize)headerSizeForSection:(NSInteger)section {
+ if ([self needsSectionHeaderForSection:section])
+ return CGSizeMake(self.bounds.size.width,
+ [BookmarkHeaderSeparatorView preferredHeight]);
+
+ return CGSizeZero;
+}
+
+// Create a cell for display at |indexPath|.
+- (UICollectionViewCell*)cellAtIndexPath:(NSIndexPath*)indexPath {
+ if (indexPath.section == self.promoSection) {
+ if (experimental_flags::IsSigninPromoEnabled()) {
+ BookmarkSigninPromoCell* signinPromoCell = [self.collectionView
+ dequeueReusableCellWithReuseIdentifier:[BookmarkSigninPromoCell
+ reuseIdentifier]
+ forIndexPath:indexPath];
+ signinPromoCell.signinPromoView.delegate = _signinPromoViewMediator;
+ [[_signinPromoViewMediator createConfigurator]
+ configureSigninPromoView:signinPromoCell.signinPromoView];
+ __weak BookmarkCollectionView* weakSelf = self;
+ signinPromoCell.closeButtonAction = ^() {
+ [weakSelf.delegate bookmarkCollectionViewDismissPromo:self];
+ };
+ return signinPromoCell;
+ } else {
+ BookmarkPromoCell* promoCell = [self.collectionView
+ dequeueReusableCellWithReuseIdentifier:[BookmarkPromoCell
+ reuseIdentifier]
+ forIndexPath:indexPath];
+ promoCell.delegate = self;
+ return promoCell;
+ }
+ }
+ const BookmarkNode* node = [self nodeAtIndexPath:indexPath];
+
+ if (indexPath.section == self.folderSection)
+ return [self cellForFolder:node indexPath:indexPath];
+
+ BookmarkItemCell* cell = [self cellForBookmark:node indexPath:indexPath];
+ return cell;
+}
+
+// Create a header view for the element at |indexPath|.
+- (UICollectionReusableView*)headerAtIndexPath:(NSIndexPath*)indexPath {
+ if (![self needsSectionHeaderForSection:indexPath.section])
+ return nil;
+
+ BookmarkHeaderSeparatorView* view = [self.collectionView
+ dequeueReusableSupplementaryViewOfKind:
+ UICollectionElementKindSectionHeader
+ withReuseIdentifier:[BookmarkHeaderSeparatorView
+ reuseIdentifier]
+ forIndexPath:indexPath];
+ view.backgroundColor = [UIColor colorWithWhite:1 alpha:1];
+ return view;
+}
+
+- (NSInteger)numberOfItemsInSection:(NSInteger)section {
+ if (section == self.folderSection)
+ return _subFolders.size();
+ if (section == self.itemsSection)
+ return _subItems.size();
+ if (section == self.promoSection)
+ return 1;
+
+ NOTREACHED();
+ return -1;
+}
+
+- (NSInteger)numberOfSections {
+ return self.sectionCount;
+}
+
+#pragma mark - BookmarkPromoCellDelegate
+
+- (void)bookmarkPromoCellDidTapSignIn:(BookmarkPromoCell*)bookmarkPromoCell {
+ [self.delegate bookmarkCollectionViewShowSignIn:self];
+}
+
+- (void)bookmarkPromoCellDidTapDismiss:(BookmarkPromoCell*)bookmarkPromoCell {
+ [self.delegate bookmarkCollectionViewDismissPromo:self];
+}
+
+#pragma mark - SigninPromoViewConsumer
+
+- (void)configureSigninPromoWithConfigurator:
+ (SigninPromoViewConfigurator*)configurator
+ identityChanged:(BOOL)identityChanged {
+ DCHECK(_signinPromoViewMediator);
+ NSIndexPath* indexPath =
+ [NSIndexPath indexPathForRow:0 inSection:self.promoSection];
+ BookmarkSigninPromoCell* signinPromoCell =
+ static_cast<BookmarkSigninPromoCell*>(
+ [self.collectionView cellForItemAtIndexPath:indexPath]);
+ if (!signinPromoCell)
+ return;
+ // Should always reconfigure the cell size even if it has to be reloaded.
+ // -[BookmarkCollectionView cellSizeForIndexPath:] uses the current
+ // cell to compute its height.
+ [configurator configureSigninPromoView:signinPromoCell.signinPromoView];
+ if (identityChanged) {
+ // The section should be reload to update the cell height.
+ NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:self.promoSection];
+ [self.collectionView reloadSections:indexSet];
+ }
+}
+
+#pragma mark - UIScrollViewDelegate
+
+- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
+ [self updateShadowFrame];
+ [self collectionViewScrolled];
+}
+
+#pragma mark - empty background
+
+// Schedules showing or hiding the empty bookmarks background view if the
+// collection view is empty by calling showEmptyBackgroundIfNeeded after
+// kShowEmptyBookmarksBackgroundRefreshDelay.
+// Multiple call to this method will cancel previous scheduled call to
+// showEmptyBackgroundIfNeeded before scheduling a new one.
+- (void)scheduleEmptyBackgroundVisibilityUpdate {
+ [NSObject cancelPreviousPerformRequestsWithTarget:self
+ selector:@selector
+ (updateEmptyBackgroundVisibility)
+ object:nil];
+ [self performSelector:@selector(updateEmptyBackgroundVisibility)
+ withObject:nil
+ afterDelay:kShowEmptyBookmarksBackgroundRefreshDelay];
+}
+
+- (BOOL)isCollectionViewEmpty {
+ BOOL collectionViewIsEmpty = YES;
+ const NSInteger numberOfSections = [self numberOfSections];
+ NSInteger section = [self shouldShowPromoCell] ? 1 : 0;
+ for (; collectionViewIsEmpty && section < numberOfSections; ++section) {
+ const NSInteger numberOfItemsInSection =
+ [self numberOfItemsInSection:section];
+ collectionViewIsEmpty = numberOfItemsInSection == 0;
+ }
+ return collectionViewIsEmpty;
+}
+
+// Shows/hides empty bookmarks background view if the collections view is empty.
+- (void)updateEmptyBackgroundVisibility {
+ const BOOL showEmptyBackground =
+ [self isCollectionViewEmpty] && ![self shouldShowPromoCell];
+ [self setEmptyBackgroundVisible:showEmptyBackground];
+}
+
+// Shows/hides empty bookmarks background view with an animation.
+- (void)setEmptyBackgroundVisible:(BOOL)emptyBackgroundVisible {
+ [UIView beginAnimations:@"alpha" context:NULL];
+ self.emptyCollectionBackgroundView.alpha = emptyBackgroundVisible ? 1 : 0;
+ [UIView commitAnimations];
+}
+
+#pragma mark - UICollectionViewDataSource
+
+- (NSInteger)collectionView:(UICollectionView*)collectionView
+ numberOfItemsInSection:(NSInteger)section {
+ const NSInteger numberOfItemsInSection =
+ [self numberOfItemsInSection:section];
+ const BOOL isCollectionViewEmpty = [self isCollectionViewEmpty];
+ self.collectionView.scrollEnabled = !isCollectionViewEmpty;
+ if (isCollectionViewEmpty) {
+ [self scheduleEmptyBackgroundVisibilityUpdate];
+ } else {
+ // Hide empty bookmarks now.
+ [self setEmptyBackgroundVisible:NO];
+ }
+ return numberOfItemsInSection;
+}
+
+- (NSInteger)numberOfSectionsInCollectionView:
+ (UICollectionView*)collectionView {
+ const NSInteger numberOfSections = [self numberOfSections];
+ const BOOL collectionViewIsEmpty = 0 == numberOfSections;
+ self.collectionView.scrollEnabled = !collectionViewIsEmpty;
+ if (collectionViewIsEmpty) {
+ [self scheduleEmptyBackgroundVisibilityUpdate];
} else {
// Hide empty bookmarks now.
[self setEmptyBackgroundVisible:NO];
@@ -413,15 +1019,43 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
return [self headerSizeForSection:section];
}
-#pragma mark - BookmarkItemCell callbacks
+#pragma mark - UIGestureRecognizer Callbacks
-- (void)didTapMenuButton:(BookmarkItemCell*)cell view:(UIView*)view {
- [self didTapMenuButtonAtIndexPath:[self.collectionView indexPathForCell:cell]
- onView:view
- forCell:cell];
+- (void)longPress:(UILongPressGestureRecognizer*)recognizer {
+ if (self.longPressRecognizer.numberOfTouches != 1 || self.editing)
+ return;
+
+ if (self.longPressRecognizer.state == UIGestureRecognizerStateRecognized ||
+ self.longPressRecognizer.state == UIGestureRecognizerStateBegan) {
+ CGPoint point =
+ [self.longPressRecognizer locationOfTouch:0 inView:self.collectionView];
+ NSIndexPath* indexPath =
+ [self.collectionView indexPathForItemAtPoint:point];
+ if (!indexPath)
+ return;
+
+ UICollectionViewCell* cell =
+ [self.collectionView cellForItemAtIndexPath:indexPath];
+
+ // Notify the subclass that long press has been received.
+ if (cell)
+ [self didLongPressCell:cell atIndexPath:indexPath];
+ }
+}
+
+#pragma mark - UIGestureRecognizerDelegate
+
+- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
+ DCHECK(gestureRecognizer == self.longPressRecognizer);
+ CGPoint point =
+ [gestureRecognizer locationOfTouch:0 inView:self.collectionView];
+ NSIndexPath* indexPath = [self.collectionView indexPathForItemAtPoint:point];
+ if (!indexPath)
+ return NO;
+ return [self allowLongPressForCellAtIndexPath:indexPath];
}
-#pragma mark - Convenience methods for subclasses
+#pragma mark - Convenience methods
- (void)updateCellAtIndexPath:(NSIndexPath*)indexPath
withImage:(UIImage*)image
@@ -466,6 +1100,10 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
return cell;
}
+// Cancels all async loads of favicons. Subclasses should call this method when
+// the bookmark model is going through significant changes, then manually call
+// loadFaviconAtIndexPath: for everything that needs to be loaded; or
+// just reload relevant cells.
- (void)cancelAllFaviconLoads {
_faviconTaskTracker.TryCancelAll();
}
@@ -475,6 +1113,8 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
_faviconLoadTasks[IntegerPair(indexPath.section, indexPath.item)]);
}
+// Asynchronously loads favicon for given index path. The loads are cancelled
+// upon cell reuse automatically.
- (void)loadFaviconAtIndexPath:(NSIndexPath*)indexPath {
// Cancel previous load attempts.
[self cancelLoadingFaviconAtIndexPath:indexPath];
@@ -483,35 +1123,36 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
__weak BookmarkCollectionView* weakSelf = self;
const bookmarks::BookmarkNode* node = [self nodeAtIndexPath:indexPath];
GURL blockURL(node->url());
- void (^faviconBlock)(const favicon_base::LargeIconResult&) = ^(
- const favicon_base::LargeIconResult& result) {
- BookmarkCollectionView* strongSelf = weakSelf;
- if (!strongSelf)
- return;
- UIImage* favIcon = nil;
- UIColor* backgroundColor = nil;
- UIColor* textColor = nil;
- NSString* fallbackText = nil;
- if (result.bitmap.is_valid()) {
- scoped_refptr<base::RefCountedMemory> data = result.bitmap.bitmap_data;
- favIcon = [UIImage imageWithData:[NSData dataWithBytes:data->front()
- length:data->size()]];
- } else if (result.fallback_icon_style) {
- backgroundColor = skia::UIColorFromSkColor(
- result.fallback_icon_style->background_color);
- textColor =
- skia::UIColorFromSkColor(result.fallback_icon_style->text_color);
-
- fallbackText =
- base::SysUTF16ToNSString(favicon::GetFallbackIconText(blockURL));
- }
-
- [strongSelf updateCellAtIndexPath:indexPath
- withImage:favIcon
- backgroundColor:backgroundColor
- textColor:textColor
- fallbackText:fallbackText];
- };
+ void (^faviconBlock)(const favicon_base::LargeIconResult&) =
+ ^(const favicon_base::LargeIconResult& result) {
+ BookmarkCollectionView* strongSelf = weakSelf;
+ if (!strongSelf)
+ return;
+ UIImage* favIcon = nil;
+ UIColor* backgroundColor = nil;
+ UIColor* textColor = nil;
+ NSString* fallbackText = nil;
+ if (result.bitmap.is_valid()) {
+ scoped_refptr<base::RefCountedMemory> data =
+ result.bitmap.bitmap_data;
+ favIcon = [UIImage imageWithData:[NSData dataWithBytes:data->front()
+ length:data->size()]];
+ } else if (result.fallback_icon_style) {
+ backgroundColor = skia::UIColorFromSkColor(
+ result.fallback_icon_style->background_color);
+ textColor =
+ skia::UIColorFromSkColor(result.fallback_icon_style->text_color);
+
+ fallbackText =
+ base::SysUTF16ToNSString(favicon::GetFallbackIconText(blockURL));
+ }
+
+ [strongSelf updateCellAtIndexPath:indexPath
+ withImage:favIcon
+ backgroundColor:backgroundColor
+ textColor:textColor
+ fallbackText:fallbackText];
+ };
CGFloat scale = [UIScreen mainScreen].scale;
CGFloat preferredSize = scale * [BookmarkItemCell preferredImageSize];
@@ -542,6 +1183,7 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
return cell;
}
+// Updates the editing state for the cell.
- (void)updateEditingStateOfCell:(BookmarkCell*)cell
atIndexPath:(NSIndexPath*)indexPath
animateMenuVisibility:(BOOL)animateMenuVisibility
@@ -553,6 +1195,12 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
animated:animateMenuVisibility];
}
+// |animateMenuVisibility| refers to whether the change in the visibility of the
+// menu button is animated.
+// |animateSelectedState| refers to whether the change in the selected state (in
+// editing mode) of the cell is animated.
+// This method updates the visibility of the menu button.
+// This method updates the selected state of the cell (in editing mode).
- (void)updateEditingStateOfCellAtIndexPath:(NSIndexPath*)indexPath
animateMenuVisibility:(BOOL)animateMenuVisibility
animateSelectedState:(BOOL)animateSelectedState {
@@ -567,262 +1215,30 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
animateSelectedState:animateSelectedState];
}
-#pragma mark - BookmarkModelObserver Callbacks
-
-- (void)bookmarkModelLoaded {
- NOTREACHED();
-}
-
-- (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
- NOTREACHED();
-}
-
-- (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
- NOTREACHED();
-}
-
-- (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
- movedFromParent:(const BookmarkNode*)oldParent
- toParent:(const BookmarkNode*)newParent {
- NOTREACHED();
-}
-
-- (void)bookmarkNodeDeleted:(const BookmarkNode*)node
- fromFolder:(const BookmarkNode*)folder {
- NOTREACHED();
-}
-
-- (void)bookmarkModelRemovedAllNodes {
- NOTREACHED();
-}
-
-#pragma mark - Public Methods That Must Be Overridden
-
-- (BOOL)shouldSelectCellAtIndexPath:(NSIndexPath*)indexPath {
- NOTREACHED();
- return NO;
-}
-
-- (void)didTapCellAtIndexPath:(NSIndexPath*)indexPath {
- NOTREACHED();
-}
-
-- (void)didAddCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
- NOTREACHED();
-}
-
-- (void)didRemoveCellForEditingAtIndexPath:(NSIndexPath*)indexPath {
- NOTREACHED();
-}
-
-- (void)didTapMenuButtonAtIndexPath:(NSIndexPath*)indexPath
- onView:(UIView*)view
- forCell:(BookmarkItemCell*)cell {
- NOTREACHED();
-}
-
-- (bookmark_cell::ButtonType)buttonTypeForCellAtIndexPath:
- (NSIndexPath*)indexPath {
- NOTREACHED();
- return bookmark_cell::ButtonNone;
-}
-
-- (BOOL)allowLongPressForCellAtIndexPath:(NSIndexPath*)indexPath {
- NOTREACHED();
- return NO;
-}
-
-- (void)didLongPressCell:(UICollectionViewCell*)cell
- atIndexPath:(NSIndexPath*)indexPath {
- NOTREACHED();
-}
-
-- (BOOL)cellIsSelectedForEditingAtIndexPath:(NSIndexPath*)indexPath {
- NOTREACHED();
- return NO;
-}
-
-- (CGSize)headerSizeForSection:(NSInteger)section {
- NOTREACHED();
- return CGSizeZero;
-}
-
-- (UICollectionViewCell*)cellAtIndexPath:(NSIndexPath*)indexPath {
- NOTREACHED();
- return nil;
-}
-
-- (UICollectionReusableView*)headerAtIndexPath:(NSIndexPath*)indexPath {
- NOTREACHED();
- return nil;
-}
-
-- (NSInteger)numberOfItemsInSection:(NSInteger)section {
- NOTREACHED();
- return 0;
-}
-- (NSInteger)numberOfSections {
- NOTREACHED();
- return 0;
-}
-
-- (void)updateCollectionView {
- NOTREACHED();
-}
-
-- (const bookmarks::BookmarkNode*)nodeAtIndexPath:(NSIndexPath*)indexPath {
- NOTREACHED();
- return nullptr;
-}
-
-#pragma mark - Methods that subclasses can override (UI)
-
-- (UIEdgeInsets)insetForSectionAtIndex:(NSInteger)section {
- if ([self isPromoSection:section])
- return UIEdgeInsetsZero;
-
- if (IsIPadIdiom()) {
- return UIEdgeInsetsMake(10, rowMarginTablet, 0, rowMarginTablet);
- } else {
- return UIEdgeInsetsZero;
- }
-}
-
-- (CGSize)cellSizeForIndexPath:(NSIndexPath*)indexPath {
- DCHECK(![self isPromoSection:indexPath.section]);
- UIEdgeInsets insets = [self insetForSectionAtIndex:indexPath.section];
- return CGSizeMake(self.bounds.size.width - (insets.right + insets.left),
- rowHeight);
-}
-
+// The minimal horizontal space between items to respect between cells in
+// |section|.
- (CGFloat)minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 0;
}
+// The minimal vertical space between items to respect between cells in
+// |section|.
- (CGFloat)minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 0;
}
+// The text to display when there are no items in the collection. Default is
+// |IDS_IOS_BOOKMARK_NO_BOOKMARKS_LABEL|.
- (NSString*)textWhenCollectionIsEmpty {
return l10n_util::GetNSString(IDS_IOS_BOOKMARK_NO_BOOKMARKS_LABEL);
}
-#pragma mark - Public Methods That Can Be Overridden
-
-- (void)collectionViewScrolled {
-}
-
-#pragma mark - Editing
-
-- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
- if (self.editing == editing)
- return;
-
- _editing = editing;
- [UIView animateWithDuration:animated ? 0.2 : 0.0
- delay:0
- options:UIViewAnimationOptionBeginFromCurrentState
- animations:^{
- self.shadow.alpha = editing ? 0.0 : 1.0;
- }
- completion:nil];
-
- // If the promo is active this means that it is removed and added as the edit
- // mode changes, making reloading the data mandatory.
- if ([self isPromoActive]) {
- [self.collectionView reloadData];
- [self.collectionView.collectionViewLayout invalidateLayout];
- } else {
- // Update the visual state of the bookmark cells without reloading the
- // section.
- // This prevents flickering of images that need to be asynchronously
- // reloaded.
- NSArray* indexPaths = [self.collectionView indexPathsForVisibleItems];
- for (NSIndexPath* indexPath in indexPaths) {
- [self updateEditingStateOfCellAtIndexPath:indexPath
- animateMenuVisibility:animated
- animateSelectedState:NO];
- }
- }
-}
-
-#pragma mark - Public Methods
-
-- (void)changeOrientation:(UIInterfaceOrientation)orientation {
- [self updateCollectionView];
-}
-
-- (CGFloat)contentPositionInPortraitOrientation {
- if (IsPortrait())
- return self.collectionView.contentOffset.y;
-
- // In short landscape mode and portrait mode, there are 2 cells per row.
- if ([self wideLandscapeMode])
- return self.collectionView.contentOffset.y;
-
- // In wide landscape mode, there are 3 cells per row.
- return self.collectionView.contentOffset.y * 3 / 2.0;
-}
-
-- (void)applyContentPosition:(CGFloat)position {
- if (IsLandscape() && [self wideLandscapeMode]) {
- position = position * 2 / 3.0;
- }
-
- CGFloat y =
- MIN(position,
- [self.collectionView.collectionViewLayout collectionViewContentSize]
- .height);
- self.collectionView.contentOffset =
- CGPointMake(self.collectionView.contentOffset.x, y);
-}
-
-- (void)setScrollsToTop:(BOOL)scrollsToTop {
- self.collectionView.scrollsToTop = scrollsToTop;
-}
-
-#pragma mark - Private Methods
-
+// In landscape mode, there are 2 widths: 480pt and 568pt. Returns YES if the
+// width is 568pt.
- (BOOL)wideLandscapeMode {
return self.frame.size.width > 567;
}
-#pragma mark - UIGestureRecognizer Callbacks
-
-- (void)longPress:(UILongPressGestureRecognizer*)recognizer {
- if (self.longPressRecognizer.numberOfTouches != 1 || self.editing)
- return;
-
- if (self.longPressRecognizer.state == UIGestureRecognizerStateRecognized ||
- self.longPressRecognizer.state == UIGestureRecognizerStateBegan) {
- CGPoint point =
- [self.longPressRecognizer locationOfTouch:0 inView:self.collectionView];
- NSIndexPath* indexPath =
- [self.collectionView indexPathForItemAtPoint:point];
- if (!indexPath)
- return;
-
- UICollectionViewCell* cell =
- [self.collectionView cellForItemAtIndexPath:indexPath];
-
- // Notify the subclass that long press has been received.
- if (cell)
- [self didLongPressCell:cell atIndexPath:indexPath];
- }
-}
-
-#pragma mark - UIGestureRecognizerDelegate
-
-- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
- DCHECK(gestureRecognizer == self.longPressRecognizer);
- CGPoint point =
- [gestureRecognizer locationOfTouch:0 inView:self.collectionView];
- NSIndexPath* indexPath = [self.collectionView indexPathForItemAtPoint:point];
- if (!indexPath)
- return NO;
- return [self allowLongPressForCellAtIndexPath:indexPath];
-}
-
#pragma mark - Promo Cell
- (BOOL)isPromoSection:(NSInteger)section {
@@ -830,7 +1246,7 @@ const NSTimeInterval kShowEmptyBookmarksBackgroundRefreshDelay = 1.0;
}
- (BOOL)shouldShowPromoCell {
- return NO;
+ return _promoVisible;
}
- (BOOL)isPromoActive {

Powered by Google App Engine
This is Rietveld 408576698