diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 059dc6860..7d0146bdd 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -842,6 +842,10 @@ EC51E7AF2514D2E100AAE7DB /* RoomInfoListViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC51E7A42514D2E100AAE7DB /* RoomInfoListViewAction.swift */; }; EC51E7B02514D2E100AAE7DB /* RoomInfoCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC51E7A52514D2E100AAE7DB /* RoomInfoCoordinatorType.swift */; }; EC619C1924DAD96000663A80 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC619C1824DAD96000663A80 /* UIScrollView.swift */; }; + EC6D9D2625B9A5CE00B79D10 /* CallTransferSelectContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6D9D2425B9A5CE00B79D10 /* CallTransferSelectContactViewController.swift */; }; + EC6D9D2725B9A5CE00B79D10 /* CallTransferSelectContactViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EC6D9D2525B9A5CE00B79D10 /* CallTransferSelectContactViewController.storyboard */; }; + EC6D9D2A25B9A61800B79D10 /* CallTransferMainViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EC6D9D2825B9A61800B79D10 /* CallTransferMainViewController.storyboard */; }; + EC6D9D2B25B9A61800B79D10 /* CallTransferMainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6D9D2925B9A61800B79D10 /* CallTransferMainViewController.swift */; }; EC711B4624A63B13008F830C /* MXRecoveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC711B4524A63B13008F830C /* MXRecoveryService.swift */; }; EC711B7424A63B37008F830C /* SecretsSetupRecoveryKeyViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC711B4A24A63B36008F830C /* SecretsSetupRecoveryKeyViewModelType.swift */; }; EC711B7524A63B37008F830C /* SecretsSetupRecoveryKeyCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC711B4B24A63B36008F830C /* SecretsSetupRecoveryKeyCoordinatorType.swift */; }; @@ -2129,6 +2133,10 @@ EC51E7A42514D2E100AAE7DB /* RoomInfoListViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomInfoListViewAction.swift; sourceTree = ""; }; EC51E7A52514D2E100AAE7DB /* RoomInfoCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomInfoCoordinatorType.swift; sourceTree = ""; }; EC619C1824DAD96000663A80 /* UIScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = ""; }; + EC6D9D2425B9A5CE00B79D10 /* CallTransferSelectContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallTransferSelectContactViewController.swift; sourceTree = ""; }; + EC6D9D2525B9A5CE00B79D10 /* CallTransferSelectContactViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = CallTransferSelectContactViewController.storyboard; sourceTree = ""; }; + EC6D9D2825B9A61800B79D10 /* CallTransferMainViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = CallTransferMainViewController.storyboard; sourceTree = ""; }; + EC6D9D2925B9A61800B79D10 /* CallTransferMainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallTransferMainViewController.swift; sourceTree = ""; }; EC711B4524A63B13008F830C /* MXRecoveryService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRecoveryService.swift; sourceTree = ""; }; EC711B4A24A63B36008F830C /* SecretsSetupRecoveryKeyViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsSetupRecoveryKeyViewModelType.swift; sourceTree = ""; }; EC711B4B24A63B36008F830C /* SecretsSetupRecoveryKeyCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsSetupRecoveryKeyCoordinatorType.swift; sourceTree = ""; }; @@ -3553,6 +3561,7 @@ B1B5567620EE6C4C00210D55 /* Modules */ = { isa = PBXGroup; children = ( + EC6D9D2325B9A5CE00B79D10 /* CallTransfer */, ECF57A3725090C23004BBF9D /* CreateRoom */, B1BB648724FD07B3008238AE /* Application */, B10A3E9524FE86AE007C380F /* SplitView */, @@ -5132,6 +5141,33 @@ path = RoomInfo; sourceTree = ""; }; + EC6D9D2325B9A5CE00B79D10 /* CallTransfer */ = { + isa = PBXGroup; + children = ( + EC6D9D2925B9A61800B79D10 /* CallTransferMainViewController.swift */, + EC6D9D2825B9A61800B79D10 /* CallTransferMainViewController.storyboard */, + EC6D9D2C25B9D07400B79D10 /* SelectContact */, + ); + path = CallTransfer; + sourceTree = ""; + }; + EC6D9D2C25B9D07400B79D10 /* SelectContact */ = { + isa = PBXGroup; + children = ( + EC6D9D2425B9A5CE00B79D10 /* CallTransferSelectContactViewController.swift */, + EC6D9D2525B9A5CE00B79D10 /* CallTransferSelectContactViewController.storyboard */, + EC6D9D2D25B9D08000B79D10 /* Cells */, + ); + path = SelectContact; + sourceTree = ""; + }; + EC6D9D2D25B9D08000B79D10 /* Cells */ = { + isa = PBXGroup; + children = ( + ); + path = Cells; + sourceTree = ""; + }; EC711B4724A63B36008F830C /* Secrets */ = { isa = PBXGroup; children = ( @@ -5991,6 +6027,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EC6D9D2A25B9A61800B79D10 /* CallTransferMainViewController.storyboard in Resources */, B1B5590220EF768F00210D55 /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib in Resources */, B1B558CA20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib in Resources */, B152C73522DF599C0041315A /* apple_emojis_data.json in Resources */, @@ -6104,6 +6141,7 @@ EC757B2625B85C7F00DF5787 /* DialpadViewController.storyboard in Resources */, B1B557E820EF60F500210D55 /* MessagesSearchResultTextMsgBubbleCell.xib in Resources */, B1B558D920EF768F00210D55 /* RoomOutgoingEncryptedAttachmentBubbleCell.xib in Resources */, + EC6D9D2725B9A5CE00B79D10 /* CallTransferSelectContactViewController.storyboard in Resources */, B1B5573020EE6C4D00210D55 /* BugReportViewController.xib in Resources */, B169329B20F39E6300746532 /* Main.storyboard in Resources */, B1CE83DB2422817200D07506 /* KeyVerificationVerifyByScanningViewController.storyboard in Resources */, @@ -6972,6 +7010,7 @@ EC711B7D24A63B37008F830C /* SecretsSetupRecoveryPassphraseViewModel.swift in Sources */, EC711B8024A63B37008F830C /* SecretsSetupRecoveryPassphraseViewState.swift in Sources */, B1C45A89232A8C2600165425 /* SettingsIdentityServerViewController.swift in Sources */, + EC6D9D2B25B9A61800B79D10 /* CallTransferMainViewController.swift in Sources */, EC85D7152477DCD7002C44C9 /* KeyVerificationScanConfirmationViewController.swift in Sources */, ECFBD5FE250FA59B00DD5F5A /* RoomCreationEventsModalViewModel.swift in Sources */, B14F143322144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModel.swift in Sources */, @@ -7069,6 +7108,7 @@ B1098C0D21ED07E4000DDA48 /* NavigationRouter.swift in Sources */, EC711B8E24A63B37008F830C /* SecretsRecoveryMode.swift in Sources */, B110872321F098F0003554A5 /* ActivityIndicatorPresenterType.swift in Sources */, + EC6D9D2625B9A5CE00B79D10 /* CallTransferSelectContactViewController.swift in Sources */, B139C22321FF01B200BB68EC /* KeyBackupRecoverFromPassphraseCoordinatorType.swift in Sources */, B14084CE23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift in Sources */, B14F143222144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinator.swift in Sources */, diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index 62f0d5b0c..0b1492b69 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -12,6 +12,16 @@ import UIKit // swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name internal enum StoryboardScene { + internal enum CallTransferMainViewController: StoryboardType { + internal static let storyboardName = "CallTransferMainViewController" + + internal static let initialScene = InitialSceneType(storyboard: CallTransferMainViewController.self) + } + internal enum CallTransferSelectContactViewController: StoryboardType { + internal static let storyboardName = "CallTransferSelectContactViewController" + + internal static let initialScene = InitialSceneType(storyboard: CallTransferSelectContactViewController.self) + } internal enum DeviceVerificationIncomingViewController: StoryboardType { internal static let storyboardName = "DeviceVerificationIncomingViewController" diff --git a/Riot/Modules/CallTransfer/CallTransferMainViewController.storyboard b/Riot/Modules/CallTransfer/CallTransferMainViewController.storyboard new file mode 100644 index 000000000..3e4c7389a --- /dev/null +++ b/Riot/Modules/CallTransfer/CallTransferMainViewController.storyboard @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/CallTransfer/CallTransferMainViewController.swift b/Riot/Modules/CallTransfer/CallTransferMainViewController.swift new file mode 100644 index 000000000..b89ccc35a --- /dev/null +++ b/Riot/Modules/CallTransfer/CallTransferMainViewController.swift @@ -0,0 +1,216 @@ +// File created from simpleScreenTemplate +// $ createSimpleScreen.sh CallTransfer2 CallTransferMain +/* + 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 + +@objc protocol CallTransferMainViewControllerDelegate: class { + func callTransferMainViewControllerDidComplete(_ viewController: CallTransferMainViewController, + consult: Bool, + contact: MXKContact?, + phoneNumber: String?) + func callTransferMainViewControllerDidCancel(_ viewController: CallTransferMainViewController) +} + +@objcMembers +final class CallTransferMainViewController: UIViewController { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var segmentedControl: UISegmentedControl! { + didSet { + segmentedControl.setTitle(VectorL10n.callTransferUsers, forSegmentAt: 0) + segmentedControl.setTitle(VectorL10n.callTransferDialpad, forSegmentAt: 1) + } + } + @IBOutlet private weak var containerView: UIView! + @IBOutlet private weak var bottomBgView: UIView! + @IBOutlet private weak var consultButton: UIButton! + @IBOutlet private weak var connectButton: RoundedButton! { + didSet { + connectButton.isEnabled = false + } + } + + // MARK: Private + + private var selectedContact: MXKContact? { + didSet { + updateConnectButton() + } + } + private var phoneNumber: String? { + didSet { + updateConnectButton() + } + } + private var session: MXSession! + private var theme: Theme! + + private lazy var contactsVC: CallTransferSelectContactViewController = { + let controller = CallTransferSelectContactViewController.instantiate(withSession: session) + controller.delegate = self + return controller + }() + + private lazy var dialpadVC: DialpadViewController = { + let configuration = DialpadConfiguration(showsTitle: false, + showsCloseButton: false, + showsCallButton: false) + let controller = DialpadViewController.instantiate(withConfiguration: configuration) + controller.delegate = self + return controller + }() + + // MARK: Public + + weak var delegate: CallTransferMainViewControllerDelegate? + + // MARK: - Setup + + class func instantiate(withSession session: MXSession) -> CallTransferMainViewController { + let viewController = StoryboardScene.CallTransferMainViewController.initialScene.instantiate() + viewController.session = 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.title = VectorL10n.callTransferTitle + self.vc_removeBackTitle() + + self.setupViews() + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func updateConnectButton() { + if selectedContact != nil { + connectButton.isEnabled = true + } else if let phoneNumber = phoneNumber, !phoneNumber.isEmpty { + connectButton.isEnabled = true + } else { + connectButton.isEnabled = false + } + } + + private func setupViews() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + self.navigationItem.leftBarButtonItem = cancelBarButtonItem + + addChild(contactsVC) + addChild(dialpadVC) + + if let view = contentView(at: segmentedControl.selectedSegmentIndex) { + containerView.vc_addSubViewMatchingParent(view) + } + } + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.bottomBgView.backgroundColor = theme.headerBackgroundColor + self.consultButton.tintColor = theme.tintColor + self.consultButton.setTitleColor(theme.textPrimaryColor, for: .normal) + self.connectButton.update(theme: theme) + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + private func contentView(at index: Int) -> UIView? { + switch index { + case 0: + return contactsVC.view + case 1: + return dialpadVC.view + default: + return nil + } + } + + // MARK: - Actions + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + @IBAction private func segmentedControlValueChanged(_ sender: UISegmentedControl) { + containerView.vc_removeAllSubviews() + if let view = contentView(at: sender.selectedSegmentIndex) { + containerView.vc_addSubViewMatchingParent(view) + } + } + + @IBAction private func consultButtonAction(_ sender: UIButton) { + sender.isSelected = !sender.isSelected + } + + @IBAction private func connectButtonAction(_ sender: UIButton) { + delegate?.callTransferMainViewControllerDidComplete(self, + consult: consultButton.isSelected, + contact: selectedContact, + phoneNumber: phoneNumber) + } + + private func cancelButtonAction() { + self.delegate?.callTransferMainViewControllerDidCancel(self) + } +} + +// MARK: - CallTransferSelectContactViewControllerDelegate + +extension CallTransferMainViewController: CallTransferSelectContactViewControllerDelegate { + + func callTransferSelectContactViewControllerDidSelectContact(_ viewController: CallTransferSelectContactViewController, contact: MXKContact?) { + selectedContact = contact + } + +} + +// MARK: - DialpadViewControllerDelegate + +extension CallTransferMainViewController: DialpadViewControllerDelegate { + + func dialpadViewControllerDidTapDigit(_ viewController: DialpadViewController, digit: String) { + phoneNumber = viewController.rawPhoneNumber + } + +} diff --git a/Riot/Modules/CallTransfer/SelectContact/CallTransferSelectContactViewController.storyboard b/Riot/Modules/CallTransfer/SelectContact/CallTransferSelectContactViewController.storyboard new file mode 100644 index 000000000..0b8ad7951 --- /dev/null +++ b/Riot/Modules/CallTransfer/SelectContact/CallTransferSelectContactViewController.storyboard @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/CallTransfer/SelectContact/CallTransferSelectContactViewController.swift b/Riot/Modules/CallTransfer/SelectContact/CallTransferSelectContactViewController.swift new file mode 100644 index 000000000..1ca8f8e31 --- /dev/null +++ b/Riot/Modules/CallTransfer/SelectContact/CallTransferSelectContactViewController.swift @@ -0,0 +1,287 @@ +// 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: class { + func callTransferSelectContactViewControllerDidSelectContact(_ viewController: CallTransferSelectContactViewController, + contact: MXKContact?) +} + +final class CallTransferSelectContactViewController: UIViewController { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var mainTableView: UITableView! + + // MARK: Private + + private var session: MXSession! + private var theme: Theme! + private var contactsDataSource: ContactsDataSource! { + didSet { + contactsDataSource.delegate = self + } + } + 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 = .checkmark + } + + 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) -> CallTransferSelectContactViewController { + let viewController = StoryboardScene.CallTransferSelectContactViewController.initialScene.instantiate() + viewController.session = session + viewController.theme = ThemeService.shared().theme + viewController.contactsDataSource = ContactsDataSource(matrixSession: session) + 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 var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func updateSections() { + var tmpSections: [Section] = [] + + 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, 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 {}