NewRoomTableViewController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. //
  2. // SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
  3. // SPDX-License-Identifier: GPL-3.0-or-later
  4. //
  5. import UIKit
  6. enum NewRoomOption: Int {
  7. case createNewRoom = 0
  8. case joinOpenRoom
  9. }
  10. @objcMembers class NewRoomTableViewController: UITableViewController,
  11. UINavigationControllerDelegate,
  12. UISearchResultsUpdating {
  13. var account: TalkAccount
  14. var indexes: [String] = [""]
  15. var contacts: [String: [NCUser]] = [:]
  16. var searchController: UISearchController
  17. let resultTableViewController = ContactsSearchResultTableViewController(style: .insetGrouped)
  18. var searchTimer: Timer?
  19. var searchRequest: URLSessionTask?
  20. init(account: TalkAccount) {
  21. self.account = account
  22. self.searchController = UISearchController(searchResultsController: resultTableViewController)
  23. super.init(style: .insetGrouped)
  24. }
  25. required init?(coder: NSCoder) {
  26. fatalError("init(coder:) has not been implemented")
  27. }
  28. override func viewWillAppear(_ animated: Bool) {
  29. super.viewWillAppear(animated)
  30. self.navigationItem.hidesSearchBarWhenScrolling = false
  31. self.getPossibleContacts()
  32. }
  33. override func viewDidLoad() {
  34. super.viewDidLoad()
  35. NCAppBranding.styleViewController(self)
  36. self.navigationItem.title = NSLocalizedString("New conversation", comment: "")
  37. self.resultTableViewController.tableView.delegate = self
  38. self.searchController.searchResultsUpdater = self
  39. self.searchController.searchBar.tintColor = NCAppBranding.themeTextColor()
  40. self.navigationItem.searchController = searchController
  41. self.navigationItem.searchController?.searchBar.searchTextField.backgroundColor = NCUtils.searchbarBGColor(forColor: NCAppBranding.themeColor())
  42. if #available(iOS 16.0, *) {
  43. self.navigationItem.preferredSearchBarPlacement = .stacked
  44. }
  45. if let searchTextField = searchController.searchBar.value(forKey: "searchField") as? UITextField {
  46. searchTextField.tintColor = NCAppBranding.themeTextColor()
  47. searchTextField.textColor = NCAppBranding.themeTextColor()
  48. DispatchQueue.main.async {
  49. // Search bar placeholder
  50. searchTextField.attributedPlaceholder = NSAttributedString(string: NSLocalizedString("Search", comment: ""),
  51. attributes: [NSAttributedString.Key.foregroundColor: NCAppBranding.themeTextColor().withAlphaComponent(0.5)])
  52. // Search bar search icon
  53. if let searchImageView = searchTextField.leftView as? UIImageView {
  54. searchImageView.image = searchImageView.image?.withRenderingMode(.alwaysTemplate)
  55. searchImageView.tintColor = NCAppBranding.themeTextColor().withAlphaComponent(0.5)
  56. }
  57. // Search bar search clear button
  58. if let clearButton = searchTextField.value(forKey: "_clearButton") as? UIButton {
  59. let clearButtonImage = clearButton.imageView?.image?.withRenderingMode(.alwaysTemplate)
  60. clearButton.setImage(clearButtonImage, for: .normal)
  61. clearButton.setImage(clearButtonImage, for: .highlighted)
  62. clearButton.tintColor = NCAppBranding.themeTextColor()
  63. }
  64. }
  65. }
  66. self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(self.cancelButtonPressed))
  67. self.navigationItem.leftBarButtonItem?.tintColor = NCAppBranding.themeTextColor()
  68. self.tableView.register(UINib(nibName: kContactsTableCellNibName, bundle: nil), forCellReuseIdentifier: kContactCellIdentifier)
  69. }
  70. func cancelButtonPressed() {
  71. self.dismiss(animated: true, completion: nil)
  72. }
  73. // MARK: - New room options
  74. func getNewRoomOptions() -> [NewRoomOption] {
  75. var options = [NewRoomOption]()
  76. // New group room
  77. if NCSettingsController.sharedInstance().canCreateGroupAndPublicRooms() {
  78. options.append(.createNewRoom)
  79. }
  80. // List open rooms
  81. if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityListableRooms, forAccountId: self.account.accountId) {
  82. options.append(.joinOpenRoom)
  83. }
  84. return options
  85. }
  86. // MARK: - Contacts
  87. func getPossibleContacts() {
  88. NCAPIController.sharedInstance().getContactsFor(account, forRoom: "new", groupRoom: false, withSearchParam: "") { indexes, _, contactList, error in
  89. if error == nil, let indexes = indexes as? [String], let contactList = contactList as? [NCUser] {
  90. let storedContacts = NCContact.contacts(forAccountId: self.account.accountId, contains: nil)
  91. let combinedContactList = NCUser.combineUsersArray(storedContacts, withUsersArray: contactList)
  92. if let combinedContacts = NCUser.indexedUsers(fromUsersArray: combinedContactList) {
  93. let combinedIndexes = Array(combinedContacts.keys).sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
  94. self.indexes.append(contentsOf: combinedIndexes)
  95. self.contacts = combinedContacts
  96. self.tableView.reloadData()
  97. }
  98. }
  99. }
  100. }
  101. // MARK: - Search
  102. func updateSearchResults(for searchController: UISearchController) {
  103. self.searchTimer?.invalidate()
  104. self.resultTableViewController.showSearchingUI()
  105. DispatchQueue.main.async {
  106. self.searchTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.searchForContacts), userInfo: nil, repeats: false)
  107. }
  108. }
  109. func searchForContacts() {
  110. if let searchTerm = self.searchController.searchBar.text, !searchTerm.isEmpty {
  111. self.searchForContactsWithSearchParameter(searchTerm)
  112. }
  113. }
  114. func searchForContactsWithSearchParameter(_ searchParameter: String) {
  115. searchRequest?.cancel()
  116. searchRequest = NCAPIController.sharedInstance().getContactsFor(account, forRoom: "new", groupRoom: false, withSearchParam: searchParameter) { indexes, contacts, contactList, error in
  117. if error == nil, let contactList = contactList as? [NCUser] {
  118. let storedContacts = NCContact.contacts(forAccountId: self.account.accountId, contains: searchParameter)
  119. let combinedContactList = NCUser.combineUsersArray(storedContacts, withUsersArray: contactList)
  120. if let combinedContacts = NCUser.indexedUsers(fromUsersArray: combinedContactList) {
  121. let combinedIndexes = Array(combinedContacts.keys).sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
  122. self.resultTableViewController.setSearchResultContacts(combinedContacts, indexes: combinedIndexes)
  123. }
  124. }
  125. }
  126. }
  127. // MARK: - TableView
  128. override func numberOfSections(in tableView: UITableView) -> Int {
  129. return indexes.count
  130. }
  131. override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
  132. return indexes
  133. }
  134. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  135. return indexes[section]
  136. }
  137. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  138. if section == 0 {
  139. return getNewRoomOptions().count
  140. } else {
  141. let index = indexes[section]
  142. return contacts[index]?.count ?? 0
  143. }
  144. }
  145. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  146. if indexPath.section == 0 {
  147. let options = getNewRoomOptions()
  148. let option = options[indexPath.row]
  149. var newRoomOptionCell = UITableViewCell()
  150. switch option {
  151. case .createNewRoom:
  152. newRoomOptionCell = tableView.dequeueReusableCell(withIdentifier: "NewGroupRoomCellIdentifier") ?? UITableViewCell(style: .default, reuseIdentifier: "NewGroupRoomCellIdentifier")
  153. newRoomOptionCell.textLabel?.text = NSLocalizedString("Create a new conversation", comment: "")
  154. newRoomOptionCell.imageView?.image = UIImage(systemName: "bubble")
  155. case .joinOpenRoom:
  156. newRoomOptionCell = tableView.dequeueReusableCell(withIdentifier: "ListOpenRoomsCellIdentifier") ?? UITableViewCell(style: .default, reuseIdentifier: "ListOpenRoomsCellIdentifier")
  157. newRoomOptionCell.textLabel?.text = NSLocalizedString("Join open conversations", comment: "")
  158. newRoomOptionCell.imageView?.image = UIImage(systemName: "list.bullet")
  159. }
  160. newRoomOptionCell.imageView?.tintColor = .secondaryLabel
  161. newRoomOptionCell.imageView?.contentMode = .scaleAspectFit
  162. newRoomOptionCell.textLabel?.numberOfLines = 0
  163. return newRoomOptionCell
  164. } else {
  165. let index = indexes[indexPath.section]
  166. let contactsForIndex = contacts[index]
  167. guard let contact = contactsForIndex?[indexPath.row] else {
  168. return UITableViewCell()
  169. }
  170. let contactCell = tableView.dequeueReusableCell(withIdentifier: kContactCellIdentifier) as? ContactsTableViewCell ??
  171. ContactsTableViewCell(style: .default, reuseIdentifier: kContactCellIdentifier)
  172. contactCell.labelTitle.text = contact.name
  173. let contactType = contact.source as String
  174. contactCell.contactImage.setActorAvatar(forId: contact.userId, withType: contactType, withDisplayName: contact.name, withRoomToken: nil)
  175. return contactCell
  176. }
  177. }
  178. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  179. if searchController.isActive && !resultTableViewController.contacts.isEmpty {
  180. let index = resultTableViewController.indexes[indexPath.section]
  181. let contactsForIndex = resultTableViewController.contacts[index]
  182. guard let contact = contactsForIndex?[indexPath.row] else { return }
  183. self.createRoomWithContact(contact)
  184. } else if indexPath.section == 0 {
  185. let options = getNewRoomOptions()
  186. let option = options[indexPath.row]
  187. if option == .createNewRoom {
  188. self.presentCreateRoomViewController()
  189. } else if option == .joinOpenRoom {
  190. self.presentJoinOpenRoomsViewController()
  191. }
  192. } else {
  193. let index = indexes[indexPath.section]
  194. let contactsForIndex = contacts[index]
  195. guard let contact = contactsForIndex?[indexPath.row] else { return }
  196. self.createRoomWithContact(contact)
  197. }
  198. tableView.deselectRow(at: indexPath, animated: true)
  199. }
  200. // MARK: - Actions
  201. func presentCreateRoomViewController() {
  202. let viewController = RoomCreationTableViewController(account: account)
  203. let navController = NCNavigationController(rootViewController: viewController)
  204. self.present(navController, animated: true)
  205. }
  206. func presentJoinOpenRoomsViewController() {
  207. let viewController = OpenConversationsTableViewController()
  208. let navController = NCNavigationController(rootViewController: viewController)
  209. self.present(navController, animated: true)
  210. }
  211. func createRoomWithContact(_ contact: NCUser) {
  212. NCAPIController.sharedInstance().createRoom(forAccount: account, withInvite: contact.userId, ofType: .oneToOne, andName: nil) { room, error in
  213. if let token = room?.token, error == nil {
  214. self.navigationController?.dismiss(animated: true) {
  215. NotificationCenter.default.post(name: NSNotification.Name.NCSelectedUserForChat, object: self, userInfo: ["token": token])
  216. }
  217. }
  218. }
  219. }
  220. }