chromium/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.mm

// Copyright 2017 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/content_suggestions/content_suggestions_collection_utils.h"

#import "base/i18n/rtl.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/location_bar/ui_bundled/location_bar_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/elements/new_feature_badge_view.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/start_surface/ui_bundled/start_surface_features.h"
#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
#import "ios/chrome/browser/ui/toolbar/public/toolbar_constants.h"
#import "ios/chrome/browser/ui/toolbar/public/toolbar_utils.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/pointer_interaction_util.h"
#import "ios/chrome/common/ui/util/ui_util.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/components/ui_util/dynamic_type_util.h"
#import "ui/base/device_form_factor.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/gfx/ios/uikit_util.h"

namespace {

// Width of search field.
const CGFloat kSearchFieldLarge = 432;
const CGFloat kSearchFieldSmall = 343;
const CGFloat kSearchFieldSmallMin = 304;
const CGFloat kSearchFieldMinMargin = 8;

const CGFloat kTopSpacingMaterial = 24;

// Top margin for the doodle.
const CGFloat kDoodleTopMarginRegularXRegular = 162;
const CGFloat kDoodleTopMarginOther = 65;
// Size of the doodle top margin which is multiplied by the scaled font factor,
// and added to `kDoodleTopMarginOther` on non Regular x Regular form factors.
const CGFloat kDoodleScaledTopMarginOther = 10;
const CGFloat kLargeFakeboxExtraDoodleTopMargin = 10;

// Top margin for the search field
const CGFloat kSearchFieldTopMargin = 22;
const CGFloat kLargeFakeboxSearchFieldTopMargin = 40;

// Bottom margin for the search field.
const CGFloat kNTPShrunkLogoSearchFieldBottomPadding = 20;

// Height for the logo and doodle frame.
const CGFloat kGoogleSearchDoodleHeight = 120;

// Height for the shrunk doodle frame.
// TODO(crbug.com/40744549): clean up post-launch.
const CGFloat kGoogleSearchDoodleShrunkHeight = 68;

// Height for the shrunk logo frame.
// TODO(crbug.com/40744549): clean up post-launch.
const CGFloat kGoogleSearchLogoHeight = 36;
const CGFloat kLargeFakeboxGoogleSearchLogoHeight = 50;

// The size of the symbol image.
const CGFloat kSymbolContentSuggestionsPointSize = 18;

// Constants for a symbol button with an new badge.
const CGFloat kSymbolButtonSize = 37.0;
const CGFloat kButtonShadowOpacity = 0.35;
const CGFloat kButtonShadowRadius = 1.0;
const CGFloat kButtonShadowVerticalOffset = 1.0;
const CGFloat kNewBadgeOffsetFromButtonCenter = 14.0;

// The height of the Fakebox.
const CGFloat kFakeboxHeight = 65;
const CGFloat kFakeboxHeightNonDynamic = 45;

// The height of the Fakebox when it is pinned to the top.
const CGFloat kPinnedFakeboxHeight = 48;
const CGFloat kPinnedFakeboxHeightNonDynamic = 18;

// Height and width of the new feature badge.
const CGFloat kNewFeatureBadgeSize = 20;
// Font size of the new feature badge label.
const CGFloat kNewFeatureFontSize = 10;

// Returns the amount of vertical margin to include in the Fake Toolbar.
CGFloat FakeToolbarVerticalMargin() {
  UIContentSizeCategory category =
      [UIApplication sharedApplication].preferredContentSizeCategory;
  CGFloat vertical_margin =
      2 * kAdaptiveLocationBarVerticalMargin - kTopToolbarUnsplitMargin;
  CGFloat dynamic_type_vertical_adjustment =
      (ToolbarClampedFontSizeMultiplier(category) - 1) *
      (kLocationBarVerticalMarginDynamicType +
       kAdaptiveLocationBarVerticalMargin);
  return vertical_margin + dynamic_type_vertical_adjustment;
}

// Returns the color to use for the Lens and Voice icons in the Fakebox.
UIColor* FakeboxIconColor() {
  return [UIColor colorNamed:kGrey700Color];
}

// Sets up fakebox button with a round background and new badge view.
void SetUpButtonWithNewFeatureBadge(UIButton* button) {
  [button setTranslatesAutoresizingMaskIntoConstraints:NO];

  button.backgroundColor = [UIColor colorNamed:kOmniboxKeyboardButtonColor];
  button.layer.cornerRadius = kSymbolButtonSize / 2;

  button.layer.shadowColor = [UIColor blackColor].CGColor;
  button.layer.shadowOffset = CGSizeMake(0, kButtonShadowVerticalOffset);
  button.layer.shadowOpacity = kButtonShadowOpacity;
  button.layer.shadowRadius = kButtonShadowRadius;

  NewFeatureBadgeView* badgeView =
      [[NewFeatureBadgeView alloc] initWithBadgeSize:kNewFeatureBadgeSize
                                            fontSize:kNewFeatureFontSize];
  badgeView.translatesAutoresizingMaskIntoConstraints = NO;
  badgeView.accessibilityElementsHidden = YES;
  [button.imageView addSubview:badgeView];

  [NSLayoutConstraint activateConstraints:@[
    [button.widthAnchor constraintEqualToConstant:kSymbolButtonSize],
    [button.heightAnchor constraintEqualToConstant:kSymbolButtonSize],
    [badgeView.centerXAnchor
        constraintEqualToAnchor:button.imageView.centerXAnchor
                       constant:kNewBadgeOffsetFromButtonCenter],
    [badgeView.centerYAnchor
        constraintEqualToAnchor:button.imageView.centerYAnchor
                       constant:-kNewBadgeOffsetFromButtonCenter],
  ]];
}
}

