// 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/ntp/ui_bundled/feed_top_section/feed_top_section_view_controller.h"
#import "base/check.h"
#import "ios/chrome/browser/discover_feed/model/feed_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/ui/authentication/cells/signin_promo_view.h"
#import "ios/chrome/browser/ui/authentication/cells/signin_promo_view_configurator.h"
#import "ios/chrome/browser/ui/authentication/cells/signin_promo_view_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/discover_feed_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_top_section/notifications_promo_view.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_top_section/notifications_promo_view_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_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"
namespace {
// Content stack padding.
const CGFloat kContentStackVerticalPadding = 9;
// Content stack padding for the notifications promo view.
const CGFloat kNotificationsContentStackTopPadding = 17;
const CGFloat kNotificationsContentStackBottomPadding = 1;
// Border radius of the promo container.
const CGFloat kPromoViewContainerBorderRadius = 15;
// Returns an array of constraints to make all sides of `innerView` and
// `outerView` match, with `innerView` inset by `insets`.
NSArray<NSLayoutConstraint*>* SameConstraintsWithInsets(
id<EdgeLayoutGuideProvider> innerView,
id<EdgeLayoutGuideProvider> outerView,
NSDirectionalEdgeInsets insets) {
return @[
[innerView.leadingAnchor constraintEqualToAnchor:outerView.leadingAnchor
constant:insets.leading],
[innerView.trailingAnchor constraintEqualToAnchor:outerView.trailingAnchor
constant:-insets.trailing],
[innerView.topAnchor constraintEqualToAnchor:outerView.topAnchor
constant:insets.top],
[innerView.bottomAnchor constraintEqualToAnchor:outerView.bottomAnchor
constant:-insets.bottom],
];
}
} // namespace
@interface FeedTopSectionViewController ()
// A vertical StackView which contains all the elements of the top section.
@property(nonatomic, strong) UIStackView* contentStack;
// The promo view UIView object. Could be `SigninPromoView` or
// `NotificationsPromoView`.
@property(nonatomic, strong) UIView* promoView;
// The signin promo view.
@property(nonatomic, strong) SigninPromoView* signinPromoView;
// The notifications promo view.
@property(nonatomic, strong) NotificationsPromoView* notificationsPromoView;
// View to contain the signin promo.
@property(nonatomic, strong) UIView* promoViewContainer;
// Stores the current UI constraints for the stack view.
@property(nonatomic, strong)
NSArray<NSLayoutConstraint*>* contentStackConstraints;
@end
@implementation FeedTopSectionViewController
@synthesize visiblePromoViewType;
- (instancetype)init {
self = [super init];
if (self) {
// Create `contentStack` early so we can set up the promo before
// `viewDidLoad`.
_contentStack = [[UIStackView alloc] init];
_contentStack.translatesAutoresizingMaskIntoConstraints = NO;
_contentStack.axis = UILayoutConstraintAxisVertical;
_contentStack.distribution = UIStackViewDistributionFill;
// TODO(crbug.com/40843602): Update background color for the view.
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.contentStack];
}
#pragma mark - FeedTopSectionConsumer
// Creates the `PromoViewContainer` and adds the SignInPromo.
- (void)createPromoViewContainerForPromoType:(PromoViewType)type {
DCHECK(!self.promoViewContainer);
DCHECK(!self.promoView);
self.promoViewContainer = [[UIView alloc] init];
self.promoViewContainer.translatesAutoresizingMaskIntoConstraints = NO;
self.promoViewContainer.backgroundColor = [UIColor colorNamed:kGrey100Color];
self.promoViewContainer.layer.cornerRadius = kPromoViewContainerBorderRadius;
self.visiblePromoViewType = type;
switch (type) {
case PromoViewTypeSignin:
self.signinPromoView = [self createSigninPromoView];
self.promoView = self.signinPromoView;
break;
case PromoViewTypeNotifications:
self.notificationsPromoView = [self createNotificationsPromoView];
self.promoView = self.notificationsPromoView;
break;
}
// Add the subview to the promoViewContainer.
[self.promoViewContainer addSubview:self.promoView];
[self.contentStack addArrangedSubview:self.promoViewContainer];
AddSameConstraints(self.promoViewContainer, self.promoView);
}
- (void)updateSigninPromoWithConfigurator:
(SigninPromoViewConfigurator*)configurator {
[configurator configureSigninPromoView:self.signinPromoView
withStyle:GetTopOfFeedPromoStyle()];
}
#pragma mark - Properties
- (void)setFeedTopSectionMutator:(id<FeedTopSectionMutator>)mutator {
_feedTopSectionMutator = mutator;
self.notificationsPromoView.mutator = _feedTopSectionMutator;
}
- (void)setSigninPromoDelegate:(id<SigninPromoViewDelegate>)delegate {
_signinPromoDelegate = delegate;
self.signinPromoView.delegate = _signinPromoDelegate;
}
#pragma mark - Private
// Returns insets to add a margin around the stackview if there are items
// to display in the stackview. Otherwise returns NSDirectionalEdgeInsetsZero.
// `visible` indicates whether or not the Feed Top Section is visible.
- (NSDirectionalEdgeInsets)stackViewInsetsForTopSectionVisible:(BOOL)visible {
if (!visible) {
return NSDirectionalEdgeInsetsZero;
}
if (self.notificationsPromoView) {
return NSDirectionalEdgeInsetsMake(
kNotificationsContentStackTopPadding, kContentStackVerticalPadding,
kNotificationsContentStackBottomPadding, kContentStackVerticalPadding);
} else {
return NSDirectionalEdgeInsetsMake(
kContentStackVerticalPadding, kContentStackVerticalPadding,
kContentStackVerticalPadding, kContentStackVerticalPadding);
}
}
// Applies constraints to the stack view for a specific visibility. `visible`
// indicates whether or not the Feed Top Section is visible.
- (void)applyStackViewConstraintsForTopSectionVisible:(BOOL)visible {
if (self.contentStackConstraints) {
[NSLayoutConstraint deactivateConstraints:self.contentStackConstraints];
}
self.contentStack.hidden = !visible;
self.view.hidden = !visible;
self.contentStackConstraints = SameConstraintsWithInsets(
self.contentStack, self.view,
[self stackViewInsetsForTopSectionVisible:visible]);
[NSLayoutConstraint activateConstraints:self.contentStackConstraints];
}
- (void)showPromo {
// Hide any visible promo when `showPromo` is called to display a new one.
if (self.promoViewContainer) {
[self hidePromo];
}
// Check if the promoViewContainer does not exist. Might not exist if the
// promo has been "hidden", which involves removing the container.
if (!self.promoViewContainer) {
[self createPromoViewContainerForPromoType:self.visiblePromoViewType];
}
[self applyStackViewConstraintsForTopSectionVisible:YES];
[self.NTPDelegate updateFeedLayout];
}
- (void)hidePromo {
[self.contentStack willRemoveSubview:self.promoViewContainer];
[self.promoViewContainer willRemoveSubview:self.promoView];
[self.promoView removeFromSuperview];
[self.promoViewContainer removeFromSuperview];
self.promoViewContainer = nil;
self.promoView = nil;
self.signinPromoView = nil;
self.notificationsPromoView = nil;
[self applyStackViewConstraintsForTopSectionVisible:NO];
}
// TODO(b/312248486): Assign configurator and delegate here.
- (NotificationsPromoView*)createNotificationsPromoView {
NotificationsPromoView* promoView =
[[NotificationsPromoView alloc] initWithFrame:CGRectZero];
promoView.translatesAutoresizingMaskIntoConstraints = NO;
promoView.mutator = self.feedTopSectionMutator;
return promoView;
}
- (SigninPromoView*)createSigninPromoView {
SigninPromoView* promoView =
[[SigninPromoView alloc] initWithFrame:CGRectZero];
promoView.translatesAutoresizingMaskIntoConstraints = NO;
promoView.delegate = self.signinPromoDelegate;
SigninPromoViewConfigurator* configurator =
self.delegate.signinPromoConfigurator;
SigninPromoViewStyle promoViewStyle = GetTopOfFeedPromoStyle();
[configurator configureSigninPromoView:promoView withStyle:promoViewStyle];
promoView.textLabel.text =
l10n_util::GetNSString(IDS_IOS_SIGNIN_SHEET_LABEL_FOR_FEED_PROMO);
return promoView;
}
@end