GroupChatDetailViewController.swift 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. import UIKit
  2. import DcCore
  3. import QuickLook
  4. import Intents
  5. class GroupChatDetailViewController: UIViewController {
  6. enum ProfileSections {
  7. case chatOptions
  8. case members
  9. case chatActions
  10. }
  11. enum ChatOption {
  12. case allMedia
  13. case ephemeralMessages
  14. }
  15. enum ChatAction {
  16. case archiveChat
  17. case leaveGroup
  18. case clearChat
  19. case deleteChat
  20. case copyToClipboard
  21. }
  22. private var chatOptions: [ChatOption]
  23. private var chatActions: [ChatAction]
  24. private let membersRowAddMembers = 0
  25. private let membersRowQrInvite = 1
  26. private var memberManagementRows: Int
  27. private let dcContext: DcContext
  28. private let sections: [ProfileSections]
  29. private var chatId: Int
  30. private var chat: DcChat {
  31. return dcContext.getChat(chatId: chatId)
  32. }
  33. var chatIsEphemeral: Bool {
  34. return chatId != 0 && dcContext.getChatEphemeralTimer(chatId: chatId) > 0
  35. }
  36. // stores contactIds
  37. private var groupMemberIds: [Int] = []
  38. private var incomingMsgsObserver: NSObjectProtocol?
  39. private var ephemeralTimerObserver: NSObjectProtocol?
  40. private var chatModifiedObserver: NSObjectProtocol?
  41. // MARK: - subviews
  42. private lazy var editBarButtonItem: UIBarButtonItem = {
  43. UIBarButtonItem(title: String.localized("global_menu_edit_desktop"), style: .plain, target: self, action: #selector(editButtonPressed))
  44. }()
  45. lazy var tableView: UITableView = {
  46. let table = UITableView(frame: .zero, style: .grouped)
  47. table.register(UITableViewCell.self, forCellReuseIdentifier: "tableCell")
  48. table.register(ActionCell.self, forCellReuseIdentifier: "actionCell")
  49. table.register(ContactCell.self, forCellReuseIdentifier: "contactCell")
  50. table.delegate = self
  51. table.dataSource = self
  52. table.tableHeaderView = groupHeader
  53. return table
  54. }()
  55. private lazy var groupHeader: ContactDetailHeader = {
  56. let header = ContactDetailHeader()
  57. header.onAvatarTap = showGroupAvatarIfNeeded
  58. header.showMuteButton(show: chat.isMuted)
  59. header.showSearchButton(show: true)
  60. header.onSearchButtonTapped = showSearch
  61. header.onMuteButtonTapped = toggleMuteChat
  62. header.setRecentlySeen(false)
  63. return header
  64. }()
  65. private lazy var ephemeralMessagesCell: UITableViewCell = {
  66. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  67. cell.textLabel?.text = String.localized("ephemeral_messages")
  68. if #available(iOS 13.0, *) {
  69. cell.imageView?.image = UIImage(named: "ephemeral_timer")?.withTintColor(UIColor.systemBlue)
  70. }
  71. cell.accessoryType = .disclosureIndicator
  72. return cell
  73. }()
  74. private lazy var archiveChatCell: ActionCell = {
  75. let cell = ActionCell()
  76. cell.actionTitle = chat.isArchived ? String.localized("menu_unarchive_chat") : String.localized("menu_archive_chat")
  77. cell.actionColor = UIColor.systemBlue
  78. return cell
  79. }()
  80. private lazy var leaveGroupCell: ActionCell = {
  81. let cell = ActionCell()
  82. cell.actionTitle = String.localized("menu_leave_group")
  83. cell.actionColor = UIColor.red
  84. return cell
  85. }()
  86. private lazy var copyToClipboardCell: ActionCell = {
  87. let cell = ActionCell()
  88. cell.actionTitle = String.localized("menu_copy_to_clipboard")
  89. cell.actionColor = SystemColor.blue.uiColor
  90. return cell
  91. }()
  92. private lazy var clearChatCell: ActionCell = {
  93. let cell = ActionCell()
  94. cell.actionTitle = String.localized("clear_chat")
  95. cell.actionColor = UIColor.red
  96. return cell
  97. }()
  98. private lazy var deleteChatCell: ActionCell = {
  99. let cell = ActionCell()
  100. cell.actionTitle = String.localized("menu_delete_chat")
  101. cell.actionColor = UIColor.red
  102. return cell
  103. }()
  104. private lazy var allMediaCell: UITableViewCell = {
  105. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  106. cell.textLabel?.text = String.localized("menu_all_media")
  107. if #available(iOS 13.0, *) {
  108. cell.imageView?.image = UIImage(systemName: "photo.on.rectangle") // added in ios13
  109. }
  110. cell.accessoryType = .disclosureIndicator
  111. return cell
  112. }()
  113. init(chatId: Int, dcContext: DcContext) {
  114. self.dcContext = dcContext
  115. self.chatId = chatId
  116. let chat = dcContext.getChat(chatId: chatId)
  117. self.chatActions = []
  118. self.chatOptions = []
  119. self.memberManagementRows = 0
  120. if chat.isMailinglist {
  121. self.sections = [.chatOptions, .chatActions]
  122. } else if chat.isBroadcast {
  123. self.sections = [.chatOptions, .members, .chatActions]
  124. } else {
  125. self.sections = [.chatOptions, .members, .chatActions]
  126. }
  127. super.init(nibName: nil, bundle: nil)
  128. setupSubviews()
  129. }
  130. required init?(coder _: NSCoder) {
  131. fatalError("init(coder:) has not been implemented")
  132. }
  133. private func setupSubviews() {
  134. view.addSubview(tableView)
  135. tableView.translatesAutoresizingMaskIntoConstraints = false
  136. tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
  137. tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
  138. tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
  139. tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
  140. }
  141. // MARK: - lifecycle
  142. override func viewDidLoad() {
  143. super.viewDidLoad()
  144. if chat.isMailinglist {
  145. title = String.localized("mailing_list")
  146. } else if chat.isBroadcast {
  147. title = String.localized("broadcast_list")
  148. } else {
  149. title = String.localized("tab_group")
  150. }
  151. navigationItem.rightBarButtonItem = editBarButtonItem
  152. groupHeader.frame = CGRect(0, 0, tableView.frame.width, ContactCell.cellHeight)
  153. }
  154. override func viewWillAppear(_ animated: Bool) {
  155. super.viewWillAppear(animated)
  156. updateGroupMembers()
  157. updateOptions()
  158. tableView.reloadData()
  159. setupObservers()
  160. updateHeader()
  161. updateMediaCellValues()
  162. updateEphemeralTimerCellValue()
  163. // when sharing to ourself in DocumentGalleryController,
  164. // end of sharing is not easily catchable nor results in applicationWillEnterForeground();
  165. // therefore, do the update here.
  166. AppDelegate.emitMsgsChangedIfShareExtensionWasUsed()
  167. }
  168. override func viewWillDisappear(_ animated: Bool) {
  169. removeObservers()
  170. }
  171. override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
  172. if previousTraitCollection?.preferredContentSizeCategory !=
  173. traitCollection.preferredContentSizeCategory {
  174. groupHeader.frame = CGRect(0, 0, tableView.frame.width, ContactCell.cellHeight)
  175. }
  176. }
  177. // MARK: - observers
  178. private func setupObservers() {
  179. let nc = NotificationCenter.default
  180. incomingMsgsObserver = nc.addObserver(
  181. forName: dcNotificationIncoming,
  182. object: nil,
  183. queue: OperationQueue.main) { [weak self] notification in
  184. guard let self = self else { return }
  185. if let ui = notification.userInfo,
  186. self.chatId == ui["chat_id"] as? Int {
  187. self.updateMediaCellValues()
  188. }
  189. }
  190. ephemeralTimerObserver = nc.addObserver(
  191. forName: dcEphemeralTimerModified,
  192. object: nil,
  193. queue: OperationQueue.main) { [weak self] notification in
  194. guard let self = self else { return }
  195. if let ui = notification.userInfo,
  196. self.chatId == ui["chat_id"] as? Int {
  197. self.updateEphemeralTimerCellValue()
  198. }
  199. }
  200. chatModifiedObserver = nc.addObserver(
  201. forName: dcNotificationChatModified,
  202. object: nil,
  203. queue: OperationQueue.main) { [weak self] notification in
  204. guard let self = self else { return }
  205. if let ui = notification.userInfo,
  206. self.chatId == ui["chat_id"] as? Int {
  207. self.updateHeader()
  208. self.updateGroupMembers()
  209. self.updateOptions()
  210. self.tableView.reloadData()
  211. }
  212. }
  213. }
  214. private func removeObservers() {
  215. let nc = NotificationCenter.default
  216. if let msgChangedObserver = self.incomingMsgsObserver {
  217. nc.removeObserver(msgChangedObserver)
  218. }
  219. if let ephemeralTimerObserver = self.ephemeralTimerObserver {
  220. nc.removeObserver(ephemeralTimerObserver)
  221. }
  222. if let chatModifiedObserver = self.chatModifiedObserver {
  223. nc.removeObserver(chatModifiedObserver)
  224. }
  225. }
  226. // MARK: - update
  227. private func updateGroupMembers() {
  228. groupMemberIds = chat.getContactIds(dcContext)
  229. }
  230. private func updateOptions() {
  231. self.editBarButtonItem.isEnabled = chat.isMailinglist || chat.canSend
  232. if chat.isMailinglist {
  233. self.chatOptions = [.allMedia]
  234. self.memberManagementRows = 0
  235. self.chatActions = [.archiveChat, .copyToClipboard, .clearChat, .deleteChat]
  236. self.groupHeader.showMuteButton(show: true)
  237. } else if chat.isBroadcast {
  238. self.chatOptions = [.allMedia]
  239. self.memberManagementRows = 1
  240. self.chatActions = [.archiveChat, .clearChat, .deleteChat]
  241. self.groupHeader.showMuteButton(show: false)
  242. } else if chat.canSend {
  243. self.chatOptions = [.allMedia, .ephemeralMessages]
  244. self.memberManagementRows = 2
  245. self.chatActions = [.archiveChat, .leaveGroup, .clearChat, .deleteChat]
  246. self.groupHeader.showMuteButton(show: true)
  247. } else {
  248. self.chatOptions = [.allMedia]
  249. self.memberManagementRows = 0
  250. self.chatActions = [.archiveChat, .clearChat, .deleteChat]
  251. self.groupHeader.showMuteButton(show: true)
  252. }
  253. }
  254. private func updateHeader() {
  255. var subtitle: String?
  256. if chat.isMailinglist {
  257. let addr = chat.getMailinglistAddr()
  258. subtitle = addr.isEmpty ? nil : addr
  259. }
  260. groupHeader.updateDetails(title: chat.name, subtitle: subtitle)
  261. if let img = chat.profileImage {
  262. groupHeader.setImage(img)
  263. } else {
  264. groupHeader.setBackupImage(name: chat.name, color: chat.color)
  265. }
  266. groupHeader.setVerified(isVerified: chat.isProtected)
  267. groupHeader.setMuted(isMuted: chat.isMuted)
  268. groupHeader.showSearchButton(show: chat.canSend)
  269. }
  270. private func updateEphemeralTimerCellValue() {
  271. ephemeralMessagesCell.detailTextLabel?.text = String.localized(chatIsEphemeral ? "on" : "off")
  272. }
  273. private func updateMediaCellValues() {
  274. allMediaCell.detailTextLabel?.text = dcContext.getAllMediaCount(chatId: chatId)
  275. }
  276. // MARK: - actions
  277. @objc func editButtonPressed() {
  278. showGroupChatEdit(chat: chat)
  279. }
  280. private func toggleMuteChat() {
  281. if chat.isMuted {
  282. dcContext.setChatMuteDuration(chatId: chatId, duration: 0)
  283. groupHeader.setMuted(isMuted: false)
  284. navigationController?.popViewController(animated: true)
  285. } else {
  286. showMuteAlert()
  287. }
  288. }
  289. private func toggleArchiveChat() {
  290. let archivedBefore = chat.isArchived
  291. if !archivedBefore {
  292. NotificationManager.removeNotificationsForChat(dcContext: dcContext, chatId: chatId)
  293. }
  294. dcContext.archiveChat(chatId: chat.id, archive: !archivedBefore)
  295. if archivedBefore {
  296. archiveChatCell.actionTitle = String.localized("menu_archive_chat")
  297. } else {
  298. self.navigationController?.popToRootViewController(animated: false)
  299. }
  300. }
  301. private func getGroupMemberIdFor(_ row: Int) -> Int {
  302. let index = row - memberManagementRows
  303. if index >= 0 && index < groupMemberIds.count {
  304. return groupMemberIds[index]
  305. } else {
  306. return 0
  307. }
  308. }
  309. private func isMemberManagementRow(row: Int) -> Bool {
  310. return row < memberManagementRows
  311. }
  312. // MARK: - coordinator
  313. private func showSingleChatEdit(contactId: Int) {
  314. let editContactController = EditContactController(dcContext: dcContext, contactIdForUpdate: contactId)
  315. navigationController?.pushViewController(editContactController, animated: true)
  316. }
  317. private func showAddGroupMember(chatId: Int) {
  318. let groupMemberViewController = AddGroupMembersViewController(dcContext: dcContext, chatId: chatId)
  319. groupMemberViewController.onMembersSelected = { [weak self] (memberIds: Set<Int>) -> Void in
  320. guard let self = self else { return }
  321. let chat = self.dcContext.getChat(chatId: chatId)
  322. var chatMembersToRemove = chat.getContactIds(self.dcContext)
  323. chatMembersToRemove.removeAll(where: { memberIds.contains($0)})
  324. for contactId in chatMembersToRemove {
  325. _ = self.dcContext.removeContactFromChat(chatId: chatId, contactId: contactId)
  326. }
  327. for contactId in memberIds {
  328. _ = self.dcContext.addContactToChat(chatId: chatId, contactId: contactId)
  329. }
  330. }
  331. navigationController?.pushViewController(groupMemberViewController, animated: true)
  332. }
  333. private func showQrCodeInvite(chatId: Int) {
  334. var hint = ""
  335. let dcChat = dcContext.getChat(chatId: chatId)
  336. if !dcChat.name.isEmpty {
  337. hint = String.localizedStringWithFormat(String.localized("qrshow_join_group_hint"), dcChat.name)
  338. }
  339. let qrInviteCodeController = QrViewController(dcContext: dcContext, chatId: chatId, qrCodeHint: hint)
  340. navigationController?.pushViewController(qrInviteCodeController, animated: true)
  341. }
  342. private func showGroupChatEdit(chat: DcChat) {
  343. let editGroupViewController = EditGroupViewController(dcContext: dcContext, chat: chat)
  344. navigationController?.pushViewController(editGroupViewController, animated: true)
  345. }
  346. private func showContactDetail(of contactId: Int) {
  347. let contactDetailController = ContactDetailViewController(dcContext: dcContext, contactId: contactId)
  348. navigationController?.pushViewController(contactDetailController, animated: true)
  349. }
  350. private func showAllMedia() {
  351. navigationController?.pushViewController(AllMediaViewController(dcContext: dcContext, chatId: chatId), animated: true)
  352. }
  353. private func showSearch() {
  354. if let chatViewController = navigationController?.viewControllers.last(where: {
  355. $0 is ChatViewController
  356. }) as? ChatViewController {
  357. chatViewController.activateSearchOnAppear()
  358. navigationController?.popViewController(animated: true)
  359. }
  360. }
  361. private func deleteChat() {
  362. dcContext.deleteChat(chatId: chatId)
  363. NotificationManager.removeNotificationsForChat(dcContext: dcContext, chatId: chatId)
  364. INInteraction.delete(with: ["\(dcContext.id).\(chatId)"])
  365. navigationController?.popViewControllers(viewsToPop: 2, animated: true)
  366. }
  367. private func showGroupAvatarIfNeeded() {
  368. if let url = chat.profileImageURL {
  369. let previewController = PreviewController(dcContext: dcContext, type: .single(url))
  370. previewController.customTitle = self.title
  371. navigationController?.pushViewController(previewController, animated: true)
  372. }
  373. }
  374. }
  375. // MARK: - UITableViewDelegate, UITableViewDataSource
  376. extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSource {
  377. func numberOfSections(in _: UITableView) -> Int {
  378. return sections.count
  379. }
  380. func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
  381. let sectionType = sections[section]
  382. switch sectionType {
  383. case .chatOptions:
  384. return chatOptions.count
  385. case .members:
  386. return groupMemberIds.count + memberManagementRows
  387. case .chatActions:
  388. return chatActions.count
  389. }
  390. }
  391. func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  392. let sectionType = sections[indexPath.section]
  393. let row = indexPath.row
  394. if sectionType == .members && !isMemberManagementRow(row: row) {
  395. return ContactCell.cellHeight
  396. } else {
  397. return UITableView.automaticDimension
  398. }
  399. }
  400. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  401. let row = indexPath.row
  402. let sectionType = sections[indexPath.section]
  403. switch sectionType {
  404. case .chatOptions:
  405. switch chatOptions[row] {
  406. case .allMedia:
  407. return allMediaCell
  408. case .ephemeralMessages:
  409. return ephemeralMessagesCell
  410. }
  411. case .members:
  412. if isMemberManagementRow(row: row) {
  413. guard let actionCell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as? ActionCell else {
  414. safe_fatalError("could not dequeue action cell")
  415. break
  416. }
  417. if row == membersRowAddMembers {
  418. actionCell.actionTitle = String.localized(chat.isBroadcast ? "add_recipients" : "group_add_members")
  419. actionCell.actionColor = UIColor.systemBlue
  420. } else if row == membersRowQrInvite {
  421. actionCell.actionTitle = String.localized("qrshow_join_group_title")
  422. actionCell.actionColor = UIColor.systemBlue
  423. }
  424. return actionCell
  425. }
  426. guard let contactCell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as? ContactCell else {
  427. safe_fatalError("could not dequeue contactCell cell")
  428. break
  429. }
  430. let contactId: Int = getGroupMemberIdFor(row)
  431. let cellData = ContactCellData(
  432. contactId: contactId,
  433. chatId: dcContext.getChatIdByContactIdOld(contactId)
  434. )
  435. let cellViewModel = ContactCellViewModel(dcContext: dcContext, contactData: cellData)
  436. contactCell.updateCell(cellViewModel: cellViewModel)
  437. return contactCell
  438. case .chatActions:
  439. switch chatActions[row] {
  440. case .archiveChat:
  441. return archiveChatCell
  442. case .leaveGroup:
  443. return leaveGroupCell
  444. case .clearChat:
  445. return clearChatCell
  446. case .deleteChat:
  447. return deleteChatCell
  448. case .copyToClipboard:
  449. return copyToClipboardCell
  450. }
  451. }
  452. // should never get here
  453. return UITableViewCell(frame: .zero)
  454. }
  455. func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
  456. let sectionType = sections[indexPath.section]
  457. let row = indexPath.row
  458. switch sectionType {
  459. case .chatOptions:
  460. switch chatOptions[row] {
  461. case .allMedia:
  462. showAllMedia()
  463. case .ephemeralMessages:
  464. showEphemeralMessagesController()
  465. }
  466. case .members:
  467. if isMemberManagementRow(row: row) {
  468. if row == membersRowAddMembers {
  469. showAddGroupMember(chatId: chat.id)
  470. } else if row == membersRowQrInvite {
  471. showQrCodeInvite(chatId: chat.id)
  472. }
  473. } else {
  474. let memberId = getGroupMemberIdFor(row)
  475. if memberId == DC_CONTACT_ID_SELF {
  476. tableView.deselectRow(at: indexPath, animated: true) // animated as no other elements pop up
  477. } else {
  478. showContactDetail(of: memberId)
  479. }
  480. }
  481. case .chatActions:
  482. switch chatActions[row] {
  483. case .archiveChat:
  484. tableView.deselectRow(at: indexPath, animated: true) // animated as no other elements pop up
  485. toggleArchiveChat()
  486. case .leaveGroup:
  487. tableView.deselectRow(at: indexPath, animated: false)
  488. showLeaveGroupConfirmationAlert()
  489. case .clearChat:
  490. tableView.deselectRow(at: indexPath, animated: false)
  491. showClearChatConfirmationAlert()
  492. case .deleteChat:
  493. tableView.deselectRow(at: indexPath, animated: false)
  494. showDeleteChatConfirmationAlert()
  495. case .copyToClipboard:
  496. tableView.deselectRow(at: indexPath, animated: false)
  497. UIPasteboard.general.string = chat.getMailinglistAddr()
  498. }
  499. }
  500. }
  501. func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  502. if sections[section] == .members {
  503. return String.localizedStringWithFormat(String.localized(chat.isBroadcast ? "n_recipients" : "n_members"),
  504. chat.getContactIds(dcContext).count)
  505. }
  506. return nil
  507. }
  508. func tableView(_: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
  509. if !chat.canSend {
  510. return false
  511. }
  512. let row = indexPath.row
  513. let sectionType = sections[indexPath.section]
  514. if sectionType == .members &&
  515. !isMemberManagementRow(row: row) &&
  516. getGroupMemberIdFor(row) != DC_CONTACT_ID_SELF {
  517. return true
  518. }
  519. return false
  520. }
  521. func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
  522. if !chat.canSend {
  523. return nil
  524. }
  525. let row = indexPath.row
  526. let sectionType = sections[indexPath.section]
  527. if sectionType == .members &&
  528. !isMemberManagementRow(row: row) &&
  529. getGroupMemberIdFor(row) != DC_CONTACT_ID_SELF {
  530. // action set for members except for current user
  531. let delete = UITableViewRowAction(style: .destructive, title: String.localized("remove_desktop")) { [weak self] _, indexPath in
  532. guard let self = self else { return }
  533. let contact = self.getGroupMember(at: row)
  534. let title = String.localizedStringWithFormat(String.localized(self.chat.isBroadcast ?
  535. "ask_remove_from_broadcast" :
  536. "ask_remove_members"), contact.nameNAddr)
  537. let alert = UIAlertController(title: title, message: nil, preferredStyle: .safeActionSheet)
  538. alert.addAction(UIAlertAction(title: String.localized("remove_desktop"), style: .destructive, handler: { _ in
  539. let success = self.dcContext.removeContactFromChat(chatId: self.chat.id, contactId: contact.id)
  540. if success {
  541. self.removeGroupMemberFromTableAt(indexPath)
  542. }
  543. }))
  544. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  545. self.present(alert, animated: true, completion: nil)
  546. }
  547. delete.backgroundColor = UIColor.red
  548. return [delete]
  549. }
  550. return nil
  551. }
  552. private func getGroupMember(at row: Int) -> DcContact {
  553. return dcContext.getContact(id: getGroupMemberIdFor(row))
  554. }
  555. private func removeGroupMemberFromTableAt(_ indexPath: IndexPath) {
  556. self.groupMemberIds.remove(at: indexPath.row - memberManagementRows)
  557. self.tableView.deleteRows(at: [indexPath], with: .automatic)
  558. updateHeader() // to display correct group size
  559. }
  560. private func showEphemeralMessagesController() {
  561. let ephemeralMessagesController = EphemeralMessagesViewController(dcContext: dcContext, chatId: chatId)
  562. navigationController?.pushViewController(ephemeralMessagesController, animated: true)
  563. }
  564. }
  565. // MARK: - alerts
  566. extension GroupChatDetailViewController {
  567. private func showMuteAlert() {
  568. let alert = UIAlertController(title: String.localized("mute"), message: nil, preferredStyle: .safeActionSheet)
  569. let forever = -1
  570. addDurationSelectionAction(to: alert, key: "mute_for_one_hour", duration: Time.oneHour)
  571. addDurationSelectionAction(to: alert, key: "mute_for_two_hours", duration: Time.twoHours)
  572. addDurationSelectionAction(to: alert, key: "mute_for_one_day", duration: Time.oneDay)
  573. addDurationSelectionAction(to: alert, key: "mute_for_seven_days", duration: Time.oneWeek)
  574. addDurationSelectionAction(to: alert, key: "mute_forever", duration: forever)
  575. let cancelAction = UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil)
  576. alert.addAction(cancelAction)
  577. present(alert, animated: true, completion: nil)
  578. }
  579. private func addDurationSelectionAction(to alert: UIAlertController, key: String, duration: Int) {
  580. let action = UIAlertAction(title: String.localized(key), style: .default, handler: { _ in
  581. self.dcContext.setChatMuteDuration(chatId: self.chatId, duration: duration)
  582. self.groupHeader.setMuted(isMuted: true)
  583. self.navigationController?.popViewController(animated: true)
  584. })
  585. alert.addAction(action)
  586. }
  587. private func showClearChatConfirmationAlert() {
  588. let msgIds = dcContext.getChatMsgs(chatId: chatId)
  589. if !msgIds.isEmpty {
  590. let alert = UIAlertController(
  591. title: nil,
  592. message: String.localized(stringID: "ask_delete_messages_simple", count: msgIds.count),
  593. preferredStyle: .safeActionSheet
  594. )
  595. alert.addAction(UIAlertAction(title: String.localized("clear_chat"), style: .destructive, handler: { _ in
  596. self.dcContext.deleteMessages(msgIds: msgIds)
  597. self.navigationController?.popViewController(animated: true)
  598. }))
  599. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  600. self.present(alert, animated: true, completion: nil)
  601. }
  602. }
  603. private func showDeleteChatConfirmationAlert() {
  604. let alert = UIAlertController(
  605. title: nil,
  606. message: String.localizedStringWithFormat(String.localized("ask_delete_named_chat"), dcContext.getChat(chatId: chatId).name),
  607. preferredStyle: .safeActionSheet
  608. )
  609. alert.addAction(UIAlertAction(title: String.localized("menu_delete_chat"), style: .destructive, handler: { _ in
  610. self.deleteChat()
  611. }))
  612. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  613. self.present(alert, animated: true, completion: nil)
  614. }
  615. private func showLeaveGroupConfirmationAlert() {
  616. let alert = UIAlertController(title: String.localized("ask_leave_group"), message: nil, preferredStyle: .safeActionSheet)
  617. alert.addAction(UIAlertAction(title: String.localized("menu_leave_group"), style: .destructive, handler: { _ in
  618. _ = self.dcContext.removeContactFromChat(chatId: self.chat.id, contactId: Int(DC_CONTACT_ID_SELF))
  619. }))
  620. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  621. present(alert, animated: true, completion: nil)
  622. }
  623. }