OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/contextual_search/contextual_search_promo_view.h" |
| 6 |
| 7 #include "base/ios/weak_nsobject.h" |
| 8 #import "base/mac/scoped_nsobject.h" |
| 9 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
| 10 #import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.h" |
| 11 #include "ios/chrome/browser/ui/uikit_ui_util.h" |
| 12 #import "ios/chrome/browser/ui/util/label_link_controller.h" |
| 13 #import "ios/chrome/common/material_timing.h" |
| 14 #include "ios/chrome/common/string_util.h" |
| 15 #include "ios/chrome/grit/ios_strings.h" |
| 16 #import "ios/third_party/material_components_ios/src/components/Buttons/src/Mate
rialButtons.h" |
| 17 #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoF
ontLoader.h" |
| 18 #include "ui/base/l10n/l10n_util.h" |
| 19 #include "url/gurl.h" |
| 20 |
| 21 namespace { |
| 22 const int kMargin = 16; |
| 23 const int kSpaceBelowText = 32; |
| 24 const int kButtonSeparator = 8; |
| 25 const int kButtonHeight = 36; |
| 26 const int kButtonMinWidth = 88; |
| 27 const int kDividerHeight = 1; |
| 28 |
| 29 const int kTextFontSize = 16; |
| 30 const int kButtonFontSize = 14; |
| 31 const CGFloat kTextColorGrayShade = 0.494; |
| 32 const int kLinkColorRGB = 0x5D9AFF; |
| 33 |
| 34 const CGFloat kLineSpace = 1.15; |
| 35 |
| 36 // Animation timings. |
| 37 const CGFloat kCloseDuration = ios::material::kDuration1; |
| 38 } |
| 39 |
| 40 @interface ContextualSearchPromoView () |
| 41 @property(nonatomic, assign) id<ContextualSearchPromoViewDelegate> delegate; |
| 42 @end |
| 43 |
| 44 @implementation ContextualSearchPromoView { |
| 45 base::scoped_nsobject<LabelLinkController> _linkController; |
| 46 base::scoped_nsobject<NSMutableArray> _constraints; |
| 47 } |
| 48 |
| 49 @synthesize disabled = _disabled; |
| 50 @synthesize delegate = _delegate; |
| 51 |
| 52 + (BOOL)requiresConstraintBasedLayout { |
| 53 return YES; |
| 54 } |
| 55 |
| 56 - (instancetype)initWithFrame:(CGRect)frame |
| 57 delegate:(id<ContextualSearchPromoViewDelegate>)delegate { |
| 58 if (!(self = [super initWithFrame:frame])) |
| 59 return nil; |
| 60 |
| 61 self.delegate = delegate; |
| 62 self.backgroundColor = [UIColor colorWithWhite:0.93 alpha:1.0]; |
| 63 |
| 64 // Temp array to accumulate constraints as views are created. |
| 65 NSMutableArray* constraints = [NSMutableArray array]; |
| 66 |
| 67 // Initialize text label and its link controller. |
| 68 UILabel* text = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease]; |
| 69 base::WeakNSObject<ContextualSearchPromoView> weakSelf(self); |
| 70 _linkController.reset([[LabelLinkController alloc] |
| 71 initWithLabel:text |
| 72 action:^(const GURL& gurl) { |
| 73 [[weakSelf delegate] promoViewSettingsTapped]; |
| 74 }]); |
| 75 [_linkController setLinkColor:UIColorFromRGB(kLinkColorRGB)]; |
| 76 |
| 77 // Label is as wide as the content area of the view. |
| 78 [constraints addObject:[text.widthAnchor |
| 79 constraintEqualToAnchor:self.layoutMarginsGuide |
| 80 .widthAnchor]]; |
| 81 |
| 82 // Parse out link location from the label text. |
| 83 NSString* textString = |
| 84 l10n_util::GetNSString(IDS_IOS_CONTEXTUAL_SEARCH_SHORT_DESCRIPTION); |
| 85 NSRange linkRange; |
| 86 textString = ParseStringWithLink(textString, &linkRange); |
| 87 |
| 88 // Build style attributes for the label. |
| 89 NSMutableParagraphStyle* paragraphStyle = |
| 90 [[[NSMutableParagraphStyle alloc] init] autorelease]; |
| 91 [paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping]; |
| 92 [paragraphStyle setLineHeightMultiple:kLineSpace]; |
| 93 NSDictionary* attributes = @{ |
| 94 NSParagraphStyleAttributeName : paragraphStyle, |
| 95 NSFontAttributeName : |
| 96 [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:kTextFontSize], |
| 97 NSForegroundColorAttributeName : |
| 98 [UIColor colorWithWhite:kTextColorGrayShade alpha:1] |
| 99 }; |
| 100 |
| 101 // Create and assign attributed text to label. |
| 102 base::scoped_nsobject<NSMutableAttributedString> attributedText( |
| 103 [[NSMutableAttributedString alloc] initWithString:textString]); |
| 104 [attributedText setAttributes:attributes |
| 105 range:NSMakeRange(0, textString.length)]; |
| 106 text.attributedText = attributedText; |
| 107 [_linkController addLinkWithRange:linkRange |
| 108 url:GURL("contextualSearch://settings")]; |
| 109 text.numberOfLines = 0; |
| 110 |
| 111 UIFont* buttonFont = |
| 112 [[MDFRobotoFontLoader sharedInstance] mediumFontOfSize:kButtonFontSize]; |
| 113 |
| 114 // Create accept and decline buttons with dimensions defined by the |
| 115 // minimum height and width constants. |
| 116 MDCFlatButton* acceptButton = [[[MDCFlatButton alloc] init] autorelease]; |
| 117 acceptButton.hasOpaqueBackground = YES; |
| 118 acceptButton.inkColor = |
| 119 [[[MDCPalette cr_bluePalette] tint300] colorWithAlphaComponent:0.5f]; |
| 120 [acceptButton setBackgroundColor:[[MDCPalette cr_bluePalette] tint500] |
| 121 forState:UIControlStateNormal]; |
| 122 [acceptButton setBackgroundColor:[UIColor colorWithWhite:0.6f alpha:1.0f] |
| 123 forState:UIControlStateDisabled]; |
| 124 [constraints addObjectsFromArray:@[ |
| 125 [acceptButton.widthAnchor |
| 126 constraintGreaterThanOrEqualToConstant:kButtonMinWidth], |
| 127 [acceptButton.heightAnchor constraintEqualToConstant:kButtonHeight] |
| 128 ]]; |
| 129 [acceptButton |
| 130 setTitle:l10n_util::GetNSString(IDS_IOS_CONTEXTUAL_SEARCH_GOT_IT_BUTTON) |
| 131 forState:UIControlStateNormal]; |
| 132 acceptButton.customTitleColor = [UIColor whiteColor]; |
| 133 [acceptButton addTarget:_delegate |
| 134 action:@selector(promoViewAcceptTapped) |
| 135 forControlEvents:UIControlEventTouchUpInside]; |
| 136 [acceptButton.titleLabel setFont:buttonFont]; |
| 137 |
| 138 UIColor* customTitleColor = [[MDCPalette cr_bluePalette] tint500]; |
| 139 MDCButton* declineButton = [[[MDCFlatButton alloc] init] autorelease]; |
| 140 [constraints addObjectsFromArray:@[ |
| 141 [declineButton.widthAnchor |
| 142 constraintGreaterThanOrEqualToConstant:kButtonMinWidth], |
| 143 [declineButton.heightAnchor constraintEqualToConstant:kButtonHeight] |
| 144 ]]; |
| 145 [declineButton setTitleColor:customTitleColor forState:UIControlStateNormal]; |
| 146 [declineButton |
| 147 setTitle:l10n_util::GetNSString(IDS_IOS_CONTEXTUAL_SEARCH_PROMO_OPTOUT) |
| 148 forState:UIControlStateNormal]; |
| 149 [declineButton addTarget:_delegate |
| 150 action:@selector(promoViewDeclineTapped) |
| 151 forControlEvents:UIControlEventTouchUpInside]; |
| 152 [declineButton.titleLabel setFont:buttonFont]; |
| 153 |
| 154 // Create the divider (a simple colored view) with height defined by |
| 155 // |kDividerHeight| and width spanning this view's width. |
| 156 UIView* divider = [[[UIView alloc] initWithFrame:CGRectZero] autorelease]; |
| 157 divider.backgroundColor = [UIColor colorWithWhite:0.745 alpha:1.0]; |
| 158 [constraints addObjectsFromArray:@[ |
| 159 [divider.widthAnchor constraintEqualToAnchor:self.widthAnchor], |
| 160 [divider.heightAnchor constraintEqualToConstant:kDividerHeight], |
| 161 ]]; |
| 162 |
| 163 self.translatesAutoresizingMaskIntoConstraints = NO; |
| 164 text.translatesAutoresizingMaskIntoConstraints = NO; |
| 165 acceptButton.translatesAutoresizingMaskIntoConstraints = NO; |
| 166 declineButton.translatesAutoresizingMaskIntoConstraints = NO; |
| 167 divider.translatesAutoresizingMaskIntoConstraints = NO; |
| 168 |
| 169 [self addSubview:text]; |
| 170 [self addSubview:acceptButton]; |
| 171 [self addSubview:declineButton]; |
| 172 [self addSubview:divider]; |
| 173 |
| 174 self.layoutMargins = UIEdgeInsetsMake(kMargin, kMargin, kMargin, kMargin); |
| 175 |
| 176 // Position all of the subviews |
| 177 [constraints addObjectsFromArray:@[ |
| 178 // Text is at the top of the content area, centered horizontally. |
| 179 [text.topAnchor constraintEqualToAnchor:self.layoutMarginsGuide.topAnchor], |
| 180 [text.centerXAnchor |
| 181 constraintEqualToAnchor:self.layoutMarginsGuide.centerXAnchor], |
| 182 // Accept button is |kSpaceBelowText| below the text, and at the bottom of |
| 183 // the content area. |
| 184 [acceptButton.topAnchor constraintEqualToAnchor:text.bottomAnchor |
| 185 constant:kSpaceBelowText], |
| 186 [acceptButton.bottomAnchor |
| 187 constraintEqualToAnchor:self.layoutMarginsGuide.bottomAnchor], |
| 188 // Decline button's top is aligned with the accept button's |
| 189 [declineButton.topAnchor constraintEqualToAnchor:acceptButton.topAnchor], |
| 190 // Decline button is to the leading side of the accept button, which |
| 191 // abuts the trailing edge of the content area. |
| 192 [acceptButton.leadingAnchor |
| 193 constraintEqualToAnchor:declineButton.trailingAnchor |
| 194 constant:kButtonSeparator], |
| 195 [acceptButton.trailingAnchor |
| 196 constraintEqualToAnchor:self.layoutMarginsGuide.trailingAnchor], |
| 197 // Divider is centered horizontally at the bottom of the view. |
| 198 [divider.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], |
| 199 [divider.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], |
| 200 ]]; |
| 201 |
| 202 [NSLayoutConstraint activateConstraints:constraints]; |
| 203 _constraints.reset([constraints retain]); |
| 204 |
| 205 return self; |
| 206 } |
| 207 |
| 208 - (void)dealloc { |
| 209 // Fix for crbug.com/591043 -- Some constraints applying to autoreleased |
| 210 // views may remain active with pointers to the to-be-deallocated views |
| 211 // after the views are no longer being displayed. This ensures that all |
| 212 // constraints are disabled and will not be applied even if some subviews |
| 213 // are lingering. |
| 214 [NSLayoutConstraint deactivateConstraints:_constraints]; |
| 215 [super dealloc]; |
| 216 } |
| 217 |
| 218 - (void)setHidden:(BOOL)hidden { |
| 219 if (!hidden && self.disabled) |
| 220 return; |
| 221 [self setClosure:hidden ? 0.0 : 1.0]; |
| 222 [super setHidden:hidden]; |
| 223 } |
| 224 |
| 225 - (void)setClosure:(CGFloat)closure { |
| 226 self.transform = CGAffineTransformMakeScale(1.0, closure); |
| 227 } |
| 228 |
| 229 - (void)closeAnimated:(BOOL)animated { |
| 230 [UIView cr_animateWithDuration:animated ? kCloseDuration : 0 |
| 231 delay:0 |
| 232 curve:ios::material::CurveEaseOut |
| 233 options:UIViewAnimationOptionBeginFromCurrentState |
| 234 animations:^{ |
| 235 self.hidden = YES; |
| 236 } |
| 237 completion:nil]; |
| 238 } |
| 239 |
| 240 #pragma mark - ContextualSearchPanelMotionObserver |
| 241 |
| 242 - (void)panel:(ContextualSearchPanelView*)panel |
| 243 didChangeToState:(ContextualSearch::PanelState)toState |
| 244 fromState:(ContextualSearch::PanelState)fromState { |
| 245 if (self.disabled) |
| 246 return; |
| 247 if (toState == ContextualSearch::COVERING) { |
| 248 [self closeAnimated:YES]; |
| 249 } else if (fromState == ContextualSearch::COVERING) { |
| 250 self.hidden = NO; |
| 251 } |
| 252 } |
| 253 |
| 254 - (void)panelWillPromote:(ContextualSearchPanelView*)panel { |
| 255 _delegate = nil; |
| 256 [panel removeMotionObserver:self]; |
| 257 } |
| 258 |
| 259 @end |
OLD | NEW |