blob: a878bbd0fe0070cc71c3b758e616b6a9d3322a33 [file] [log] [blame]
// Copyright 2017 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/search_widget_extension/search_widget_view.h"
#include "base/ios/ios_util.h"
#include "base/logging.h"
#import "ios/chrome/search_widget_extension/ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
const CGFloat kContentMargin = 16;
const CGFloat kURLButtonMargin = 10;
const CGFloat kActionButtonSize = 55;
const CGFloat kIconSize = 35;
const CGFloat kMaxContentSize = 421;
const CGFloat kIconSpacing = 5;
} // namespace
@interface SearchWidgetView ()
// The target for actions in the view.
@property(nonatomic, weak) id<SearchWidgetViewActionTarget> target;
// The copied URL label containing the URL or a placeholder text.
@property(nonatomic, strong) UILabel* copiedURLLabel;
// The copued URL title label containing the title of the copied URL button.
@property(nonatomic, strong) UILabel* openCopiedURLTitleLabel;
// The hairline view shown between the action and copied URL views.
@property(nonatomic, strong) UIView* hairlineView;
// The button shown when there is a copied URL to open.
@property(nonatomic, strong) UIButton* copiedButtonView;
// The primary effect view of the widget. Add views here for a more opaque
// appearance.
@property(nonatomic, strong) UIVisualEffectView* primaryEffectView;
// The secondary effect view of the widget. Add views here for a more
// transparent appearance.
@property(nonatomic, strong) UIVisualEffectView* secondaryEffectView;
// The constraints to be activated when the copiedURL section is visible.
@property(nonatomic, strong)
NSArray<NSLayoutConstraint*>* visibleCopiedURLConstraints;
// The constraints to be activated when the copiedURL section is hidden.
@property(nonatomic, strong)
NSArray<NSLayoutConstraint*>* hiddenCopiedURLConstraints;
// Sets up the widget UI.
- (void)createUI;
// Creates the view for the action buttons.
- (UIView*)newActionsView;
// Creates the view for the copiedURL section.
- (void)initializeOpenCopiedURLSectionUsingTopAnchor:(NSLayoutAnchor*)topAnchor;
@end
@implementation SearchWidgetView
@synthesize target = _target;
@synthesize copiedURLVisible = _copiedURLVisible;
@synthesize copiedURLString = _copiedURLString;
@synthesize copiedURLLabel = _copiedURLLabel;
@synthesize openCopiedURLTitleLabel = _openCopiedURLTitleLabel;
@synthesize hairlineView = _hairlineView;
@synthesize copiedButtonView = _copiedButtonView;
@synthesize primaryEffectView = _primaryEffectView;
@synthesize secondaryEffectView = _secondaryEffectView;
@synthesize visibleCopiedURLConstraints = _visibleCopiedURLConstraints;
@synthesize hiddenCopiedURLConstraints = _hiddenCopiedURLConstraints;
- (instancetype)initWithActionTarget:(id<SearchWidgetViewActionTarget>)target
primaryVibrancyEffect:(UIVibrancyEffect*)primaryVibrancyEffect
secondaryVibrancyEffect:
(UIVibrancyEffect*)secondaryVibrancyEffect {
self = [super initWithFrame:CGRectZero];
if (self) {
DCHECK(target);
_target = target;
_primaryEffectView =
[[UIVisualEffectView alloc] initWithEffect:primaryVibrancyEffect];
_secondaryEffectView =
[[UIVisualEffectView alloc] initWithEffect:secondaryVibrancyEffect];
_copiedURLVisible = YES;
[self createUI];
[self updateCopiedURLUI];
}
return self;
}
#pragma mark - property overrides
- (void)setCopiedURLVisible:(BOOL)copiedURLVisible {
_copiedURLVisible = copiedURLVisible;
[self updateCopiedURLUI];
}
- (void)setCopiedURLString:(NSString*)copiedURL {
_copiedURLString = copiedURL;
[self updateCopiedURLUI];
}
#pragma mark - UI creation
- (void)createUI {
for (UIVisualEffectView* effectView in
@[ self.primaryEffectView, self.secondaryEffectView ]) {
[self addSubview:effectView];
effectView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint
activateConstraints:ui_util::CreateSameConstraints(self, effectView)];
}
UIView* actionsView = [self newActionsView];
[self initializeOpenCopiedURLSectionUsingTopAnchor:actionsView.bottomAnchor];
}
- (UIView*)newActionsView {
// The use of vibrancy effects requires that the icons and circular buttons be
// added to different parent views. This means that the constraints that
// position them cannot be activated until all of the buttons are added to the
// stack view that contains them, and that stack view itself is added to the
// view hierarchy. In order to manage this, |constraints| is passed into each
// invocation of the button creation method, so the constraints can be
// collected for activation once the view hierarchy is complete.
NSMutableArray<NSLayoutConstraint*>* constraints = [NSMutableArray array];
UIStackView* actionRow = [[UIStackView alloc] initWithArrangedSubviews:@[
[self newActionViewWithTitle:NSLocalizedString(@"IDS_IOS_NEW_SEARCH",
@"New Search")
imageName:@"quick_action_search"
actionSelector:@selector(openSearch:)
constraints:constraints],
[self newActionViewWithTitle:NSLocalizedString(@"IDS_IOS_INCOGNITO_SEARCH",
@"Incognito Search")
imageName:@"quick_action_incognito_search"
actionSelector:@selector(openIncognito:)
constraints:constraints],
[self newActionViewWithTitle:NSLocalizedString(@"IDS_IOS_VOICE_SEARCH",
@"Voice Search")
imageName:@"quick_action_voice_search"
actionSelector:@selector(openVoice:)
constraints:constraints],
[self newActionViewWithTitle:NSLocalizedString(@"IDS_IOS_SCAN_QR_CODE",
@"Scan QR Code")
imageName:@"quick_action_camera_search"
actionSelector:@selector(openQRCode:)
constraints:constraints],
]];
actionRow.axis = UILayoutConstraintAxisHorizontal;
actionRow.alignment = UIStackViewAlignmentTop;
actionRow.distribution = UIStackViewDistributionFillEqually;
actionRow.spacing = kIconSpacing;
actionRow.layoutMargins =
UIEdgeInsetsMake(0, kContentMargin, 0, kContentMargin);
actionRow.layoutMarginsRelativeArrangement = YES;
actionRow.translatesAutoresizingMaskIntoConstraints = NO;
[self.secondaryEffectView.contentView addSubview:actionRow];
// These constraints stretch the action row to the full width of the widget.
// Their priority is < UILayoutPriorityRequired so that they can break when
// the view is larger than kMaxContentSize.
NSLayoutConstraint* actionsLeftConstraint = [actionRow.leftAnchor
constraintEqualToAnchor:self.secondaryEffectView.leftAnchor];
actionsLeftConstraint.priority = UILayoutPriorityDefaultHigh;
NSLayoutConstraint* actionsRightConstraint = [actionRow.rightAnchor
constraintEqualToAnchor:self.secondaryEffectView.rightAnchor];
actionsRightConstraint.priority = UILayoutPriorityDefaultHigh;
// This constraint sets the top alignment for the action row. Its priority is
// < UILayoutPriorityRequired so that it can break in favor of the
// centerYAnchor rule (on the next line) when the copiedURL section is hidden.
NSLayoutConstraint* actionsTopConstraint = [actionRow.topAnchor
constraintEqualToAnchor:self.secondaryEffectView.topAnchor
constant:kContentMargin];
actionsTopConstraint.priority = UILayoutPriorityDefaultHigh;
self.hiddenCopiedURLConstraints = @[ [actionRow.centerYAnchor
constraintEqualToAnchor:self.secondaryEffectView.centerYAnchor] ];
[constraints addObjectsFromArray:@[
[actionRow.centerXAnchor
constraintEqualToAnchor:self.secondaryEffectView.centerXAnchor],
[actionRow.widthAnchor constraintLessThanOrEqualToConstant:kMaxContentSize],
actionsLeftConstraint,
actionsRightConstraint,
actionsTopConstraint,
]];
[NSLayoutConstraint activateConstraints:constraints];
return actionRow;
}
- (UIView*)newActionViewWithTitle:(NSString*)title
imageName:(NSString*)imageName
actionSelector:(SEL)actionSelector
constraints:
(NSMutableArray<NSLayoutConstraint*>*)constraints {
UIView* circleView = [[UIView alloc] initWithFrame:CGRectZero];
circleView.backgroundColor = base::ios::IsRunningOnIOS10OrLater()
? [UIColor colorWithWhite:0 alpha:0.05]
: [UIColor whiteColor];
circleView.layer.cornerRadius = kActionButtonSize / 2;
[constraints addObjectsFromArray:@[
[circleView.widthAnchor constraintEqualToConstant:kActionButtonSize],
[circleView.heightAnchor constraintEqualToConstant:kActionButtonSize]
]];
UILabel* labelView = [[UILabel alloc] initWithFrame:CGRectZero];
labelView.text = title;
labelView.numberOfLines = 0;
labelView.textAlignment = NSTextAlignmentCenter;
labelView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
labelView.isAccessibilityElement = NO;
[labelView
setContentCompressionResistancePriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisVertical];
UIStackView* stack =
[[UIStackView alloc] initWithArrangedSubviews:@[ circleView, labelView ]];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = kIconSpacing;
stack.alignment = UIStackViewAlignmentCenter;
stack.translatesAutoresizingMaskIntoConstraints = NO;
// A transparent button constrained to the same size as the stack is added to
// handle taps on the stack view.
UIButton* actionButton = [[UIButton alloc] initWithFrame:CGRectZero];
actionButton.backgroundColor = [UIColor clearColor];
[actionButton addTarget:self.target
action:actionSelector
forControlEvents:UIControlEventTouchUpInside];
actionButton.translatesAutoresizingMaskIntoConstraints = NO;
actionButton.accessibilityLabel = title;
[self addSubview:actionButton];
[constraints
addObjectsFromArray:ui_util::CreateSameConstraints(actionButton, stack)];
UIImage* iconImage = [UIImage imageNamed:imageName];
UIImageView* icon = [[UIImageView alloc] initWithImage:iconImage];
icon.translatesAutoresizingMaskIntoConstraints = NO;
[constraints addObjectsFromArray:@[
[icon.widthAnchor constraintEqualToConstant:kIconSize],
[icon.heightAnchor constraintEqualToConstant:kIconSize],
[icon.centerXAnchor constraintEqualToAnchor:circleView.centerXAnchor],
[icon.centerYAnchor constraintEqualToAnchor:circleView.centerYAnchor],
]];
if (base::ios::IsRunningOnIOS10OrLater()) {
[self.primaryEffectView.contentView addSubview:icon];
} else {
[self addSubview:icon];
}
return stack;
}
- (void)initializeOpenCopiedURLSectionUsingTopAnchor:
(NSLayoutAnchor*)topAnchor {
self.hairlineView = [[UIView alloc] initWithFrame:CGRectZero];
self.hairlineView.backgroundColor =
base::ios::IsRunningOnIOS10OrLater()
? [UIColor colorWithWhite:0 alpha:0.05]
: [UIColor whiteColor];
self.hairlineView.translatesAutoresizingMaskIntoConstraints = NO;
[self.secondaryEffectView.contentView addSubview:self.hairlineView];
self.copiedButtonView = [[UIButton alloc] initWithFrame:CGRectZero];
self.copiedButtonView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.05];
self.copiedButtonView.layer.cornerRadius = 5;
self.copiedButtonView.translatesAutoresizingMaskIntoConstraints = NO;
self.copiedButtonView.accessibilityLabel =
NSLocalizedString(@"IDS_IOS_OPEN_COPIED_LINK", nil);
[self.secondaryEffectView.contentView addSubview:self.copiedButtonView];
[self.copiedButtonView addTarget:self.target
action:@selector(openCopiedURL:)
forControlEvents:UIControlEventTouchUpInside];
self.openCopiedURLTitleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.openCopiedURLTitleLabel.textAlignment = NSTextAlignmentCenter;
self.openCopiedURLTitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.openCopiedURLTitleLabel.font =
[UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
[self.primaryEffectView.contentView addSubview:self.openCopiedURLTitleLabel];
self.copiedURLLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.copiedURLLabel.textAlignment = NSTextAlignmentCenter;
self.copiedURLLabel.font =
[UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
self.copiedURLLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.secondaryEffectView.contentView addSubview:self.copiedURLLabel];
self.visibleCopiedURLConstraints = @[
[self.hairlineView.topAnchor constraintEqualToAnchor:topAnchor
constant:kContentMargin],
[self.hairlineView.leftAnchor
constraintEqualToAnchor:self.secondaryEffectView.leftAnchor],
[self.hairlineView.rightAnchor
constraintEqualToAnchor:self.secondaryEffectView.rightAnchor],
[self.hairlineView.heightAnchor constraintEqualToConstant:0.5],
[self.copiedButtonView.centerXAnchor
constraintEqualToAnchor:self.secondaryEffectView.centerXAnchor],
[self.copiedButtonView.widthAnchor
constraintEqualToAnchor:self.secondaryEffectView.widthAnchor
constant:-2 * kContentMargin],
[self.copiedButtonView.topAnchor
constraintEqualToAnchor:self.hairlineView.bottomAnchor
constant:12],
[self.copiedButtonView.bottomAnchor
constraintEqualToAnchor:self.secondaryEffectView.bottomAnchor
constant:-kContentMargin],
[self.openCopiedURLTitleLabel.centerXAnchor
constraintEqualToAnchor:self.primaryEffectView.centerXAnchor],
[self.openCopiedURLTitleLabel.topAnchor
constraintEqualToAnchor:self.copiedButtonView.topAnchor
constant:kURLButtonMargin],
[self.openCopiedURLTitleLabel.widthAnchor
constraintEqualToAnchor:self.copiedButtonView.widthAnchor
constant:-kContentMargin * 2],
[self.copiedURLLabel.centerXAnchor
constraintEqualToAnchor:self.primaryEffectView.centerXAnchor],
[self.copiedURLLabel.topAnchor
constraintEqualToAnchor:self.openCopiedURLTitleLabel.bottomAnchor],
[self.copiedURLLabel.widthAnchor
constraintEqualToAnchor:self.openCopiedURLTitleLabel.widthAnchor],
[self.copiedURLLabel.bottomAnchor
constraintEqualToAnchor:self.copiedButtonView.bottomAnchor
constant:-kURLButtonMargin],
];
}
- (void)addTapAction:(SEL)action toView:(UIView*)view {
UIGestureRecognizer* tapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self.target action:action];
[view addGestureRecognizer:tapRecognizer];
}
- (void)updateCopiedURLUI {
// If the copiedURL section is not visible, hide all the copiedURL section
// views and activate the correct constraint set. If it is visible, show the
// views in function of whether there is or not a copied URL to show.
if (!self.copiedURLVisible) {
self.copiedURLLabel.hidden = YES;
self.openCopiedURLTitleLabel.hidden = YES;
self.hairlineView.hidden = YES;
self.copiedButtonView.hidden = YES;
[NSLayoutConstraint deactivateConstraints:self.visibleCopiedURLConstraints];
[NSLayoutConstraint activateConstraints:self.hiddenCopiedURLConstraints];
return;
}
self.copiedURLLabel.hidden = NO;
self.openCopiedURLTitleLabel.hidden = NO;
[NSLayoutConstraint deactivateConstraints:self.hiddenCopiedURLConstraints];
[NSLayoutConstraint activateConstraints:self.visibleCopiedURLConstraints];
if (self.copiedURLString) {
self.copiedButtonView.hidden = NO;
self.hairlineView.hidden = YES;
self.copiedURLLabel.text = self.copiedURLString;
self.copiedURLLabel.accessibilityLabel = self.copiedURLString;
self.openCopiedURLTitleLabel.alpha = 1;
self.openCopiedURLTitleLabel.text =
NSLocalizedString(@"IDS_IOS_OPEN_COPIED_LINK", nil);
self.openCopiedURLTitleLabel.isAccessibilityElement = NO;
self.copiedURLLabel.alpha = 1;
return;
}
self.copiedButtonView.hidden = YES;
self.hairlineView.hidden = NO;
self.copiedURLLabel.text =
NSLocalizedString(@"IDS_IOS_NO_COPIED_LINK_MESSAGE", nil);
self.copiedURLLabel.accessibilityLabel =
NSLocalizedString(@"IDS_IOS_NO_COPIED_LINK_MESSAGE", nil);
self.openCopiedURLTitleLabel.text =
NSLocalizedString(@"IDS_IOS_NO_COPIED_LINK_TITLE", nil);
self.openCopiedURLTitleLabel.accessibilityLabel =
NSLocalizedString(@"IDS_IOS_NO_COPIED_LINK_TITLE", nil);
self.openCopiedURLTitleLabel.isAccessibilityElement = YES;
if (base::ios::IsRunningOnIOS10OrLater()) {
self.copiedURLLabel.alpha = 0.5;
self.openCopiedURLTitleLabel.alpha = 0.5;
}
}
@end