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 |