/* Copyright 2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import Foundation @objc protocol SettingsDiscoveryTableViewSectionDelegate: class { func settingsDiscoveryTableViewSection(_ settingsDiscoveryTableViewSection: SettingsDiscoveryTableViewSection, tableViewCellClass: MXKTableViewCell.Type, forRow: Int) -> MXKTableViewCell func settingsDiscoveryTableViewSectionDidUpdate(_ settingsDiscoveryTableViewSection: SettingsDiscoveryTableViewSection) } private enum DiscoverySectionRows { case info(text: String) case attributedInfo(attributedText: NSAttributedString) case button(title: String, action: () -> Void) case threePid(threePid: MX3PID) } @objc final class SettingsDiscoveryTableViewSection: NSObject, Themable { // MARK: - Constants private enum Constants { static let defaultFont = UIFont.systemFont(ofSize: 17.0) } // MARK: - Properties @objc weak var delegate: SettingsDiscoveryTableViewSectionDelegate? // MARK: Private private var theme: Theme! private var viewModel: SettingsDiscoveryViewModel // Need to know the state to make `cellForRow` deliver cells accordingly private var viewState: SettingsDiscoveryViewState = .loading { didSet { self.updateRows() } } private var discoveryRows: [DiscoverySectionRows] = [] // MARK: - Setup @objc init(viewModel: SettingsDiscoveryViewModel) { self.theme = ThemeService.shared().theme self.viewModel = viewModel super.init() self.viewModel.viewDelegate = self self.viewModel.process(viewAction: .load) self.registerThemeServiceDidChangeThemeNotification() } // MARK: - Public @objc func numberOfRows() -> Int { return self.discoveryRows.count } @objc func cellForRow(atRow row: Int) -> UITableViewCell { let discoveryRow = self.discoveryRows[row] var cell: UITableViewCell? let enableInteraction: Bool if case .loading = self.viewState { enableInteraction = false } else { enableInteraction = true } switch discoveryRow { case .info(let infoText): if let infoCell: MXKTableViewCell = self.cellType(at: row) { infoCell.textLabel?.numberOfLines = 0 infoCell.textLabel?.text = infoText infoCell.selectionStyle = .none cell = infoCell } case .attributedInfo(attributedText: let infoText): if let infoCell: MXKTableViewCell = self.cellType(at: row) { infoCell.textLabel?.numberOfLines = 0 infoCell.textLabel?.attributedText = infoText infoCell.selectionStyle = .none cell = infoCell } case .button(title: let title, action: let action): if let buttonCell: MXKTableViewCellWithButton = self.cellType(at: row) { buttonCell.mxkButton.setTitle(title, for: .normal) buttonCell.mxkButton.setTitle(title, for: .highlighted) buttonCell.mxkButton.vc_addAction(action: action) buttonCell.mxkButton.isEnabled = enableInteraction cell = buttonCell } case .threePid(let threePid): if let detailCell: MXKTableViewCell = self.cellType(at: row) { detailCell.accessoryType = .disclosureIndicator let formattedThreePid: String? switch threePid.medium { case .email: formattedThreePid = threePid.address case .msisdn: formattedThreePid = MXKTools.readableMSISDN(threePid.address) default: formattedThreePid = nil } detailCell.textLabel?.text = formattedThreePid detailCell.isUserInteractionEnabled = enableInteraction cell = detailCell } } return cell ?? UITableViewCell() } @objc func reload() { self.viewModel.process(viewAction: .load) } @objc func selectRow(_ row: Int) { let discoveryRow = self.discoveryRows[row] switch discoveryRow { case .threePid(threePid: let threePid): self.viewModel.process(viewAction: .select(threePid: threePid)) case .attributedInfo(attributedText: _): if case let .loaded(displayMode) = self.viewState { switch displayMode { case .noThreePidsAdded, .threePidsAdded: self.viewModel.process(viewAction: .tapUserSettingsLink) default: break } } default: break } } func update(theme: Theme) { self.theme = theme self.updateRows() } // MARK: - Private private func registerThemeServiceDidChangeThemeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) } @objc private func themeDidChange() { self.update(theme: ThemeService.shared().theme) } private func cellType(at row: Int) -> T? { let klass: T.Type = T.self let tableViewCell = delegate?.settingsDiscoveryTableViewSection(self, tableViewCellClass: klass, forRow: row) return tableViewCell as? T } private func updateRows() { let discoveryRows: [DiscoverySectionRows] switch self.viewState { case .loading: discoveryRows = self.discoveryRows case .loaded(let displayMode): switch displayMode { case .noIdentityServer: discoveryRows = [ .info(text: VectorL10n.settingsDiscoveryNoIdentityServer) ] case .termsNotSigned(let host): discoveryRows = [ .info(text: VectorL10n.settingsDiscoveryTermsNotSigned(host)), .button(title: VectorL10n.accept, action: { [weak self] in self?.viewModel.process(viewAction: .acceptTerms) }) ] case .noThreePidsAdded: discoveryRows = [ .attributedInfo(attributedText: self.threePidsManagementInfoAttributedString()) ] case .threePidsAdded(let emails, let phoneNumbers): let emailThreePids = emails.map { (email) -> DiscoverySectionRows in return .threePid(threePid: email) } let phoneNumbersThreePids = phoneNumbers.map { (phoneNumber) -> DiscoverySectionRows in return .threePid(threePid: phoneNumber) } var threePidsRows: [DiscoverySectionRows] = [] threePidsRows.append(contentsOf: emailThreePids) threePidsRows.append(contentsOf: phoneNumbersThreePids) threePidsRows.append(.attributedInfo(attributedText: self.threePidsManagementInfoAttributedString())) discoveryRows = threePidsRows } case .error: discoveryRows = [ .info(text: VectorL10n.settingsDiscoveryErrorMessage), .button(title: VectorL10n.retry, action: { [weak self] in self?.viewModel.process(viewAction: .load) }) ] } self.discoveryRows = discoveryRows } private func threePidsManagementInfoAttributedString() -> NSAttributedString { let attributedInfoString = NSMutableAttributedString(string: VectorL10n.settingsDiscoveryThreePidsManagementInformationPart1, attributes: [.foregroundColor: self.theme.textPrimaryColor, .font: Constants.defaultFont]) attributedInfoString.append(NSAttributedString(string: VectorL10n.settingsDiscoveryThreePidsManagementInformationPart2, attributes: [.foregroundColor: self.theme.tintColor, .font: Constants.defaultFont])) attributedInfoString.append(NSAttributedString(string: VectorL10n.settingsDiscoveryThreePidsManagementInformationPart3, attributes: [.foregroundColor: self.theme.tintColor, .font: Constants.defaultFont])) return attributedInfoString } } // MARK: - SettingsDiscoveryViewModelViewDelegate extension SettingsDiscoveryTableViewSection: SettingsDiscoveryViewModelViewDelegate { func settingsDiscoveryViewModel(_ viewModel: SettingsDiscoveryViewModelType, didUpdateViewState viewState: SettingsDiscoveryViewState) { self.viewState = viewState // The tableview datasource will call `self.cellForRow()` self.delegate?.settingsDiscoveryTableViewSectionDidUpdate(self) } }