// Copyright 2024 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/settings/notifications/notifications_banner_view_controller.h"
#import "base/apple/foundation_util.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/notreached.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/table_view/cells/table_view_header_footer_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_multi_detail_text_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_cell.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_item.h"
#import "ios/chrome/browser/shared/ui/table_view/legacy_chrome_table_view_styler.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/ui/settings/notifications/notifications_constants.h"
#import "ios/chrome/browser/ui/settings/notifications/notifications_item_identifier.h"
#import "ios/chrome/browser/ui/settings/notifications/notifications_view_controller_delegate.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/device_form_factor.h"
#import "ui/base/l10n/l10n_util_mac.h"
namespace {
enum SectionIdentifier {
kNotificationOptions,
};
// Table view separator inset.
CGFloat const kTableViewSeparatorInset = 16.0;
// Title's horizontal margin.
CGFloat const kTitleHorizontalMargin = 25.0;
// Constant for the content's width anchor.
CGFloat const kContentWidthConstant = 23.0;
// Radius size of the table view.
CGFloat const kTableViewCornerRadius = 10;
// Space above the title.
CGFloat const kSpaceAboveTitle = 20.0;
// Returns the name of the banner image above the title.
NSString* BannerImageName(bool landscape) {
#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
return landscape ? kChromeNotificationsOptInBannerLandscapeImage
: kChromeNotificationsOptInBannerImage;
#else
return landscape ? kChromiumNotificationsOptInBannerLandscapeImage
: kChromiumNotificationsOptInBannerImage;
#endif
}
} // namespace
@interface NotificationsBannerViewController () <UITableViewDelegate>
// All the items for the price notifications section received by mediator.
@property(nonatomic, strong) TableViewItem* priceTrackingItem;
// All the items for the content notifications section received by mediator.
@property(nonatomic, strong) TableViewItem* contentNotificationsItem;
// All the items for the tips notifications section received by mediator.
@property(nonatomic, strong) TableViewSwitchItem* tipsNotificationsItem;
@property(nonatomic, strong)
TableViewHeaderFooterItem* tipsNotificationsFooterItem;
@end
@implementation NotificationsBannerViewController {
UITableView* _tableView;
NSLayoutConstraint* _tableViewHeightConstraint;
UITableViewDiffableDataSource<NSNumber*, NSNumber*>* _dataSource;
NSDiffableDataSourceSnapshot* _snapshot;
ChromeTableViewStyler* _tableViewStyler;
}
- (void)viewDidLoad {
self.titleText = l10n_util::GetNSString(IDS_IOS_NOTIFICATIONS_OPT_IN_TITLE);
self.primaryActionString = @" ";
self.actionButtonsVisibility = ActionButtonsVisibility::kHidden;
self.titleHorizontalMargin = kTitleHorizontalMargin;
self.titleTopMarginWhenNoHeaderImage = kSpaceAboveTitle;
self.bannerName = BannerImageName(IsLandscape(self.view.window));
self.bannerSize = ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET
? BannerImageSizeType::kStandard
: BannerImageSizeType::kShort;
self.shouldBannerFillTopSpace = YES;
self.layoutBehindNavigationBar = YES;
self.shouldHideBanner = IsCompactHeight(self.traitCollection);
self.view.accessibilityIdentifier = kNotificationsBannerTableViewId;
_tableView = [self tableView];
[self.specificContentView addSubview:_tableView];
[NSLayoutConstraint activateConstraints:@[
[_tableView.topAnchor
constraintEqualToAnchor:self.specificContentView.topAnchor],
[_tableView.centerXAnchor
constraintEqualToAnchor:self.specificContentView.centerXAnchor],
[_tableView.widthAnchor
constraintEqualToAnchor:self.specificContentView.widthAnchor],
[_tableView.bottomAnchor
constraintLessThanOrEqualToAnchor:self.specificContentView
.bottomAnchor],
]];
[self loadModel];
if (self.navigationController &&
[self.navigationController
isKindOfClass:[SettingsNavigationController class]]) {
UIBarButtonItem* doneButton =
[(SettingsNavigationController*)self.navigationController doneButton];
self.navigationItem.rightBarButtonItem = doneButton;
self.navigationItem.largeTitleDisplayMode =
UINavigationItemLargeTitleDisplayModeNever;
}
[super viewDidLoad];
// Add constraint after it is added to hierarchy.
[NSLayoutConstraint activateConstraints:@[
[self.subtitleLabel.widthAnchor
constraintEqualToAnchor:self.view.widthAnchor
constant:-kContentWidthConstant],
]];
self.view.backgroundColor = [UIColor colorNamed:kSecondaryBackgroundColor];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self updateTableViewHeightConstraint];
self.bannerName = BannerImageName(IsLandscape(self.view.window));
}
- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
self.shouldHideBanner = IsCompactHeight(self.traitCollection);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Make the navigation bar buttons white when the banner is visible.
self.navigationController.navigationBar.tintColor =
self.shouldHideBanner ? nil : UIColor.whiteColor;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.navigationController.navigationBar.tintColor = nil;
}
#pragma mark - UIAdaptivePresentationControllerDelegate
- (BOOL)presentationControllerShouldDismiss:
(UIPresentationController*)presentationController {
return YES;
}
#pragma mark - NotificationsConsumer
- (void)reloadData {
[self loadModel];
[_tableView reloadData];
}
- (void)reconfigureCellsForItems:(NSArray*)items {
for (TableViewItem* item in items) {
NotificationsItemIdentifier itemIdentifier =
static_cast<NotificationsItemIdentifier>(item.type);
if ([self.snapshot indexOfItemIdentifier:@(itemIdentifier)] != NSNotFound) {
NSIndexPath* indexPath =
[_dataSource indexPathForItemIdentifier:@(itemIdentifier)];
UITableViewCell* cell = [_tableView cellForRowAtIndexPath:indexPath];
// `cell` may be nil if the row is not currently on screen.
if (cell) {
TableViewCell* tableViewCell =
base::apple::ObjCCastStrict<TableViewCell>(cell);
[item configureCell:tableViewCell withStyler:[self tableViewStyler]];
}
}
}
}
- (void)reloadCellsForItems:(NSArray*)items
withRowAnimation:(UITableViewRowAnimation)rowAnimation {
}
#pragma mark - SettingsControllerProtocol
- (void)reportDismissalUserAction {
base::RecordAction(
base::UserMetricsAction("MobilePriceNotificationsSettingsClose"));
}
- (void)reportBackUserAction {
base::RecordAction(
base::UserMetricsAction("MobilePriceNotificationsSettingsBack"));
}
#pragma mark - LegacyChromeTableViewController
- (void)loadModel {
__weak __typeof(self) weakSelf = self;
_dataSource = [[UITableViewDiffableDataSource alloc]
initWithTableView:_tableView
cellProvider:^UITableViewCell*(UITableView* tableView,
NSIndexPath* indexPath,
NSNumber* itemIdentifier) {
return [weakSelf
cellForTableView:tableView
indexPath:indexPath
itemIdentifier:static_cast<NotificationsItemIdentifier>(
itemIdentifier.integerValue)];
}];
RegisterTableViewCell<TableViewSwitchCell>(_tableView);
RegisterTableViewCell<TableViewMultiDetailTextCell>(_tableView);
[_dataSource applySnapshot:self.snapshot animatingDifferences:NO];
}
- (NSDiffableDataSourceSnapshot*)snapshot {
if (!_snapshot) {
_snapshot = [[NSDiffableDataSourceSnapshot alloc] init];
[_snapshot appendSectionsWithIdentifiers:@[
@(SectionIdentifier::kNotificationOptions)
]];
if ([self isContentNotificationEnabled]) {
[_snapshot appendItemsWithIdentifiers:@[
@(NotificationsItemIdentifier::ItemIdentifierContent)
]];
}
if (IsIOSTipsNotificationsEnabled()) {
[_snapshot appendItemsWithIdentifiers:@[
@(NotificationsItemIdentifier::ItemIdentifierTips)
]];
}
[_snapshot appendItemsWithIdentifiers:@[
@(NotificationsItemIdentifier::ItemIdentifierPriceTracking)
]];
}
return _snapshot;
}
#pragma mark - PromoStyleViewController
- (UIFontTextStyle)titleLabelFontTextStyle {
return UIFontTextStyleTitle2;
}
#pragma mark - UIViewController
- (void)didMoveToParentViewController:(UIViewController*)parent {
[super didMoveToParentViewController:parent];
if (!parent) {
[self.presentationDelegate notificationsBannerViewControllerDidRemove:self];
}
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
NotificationsItemIdentifier selectedItem =
static_cast<NotificationsItemIdentifier>(
[_dataSource itemIdentifierForIndexPath:indexPath].integerValue);
TableViewItem* tableItem = [self tableItemForItemIdentifier:selectedItem];
[self.modelDelegate didSelectItem:tableItem];
}
- (void)tableView:(UITableView*)tableView
willDisplayCell:(UITableViewCell*)cell
forRowAtIndexPath:(NSIndexPath*)indexPath {
cell.backgroundColor = [UIColor colorNamed:kPrimaryBackgroundColor];
}
#pragma mark - Private
// Creates the table view.
- (UITableView*)tableView {
UITableView* tableView =
[[UITableView alloc] initWithFrame:CGRectZero
style:UITableViewStylePlain];
tableView.layer.cornerRadius = kTableViewCornerRadius;
tableView.estimatedRowHeight = UITableViewAutomaticDimension;
tableView.scrollEnabled = NO;
tableView.showsVerticalScrollIndicator = NO;
tableView.delegate = self;
tableView.userInteractionEnabled = YES;
tableView.translatesAutoresizingMaskIntoConstraints = NO;
tableView.separatorInset = UIEdgeInsetsZero;
_tableViewHeightConstraint =
[tableView.heightAnchor constraintEqualToConstant:0];
_tableViewHeightConstraint.active = YES;
return tableView;
}
// Returns the TableViewItem for the given itemIdentifier.
- (TableViewItem*)tableItemForItemIdentifier:
(NotificationsItemIdentifier)itemIdentifier {
switch (itemIdentifier) {
case ItemIdentifierContent:
return self.contentNotificationsItem;
case ItemIdentifierTips:
return self.tipsNotificationsItem;
case ItemIdentifierPriceTracking:
return self.priceTrackingItem;
case ItemIdentifierTipsNotificationsFooter:
NOTREACHED();
}
}
// Configures the the table view cells.
- (UITableViewCell*)cellForTableView:(UITableView*)tableView
indexPath:(NSIndexPath*)indexPath
itemIdentifier:
(NotificationsItemIdentifier)itemIdentifier {
TableViewItem* item = [self tableItemForItemIdentifier:itemIdentifier];
UITableViewCell* cell;
if ([item isKindOfClass:[TableViewSwitchItem class]]) {
cell = [self switchCellForTableView:tableView
item:item
itemIdentifier:itemIdentifier];
} else {
cell = [self detailCellForTableView:tableView item:item];
}
// Make the separator invisible on the last row.
CGFloat separatorInset =
itemIdentifier == NotificationsItemIdentifier::ItemIdentifierMaxValue
? tableView.frame.size.width
: kTableViewSeparatorInset;
cell.separatorInset = UIEdgeInsetsMake(0.f, separatorInset, 0.f, 0.f);
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [UIColor colorNamed:kSecondaryBackgroundColor];
return cell;
}
// Returns the corresponding switch cell for the given TableViewSwitchItem
// `item`.
- (UITableViewCell*)switchCellForTableView:(UITableView*)tableView
item:(TableViewItem*)item
itemIdentifier:
(NotificationsItemIdentifier)itemIdentifier {
TableViewSwitchCell* cell =
DequeueTableViewCell<TableViewSwitchCell>(tableView);
TableViewSwitchItem* switchItem =
base::apple::ObjCCastStrict<TableViewSwitchItem>(item);
[switchItem configureCell:cell withStyler:[self tableViewStyler]];
cell.switchView.tag = itemIdentifier;
[cell.switchView addTarget:self
action:@selector(switchAction:)
forControlEvents:UIControlEventValueChanged];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
// Returns the corresponding detail cell for the given TableViewDetailIconItem
// `item`.
- (UITableViewCell*)detailCellForTableView:(UITableView*)tableView
item:(TableViewItem*)item {
TableViewMultiDetailTextCell* cell =
DequeueTableViewCell<TableViewMultiDetailTextCell>(tableView);
TableViewMultiDetailTextItem* detailItem =
base::apple::ObjCCastStrict<TableViewMultiDetailTextItem>(item);
[detailItem configureCell:cell withStyler:[self tableViewStyler]];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.accessibilityTraits |= UIAccessibilityTraitButton;
cell.accessibilityIdentifier = detailItem.accessibilityIdentifier;
return cell;
}
// Called when switch is toggled.
- (void)switchAction:(UISwitch*)sender {
TableViewItem* item =
[self tableItemForItemIdentifier:static_cast<NotificationsItemIdentifier>(
sender.tag)];
TableViewSwitchItem* switchItem =
base::apple::ObjCCastStrict<TableViewSwitchItem>(item);
DCHECK(switchItem);
[self.modelDelegate didToggleSwitchItem:switchItem withValue:sender.isOn];
}
// Updates the tableView's height constraint.
- (void)updateTableViewHeightConstraint {
_tableViewHeightConstraint.constant = _tableView.contentSize.height;
}
// ChromeTableViewStyler for the cells.
- (ChromeTableViewStyler*)tableViewStyler {
if (!_tableViewStyler) {
_tableViewStyler = [[ChromeTableViewStyler alloc] init];
_tableViewStyler.cellBackgroundColor =
[UIColor colorNamed:kPrimaryBackgroundColor];
}
return _tableViewStyler;
}
@end