// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_cell.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/ui/settings/cells/settings_cells_constants.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util_mac.h"
namespace {
// Proportion of `textLayoutGuide` and `statusTextLabel`. This guarantees both
// of them at least occupies 20% of the cell.
const CGFloat kCellLabelsWidthProportion = 0.2f;
const CGFloat kInfoSymbolSize = 22;
} // namespace
@interface TableViewInfoButtonCell ()
// UILabel displayed at the trailing side of the view, it's trailing anchor
// align with the leading anchor of the `bubbleView` below. It shows the status
// of the setting's row. Mostly show On or Off, but there is use case that shows
// a search engine name. Corresponding to `statusText` from the item.
@property(nonatomic, strong) UILabel* statusTextLabel;
// Views for the leading icon.
@property(nonatomic, readonly, strong) UIImageView* iconImageView;
// Constraints that are used when the iconImageView is visible and hidden.
@property(nonatomic, strong) NSLayoutConstraint* iconVisibleConstraint;
@property(nonatomic, strong) NSLayoutConstraint* iconHiddenConstraint;
// Constraints used when the `trailingButton` is visible and hidden.
@property(nonatomic, strong)
NSLayoutConstraint* trailingButtonVisibleConstraint;
@property(nonatomic, strong) NSLayoutConstraint* trailingButtonHiddenConstraint;
// Constraints used based off of if `statusTextLabel` is visible.
@property(nonatomic, strong) NSArray* standardStatusTextLabelConstraints;
@property(nonatomic, strong) NSArray* accessibilityStatusTextLabelConstraints;
// Constraints that are used when the preferred content size is an
// "accessibility" category.
@property(nonatomic, strong) NSArray* accessibilityConstraints;
// Constraints that are used when the preferred content size is *not* an
// "accessibility" category.
@property(nonatomic, strong) NSArray* standardConstraints;
// Constraints that are used to set the top padding.
@property(nonatomic, strong) NSLayoutConstraint* topPaddingConstraint;
// Constraints that are used to set the bottom padding.
@property(nonatomic, strong) NSLayoutConstraint* bottomPaddingConstraint;
@end
@implementation TableViewInfoButtonCell {
UIView* _iconBackground;
}
@synthesize textLabel = _textLabel;
@synthesize detailTextLabel = _detailTextLabel;
- (instancetype)initWithStyle:(UITableViewCellStyle)style
reuseIdentifier:(NSString*)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.isAccessibilityElement = YES;
_isButtonSelectedForVoiceOver = YES;
_iconBackground = [[UIView alloc] init];
_iconBackground.translatesAutoresizingMaskIntoConstraints = NO;
_iconBackground.hidden = YES;
[self.contentView addSubview:_iconBackground];
_iconImageView = [[UIImageView alloc] init];
_iconImageView.translatesAutoresizingMaskIntoConstraints = NO;
_iconImageView.contentMode = UIViewContentModeCenter;
[_iconBackground addSubview:_iconImageView];
AddSameCenterConstraints(_iconBackground, _iconImageView);
UILayoutGuide* textLayoutGuide = [[UILayoutGuide alloc] init];
[self.contentView addLayoutGuide:textLayoutGuide];
_textLabel = [[UILabel alloc] init];
_textLabel.translatesAutoresizingMaskIntoConstraints = NO;
_textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
_textLabel.adjustsFontForContentSizeCategory = YES;
_textLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
_textLabel.numberOfLines = 0;
[self.contentView addSubview:_textLabel];
_detailTextLabel = [[UILabel alloc] init];
_detailTextLabel.translatesAutoresizingMaskIntoConstraints = NO;
_detailTextLabel.font =
[UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
_detailTextLabel.adjustsFontForContentSizeCategory = YES;
_detailTextLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
_detailTextLabel.numberOfLines = 0;
[self.contentView addSubview:_detailTextLabel];
_statusTextLabel = [[UILabel alloc] init];
_statusTextLabel.translatesAutoresizingMaskIntoConstraints = NO;
_statusTextLabel.font =
[UIFont preferredFontForTextStyle:UIFontTextStyleBody];
_statusTextLabel.adjustsFontForContentSizeCategory = YES;
_statusTextLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
[self.contentView addSubview:_statusTextLabel];
_trailingButton = [UIButton buttonWithType:UIButtonTypeSystem];
UIImage* infoImage =
DefaultSymbolTemplateWithPointSize(kInfoCircleSymbol, kInfoSymbolSize);
[_trailingButton setImage:infoImage forState:UIControlStateNormal];
_trailingButton.tintColor = [UIColor colorNamed:kBlueColor];
_trailingButton.translatesAutoresizingMaskIntoConstraints = NO;
[_trailingButton
setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh + 1
forAxis:
UILayoutConstraintAxisHorizontal];
_trailingButton.accessibilityIdentifier = kTableViewCellInfoButtonViewId;
[self.contentView addSubview:_trailingButton];
// Set up the constraint assuming that the button is hidden.
_trailingButtonVisibleConstraint = [textLayoutGuide.trailingAnchor
constraintLessThanOrEqualToAnchor:_trailingButton.leadingAnchor
constant:-kTableViewHorizontalSpacing];
_trailingButtonHiddenConstraint = [textLayoutGuide.trailingAnchor
constraintLessThanOrEqualToAnchor:self.contentView.trailingAnchor
constant:-kTableViewHorizontalSpacing];
// Set up the constraints assuming that the icon image is hidden.
_iconVisibleConstraint = [textLayoutGuide.leadingAnchor
constraintEqualToAnchor:_iconBackground.trailingAnchor
constant:kTableViewImagePadding];
_iconHiddenConstraint = [textLayoutGuide.leadingAnchor
constraintEqualToAnchor:self.contentView.leadingAnchor
constant:kTableViewHorizontalSpacing];
// Set the constranits of `textLabel` and `statusTextLabel` to make their
// width >= 20% of the cell to ensure both of them have a space.
NSLayoutConstraint* widthConstraintStatus = [_statusTextLabel.widthAnchor
constraintGreaterThanOrEqualToAnchor:self.contentView.widthAnchor
multiplier:kCellLabelsWidthProportion];
widthConstraintStatus.priority = UILayoutPriorityDefaultHigh + 1;
NSLayoutConstraint* widthConstraintLayoutGuide =
[textLayoutGuide.widthAnchor
constraintGreaterThanOrEqualToAnchor:self.contentView.widthAnchor
multiplier:kCellLabelsWidthProportion];
widthConstraintLayoutGuide.priority = UILayoutPriorityDefaultHigh + 1;
// Set the content hugging property to `statusTextLabel` to wrap the text
// and give other label more space.
[_statusTextLabel
setContentHuggingPriority:UILayoutPriorityDefaultHigh + 2
forAxis:UILayoutConstraintAxisHorizontal];
_standardConstraints = @[
[_trailingButton.centerYAnchor
constraintEqualToAnchor:self.contentView.centerYAnchor],
[_trailingButton.trailingAnchor
constraintEqualToAnchor:self.contentView.trailingAnchor
constant:-kTableViewHorizontalSpacing],
_trailingButtonVisibleConstraint,
[textLayoutGuide.centerYAnchor
constraintEqualToAnchor:self.contentView.centerYAnchor],
[textLayoutGuide.widthAnchor
constraintGreaterThanOrEqualToAnchor:self.contentView.widthAnchor
multiplier:kCellLabelsWidthProportion],
widthConstraintStatus,
widthConstraintLayoutGuide,
];
_accessibilityConstraints = @[
[_trailingButton.topAnchor
constraintEqualToAnchor:_statusTextLabel.bottomAnchor
constant:kTableViewLargeVerticalSpacing],
[_trailingButton.leadingAnchor
constraintEqualToAnchor:self.contentView.leadingAnchor
constant:kTableViewHorizontalSpacing],
[_trailingButton.bottomAnchor
constraintEqualToAnchor:self.contentView.bottomAnchor
constant:-kTableViewLargeVerticalSpacing],
_trailingButtonHiddenConstraint,
];
_topPaddingConstraint = [textLayoutGuide.topAnchor
constraintGreaterThanOrEqualToAnchor:self.contentView.topAnchor
constant:
kTableViewOneLabelCellVerticalSpacing];
_bottomPaddingConstraint = [self.contentView.bottomAnchor
constraintGreaterThanOrEqualToAnchor:textLayoutGuide.bottomAnchor
constant:
kTableViewOneLabelCellVerticalSpacing];
_standardStatusTextLabelConstraints = @[
[_statusTextLabel.centerYAnchor
constraintEqualToAnchor:self.contentView.centerYAnchor],
[_statusTextLabel.leadingAnchor
constraintGreaterThanOrEqualToAnchor:textLayoutGuide.trailingAnchor
constant:kTableViewHorizontalSpacing],
[_statusTextLabel.trailingAnchor
constraintEqualToAnchor:_trailingButton.leadingAnchor
constant:-kTableViewHorizontalSpacing]
];
_accessibilityStatusTextLabelConstraints = @[
[_statusTextLabel.topAnchor
constraintEqualToAnchor:textLayoutGuide.bottomAnchor
constant:kTableViewLargeVerticalSpacing],
[_statusTextLabel.leadingAnchor
constraintEqualToAnchor:self.contentView.leadingAnchor
constant:kTableViewHorizontalSpacing],
[_statusTextLabel.trailingAnchor
constraintEqualToAnchor:self.contentView.trailingAnchor
constant:-kTableViewHorizontalSpacing]
];
[NSLayoutConstraint activateConstraints:@[
[_iconBackground.leadingAnchor
constraintEqualToAnchor:self.contentView.leadingAnchor
constant:kTableViewHorizontalSpacing],
[_iconBackground.widthAnchor
constraintEqualToConstant:kTableViewIconImageSize],
[_iconBackground.heightAnchor
constraintEqualToAnchor:_iconBackground.widthAnchor],
[_iconBackground.centerYAnchor
constraintEqualToAnchor:textLayoutGuide.centerYAnchor],
_iconHiddenConstraint,
[textLayoutGuide.leadingAnchor
constraintEqualToAnchor:_textLabel.leadingAnchor],
[textLayoutGuide.leadingAnchor
constraintEqualToAnchor:_detailTextLabel.leadingAnchor],
[textLayoutGuide.trailingAnchor
constraintEqualToAnchor:_textLabel.trailingAnchor],
[textLayoutGuide.trailingAnchor
constraintEqualToAnchor:_detailTextLabel.trailingAnchor],
[textLayoutGuide.topAnchor constraintEqualToAnchor:_textLabel.topAnchor],
[textLayoutGuide.bottomAnchor
constraintEqualToAnchor:_detailTextLabel.bottomAnchor],
[_textLabel.bottomAnchor
constraintEqualToAnchor:_detailTextLabel.topAnchor],
_topPaddingConstraint,
_bottomPaddingConstraint,
]];
if (UIContentSizeCategoryIsAccessibilityCategory(
self.traitCollection.preferredContentSizeCategory)) {
[NSLayoutConstraint activateConstraints:_accessibilityConstraints];
} else {
[NSLayoutConstraint activateConstraints:_standardConstraints];
}
}
return self;
}
- (void)updatePaddingForDetailText:(BOOL)hasDetailText {
if (hasDetailText) {
self.topPaddingConstraint.constant = kTableViewTwoLabelsCellVerticalSpacing;
self.bottomPaddingConstraint.constant =
kTableViewTwoLabelsCellVerticalSpacing;
} else {
self.topPaddingConstraint.constant = kTableViewOneLabelCellVerticalSpacing;
self.bottomPaddingConstraint.constant =
kTableViewOneLabelCellVerticalSpacing;
}
}
- (void)setIconImage:(UIImage*)image
tintColor:(UIColor*)tintColor
backgroundColor:(UIColor*)backgroundColor
cornerRadius:(CGFloat)cornerRadius {
if (tintColor) {
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
self.iconImageView.image = image;
self.iconImageView.tintColor = tintColor;
_iconBackground.layer.cornerRadius = cornerRadius;
_iconBackground.backgroundColor = backgroundColor;
BOOL hidden = (image == nil);
_iconBackground.hidden = hidden;
if (hidden) {
self.iconVisibleConstraint.active = NO;
self.iconHiddenConstraint.active = YES;
} else {
self.iconHiddenConstraint.active = NO;
self.iconVisibleConstraint.active = YES;
}
}
- (void)hideUIButton:(BOOL)isHidden {
self.trailingButton.hidden = isHidden;
self.trailingButtonHiddenConstraint.active = NO;
if (isHidden) {
self.trailingButtonHiddenConstraint.active = YES;
self.trailingButtonVisibleConstraint.active = NO;
} else {
// In `standardConstraints`, `textLayoutGuide` is constrained to
// trailingButton.leadingAnchor. In `accessibilityConstraints`,
// trailingButton.leadingAnchor is constrained to contentView.leadingAnchor.
// Therefore, if accessibility features are used, we should not activate a
// constraint between `textLayoutGuide` and `trailingButton` since this
// would mean the textLayoutGuide would be positioned before the
// contentView.leadingAnchor.
BOOL accessibilityEnabled = UIContentSizeCategoryIsAccessibilityCategory(
self.traitCollection.preferredContentSizeCategory);
self.trailingButtonHiddenConstraint.active = accessibilityEnabled;
self.trailingButtonVisibleConstraint.active = !accessibilityEnabled;
}
}
- (void)setStatusText:(NSString*)statusText {
if (statusText) {
_statusTextLabel.text = statusText;
if (UIContentSizeCategoryIsAccessibilityCategory(
self.traitCollection.preferredContentSizeCategory)) {
[NSLayoutConstraint
deactivateConstraints:_standardStatusTextLabelConstraints];
[NSLayoutConstraint
activateConstraints:_accessibilityStatusTextLabelConstraints];
} else {
[NSLayoutConstraint
deactivateConstraints:_accessibilityStatusTextLabelConstraints];
[NSLayoutConstraint
activateConstraints:_standardStatusTextLabelConstraints];
}
}
}
#pragma mark - UIView
- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
BOOL isCurrentContentSizeAccessibility =
UIContentSizeCategoryIsAccessibilityCategory(
self.traitCollection.preferredContentSizeCategory);
if (UIContentSizeCategoryIsAccessibilityCategory(
previousTraitCollection.preferredContentSizeCategory) !=
isCurrentContentSizeAccessibility) {
if (isCurrentContentSizeAccessibility) {
[NSLayoutConstraint deactivateConstraints:_standardConstraints];
[NSLayoutConstraint activateConstraints:_accessibilityConstraints];
} else {
[NSLayoutConstraint deactivateConstraints:_accessibilityConstraints];
[NSLayoutConstraint activateConstraints:_standardConstraints];
}
}
}
#pragma mark - UITableViewCell
- (void)prepareForReuse {
[super prepareForReuse];
self.textLabel.text = nil;
self.detailTextLabel.text = nil;
self.statusTextLabel.text = nil;
self.trailingButton.tag = 0;
[self setIconImage:nil tintColor:nil backgroundColor:nil cornerRadius:0];
[_trailingButton removeTarget:nil
action:nil
forControlEvents:[_trailingButton allControlEvents]];
}
#pragma mark - UIAccessibility
- (CGPoint)accessibilityActivationPoint {
// Center the activation point over the info button, so that double-tapping
// triggers to show the popover if `isButtonSelectedForVoiceOver` is
// true.
if (!self.isButtonSelectedForVoiceOver) {
return self.center;
}
CGRect buttonFrame = UIAccessibilityConvertFrameToScreenCoordinates(
self.trailingButton.frame, self);
return CGPointMake(CGRectGetMidX(buttonFrame), CGRectGetMidY(buttonFrame));
}
- (NSString*)accessibilityHint {
if (self.customizedAccessibilityHint.length) {
return self.customizedAccessibilityHint;
}
return l10n_util::GetNSString(IDS_IOS_INFO_BUTTON_ACCESSIBILITY_HINT);
}
- (NSString*)accessibilityLabel {
if (!self.detailTextLabel.text) {
return self.textLabel.text;
}
return [NSString stringWithFormat:@"%@, %@", self.textLabel.text,
self.detailTextLabel.text];
}
- (NSString*)accessibilityValue {
return self.statusTextLabel.text;
}
@end