| // Copyright 2022 The Chromium Authors |
| // 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/incognito_interstitial/incognito_interstitial_view_controller.h" |
| |
| #import <algorithm> |
| |
| #import "base/apple/foundation_util.h" |
| #import "base/check.h" |
| #import "base/ios/ios_util.h" |
| #import "ios/chrome/browser/shared/public/features/features.h" |
| #import "ios/chrome/browser/shared/ui/elements/extended_touch_target_button.h" |
| #import "ios/chrome/browser/shared/ui/util/attributed_string_util.h" |
| #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h" |
| #import "ios/chrome/browser/ui/incognito_interstitial/incognito_interstitial_constants.h" |
| #import "ios/chrome/browser/ui/ntp/incognito/incognito_view.h" |
| #import "ios/chrome/browser/ui/ntp/incognito/revamped_incognito_view.h" |
| #import "ios/chrome/common/button_configuration_util.h" |
| #import "ios/chrome/common/ui/colors/semantic_color_names.h" |
| #import "ios/chrome/common/ui/util/constraints_ui_util.h" |
| #import "ios/chrome/grit/ios_strings.h" |
| #import "ui/base/device_form_factor.h" |
| #import "ui/base/l10n/l10n_util_mac.h" |
| |
| namespace { |
| |
| // Opacity of navigation bar should be 0% at offset keyframe 0 and 100% at |
| // keyframe 1. |
| const CGFloat kNavigationBarFadeInKeyFrame0 = 70; |
| const CGFloat kNavigationBarFadeInKeyFrame1 = 140; |
| const CGFloat kNavigationBarFadeInCompactHeightKeyFrame0 = 20; |
| const CGFloat kNavigationBarFadeInCompactHeightKeyFrame1 = 50; |
| |
| // Name of banner at the top of the view. |
| NSString* const kIncognitoInterstitialBannerName = |
| @"incognito_interstitial_screen_banner"; |
| |
| // Maximum number of lines for the URL label, before the user unfolds it. |
| const int kURLLabelDefaultNumberOfLines = 3; |
| |
| // Line height multiple for the title label. |
| const CGFloat kTitleLabelLineHeightMultiple = 1.3; |
| |
| } // namespace |
| |
| @interface IncognitoInterstitialViewController () |
| |
| // The navigation bar to display at the top of the view, to contain a "Cancel" |
| // button. |
| @property(nonatomic, strong) UINavigationBar* navigationBar; |
| |
| // Vertical offset of internal scroll view, to update navigation bar opacity. |
| @property(nonatomic, assign) CGFloat scrollViewContentOffsetY; |
| |
| // Label to display the URL which is going to be opened. |
| @property(nonatomic, strong) UILabel* URLLabel; |
| |
| // Button which allows the user to remove the limit on the number of lines |
| // for `URLLabel`. |
| @property(nonatomic, strong) UIButton* expandURLButton; |
| |
| // Whether the number of lines of `URLLabel` is unlimited. |
| @property(nonatomic, assign) BOOL URLIsExpanded; |
| |
| @end |
| |
| @implementation IncognitoInterstitialViewController |
| |
| @dynamic delegate; |
| |
| #pragma mark - UIViewController |
| |
| - (void)viewDidLoad { |
| self.view.accessibilityIdentifier = |
| kIncognitoInterstitialAccessibilityIdentifier; |
| |
| self.bannerName = kIncognitoInterstitialBannerName; |
| self.bannerSize = BannerImageSizeType::kStandard; |
| self.shouldBannerFillTopSpace = YES; |
| self.shouldHideBanner = IsCompactHeight(self.traitCollection); |
| |
| NSString* title = |
| l10n_util::GetNSString(IDS_IOS_INCOGNITO_INTERSTITIAL_TITLE); |
| self.title = title; |
| self.titleText = title; |
| self.titleHorizontalMargin = 0; |
| self.primaryActionString = l10n_util::GetNSString( |
| IDS_IOS_INCOGNITO_INTERSTITIAL_OPEN_IN_CHROME_INCOGNITO); |
| self.secondaryActionString = |
| l10n_util::GetNSString(IDS_IOS_INCOGNITO_INTERSTITIAL_OPEN_IN_CHROME); |
| |
| // This needs to be called after parameters of `PromoStyleViewController` have |
| // been set, but before adding additional layout constraints, since these |
| // constraints can only be activated once the complete view hierarchy has been |
| // constructed and relevant views belong to the same hierarchy. |
| [super viewDidLoad]; |
| |
| // Fix the line height multiple of `self.titleLabel`. |
| [self fixTitleLabelLineHeightMultiple]; |
| |
| self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark; |
| self.modalInPresentation = YES; |
| |
| // Creating the Incognito view (same one as NTP). |
| UIScrollView* incognitoView = NULL; |
| if (base::FeatureList::IsEnabled(kIncognitoNtpRevamp)) { |
| RevampedIncognitoView* revampedIncognitoView = |
| [[RevampedIncognitoView alloc] initWithFrame:CGRectZero |
| showTopIncognitoImageAndTitle:NO |
| stackViewHorizontalMargin:0 |
| stackViewMaxWidth:CGFLOAT_MAX]; |
| revampedIncognitoView.URLLoaderDelegate = self.URLLoaderDelegate; |
| incognitoView = revampedIncognitoView; |
| } else { |
| IncognitoView* revampedIncognitoView = |
| [[IncognitoView alloc] initWithFrame:CGRectZero |
| showTopIncognitoImageAndTitle:NO |
| stackViewHorizontalMargin:0 |
| stackViewMaxWidth:CGFLOAT_MAX]; |
| revampedIncognitoView.URLLoaderDelegate = self.URLLoaderDelegate; |
| incognitoView = revampedIncognitoView; |
| } |
| incognitoView.translatesAutoresizingMaskIntoConstraints = NO; |
| incognitoView.bounces = NO; |
| |
| // The Incognito view is put inside a container because it might try |
| // to put constraints on its superview. |
| UIView* incognitoViewContainer = [[UIView alloc] init]; |
| incognitoViewContainer.translatesAutoresizingMaskIntoConstraints = NO; |
| [incognitoViewContainer addSubview:incognitoView]; |
| |
| // A stack view is created to contain the URL label as well as the |
| // Incognito view container. |
| UIStackView* stackView = [[UIStackView alloc] |
| initWithArrangedSubviews:@[ self.URLLabel, incognitoViewContainer ]]; |
| stackView.translatesAutoresizingMaskIntoConstraints = NO; |
| stackView.axis = UILayoutConstraintAxisVertical; |
| [self.specificContentView addSubview:stackView]; |
| |
| // Create the gradient view located on the leading end of the "more" button |
| // which lets the user unfold the URL label. |
| UIView* gradientView = [[UIView alloc] |
| initWithFrame:CGRectMake(0, 0, self.URLLabel.font.lineHeight, |
| self.URLLabel.font.lineHeight)]; |
| gradientView.translatesAutoresizingMaskIntoConstraints = NO; |
| gradientView.backgroundColor = self.view.backgroundColor; |
| CAGradientLayer* gradientLayer = [CAGradientLayer layer]; |
| gradientLayer.frame = gradientView.bounds; |
| gradientLayer.startPoint = CGPointMake(0.0, 0.0); |
| gradientLayer.endPoint = CGPointMake(1.0, 0.0); |
| gradientLayer.colors = |
| @[ (id)[UIColor clearColor].CGColor, (id)[UIColor whiteColor].CGColor ]; |
| gradientView.layer.mask = gradientLayer; |
| [self.expandURLButton addSubview:gradientView]; |
| // Add the "more" button to the content view. |
| [self.specificContentView addSubview:self.expandURLButton]; |
| |
| UIBarButtonItem* cancelButton = [[UIBarButtonItem alloc] |
| initWithBarButtonSystemItem:UIBarButtonSystemItemCancel |
| target:self.delegate |
| action:@selector(didTapCancelButton)]; |
| cancelButton.accessibilityIdentifier = |
| kIncognitoInterstitialCancelButtonAccessibilityIdentifier; |
| |
| UINavigationItem* navigationRootItem = |
| [[UINavigationItem alloc] initWithTitle:@""]; |
| navigationRootItem.rightBarButtonItem = cancelButton; |
| |
| self.navigationBar = [[UINavigationBar alloc] init]; |
| [self.navigationBar pushNavigationItem:navigationRootItem animated:false]; |
| [self updateNavigationBarAppearance]; |
| |
| incognitoView.translatesAutoresizingMaskIntoConstraints = NO; |
| [NSLayoutConstraint activateConstraints:@[ |
| [stackView.leadingAnchor |
| constraintEqualToAnchor:self.specificContentView.leadingAnchor], |
| [stackView.trailingAnchor |
| constraintEqualToAnchor:self.specificContentView.trailingAnchor], |
| [stackView.topAnchor |
| constraintEqualToAnchor:self.specificContentView.topAnchor], |
| [stackView.bottomAnchor |
| constraintEqualToAnchor:self.specificContentView.bottomAnchor], |
| [incognitoView.heightAnchor |
| constraintEqualToAnchor:incognitoView.contentLayoutGuide.heightAnchor], |
| [incognitoViewContainer.leadingAnchor |
| constraintEqualToAnchor:incognitoView.leadingAnchor], |
| [incognitoViewContainer.trailingAnchor |
| constraintEqualToAnchor:incognitoView.trailingAnchor], |
| [incognitoViewContainer.topAnchor |
| constraintEqualToAnchor:incognitoView.topAnchor], |
| [incognitoViewContainer.bottomAnchor |
| constraintEqualToAnchor:incognitoView.bottomAnchor], |
| [gradientView.trailingAnchor |
| constraintEqualToAnchor:self.expandURLButton.leadingAnchor], |
| [gradientView.bottomAnchor |
| constraintEqualToAnchor:self.expandURLButton.bottomAnchor], |
| [gradientView.topAnchor |
| constraintEqualToAnchor:self.expandURLButton.topAnchor], |
| [gradientView.widthAnchor |
| constraintEqualToAnchor:gradientView.heightAnchor], |
| [self.expandURLButton.trailingAnchor |
| constraintEqualToAnchor:self.URLLabel.trailingAnchor], |
| [self.expandURLButton.bottomAnchor |
| constraintEqualToAnchor:self.URLLabel.bottomAnchor], |
| ]]; |
| |
| [self.view addSubview:self.navigationBar]; |
| self.navigationBar.translatesAutoresizingMaskIntoConstraints = NO; |
| [NSLayoutConstraint activateConstraints:@[ |
| [self.navigationBar.leadingAnchor |
| constraintEqualToAnchor:self.view.leadingAnchor], |
| [self.navigationBar.trailingAnchor |
| constraintEqualToAnchor:self.view.trailingAnchor], |
| [self.navigationBar.topAnchor constraintEqualToAnchor:self.view.topAnchor], |
| ]]; |
| } |
| |
| - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { |
| [super traitCollectionDidChange:previousTraitCollection]; |
| self.shouldHideBanner = IsCompactHeight(self.traitCollection); |
| [self updateNavigationBarAppearance]; |
| } |
| |
| - (void)viewDidLayoutSubviews { |
| if (self.URLIsExpanded) { |
| self.expandURLButton.hidden = YES; |
| self.URLLabel.numberOfLines = 0; |
| } else { |
| CGRect URLLabelBoundingRect = [self.URLLabel.text |
| boundingRectWithSize:CGSizeMake(self.URLLabel.bounds.size.width, |
| CGFLOAT_MAX) |
| options:NSStringDrawingUsesLineFragmentOrigin |
| attributes:@{NSFontAttributeName : self.URLLabel.font} |
| context:nil]; |
| int expandedNumberOfLines = |
| URLLabelBoundingRect.size.height / self.URLLabel.font.lineHeight; |
| self.expandURLButton.hidden = |
| (expandedNumberOfLines <= kURLLabelDefaultNumberOfLines); |
| } |
| } |
| |
| - (void)viewDidAppear:(BOOL)animated { |
| [super viewDidAppear:animated]; |
| // Ensure the title label is focused when the Incognito interstial appears. |
| UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, |
| self.titleLabel); |
| } |
| |
| #pragma mark - Accessors |
| |
| - (UILabel*)URLLabel { |
| if (!_URLLabel) { |
| _URLLabel = [[UILabel alloc] initWithFrame:self.view.frame]; |
| _URLLabel.lineBreakMode = NSLineBreakByClipping; |
| _URLLabel.text = self.URLText; |
| _URLLabel.numberOfLines = kURLLabelDefaultNumberOfLines; |
| _URLLabel.textAlignment = NSTextAlignmentCenter; |
| _URLLabel.translatesAutoresizingMaskIntoConstraints = NO; |
| _URLLabel.accessibilityIdentifier = |
| kIncognitoInterstitialURLLabelAccessibilityIdentifier; |
| } |
| return _URLLabel; |
| } |
| |
| - (UIButton*)expandURLButton { |
| if (!_expandURLButton) { |
| __weak __typeof(self) weakSelf = self; |
| UIAction* readMoreAction = [UIAction actionWithHandler:^(id sender) { |
| [weakSelf expandURLButtonWasTapped]; |
| }]; |
| |
| NSAttributedString* readMoreString = [[NSAttributedString alloc] |
| initWithString:l10n_util::GetNSString( |
| IDS_IOS_INCOGNITO_INTERSTITIAL_URL_READ_MORE_BUTTON) |
| attributes:@{ |
| NSFontAttributeName : _URLLabel.font, |
| NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor] |
| }]; |
| |
| _expandURLButton = |
| [[ExtendedTouchTargetButton alloc] initWithFrame:CGRectZero |
| primaryAction:readMoreAction]; |
| |
| if (IsUIButtonConfigurationEnabled()) { |
| UIButtonConfiguration* buttonConfiguration = |
| [UIButtonConfiguration plainButtonConfiguration]; |
| buttonConfiguration.contentInsets = NSDirectionalEdgeInsetsMake( |
| CGFLOAT_EPSILON, CGFLOAT_EPSILON, CGFLOAT_EPSILON, CGFLOAT_EPSILON); |
| buttonConfiguration.attributedTitle = readMoreString; |
| _expandURLButton.configuration = buttonConfiguration; |
| } else { |
| UIEdgeInsets insets = UIEdgeInsetsMake(CGFLOAT_EPSILON, CGFLOAT_EPSILON, |
| CGFLOAT_EPSILON, CGFLOAT_EPSILON); |
| SetTitleEdgeInsets(_expandURLButton, insets); |
| SetContentEdgeInsets(_expandURLButton, insets); |
| [_expandURLButton setAttributedTitle:readMoreString |
| forState:UIControlStateNormal]; |
| } |
| |
| _expandURLButton.backgroundColor = self.view.backgroundColor; |
| _expandURLButton.translatesAutoresizingMaskIntoConstraints = NO; |
| // On voice over, the full info is on the URL field and this button isn't |
| // needed. |
| _expandURLButton.accessibilityElementsHidden = YES; |
| } |
| return _expandURLButton; |
| } |
| |
| #pragma mark - UIScrollViewDelegate |
| |
| // This override allows scroll detection of the scroll view contained within the |
| // underlying PromoStyleViewController. |
| - (void)scrollViewDidScroll:(UIScrollView*)scrollView { |
| // Constrain vertical content offset to positive values only. |
| CGPoint contentOffset = scrollView.contentOffset; |
| contentOffset.y = fmax(0, contentOffset.y); |
| scrollView.contentOffset = contentOffset; |
| |
| self.scrollViewContentOffsetY = scrollView.contentOffset.y; |
| [self updateNavigationBarAppearance]; |
| |
| [super scrollViewDidScroll:scrollView]; |
| } |
| |
| #pragma mark - Private |
| |
| - (void)updateNavigationBarAppearance { |
| CGFloat keyFrame0 = IsCompactHeight(self.traitCollection) |
| ? kNavigationBarFadeInCompactHeightKeyFrame0 |
| : kNavigationBarFadeInKeyFrame0; |
| CGFloat keyFrame1 = IsCompactHeight(self.traitCollection) |
| ? kNavigationBarFadeInCompactHeightKeyFrame1 |
| : kNavigationBarFadeInKeyFrame1; |
| CGFloat opacity = |
| (self.scrollViewContentOffsetY - keyFrame0) / (keyFrame1 - keyFrame0); |
| opacity = std::clamp(opacity, 0.0, 1.0, std::less_equal<>()); |
| |
| UIColor* backgroundColor = |
| [UIColor colorNamed:kGroupedPrimaryBackgroundColor]; |
| UIColor* shadowColor = [UIColor colorNamed:kSeparatorColor]; |
| CGFloat backgroundAlpha = CGColorGetAlpha(backgroundColor.CGColor); |
| CGFloat shadowAlpha = CGColorGetAlpha(shadowColor.CGColor); |
| |
| if (@available(iOS 15, *)) { |
| UINavigationBarAppearance* appearance = |
| [[UINavigationBarAppearance alloc] init]; |
| [appearance configureWithOpaqueBackground]; |
| appearance.backgroundColor = |
| [backgroundColor colorWithAlphaComponent:backgroundAlpha * opacity]; |
| appearance.shadowColor = |
| [shadowColor colorWithAlphaComponent:shadowAlpha * opacity]; |
| |
| self.navigationBar.compactAppearance = appearance; |
| self.navigationBar.standardAppearance = appearance; |
| self.navigationBar.scrollEdgeAppearance = appearance; |
| } else { |
| UIImage* whiteImage = ImageWithColor(UIColor.whiteColor); |
| UIImage* navigationBarBackgroundImage = [whiteImage |
| imageWithTintColor:[backgroundColor |
| colorWithAlphaComponent:backgroundAlpha * |
| opacity]]; |
| UIImage* navigationBarShadowImage = [whiteImage |
| imageWithTintColor:[shadowColor |
| colorWithAlphaComponent:shadowAlpha * opacity]]; |
| [self.navigationBar setBackgroundImage:navigationBarBackgroundImage |
| forBarMetrics:UIBarMetricsDefault]; |
| self.navigationBar.shadowImage = navigationBarShadowImage; |
| } |
| |
| self.navigationBar.tintColor = [UIColor colorNamed:kBlueColor]; |
| } |
| |
| // Called when `expandURLButton` is tapped. |
| - (void)expandURLButtonWasTapped { |
| self.URLIsExpanded = YES; |
| [self.view setNeedsLayout]; |
| } |
| |
| // Set the `attributedText` attribute of `self.titleLabel` to customize the line |
| // height multiple. |
| - (void)fixTitleLabelLineHeightMultiple { |
| NSMutableAttributedString* titleAttributedText = |
| [NSAttributedStringFromUILabel(self.titleLabel) mutableCopy]; |
| NSMutableDictionary* attributes = [NSMutableDictionary |
| dictionaryWithDictionary:[titleAttributedText attributesAtIndex:0 |
| effectiveRange:nil]]; |
| NSMutableParagraphStyle* paragraphStyle = |
| [[NSMutableParagraphStyle alloc] init]; |
| [paragraphStyle setParagraphStyle:attributes[NSParagraphStyleAttributeName]]; |
| paragraphStyle.lineHeightMultiple = kTitleLabelLineHeightMultiple; |
| attributes[NSParagraphStyleAttributeName] = paragraphStyle; |
| [titleAttributedText |
| setAttributes:attributes |
| range:NSMakeRange(0, titleAttributedText.length)]; |
| self.titleLabel.attributedText = titleAttributedText; |
| } |
| |
| @end |