ChatListController.swift 9.9 KB

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