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

Unified Diff: ios/chrome/browser/ui/bookmarks/bookmark_collection_view.mm

Issue 2586993002: Upstream Chrome on iOS source code [3/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View 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
new file mode 100644
index 0000000000000000000000000000000000000000..e342f092a4d743ec0c3ae614a2943f93f13a33f2
--- /dev/null
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_collection_view.mm
@@ -0,0 +1,859 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/bookmarks/bookmark_collection_view.h"
+
+#import <UIKit/UIGestureRecognizerSubclass.h>
+#include <algorithm>
+#include <map>
+#include <memory>
+
+#include "base/ios/weak_nsobject.h"
+#include "base/mac/bind_objc_block.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/objc_property_releaser.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_model_observer.h"
+#include "components/favicon/core/fallback_url_util.h"
+#include "components/favicon/core/large_icon_service.h"
+#include "components/favicon_base/fallback_icon_style.h"
+#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/favicon/ios_chrome_large_icon_service_factory.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"
+#import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#include "ios/chrome/grit/ios_strings.h"
+#include "skia/ext/skia_utils_ios.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+
+using bookmarks::BookmarkNode;
+
+namespace {
+
+// Used to store a pair of NSIntegers when storing a NSIndexPath in C++
+// collections.
+using IntegerPair = std::pair<NSInteger, NSInteger>;
+
+// The margin between the side of the view and the first and last tile.
+CGFloat rowMarginTablet = 24.0;
+CGFloat rowHeight = 48.0;
+// Minimal acceptable favicon size, in points.
+CGFloat minFaviconSizePt = 16;
+
+// Delay in seconds to which the empty background view will be shown when the
+// collection view is empty.
+// 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,
+ UICollectionViewDelegateFlowLayout,
+ UIGestureRecognizerDelegate> {
+ std::unique_ptr<bookmarks::BookmarkModelBridge> _modelBridge;
+ ios::ChromeBrowserState* _browserState;
+
+ base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkCollectionView;
+
+ // Map of favicon load tasks for each index path. Used to keep track of
+ // pending favicon load operations so that they can be cancelled upon cell
+ // reuse. Keys are (section, item) pairs of cell index paths.
+ std::map<IntegerPair, base::CancelableTaskTracker::TaskId> _faviconLoadTasks;
+ // Task tracker used for async favicon loads.
+ base::CancelableTaskTracker _faviconTaskTracker;
+}
+
+// Redefined to be readwrite.
+@property(nonatomic, assign) bookmarks::BookmarkModel* bookmarkModel;
+// Redefined to be readwrite.
+@property(nonatomic, retain) UICollectionView* collectionView;
+// Redefined to be readwrite.
+@property(nonatomic, assign) BOOL editing;
+// Detects a long press on a cell.
+@property(nonatomic, retain) UILongPressGestureRecognizer* longPressRecognizer;
+// Background view of the collection view shown when there is no items.
+@property(nonatomic, retain)
+ BookmarkCollectionViewBackground* emptyCollectionBackgroundView;
+// Shadow to display over the content.
+@property(nonatomic, retain) 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;
+
+// In landscape mode, there are 2 widths: 480pt and 568pt. Returns YES if the
+// width is 568pt.
+- (BOOL)wideLandscapeMode;
+
+// 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 bookmarkModel = _bookmarkModel;
+@synthesize collectionView = _collectionView;
+@synthesize editing = _editing;
+@synthesize emptyCollectionBackgroundView = _emptyCollectionBackgroundView;
+@synthesize loader = _loader;
+@synthesize longPressRecognizer = _longPressRecognizer;
+@synthesize browserState = _browserState;
+@synthesize shadow = _shadow;
+
+#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) {
+ _propertyReleaser_BookmarkCollectionView.Init(
+ self, [BookmarkCollectionView class]);
+
+ _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();
+ [super dealloc];
+}
+
+- (void)setupViews {
+ self.backgroundColor = bookmark_utils_ios::mainBackgroundColor();
+ base::scoped_nsobject<UICollectionViewFlowLayout> layout(
+ [[UICollectionViewFlowLayout alloc] init]);
+
+ base::scoped_nsobject<UICollectionView> collectionView(
+ [[UICollectionView alloc] initWithFrame:self.bounds
+ collectionViewLayout:layout]);
+ self.collectionView = collectionView;
+ self.collectionView.backgroundColor = [UIColor clearColor];
+ self.collectionView.autoresizingMask =
+ UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
+ self.collectionView.alwaysBounceVertical = YES;
+ self.collectionView.delegate = self;
+ self.collectionView.dataSource = self;
+ [self.collectionView registerClass:[BookmarkFolderCell class]
+ forCellWithReuseIdentifier:[BookmarkFolderCell reuseIdentifier]];
+ [self.collectionView registerClass:[BookmarkItemCell class]
+ forCellWithReuseIdentifier:[BookmarkItemCell reuseIdentifier]];
+ [self.collectionView registerClass:[BookmarkHeaderView class]
+ forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
+ withReuseIdentifier:[BookmarkHeaderView reuseIdentifier]];
+ [self.collectionView
+ registerClass:[BookmarkHeaderSeparatorView class]
+ forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
+ withReuseIdentifier:[BookmarkHeaderSeparatorView reuseIdentifier]];
+ [self.collectionView registerClass:[BookmarkPromoCell class]
+ forCellWithReuseIdentifier:[BookmarkPromoCell reuseIdentifier]];
+
+ [self addSubview:self.collectionView];
+
+ // Set up the background view shown when the collection is empty.
+ base::scoped_nsobject<BookmarkCollectionViewBackground>
+ emptyCollectionBackgroundView(
+ [[BookmarkCollectionViewBackground alloc] initWithFrame:CGRectZero]);
+ self.emptyCollectionBackgroundView = emptyCollectionBackgroundView;
+ self.emptyCollectionBackgroundView.autoresizingMask =
+ UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
+ self.emptyCollectionBackgroundView.alpha = 0;
+ self.emptyCollectionBackgroundView.text = [self textWhenCollectionIsEmpty];
+
+ self.emptyCollectionBackgroundView.frame = self.collectionView.bounds;
+ self.collectionView.backgroundView = self.emptyCollectionBackgroundView;
+
+ [self updateShadow];
+
+ self.longPressRecognizer =
+ base::scoped_nsobject<UILongPressGestureRecognizer>(
+ [[UILongPressGestureRecognizer alloc]
+ initWithTarget:self
+ action:@selector(longPress:)]);
+ self.longPressRecognizer.delegate = self;
+ [self.collectionView addGestureRecognizer:self.longPressRecognizer];
+}
+
+- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
+ [self updateShadow];
+}
+
+- (void)updateShadow {
+ // Remove the current one, if any.
+ [self.shadow removeFromSuperview];
+
+ if (IsCompact(self)) {
+ self.shadow =
+ bookmark_utils_ios::dropShadowWithWidth(CGRectGetWidth(self.bounds));
+ } else {
+ self.shadow = [[[UIView alloc]
+ initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds),
+ 1 / [[UIScreen mainScreen] scale])]
+ autorelease];
+ self.shadow.backgroundColor = [UIColor colorWithWhite:0.0 alpha:.12];
+ }
+
+ [self updateShadowFrame];
+ self.shadow.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ if (self.editing)
+ self.shadow.alpha = 0.0;
+
+ // Add the new shadow.
+ [self addSubview:self.shadow];
+}
+
+- (void)updateShadowFrame {
+ CGFloat shadowHeight = CGRectGetHeight(self.shadow.frame);
+ CGFloat y = std::min<CGFloat>(
+ 0.0, self.collectionView.contentOffset.y - shadowHeight);
+ self.shadow.frame =
+ CGRectMake(0, y, CGRectGetWidth(self.bounds), shadowHeight);
+}
+
+#pragma mark - UIScrollViewDelegate
+
+- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
+ [self updateShadowFrame];
+ [self collectionViewScrolled];
+}
+
+#pragma mark - empty background
+
+- (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;
+}
+
+- (void)updateEmptyBackgroundVisibility {
+ const BOOL showEmptyBackground =
+ [self isCollectionViewEmpty] && ![self shouldShowPromoCell];
+ [self setEmptyBackgroundVisible:showEmptyBackground];
+}
+
+- (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];
+ }
+ return numberOfSections;
+}
+
+- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
+ cellForItemAtIndexPath:(NSIndexPath*)indexPath {
+ return [self cellAtIndexPath:indexPath];
+}
+
+- (UICollectionReusableView*)collectionView:(UICollectionView*)collectionView
+ viewForSupplementaryElementOfKind:(NSString*)kind
+ atIndexPath:(NSIndexPath*)indexPath {
+ return [self headerAtIndexPath:indexPath];
+}
+
+- (BOOL)collectionView:(UICollectionView*)collectionView
+ shouldSelectItemAtIndexPath:(NSIndexPath*)indexPath {
+ return [self shouldSelectCellAtIndexPath:indexPath];
+}
+
+- (void)collectionView:(UICollectionView*)collectionView
+ didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
+ if (self.editing)
+ [self toggleSelectedForEditingAtIndexPath:indexPath];
+ else
+ [self didTapCellAtIndexPath:indexPath];
+}
+
+- (void)toggleSelectedForEditingAtIndexPath:(NSIndexPath*)indexPath {
+ BOOL selected = [self cellIsSelectedForEditingAtIndexPath:indexPath];
+ if (selected)
+ [self didRemoveCellForEditingAtIndexPath:indexPath];
+ else
+ [self didAddCellForEditingAtIndexPath:indexPath];
+
+ [self updateEditingStateOfCellAtIndexPath:indexPath
+ animateMenuVisibility:NO
+ animateSelectedState:YES];
+}
+
+#pragma mark - UICollectionViewDelegate
+
+- (void)collectionView:(UICollectionView*)collectionView
+ didEndDisplayingCell:(UICollectionViewCell*)cell
+ forItemAtIndexPath:(NSIndexPath*)indexPath {
+ _faviconTaskTracker.TryCancel(
+ _faviconLoadTasks[IntegerPair(indexPath.section, indexPath.item)]);
+}
+
+#pragma mark - UICollectionViewDelegateFlowLayout
+
+- (UIEdgeInsets)collectionView:(UICollectionView*)collectionView
+ layout:(UICollectionViewLayout*)collectionViewLayout
+ insetForSectionAtIndex:(NSInteger)section {
+ return [self insetForSectionAtIndex:section];
+}
+
+- (CGFloat)collectionView:(UICollectionView*)collectionView
+ layout:(UICollectionViewLayout*)
+ collectionViewLayout
+ minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
+ return [self minimumInteritemSpacingForSectionAtIndex:section];
+}
+
+- (CGFloat)collectionView:(UICollectionView*)collectionView
+ layout:(UICollectionViewLayout*)layout
+ minimumLineSpacingForSectionAtIndex:(NSInteger)section {
+ return [self minimumLineSpacingForSectionAtIndex:section];
+}
+
+- (CGSize)collectionView:(UICollectionView*)collectionView
+ layout:(UICollectionViewLayout*)collectionViewLayout
+ sizeForItemAtIndexPath:(NSIndexPath*)indexPath {
+ return [self cellSizeForIndexPath:indexPath];
+}
+
+- (CGSize)collectionView:(UICollectionView*)collectionView
+ layout:
+ (UICollectionViewLayout*)collectionViewLayout
+ referenceSizeForHeaderInSection:(NSInteger)section {
+ return [self headerSizeForSection:section];
+}
+
+#pragma mark - BookmarkItemCell callbacks
+
+- (void)didTapMenuButton:(BookmarkItemCell*)cell view:(UIView*)view {
+ [self didTapMenuButtonAtIndexPath:[self.collectionView indexPathForCell:cell]
+ onView:view
+ forCell:cell];
+}
+
+#pragma mark - Convenience methods for subclasses
+
+- (void)updateCellAtIndexPath:(NSIndexPath*)indexPath
+ withImage:(UIImage*)image
+ backgroundColor:(UIColor*)backgroundColor
+ textColor:(UIColor*)textColor
+ fallbackText:(NSString*)text {
+ BookmarkItemCell* cell = base::mac::ObjCCast<BookmarkItemCell>(
+ [self.collectionView cellForItemAtIndexPath:indexPath]);
+ if (!cell)
+ return;
+
+ if (image) {
+ [cell setImage:image];
+ } else {
+ [cell setPlaceholderText:text
+ textColor:textColor
+ backgroundColor:backgroundColor];
+ }
+}
+
+- (BookmarkItemCell*)cellForBookmark:(const BookmarkNode*)node
+ indexPath:(NSIndexPath*)indexPath {
+ DCHECK(![self isPromoSection:indexPath.section]);
+
+ BOOL selected = [self cellIsSelectedForEditingAtIndexPath:indexPath];
+
+ BookmarkItemCell* cell = [self.collectionView
+ dequeueReusableCellWithReuseIdentifier:[BookmarkItemCell reuseIdentifier]
+ forIndexPath:indexPath];
+
+ [cell updateWithTitle:bookmark_utils_ios::TitleForBookmarkNode(node)];
+ [cell setSelectedForEditing:selected animated:NO];
+ [cell setButtonTarget:self action:@selector(didTapMenuButton:view:)];
+
+ [self updateEditingStateOfCell:cell
+ atIndexPath:indexPath
+ animateMenuVisibility:NO
+ animateSelectedState:NO];
+
+ [self loadFaviconAtIndexPath:indexPath];
+
+ return cell;
+}
+
+- (void)cancelAllFaviconLoads {
+ _faviconTaskTracker.TryCancelAll();
+}
+
+- (void)cancelLoadingFaviconAtIndexPath:(NSIndexPath*)indexPath {
+ _faviconTaskTracker.TryCancel(
+ _faviconLoadTasks[IntegerPair(indexPath.section, indexPath.item)]);
+}
+
+- (void)loadFaviconAtIndexPath:(NSIndexPath*)indexPath {
+ // Cancel previous load attempts.
+ [self cancelLoadingFaviconAtIndexPath:indexPath];
+
+ // Start loading a favicon.
+ base::WeakNSObject<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) {
+ base::scoped_nsobject<BookmarkCollectionView> strongSelf([weakSelf retain]);
+ 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.get();
+ 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];
+ CGFloat minSize = scale * minFaviconSizePt;
+
+ base::CancelableTaskTracker::TaskId taskId =
+ IOSChromeLargeIconServiceFactory::GetForBrowserState(self.browserState)
+ ->GetLargeIconOrFallbackStyle(node->url(), minSize, preferredSize,
+ base::BindBlock(faviconBlock),
+ &_faviconTaskTracker);
+ _faviconLoadTasks[IntegerPair(indexPath.section, indexPath.item)] = taskId;
+}
+
+- (BookmarkFolderCell*)cellForFolder:(const BookmarkNode*)node
+ indexPath:(NSIndexPath*)indexPath {
+ DCHECK(![self isPromoSection:indexPath.section]);
+ BookmarkFolderCell* cell = [self.collectionView
+ dequeueReusableCellWithReuseIdentifier:[BookmarkFolderCell
+ reuseIdentifier]
+ forIndexPath:indexPath];
+ [self updateEditingStateOfCell:cell
+ atIndexPath:indexPath
+ animateMenuVisibility:NO
+ animateSelectedState:NO];
+ [cell updateWithTitle:bookmark_utils_ios::TitleForBookmarkNode(node)];
+ [cell setButtonTarget:self action:@selector(didTapMenuButton:view:)];
+
+ return cell;
+}
+
+- (void)updateEditingStateOfCell:(BookmarkCell*)cell
+ atIndexPath:(NSIndexPath*)indexPath
+ animateMenuVisibility:(BOOL)animateMenuVisibility
+ animateSelectedState:(BOOL)animateSelectedState {
+ BOOL selected = [self cellIsSelectedForEditingAtIndexPath:indexPath];
+ [cell setSelectedForEditing:selected animated:animateSelectedState];
+ BookmarkItemCell* itemCell = static_cast<BookmarkItemCell*>(cell);
+ [itemCell showButtonOfType:[self buttonTypeForCellAtIndexPath:indexPath]
+ animated:animateMenuVisibility];
+}
+
+- (void)updateEditingStateOfCellAtIndexPath:(NSIndexPath*)indexPath
+ animateMenuVisibility:(BOOL)animateMenuVisibility
+ animateSelectedState:(BOOL)animateSelectedState {
+ BookmarkCell* cell = base::mac::ObjCCast<BookmarkCell>(
+ [self.collectionView cellForItemAtIndexPath:indexPath]);
+ if (!cell)
+ return;
+
+ [self updateEditingStateOfCell:cell
+ atIndexPath:indexPath
+ animateMenuVisibility:animateMenuVisibility
+ 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 {
+ if ([self isPromoSection:indexPath.section]) {
+ CGRect estimatedFrame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), 100);
+ UICollectionViewCell* cell =
+ [self.collectionView cellForItemAtIndexPath:indexPath];
+ if (!cell) {
+ cell = [[[BookmarkPromoCell alloc] initWithFrame:estimatedFrame]
+ autorelease];
+ }
+ cell.frame = estimatedFrame;
+ [cell layoutIfNeeded];
+ return [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
+ }
+
+ UIEdgeInsets insets = [self insetForSectionAtIndex:indexPath.section];
+ return CGSizeMake(self.bounds.size.width - (insets.right + insets.left),
+ rowHeight);
+}
+
+- (CGFloat)minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
+ return 0;
+}
+
+- (CGFloat)minimumLineSpacingForSectionAtIndex:(NSInteger)section {
+ return 0;
+}
+
+- (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
+
+- (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 {
+ return section == 0 && [self shouldShowPromoCell];
+}
+
+- (BOOL)shouldShowPromoCell {
+ return NO;
+}
+
+- (BOOL)isPromoActive {
+ return NO;
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698