chromium/ios/chrome/browser/home_customization/ui/home_customization_toggle_cell.mm

// 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/home_customization/ui/home_customization_toggle_cell.h"

#import "base/check.h"
#import "ios/chrome/browser/home_customization/ui/home_customization_mutator.h"
#import "ios/chrome/browser/home_customization/utils/home_customization_constants.h"
#import "ios/chrome/browser/home_customization/utils/home_customization_helper.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_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/common/ui/util/ui_util.h"

namespace {

// The border radius for the entire cell.
const CGFloat kContainerBorderRadius = 12;

// The margins of the container.
const CGFloat kHorizontalMargin = 12;
const CGFloat kVerticalMargin = 8;

// The width of the main icon container.
const CGFloat kIconImageViewWidth = 32;

// The width of the navigation icon container.
const CGFloat kNavigationIconImageViewWidth = 16;

}  // namespace

@implementation HomeCustomizationToggleCell {
  // The type for this toggle cell, indicating what module it represents.
  CustomizationToggleType _type;

  // The horizontal stack view containing all the cell's content.
  UIStackView* _contentStackView;

  // The horizontal stack view containing the content which can be tapped to
  // navigate to its submenu.
  UIStackView* _navigableStackView;

  // The text stack view containing the title and subtitle.
  UIStackView* _textStackView;
  UILabel* _title;
  UILabel* _subtitle;

  // The main icon representing what the cell controls.
  UIView* _iconContainer;
  UIImageView* _iconImageView;

  // The navigation icon, visible if the cell can be tapped to access a submenu.
  UIView* _navigationIconContainer;
  UIImageView* _navigationImageView;
  UIView* _navigationSeparator;

  // The switch to toggle module visibility.
  UISwitch* _switch;

  // The tap recognizer for navigating to the cell's submenu, if supported.
  UITapGestureRecognizer* _tapRecognizer;
}

- (instancetype)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    // Configure `contentView`, representing the overall container.
    self.contentView.backgroundColor = [UIColor colorNamed:kGrey100Color];
    self.contentView.layer.cornerRadius = kContainerBorderRadius;
    self.contentView.layoutMargins = UIEdgeInsetsMake(
        kVerticalMargin, kHorizontalMargin, kVerticalMargin, kHorizontalMargin);

    // Sets the title, subtitle and stack view to lay them out vertically.
    _title = [[UILabel alloc] init];
    _title.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    _title.numberOfLines = 0;
    _title.textColor = [UIColor colorNamed:kTextPrimaryColor];

    _subtitle = [[UILabel alloc] init];
    _subtitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
    _subtitle.numberOfLines = 2;
    _subtitle.textColor = [UIColor colorNamed:kTextSecondaryColor];

    _textStackView =
        [[UIStackView alloc] initWithArrangedSubviews:@[ _title, _subtitle ]];
    _textStackView.translatesAutoresizingMaskIntoConstraints = NO;
    _textStackView.axis = UILayoutConstraintAxisVertical;
    _textStackView.alignment = UIStackViewAlignmentLeading;
    _textStackView.spacing = 0;

    // Creates the image with a placeholder icon which will be replaced when the
    // cell is configured.
    _iconContainer = [[UIView alloc] init];
    _iconImageView = [[UIImageView alloc]
        initWithImage:DefaultSymbolWithPointSize(kSliderHorizontalSymbol,
                                                 kToggleIconPointSize)];
    _iconImageView.tintColor = [UIColor colorNamed:kTextPrimaryColor];
    [_iconContainer addSubview:_iconImageView];
    _iconContainer.translatesAutoresizingMaskIntoConstraints = NO;
    _iconImageView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
      [_iconContainer.widthAnchor
          constraintEqualToConstant:kIconImageViewWidth],
      [_iconContainer.centerXAnchor
          constraintEqualToAnchor:_iconImageView.centerXAnchor],
      [_iconContainer.centerYAnchor
          constraintEqualToAnchor:_iconImageView.centerYAnchor],
    ]];

    // Creates navigation icon, switch and stack view to lay them out
    // horizontally.
    _navigationIconContainer = [[UIView alloc] init];
    _navigationImageView = [[UIImageView alloc]
        initWithImage:DefaultSymbolWithPointSize(kChevronForwardSymbol,
                                                 kToggleIconPointSize)];
    _navigationImageView.tintColor = [UIColor colorNamed:kTextQuaternaryColor];
    [_navigationIconContainer addSubview:_navigationImageView];
    _navigationIconContainer.translatesAutoresizingMaskIntoConstraints = NO;
    _navigationImageView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
      [_navigationIconContainer.widthAnchor
          constraintEqualToConstant:kNavigationIconImageViewWidth],
      [_navigationIconContainer.centerXAnchor
          constraintEqualToAnchor:_navigationImageView.centerXAnchor],
      [_navigationIconContainer.centerYAnchor
          constraintEqualToAnchor:_navigationImageView.centerYAnchor],
    ]];

    _switch = [[UISwitch alloc] init];
    [_switch addTarget:self
                  action:@selector(toggleModuleVisibility:)
        forControlEvents:UIControlEventValueChanged];

    _navigationSeparator = [[UIView alloc] init];
    _navigationSeparator.translatesAutoresizingMaskIntoConstraints = NO;
    _navigationSeparator.backgroundColor = [UIColor colorNamed:kGrey200Color];

    _navigableStackView = [[UIStackView alloc] initWithArrangedSubviews:@[
      _iconContainer,
      _textStackView,
      _navigationIconContainer,
      _navigationSeparator,
    ]];
    _navigableStackView.translatesAutoresizingMaskIntoConstraints = NO;
    _navigableStackView.axis = UILayoutConstraintAxisHorizontal;
    _navigableStackView.alignment = UIStackViewAlignmentCenter;
    _navigableStackView.spacing = UIStackViewSpacingUseSystem;

    // Anchor separator to the full height of the cell, which includes the
    // margin.
    [NSLayoutConstraint activateConstraints:@[
      [_navigationSeparator.widthAnchor constraintEqualToConstant:1],
      [_navigationSeparator.heightAnchor
          constraintEqualToAnchor:_navigableStackView.heightAnchor
                         constant:kVerticalMargin * 2],
    ]];

    _contentStackView = [[UIStackView alloc]
        initWithArrangedSubviews:@[ _navigableStackView, _switch ]];
    _contentStackView.translatesAutoresizingMaskIntoConstraints = NO;
    _contentStackView.axis = UILayoutConstraintAxisHorizontal;
    _contentStackView.alignment = UIStackViewAlignmentCenter;
    _contentStackView.spacing = UIStackViewSpacingUseSystem;

    [self.contentView addSubview:_contentStackView];

    AddSameConstraints(_contentStackView, self.contentView.layoutMarginsGuide);
  }
  return self;
}

