// File created from simpleScreenTemplate // $ createSimpleScreen.sh CallTransfer CallTransferSelectContact /* Copyright 2020 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 UIKit import Reusable protocol CallTransferSelectContactViewControllerDelegate: AnyObject { func callTransferSelectContactViewControllerDidSelectContact(_ viewController: CallTransferSelectContactViewController, contact: MXKContact?) } final class CallTransferSelectContactViewController: UIViewController { // MARK: - Properties // MARK: Outlets @IBOutlet private weak var mainTableView: UITableView! // MARK: Private private enum Constants { static let maxNumberOfRecentContacts: UInt = 10 static let sectionHeaderHeightDefault: CGFloat = 30.0 static let sectionHeaderHeightHidden: CGFloat = 0.01 } private var session: MXSession! private var theme: Theme! private var contactsDataSource: ContactsDataSource! { didSet { for userId in ignoredUserIds { contactsDataSource.ignoredContactsByMatrixId[userId] = MXKContact() } contactsDataSource.delegate = self } } private var ignoredUserIds: [String] = [] private var selectedIndexPath: IndexPath? private lazy var mainSearchBar: UISearchBar = { let searchBar = UISearchBar(frame: CGRect(origin: .zero, size: CGSize(width: mainTableView.frame.width, height: 44))) searchBar.delegate = self searchBar.placeholder = VectorL10n.searchDefaultPlaceholder searchBar.returnKeyType = .done return searchBar }() private struct Row { var contact: MXKContact var accessoryType: UITableViewCell.AccessoryType = .none } private struct Section { var header: String? var rows: [Row] } private var sections: [Section] = [] { didSet { mainTableView.reloadData() } } // MARK: Public weak var delegate: CallTransferSelectContactViewControllerDelegate? // MARK: - Setup class func instantiate(withSession session: MXSession, ignoredUserIds: [String] = []) -> CallTransferSelectContactViewController { let viewController = StoryboardScene.CallTransferSelectContactViewController.initialScene.instantiate() viewController.session = session viewController.ignoredUserIds = ignoredUserIds viewController.contactsDataSource = MatrixContactsDataSource(matrixSession: session) viewController.theme = ThemeService.shared().theme return viewController } // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. self.vc_removeBackTitle() self.setupViews() self.registerThemeServiceDidChangeThemeNotification() self.update(theme: self.theme) updateSections() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) MXKContactManager.shared().refreshLocalContacts() } override var preferredStatusBarStyle: UIStatusBarStyle { return self.theme.statusBarStyle } // MARK: - Private private func updateSections() { var tmpSections: [Section] = [] let users = session.callManager.getRecentCalledUsers(Constants.maxNumberOfRecentContacts, ignoredUserIds: ignoredUserIds) var recentRows: [Row] = [] for (index, user) in users.enumerated() { let indexPath = IndexPath(row: index, section: 0) let accessoryType: UITableViewCell.AccessoryType = indexPath == selectedIndexPath ? .checkmark : .none let row = Row(contact: MXKContact(matrixContactWithDisplayName: user.displayname, matrixID: user.userId, andMatrixAvatarURL: user.avatarUrl), accessoryType: accessoryType) recentRows.append(row) } let recentsSection = Section(header: VectorL10n.callTransferContactsRecent, rows: recentRows) tmpSections.append(recentsSection) let sectionOffset = tmpSections.count for section in 0.. Int { return sections.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sections[section].rows.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let row = sections[indexPath.section].rows[indexPath.row] let cell: ContactTableViewCell = tableView.dequeueReusableCell(for: indexPath) cell.render(row.contact) // replace presence with user id cell.contactInformationLabel.text = row.contact.matrixIdentifiers.first as? String if row.accessoryType == .checkmark { cell.accessoryView = UIImageView(image: Asset.Images.checkmark.image) } else { cell.accessoryView = nil cell.accessoryType = row.accessoryType } return cell } } // MARK: - UITableViewDelegate extension CallTransferSelectContactViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cell.backgroundColor = theme.backgroundColor cell.selectedBackgroundView = UIView() cell.selectedBackgroundView?.backgroundColor = theme.selectedBackgroundColor } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sections[section].header } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if sections[section].header != nil { return Constants.sectionHeaderHeightDefault } return Constants.sectionHeaderHeightHidden } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { if sections[section].header != nil { return Constants.sectionHeaderHeightDefault } return Constants.sectionHeaderHeightHidden } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if selectedIndexPath == indexPath { selectedIndexPath = nil delegate?.callTransferSelectContactViewControllerDidSelectContact(self, contact: nil) } else { selectedIndexPath = indexPath let contact = sections[indexPath.section].rows[indexPath.row].contact delegate?.callTransferSelectContactViewControllerDidSelectContact(self, contact: contact) } updateSections() } } // MARK: - UISearchBarDelegate extension CallTransferSelectContactViewController { override func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { contactsDataSource.search(withPattern: searchText, forceReset: false) } override func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { if let contact = contactsDataSource.searchInputContact { delegate?.callTransferSelectContactViewControllerDidSelectContact(self, contact: contact) } searchBar.resignFirstResponder() } override func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { searchBar.text = nil contactsDataSource.search(withPattern: nil, forceReset: false) searchBar.resignFirstResponder() } override func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { DispatchQueue.main.async { searchBar.setShowsCancelButton(true, animated: true) } } override func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { DispatchQueue.main.async { searchBar.setShowsCancelButton(false, animated: false) } } } // MARK: - MXKDataSourceDelegate extension CallTransferSelectContactViewController: MXKDataSourceDelegate { func cellViewClass(for cellData: MXKCellData!) -> MXKCellRendering.Type! { return nil } func cellReuseIdentifier(for cellData: MXKCellData!) -> String! { return nil } func dataSource(_ dataSource: MXKDataSource!, didCellChange changes: Any!) { updateSections() } func dataSource(_ dataSource: MXKDataSource!, didStateChange state: MXKDataSourceState) { updateSections() } } extension ContactTableViewCell: Reusable {}