Index: ios/chrome/browser/ui/bookmarks/bookmark_collection_cells.mm |
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_collection_cells.mm b/ios/chrome/browser/ui/bookmarks/bookmark_collection_cells.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..9d7be019d491fa4bba3181fba799d738a9284362 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_collection_cells.mm |
@@ -0,0 +1,550 @@ |
+// 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_cells.h" |
+ |
+#import <QuartzCore/QuartzCore.h> |
+ |
+#include "base/logging.h" |
+#include "base/mac/objc_property_releaser.h" |
+#include "base/mac/scoped_nsobject.h" |
+#import "ios/chrome/browser/ui/bookmarks/bookmark_extended_button.h" |
+#import "ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.h" |
+#import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h" |
+#import "ios/chrome/browser/ui/rtl_geometry.h" |
+#import "ios/chrome/browser/ui/uikit_ui_util.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#include "ios/chrome/grit/ios_theme_resources.h" |
+#import "ios/third_party/material_components_ios/src/components/Ink/src/MaterialInk.h" |
+#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h" |
+#include "ui/base/l10n/l10n_util_mac.h" |
+#include "ui/base/resource/resource_bundle.h" |
+#import "ui/gfx/ios/NSString+CrStringDrawing.h" |
+#include "url/gurl.h" |
+ |
+namespace { |
+const CGFloat kBookmarkItemCellDefaultImageSize = 40.0; |
+const CGFloat kBookmarkFolderCellDefaultImageSize = 24.0; |
+} // namespace |
+ |
+@interface BookmarkCell () { |
+ @protected |
+ // Subclasses should set these in the constructor with the wanted values. |
+ CGFloat _imageSize; |
+ |
+ @private |
+ base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkCell; |
+} |
+// Redefined to be read-write. |
+@property(nonatomic, retain) UILabel* titleLabel; |
+// Redefined to readwrite. |
+@property(nonatomic, retain) UIImageView* imageView; |
+// Label to show placeholder text when there is no image displayed. |
+@property(nonatomic, retain) UILabel* placeholderLabel; |
+// When the cell is selected for editing, a cover is shown with a blue color. |
+// Subclasses should insert new views below this view. |
+@property(nonatomic, retain) UIView* highlightCover; |
+// Lists the accessibility elements that are to be seen by UIAccessibility. |
+@property(nonatomic, readonly) NSMutableArray* accessibilityElements; |
+// Location of the last touch on the cell. |
+@property(nonatomic, assign) CGPoint touchLocation; |
+// The view doing the highlight animation. Only set while the cell is |
+// highlighted. |
+@property(nonatomic, retain) MDCInkView* touchFeedbackView; |
+@property(nonatomic, retain) BookmarkExtendedButton* button; |
+@property(nonatomic, assign) SEL buttonAction; |
+@property(nonatomic, assign) id buttonTarget; |
+@end |
+ |
+@implementation BookmarkCell |
+@synthesize accessibilityElements = _accessibilityElements; |
+@synthesize titleLabel = _titleLabel; |
+@synthesize imageView = _imageView; |
+@synthesize highlightCover = _highlightCover; |
+@synthesize shouldAnimateImageChanges = _shouldAnimateImageChanges; |
+@synthesize touchLocation = _touchLocation; |
+@synthesize touchFeedbackView = _touchFeedbackView; |
+@synthesize button = _button; |
+@synthesize buttonAction = _buttonAction; |
+@synthesize buttonTarget = _buttonTarget; |
+@synthesize placeholderLabel = _placeholderLabel; |
+ |
++ (NSString*)reuseIdentifier { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (instancetype)initWithFrame:(CGRect)frame { |
+ self = [super initWithFrame:frame]; |
+ if (self) { |
+ _propertyReleaser_BookmarkCell.Init(self, [BookmarkCell class]); |
+ self.exclusiveTouch = YES; |
+ self.backgroundColor = [UIColor whiteColor]; |
+ |
+ _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; |
+ _titleLabel.textColor = bookmark_utils_ios::darkTextColor(); |
+ self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO; |
+ [self.contentView addSubview:_titleLabel]; |
+ |
+ _imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; |
+ _imageView.clipsToBounds = YES; |
+ self.imageView.translatesAutoresizingMaskIntoConstraints = NO; |
+ [self.contentView addSubview:_imageView]; |
+ |
+ _placeholderLabel = [[UILabel alloc] init]; |
+ _placeholderLabel.textAlignment = NSTextAlignmentCenter; |
+ [self.contentView addSubview:_placeholderLabel]; |
+ |
+ _highlightCover = [[UIView alloc] initWithFrame:CGRectZero]; |
+ _highlightCover.backgroundColor = |
+ [bookmark_utils_ios::blueColor() colorWithAlphaComponent:0.7]; |
+ _highlightCover.hidden = YES; |
+ _highlightCover.translatesAutoresizingMaskIntoConstraints = NO; |
+ [self.contentView addSubview:_highlightCover]; |
+ |
+ self.button = base::scoped_nsobject<BookmarkExtendedButton>( |
+ [[BookmarkExtendedButton alloc] init]); |
+ self.button.contentMode = UIViewContentModeCenter; |
+ self.button.backgroundColor = [UIColor clearColor]; |
+ [self.button addTarget:self |
+ action:@selector(buttonTapped:) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ self.button.translatesAutoresizingMaskIntoConstraints = NO; |
+ [self.contentView insertSubview:self.button |
+ belowSubview:self.highlightCover]; |
+ |
+ _accessibilityElements = [[NSMutableArray alloc] init]; |
+ self.contentView.isAccessibilityElement = YES; |
+ self.contentView.accessibilityTraits |= UIAccessibilityTraitButton; |
+ [_accessibilityElements addObject:self.contentView]; |
+ [self.accessibilityElements addObject:self.button]; |
+ } |
+ return self; |
+} |
+ |
++ (BOOL)requiresConstraintBasedLayout { |
+ return YES; |
+} |
+ |
+- (void)updateConstraints { |
+ if (_imageSize) { |
+ // Create constraints. |
+ |
+ // Align all the views on the same horizontal line. |
+ AddSameCenterYConstraint(self.contentView, self.titleLabel); |
+ AddSameCenterYConstraint(self.contentView, self.imageView); |
+ AddSameCenterYConstraint(self.contentView, self.button); |
+ |
+ // clang-format off |
+ ApplyVisualConstraintsWithMetrics( |
+ @[ |
+ @"H:|-leadingImageMargin-[image(imageSize)]", |
+ @"H:|-leadingMargin-[title]-[button(buttonSize)]|", |
+ @"V:[image(imageSize)]", |
+ @"V:[button(buttonSize)]", |
+ @"H:|[highlight]|", |
+ @"V:|[highlight]|" |
+ ], |
+ @{ |
+ @"title" : self.titleLabel, |
+ @"image" : self.imageView, |
+ @"button" : self.button, |
+ @"highlight" : self.highlightCover |
+ }, |
+ @{ |
+ @"buttonSize" : [NSNumber numberWithFloat:32.0], |
+ @"leadingImageMargin" : [NSNumber numberWithFloat:16.0], |
+ @"leadingMargin" : [NSNumber numberWithFloat:64.0], |
+ @"imageSize" : [NSNumber numberWithFloat:_imageSize], |
+ }, |
+ self.contentView); |
+ // clang-format on |
+ } |
+ |
+ [NSLayoutConstraint activateConstraints:@[ |
+ [self.placeholderLabel.leadingAnchor |
+ constraintEqualToAnchor:self.imageView.leadingAnchor], |
+ [self.placeholderLabel.trailingAnchor |
+ constraintEqualToAnchor:self.imageView.trailingAnchor], |
+ [self.placeholderLabel.topAnchor |
+ constraintEqualToAnchor:self.imageView.topAnchor], |
+ [self.placeholderLabel.bottomAnchor |
+ constraintEqualToAnchor:self.imageView.bottomAnchor] |
+ ]]; |
+ self.placeholderLabel.translatesAutoresizingMaskIntoConstraints = NO; |
+ |
+ [super updateConstraints]; |
+} |
+ |
+- (void)setSelectedForEditing:(BOOL)selected animated:(BOOL)animated { |
+ self.highlightCover.hidden = !selected; |
+ if (selected) |
+ self.contentView.accessibilityTraits |= UIAccessibilityTraitSelected; |
+ else |
+ self.contentView.accessibilityTraits &= ~UIAccessibilityTraitSelected; |
+} |
+ |
+- (void)setImage:(UIImage*)image { |
+ self.imageView.image = image; |
+ self.placeholderLabel.hidden = YES; |
+ self.imageView.hidden = NO; |
+ |
+ if (!self.shouldAnimateImageChanges) |
+ return; |
+ |
+ CATransition* transition = [CATransition animation]; |
+ transition.duration = 0.2f; |
+ transition.timingFunction = [CAMediaTimingFunction |
+ functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; |
+ transition.type = kCATransitionFade; |
+ [self.imageView.layer addAnimation:transition forKey:nil]; |
+} |
+ |
+- (void)setPlaceholderText:(NSString*)text |
+ textColor:(UIColor*)textColor |
+ backgroundColor:(UIColor*)backgroundColor { |
+ self.placeholderLabel.hidden = NO; |
+ self.imageView.hidden = YES; |
+ |
+ self.placeholderLabel.backgroundColor = backgroundColor; |
+ self.placeholderLabel.textColor = textColor; |
+ self.placeholderLabel.text = text; |
+} |
+ |
+- (void)prepareForReuse { |
+ self.imageView.image = nil; |
+ self.imageView.backgroundColor = [UIColor whiteColor]; |
+ self.highlightCover.hidden = YES; |
+ self.shouldAnimateImageChanges = NO; |
+ [self.touchFeedbackView cancelAllAnimationsAnimated:NO]; |
+ [self.touchFeedbackView removeFromSuperview]; |
+ self.touchFeedbackView = nil; |
+ [self updateWithTitle:nil]; |
+ self.placeholderLabel.hidden = YES; |
+ self.imageView.hidden = NO; |
+ |
+ [super prepareForReuse]; |
+} |
+ |
+- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { |
+ UITouch* touch = [touches anyObject]; |
+ self.touchLocation = [touch locationInView:self]; |
+ [super touchesBegan:touches withEvent:event]; |
+} |
+ |
+- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { |
+ UITouch* touch = [touches anyObject]; |
+ self.touchLocation = [touch locationInView:self]; |
+ [super touchesEnded:touches withEvent:event]; |
+} |
+ |
+- (void)setHighlighted:(BOOL)highlighted { |
+ [super setHighlighted:highlighted]; |
+ |
+ // To prevent selection messing up with ink, immediately remove ink. |
+ [self.touchFeedbackView removeFromSuperview]; |
+ self.touchFeedbackView = nil; |
+ |
+ if (highlighted) { |
+ // Creates an ink feedback and animates it. |
+ base::scoped_nsobject<MDCInkView> touchFeedbackView( |
+ [[MDCInkView alloc] initWithFrame:self.bounds]); |
+ [self addSubview:touchFeedbackView]; |
+ self.touchFeedbackView = touchFeedbackView; |
+ [self.touchFeedbackView startTouchBeganAnimationAtPoint:self.touchLocation |
+ completion:nil]; |
+ } else { |
+ [self.touchFeedbackView startTouchEndedAnimationAtPoint:self.touchLocation |
+ completion:nil]; |
+ } |
+} |
+ |
+- (void)updateWithTitle:(NSString*)title { |
+ self.titleLabel.text = title; |
+ [self setNeedsLayout]; |
+ [self updateAccessibilityValues]; |
+} |
+ |
+#pragma mark - Button |
+- (void)setButtonTarget:(id)target action:(SEL)action { |
+ self.buttonTarget = target; |
+ self.buttonAction = action; |
+} |
+ |
+- (void)buttonTapped:(id)target { |
+ [self.buttonTarget performSelector:self.buttonAction |
+ withObject:self |
+ withObject:target]; |
+} |
+ |
+- (void)showButtonOfType:(bookmark_cell::ButtonType)buttonType |
+ animated:(BOOL)animated { |
+ switch (buttonType) { |
+ case bookmark_cell::ButtonNone: |
+ [self removeButtonAnimated:animated]; |
+ break; |
+ |
+ case bookmark_cell::ButtonMenu: { |
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
+ UIImage* bgImage = |
+ rb.GetNativeImageNamed(IDR_IOS_TOOLBAR_LIGHT_TOOLS).ToUIImage(); |
+ [self addButtonWithImage:bgImage animated:animated]; |
+ break; |
+ } |
+ } |
+} |
+ |
+- (void)addButtonWithImage:(UIImage*)image animated:(BOOL)animated { |
+ // Set the image. |
+ [self.button setImage:image forState:UIControlStateNormal]; |
+ |
+ // Animates the button in. |
+ [UIView animateWithDuration:animated ? 0.15 : 0 |
+ delay:0 |
+ options:UIViewAnimationOptionBeginFromCurrentState |
+ animations:^{ |
+ self.button.alpha = 1.0; |
+ } |
+ completion:^(BOOL finished) { |
+ if (finished) { |
+ // If this method is called twice in succession, the button should |
+ // only be added once to |self.accessibilityElements|. |
+ if (![self.accessibilityElements containsObject:self.button]) |
+ [self.accessibilityElements addObject:self.button]; |
+ } |
+ }]; |
+} |
+ |
+- (void)removeButtonAnimated:(BOOL)animated { |
+ if (!self.button) |
+ return; |
+ |
+ [UIView animateWithDuration:animated ? 0.15 : 0 |
+ delay:0 |
+ options:UIViewAnimationOptionBeginFromCurrentState |
+ animations:^{ |
+ self.button.alpha = 0.0; |
+ } |
+ completion:^(BOOL finished) { |
+ if (finished) { |
+ [self.accessibilityElements removeObject:self.button]; |
+ } |
+ }]; |
+} |
+ |
+#pragma mark - Accessibility |
+ |
+- (void)updateAccessibilityValues { |
+ self.button.accessibilityIdentifier = |
+ [NSString stringWithFormat:@"%@ %@", self.titleLabel.text, @"Info"]; |
+ self.button.accessibilityLabel = l10n_util::GetNSString( |
+ IDS_IOS_BOOKMARK_NEW_MORE_BUTTON_ACCESSIBILITY_LABEL); |
+} |
+ |
+- (NSInteger)accessibilityElementCount { |
+ return [self.accessibilityElements count]; |
+} |
+ |
+- (id)accessibilityElementAtIndex:(NSInteger)index { |
+ return [self.accessibilityElements objectAtIndex:index]; |
+} |
+ |
+- (NSInteger)indexOfAccessibilityElement:(id)element { |
+ return [self.accessibilityElements indexOfObject:element]; |
+} |
+ |
+@end |
+ |
+#pragma mark - BookmarkItemCell |
+ |
+@interface BookmarkItemCell () { |
+ base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkItemCell; |
+} |
+@end |
+ |
+@implementation BookmarkItemCell |
+ |
++ (NSString*)reuseIdentifier { |
+ return @"BookmarkItemCell"; |
+} |
+ |
++ (CGFloat)preferredImageSize { |
+ return kBookmarkItemCellDefaultImageSize; |
+} |
+ |
+- (instancetype)initWithFrame:(CGRect)frame { |
+ self = [super initWithFrame:frame]; |
+ if (self) { |
+ _propertyReleaser_BookmarkItemCell.Init(self, [BookmarkItemCell class]); |
+ |
+ // Set the non-layout properties of the titles. |
+ UIFont* font = [MDCTypography subheadFont]; |
+ self.titleLabel.font = font; |
+ self.titleLabel.backgroundColor = [UIColor clearColor]; |
+ self.titleLabel.numberOfLines = 1; |
+ |
+ _imageSize = kBookmarkItemCellDefaultImageSize; |
+ } |
+ return self; |
+} |
+ |
+- (void)updateWithTitle:(NSString*)title { |
+ [super updateWithTitle:title]; |
+ [self updateAccessibilityValues]; |
+} |
+ |
+- (void)updateAccessibilityValues { |
+ [super updateAccessibilityValues]; |
+ self.contentView.accessibilityLabel = |
+ [NSString stringWithFormat:@"%@", self.titleLabel.text]; |
+ self.contentView.accessibilityIdentifier = self.titleLabel.text; |
+} |
+ |
+- (void)prepareForReuse { |
+ [super prepareForReuse]; |
+ [self updateAccessibilityValues]; |
+} |
+ |
+@end |
+ |
+#pragma mark - BookmarkFolderCell |
+ |
+@interface BookmarkFolderCell () { |
+ base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkFolderCell; |
+} |
+ |
+@end |
+ |
+@implementation BookmarkFolderCell |
+ |
++ (NSString*)reuseIdentifier { |
+ return @"BookmarkFolderCell"; |
+} |
+ |
+- (instancetype)initWithFrame:(CGRect)frame { |
+ self = [super initWithFrame:frame]; |
+ if (self) { |
+ _propertyReleaser_BookmarkFolderCell.Init(self, [BookmarkFolderCell class]); |
+ |
+ self.imageView.image = [UIImage imageNamed:@"bookmark_gray_folder"]; |
+ self.titleLabel.font = [MDCTypography subheadFont]; |
+ |
+ _imageSize = kBookmarkFolderCellDefaultImageSize; |
+ } |
+ return self; |
+} |
+ |
+- (void)prepareForReuse { |
+ [super prepareForReuse]; |
+ [self setImage:[UIImage imageNamed:@"bookmark_gray_folder"]]; |
+} |
+ |
+- (void)updateWithTitle:(NSString*)title { |
+ [super updateWithTitle:title]; |
+ |
+ self.contentView.accessibilityLabel = title; |
+ self.contentView.accessibilityIdentifier = title; |
+} |
+ |
+@end |
+ |
+#pragma mark - BookmarkHeaderView |
+ |
+@interface BookmarkHeaderView () { |
+ base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkHeaderView; |
+} |
+@property(nonatomic, retain) UILabel* titleLabel; |
+@end |
+ |
+@implementation BookmarkHeaderView |
+@synthesize titleLabel = _titleLabel; |
++ (NSString*)reuseIdentifier { |
+ return @"BookmarkHeaderView"; |
+} |
+ |
++ (CGFloat)handsetHeight { |
+ return 47.0; |
+} |
+ |
+- (instancetype)initWithFrame:(CGRect)frame { |
+ self = [super initWithFrame:frame]; |
+ if (self) { |
+ _propertyReleaser_BookmarkHeaderView.Init(self, [BookmarkHeaderView class]); |
+ base::scoped_nsobject<UILabel> titleLabel( |
+ [[UILabel alloc] initWithFrame:CGRectZero]); |
+ self.titleLabel = titleLabel; |
+ UIFont* font = [MDCTypography body2Font]; |
+ self.titleLabel.font = font; |
+ self.titleLabel.textColor = bookmark_utils_ios::lightTextColor(); |
+ self.titleLabel.backgroundColor = [UIColor clearColor]; |
+ self.titleLabel.accessibilityTraits |= UIAccessibilityTraitHeader; |
+ [self addSubview:self.titleLabel]; |
+ } |
+ return self; |
+} |
+ |
+- (void)layoutSubviews { |
+ [super layoutSubviews]; |
+ |
+ // On handsets the title is aligned with the icon in the navigation bar. |
+ CGFloat margin = 16.0; |
+ if (IsIPadIdiom()) |
+ margin = 24.0; // On tablets the alignment is the same as the tiles. |
+ self.titleLabel.frame = |
+ CGRectMake(margin, 0, self.bounds.size.width - margin * 2.0, |
+ self.bounds.size.height); |
+} |
+ |
+- (void)setTitle:(NSString*)title { |
+ self.titleLabel.text = title; |
+} |
+ |
+- (void)updateLayoutWithLeftMargin:(CGFloat)leftMargin { |
+ self.titleLabel.frame = |
+ CGRectMake(leftMargin, 0, self.bounds.size.width - leftMargin, |
+ self.bounds.size.height); |
+} |
+ |
+@end |
+ |
+#pragma mark - BookmarkHeaderSeparatorView |
+ |
+@interface BookmarkHeaderSeparatorView () { |
+ base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkHeaderSeparatorView; |
+} |
+// The bottom separator line. |
+@property(nonatomic, retain) UIView* lineView; |
+@end |
+ |
+@implementation BookmarkHeaderSeparatorView |
+ |
+@synthesize lineView = _lineView; |
+ |
++ (NSString*)reuseIdentifier { |
+ return NSStringFromClass(self); |
+} |
+ |
++ (CGFloat)preferredHeight { |
+ return 16; |
+} |
+ |
+- (instancetype)initWithFrame:(CGRect)frame { |
+ self = [super initWithFrame:frame]; |
+ if (self) { |
+ _propertyReleaser_BookmarkHeaderSeparatorView.Init( |
+ self, [BookmarkHeaderSeparatorView class]); |
+ _lineView = [[UIView alloc] init]; |
+ _lineView.backgroundColor = bookmark_utils_ios::separatorColor(); |
+ [self addSubview:_lineView]; |
+ } |
+ return self; |
+} |
+ |
+- (void)layoutSubviews { |
+ CGFloat lineHeight = 1; |
+ CGFloat lineY = self.bounds.size.height - lineHeight - 8; |
+ _lineView.frame = CGRectMake(0, lineY, self.bounds.size.width, lineHeight); |
+} |
+ |
+@end |