Index: ios/chrome/browser/ui/contextual_search/contextual_search_promo_view.mm |
diff --git a/ios/chrome/browser/ui/contextual_search/contextual_search_promo_view.mm b/ios/chrome/browser/ui/contextual_search/contextual_search_promo_view.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a4e91b3ebf95f1bcfe984c584a8e258f67cee840 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/contextual_search/contextual_search_promo_view.mm |
@@ -0,0 +1,259 @@ |
+// Copyright 2014 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/contextual_search/contextual_search_promo_view.h" |
+ |
+#include "base/ios/weak_nsobject.h" |
+#import "base/mac/scoped_nsobject.h" |
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
+#import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.h" |
+#include "ios/chrome/browser/ui/uikit_ui_util.h" |
+#import "ios/chrome/browser/ui/util/label_link_controller.h" |
+#import "ios/chrome/common/material_timing.h" |
+#include "ios/chrome/common/string_util.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h" |
+#import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "url/gurl.h" |
+ |
+namespace { |
+const int kMargin = 16; |
+const int kSpaceBelowText = 32; |
+const int kButtonSeparator = 8; |
+const int kButtonHeight = 36; |
+const int kButtonMinWidth = 88; |
+const int kDividerHeight = 1; |
+ |
+const int kTextFontSize = 16; |
+const int kButtonFontSize = 14; |
+const CGFloat kTextColorGrayShade = 0.494; |
+const int kLinkColorRGB = 0x5D9AFF; |
+ |
+const CGFloat kLineSpace = 1.15; |
+ |
+// Animation timings. |
+const CGFloat kCloseDuration = ios::material::kDuration1; |
+} |
+ |
+@interface ContextualSearchPromoView () |
+@property(nonatomic, assign) id<ContextualSearchPromoViewDelegate> delegate; |
+@end |
+ |
+@implementation ContextualSearchPromoView { |
+ base::scoped_nsobject<LabelLinkController> _linkController; |
+ base::scoped_nsobject<NSMutableArray> _constraints; |
+} |
+ |
+@synthesize disabled = _disabled; |
+@synthesize delegate = _delegate; |
+ |
++ (BOOL)requiresConstraintBasedLayout { |
+ return YES; |
+} |
+ |
+- (instancetype)initWithFrame:(CGRect)frame |
+ delegate:(id<ContextualSearchPromoViewDelegate>)delegate { |
+ if (!(self = [super initWithFrame:frame])) |
+ return nil; |
+ |
+ self.delegate = delegate; |
+ self.backgroundColor = [UIColor colorWithWhite:0.93 alpha:1.0]; |
+ |
+ // Temp array to accumulate constraints as views are created. |
+ NSMutableArray* constraints = [NSMutableArray array]; |
+ |
+ // Initialize text label and its link controller. |
+ UILabel* text = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease]; |
+ base::WeakNSObject<ContextualSearchPromoView> weakSelf(self); |
+ _linkController.reset([[LabelLinkController alloc] |
+ initWithLabel:text |
+ action:^(const GURL& gurl) { |
+ [[weakSelf delegate] promoViewSettingsTapped]; |
+ }]); |
+ [_linkController setLinkColor:UIColorFromRGB(kLinkColorRGB)]; |
+ |
+ // Label is as wide as the content area of the view. |
+ [constraints addObject:[text.widthAnchor |
+ constraintEqualToAnchor:self.layoutMarginsGuide |
+ .widthAnchor]]; |
+ |
+ // Parse out link location from the label text. |
+ NSString* textString = |
+ l10n_util::GetNSString(IDS_IOS_CONTEXTUAL_SEARCH_SHORT_DESCRIPTION); |
+ NSRange linkRange; |
+ textString = ParseStringWithLink(textString, &linkRange); |
+ |
+ // Build style attributes for the label. |
+ NSMutableParagraphStyle* paragraphStyle = |
+ [[[NSMutableParagraphStyle alloc] init] autorelease]; |
+ [paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping]; |
+ [paragraphStyle setLineHeightMultiple:kLineSpace]; |
+ NSDictionary* attributes = @{ |
+ NSParagraphStyleAttributeName : paragraphStyle, |
+ NSFontAttributeName : |
+ [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:kTextFontSize], |
+ NSForegroundColorAttributeName : |
+ [UIColor colorWithWhite:kTextColorGrayShade alpha:1] |
+ }; |
+ |
+ // Create and assign attributed text to label. |
+ base::scoped_nsobject<NSMutableAttributedString> attributedText( |
+ [[NSMutableAttributedString alloc] initWithString:textString]); |
+ [attributedText setAttributes:attributes |
+ range:NSMakeRange(0, textString.length)]; |
+ text.attributedText = attributedText; |
+ [_linkController addLinkWithRange:linkRange |
+ url:GURL("contextualSearch://settings")]; |
+ text.numberOfLines = 0; |
+ |
+ UIFont* buttonFont = |
+ [[MDFRobotoFontLoader sharedInstance] mediumFontOfSize:kButtonFontSize]; |
+ |
+ // Create accept and decline buttons with dimensions defined by the |
+ // minimum height and width constants. |
+ MDCFlatButton* acceptButton = [[[MDCFlatButton alloc] init] autorelease]; |
+ acceptButton.hasOpaqueBackground = YES; |
+ acceptButton.inkColor = |
+ [[[MDCPalette cr_bluePalette] tint300] colorWithAlphaComponent:0.5f]; |
+ [acceptButton setBackgroundColor:[[MDCPalette cr_bluePalette] tint500] |
+ forState:UIControlStateNormal]; |
+ [acceptButton setBackgroundColor:[UIColor colorWithWhite:0.6f alpha:1.0f] |
+ forState:UIControlStateDisabled]; |
+ [constraints addObjectsFromArray:@[ |
+ [acceptButton.widthAnchor |
+ constraintGreaterThanOrEqualToConstant:kButtonMinWidth], |
+ [acceptButton.heightAnchor constraintEqualToConstant:kButtonHeight] |
+ ]]; |
+ [acceptButton |
+ setTitle:l10n_util::GetNSString(IDS_IOS_CONTEXTUAL_SEARCH_GOT_IT_BUTTON) |
+ forState:UIControlStateNormal]; |
+ acceptButton.customTitleColor = [UIColor whiteColor]; |
+ [acceptButton addTarget:_delegate |
+ action:@selector(promoViewAcceptTapped) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ [acceptButton.titleLabel setFont:buttonFont]; |
+ |
+ UIColor* customTitleColor = [[MDCPalette cr_bluePalette] tint500]; |
+ MDCButton* declineButton = [[[MDCFlatButton alloc] init] autorelease]; |
+ [constraints addObjectsFromArray:@[ |
+ [declineButton.widthAnchor |
+ constraintGreaterThanOrEqualToConstant:kButtonMinWidth], |
+ [declineButton.heightAnchor constraintEqualToConstant:kButtonHeight] |
+ ]]; |
+ [declineButton setTitleColor:customTitleColor forState:UIControlStateNormal]; |
+ [declineButton |
+ setTitle:l10n_util::GetNSString(IDS_IOS_CONTEXTUAL_SEARCH_PROMO_OPTOUT) |
+ forState:UIControlStateNormal]; |
+ [declineButton addTarget:_delegate |
+ action:@selector(promoViewDeclineTapped) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ [declineButton.titleLabel setFont:buttonFont]; |
+ |
+ // Create the divider (a simple colored view) with height defined by |
+ // |kDividerHeight| and width spanning this view's width. |
+ UIView* divider = [[[UIView alloc] initWithFrame:CGRectZero] autorelease]; |
+ divider.backgroundColor = [UIColor colorWithWhite:0.745 alpha:1.0]; |
+ [constraints addObjectsFromArray:@[ |
+ [divider.widthAnchor constraintEqualToAnchor:self.widthAnchor], |
+ [divider.heightAnchor constraintEqualToConstant:kDividerHeight], |
+ ]]; |
+ |
+ self.translatesAutoresizingMaskIntoConstraints = NO; |
+ text.translatesAutoresizingMaskIntoConstraints = NO; |
+ acceptButton.translatesAutoresizingMaskIntoConstraints = NO; |
+ declineButton.translatesAutoresizingMaskIntoConstraints = NO; |
+ divider.translatesAutoresizingMaskIntoConstraints = NO; |
+ |
+ [self addSubview:text]; |
+ [self addSubview:acceptButton]; |
+ [self addSubview:declineButton]; |
+ [self addSubview:divider]; |
+ |
+ self.layoutMargins = UIEdgeInsetsMake(kMargin, kMargin, kMargin, kMargin); |
+ |
+ // Position all of the subviews |
+ [constraints addObjectsFromArray:@[ |
+ // Text is at the top of the content area, centered horizontally. |
+ [text.topAnchor constraintEqualToAnchor:self.layoutMarginsGuide.topAnchor], |
+ [text.centerXAnchor |
+ constraintEqualToAnchor:self.layoutMarginsGuide.centerXAnchor], |
+ // Accept button is |kSpaceBelowText| below the text, and at the bottom of |
+ // the content area. |
+ [acceptButton.topAnchor constraintEqualToAnchor:text.bottomAnchor |
+ constant:kSpaceBelowText], |
+ [acceptButton.bottomAnchor |
+ constraintEqualToAnchor:self.layoutMarginsGuide.bottomAnchor], |
+ // Decline button's top is aligned with the accept button's |
+ [declineButton.topAnchor constraintEqualToAnchor:acceptButton.topAnchor], |
+ // Decline button is to the leading side of the accept button, which |
+ // abuts the trailing edge of the content area. |
+ [acceptButton.leadingAnchor |
+ constraintEqualToAnchor:declineButton.trailingAnchor |
+ constant:kButtonSeparator], |
+ [acceptButton.trailingAnchor |
+ constraintEqualToAnchor:self.layoutMarginsGuide.trailingAnchor], |
+ // Divider is centered horizontally at the bottom of the view. |
+ [divider.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], |
+ [divider.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], |
+ ]]; |
+ |
+ [NSLayoutConstraint activateConstraints:constraints]; |
+ _constraints.reset([constraints retain]); |
+ |
+ return self; |
+} |
+ |
+- (void)dealloc { |
+ // Fix for crbug.com/591043 -- Some constraints applying to autoreleased |
+ // views may remain active with pointers to the to-be-deallocated views |
+ // after the views are no longer being displayed. This ensures that all |
+ // constraints are disabled and will not be applied even if some subviews |
+ // are lingering. |
+ [NSLayoutConstraint deactivateConstraints:_constraints]; |
+ [super dealloc]; |
+} |
+ |
+- (void)setHidden:(BOOL)hidden { |
+ if (!hidden && self.disabled) |
+ return; |
+ [self setClosure:hidden ? 0.0 : 1.0]; |
+ [super setHidden:hidden]; |
+} |
+ |
+- (void)setClosure:(CGFloat)closure { |
+ self.transform = CGAffineTransformMakeScale(1.0, closure); |
+} |
+ |
+- (void)closeAnimated:(BOOL)animated { |
+ [UIView cr_animateWithDuration:animated ? kCloseDuration : 0 |
+ delay:0 |
+ curve:ios::material::CurveEaseOut |
+ options:UIViewAnimationOptionBeginFromCurrentState |
+ animations:^{ |
+ self.hidden = YES; |
+ } |
+ completion:nil]; |
+} |
+ |
+#pragma mark - ContextualSearchPanelMotionObserver |
+ |
+- (void)panel:(ContextualSearchPanelView*)panel |
+ didChangeToState:(ContextualSearch::PanelState)toState |
+ fromState:(ContextualSearch::PanelState)fromState { |
+ if (self.disabled) |
+ return; |
+ if (toState == ContextualSearch::COVERING) { |
+ [self closeAnimated:YES]; |
+ } else if (fromState == ContextualSearch::COVERING) { |
+ self.hidden = NO; |
+ } |
+} |
+ |
+- (void)panelWillPromote:(ContextualSearchPanelView*)panel { |
+ _delegate = nil; |
+ [panel removeMotionObserver:self]; |
+} |
+ |
+@end |