ChatListController.swift 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import Foundation
  2. import UIKit
  3. import DcCore
  4. protocol ChatListDelegate: class {
  5. func onChatSelected(chatId: Int)
  6. }
  7. class ChatListController: UITableViewController {
  8. let dcContext: DcContext
  9. let viewModel: ChatListViewModel
  10. let contactCellReuseIdentifier = "contactCellReuseIdentifier"
  11. weak var chatListDelegate: ChatListDelegate?
  12. var keyboardAppearedObserver: Any?
  13. var keyboardDisappearedObserver: Any?
  14. /// MARK - search
  15. private lazy var searchController: UISearchController = {
  16. let searchController = UISearchController(searchResultsController: nil)
  17. searchController.searchResultsUpdater = viewModel
  18. searchController.obscuresBackgroundDuringPresentation = false
  19. searchController.searchBar.placeholder = String.localized("search")
  20. searchController.dimsBackgroundDuringPresentation = false
  21. searchController.hidesNavigationBarDuringPresentation = true
  22. searchController.searchBar.delegate = self
  23. return searchController
  24. }()
  25. private lazy var emptySearchStateLabel: EmptyStateLabel = {
  26. let label = EmptyStateLabel()
  27. label.isHidden = true
  28. return label
  29. }()
  30. init(dcContext: DcContext, chatListDelegate: ChatListDelegate) {
  31. self.dcContext = dcContext
  32. self.chatListDelegate = chatListDelegate
  33. self.viewModel = ChatListViewModel(dcContext: dcContext)
  34. super.init(style: .grouped)
  35. viewModel.onChatListUpdate = handleChatListUpdate
  36. }
  37. required init?(coder: NSCoder) {
  38. fatalError("init(coder:) has not been implemented")
  39. }
  40. override func viewWillAppear(_ animated: Bool) {
  41. preferredContentSize = UIScreen.main.bounds.size
  42. navigationItem.hidesSearchBarWhenScrolling = false
  43. }
  44. override func viewDidAppear(_ animated: Bool) {
  45. navigationItem.hidesSearchBarWhenScrolling = true
  46. let nc = NotificationCenter.default
  47. keyboardAppearedObserver = nc.addObserver(self,
  48. selector: #selector(keyboardWillShow(_:)),
  49. name: UIResponder.keyboardWillShowNotification,
  50. object: nil)
  51. keyboardDisappearedObserver = nc.addObserver(self,
  52. selector: #selector(keyboardWillHide(_:)),
  53. name: UIResponder.keyboardWillHideNotification,
  54. object: nil)
  55. }
  56. override func viewDidLoad() {
  57. super.viewDidLoad()
  58. navigationItem.searchController = searchController
  59. configureTableView()
  60. setupSubviews()
  61. }
  62. override func viewDidDisappear(_ animated: Bool) {
  63. if let keyboardAppearedObserver = keyboardAppearedObserver {
  64. NotificationCenter.default.removeObserver(keyboardAppearedObserver)
  65. }
  66. if let keyboardDisappearedObserver = keyboardDisappearedObserver {
  67. NotificationCenter.default.removeObserver(keyboardDisappearedObserver)
  68. }
  69. }
  70. @objc func keyboardWillShow(_ notification: Notification) {
  71. if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
  72. tableView.tableFooterView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: keyboardSize.height))
  73. }
  74. }
  75. @objc func keyboardWillHide(_ notification: Notification) {
  76. tableView.tableFooterView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: Double.leastNormalMagnitude))
  77. }
  78. // MARK: - setup
  79. private func setupSubviews() {
  80. view.addSubview(emptySearchStateLabel)
  81. emptySearchStateLabel.translatesAutoresizingMaskIntoConstraints = false
  82. emptySearchStateLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40).isActive = true
  83. emptySearchStateLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40).isActive = true
  84. emptySearchStateLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40).isActive = true
  85. emptySearchStateLabel.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
  86. }
  87. // MARK: - configuration
  88. private func configureTableView() {
  89. tableView.register(ChatListCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
  90. tableView.rowHeight = 64
  91. tableView.tableHeaderView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: Double.leastNormalMagnitude))
  92. tableView.tableFooterView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: Double.leastNormalMagnitude))
  93. }
  94. override func numberOfSections(in tableView: UITableView) -> Int {
  95. return viewModel.numberOfSections
  96. }
  97. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  98. return viewModel.numberOfRowsIn(section: section)
  99. }
  100. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  101. guard let cell = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier, for: indexPath) as? ChatListCell else {
  102. fatalError("could not deque TableViewCell")
  103. }
  104. let cellData = viewModel.cellDataFor(section: indexPath.section, row: indexPath.row)
  105. cell.updateCell(cellViewModel: cellData)
  106. return cell
  107. }
  108. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  109. if let chatId = viewModel.getChatId(section: indexPath.section, row: indexPath.row) {
  110. chatListDelegate?.onChatSelected(chatId: chatId)
  111. }
  112. let cellData = viewModel.cellDataFor(section: indexPath.section, row: indexPath.row)
  113. switch cellData.type {
  114. case .chat(let data):
  115. chatListDelegate?.onChatSelected(chatId: data.chatId)
  116. case .contact(let data):
  117. if let chatId = data.chatId {
  118. chatListDelegate?.onChatSelected(chatId: chatId)
  119. } else {
  120. let chatId = dcContext.createChatByContactId(contactId: data.contactId)
  121. chatListDelegate?.onChatSelected(chatId: chatId)
  122. }
  123. default:
  124. fatalError("Other types are not allowed in Share contact search")
  125. }
  126. }
  127. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  128. return viewModel.titleForHeaderIn(section: section)
  129. }
  130. func handleChatListUpdate() {
  131. tableView.reloadData()
  132. if let emptySearchText = viewModel.emptySearchText {
  133. let text = String.localizedStringWithFormat(
  134. String.localized("search_no_result_for_x"),
  135. emptySearchText
  136. )
  137. emptySearchStateLabel.text = text
  138. emptySearchStateLabel.isHidden = false
  139. } else {
  140. emptySearchStateLabel.text = nil
  141. emptySearchStateLabel.isHidden = true
  142. }
  143. }
  144. }
  145. // MARK: - uisearchbardelegate
  146. extension ChatListController: UISearchBarDelegate {
  147. func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
  148. viewModel.beginSearch()
  149. return true
  150. }
  151. func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
  152. // searchBar will be set to "" by system
  153. viewModel.endSearch()
  154. DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
  155. self.tableView.scrollToTop()
  156. }
  157. }
  158. func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  159. tableView.scrollToTop()
  160. return true
  161. }
  162. }