| Index: ios/chrome/search_widget_extension/search_widget_view.mm
|
| diff --git a/ios/chrome/search_widget_extension/search_widget_view.mm b/ios/chrome/search_widget_extension/search_widget_view.mm
|
| index 42a7f8944092839ca1b6516dc943f9907ab0ea27..e2cc85ad53b47a29a91ba9f81e6fa9cee69709ea 100644
|
| --- a/ios/chrome/search_widget_extension/search_widget_view.mm
|
| +++ b/ios/chrome/search_widget_extension/search_widget_view.mm
|
| @@ -3,8 +3,8 @@
|
| // found in the LICENSE file.
|
|
|
| #import "ios/chrome/search_widget_extension/search_widget_view.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."
|
| @@ -12,95 +12,358 @@
|
|
|
| namespace {
|
|
|
| -const CGFloat kCursorHeight = 40;
|
| -const CGFloat kCursorWidth = 2;
|
| -const CGFloat kCursorHorizontalPadding = 10;
|
| -const CGFloat kCursorVerticalPadding = 10;
|
| -const CGFloat kFakeboxHorizontalPadding = 40;
|
| -const CGFloat kFakeboxVerticalPadding = 40;
|
| +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 () {
|
| - __weak id<SearchWidgetViewActionTarget> _target;
|
| -}
|
| +@interface SearchWidgetView ()
|
|
|
| -@property(nonatomic, copy) NSString* copiedURL;
|
| +// 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;
|
| -@property(nonatomic, weak) UIView* cursor;
|
| +// 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 and adds a fake omnibox with blinking cursor to the view and sets the
|
| -// class cursor property.
|
| -- (void)addFakebox;
|
| +// Creates the view for the copiedURL section.
|
| +- (void)initializeOpenCopiedURLSectionUsingTopAnchor:(NSLayoutAnchor*)topAnchor;
|
|
|
| @end
|
|
|
| @implementation SearchWidgetView
|
|
|
| -@synthesize copiedURL = _copiedURL;
|
| +@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;
|
|
|
| -@synthesize cursor = _cursor;
|
| -
|
| -- (instancetype)initWithActionTarget:(id<SearchWidgetViewActionTarget>)target {
|
| +- (instancetype)initWithActionTarget:(id<SearchWidgetViewActionTarget>)target
|
| + primaryVibrancyEffect:(UIVibrancyEffect*)primaryVibrancyEffect
|
| + secondaryVibrancyEffect:
|
| + (UIVibrancyEffect*)secondaryVibrancyEffect {
|
| self = [super initWithFrame:CGRectZero];
|
| if (self) {
|
| DCHECK(target);
|
| _target = target;
|
| - [self addFakebox];
|
| + _primaryEffectView =
|
| + [[UIVisualEffectView alloc] initWithEffect:primaryVibrancyEffect];
|
| + _secondaryEffectView =
|
| + [[UIVisualEffectView alloc] initWithEffect:secondaryVibrancyEffect];
|
| + _copiedURLVisible = YES;
|
| + [self createUI];
|
| + [self updateCopiedURLUI];
|
| }
|
| return self;
|
| }
|
|
|
| -- (void)addFakebox {
|
| - UIView* fakebox = [[UIView alloc] initWithFrame:CGRectZero];
|
| +#pragma mark - property overrides
|
|
|
| - UIGestureRecognizer* tapRecognizer =
|
| - [[UITapGestureRecognizer alloc] initWithTarget:_target
|
| - action:@selector(openSearch:)];
|
| -
|
| - [fakebox addGestureRecognizer:tapRecognizer];
|
| - [self addSubview:fakebox];
|
| -
|
| - UIView* cursor = [[UIView alloc] initWithFrame:CGRectZero];
|
| - self.cursor = cursor;
|
| - self.cursor.backgroundColor = [UIColor blueColor];
|
| - [fakebox addSubview:self.cursor];
|
| -
|
| - [fakebox setTranslatesAutoresizingMaskIntoConstraints:NO];
|
| - [self.cursor setTranslatesAutoresizingMaskIntoConstraints:NO];
|
| - [NSLayoutConstraint activateConstraints:@[
|
| - [[fakebox leadingAnchor] constraintEqualToAnchor:self.leadingAnchor
|
| - constant:kFakeboxHorizontalPadding],
|
| - [[fakebox trailingAnchor]
|
| - constraintEqualToAnchor:self.trailingAnchor
|
| - constant:-kFakeboxHorizontalPadding],
|
| - [[fakebox topAnchor] constraintEqualToAnchor:self.topAnchor
|
| - constant:kFakeboxVerticalPadding],
|
| - [[fakebox heightAnchor]
|
| - constraintEqualToConstant:kCursorHeight + 2 * kCursorVerticalPadding],
|
| -
|
| - [[self.cursor widthAnchor] constraintEqualToConstant:kCursorWidth],
|
| - [[self.cursor leadingAnchor]
|
| - constraintEqualToAnchor:fakebox.leadingAnchor
|
| - constant:kCursorHorizontalPadding],
|
| - [[self.cursor heightAnchor] constraintEqualToConstant:kCursorHeight],
|
| - [[self.cursor centerYAnchor] constraintEqualToAnchor:fakebox.centerYAnchor]
|
| +- (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:@"New Search"
|
| + imageName:@"quick_action_search"
|
| + actionSelector:@selector(openSearch:)
|
| + constraints:constraints],
|
| + [self newActionViewWithTitle:@"Incognito Search"
|
| + imageName:@"quick_action_incognito_search"
|
| + actionSelector:@selector(openIncognito:)
|
| + constraints:constraints],
|
| + [self newActionViewWithTitle:@"Voice Search"
|
| + imageName:@"quick_action_voice_search"
|
| + actionSelector:@selector(openVoice:)
|
| + constraints:constraints],
|
| + [self newActionViewWithTitle:@"Scan QR/Bar 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 = [UIColor colorWithWhite:0 alpha:0.05];
|
| + 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
|
| + 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;
|
| + [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],
|
| ]];
|
| + [self.primaryEffectView.contentView addSubview:icon];
|
|
|
| - [UIView animateWithDuration:0.3
|
| - delay:0.0
|
| - options:UIViewAnimationOptionRepeat |
|
| - UIViewAnimationOptionAutoreverse
|
| - animations:^{
|
| - self.cursor.alpha = 0.0f;
|
| - }
|
| - completion:nil];
|
| + return stack;
|
| }
|
|
|
| -- (void)updateCopiedURL:(NSString*)copiedURL {
|
| - self.copiedURL = copiedURL;
|
| - self.copiedURLLabel.text = copiedURL;
|
| +- (void)initializeOpenCopiedURLSectionUsingTopAnchor:
|
| + (NSLayoutAnchor*)topAnchor {
|
| + self.hairlineView = [[UIView alloc] initWithFrame:CGRectZero];
|
| + self.hairlineView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.05];
|
| + 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.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.openCopiedURLTitleLabel.alpha = 1;
|
| + self.openCopiedURLTitleLabel.text = @"Open Copied Link";
|
| + self.copiedURLLabel.alpha = 1;
|
| + return;
|
| + }
|
| +
|
| + self.copiedButtonView.hidden = YES;
|
| + self.hairlineView.hidden = NO;
|
| + self.copiedURLLabel.text = @"Links you copy will appear here.";
|
| + self.openCopiedURLTitleLabel.alpha = 0.5;
|
| + self.openCopiedURLTitleLabel.text = @"No Copied Link";
|
| + self.copiedURLLabel.alpha = 0.5;
|
| +}
|
| @end
|
|
|