ChatListController.swift 6.7 KB

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