GroupMembersViewController.swift 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import UIKit
  2. import DcCore
  3. protocol GroupMemberSelectionDelegate: class {
  4. func selected(contactId: Int, selected: Bool)
  5. }
  6. // MARK: - GroupMembersViewController
  7. class GroupMembersViewController: UITableViewController {
  8. weak var groupMemberSelectionDelegate: GroupMemberSelectionDelegate?
  9. var enableCheckmarks = true
  10. var numberOfSections = 1
  11. let dcContext: DcContext
  12. // MARK: - datasource
  13. var contactIds: [Int] = [] {
  14. didSet {
  15. tableView.reloadData()
  16. }
  17. }
  18. // used when seachbar is active
  19. private var filteredContactIds: [Int] = []
  20. var numberOfRowsForContactList: Int {
  21. return isFiltering ? filteredContactIds.count : contactIds.count
  22. }
  23. var selectedContactIds: Set<Int> = []
  24. private func contactIdByRow(_ row: Int) -> Int {
  25. return isFiltering ? filteredContactIds[row] : contactIds[row]
  26. }
  27. private func contactViewModelBy(row: Int) -> ContactCellViewModel {
  28. let id = contactIdByRow(row)
  29. return ContactCellViewModel.make(contactId: id, searchText: searchText, dcContext: dcContext)
  30. }
  31. // MARK: - search
  32. // searchBar active?
  33. private var isFiltering: Bool {
  34. return searchController.isActive && !searchBarIsEmpty
  35. }
  36. private var searchBarIsEmpty: Bool {
  37. return searchController.searchBar.text?.isEmpty ?? true
  38. }
  39. open var searchText: String? {
  40. return searchController.searchBar.text
  41. }
  42. // MARK: - subview configuration
  43. private lazy var searchController: UISearchController = {
  44. let searchController = UISearchController(searchResultsController: nil)
  45. searchController.searchResultsUpdater = self
  46. searchController.obscuresBackgroundDuringPresentation = false
  47. searchController.searchBar.placeholder = String.localized("search")
  48. searchController.hidesNavigationBarDuringPresentation = false
  49. return searchController
  50. }()
  51. private lazy var emptySearchStateLabel: EmptyStateLabel = {
  52. let label = EmptyStateLabel()
  53. label.isHidden = true
  54. return label
  55. }()
  56. private lazy var emptySearchStateLabelWidthConstraint: NSLayoutConstraint? = {
  57. return emptySearchStateLabel.widthAnchor.constraint(equalTo: tableView.widthAnchor)
  58. }()
  59. init() {
  60. self.dcContext = DcContext.shared
  61. super.init(style: .grouped)
  62. hidesBottomBarWhenPushed = true
  63. }
  64. required init?(coder _: NSCoder) {
  65. fatalError("init(coder:) has not been implemented")
  66. }
  67. // MARK: - lifecycle
  68. override func viewDidLoad() {
  69. navigationItem.searchController = searchController
  70. navigationItem.hidesSearchBarWhenScrolling = false
  71. configureTableView()
  72. definesPresentationContext = true
  73. }
  74. // MARK: - setup + configuration
  75. private func configureTableView() {
  76. tableView.register(ContactCell.self, forCellReuseIdentifier: ContactCell.reuseIdentifier)
  77. tableView.sectionHeaderHeight = UITableView.automaticDimension
  78. }
  79. // MARK: - UITableView datasource + delegate
  80. override func numberOfSections(in _: UITableView) -> Int {
  81. return numberOfSections
  82. }
  83. override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
  84. return numberOfRowsForContactList
  85. }
  86. override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  87. return ContactCell.cellHeight
  88. }
  89. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  90. return updateContactCell(for: indexPath)
  91. }
  92. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  93. didSelectContactCell(at: indexPath)
  94. }
  95. func updateContactCell(for indexPath: IndexPath) -> UITableViewCell {
  96. guard let cell: ContactCell = tableView.dequeueReusableCell(withIdentifier: ContactCell.reuseIdentifier, for: indexPath) as? ContactCell else {
  97. safe_fatalError("unsupported cell type")
  98. return UITableViewCell()
  99. }
  100. let row = indexPath.row
  101. let cellViewModel = contactViewModelBy(row: indexPath.row)
  102. cell.updateCell(cellViewModel: cellViewModel)
  103. cell.accessoryType = selectedContactIds.contains(contactIdByRow(row)) && enableCheckmarks ? .checkmark : .none
  104. return cell
  105. }
  106. // MARK: - actions
  107. func didSelectContactCell(at indexPath: IndexPath) {
  108. let row = indexPath.row
  109. if let cell = tableView.cellForRow(at: indexPath) {
  110. tableView.deselectRow(at: indexPath, animated: true)
  111. let contactId = contactIdByRow(row)
  112. if selectedContactIds.contains(contactId) {
  113. selectedContactIds.remove(contactId)
  114. if enableCheckmarks {
  115. cell.accessoryType = .none
  116. }
  117. groupMemberSelectionDelegate?.selected(contactId: contactId, selected: false)
  118. } else {
  119. selectedContactIds.insert(contactId)
  120. if enableCheckmarks {
  121. cell.accessoryType = .checkmark
  122. }
  123. groupMemberSelectionDelegate?.selected(contactId: contactId, selected: true)
  124. }
  125. }
  126. }
  127. }
  128. // MARK: - UISearchResultsUpdating
  129. extension GroupMembersViewController: UISearchResultsUpdating {
  130. func updateSearchResults(for searchController: UISearchController) {
  131. if let searchText = searchController.searchBar.text {
  132. filterContentForSearchText(searchText)
  133. }
  134. }
  135. private func filterContentForSearchText(_ searchText: String, scope _: String = String.localized("pref_show_emails_all")) {
  136. filteredContactIds = dcContext.getContacts(flags: DC_GCL_ADD_SELF, queryString: searchText)
  137. tableView.reloadData()
  138. tableView.scrollToTop()
  139. // handle empty searchstate
  140. if searchController.isActive && filteredContactIds.isEmpty {
  141. let text = String.localizedStringWithFormat(
  142. String.localized("search_no_result_for_x"),
  143. searchText
  144. )
  145. emptySearchStateLabel.text = text
  146. emptySearchStateLabel.isHidden = false
  147. tableView.tableHeaderView = emptySearchStateLabel
  148. emptySearchStateLabelWidthConstraint?.isActive = true
  149. } else {
  150. emptySearchStateLabel.text = nil
  151. emptySearchStateLabel.isHidden = true
  152. emptySearchStateLabelWidthConstraint?.isActive = false
  153. tableView.tableHeaderView = nil
  154. }
  155. }
  156. override func viewDidLayoutSubviews() {
  157. super.viewDidLayoutSubviews()
  158. //ensure the empty search state message can be fully read
  159. if searchController.isActive && filteredContactIds.isEmpty {
  160. tableView.scrollRectToVisible(emptySearchStateLabel.frame, animated: false)
  161. }
  162. }
  163. }