// Copyright 2021 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/ntp/ui_bundled/feed_header_view_controller.h"
#import "ios/chrome/browser/shared/public/features/features.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/ui/content_suggestions/ntp_home_constant.h"
#import "ios/chrome/browser/ntp/shared/metrics/feed_metrics_recorder.h"
#import "ios/chrome/browser/ntp/ui_bundled/discover_feed_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_control_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_menu_commands.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_feature.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/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"
namespace {
// Leading margin for title label. Its used to align with the Card leading
// margin.
const CGFloat kTitleHorizontalMargin = 19;
// Trailing/leading margins for header buttons. Its used to align with the card
// margins.
const CGFloat kButtonHorizontalMargin = 14;
// Font size for the custom search engine label.
const CGFloat kCustomSearchEngineLabelFontSize = 13;
// Font size for the hidden feed label.
const CGFloat kHiddenFeedLabelFontSize = 16;
// The width of the label for when the feed is hidden.
const CGFloat kHiddenFeedLabelWidth = 250;
// Insets for header menu button.
const CGFloat kHeaderManagementButtonInset = 2;
// The height of the header container. The content is unaffected.
const CGFloat kDiscoverFeedHeaderHeight = 40;
const CGFloat kCustomSearchEngineLabelHeight = 18;
// * Values below are exclusive to Web Channels.
// The height and width of the header menu button. Based on the default
// UISegmentedControl height.
const CGFloat kButtonSize = 28;
// The radius of the dot indicating that there is new content in the Following
// feed.
const CGFloat kFollowingDotRadius = 3;
// The distance between the Following segment dot and the Following label.
const CGFloat kFollowingDotMargin = 8;
// Duration of the fade animation for elements that toggle when switching feeds.
const CGFloat kSegmentAnimationDuration = 0.3;
// Padding on top of the header.
const CGFloat kTopVerticalPaddingFollowing = 15;
const CGFloat kTopVerticalPadding = 5;
// The size of feed symbol images.
NSInteger kFeedSymbolPointSize = 17;
} // namespace
@interface FeedHeaderViewController ()
// View containing elements of the header. Handles header sizing.
@property(nonatomic, strong) UIView* container;
// Title label element for the feed.
@property(nonatomic, strong) UILabel* titleLabel;
// Button for opening top-level feed management menu.
// Redefined to not be readonly.
@property(nonatomic, strong) UIButton* managementButton;
// Button for sorting feed content. Only used for Following feed.
@property(nonatomic, strong) UIButton* sortButton;
// Segmented control for toggling between the two feeds.
@property(nonatomic, strong) UISegmentedControl* segmentedControl;
// The dot in the Following segment indicating new content in the Following
// feed.
@property(nonatomic, strong) UIView* followingDot;
// The blurred background of the feed header.
@property(nonatomic, strong) UIVisualEffectView* blurBackgroundView;
// The view informing the user that the feed is powered by Google if they don't
// have Google as their default search engine.
@property(nonatomic, strong) UILabel* customSearchEngineView;
// The label for when the feed visibility is disabled.
@property(nonatomic, strong) UILabel* hiddenFeedLabel;
// The constraints for the currently visible components of the header.
@property(nonatomic, strong)
NSMutableArray<NSLayoutConstraint*>* feedHeaderConstraints;
// Whether the Following segment dot should currently be visible.
@property(nonatomic, assign) BOOL followingDotVisible;
@end
@implementation FeedHeaderViewController
- (instancetype)initWithFollowingDotVisible:(BOOL)followingDotVisible {
self = [super initWithNibName:nil bundle:nil];
if (self) {
_followingDotVisible = followingDotVisible;
// The menu button is created early so that it can be assigned a tap action
// before the view loads.
_managementButton = [[UIButton alloc] init];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// The background color will be clear for continuity with the overall NTP
// gradient view.
self.view.backgroundColor = [UIColor clearColor];
self.view.maximumContentSizeCategory =
UIContentSizeCategoryAccessibilityMedium;
self.view.accessibilityLabel = kNTPFeedHeaderIdentifier;
self.container = [[UIView alloc] init];
self.view.translatesAutoresizingMaskIntoConstraints = NO;
self.container.translatesAutoresizingMaskIntoConstraints = NO;
if (!IsHomeCustomizationEnabled()) {
[self configureManagementButton:self.managementButton];
}
[self configureHeaderViews];
[self.view addSubview:self.container];
[self applyHeaderConstraints];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
if ([self.feedControlDelegate isFollowingFeedAvailable]) {
[self updateSegmentedControlFont:self.segmentedControl];
} else {
UIFont* font = [self fontForTitleLabel];
self.titleLabel.font = font;
}
}
#pragma mark - UITraitEnvironment
- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (previousTraitCollection.preferredContentSizeCategory !=
self.traitCollection.preferredContentSizeCategory) {
[self.view setNeedsLayout];
}
}
#pragma mark - Public
- (void)toggleBackgroundBlur:(BOOL)blurred animated:(BOOL)animated {
if (UIAccessibilityIsReduceTransparencyEnabled() ||
![self.feedControlDelegate isFollowingFeedAvailable] ||
!self.blurBackgroundView) {
return;
}
// Applies blur to header background.
if (!animated) {
self.blurBackgroundView.hidden = !blurred;
self.view.backgroundColor = [self backgroundColorForBlurredState:blurred];
return;
}
[UIView transitionWithView:self.blurBackgroundView
duration:0.3
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.blurBackgroundView.hidden = !blurred;
}
completion:^(BOOL finished) {
// Only reduce opacity after the animation is complete to avoid showing
// content suggestions tiles momentarily.
self.view.backgroundColor =
[self backgroundColorForBlurredState:blurred];
}];
}
- (CGFloat)feedHeaderHeight {
return [self.feedControlDelegate isFollowingFeedAvailable]
? FollowingFeedHeaderHeight()
: kDiscoverFeedHeaderHeight;
}
- (CGFloat)customSearchEngineViewHeight {
return [self.NTPDelegate isGoogleDefaultSearchEngine] ||
![self.feedControlDelegate isFollowingFeedAvailable]
? 0
: kCustomSearchEngineLabelHeight;
}
- (void)updateFollowingDotForUnseenContent:(BOOL)hasUnseenContent {
DCHECK([self.feedControlDelegate isFollowingFeedAvailable]);
// Don't show the dot if the user is already on the Following feed.
if ([self.feedControlDelegate selectedFeed] == FeedTypeFollowing) {
self.followingDotVisible = NO;
return;
}
self.followingDotVisible = hasUnseenContent;
[UIView animateWithDuration:kSegmentAnimationDuration
animations:^{
self.followingDot.alpha = hasUnseenContent ? 1 : 0;
}];
}
- (void)updateForDefaultSearchEngineChanged {
if (!self.viewLoaded) {
return;
}
if (![self.feedControlDelegate isFollowingFeedAvailable]) {
[self.titleLabel setText:[self feedHeaderTitleText]];
[self.titleLabel setNeedsDisplay];
return;
}
if ([self.NTPDelegate isGoogleDefaultSearchEngine]) {
[self removeCustomSearchEngineView];
} else {
[self addCustomSearchEngineView];
}
[self applyHeaderConstraints];
}
- (void)updateForFeedVisibilityChanged {
// When feed visibility changes, the menu content is recreated.
[self.feedMenuHandler configureManagementMenu:self.managementButton];
if (![self.feedControlDelegate isFollowingFeedAvailable]) {
[self.titleLabel setText:[self feedHeaderTitleText]];
[self.titleLabel setNeedsDisplay];
return;
}
[self resetView];
if ([self.feedControlDelegate shouldFeedBeVisible]) {
[self addViewsForVisibleFeed];
} else {
[self addViewsForHiddenFeed];
}
[self applyHeaderConstraints];
}
- (void)updateForFollowingFeedVisibilityChanged {
[self resetView];
[self.titleLabel removeFromSuperview];
// The management button is different for the Following feed header, so it's
// recreated.
if (self.managementButton) {
[self.managementButton removeFromSuperview];
self.managementButton = nil;
}
if (!IsHomeCustomizationEnabled()) {
self.managementButton = [[UIButton alloc] init];
[self configureManagementButton:self.managementButton];
[self.feedMenuHandler configureManagementMenu:self.managementButton];
}
[self configureHeaderViews];
[self applyHeaderConstraints];
}
- (void)updateForSelectedFeed {
FeedType selectedFeed = [self.feedControlDelegate selectedFeed];
self.segmentedControl.selectedSegmentIndex =
static_cast<NSInteger>(selectedFeed);
if (!IsFollowUIUpdateEnabled()) {
self.sortButton.alpha = selectedFeed == FeedTypeDiscover ? 0 : 1;
}
}
#pragma mark - Setters
// Sets `followingFeedSortType` and recreates the sort menu to assign the active
// sort type.
- (void)setFollowingFeedSortType:(FollowingFeedSortType)followingFeedSortType {
CHECK(!IsFollowUIUpdateEnabled());
_followingFeedSortType = followingFeedSortType;
if (self.sortButton) {
self.sortButton.menu = [self createSortMenu];
}
}
#pragma mark - Private
- (void)configureHeaderViews {
if ([self.feedControlDelegate isFollowingFeedAvailable]) {
if ([self.feedControlDelegate shouldFeedBeVisible]) {
[self addViewsForVisibleFeed];
} else {
[self addViewsForHiddenFeed];
}
if (![self.NTPDelegate isGoogleDefaultSearchEngine]) {
[self addCustomSearchEngineView];
}
} else {
self.titleLabel = [self createTitleLabel];
[self.container addSubview:self.titleLabel];
}
if (!IsHomeCustomizationEnabled()) {
[self.feedMenuHandler configureManagementMenu:self.managementButton];
}
}
// Creates sort menu with its content and active sort type.
- (UIMenu*)createSortMenu {
NSMutableArray<UIAction*>* sortActions = [NSMutableArray array];
// Create menu actions.
UIAction* sortByPublisherAction = [UIAction
actionWithTitle:l10n_util::GetNSString(IDS_IOS_FEED_SORT_PUBLISHER)
image:nil
identifier:nil
handler:^(UIAction* action) {
[self.feedControlDelegate handleSortTypeForFollowingFeed:
FollowingFeedSortTypeByPublisher];
}];
[sortActions addObject:sortByPublisherAction];
UIAction* sortByLatestAction = [UIAction
actionWithTitle:l10n_util::GetNSString(IDS_IOS_FEED_SORT_LATEST)
image:nil
identifier:nil
handler:^(UIAction* action) {
[self.feedControlDelegate handleSortTypeForFollowingFeed:
FollowingFeedSortTypeByLatest];
}];
[sortActions addObject:sortByLatestAction];
// Set active sorting.
switch (self.followingFeedSortType) {
case FollowingFeedSortTypeByLatest:
sortByLatestAction.state = UIMenuElementStateOn;
break;
case FollowingFeedSortTypeByPublisher:
default:
sortByPublisherAction.state = UIMenuElementStateOn;
}
return [UIMenu menuWithTitle:@"" children:sortActions];
}
// Configures the feed header's menu button.
- (void)configureManagementButton:(UIButton*)managementButton {
UIButtonConfiguration* buttonConfiguration =
[UIButtonConfiguration plainButtonConfiguration];
managementButton.translatesAutoresizingMaskIntoConstraints = NO;
managementButton.showsMenuAsPrimaryAction = YES;
[managementButton addTarget:self.feedMenuHandler
action:@selector(configureManagementMenu:)
forControlEvents:UIControlEventTouchDown];
managementButton.accessibilityIdentifier =
kNTPFeedHeaderManagementButtonIdentifier;
managementButton.accessibilityLabel =
l10n_util::GetNSString(IDS_IOS_DISCOVER_FEED_MENU_ACCESSIBILITY_LABEL);
if ([self.feedControlDelegate isFollowingFeedAvailable]) {
buttonConfiguration.image =
DefaultSymbolTemplateWithPointSize(kMenuSymbol, kFeedSymbolPointSize);
managementButton.clipsToBounds = YES;
} else {
UIImage* menuIcon = DefaultSymbolTemplateWithPointSize(
kSettingsFilledSymbol, kFeedSymbolPointSize);
buttonConfiguration.image = menuIcon;
buttonConfiguration.baseForegroundColor =
[UIColor colorNamed:kGrey600Color];
buttonConfiguration.imagePadding = kHeaderManagementButtonInset;
}
[self.container addSubview:managementButton];
managementButton.configuration = buttonConfiguration;
}
// Configures and returns the feed header's sorting button.
- (UIButton*)createSortButton {
DCHECK([self.feedControlDelegate isFollowingFeedAvailable]);
UIButton* sortButton = [[UIButton alloc] init];
sortButton.translatesAutoresizingMaskIntoConstraints = NO;
sortButton.accessibilityIdentifier = kNTPFeedHeaderSortButtonIdentifier;
sortButton.accessibilityLabel =
l10n_util::GetNSString(IDS_IOS_FEED_SORT_ACCESSIBILITY_LABEL);
[sortButton setImage:DefaultSymbolTemplateWithPointSize(kSortSymbol,
kFeedSymbolPointSize)
forState:UIControlStateNormal];
sortButton.showsMenuAsPrimaryAction = YES;
// The sort button is only visible if the Following feed is selected, and if
// the Follow UI update is not enabled.
if (IsFollowUIUpdateEnabled()) {
sortButton.alpha = 0;
} else {
sortButton.alpha =
[self.feedControlDelegate selectedFeed] == FeedTypeDiscover ? 0 : 1;
}
sortButton.configuration = [UIButtonConfiguration plainButtonConfiguration];
return sortButton;
}
// Configures and returns the feed header's title label.
- (UILabel*)createTitleLabel {
UILabel* titleLabel = [[UILabel alloc] init];
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
titleLabel.font = [self fontForTitleLabel];
titleLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
titleLabel.accessibilityIdentifier =
ntp_home::DiscoverHeaderTitleAccessibilityID();
titleLabel.text = [self feedHeaderTitleText];
return titleLabel;
}
// Configures and returns the segmented control for toggling between feeds.
- (UISegmentedControl*)createSegmentedControl {
// Create segmented control with labels.
NSArray* headerLabels = [NSArray
arrayWithObjects:l10n_util::GetNSString(IDS_IOS_DISCOVER_FEED_TITLE),
l10n_util::GetNSString(IDS_IOS_FOLLOWING_FEED_TITLE),
nil];
UISegmentedControl* segmentedControl =
[[UISegmentedControl alloc] initWithItems:headerLabels];
segmentedControl.translatesAutoresizingMaskIntoConstraints = NO;
[segmentedControl setApportionsSegmentWidthsByContent:NO];
[self updateSegmentedControlFont:segmentedControl];
// Set selected feed and tap action.
segmentedControl.selectedSegmentIndex =
static_cast<NSInteger>([self.feedControlDelegate selectedFeed]);
[segmentedControl addTarget:self
action:@selector(onSegmentSelected:)
forControlEvents:UIControlEventValueChanged];
segmentedControl.accessibilityIdentifier =
kNTPFeedHeaderSegmentedControlIdentifier;
return segmentedControl;
}
- (UIFont*)fontForTitleLabel {
return CreateDynamicFont(UIFontTextStyleFootnote, UIFontWeightSemibold,
self.view);
}
- (UIFont*)fontForSegmentedControl {
return CreateDynamicFont(UIFontTextStyleSubheadline, UIFontWeightMedium,
self.view);
}
// Configures and returns the dot indicating that there is new content in the
// Following feed.
- (UIView*)createFollowingDot {
UIView* followingDot = [[UIView alloc] init];
followingDot.layer.cornerRadius = kFollowingDotRadius;
followingDot.backgroundColor = [UIColor colorNamed:kBlue500Color];
followingDot.translatesAutoresizingMaskIntoConstraints = NO;
return followingDot;
}
// Configures and returns the blurred background of the feed header.
- (UIVisualEffectView*)createBlurBackground {
UIBlurEffect* blurEffect =
[UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemMaterial];
UIVisualEffectView* blurBackgroundView =
[[UIVisualEffectView alloc] initWithEffect:blurEffect];
blurBackgroundView.translatesAutoresizingMaskIntoConstraints = NO;
return blurBackgroundView;
}
// Configures and returns the label for when the feed visibility is
// disabled.
- (UILabel*)createHiddenFeedLabel {
UILabel* hiddenFeedLabel = [[UILabel alloc] init];
[hiddenFeedLabel setText:l10n_util::GetNSString(
IDS_IOS_DISCOVER_FEED_HEADER_TURNED_OFF_LABEL)];
hiddenFeedLabel.translatesAutoresizingMaskIntoConstraints = NO;
hiddenFeedLabel.font = [UIFont systemFontOfSize:kHiddenFeedLabelFontSize];
hiddenFeedLabel.textColor = [UIColor colorNamed:kGrey600Color];
hiddenFeedLabel.numberOfLines = 0;
hiddenFeedLabel.textAlignment = NSTextAlignmentCenter;
return hiddenFeedLabel;
}
// Updates the font and color of the segmented control header to adapt to the
// current dynamic sizing.
- (void)updateSegmentedControlFont:(UISegmentedControl*)segmentedControl {
NSDictionary* normalAttributes = [NSDictionary
dictionaryWithObjectsAndKeys:[self fontForSegmentedControl],
NSFontAttributeName,
[UIColor colorNamed:kTextSecondaryColor],
NSForegroundColorAttributeName, nil];
[segmentedControl setTitleTextAttributes:normalAttributes
forState:UIControlStateNormal];
NSDictionary* selectedAttributes = [NSDictionary
dictionaryWithObjectsAndKeys:[self fontForSegmentedControl],
NSFontAttributeName,
[UIColor colorNamed:kTextPrimaryColor],
NSForegroundColorAttributeName, nil];
[segmentedControl setTitleTextAttributes:selectedAttributes
forState:UIControlStateSelected];
}
- (void)addCustomSearchEngineView {
if (self.customSearchEngineView) {
[self removeCustomSearchEngineView];
}
self.customSearchEngineView = [[UILabel alloc] init];
self.customSearchEngineView.text =
l10n_util::GetNSString(IDS_IOS_FEED_CUSTOM_SEARCH_ENGINE_LABEL);
self.customSearchEngineView.font =
[UIFont systemFontOfSize:kCustomSearchEngineLabelFontSize];
self.customSearchEngineView.textColor = [UIColor colorNamed:kGrey500Color];
self.customSearchEngineView.translatesAutoresizingMaskIntoConstraints = NO;
self.customSearchEngineView.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:self.customSearchEngineView];
}
- (void)removeCustomSearchEngineView {
[self.customSearchEngineView removeFromSuperview];
self.customSearchEngineView = nil;
}
// Applies constraints for the feed header elements' positioning.
- (void)applyHeaderConstraints {
// Remove previous constraints if they were already set.
if (self.feedHeaderConstraints) {
[NSLayoutConstraint deactivateConstraints:self.feedHeaderConstraints];
self.feedHeaderConstraints = nil;
}
self.feedHeaderConstraints = [[NSMutableArray alloc] init];
CGFloat totalHeaderHeight =
[self feedHeaderHeight] + [self customSearchEngineViewHeight];
totalHeaderHeight += [self.feedControlDelegate isFollowingFeedAvailable]
? kTopVerticalPaddingFollowing
: kTopVerticalPadding;
// Anchor container.
[self.feedHeaderConstraints addObjectsFromArray:@[
// Anchor container and menu button.
[self.view.heightAnchor constraintEqualToConstant:totalHeaderHeight],
[self.container.heightAnchor
constraintEqualToConstant:[self feedHeaderHeight]],
[self.container.bottomAnchor
constraintEqualToAnchor:self.view.bottomAnchor],
[self.container.centerXAnchor
constraintEqualToAnchor:self.view.centerXAnchor],
[self.container.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
]];
if (!IsHomeCustomizationEnabled()) {
// Anchor management button.
[self.feedHeaderConstraints addObjectsFromArray:@[
[self.managementButton.trailingAnchor
constraintEqualToAnchor:self.container.trailingAnchor
constant:-kButtonHorizontalMargin],
[self.managementButton.centerYAnchor
constraintEqualToAnchor:self.container.centerYAnchor],
// Set menu button size.
[self.managementButton.heightAnchor
constraintEqualToConstant:kButtonSize],
[self.managementButton.widthAnchor constraintEqualToConstant:kButtonSize],
]];
}
if ([self.feedControlDelegate isFollowingFeedAvailable]) {
// Anchor views based on the feed being visible or hidden.
if ([self.feedControlDelegate shouldFeedBeVisible]) {
[self anchorSegmentedControlAndDot];
// Anchor sort button.
[self.feedHeaderConstraints addObjectsFromArray:@[
[self.sortButton.heightAnchor constraintEqualToConstant:kButtonSize],
[self.sortButton.widthAnchor constraintEqualToConstant:kButtonSize],
[self.sortButton.leadingAnchor
constraintEqualToAnchor:self.container.leadingAnchor
constant:kButtonHorizontalMargin],
[self.sortButton.centerYAnchor
constraintEqualToAnchor:self.container.centerYAnchor],
]];
// Anchor blur background view if reduce transparency is disabled.
if (self.blurBackgroundView) {
[self.feedHeaderConstraints addObjectsFromArray:@[
[self.blurBackgroundView.trailingAnchor
constraintEqualToAnchor:self.view.trailingAnchor],
[self.blurBackgroundView.leadingAnchor
constraintEqualToAnchor:self.view.leadingAnchor],
[self.blurBackgroundView.topAnchor
constraintEqualToAnchor:self.container.topAnchor],
[self.blurBackgroundView.bottomAnchor
constraintEqualToAnchor:self.container.bottomAnchor],
]];
}
} else {
[self.feedHeaderConstraints addObjectsFromArray:@[
[self.hiddenFeedLabel.centerXAnchor
constraintEqualToAnchor:self.container.centerXAnchor],
[self.hiddenFeedLabel.centerYAnchor
constraintEqualToAnchor:self.container.centerYAnchor],
[self.hiddenFeedLabel.widthAnchor
constraintEqualToConstant:kHiddenFeedLabelWidth],
]];
}
// If Google is not the default search engine, anchor the custom search
// engine view.
if (![self.NTPDelegate isGoogleDefaultSearchEngine] &&
[self.feedControlDelegate shouldFeedBeVisible]) {
[self.feedHeaderConstraints addObjectsFromArray:@[
// Anchors custom search engine view.
[self.customSearchEngineView.widthAnchor
constraintEqualToAnchor:self.view.widthAnchor],
[self.customSearchEngineView.heightAnchor
constraintEqualToConstant:kCustomSearchEngineLabelHeight],
[self.customSearchEngineView.bottomAnchor
constraintEqualToAnchor:self.container.topAnchor],
]];
}
} else {
[self.feedHeaderConstraints addObjectsFromArray:@[
// Anchors title label.
[self.titleLabel.leadingAnchor
constraintEqualToAnchor:self.container.leadingAnchor
constant:kTitleHorizontalMargin],
[self.titleLabel.trailingAnchor
constraintLessThanOrEqualToAnchor:IsHomeCustomizationEnabled()
? self.container.trailingAnchor
: self.managementButton
.leadingAnchor],
[self.titleLabel.centerYAnchor
constraintEqualToAnchor:self.container.centerYAnchor],
]];
}
[NSLayoutConstraint activateConstraints:self.feedHeaderConstraints];
}
// Anchors the segmented control and the unseen content dot.
- (void)anchorSegmentedControlAndDot {
// Anchor segmented control.
[self.feedHeaderConstraints addObjectsFromArray:@[
[self.segmentedControl.centerXAnchor
constraintEqualToAnchor:self.container.centerXAnchor],
[self.segmentedControl.centerYAnchor
constraintEqualToAnchor:self.container.centerYAnchor],
[self.segmentedControl.leadingAnchor
constraintEqualToAnchor:self.sortButton.trailingAnchor
constant:kButtonHorizontalMargin],
]];
if (IsHomeCustomizationEnabled()) {
[self.feedHeaderConstraints addObjectsFromArray:@[
[self.segmentedControl.trailingAnchor
constraintLessThanOrEqualToAnchor:self.container.leadingAnchor],
]];
} else {
[self.feedHeaderConstraints addObjectsFromArray:@[
[self.segmentedControl.trailingAnchor
constraintLessThanOrEqualToAnchor:self.managementButton.leadingAnchor
constant:-kButtonHorizontalMargin],
]];
}
// Set Following segment dot size.
[self.feedHeaderConstraints addObjectsFromArray:@[
[self.followingDot.heightAnchor
constraintEqualToConstant:kFollowingDotRadius * 2],
[self.followingDot.widthAnchor
constraintEqualToConstant:kFollowingDotRadius * 2],
]];
// Find the "Following" label within the segmented control, since it is not
// exposed by UISegmentedControl. First loop iterates through UISegments, and
// next loop iterates to find their nested UISegmentLabels.
UILabel* followingLabel;
for (UIView* view in self.segmentedControl.subviews) {
for (UIView* subview in view.subviews) {
if ([NSStringFromClass([subview class])
isEqualToString:@"UISegmentLabel"]) {
UILabel* currentLabel = static_cast<UILabel*>(subview);
if ([currentLabel.text
isEqualToString:l10n_util::GetNSString(
IDS_IOS_FOLLOWING_FEED_TITLE)]) {
followingLabel = currentLabel;
break;
}
}
}
}
// If the label was found, anchor the dot to it. Otherwise, anchor the dot
// to the top corner of the segmented control.
if (followingLabel) {
[self.feedHeaderConstraints addObjectsFromArray:@[
// Anchor Following segment dot to label text.
[self.followingDot.leftAnchor
constraintEqualToAnchor:followingLabel.rightAnchor
constant:kFollowingDotMargin],
[self.followingDot.bottomAnchor
constraintEqualToAnchor:followingLabel.topAnchor
constant:kFollowingDotMargin],
]];
} else {
[self.feedHeaderConstraints addObjectsFromArray:@[
// Anchor Following segment dot to top corner.
[self.followingDot.rightAnchor
constraintEqualToAnchor:self.segmentedControl.rightAnchor
constant:-kFollowingDotMargin],
[self.followingDot.topAnchor
constraintEqualToAnchor:self.segmentedControl.topAnchor
constant:kFollowingDotMargin],
]];
}
}
// Adds views that only appear when the feed visibility is enabled.
- (void)addViewsForVisibleFeed {
self.segmentedControl = [self createSegmentedControl];
[self.container addSubview:self.segmentedControl];
self.followingDot = [self createFollowingDot];
self.followingDot.alpha = self.followingDotVisible ? 1 : 0;
[self.segmentedControl addSubview:self.followingDot];
self.sortButton = [self createSortButton];
// If the Follow UI update is enabled, we still create the sort button to help
// anchor the segmented control. However, the sort button remains invisible
// and the menu is not created.
if (!IsFollowUIUpdateEnabled()) {
self.sortButton.menu = [self createSortMenu];
}
[self.container addSubview:self.sortButton];
if (!UIAccessibilityIsReduceTransparencyEnabled()) {
self.blurBackgroundView = [self createBlurBackground];
[self.view addSubview:self.blurBackgroundView];
[self.view sendSubviewToBack:self.blurBackgroundView];
// The blurred background has a tint that is visible when the header is
// over the standard NTP background. For this reason, we only add the blur
// background when scrolled into the feed.
self.blurBackgroundView.hidden = YES;
}
if (![self.NTPDelegate isGoogleDefaultSearchEngine]) {
[self addCustomSearchEngineView];
}
}
// Adds views that only appear when the feed visibility is disabled.
- (void)addViewsForHiddenFeed {
self.hiddenFeedLabel = [self createHiddenFeedLabel];
[self.container addSubview:self.hiddenFeedLabel];
}
// Removes the subviews from the header.
- (void)resetView {
if (self.hiddenFeedLabel) {
[self.hiddenFeedLabel removeFromSuperview];
self.hiddenFeedLabel = nil;
}
if (self.followingDot) {
[self.followingDot removeFromSuperview];
self.followingDot = nil;
}
if (self.segmentedControl) {
[self.segmentedControl removeFromSuperview];
self.segmentedControl = nil;
}
if (self.sortButton) {
[self.sortButton removeFromSuperview];
self.sortButton = nil;
}
if (self.customSearchEngineView) {
[self removeCustomSearchEngineView];
}
}
// Handles a new feed being selected from the header.
- (void)onSegmentSelected:(UISegmentedControl*)segmentedControl {
switch (segmentedControl.selectedSegmentIndex) {
case static_cast<NSInteger>(FeedTypeDiscover): {
[self.feedMetricsRecorder
recordFeedSelected:FeedTypeDiscover
fromPreviousFeedPosition:[self.feedControlDelegate
lastVisibleFeedCardIndex]];
[self.feedControlDelegate handleFeedSelected:FeedTypeDiscover];
[UIView animateWithDuration:kSegmentAnimationDuration
animations:^{
self.sortButton.alpha = 0;
}];
break;
}
case static_cast<NSInteger>(FeedTypeFollowing): {
[self.feedMetricsRecorder
recordFeedSelected:FeedTypeFollowing
fromPreviousFeedPosition:[self.feedControlDelegate
lastVisibleFeedCardIndex]];
[self.feedControlDelegate handleFeedSelected:FeedTypeFollowing];
// Only show sorting button for Following feed.
[UIView animateWithDuration:kSegmentAnimationDuration
animations:^{
self.sortButton.alpha =
IsFollowUIUpdateEnabled() ? 0 : 1;
}];
break;
}
}
}
// The title text for the Discover feed header based on user prefs.
- (NSString*)feedHeaderTitleText {
DCHECK(![self.feedControlDelegate isFollowingFeedAvailable]);
// Set the title based on the default search engine.
NSString* feedHeaderTitleText =
[self.NTPDelegate isGoogleDefaultSearchEngine]
? l10n_util::GetNSString(IDS_IOS_DISCOVER_FEED_TITLE)
: l10n_util::GetNSString(IDS_IOS_DISCOVER_FEED_TITLE_NON_DSE);
// Append the title text if the feed is turned off.
if (![self.feedControlDelegate shouldFeedBeVisible]) {
feedHeaderTitleText =
[NSString stringWithFormat:@"%@ – %@", feedHeaderTitleText,
l10n_util::GetNSString(
IDS_IOS_DISCOVER_FEED_TITLE_OFF_LABEL)];
}
return feedHeaderTitleText;
}
// Returns the background color for this view.
// Applies an opacity to the background. If ReduceTransparency is enabled,
// then this replaces the blur effect.
// With the Magic Stack enabled, the background color will
// be clear for continuity with the overall NTP gradient view.
- (UIColor*)backgroundColorForBlurredState:(BOOL)blurred {
if (blurred) {
return [[UIColor colorNamed:kBackgroundColor] colorWithAlphaComponent:0.1];
} else {
return [UIColor clearColor];
}
}
@end