ChatListController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import UIKit
  2. class ChatListController: UIViewController {
  3. weak var coordinator: ChatListCoordinator?
  4. private var dcContext: DcContext
  5. private var chatList: DcChatlist?
  6. private var showArchive: Bool
  7. private lazy var chatTable: UITableView = {
  8. let chatTable = UITableView()
  9. chatTable.dataSource = self
  10. chatTable.delegate = self
  11. chatTable.rowHeight = 80
  12. return chatTable
  13. }()
  14. private var msgChangedObserver: Any?
  15. private var incomingMsgObserver: Any?
  16. private var viewChatObserver: Any?
  17. private var newButton: UIBarButtonItem!
  18. lazy var cancelButton: UIBarButtonItem = {
  19. let button = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
  20. return button
  21. }()
  22. init(dcContext: DcContext, showArchive: Bool) {
  23. self.dcContext = dcContext
  24. self.showArchive = showArchive
  25. dcContext.updateDeviceChats()
  26. super.init(nibName: nil, bundle: nil)
  27. }
  28. required init?(coder _: NSCoder) {
  29. fatalError("init(coder:) has not been implemented")
  30. }
  31. override func viewWillAppear(_ animated: Bool) {
  32. super.viewWillAppear(animated)
  33. getChatList()
  34. updateTitle()
  35. }
  36. override func viewDidAppear(_ animated: Bool) {
  37. super.viewDidAppear(animated)
  38. let nc = NotificationCenter.default
  39. msgChangedObserver = nc.addObserver(forName: dcNotificationChanged,
  40. object: nil, queue: nil) { _ in
  41. self.getChatList()
  42. }
  43. incomingMsgObserver = nc.addObserver(forName: dcNotificationIncoming,
  44. object: nil, queue: nil) { _ in
  45. self.getChatList()
  46. }
  47. viewChatObserver = nc.addObserver(forName: dcNotificationViewChat, object: nil, queue: nil) { notification in
  48. if let chatId = notification.userInfo?["chat_id"] as? Int {
  49. self.coordinator?.showChat(chatId: chatId)
  50. }
  51. }
  52. }
  53. override func viewDidDisappear(_ animated: Bool) {
  54. super.viewDidDisappear(animated)
  55. let nc = NotificationCenter.default
  56. if let msgChangedObserver = self.msgChangedObserver {
  57. nc.removeObserver(msgChangedObserver)
  58. }
  59. if let incomingMsgObserver = self.incomingMsgObserver {
  60. nc.removeObserver(incomingMsgObserver)
  61. }
  62. if let viewChatObserver = self.viewChatObserver {
  63. nc.removeObserver(viewChatObserver)
  64. }
  65. }
  66. override func viewDidLoad() {
  67. super.viewDidLoad()
  68. newButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.compose, target: self, action: #selector(didPressNewChat))
  69. newButton.tintColor = DcColors.primary
  70. navigationItem.rightBarButtonItem = newButton
  71. setupChatTable()
  72. }
  73. private func setupChatTable() {
  74. view.addSubview(chatTable)
  75. chatTable.translatesAutoresizingMaskIntoConstraints = false
  76. chatTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
  77. chatTable.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
  78. chatTable.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
  79. chatTable.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
  80. }
  81. @objc func didPressNewChat() {
  82. coordinator?.showNewChatController()
  83. }
  84. @objc func cancelButtonPressed() {
  85. RelayHelper.sharedInstance.cancel()
  86. updateTitle()
  87. }
  88. private func getNumberOfArchivedChats() -> Int {
  89. let chatList = dcContext.getChatlist(flags: DC_GCL_ARCHIVED_ONLY, queryString: nil, queryId: 0)
  90. return chatList.length
  91. }
  92. private func getChatList() {
  93. var gclFlags: Int32 = 0
  94. if showArchive {
  95. gclFlags |= DC_GCL_ARCHIVED_ONLY
  96. }
  97. chatList = dcContext.getChatlist(flags: gclFlags, queryString: nil, queryId: 0)
  98. chatTable.reloadData()
  99. }
  100. private func updateTitle() {
  101. if RelayHelper.sharedInstance.isForwarding() {
  102. title = String.localized("forward_to")
  103. if !showArchive {
  104. navigationItem.setLeftBarButton(cancelButton, animated: true)
  105. }
  106. } else {
  107. title = showArchive ? String.localized("chat_archived_chats_title") :
  108. String.localized("pref_chats")
  109. navigationItem.setLeftBarButton(nil, animated: true)
  110. }
  111. }
  112. }
  113. extension ChatListController: UITableViewDataSource, UITableViewDelegate {
  114. func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
  115. guard let chatList = self.chatList else {
  116. fatalError("chatList was nil in data source")
  117. }
  118. return chatList.length
  119. }
  120. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  121. let row = indexPath.row
  122. guard let chatList = self.chatList else {
  123. fatalError("chatList was nil in data source")
  124. }
  125. let chatId = chatList.getChatId(index: row)
  126. if chatId == DC_CHAT_ID_ARCHIVED_LINK {
  127. return getArchiveCell(tableView)
  128. }
  129. let cell: ContactCell
  130. if chatId == DC_CHAT_ID_DEADDROP {
  131. cell = getDeaddropCell(tableView)
  132. } else if let c = tableView.dequeueReusableCell(withIdentifier: "ChatCell") as? ContactCell {
  133. cell = c
  134. } else {
  135. cell = ContactCell(style: .default, reuseIdentifier: "ChatCell")
  136. }
  137. let chat = DcChat(id: chatId)
  138. let summary = chatList.getSummary(index: row)
  139. let unreadMessages = dcContext.getUnreadMessages(chatId: chatId)
  140. cell.nameLabel.attributedText = (unreadMessages > 0) ?
  141. NSAttributedString(string: chat.name, attributes: [ .font: UIFont.systemFont(ofSize: 16, weight: .bold) ]) :
  142. NSAttributedString(string: chat.name, attributes: [ .font: UIFont.systemFont(ofSize: 16, weight: .medium) ])
  143. if chatId == DC_CHAT_ID_DEADDROP {
  144. let contact = DcContact(id: DcMsg(id: chatList.getMsgId(index: row)).fromContactId)
  145. if let img = contact.profileImage {
  146. cell.resetBackupImage()
  147. cell.setImage(img)
  148. } else {
  149. cell.setBackupImage(name: contact.name, color: contact.color)
  150. }
  151. } else {
  152. if let img = chat.profileImage {
  153. cell.resetBackupImage()
  154. cell.setImage(img)
  155. } else {
  156. cell.setBackupImage(name: chat.name, color: chat.color)
  157. }
  158. }
  159. cell.setVerified(isVerified: chat.isVerified)
  160. let result1 = summary.text1 ?? ""
  161. let result2 = summary.text2 ?? ""
  162. let result: String
  163. if !result1.isEmpty, !result2.isEmpty {
  164. result = "\(result1): \(result2)"
  165. } else {
  166. result = "\(result1)\(result2)"
  167. }
  168. cell.emailLabel.text = result
  169. cell.setTimeLabel(summary.timestamp)
  170. cell.setUnreadMessageCounter(unreadMessages)
  171. cell.setDeliveryStatusIndicator(summary.state)
  172. return cell
  173. }
  174. func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
  175. let row = indexPath.row
  176. guard let chatList = chatList else { return }
  177. let chatId = chatList.getChatId(index: row)
  178. if chatId == DC_CHAT_ID_DEADDROP {
  179. let msgId = chatList.getMsgId(index: row)
  180. let dcMsg = DcMsg(id: msgId)
  181. let dcContact = DcContact(id: dcMsg.fromContactId)
  182. let title = String.localizedStringWithFormat(String.localized("ask_start_chat_with"), dcContact.nameNAddr)
  183. let alert = UIAlertController(title: title, message: nil, preferredStyle: .safeActionSheet)
  184. alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { _ in
  185. let chat = dcMsg.createChat()
  186. self.coordinator?.showChat(chatId: chat.id)
  187. }))
  188. alert.addAction(UIAlertAction(title: String.localized("not_now"), style: .default, handler: { _ in
  189. dcContact.marknoticed()
  190. }))
  191. alert.addAction(UIAlertAction(title: String.localized("menu_block_contact"), style: .destructive, handler: { _ in
  192. dcContact.block()
  193. }))
  194. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel))
  195. present(alert, animated: true, completion: nil)
  196. } else if chatId == DC_CHAT_ID_ARCHIVED_LINK {
  197. coordinator?.showArchive()
  198. } else {
  199. coordinator?.showChat(chatId: chatId)
  200. }
  201. }
  202. func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
  203. let row = indexPath.row
  204. guard let chatList = chatList else {
  205. return []
  206. }
  207. let chatId = chatList.getChatId(index: row)
  208. if chatId==DC_CHAT_ID_ARCHIVED_LINK || chatId==DC_CHAT_ID_DEADDROP {
  209. return []
  210. // returning nil may result in a default delete action,
  211. // see https://forums.developer.apple.com/thread/115030
  212. }
  213. var title = String.localized("archive")
  214. if showArchive {
  215. title = String.localized("unarchive")
  216. }
  217. let archive = UITableViewRowAction(style: .destructive, title: title) { [unowned self] _, _ in
  218. self.dcContext.archiveChat(chatId: chatId, archive: !self.showArchive)
  219. }
  220. archive.backgroundColor = UIColor.lightGray
  221. let delete = UITableViewRowAction(style: .destructive, title: String.localized("delete")) { [unowned self] _, _ in
  222. self.showDeleteChatConfirmationAlert(chatId: chatId)
  223. }
  224. delete.backgroundColor = UIColor.red
  225. return [archive, delete]
  226. }
  227. func getDeaddropCell(_ tableView: UITableView) -> ContactCell {
  228. let deaddropCell: ContactCell
  229. if let cell = tableView.dequeueReusableCell(withIdentifier: "DeaddropCell") as? ContactCell {
  230. deaddropCell = cell
  231. } else {
  232. deaddropCell = ContactCell(style: .default, reuseIdentifier: "DeaddropCell")
  233. }
  234. deaddropCell.backgroundColor = DcColors.deaddropBackground
  235. deaddropCell.contentView.backgroundColor = DcColors.deaddropBackground
  236. return deaddropCell
  237. }
  238. func getArchiveCell(_ tableView: UITableView) -> UITableViewCell {
  239. let archiveCell: UITableViewCell
  240. if let cell = tableView.dequeueReusableCell(withIdentifier: "ArchiveCell") {
  241. archiveCell = cell
  242. } else {
  243. archiveCell = UITableViewCell(style: .default, reuseIdentifier: "ArchiveCell")
  244. }
  245. archiveCell.textLabel?.textAlignment = .center
  246. var title = String.localized("chat_archived_chats_title")
  247. let count = getNumberOfArchivedChats()
  248. title.append(" (\(count))")
  249. archiveCell.textLabel?.text = title
  250. archiveCell.textLabel?.textColor = .systemBlue
  251. return archiveCell
  252. }
  253. private func showDeleteChatConfirmationAlert(chatId: Int) {
  254. let alert = UIAlertController(
  255. title: nil,
  256. message: String.localized("ask_delete_chat_desktop"),
  257. preferredStyle: .safeActionSheet
  258. )
  259. alert.addAction(UIAlertAction(title: String.localized("menu_delete_chat"), style: .destructive, handler: { _ in
  260. self.dcContext.deleteChat(chatId: chatId)
  261. self.getChatList()
  262. }))
  263. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  264. self.present(alert, animated: true, completion: nil)
  265. }
  266. }