chromium/ios/chrome/browser/ui/settings/safety_check/safety_check_table_view_controller.swift

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import UIKit

// Delegate for presentation events related to SafetyCheckTableViewController.
@objc
protocol SafetyCheckTableViewControllerPresentationDelegate: AnyObject {

  // Called when the view controller is removed from its parent.
  func safetyCheckTableViewControllerDidRemove(_ controller: SafetyCheckTableViewController)

}

// Workaround to start the section identifier enum with the base value that's in
// shared ObjC code. Swift doesn't allow named constants in this context.
// Need to remember to use |settingsRawValue| instead of |rawValue|.
protocol SettingsEnum: RawRepresentable where RawValue == Int {}
extension SettingsEnum {
  var settingsRawValue: Int {
    // TODO(crbug.com/40210643): This fails on offical builders when trying
    // to use the ObjC constant. Hard-code it as a workaround.
    return self.rawValue + 10  // kSectionIdentifierEnumZero
  }
}

@objc
class SafetyCheckTableViewController: SettingsRootTableViewController, SafetyCheckConsumer {
  // The accessibility identifier of the safety check table view.
  @objc static let accessibilityIdentifier = "kSafetyCheckTableViewId"

  @objc weak var presentationDelegate: SafetyCheckTableViewControllerPresentationDelegate?
  // Handler for taps on items on the safety check page.
  @objc weak var serviceDelegate: SafetyCheckServiceDelegate?

  // MARK: Private enums

  enum SettingsIdentifier: Int, SettingsEnum {
    case checkTypes
    case checkStart
  }

  // MARK: Private members

  // Current state of array of items that form the safety check.
  private var checkTypesItems: [TableViewItem]? {
    didSet {
      reloadData()
    }
  }

  // Header for the safety check page.
  private var safetyCheckHeaderItem: TableViewLinkHeaderFooterItem? {
    didSet {
      reloadData()
    }
  }

  // Current display state of the check start item.
  private var checkStartItem: TableViewItem? {
    didSet {
      reloadData()
    }
  }

  // Footer with timestamp for the safety check page.
  private var safetyCheckFooterItem: TableViewLinkHeaderFooterItem? {
    didSet {
      reloadData()
    }
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.accessibilityIdentifier = SafetyCheckTableViewController.accessibilityIdentifier
    self.title = L10nUtils.string(messageId: IDS_OPTIONS_ADVANCED_SECTION_TITLE_SAFETY_CHECK)
  }

  // MARK: SafetyCheckConsumer

  func setCheck(_ items: [TableViewItem]) {
    checkTypesItems = items
  }

  func setSafetyCheckHeaderItem(_ item: TableViewLinkHeaderFooterItem!) {
    safetyCheckHeaderItem = item
  }

  func setCheckStart(_ item: TableViewItem!) {
    checkStartItem = item
  }

  func setTimestampFooterItem(_ item: TableViewLinkHeaderFooterItem!) {
    safetyCheckFooterItem = item
  }

  // MARK: LegacyChromeTableViewController

  override func loadModel() {
    super.loadModel()

    if let items = self.checkTypesItems {
      let checkTypes = SettingsIdentifier.checkTypes.settingsRawValue
      if items.count != 0 {
        self.tableViewModel.addSection(withIdentifier: checkTypes)
        for item in items {
          self.tableViewModel.add(item, toSectionWithIdentifier: checkTypes)
        }
      }

      if let item = self.safetyCheckHeaderItem {
        self.tableViewModel.setHeader(item, forSectionWithIdentifier: checkTypes)
      }
    }

    if let item = self.checkStartItem {
      let checkStart = SettingsIdentifier.checkStart.settingsRawValue
      self.tableViewModel.addSection(withIdentifier: checkStart)
      self.tableViewModel.add(item, toSectionWithIdentifier: checkStart)
      if let footerItem = self.safetyCheckFooterItem {
        self.tableViewModel.setFooter(footerItem, forSectionWithIdentifier: checkStart)
      }
    }
  }

  // MARK: UIViewController

  override func didMove(toParent parent: UIViewController?) {
    super.didMove(toParent: parent)
    if parent == nil {
      self.presentationDelegate?.safetyCheckTableViewControllerDidRemove(self)
    }
  }

  // MARK: UITableViewDelegate

  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    super.tableView(tableView, didSelectRowAt: indexPath)
    let item = self.tableViewModel.item(at: indexPath)
    self.serviceDelegate?.didSelect(item)
    tableView.deselectRow(at: indexPath, animated: false)
  }

  override func tableView(
    _ tableView: UITableView,
    shouldHighlightRowAt indexPath: IndexPath
  ) -> Bool {
    let item = self.tableViewModel.item(at: indexPath)
    return self.serviceDelegate?.isItemClickable(item) ?? false
  }

  // MARK: UITabelViewDataSource

  override func tableView(
    _ tableView: UITableView,
    cellForRowAt indexPath: IndexPath
  ) -> UITableViewCell {
    let cell = super.tableView(tableView, cellForRowAt: indexPath)
    let tableItem = self.tableViewModel.item(at: indexPath)
    if let delegate = self.serviceDelegate,
      let item = tableItem,
      delegate.isItemWithErrorInfo(item),
      let settingsCheckCell = cell as? SettingsCheckCell
    {
      settingsCheckCell.infoButton.tag = item.type
      settingsCheckCell.infoButton.addTarget(
        self,
        action: #selector(didTapErrorInfoButton),
        for: .touchUpInside)
    }
    return cell
  }

  // MARK: Private

  // Called when user tapped on the information button of an item. Shows popover with detailed
  // description of an error if needed.
  @objc func didTapErrorInfoButton(sender: UIButton) {
    self.serviceDelegate?.infoButtonWasTapped(sender, usingItemType: sender.tag)
  }
}