OLD | NEW |
---|---|
1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "ios/chrome/search_widget_extension/search_widget_view.h" | 5 #import "ios/chrome/search_widget_extension/search_widget_view.h" |
6 | |
7 #include "base/logging.h" | 6 #include "base/logging.h" |
7 #import "ios/chrome/search_widget_extension/ui_util.h" | |
8 | 8 |
9 #if !defined(__has_feature) || !__has_feature(objc_arc) | 9 #if !defined(__has_feature) || !__has_feature(objc_arc) |
10 #error "This file requires ARC support." | 10 #error "This file requires ARC support." |
11 #endif | 11 #endif |
12 | 12 |
13 namespace { | 13 namespace { |
14 | 14 |
15 const CGFloat kCursorHeight = 40; | 15 const CGFloat kContentMargin = 16; |
16 const CGFloat kCursorWidth = 2; | 16 const CGFloat kURLButtonMargin = 10; |
17 const CGFloat kCursorHorizontalPadding = 10; | 17 const CGFloat kActionButtonSize = 55; |
18 const CGFloat kCursorVerticalPadding = 10; | 18 const CGFloat kIconSize = 35; |
19 const CGFloat kFakeboxHorizontalPadding = 40; | 19 const CGFloat kMaxContentSize = 421; |
20 const CGFloat kFakeboxVerticalPadding = 40; | 20 const CGFloat kIconSpacing = 5; |
21 | 21 |
22 } // namespace | 22 } // namespace |
23 | 23 |
24 @interface SearchWidgetView () { | 24 @interface SearchWidgetView () |
25 __weak id<SearchWidgetViewActionTarget> _target; | 25 |
26 } | 26 // The target for actions in the view. |
27 | 27 @property(nonatomic, weak) id<SearchWidgetViewActionTarget> target; |
28 @property(nonatomic, copy) NSString* copiedURL; | 28 // The copied URL label containing the URL or a placeholder text. |
29 @property(nonatomic, strong) UILabel* copiedURLLabel; | 29 @property(nonatomic, strong) UILabel* copiedURLLabel; |
30 @property(nonatomic, weak) UIView* cursor; | 30 // The copued URL title label containing the title of the copied URL button. |
31 | 31 @property(nonatomic, strong) UILabel* openCopiedURLTitleLabel; |
32 // Creates and adds a fake omnibox with blinking cursor to the view and sets the | 32 // The hairline view shown between the action and copied URL views. |
33 // class cursor property. | 33 @property(nonatomic, strong) UIView* hairlineView; |
34 - (void)addFakebox; | 34 // The button shown when there is a copied URL to open. |
35 @property(nonatomic, strong) UIButton* copiedButtonView; | |
36 // The primary effect view of the widget. Add views here for a more opaque | |
37 // appearance. | |
38 @property(nonatomic, strong) UIVisualEffectView* primaryEffectView; | |
39 // The secondary effect view of the widget. Add views here for a more | |
40 // transparent appearance. | |
41 @property(nonatomic, strong) UIVisualEffectView* secondaryEffectView; | |
42 // The constraints to be activated when the copiedURL section is visible. | |
43 @property(nonatomic, strong) | |
44 NSArray<NSLayoutConstraint*>* visibleCopiedURLConstraints; | |
45 // The constraints to be activated when the copiedURL section is hidden. | |
46 @property(nonatomic, strong) | |
47 NSArray<NSLayoutConstraint*>* hiddenCopiedURLConstraints; | |
48 | |
49 // Sets up the widget UI. | |
50 - (void)createUI; | |
51 | |
52 // Creates the view for the action buttons. | |
53 - (UIView*)newActionsView; | |
54 | |
55 // Creates the view for the copiedURL section. | |
56 - (void)initializeOpenCopiedURLSectionUsingTopAnchor:(NSLayoutAnchor*)topAnchor; | |
35 | 57 |
36 @end | 58 @end |
37 | 59 |
38 @implementation SearchWidgetView | 60 @implementation SearchWidgetView |
39 | 61 |
40 @synthesize copiedURL = _copiedURL; | 62 @synthesize target = _target; |
63 @synthesize copiedURLVisible = _copiedURLVisible; | |
64 @synthesize copiedURLString = _copiedURLString; | |
41 @synthesize copiedURLLabel = _copiedURLLabel; | 65 @synthesize copiedURLLabel = _copiedURLLabel; |
42 | 66 @synthesize openCopiedURLTitleLabel = _openCopiedURLTitleLabel; |
43 @synthesize cursor = _cursor; | 67 @synthesize hairlineView = _hairlineView; |
44 | 68 @synthesize copiedButtonView = _copiedButtonView; |
45 - (instancetype)initWithActionTarget:(id<SearchWidgetViewActionTarget>)target { | 69 @synthesize primaryEffectView = _primaryEffectView; |
70 @synthesize secondaryEffectView = _secondaryEffectView; | |
71 @synthesize visibleCopiedURLConstraints = _visibleCopiedURLConstraints; | |
72 @synthesize hiddenCopiedURLConstraints = _hiddenCopiedURLConstraints; | |
73 | |
74 - (instancetype)initWithActionTarget:(id<SearchWidgetViewActionTarget>)target | |
75 primaryVibrancyEffect:(UIVibrancyEffect*)primaryVibrancyEffect | |
76 secondaryVibrancyEffect: | |
77 (UIVibrancyEffect*)secondaryVibrancyEffect { | |
46 self = [super initWithFrame:CGRectZero]; | 78 self = [super initWithFrame:CGRectZero]; |
47 if (self) { | 79 if (self) { |
48 DCHECK(target); | 80 DCHECK(target); |
49 _target = target; | 81 _target = target; |
50 [self addFakebox]; | 82 _primaryEffectView = |
83 [[UIVisualEffectView alloc] initWithEffect:primaryVibrancyEffect]; | |
84 _secondaryEffectView = | |
85 [[UIVisualEffectView alloc] initWithEffect:secondaryVibrancyEffect]; | |
86 _copiedURLVisible = YES; | |
87 [self createUI]; | |
88 [self updateCopiedURLUI]; | |
51 } | 89 } |
52 return self; | 90 return self; |
53 } | 91 } |
54 | 92 |
55 - (void)addFakebox { | 93 #pragma mark - property overrides |
56 UIView* fakebox = [[UIView alloc] initWithFrame:CGRectZero]; | 94 |
57 | 95 - (void)setCopiedURLVisible:(BOOL)copiedURLVisible { |
96 _copiedURLVisible = copiedURLVisible; | |
97 [self updateCopiedURLUI]; | |
98 } | |
99 | |
100 - (void)setCopiedURLString:(NSString*)copiedURL { | |
101 _copiedURLString = copiedURL; | |
102 [self updateCopiedURLUI]; | |
103 } | |
104 | |
105 #pragma mark - UI creation | |
106 | |
107 - (void)createUI { | |
108 for (UIVisualEffectView* effectView in | |
109 @[ self.primaryEffectView, self.secondaryEffectView ]) { | |
110 [self addSubview:effectView]; | |
111 effectView.translatesAutoresizingMaskIntoConstraints = NO; | |
112 [NSLayoutConstraint | |
113 activateConstraints:ui_util::CreateSameConstraints(self, effectView)]; | |
114 } | |
115 | |
116 UIView* actionsView = [self newActionsView]; | |
117 [self initializeOpenCopiedURLSectionUsingTopAnchor:actionsView.bottomAnchor]; | |
118 } | |
119 | |
120 - (UIView*)newActionsView { | |
121 // Views added to different effect views need to be constrained to each other. | |
122 // This is made easier by putting all the constraints into an array and | |
marq (ping after 24h)
2017/05/09 16:01:43
Activating constraints all at once is *somewhat* m
lody
2017/05/09 16:17:57
As seen offline, this is functionally necessary. P
marq (ping after 24h)
2017/05/10 10:48:42
My rough suggestion:
"The vibrancy effects requir
lody
2017/05/10 12:54:02
perfect! thanks.
| |
123 // activating them all at once after adding all the views. | |
124 NSMutableArray<NSLayoutConstraint*>* constraintArray = [NSMutableArray array]; | |
marq (ping after 24h)
2017/05/10 10:48:42
Rename to 'constraints' (prefer not to include typ
lody
2017/05/10 12:54:02
Done.
| |
125 | |
126 UIStackView* actionRow = [[UIStackView alloc] initWithArrangedSubviews:@[ | |
127 [self newActionViewWithTitle:@"New Search" | |
128 imageName:@"quick_action_search" | |
129 actionSelector:@selector(openSearch:) | |
130 constraintArray:constraintArray], | |
131 [self newActionViewWithTitle:@"Incognito Search" | |
132 imageName:@"quick_action_incognito_search" | |
133 actionSelector:@selector(openIncognito:) | |
134 constraintArray:constraintArray], | |
135 [self newActionViewWithTitle:@"Voice Search" | |
136 imageName:@"quick_action_voice_search" | |
137 actionSelector:@selector(openVoice:) | |
138 constraintArray:constraintArray], | |
139 [self newActionViewWithTitle:@"Scan QR/Bar Code" | |
140 imageName:@"quick_action_camera_search" | |
141 actionSelector:@selector(openQRCode:) | |
142 constraintArray:constraintArray], | |
143 ]]; | |
144 | |
145 actionRow.axis = UILayoutConstraintAxisHorizontal; | |
146 actionRow.alignment = UIStackViewAlignmentTop; | |
147 actionRow.distribution = UIStackViewDistributionFillEqually; | |
148 actionRow.spacing = kIconSpacing; | |
149 actionRow.layoutMargins = | |
150 UIEdgeInsetsMake(0, kContentMargin, 0, kContentMargin); | |
151 actionRow.layoutMarginsRelativeArrangement = YES; | |
152 actionRow.translatesAutoresizingMaskIntoConstraints = NO; | |
153 | |
154 [self.secondaryEffectView.contentView addSubview:actionRow]; | |
155 | |
156 // These constraints stretch the action row to the full width of the widget. | |
157 // Their priority is < UILayoutPriorityRequired so that they can break when | |
158 // the view is larger than kMaxContentSize. | |
159 NSLayoutConstraint* actionsLeftConstraint = [actionRow.leftAnchor | |
160 constraintEqualToAnchor:self.secondaryEffectView.leftAnchor]; | |
161 actionsLeftConstraint.priority = UILayoutPriorityDefaultHigh; | |
162 | |
163 NSLayoutConstraint* actionsRightConstraint = [actionRow.rightAnchor | |
164 constraintEqualToAnchor:self.secondaryEffectView.rightAnchor]; | |
165 actionsRightConstraint.priority = UILayoutPriorityDefaultHigh; | |
166 | |
167 // This constraint sets the top alignment for the action row. Its priority is | |
168 // < UILayoutPriorityRequired so that it can break in favor of the | |
169 // centerYAnchor rule (on the next line) when the copiedURL section is hidden. | |
170 NSLayoutConstraint* actionsTopConstraint = [actionRow.topAnchor | |
171 constraintEqualToAnchor:self.secondaryEffectView.topAnchor | |
172 constant:kContentMargin]; | |
173 actionsTopConstraint.priority = UILayoutPriorityDefaultHigh; | |
174 | |
175 self.hiddenCopiedURLConstraints = @[ [actionRow.centerYAnchor | |
176 constraintEqualToAnchor:self.secondaryEffectView.centerYAnchor] ]; | |
177 | |
178 [constraintArray addObjectsFromArray:@[ | |
179 [actionRow.centerXAnchor | |
180 constraintEqualToAnchor:self.secondaryEffectView.centerXAnchor], | |
181 [actionRow.widthAnchor constraintLessThanOrEqualToConstant:kMaxContentSize], | |
182 actionsLeftConstraint, | |
183 actionsRightConstraint, | |
184 actionsTopConstraint, | |
185 ]]; | |
186 | |
187 [NSLayoutConstraint activateConstraints:constraintArray]; | |
188 | |
189 return actionRow; | |
190 } | |
191 | |
192 - (UIView*)newActionViewWithTitle:(NSString*)title | |
193 imageName:(NSString*)imageName | |
194 actionSelector:(SEL)actionSelector | |
195 constraintArray: | |
196 (NSMutableArray<NSLayoutConstraint*>*)constraintArray { | |
197 UIView* circleView = [[UIView alloc] initWithFrame:CGRectZero]; | |
198 circleView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.05]; | |
199 circleView.layer.cornerRadius = kActionButtonSize / 2; | |
200 | |
201 [constraintArray addObjectsFromArray:@[ | |
202 [circleView.widthAnchor constraintEqualToConstant:kActionButtonSize], | |
203 [circleView.heightAnchor constraintEqualToConstant:kActionButtonSize] | |
204 ]]; | |
205 | |
206 UILabel* labelView = [[UILabel alloc] initWithFrame:CGRectZero]; | |
207 labelView.text = title; | |
208 labelView.numberOfLines = 0; | |
209 labelView.textAlignment = NSTextAlignmentCenter; | |
210 labelView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]; | |
211 [labelView | |
212 setContentCompressionResistancePriority:UILayoutPriorityRequired | |
213 forAxis:UILayoutConstraintAxisVertical]; | |
214 | |
215 UIStackView* stack = | |
216 [[UIStackView alloc] initWithArrangedSubviews:@[ circleView, labelView ]]; | |
217 stack.axis = UILayoutConstraintAxisVertical; | |
218 stack.spacing = kIconSpacing; | |
219 stack.alignment = UIStackViewAlignmentCenter; | |
220 stack.translatesAutoresizingMaskIntoConstraints = NO; | |
221 | |
222 // A transparent button constrained to the same size as the stack is added to | |
223 // handle taps on the stack view. | |
224 UIButton* actionButton = [[UIButton alloc] initWithFrame:CGRectZero]; | |
225 actionButton.backgroundColor = [UIColor clearColor]; | |
226 [actionButton addTarget:self.target | |
227 action:actionSelector | |
228 forControlEvents:UIControlEventTouchUpInside]; | |
229 actionButton.translatesAutoresizingMaskIntoConstraints = NO; | |
230 [self addSubview:actionButton]; | |
231 [constraintArray | |
232 addObjectsFromArray:ui_util::CreateSameConstraints(actionButton, stack)]; | |
233 | |
234 UIImage* iconImage = [UIImage imageNamed:imageName]; | |
235 UIImageView* icon = [[UIImageView alloc] initWithImage:iconImage]; | |
236 icon.translatesAutoresizingMaskIntoConstraints = NO; | |
237 | |
238 [constraintArray addObjectsFromArray:@[ | |
239 [icon.widthAnchor constraintEqualToConstant:kIconSize], | |
240 [icon.heightAnchor constraintEqualToConstant:kIconSize], | |
241 [icon.centerXAnchor constraintEqualToAnchor:circleView.centerXAnchor], | |
242 [icon.centerYAnchor constraintEqualToAnchor:circleView.centerYAnchor], | |
243 ]]; | |
244 [self.primaryEffectView.contentView addSubview:icon]; | |
245 | |
246 return stack; | |
247 } | |
248 | |
249 - (void)initializeOpenCopiedURLSectionUsingTopAnchor: | |
250 (NSLayoutAnchor*)topAnchor { | |
251 self.hairlineView = [[UIView alloc] initWithFrame:CGRectZero]; | |
252 self.hairlineView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.05]; | |
253 self.hairlineView.translatesAutoresizingMaskIntoConstraints = NO; | |
254 [self.secondaryEffectView.contentView addSubview:self.hairlineView]; | |
255 | |
256 self.copiedButtonView = [[UIButton alloc] initWithFrame:CGRectZero]; | |
257 self.copiedButtonView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.05]; | |
258 self.copiedButtonView.layer.cornerRadius = 5; | |
259 self.copiedButtonView.translatesAutoresizingMaskIntoConstraints = NO; | |
260 [self.secondaryEffectView.contentView addSubview:self.copiedButtonView]; | |
261 [self.copiedButtonView addTarget:self.target | |
262 action:@selector(openCopiedURL:) | |
263 forControlEvents:UIControlEventTouchUpInside]; | |
264 | |
265 self.openCopiedURLTitleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; | |
266 self.openCopiedURLTitleLabel.textAlignment = NSTextAlignmentCenter; | |
267 self.openCopiedURLTitleLabel.translatesAutoresizingMaskIntoConstraints = NO; | |
268 self.openCopiedURLTitleLabel.font = | |
269 [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; | |
270 [self.primaryEffectView.contentView addSubview:self.openCopiedURLTitleLabel]; | |
271 | |
272 self.copiedURLLabel = [[UILabel alloc] initWithFrame:CGRectZero]; | |
273 self.copiedURLLabel.textAlignment = NSTextAlignmentCenter; | |
274 self.copiedURLLabel.font = | |
275 [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]; | |
276 self.copiedURLLabel.translatesAutoresizingMaskIntoConstraints = NO; | |
277 [self.secondaryEffectView.contentView addSubview:self.copiedURLLabel]; | |
278 | |
279 self.visibleCopiedURLConstraints = @[ | |
280 [self.hairlineView.topAnchor constraintEqualToAnchor:topAnchor | |
281 constant:kContentMargin], | |
282 [self.hairlineView.leftAnchor | |
283 constraintEqualToAnchor:self.secondaryEffectView.leftAnchor], | |
284 [self.hairlineView.rightAnchor | |
285 constraintEqualToAnchor:self.secondaryEffectView.rightAnchor], | |
286 [self.hairlineView.heightAnchor constraintEqualToConstant:0.5], | |
287 | |
288 [self.copiedButtonView.centerXAnchor | |
289 constraintEqualToAnchor:self.secondaryEffectView.centerXAnchor], | |
290 [self.copiedButtonView.widthAnchor | |
291 constraintEqualToAnchor:self.secondaryEffectView.widthAnchor | |
292 constant:-2 * kContentMargin], | |
293 [self.copiedButtonView.topAnchor | |
294 constraintEqualToAnchor:self.hairlineView.bottomAnchor | |
295 constant:12], | |
296 [self.copiedButtonView.bottomAnchor | |
297 constraintEqualToAnchor:self.secondaryEffectView.bottomAnchor | |
298 constant:-kContentMargin], | |
299 | |
300 [self.openCopiedURLTitleLabel.centerXAnchor | |
301 constraintEqualToAnchor:self.primaryEffectView.centerXAnchor], | |
302 [self.openCopiedURLTitleLabel.topAnchor | |
303 constraintEqualToAnchor:self.copiedButtonView.topAnchor | |
304 constant:kURLButtonMargin], | |
305 [self.openCopiedURLTitleLabel.widthAnchor | |
306 constraintEqualToAnchor:self.copiedButtonView.widthAnchor | |
307 constant:-kContentMargin * 2], | |
308 | |
309 [self.copiedURLLabel.centerXAnchor | |
310 constraintEqualToAnchor:self.primaryEffectView.centerXAnchor], | |
311 [self.copiedURLLabel.topAnchor | |
312 constraintEqualToAnchor:self.openCopiedURLTitleLabel.bottomAnchor], | |
313 [self.copiedURLLabel.widthAnchor | |
314 constraintEqualToAnchor:self.openCopiedURLTitleLabel.widthAnchor], | |
315 [self.copiedURLLabel.bottomAnchor | |
316 constraintEqualToAnchor:self.copiedButtonView.bottomAnchor | |
317 constant:-kURLButtonMargin], | |
318 ]; | |
319 } | |
320 | |
321 - (void)addTapAction:(SEL)action toView:(UIView*)view { | |
58 UIGestureRecognizer* tapRecognizer = | 322 UIGestureRecognizer* tapRecognizer = |
59 [[UITapGestureRecognizer alloc] initWithTarget:_target | 323 [[UITapGestureRecognizer alloc] initWithTarget:self.target action:action]; |
60 action:@selector(openSearch:)]; | 324 [view addGestureRecognizer:tapRecognizer]; |
61 | 325 } |
62 [fakebox addGestureRecognizer:tapRecognizer]; | 326 |
63 [self addSubview:fakebox]; | 327 - (void)updateCopiedURLUI { |
64 | 328 // If the copiedURL section is not visible, hide all the copiedURL section |
65 UIView* cursor = [[UIView alloc] initWithFrame:CGRectZero]; | 329 // views and activate the correct constraint set. If it is visible, show the |
66 self.cursor = cursor; | 330 // views in function of whether there is or not a copied URL to show. |
67 self.cursor.backgroundColor = [UIColor blueColor]; | 331 |
68 [fakebox addSubview:self.cursor]; | 332 if (!self.copiedURLVisible) { |
69 | 333 self.copiedURLLabel.hidden = YES; |
70 [fakebox setTranslatesAutoresizingMaskIntoConstraints:NO]; | 334 self.openCopiedURLTitleLabel.hidden = YES; |
71 [self.cursor setTranslatesAutoresizingMaskIntoConstraints:NO]; | 335 self.hairlineView.hidden = YES; |
72 [NSLayoutConstraint activateConstraints:@[ | 336 self.copiedButtonView.hidden = YES; |
73 [[fakebox leadingAnchor] constraintEqualToAnchor:self.leadingAnchor | 337 [NSLayoutConstraint deactivateConstraints:self.visibleCopiedURLConstraints]; |
74 constant:kFakeboxHorizontalPadding], | 338 [NSLayoutConstraint activateConstraints:self.hiddenCopiedURLConstraints]; |
75 [[fakebox trailingAnchor] | 339 return; |
76 constraintEqualToAnchor:self.trailingAnchor | 340 } |
77 constant:-kFakeboxHorizontalPadding], | 341 |
78 [[fakebox topAnchor] constraintEqualToAnchor:self.topAnchor | 342 self.copiedURLLabel.hidden = NO; |
79 constant:kFakeboxVerticalPadding], | 343 self.openCopiedURLTitleLabel.hidden = NO; |
80 [[fakebox heightAnchor] | 344 |
81 constraintEqualToConstant:kCursorHeight + 2 * kCursorVerticalPadding], | 345 [NSLayoutConstraint deactivateConstraints:self.hiddenCopiedURLConstraints]; |
82 | 346 [NSLayoutConstraint activateConstraints:self.visibleCopiedURLConstraints]; |
83 [[self.cursor widthAnchor] constraintEqualToConstant:kCursorWidth], | 347 |
84 [[self.cursor leadingAnchor] | 348 if (self.copiedURLString) { |
85 constraintEqualToAnchor:fakebox.leadingAnchor | 349 self.copiedButtonView.hidden = NO; |
86 constant:kCursorHorizontalPadding], | 350 self.hairlineView.hidden = YES; |
87 [[self.cursor heightAnchor] constraintEqualToConstant:kCursorHeight], | 351 self.copiedURLLabel.text = self.copiedURLString; |
88 [[self.cursor centerYAnchor] constraintEqualToAnchor:fakebox.centerYAnchor] | 352 self.openCopiedURLTitleLabel.alpha = 1; |
89 ]]; | 353 self.openCopiedURLTitleLabel.text = @"Open Copied Link"; |
90 | 354 self.copiedURLLabel.alpha = 1; |
91 [UIView animateWithDuration:0.3 | 355 return; |
92 delay:0.0 | 356 } |
93 options:UIViewAnimationOptionRepeat | | 357 |
94 UIViewAnimationOptionAutoreverse | 358 self.copiedButtonView.hidden = YES; |
95 animations:^{ | 359 self.hairlineView.hidden = NO; |
96 self.cursor.alpha = 0.0f; | 360 self.copiedURLLabel.text = @"Links you copy will appear here."; |
97 } | 361 self.openCopiedURLTitleLabel.alpha = 0.5; |
98 completion:nil]; | 362 self.openCopiedURLTitleLabel.text = @"No Copied Link"; |
99 } | 363 self.copiedURLLabel.alpha = 0.5; |
100 | 364 } |
101 - (void)updateCopiedURL:(NSString*)copiedURL { | |
102 self.copiedURL = copiedURL; | |
103 self.copiedURLLabel.text = copiedURL; | |
104 } | |
105 | |
106 @end | 365 @end |
OLD | NEW |