NewChatViewController.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. //
  2. // NewChatViewController.swift
  3. // deltachat-ios
  4. //
  5. // Created by Jonas Reinsch on 21.11.17.
  6. // Copyright © 2017 Jonas Reinsch. All rights reserved.
  7. //
  8. import ALCameraViewController
  9. import Contacts
  10. import UIKit
  11. protocol ChatDisplayer: class {
  12. func displayNewChat(contactId: Int)
  13. func displayChatForId(chatId: Int)
  14. }
  15. class NewChatViewController: UITableViewController {
  16. private lazy var searchController: UISearchController = {
  17. let searchController = UISearchController(searchResultsController: nil)
  18. searchController.searchResultsUpdater = self
  19. searchController.obscuresBackgroundDuringPresentation = false
  20. searchController.searchBar.placeholder = "Search Contact"
  21. return searchController
  22. }()
  23. var contactIds: [Int] = Utils.getContactIds() {
  24. didSet {
  25. tableView.reloadData()
  26. }
  27. }
  28. var contacts:[MRContact] {
  29. return contactIds.map({MRContact(id: $0)})
  30. }
  31. var filteredContacts: [MRContact] = []
  32. weak var chatDisplayer: ChatDisplayer?
  33. var syncObserver: Any?
  34. var hud: ProgressHud?
  35. let deviceContactHandler = DeviceContactsHandler()
  36. var deviceContactAccessGranted: Bool = false {
  37. didSet {
  38. tableView.reloadData()
  39. }
  40. }
  41. init() {
  42. super.init(style: .grouped)
  43. }
  44. required init?(coder _: NSCoder) {
  45. fatalError("init(coder:) has not been implemented")
  46. }
  47. override func viewDidLoad() {
  48. super.viewDidLoad()
  49. title = "New Chat"
  50. navigationController?.navigationBar.prefersLargeTitles = true
  51. let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(NewChatViewController.cancelButtonPressed))
  52. navigationItem.rightBarButtonItem = cancelButton
  53. deviceContactHandler.importDeviceContacts(delegate: self)
  54. navigationItem.searchController = searchController
  55. definesPresentationContext = true // to make sure searchbar will only be shown in this viewController
  56. }
  57. override func viewWillAppear(_ animated: Bool) {
  58. super.viewWillAppear(animated)
  59. self.deviceContactAccessGranted = CNContactStore.authorizationStatus(for: .contacts) == .authorized
  60. contactIds = Utils.getContactIds()
  61. // this will show the searchbar on launch -> will be set back to true on viewDidAppear
  62. if #available(iOS 11.0, *) {
  63. navigationItem.hidesSearchBarWhenScrolling = false
  64. }
  65. }
  66. override func viewDidAppear(_ animated: Bool) {
  67. super.viewDidAppear(animated)
  68. if #available(iOS 11.0, *) {
  69. navigationItem.hidesSearchBarWhenScrolling = true
  70. }
  71. let nc = NotificationCenter.default
  72. syncObserver = nc.addObserver(
  73. forName: dcNotificationSecureJoinerProgress,
  74. object: nil,
  75. queue: nil
  76. ) {
  77. notification in
  78. if let ui = notification.userInfo {
  79. if ui["error"] as! Bool {
  80. self.hud?.error(ui["errorMessage"] as? String)
  81. } else if ui["done"] as! Bool {
  82. self.hud?.done()
  83. } else {
  84. self.hud?.progress(ui["progress"] as! Int)
  85. }
  86. }
  87. }
  88. }
  89. override func viewDidDisappear(_ animated: Bool) {
  90. super.viewDidDisappear(animated)
  91. let nc = NotificationCenter.default
  92. if let syncObserver = self.syncObserver {
  93. nc.removeObserver(syncObserver)
  94. }
  95. }
  96. @objc func cancelButtonPressed() {
  97. dismiss(animated: true, completion: nil)
  98. }
  99. override func didReceiveMemoryWarning() {
  100. super.didReceiveMemoryWarning()
  101. // Dispose of any resources that can be recreated.
  102. }
  103. // MARK: - Table view data source
  104. override func numberOfSections(in _: UITableView) -> Int {
  105. return deviceContactAccessGranted ? 2 : 3
  106. }
  107. override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
  108. if section == 0 {
  109. return 3
  110. } else if section == 1 {
  111. if deviceContactAccessGranted {
  112. return contactIds.count
  113. } else {
  114. return 1
  115. }
  116. } else {
  117. return contactIds.count
  118. }
  119. }
  120. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  121. let section = indexPath.section
  122. let row = indexPath.row
  123. if section == 0 {
  124. if row == 0 {
  125. // new group row
  126. let cell: UITableViewCell
  127. if let c = tableView.dequeueReusableCell(withIdentifier: "newContactCell") {
  128. cell = c
  129. } else {
  130. cell = UITableViewCell(style: .default, reuseIdentifier: "newContactCell")
  131. }
  132. cell.textLabel?.text = "New Group"
  133. cell.textLabel?.textColor = view.tintColor
  134. return cell
  135. }
  136. if row == 1 {
  137. // new contact row
  138. let cell: UITableViewCell
  139. if let c = tableView.dequeueReusableCell(withIdentifier: "scanGroupCell") {
  140. cell = c
  141. } else {
  142. cell = UITableViewCell(style: .default, reuseIdentifier: "scanGroupCell")
  143. }
  144. cell.textLabel?.text = "Scan Group QR Code"
  145. cell.textLabel?.textColor = view.tintColor
  146. return cell
  147. }
  148. if row == 2 {
  149. // new contact row
  150. let cell: UITableViewCell
  151. if let c = tableView.dequeueReusableCell(withIdentifier: "newContactCell") {
  152. cell = c
  153. } else {
  154. cell = UITableViewCell(style: .default, reuseIdentifier: "newContactCell")
  155. }
  156. cell.textLabel?.text = "New Contact"
  157. cell.textLabel?.textColor = view.tintColor
  158. return cell
  159. }
  160. } else if section == 1 {
  161. if deviceContactAccessGranted {
  162. let cell: ContactCell
  163. if let c = tableView.dequeueReusableCell(withIdentifier: "contactCell") as? ContactCell {
  164. cell = c
  165. } else {
  166. cell = ContactCell(style: .default, reuseIdentifier: "contactCell")
  167. }
  168. let contactId = contactIds[row]
  169. updateContactCell(cell: cell, contactId: contactId)
  170. return cell
  171. } else {
  172. let cell: ActionCell
  173. if let c = tableView.dequeueReusableCell(withIdentifier: "actionCell") as? ActionCell {
  174. cell = c
  175. } else {
  176. cell = ActionCell(style: .default, reuseIdentifier: "actionCell")
  177. }
  178. cell.actionTitle = "Import Device Contacts"
  179. return cell
  180. }
  181. } else {
  182. // section 2
  183. let cell: ContactCell
  184. if let c = tableView.dequeueReusableCell(withIdentifier: "contactCell") as? ContactCell {
  185. cell = c
  186. } else {
  187. cell = ContactCell(style: .default, reuseIdentifier: "contactCell")
  188. }
  189. let contactId = contactIds[row]
  190. updateContactCell(cell: cell, contactId: contactId)
  191. return cell
  192. }
  193. // will actually never get here but compiler not happy
  194. return UITableViewCell(style: .default, reuseIdentifier: "cell")
  195. }
  196. override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
  197. let row = indexPath.row
  198. let section = indexPath.section
  199. if section == 0 {
  200. if row == 0 {
  201. let newGroupController = NewGroupViewController()
  202. navigationController?.pushViewController(newGroupController, animated: true)
  203. }
  204. if row == 1 {
  205. if UIImagePickerController.isSourceTypeAvailable(.camera) {
  206. let controller = QrCodeReaderController()
  207. controller.delegate = self
  208. present(controller, animated: true, completion: nil)
  209. } else {
  210. let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
  211. alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
  212. self.dismiss(animated: true, completion: nil)
  213. }))
  214. present(alert, animated: true, completion: nil)
  215. }
  216. }
  217. if row == 2 {
  218. let newContactController = NewContactController()
  219. navigationController?.pushViewController(newContactController, animated: true)
  220. }
  221. } else if section == 1 {
  222. if deviceContactAccessGranted {
  223. let contactIndex = row
  224. let contactId = contactIds[contactIndex]
  225. dismiss(animated: false) {
  226. self.chatDisplayer?.displayNewChat(contactId: contactId)
  227. }
  228. } else {
  229. showSettingsAlert()
  230. }
  231. } else {
  232. let contactIndex = row
  233. let contactId = contactIds[contactIndex]
  234. dismiss(animated: false) {
  235. self.chatDisplayer?.displayNewChat(contactId: contactId)
  236. }
  237. }
  238. }
  239. override func tableView(_: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
  240. let row = indexPath.row
  241. if row > 2 {
  242. let contactIndex = row - 3
  243. let contactId = contactIds[contactIndex]
  244. // let newContactController = NewContactController(contactIdForUpdate: contactId)
  245. // navigationController?.pushViewController(newContactController, animated: true)
  246. let contactProfileController = ContactProfileViewController(contactId: contactId)
  247. navigationController?.pushViewController(contactProfileController, animated: true)
  248. }
  249. }
  250. private func updateContactCell(cell: ContactCell, contactId: Int) {
  251. let contact = MRContact(id: contactId)
  252. cell.nameLabel.text = contact.name
  253. cell.emailLabel.text = contact.email
  254. cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
  255. cell.setColor(contact.color)
  256. cell.accessoryType = .detailDisclosureButton
  257. }
  258. private func searchBarIsEmpty() -> Bool {
  259. return searchController.searchBar.text?.isEmpty ?? true
  260. }
  261. private func filterContentForSearchText(_ searchText: String, scope: String = "All") {
  262. filteredContacts = contacts.filter({ (contact: MRContact) -> Bool in
  263. let matches = contact.name.lowercased().contains(searchText.lowercased()) || contact.email.lowercased().contains(searchText.lowercased())
  264. return matches
  265. })
  266. tableView.reloadData()
  267. }
  268. }
  269. extension NewChatViewController: QrCodeReaderDelegate {
  270. func handleQrCode(_ code: String) {
  271. logger.info("decoded: \(code)")
  272. let check = dc_check_qr(mailboxPointer, code)!
  273. logger.info("got ver: \(check)")
  274. if dc_lot_get_state(check) == DC_QR_ASK_VERIFYGROUP {
  275. hud = ProgressHud("Synchronizing Account", in: view)
  276. DispatchQueue.global(qos: .userInitiated).async {
  277. let id = dc_join_securejoin(mailboxPointer, code)
  278. DispatchQueue.main.async {
  279. self.dismiss(animated: true) {
  280. self.chatDisplayer?.displayChatForId(chatId: Int(id))
  281. }
  282. }
  283. }
  284. } else {
  285. let alert = UIAlertController(title: "Not a valid group QR Code", message: code, preferredStyle: .alert)
  286. alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
  287. self.dismiss(animated: true, completion: nil)
  288. }))
  289. present(alert, animated: true, completion: nil)
  290. }
  291. dc_lot_unref(check)
  292. }
  293. }
  294. extension NewChatViewController: DeviceContactsDelegate {
  295. func accessGranted() {
  296. deviceContactAccessGranted = true
  297. }
  298. func accessDenied() {
  299. deviceContactAccessGranted = false
  300. }
  301. private func showSettingsAlert() {
  302. let alert = UIAlertController(
  303. title: "Import Contacts from to your device",
  304. message: "To chat with contacts from your device open the settings menu and enable the Contacts option",
  305. preferredStyle: .alert)
  306. alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { _ in
  307. UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
  308. })
  309. alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
  310. })
  311. present(alert, animated: true)
  312. }
  313. }
  314. extension NewChatViewController: UISearchResultsUpdating {
  315. func updateSearchResults(for searchController: UISearchController) {
  316. // TODO
  317. }
  318. }
  319. protocol DeviceContactsDelegate {
  320. func accessGranted()
  321. func accessDenied()
  322. }