#pragma mark - Public

- (void)configureCellWithType:(CustomizationToggleType)type
                      enabled:(BOOL)enabled {
  [self prepareForReuse];

  _type = type;

  _title.text = [HomeCustomizationHelper titleForToggleType:type];
  _subtitle.text = [HomeCustomizationHelper subtitleForToggleType:type];
  _iconImageView.image = [HomeCustomizationHelper iconForToggleType:type];
  _switch.on = enabled;

  if ([HomeCustomizationHelper doesTypeHaveSubmenu:type]) {
    _tapRecognizer = [[UITapGestureRecognizer alloc]
        initWithTarget:self
                action:@selector(navigateToSubmenu:)];
    [_navigableStackView addGestureRecognizer:_tapRecognizer];
  }

  [self updateNavigationIconForSwitchEnabled:enabled];

  self.accessibilityIdentifier =
      [HomeCustomizationHelper accessibilityIdentifierForToggleType:type];

  _navigableStackView.accessibilityIdentifier = [HomeCustomizationHelper
      navigableAccessibilityIdentifierForToggleType:type];
}

#pragma mark - Private

// Prepares the cell for reuse by the collection view.
- (void)prepareForReuse {
  [super prepareForReuse];
  self.accessibilityIdentifier = nil;
  _title.text = nil;
  _subtitle.text = nil;
  _iconImageView.image = nil;
  _switch.on = NO;
  _navigationImageView.hidden = NO;
  [_navigableStackView removeGestureRecognizer:_tapRecognizer];
  _tapRecognizer = nil;
}

// Handles the cell's UISwitch being toggled, which triggers a change in the
// module's visibility.
- (void)toggleModuleVisibility:(UISwitch*)sender {
  BOOL isEnabling = sender.isOn;
  [self.mutator toggleModuleVisibilityForType:_type enabled:isEnabling];
}

// Navigates to the customization submenu for the cell's type.
- (void)navigateToSubmenu:(UIView*)sender {
  CHECK([HomeCustomizationHelper doesTypeHaveSubmenu:_type]);
  [self.mutator navigateToSubmenuForType:_type];
}

// Updates the navigation icon's visibility and the tap recognizer for the
// current state of the switch.
- (void)updateNavigationIconForSwitchEnabled:(BOOL)enabled {
  // Show/hide the navigation icon when toggling the switch.
  BOOL shouldShowNavigationIcon =
      [HomeCustomizationHelper doesTypeHaveSubmenu:_type] && enabled;
  _navigationImageView.hidden = !shouldShowNavigationIcon;
  _navigationSeparator.hidden = !shouldShowNavigationIcon;
  _title.accessibilityTraits |= shouldShowNavigationIcon
                                    ? UIAccessibilityTraitButton
                                    : UIAccessibilityTraitNone;

  // Enable/disable the navigation on tap.
  [_tapRecognizer setEnabled:enabled];
}

@end