1// Copyright 2015 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/first_run/welcome_to_chrome_view.h"
6
7#import <MaterialComponents/MaterialTypography.h>
8
9#include "base/check_op.h"
10#include "base/i18n/rtl.h"
11#include "base/notreached.h"
12#include "base/strings/sys_string_conversions.h"
13#import "ios/chrome/browser/ui/elements/text_view_selection_disabled.h"
14#include "ios/chrome/browser/ui/fancy_ui/primary_action_button.h"
15#include "ios/chrome/browser/ui/first_run/first_run_util.h"
16#import "ios/chrome/browser/ui/util/CRUILabel+AttributeUtils.h"
17#import "ios/chrome/browser/ui/util/label_link_controller.h"
18#import "ios/chrome/browser/ui/util/label_observer.h"
19#include "ios/chrome/browser/ui/util/ui_util.h"
20#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
21#include "ios/chrome/common/string_util.h"
22#import "ios/chrome/common/ui/colors/semantic_color_names.h"
23#include "ios/chrome/grit/ios_chromium_strings.h"
24#include "ios/chrome/grit/ios_strings.h"
25#include "ui/base/l10n/l10n_util.h"
26#include "url/gurl.h"
27
28#if !defined(__has_feature) || !__has_feature(objc_arc)
29#error "This file requires ARC support."
30#endif
31
32namespace {
33
34// An enum type to describe size classes.
35typedef NS_ENUM(NSInteger, SizeClassIdiom) {
36  COMPACT = 0,
37  REGULAR,
38  UNSPECIFIED,
39  SIZE_CLASS_COUNT = UNSPECIFIED,
40};
41
42// Returns the SizeClassIdiom corresponding with |size_class|.
43SizeClassIdiom GetSizeClassIdiom(UIUserInterfaceSizeClass size_class) {
44  switch (size_class) {
45    case UIUserInterfaceSizeClassCompact:
46      return COMPACT;
47    case UIUserInterfaceSizeClassRegular:
48      return REGULAR;
49    case UIUserInterfaceSizeClassUnspecified:
50      return UNSPECIFIED;
51  }
52}
53
54// Accessibility identifier for the checkbox button.
55NSString* const kUMAMetricsButtonAccessibilityIdentifier =
56    @"UMAMetricsButtonAccessibilityIdentifier";
57
58// The width of the container view for a REGULAR width size class.
59const CGFloat kContainerViewRegularWidth = 510.0;
60
61// The percentage of the view's width taken up by the container view for a
62// COMPACT width size class.
63const CGFloat kContainerViewCompactWidthPercentage = 0.8;
64
65// Layout constants.
66const CGFloat kImageTopPadding[SIZE_CLASS_COUNT] = {32.0, 50.0};
67const CGFloat kTOSTextViewTopPadding[SIZE_CLASS_COUNT] = {34.0, 40.0};
68const CGFloat kOptInLabelPadding[SIZE_CLASS_COUNT] = {10.0, 14.0};
69const CGFloat kCheckBoxPadding[SIZE_CLASS_COUNT] = {10.0, 16.0};
70const CGFloat kOKButtonBottomPadding[SIZE_CLASS_COUNT] = {32.0, 32.0};
71const CGFloat kOKButtonHeight[SIZE_CLASS_COUNT] = {36.0, 54.0};
72// Multiplier matches that used in LaunchScreen.xib to determine size of logo.
73const CGFloat kAppLogoProportionMultiplier = 0.381966;
74
75// Font sizes.
76const CGFloat kTitleLabelFontSize[SIZE_CLASS_COUNT] = {24.0, 36.0};
77const CGFloat kTOSTOSTextViewFontSize[SIZE_CLASS_COUNT] = {14.0, 21.0};
78const CGFloat kLegacyTOSLabelLineHeight[SIZE_CLASS_COUNT] = {20.0, 32.0};
79const CGFloat kOptInLabelFontSize[SIZE_CLASS_COUNT] = {13.0, 19.0};
80const CGFloat kOptInLabelLineHeight[SIZE_CLASS_COUNT] = {18.0, 26.0};
81const CGFloat kOKButtonTitleLabelFontSize[SIZE_CLASS_COUNT] = {14.0, 20.0};
82
83// Animation constants
84const CGFloat kAnimationDuration = .4;
85// Delay animation to avoid interaction with launch screen fadeout.
86const CGFloat kAnimationDelay = .5;
87
88// Image names.
89NSString* const kAppLogoImageName = @"launchscreen_app_logo";
90NSString* const kCheckBoxImageName = @"checkbox";
91NSString* const kCheckBoxCheckedImageName = @"checkbox_checked";
92
93// Constant for the Terms of Service URL in the first run experience.
94const char kTermsOfServiceUrl[] = "internal://terms-of-service";
95
96}  // namespace
97
98@interface WelcomeToChromeView () <UITextViewDelegate> {
99  UIView* _containerView;
100  UILabel* _titleLabel;
101  UIImageView* _imageView;
102  UIButton* _checkBoxButton;
103  UILabel* _optInLabel;
104  PrimaryActionButton* _OKButton;
105
106  // Used for iOS 12 compatibility.
107  UILabel* _legacyTOSLabel;
108  LabelLinkController* _legacyTOSLabelLinkController;
109}
110
111// Subview properties are lazily instantiated upon their first use.
112
113// The "Terms of Service" legacy label used for iOS 12 compatibility.
114@property(strong, nonatomic, readonly) UILabel* legacyTOSLabel;
115// Legacy observer for setting the size of the TOSLabel with cr_lineHeight used
116// for iOS 12 compatibility.
117@property(strong, nonatomic) LabelObserver* legacyTOSObserver;
118
119// A container view used to layout and center subviews.
120@property(strong, nonatomic, readonly) UIView* containerView;
121// The "Welcome to Chrome" label that appears at the top of the view.
122@property(strong, nonatomic, readonly) UILabel* titleLabel;
123// The Chrome logo image view.
124@property(strong, nonatomic, readonly) UIImageView* imageView;
125// The "Terms of Service" text view.
126@property(strong, nonatomic) TextViewSelectionDisabled* TOSTextView;
127// The stats reporting opt-in label.
128@property(strong, nonatomic, readonly) UILabel* optInLabel;
129// Observer for setting the size of the optInLabel with cr_lineHeight.
130@property(strong, nonatomic) LabelObserver* optInObserver;
131// The stats reporting opt-in checkbox button.
132@property(strong, nonatomic, readonly) UIButton* checkBoxButton;
133// The "Accept & Continue" button.
134@property(strong, nonatomic, readonly) PrimaryActionButton* OKButton;
135
136// Subview layout methods.  They must be called in the order declared here, as
137// subsequent subview layouts depend on the layouts that precede them.
138- (void)layoutTitleLabel;
139- (void)layoutImageView;
140- (void)layoutTOSTextView;
141- (void)layoutOptInLabel;
142- (void)layoutCheckBoxButton;
143- (void)layoutContainerView;
144- (void)layoutOKButton;
145
146// Calls the subview configuration selectors below.
147- (void)configureSubviews;
148
149// Subview configuration methods.
150- (void)configureTitleLabel;
151- (void)configureImageView;
152- (void)configureTOSTextView;
153- (void)configureOptInLabel;
154- (void)configureContainerView;
155- (void)configureOKButton;
156
157// Action triggered by the check box button.
158- (void)checkBoxButtonWasTapped;
159
160// Action triggered by the ok button.
161- (void)OKButtonWasTapped;
162
163@end
164
165@implementation WelcomeToChromeView
166
167- (instancetype)initWithFrame:(CGRect)frame {
168  self = [super initWithFrame:frame];
169  if (self) {
170    self.backgroundColor = [UIColor colorNamed:kBackgroundColor];
171    self.autoresizingMask =
172        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
173  }
174  return self;
175}
176
177- (void)runLaunchAnimation {
178  // Prepare for animation by making views (except for the logo) transparent
179  // and finding the initial and final location of the logo.
180  self.titleLabel.alpha = 0.0;
181  self.TOSTextView.alpha = 0.0;
182  self.legacyTOSLabel.alpha = 0.0;
183  self.optInLabel.alpha = 0.0;
184  self.checkBoxButton.alpha = 0.0;
185  self.OKButton.alpha = 0.0;
186
187  // Get final location of logo based on result from previously run
188  // layoutSubviews.
189  CGRect finalLogoFrame = self.imageView.frame;
190  // Ensure that frame position is valid and that layoutSubviews ran
191  // before this method.
192  DCHECK(finalLogoFrame.origin.x >= 0 && finalLogoFrame.origin.y >= 0);
193  self.imageView.center = CGPointMake(CGRectGetMidX(self.containerView.bounds),
194                                      CGRectGetMidY(self.containerView.bounds));
195
196  __weak WelcomeToChromeView* weakSelf = self;
197  [UIView animateWithDuration:kAnimationDuration
198                        delay:kAnimationDelay
199                      options:UIViewAnimationCurveEaseInOut
200                   animations:^{
201                     [weakSelf imageView].frame = finalLogoFrame;
202                     [weakSelf titleLabel].alpha = 1.0;
203                     [weakSelf TOSTextView].alpha = 1.0;
204                     [weakSelf legacyTOSLabel].alpha = 1.0;
205                     [weakSelf optInLabel].alpha = 1.0;
206                     [weakSelf checkBoxButton].alpha = 1.0;
207                     [weakSelf OKButton].alpha = 1.0;
208                   }
209                   completion:nil];
210}
211
212- (void)dealloc {
213  [self.legacyTOSObserver stopObserving];
214
215  [self.optInObserver stopObserving];
216}
217
218#pragma mark - Accessors
219
220- (BOOL)isCheckBoxSelected {
221  return self.checkBoxButton.selected;
222}
223
224- (void)setCheckBoxSelected:(BOOL)checkBoxSelected {
225  if (checkBoxSelected != self.checkBoxButton.selected)
226    [self checkBoxButtonWasTapped];
227}
228
229- (UIView*)containerView {
230  if (!_containerView) {
231    _containerView = [[UIView alloc] initWithFrame:CGRectZero];
232  }
233  return _containerView;
234}
235
236- (UILabel*)titleLabel {
237  if (!_titleLabel) {
238    _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
239    [_titleLabel setNumberOfLines:0];
240    [_titleLabel setLineBreakMode:NSLineBreakByWordWrapping];
241    [_titleLabel setBaselineAdjustment:UIBaselineAdjustmentAlignBaselines];
242    [_titleLabel
243        setText:l10n_util::GetNSString(IDS_IOS_FIRSTRUN_WELCOME_TO_CHROME)];
244  }
245  return _titleLabel;
246}
247
248- (UIImageView*)imageView {
249  if (!_imageView) {
250    UIImage* image = [UIImage imageNamed:kAppLogoImageName];
251    _imageView = [[UIImageView alloc] initWithImage:image];
252  }
253  return _imageView;
254}
255
256- (TextViewSelectionDisabled*)TOSTextView {
257  if (!_TOSTextView) {
258    _TOSTextView = [[TextViewSelectionDisabled alloc] initWithFrame:CGRectZero];
259  }
260  return _TOSTextView;
261}
262
263- (UILabel*)legacyTOSLabel {
264  if (!_legacyTOSLabel) {
265    _legacyTOSLabel = [[UILabel alloc] initWithFrame:CGRectZero];
266    // Add an observer to the label to be able to keep the cr_lineHeight.
267    self.legacyTOSObserver = [LabelObserver observerForLabel:_legacyTOSLabel];
268    [self.legacyTOSObserver startObserving];
269
270    [_legacyTOSLabel setNumberOfLines:0];
271    [_legacyTOSLabel setTextAlignment:NSTextAlignmentCenter];
272  }
273  return _legacyTOSLabel;
274}
275
276- (UILabel*)optInLabel {
277  if (!_optInLabel) {
278    _optInLabel = [[UILabel alloc] initWithFrame:CGRectZero];
279    // Add an observer to the label to be able to keep the cr_lineHeight.
280    self.optInObserver = [LabelObserver observerForLabel:_optInLabel];
281    [self.optInObserver startObserving];
282
283    [_optInLabel setNumberOfLines:0];
284    [_optInLabel
285        setText:l10n_util::GetNSString(IDS_IOS_FIRSTRUN_NEW_OPT_IN_LABEL)];
286    [_optInLabel setTextAlignment:NSTextAlignmentNatural];
287  }
288  return _optInLabel;
289}
290
291- (UIButton*)checkBoxButton {
292  if (!_checkBoxButton) {
293    _checkBoxButton = [[UIButton alloc] initWithFrame:CGRectZero];
294    [_checkBoxButton setBackgroundColor:[UIColor clearColor]];
295    [_checkBoxButton addTarget:self
296                        action:@selector(checkBoxButtonWasTapped)
297              forControlEvents:UIControlEventTouchUpInside];
298    SetA11yLabelAndUiAutomationName(_checkBoxButton,
299                                    IDS_IOS_FIRSTRUN_NEW_OPT_IN_LABEL,
300                                    kUMAMetricsButtonAccessibilityIdentifier);
301    [_checkBoxButton
302        setAccessibilityValue:l10n_util::GetNSString(IDS_IOS_SETTING_OFF)];
303    [_checkBoxButton setImage:[UIImage imageNamed:kCheckBoxImageName]
304                     forState:UIControlStateNormal];
305    UIImage* selectedImage = [[UIImage imageNamed:kCheckBoxCheckedImageName]
306        imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
307    [_checkBoxButton setImage:selectedImage forState:UIControlStateSelected];
308  }
309  return _checkBoxButton;
310}
311
312- (PrimaryActionButton*)OKButton {
313  if (!_OKButton) {
314    _OKButton = [[PrimaryActionButton alloc] initWithFrame:CGRectZero];
315    [_OKButton addTarget:self
316                  action:@selector(OKButtonWasTapped)
317        forControlEvents:UIControlEventTouchUpInside];
318    NSString* acceptAndContinue =
319        l10n_util::GetNSString(IDS_IOS_FIRSTRUN_OPT_IN_ACCEPT_BUTTON);
320    [_OKButton setTitle:acceptAndContinue forState:UIControlStateNormal];
321    [_OKButton setTitle:acceptAndContinue forState:UIControlStateHighlighted];
322    // UIAutomation tests look for the Accept button to skip through the
323    // First Run UI when it shows up.
324    SetA11yLabelAndUiAutomationName(
325        _OKButton, IDS_IOS_FIRSTRUN_OPT_IN_ACCEPT_BUTTON, @"Accept & Continue");
326  }
327  return _OKButton;
328}
329
330#pragma mark - Layout
331
332- (void)willMoveToSuperview:(nullable UIView*)newSuperview {
333  [super willMoveToSuperview:newSuperview];
334
335  // Early return if the view hierarchy is already built.
336  if (self.containerView.superview) {
337    DCHECK_EQ(self, self.containerView.superview);
338    return;
339  }
340
341  [self addSubview:self.containerView];
342  [self.containerView addSubview:self.titleLabel];
343  [self.containerView addSubview:self.imageView];
344  if (@available(iOS 13.5, *)) {
345    [self.containerView addSubview:self.TOSTextView];
346  } else {
347    [self.containerView addSubview:self.legacyTOSLabel];
348  }
349  [self.containerView addSubview:self.optInLabel];
350  [self.containerView addSubview:self.checkBoxButton];
351  [self addSubview:self.OKButton];
352  [self configureSubviews];
353}
354
355- (void)safeAreaInsetsDidChange {
356  [super safeAreaInsetsDidChange];
357  [self layoutOKButtonAndContainerView];
358}
359
360- (void)layoutSubviews {
361  [super layoutSubviews];
362  [self layoutTitleLabel];
363  [self layoutImageView];
364  if (@available(iOS 13.5, *)) {
365    [self layoutTOSTextView];
366  } else {
367    [self layoutLegacyTOSLabel];
368  }
369  [self layoutOptInLabel];
370  [self layoutCheckBoxButton];
371  [self layoutOKButtonAndContainerView];
372}
373
374- (void)layoutOKButtonAndContainerView {
375  // The OK Button must be laid out before the container view so that the
376  // container view can take its position into account.
377  [self layoutOKButton];
378  [self layoutContainerView];
379}
380
381- (void)layoutTitleLabel {
382  // The label is centered and top-aligned with the container view.
383  CGSize containerSize = self.containerView.bounds.size;
384  containerSize.height = CGFLOAT_MAX;
385  CGSize titleLabelSize = [self.titleLabel sizeThatFits:containerSize];
386  self.titleLabel.frame = AlignRectOriginAndSizeToPixels(
387      CGRectMake((containerSize.width - titleLabelSize.width) / 2.0, 0.0,
388                 titleLabelSize.width, titleLabelSize.height));
389}
390
391- (void)layoutImageView {
392  // The image is centered and laid out below |titleLabel| as specified by
393  // kImageTopPadding.
394  CGSize imageViewSize = self.imageView.bounds.size;
395  CGFloat imageViewTopPadding = kImageTopPadding[[self heightSizeClassIdiom]];
396  self.imageView.frame = AlignRectOriginAndSizeToPixels(CGRectMake(
397      (CGRectGetWidth(self.containerView.bounds) - imageViewSize.width) / 2.0,
398      CGRectGetMaxY(self.titleLabel.frame) + imageViewTopPadding,
399      imageViewSize.width, imageViewSize.height));
400}
401
402- (void)layoutTOSTextView {
403  // The TOSTextView is centered and laid out below |imageView| as specified by
404  // kTOSTextViewTopPadding.
405  CGSize containerSize = self.containerView.bounds.size;
406  containerSize.height = CGFLOAT_MAX;
407  CGSize TOSTextViewSize = [self.TOSTextView sizeThatFits:containerSize];
408  CGFloat TOSTextViewTopPadding =
409      kTOSTextViewTopPadding[[self heightSizeClassIdiom]];
410  CGRect frame =
411      CGRectMake((containerSize.width - TOSTextViewSize.width) / 2.0,
412                 CGRectGetMaxY(self.imageView.frame) + TOSTextViewTopPadding,
413                 TOSTextViewSize.width, TOSTextViewSize.height);
414  self.TOSTextView.frame = AlignRectOriginAndSizeToPixels(frame);
415}
416
417- (void)layoutLegacyTOSLabel {
418  // The TOS label is centered and laid out below |imageView| as specified by
419  // kTOSLabelTopPadding.
420  CGSize containerSize = self.containerView.bounds.size;
421  containerSize.height = CGFLOAT_MAX;
422  self.legacyTOSLabel.frame = {CGPointZero, containerSize};
423  NSString* TOSText = l10n_util::GetNSString(IDS_IOS_FIRSTRUN_AGREE_TO_TERMS);
424  NSRange tosLinkTextRange = NSMakeRange(NSNotFound, 0);
425  TOSText = ParseStringWithTag(TOSText, &tosLinkTextRange,
426                               @"BEGIN_LINK_TOS[ \t]*", @"[ \t]*END_LINK_TOS");
427
428  DCHECK_NE(NSNotFound, static_cast<NSInteger>(tosLinkTextRange.location));
429  DCHECK_NE(0u, tosLinkTextRange.length);
430
431  self.legacyTOSLabel.text = TOSText;
432
433  __weak WelcomeToChromeView* weakSelf = self;
434  ProceduralBlockWithURL action = ^(const GURL& url) {
435    WelcomeToChromeView* strongSelf = weakSelf;
436    if (!strongSelf)
437      return;
438    if (url == kTermsOfServiceUrl) {
439      [[strongSelf delegate] welcomeToChromeViewDidTapTOSLink];
440    } else {
441      NOTREACHED();
442    }
443  };
444
445  _legacyTOSLabelLinkController =
446      [[LabelLinkController alloc] initWithLabel:_legacyTOSLabel action:action];
447  [_legacyTOSLabelLinkController addLinkWithRange:tosLinkTextRange
448                                              url:GURL(kTermsOfServiceUrl)];
449  [_legacyTOSLabelLinkController setLinkColor:[UIColor colorNamed:kBlueColor]];
450
451  CGSize TOSLabelSize = [self.legacyTOSLabel sizeThatFits:containerSize];
452  CGFloat TOSLabelTopPadding =
453      kTOSTextViewTopPadding[[self heightSizeClassIdiom]];
454  self.legacyTOSLabel.frame = AlignRectOriginAndSizeToPixels(
455      CGRectMake((containerSize.width - TOSLabelSize.width) / 2.0,
456                 CGRectGetMaxY(self.imageView.frame) + TOSLabelTopPadding,
457                 TOSLabelSize.width, TOSLabelSize.height));
458}
459
460- (void)layoutOptInLabel {
461  // The opt in label is laid out to the right (or left in RTL) of the check box
462  // button and below |TOSLabel| as specified by kOptInLabelPadding.
463  CGSize checkBoxSize =
464      [self.checkBoxButton imageForState:self.checkBoxButton.state].size;
465  CGFloat checkBoxPadding = kCheckBoxPadding[[self widthSizeClassIdiom]];
466  CGFloat optInLabelSidePadding = checkBoxSize.width + 2.0 * checkBoxPadding;
467  CGSize optInLabelSize = [self.optInLabel
468      sizeThatFits:CGSizeMake(CGRectGetWidth(self.containerView.bounds) -
469                                  optInLabelSidePadding,
470                              CGFLOAT_MAX)];
471  CGFloat optInLabelTopPadding =
472      kOptInLabelPadding[[self heightSizeClassIdiom]];
473  CGFloat optInLabelOriginX =
474      base::i18n::IsRTL() ? 0.0f : optInLabelSidePadding;
475  if (@available(iOS 13.5, *)) {
476    self.optInLabel.frame = AlignRectOriginAndSizeToPixels(
477        CGRectMake(optInLabelOriginX,
478                   CGRectGetMaxY(self.TOSTextView.frame) + optInLabelTopPadding,
479                   optInLabelSize.width, optInLabelSize.height));
480  } else {
481    self.optInLabel.frame = AlignRectOriginAndSizeToPixels(CGRectMake(
482        optInLabelOriginX,
483        CGRectGetMaxY(self.legacyTOSLabel.frame) + optInLabelTopPadding,
484        optInLabelSize.width, optInLabelSize.height));
485  }
486}
487
488- (void)layoutCheckBoxButton {
489  // The checkBoxButton is laid out to the left of |optInLabel|.  The view
490  // itself is sized so that it covers the label, and the image insets are
491  // chosen such that the check box image is centered vertically with
492  // |optInLabel|.
493  CGSize checkBoxSize =
494      [self.checkBoxButton imageForState:self.checkBoxButton.state].size;
495  CGFloat checkBoxPadding = kCheckBoxPadding[[self widthSizeClassIdiom]];
496  CGSize checkBoxButtonSize =
497      CGSizeMake(CGRectGetWidth(self.optInLabel.frame) + checkBoxSize.width +
498                     2.0 * checkBoxPadding,
499                 std::max(CGRectGetHeight(self.optInLabel.frame),
500                          checkBoxSize.height + 2.0f * checkBoxPadding));
501  self.checkBoxButton.frame = AlignRectOriginAndSizeToPixels(CGRectMake(
502      0.0f,
503      CGRectGetMidY(self.optInLabel.frame) - checkBoxButtonSize.height / 2.0,
504      checkBoxButtonSize.width, checkBoxButtonSize.height));
505  CGFloat largeHorizontalInset =
506      checkBoxButtonSize.width - checkBoxSize.width - checkBoxPadding;
507  CGFloat smallHorizontalInset = checkBoxPadding;
508  self.checkBoxButton.imageEdgeInsets = UIEdgeInsetsMake(
509      (checkBoxButtonSize.height - checkBoxSize.height) / 2.0,
510      base::i18n::IsRTL() ? largeHorizontalInset : smallHorizontalInset,
511      (checkBoxButtonSize.height - checkBoxSize.height) / 2.0,
512      base::i18n::IsRTL() ? smallHorizontalInset : largeHorizontalInset);
513}
514
515- (void)layoutContainerView {
516  // The container view is resized according to the final layout of
517  // |checkBoxButton|, which is its lowest subview.  The resized view is then
518  // centered horizontally and vertically. If necessary, it is shifted up to
519  // allow |kOptInLabelPadding| between |optInLabel| and |OKButton|.
520  CGSize containerViewSize = self.containerView.bounds.size;
521  containerViewSize.height = CGRectGetMaxY(self.checkBoxButton.frame);
522
523  CGFloat padding = kOptInLabelPadding[[self heightSizeClassIdiom]];
524  CGFloat originY = fmin(
525      (CGRectGetHeight(self.bounds) - containerViewSize.height) / 2.0,
526      CGRectGetMinY(self.OKButton.frame) - padding - containerViewSize.height);
527
528  self.containerView.frame = AlignRectOriginAndSizeToPixels(CGRectMake(
529      (CGRectGetWidth(self.bounds) - containerViewSize.width) / 2.0, originY,
530      containerViewSize.width, CGRectGetMaxY(self.checkBoxButton.frame)));
531}
532
533- (void)layoutOKButton {
534  // The OK button is laid out at the bottom of the view as specified by
535  // kOKButtonBottomPadding.
536  CGFloat OKButtonBottomPadding =
537      kOKButtonBottomPadding[[self widthSizeClassIdiom]];
538  CGSize OKButtonSize = self.OKButton.bounds.size;
539  CGFloat bottomSafeArea = self.safeAreaInsets.bottom;
540  self.OKButton.frame = AlignRectOriginAndSizeToPixels(
541      CGRectMake((CGRectGetWidth(self.bounds) - OKButtonSize.width) / 2.0,
542                 CGRectGetMaxY(self.bounds) - OKButtonSize.height -
543                     OKButtonBottomPadding - bottomSafeArea,
544                 OKButtonSize.width, OKButtonSize.height));
545}
546
547- (void)traitCollectionDidChange:
548    (nullable UITraitCollection*)previousTraitCollection {
549  [super traitCollectionDidChange:previousTraitCollection];
550  [self configureSubviews];
551}
552
553- (void)configureSubviews {
554  [self configureContainerView];
555  [self configureTitleLabel];
556  [self configureImageView];
557  if (@available(iOS 13.5, *)) {
558    [self configureTOSTextView];
559  } else {
560    [self configureLegacyTOSLabel];
561  }
562  [self configureOptInLabel];
563  [self configureOKButton];
564  [self setNeedsLayout];
565}
566
567- (void)configureTitleLabel {
568  self.titleLabel.font = [[MDCTypography fontLoader]
569      regularFontOfSize:kTitleLabelFontSize[[self widthSizeClassIdiom]]];
570}
571
572- (void)configureImageView {
573  CGFloat sideLength = self.imageView.image.size.width;
574  if ([self widthSizeClassIdiom] == COMPACT) {
575    sideLength = self.bounds.size.width * kAppLogoProportionMultiplier;
576  } else if ([self heightSizeClassIdiom] == COMPACT) {
577    sideLength = self.bounds.size.height * kAppLogoProportionMultiplier;
578  }
579  self.imageView.bounds = AlignRectOriginAndSizeToPixels(
580      CGRectMake(self.imageView.bounds.origin.x, self.imageView.bounds.origin.y,
581                 sideLength, sideLength));
582}
583
584- (void)configureTOSTextView {
585  self.TOSTextView.scrollEnabled = NO;
586  self.TOSTextView.editable = NO;
587  self.TOSTextView.adjustsFontForContentSizeCategory = YES;
588  self.TOSTextView.delegate = self;
589  self.TOSTextView.backgroundColor = UIColor.clearColor;
590  self.TOSTextView.linkTextAttributes =
591      @{NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor]};
592
593  NSString* TOSText = l10n_util::GetNSString(IDS_IOS_FIRSTRUN_AGREE_TO_TERMS);
594  NSRange tosLinkTextRange = NSMakeRange(NSNotFound, 0);
595  TOSText = ParseStringWithTag(TOSText, &tosLinkTextRange,
596                               @"BEGIN_LINK_TOS[ \t]*", @"[ \t]*END_LINK_TOS");
597
598  DCHECK_NE(NSNotFound, static_cast<NSInteger>(tosLinkTextRange.location));
599  DCHECK_NE(0u, tosLinkTextRange.length);
600
601  NSRange fullRange = NSMakeRange(0, TOSText.length);
602  NSURL* URL =
603      [NSURL URLWithString:base::SysUTF8ToNSString(kTermsOfServiceUrl)];
604  UIFont* font = [[MDCTypography fontLoader]
605      regularFontOfSize:kTOSTOSTextViewFontSize[[self widthSizeClassIdiom]]];
606  NSMutableParagraphStyle* style =
607      [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
608  style.alignment = NSTextAlignmentCenter;
609
610  NSMutableAttributedString* attributedText =
611      [[NSMutableAttributedString alloc] initWithString:TOSText];
612  [attributedText addAttributes:@{
613    NSForegroundColorAttributeName : [UIColor colorNamed:kTextPrimaryColor],
614    NSParagraphStyleAttributeName : style,
615    NSFontAttributeName : font
616  }
617                          range:fullRange];
618  [attributedText addAttribute:NSLinkAttributeName
619                         value:URL
620                         range:tosLinkTextRange];
621
622  self.TOSTextView.attributedText = attributedText;
623}
624
625- (void)configureOptInLabel {
626  self.optInLabel.font = [[MDCTypography fontLoader]
627      regularFontOfSize:kOptInLabelFontSize[[self widthSizeClassIdiom]]];
628  self.optInLabel.cr_lineHeight =
629      kOptInLabelLineHeight[[self widthSizeClassIdiom]];
630}
631
632- (void)configureLegacyTOSLabel {
633  self.legacyTOSLabel.font = [[MDCTypography fontLoader]
634      regularFontOfSize:kTOSTOSTextViewFontSize[[self widthSizeClassIdiom]]];
635  self.legacyTOSLabel.cr_lineHeight =
636      kLegacyTOSLabelLineHeight[[self widthSizeClassIdiom]];
637}
638
639- (void)configureContainerView {
640  CGFloat containerViewWidth =
641      [self widthSizeClassIdiom] == COMPACT
642          ? kContainerViewCompactWidthPercentage * CGRectGetWidth(self.bounds)
643          : kContainerViewRegularWidth;
644  self.containerView.frame =
645      CGRectMake(0.0, 0.0, containerViewWidth, CGFLOAT_MAX);
646}
647
648- (void)configureOKButton {
649  UIFont* font = [[MDCTypography fontLoader]
650      mediumFontOfSize:kOKButtonTitleLabelFontSize[[self widthSizeClassIdiom]]];
651  [self.OKButton setTitleFont:font forState:UIControlStateNormal];
652  CGSize size = [self.OKButton
653      sizeThatFits:CGSizeMake(CGFLOAT_MAX,
654                              kOKButtonHeight[[self widthSizeClassIdiom]])];
655  [self.OKButton
656      setBounds:CGRectMake(0, 0, size.width,
657                           kOKButtonHeight[[self widthSizeClassIdiom]])];
658}
659
660- (SizeClassIdiom)widthSizeClassIdiom {
661  UIWindow* keyWindow = [UIApplication sharedApplication].keyWindow;
662  UIUserInterfaceSizeClass sizeClass = self.traitCollection.horizontalSizeClass;
663  if (sizeClass == UIUserInterfaceSizeClassUnspecified)
664    sizeClass = keyWindow.traitCollection.horizontalSizeClass;
665  return GetSizeClassIdiom(sizeClass);
666}
667
668- (SizeClassIdiom)heightSizeClassIdiom {
669  UIWindow* keyWindow = [UIApplication sharedApplication].keyWindow;
670  UIUserInterfaceSizeClass sizeClass = self.traitCollection.verticalSizeClass;
671  if (sizeClass == UIUserInterfaceSizeClassUnspecified)
672    sizeClass = keyWindow.traitCollection.verticalSizeClass;
673  return GetSizeClassIdiom(sizeClass);
674}
675
676#pragma mark -
677
678- (void)checkBoxButtonWasTapped {
679  self.checkBoxButton.selected = !self.checkBoxButton.selected;
680  self.checkBoxButton.accessibilityValue =
681      self.checkBoxButton.selected
682          ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
683          : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
684}
685
686- (void)OKButtonWasTapped {
687  [self.delegate welcomeToChromeViewDidTapOKButton:self];
688}
689
690#pragma mark - UITextViewDelegate
691
692- (BOOL)textView:(TextViewSelectionDisabled*)textView
693    shouldInteractWithURL:(NSURL*)URL
694                  inRange:(NSRange)characterRange
695              interaction:(UITextItemInteraction)interaction {
696  DCHECK(textView == self.TOSTextView);
697  DCHECK(GURL(base::SysNSStringToUTF8(URL.absoluteString)) ==
698         kTermsOfServiceUrl);
699  [self.delegate welcomeToChromeViewDidTapTOSLink];
700  // Returns NO as the app is handling the opening of the URL.
701  return NO;
702}
703
704@end
705