| // Copyright 2018 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/popup_menu/cells/popup_menu_tools_item.h" |
| |
| #include <stdlib.h> |
| |
| #include "base/logging.h" |
| #import "ios/chrome/browser/ui/popup_menu/public/popup_menu_ui_constants.h" |
| #import "ios/chrome/browser/ui/reading_list/number_badge_view.h" |
| #import "ios/chrome/browser/ui/reading_list/text_badge_view.h" |
| #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h" |
| #import "ios/chrome/browser/ui/util/uikit_ui_util.h" |
| #import "ios/chrome/common/material_timing.h" |
| #import "ios/chrome/common/ui_util/constraints_ui_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| const int kEnabledDefaultColor = 0x1A73E8; |
| const int kEnabledDestructiveColor = 0xEA4334; |
| const CGFloat kImageLength = 28; |
| const CGFloat kCellHeight = 44; |
| const CGFloat kInnerMargin = 11; |
| const CGFloat kMargin = 15; |
| const CGFloat kTopMargin = 8; |
| const CGFloat kMaxHeight = 100; |
| NSString* const kToolsMenuTextBadgeAccessibilityIdentifier = |
| @"kToolsMenuTextBadgeAccessibilityIdentifier"; |
| } // namespace |
| |
| @implementation PopupMenuToolsItem |
| |
| @synthesize actionIdentifier = _actionIdentifier; |
| @synthesize badgeNumber = _badgeNumber; |
| @synthesize badgeText = _badgeText; |
| @synthesize image = _image; |
| @synthesize title = _title; |
| @synthesize enabled = _enabled; |
| @synthesize destructiveAction = _destructiveAction; |
| |
| - (instancetype)initWithType:(NSInteger)type { |
| self = [super initWithType:type]; |
| if (self) { |
| self.cellClass = [PopupMenuToolsCell class]; |
| _enabled = YES; |
| } |
| return self; |
| } |
| |
| - (void)configureCell:(PopupMenuToolsCell*)cell |
| withStyler:(ChromeTableViewStyler*)styler { |
| [super configureCell:cell withStyler:styler]; |
| cell.titleLabel.text = self.title; |
| cell.imageView.image = self.image; |
| cell.accessibilityTraits = UIAccessibilityTraitButton; |
| cell.userInteractionEnabled = self.enabled; |
| cell.destructiveAction = self.destructiveAction; |
| [cell setBadgeNumber:self.badgeNumber]; |
| [cell setBadgeText:self.badgeText]; |
| } |
| |
| #pragma mark - PopupMenuItem |
| |
| - (CGSize)cellSizeForWidth:(CGFloat)width { |
| // TODO(crbug.com/828357): This should be done at the table view level. |
| static PopupMenuToolsCell* cell; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ |
| cell = [[PopupMenuToolsCell alloc] init]; |
| [cell registerForContentSizeUpdates]; |
| }); |
| |
| [self configureCell:cell withStyler:[[ChromeTableViewStyler alloc] init]]; |
| cell.frame = CGRectMake(0, 0, width, kMaxHeight); |
| [cell setNeedsLayout]; |
| [cell layoutIfNeeded]; |
| return [cell systemLayoutSizeFittingSize:CGSizeMake(width, kMaxHeight)]; |
| } |
| |
| @end |
| |
| #pragma mark - PopupMenuToolsCell |
| |
| @interface PopupMenuToolsCell () |
| |
| // Title label for the cell, redefined as readwrite. |
| @property(nonatomic, strong, readwrite) UILabel* titleLabel; |
| // Image view for the cell, redefined as readwrite. |
| @property(nonatomic, strong, readwrite) UIImageView* imageView; |
| // Badge displaying a number. |
| @property(nonatomic, strong) NumberBadgeView* numberBadgeView; |
| // Badge displaying text. |
| @property(nonatomic, strong) TextBadgeView* textBadgeView; |
| // Constraints between the trailing of the label and the badges. |
| @property(nonatomic, strong) NSLayoutConstraint* titleToBadgeConstraint; |
| // Color for the title and the image. |
| @property(nonatomic, strong, readonly) UIColor* contentColor; |
| |
| @end |
| |
| @implementation PopupMenuToolsCell |
| |
| @synthesize imageView = _imageView; |
| @synthesize numberBadgeView = _numberBadgeView; |
| @synthesize textBadgeView = _textBadgeView; |
| @synthesize titleLabel = _titleLabel; |
| @synthesize titleToBadgeConstraint = _titleToBadgeConstraint; |
| @synthesize destructiveAction = _destructiveAction; |
| |
| - (instancetype)initWithStyle:(UITableViewCellStyle)style |
| reuseIdentifier:(NSString*)reuseIdentifier { |
| self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; |
| if (self) { |
| UIView* selectedBackgroundView = [[UIView alloc] init]; |
| selectedBackgroundView.backgroundColor = |
| [UIColor colorWithWhite:0 alpha:kSelectedItemBackgroundAlpha]; |
| self.selectedBackgroundView = selectedBackgroundView; |
| |
| _titleLabel = [[UILabel alloc] init]; |
| _titleLabel.numberOfLines = 0; |
| _titleLabel.font = [self titleFont]; |
| [_titleLabel |
| setContentCompressionResistancePriority:UILayoutPriorityDefaultLow |
| forAxis: |
| UILayoutConstraintAxisHorizontal]; |
| [_titleLabel setContentHuggingPriority:UILayoutPriorityDefaultLow - 1 |
| forAxis:UILayoutConstraintAxisHorizontal]; |
| // The compression resistance has to be higher priority than the minimal |
| // height constraint so it can increase the height of the cell to be |
| // displayed on multiple lines. |
| [_titleLabel |
| setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh |
| forAxis:UILayoutConstraintAxisVertical]; |
| _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; |
| _titleLabel.adjustsFontForContentSizeCategory = YES; |
| |
| _imageView = [[UIImageView alloc] init]; |
| _imageView.translatesAutoresizingMaskIntoConstraints = NO; |
| |
| _numberBadgeView = [[NumberBadgeView alloc] init]; |
| _numberBadgeView.translatesAutoresizingMaskIntoConstraints = NO; |
| |
| _textBadgeView = [[TextBadgeView alloc] initWithText:nil]; |
| _textBadgeView.translatesAutoresizingMaskIntoConstraints = NO; |
| _textBadgeView.accessibilityIdentifier = |
| kToolsMenuTextBadgeAccessibilityIdentifier; |
| _textBadgeView.hidden = YES; |
| |
| [self.contentView addSubview:_titleLabel]; |
| [self.contentView addSubview:_imageView]; |
| [self.contentView addSubview:_numberBadgeView]; |
| [self.contentView addSubview:_textBadgeView]; |
| |
| [NSLayoutConstraint activateConstraints:@[ |
| [_titleLabel.centerYAnchor |
| constraintEqualToAnchor:self.contentView.centerYAnchor], |
| // Align the center of image with the center of the capital letter of the |
| // first line of the title. |
| [_imageView.centerYAnchor |
| constraintEqualToAnchor:_titleLabel.firstBaselineAnchor |
| constant:-[self titleFont].capHeight / 2.0], |
| [_numberBadgeView.centerYAnchor |
| constraintEqualToAnchor:_imageView.centerYAnchor], |
| [_textBadgeView.centerYAnchor |
| constraintEqualToAnchor:_imageView.centerYAnchor], |
| [self.contentView.heightAnchor |
| constraintGreaterThanOrEqualToConstant:kCellHeight], |
| ]]; |
| ApplyVisualConstraintsWithMetrics( |
| @[ |
| @"H:|-(margin)-[image(imageLength)]-(innerMargin)-[label]", |
| @"H:[numberBadge]-(margin)-|", @"H:[textBadge]-(margin)-|", |
| @"V:|-(>=topMargin)-[label]-(>=topMargin)-|" |
| ], |
| @{ |
| @"image" : _imageView, |
| @"label" : _titleLabel, |
| @"numberBadge" : _numberBadgeView, |
| @"textBadge" : _textBadgeView |
| }, |
| @{ |
| @"margin" : @(kMargin), |
| @"innerMargin" : @(kInnerMargin), |
| @"topMargin" : @(kTopMargin), |
| @"imageLength" : @(kImageLength), |
| }); |
| |
| // The height constraint is used to have something as small as possible when |
| // calculating the size of the prototype cell. |
| NSLayoutConstraint* heightConstraint = |
| [self.contentView.heightAnchor constraintEqualToConstant:kCellHeight]; |
| heightConstraint.priority = UILayoutPriorityDefaultLow; |
| |
| NSLayoutConstraint* trailingEdge = [_titleLabel.trailingAnchor |
| constraintEqualToAnchor:self.contentView.trailingAnchor |
| constant:-kMargin]; |
| trailingEdge.priority = UILayoutPriorityDefaultHigh - 2; |
| [NSLayoutConstraint |
| activateConstraints:@[ trailingEdge, heightConstraint ]]; |
| |
| self.isAccessibilityElement = YES; |
| } |
| return self; |
| } |
| |
| - (void)setBadgeNumber:(NSInteger)badgeNumber { |
| BOOL wasHidden = self.numberBadgeView.hidden; |
| [self.numberBadgeView setNumber:badgeNumber animated:NO]; |
| // If the number badge is shown, then the text badge must be hidden. |
| if (!self.numberBadgeView.hidden && !self.textBadgeView.hidden) { |
| [self setBadgeText:nil]; |
| } |
| if (!self.numberBadgeView.hidden && wasHidden) { |
| self.titleToBadgeConstraint.active = NO; |
| self.titleToBadgeConstraint = [self.numberBadgeView.leadingAnchor |
| constraintGreaterThanOrEqualToAnchor:self.titleLabel.trailingAnchor |
| constant:kInnerMargin]; |
| self.titleToBadgeConstraint.active = YES; |
| } else if (self.numberBadgeView.hidden && !wasHidden) { |
| self.titleToBadgeConstraint.active = NO; |
| } |
| } |
| |
| - (void)setBadgeText:(NSString*)badgeText { |
| // Only 1 badge can be visible at a time, and the number badge takes priority. |
| if (badgeText && !self.numberBadgeView.isHidden) { |
| return; |
| } |
| |
| if (badgeText) { |
| [self.textBadgeView setText:badgeText]; |
| if (self.textBadgeView.hidden) { |
| self.textBadgeView.hidden = NO; |
| self.titleToBadgeConstraint.active = NO; |
| self.titleToBadgeConstraint = [self.textBadgeView.leadingAnchor |
| constraintGreaterThanOrEqualToAnchor:self.titleLabel.trailingAnchor |
| constant:kInnerMargin]; |
| self.titleToBadgeConstraint.active = YES; |
| self.textBadgeView.alpha = 1; |
| } |
| } else if (!self.textBadgeView.hidden) { |
| self.textBadgeView.hidden = YES; |
| self.titleToBadgeConstraint.active = NO; |
| } |
| } |
| |
| - (void)setDestructiveAction:(BOOL)destructiveAction { |
| _destructiveAction = destructiveAction; |
| if (self.userInteractionEnabled) { |
| self.titleLabel.textColor = self.contentColor; |
| self.imageView.tintColor = self.contentColor; |
| } |
| } |
| |
| - (UIColor*)contentColor { |
| if (self.destructiveAction) |
| return UIColorFromRGB(kEnabledDestructiveColor); |
| return UIColorFromRGB(kEnabledDefaultColor); |
| } |
| |
| - (void)registerForContentSizeUpdates { |
| // This is needed because if the cell is static (used for height), |
| // adjustsFontForContentSizeCategory isn't working. |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(preferredContentSizeDidChange:) |
| name:UIContentSizeCategoryDidChangeNotification |
| object:nil]; |
| } |
| |
| #pragma mark - UIView |
| |
| - (void)layoutSubviews { |
| [super layoutSubviews]; |
| |
| // Adjust the text label preferredMaxLayoutWidth when the parent's width |
| // changes, for instance on screen rotation. |
| CGFloat parentWidth = CGRectGetWidth(self.contentView.bounds); |
| |
| CGFloat trailingMargin = kMargin; |
| if (!self.numberBadgeView.hidden) { |
| trailingMargin += self.numberBadgeView.bounds.size.width + kInnerMargin; |
| } else if (!self.textBadgeView.hidden) { |
| trailingMargin += self.textBadgeView.bounds.size.width + kInnerMargin; |
| } |
| CGFloat leadingMargin = kMargin + kImageLength + kInnerMargin; |
| |
| self.titleLabel.preferredMaxLayoutWidth = |
| parentWidth - leadingMargin - trailingMargin; |
| |
| // Re-layout with the new preferred width to allow the label to adjust its |
| // height. |
| [super layoutSubviews]; |
| } |
| |
| #pragma mark - UITableViewCell |
| |
| - (void)prepareForReuse { |
| [super prepareForReuse]; |
| self.userInteractionEnabled = YES; |
| self.accessibilityTraits &= ~UIAccessibilityTraitNotEnabled; |
| } |
| |
| - (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled { |
| [super setUserInteractionEnabled:userInteractionEnabled]; |
| if (userInteractionEnabled) { |
| self.titleLabel.textColor = self.contentColor; |
| self.imageView.tintColor = self.contentColor; |
| } else { |
| self.titleLabel.textColor = [[self class] disabledColor]; |
| self.imageView.tintColor = [[self class] disabledColor]; |
| self.accessibilityTraits |= UIAccessibilityTraitNotEnabled; |
| } |
| } |
| |
| #pragma mark - Private |
| |
| // Callback when the preferred Content Size change. |
| - (void)preferredContentSizeDidChange:(NSNotification*)notification { |
| self.titleLabel.font = [self titleFont]; |
| } |
| |
| // Font to be used for the title. |
| - (UIFont*)titleFont { |
| return [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; |
| } |
| |
| // Returns the color of the disabled button's title. |
| + (UIColor*)disabledColor { |
| static UIColor* systemTintColorForDisabled = nil; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ |
| UIButton* button = [UIButton buttonWithType:UIButtonTypeSystem]; |
| systemTintColorForDisabled = |
| [button titleColorForState:UIControlStateDisabled]; |
| }); |
| return systemTintColorForDisabled; |
| } |
| |
| @end |