namespace content_suggestions {

const CGFloat kHintTextScale = 0.15;
const CGFloat kReturnToRecentTabSectionBottomMargin = 25;

CGFloat DoodleHeight(BOOL logo_is_showing,
                     BOOL doodle_is_showing,
                     UITraitCollection* trait_collection) {
  // For users with non-Google default search engine, there is no doodle.
  if (!IsRegularXRegularSizeClass(trait_collection) && !logo_is_showing) {
    return 0;
  }

  if (logo_is_showing) {
    if (doodle_is_showing ||
        (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET)) {
      return kGoogleSearchDoodleShrunkHeight;
    } else if (IsIOSLargeFakeboxEnabled()) {
      return kLargeFakeboxGoogleSearchLogoHeight;
    } else {
      return kGoogleSearchLogoHeight;
    }
  }

  return kGoogleSearchDoodleHeight;
}

CGFloat DoodleTopMargin(CGFloat top_inset,
                        UITraitCollection* trait_collection) {
  if (IsRegularXRegularSizeClass(trait_collection))
    return kDoodleTopMarginRegularXRegular;
  CGFloat top_margin =
      top_inset +
      AlignValueToPixel(kDoodleScaledTopMarginOther *
                        ui_util::SystemSuggestedFontSizeMultiplier());
  // If Magic Stack is not enabled, this value is zero (e.g. no-op).
  top_margin -= ReducedNTPTopMarginSpaceForMagicStack();
  if (IsIOSLargeFakeboxEnabled()) {
    top_margin += kLargeFakeboxExtraDoodleTopMargin;
  }
  top_margin += kDoodleTopMarginOther;
  return top_margin;
}

CGFloat HeaderSeparatorHeight() {
  return ui::AlignValueToUpperPixel(kToolbarSeparatorHeight);
}

CGFloat SearchFieldTopMargin() {
  return IsIOSLargeFakeboxEnabled() ? kLargeFakeboxSearchFieldTopMargin
                                    : kSearchFieldTopMargin;
}

CGFloat SearchFieldWidth(CGFloat width, UITraitCollection* trait_collection) {
  if (!IsCompactWidth(trait_collection) && !IsCompactHeight(trait_collection))
    return kSearchFieldLarge;

  // Special case for narrow sizes.
  return std::max(
      kSearchFieldSmallMin,
      std::min(kSearchFieldSmall, width - kSearchFieldMinMargin * 2));
}

CGFloat FakeOmniboxHeight() {
  if (IsIOSLargeFakeboxEnabled()) {
    CGFloat multiplier = ui_util::SystemSuggestedFontSizeMultiplier();
    return AlignValueToPixel((kFakeboxHeight - kFakeboxHeightNonDynamic) *
                                 multiplier +
                             kFakeboxHeightNonDynamic);
  }
  return ToolbarExpandedHeight(
      [UIApplication sharedApplication].preferredContentSizeCategory);
}

CGFloat PinnedFakeOmniboxHeight() {
  if (IsIOSLargeFakeboxEnabled()) {
    CGFloat multiplier = ui_util::SystemSuggestedFontSizeMultiplier();
    return AlignValueToPixel(
        (kPinnedFakeboxHeight - kPinnedFakeboxHeightNonDynamic) * multiplier +
        kPinnedFakeboxHeightNonDynamic);
  }
  return LocationBarHeight(
      [UIApplication sharedApplication].preferredContentSizeCategory);
}

CGFloat FakeToolbarHeight() {
  if (IsIOSLargeFakeboxEnabled()) {
    return PinnedFakeOmniboxHeight() + FakeToolbarVerticalMargin();
  }
  return ToolbarExpandedHeight(
      [UIApplication sharedApplication].preferredContentSizeCategory);
}

CGFloat HeightForLogoHeader(BOOL logo_is_showing,
                            BOOL doodle_is_showing,
                            UITraitCollection* trait_collection) {
  CGFloat header_height =
      DoodleTopMargin(0, trait_collection) +
      DoodleHeight(logo_is_showing, doodle_is_showing, trait_collection) +
      SearchFieldTopMargin() + FakeOmniboxHeight() +
      ntp_header::kScrolledToTopOmniboxBottomMargin +
      ceil(HeaderSeparatorHeight());
  if (!IsRegularXRegularSizeClass(trait_collection)) {
    return header_height;
  }
  if (!logo_is_showing) {
    // Returns sufficient vertical space for the Identity Disc to be
    // displayed.
    return ntp_home::kIdentityAvatarDimension +
           2 * (ntp_home::kHeaderIconMargin + ntp_home::kIdentityAvatarPadding);
  }

  header_height += kTopSpacingMaterial;

  return header_height;
}

CGFloat HeaderBottomPadding() {
  return kNTPShrunkLogoSearchFieldBottomPadding;
}

void ConfigureSearchHintLabel(UILabel* search_hint_label,
                              UIView* search_tab_target) {
  [search_hint_label setTranslatesAutoresizingMaskIntoConstraints:NO];
  [search_tab_target addSubview:search_hint_label];

  [search_hint_label setText:l10n_util::GetNSString(IDS_OMNIBOX_EMPTY_HINT)];
  if (base::i18n::IsRTL()) {
    [search_hint_label setTextAlignment:NSTextAlignmentRight];
  }
  search_hint_label.textColor = SearchHintLabelColor();
  search_hint_label.adjustsFontForContentSizeCategory = YES;
  search_hint_label.textAlignment = NSTextAlignmentCenter;
}

void ConfigureVoiceSearchButton(UIButton* voice_search_button,
                                BOOL use_color_icon) {
  [voice_search_button setTranslatesAutoresizingMaskIntoConstraints:NO];

  UIButtonConfiguration* buttonConfig =
      [UIButtonConfiguration plainButtonConfiguration];
  buttonConfig.contentInsets = NSDirectionalEdgeInsetsMake(0, 0, 0, 0);
  voice_search_button.configuration = buttonConfig;

  voice_search_button.tintColor = FakeboxIconColor();
  UIImage* mic_image = CustomSymbolWithPointSize(
      kVoiceSymbol, kSymbolContentSuggestionsPointSize);
  mic_image = use_color_icon ? MakeSymbolMulticolor(mic_image)
                             : MakeSymbolMonochrome(mic_image);
  [voice_search_button setImage:mic_image forState:UIControlStateNormal];
  [voice_search_button setAccessibilityLabel:l10n_util::GetNSString(
                                                 IDS_IOS_ACCNAME_VOICE_SEARCH)];
  [voice_search_button setAccessibilityIdentifier:@"Voice Search"];

  voice_search_button.pointerInteractionEnabled = YES;
  // Make the pointer shape fit the location bar's semi-circle end shape.
  voice_search_button.pointerStyleProvider =
      CreateLiftEffectCirclePointerStyleProvider();
}

void ConfigureLensButtonAppearance(UIButton* lens_button,
                                   BOOL use_new_badge,
                                   BOOL use_color_icon) {
  lens_button.translatesAutoresizingMaskIntoConstraints = NO;

  UIButtonConfiguration* buttonConfig =
      [UIButtonConfiguration plainButtonConfiguration];
  buttonConfig.contentInsets = NSDirectionalEdgeInsetsMake(0, 0, 0, 0);
  lens_button.configuration = buttonConfig;
  lens_button.accessibilityLabel = l10n_util::GetNSString(IDS_IOS_ACCNAME_LENS);
  lens_button.accessibilityIdentifier = @"Lens";

  lens_button.pointerInteractionEnabled = YES;
  // Make the pointer shape fit the location bar's semi-circle end shape.
  lens_button.pointerStyleProvider =
      CreateLiftEffectCirclePointerStyleProvider();

  // Use a monochrome or colored symbol with no background.
  UIImage* camera_image = CustomSymbolWithPointSize(
      kCameraLensSymbol, kSymbolContentSuggestionsPointSize);
  camera_image = use_color_icon ? MakeSymbolMulticolor(camera_image)
                                : MakeSymbolMonochrome(camera_image);
  [lens_button setImage:camera_image forState:UIControlStateNormal];
  lens_button.tintColor = FakeboxIconColor();

  if (use_new_badge) {
    // Show the "New" badge and colored symbol.
    SetUpButtonWithNewFeatureBadge(lens_button);
  }
}

void ConfigureLensButtonWithNewBadgeAlpha(UIButton* lens_button,
                                          CGFloat new_badge_alpha) {
  // Fade button background.
  lens_button.backgroundColor =
      [[UIColor colorNamed:kOmniboxKeyboardButtonColor]
          colorWithAlphaComponent:new_badge_alpha];
  lens_button.layer.shadowOpacity = kButtonShadowOpacity * new_badge_alpha;

  // Scale the N badge.
  for (UIView* subview in lens_button.imageView.subviews) {
    subview.alpha = new_badge_alpha;
    subview.transform = CGAffineTransformScale(
        CGAffineTransformIdentity, new_badge_alpha, new_badge_alpha);
  }
}

UIView* NearestAncestor(UIView* view, Class of_class) {
  if (!view) {
    return nil;
  }
  if ([view isKindOfClass:of_class]) {
    return view;
  }
  return NearestAncestor([view superview], of_class);
}

UIColor* SearchHintLabelColor() {
  return [UIColor colorNamed:kGrey800Color];
}

int SetUpListTitleStringID() {
  return IsIOSTipsNotificationsEnabled() ? IDS_IOS_SET_UP_LIST_TIPS_TITLE
                                         : IDS_IOS_SET_UP_LIST_TITLE;
}

NSString* SetUpListTitleString() {
  return l10n_util::GetNSString(SetUpListTitleStringID());
}

}  // namespace content_suggestions