/* Copyright 2018-2024 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ import Foundation import Reusable final class TermsView: UIView, NibOwnerLoadable, UITableViewDelegate, UITableViewDataSource { @IBOutlet private weak var tableView: UITableView! @IBOutlet private weak var acceptButton: UIButton! @objc weak var delegate: MXKAuthInputsViewDelegate? private var acceptedCallback: (() -> Void)? /// NavigationVC to display a policy content private var navigationController: RiotNavigationController? /// The list of policies to be accepted by the end user private var policies: [MXLoginPolicyData] = [] /// Policies already accepted by the end user /// Combined with `policies`, this is the view model for `tableView` private var acceptedPolicies: Set = [] /// The index of the policy being displayed fullscreen within `navigationController` private var displayedPolicyIndex: Int? // MARK: - Setup convenience init() { self.init(frame: CGRect.zero) } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) loadNibContent() commonInit() } override init(frame: CGRect) { super.init(frame: frame) loadNibContent() commonInit() } private func commonInit() { tableView.delegate = self tableView.dataSource = self tableView.separatorStyle = .none tableView.register(TableViewCellWithCheckBoxAndLabel.nib(), forCellReuseIdentifier: TableViewCellWithCheckBoxAndLabel.defaultReuseIdentifier()) acceptButton.clipsToBounds = true acceptButton.setTitle(VectorL10n.accept, for: .normal) acceptButton.setTitle(VectorL10n.accept, for: .highlighted) acceptButton.layer.masksToBounds = true acceptButton.layer.cornerRadius = 5 customizeViewRendering() } func customizeViewRendering() { self.backgroundColor = UIColor.clear self.tableView.backgroundColor = UIColor.clear acceptButton.backgroundColor = ThemeService.shared().theme.tintColor } // MARK: - Public /// Display a list of policies the end user must accept. /// /// - Parameters: /// - terms: Terms data sent by the homeserver. /// - onAccepted: block called when the user has accepted all of them. @objc func displayTerms(terms: MXLoginTerms, onAccepted: @escaping () -> Void) { acceptedCallback = onAccepted let lang: String? = Bundle.mxk_language() policies = terms.policiesData(forLanguage: lang, defaultLanguage: "en") acceptedPolicies.removeAll() reload() } // MARK: - Private private func reload() { tableView.reloadData() // Enable the button only if the user has accepted all policies acceptButton.isEnabled = (policies.count == acceptedPolicies.count) acceptButton.alpha = acceptButton.isEnabled ? 1 : 0.5 } @IBAction private func didAcceptButtonTapped(_ sender: Any) { if policies.count == acceptedPolicies.count { acceptedCallback?() } } // MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return policies.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCellWithCheckBoxAndLabel.defaultReuseIdentifier(), for: indexPath) as? TableViewCellWithCheckBoxAndLabel else { fatalError("\(String(describing: TableViewCellWithCheckBoxAndLabel.self)) should be registered") } let policy = policies[indexPath.row] let accepted = acceptedPolicies .contains(indexPath.row) cell.label.text = policy.name cell.isEnabled = accepted cell.vc_setAccessoryDisclosureIndicatorWithCurrentTheme() cell.backgroundColor = UIColor.clear if let checkBox = cell.checkBox, checkBox.gestureRecognizers?.isEmpty ?? true { let gesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapCheckbox)) gesture.numberOfTapsRequired = 1 gesture.numberOfTouchesRequired = 1 checkBox.isUserInteractionEnabled = true checkBox.tag = indexPath.row checkBox.addGestureRecognizer(gesture) } return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { self.displayPolicy(policyIndex: indexPath.row) } @objc private func didTapCheckbox(sender: UITapGestureRecognizer) { guard let policyIndex = sender.view?.tag else { return } if acceptedPolicies.contains(policyIndex) { acceptedPolicies.remove(policyIndex) } else { acceptedPolicies.insert(policyIndex) } reload() } // MARK: - Policy content display func displayPolicy(policyIndex: Int) { displayedPolicyIndex = policyIndex let policy = policies[policyIndex] // Display the policy webpage into our webview let webViewViewController: WebViewViewController = WebViewViewController(url: policy.url) webViewViewController.title = policy.name let leftBarButtonItem: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "back_icon"), style: .plain, target: self, action: #selector(didTapCancelOnPolicyScreen)) webViewViewController.navigationItem.leftBarButtonItem = leftBarButtonItem navigationController = RiotNavigationController() delegate?.authInputsView?(nil, present: navigationController, animated: false) navigationController?.pushViewController(webViewViewController, animated: false) } @objc private func didTapCancelOnPolicyScreen() { removePolicyScreen() } private func removePolicyScreen() { displayedPolicyIndex = nil navigationController?.dismiss(animated: false) navigationController = nil } }