123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- import UIKit
- class GroupChatDetailViewController: UIViewController {
- enum ProfileSections {
- case attachments
- //case memberManagement // add member, qr invideCode
- case members // contactCells
- case chatActions // archive, leave, delete
- }
- private let attachmentsRowGallery = 0
- private let attachmentsRowDocuments = 1
- private let membersRowAddMembers = 0
- private let membersRowQrInvite = 1
- private let memberManagementRows = 2
- private let chatActionsRowArchiveChat = 0
- private let chatActionsRowLeaveGroup = 1
- private let chatActionsRowDeleteChat = 2
- private let context: DcContext
- weak var coordinator: GroupChatDetailCoordinator?
- private let sections: [ProfileSections] = [.attachments, .members, .chatActions]
- private var currentUser: DcContact? {
- let myId = groupMemberIds.filter { DcContact(id: $0).email == DcConfig.addr }.first
- guard let currentUserId = myId else {
- return nil
- }
- return DcContact(id: currentUserId)
- }
- fileprivate var chat: DcChat
- // stores contactIds
- private var groupMemberIds: [Int] = []
- // MARK: - subviews
- private lazy var editBarButtonItem: UIBarButtonItem = {
- UIBarButtonItem(title: String.localized("global_menu_edit_desktop"), style: .plain, target: self, action: #selector(editButtonPressed))
- }()
- lazy var tableView: UITableView = {
- let table = UITableView(frame: .zero, style: .grouped)
- table.bounces = false
- table.register(UITableViewCell.self, forCellReuseIdentifier: "tableCell")
- table.register(ActionCell.self, forCellReuseIdentifier: "actionCell")
- table.register(ContactCell.self, forCellReuseIdentifier: "contactCell")
- table.delegate = self
- table.dataSource = self
- table.tableHeaderView = groupHeader
- return table
- }()
- private lazy var groupHeader: ContactDetailHeader = {
- let header = ContactDetailHeader()
- header.updateDetails(
- title: chat.name,
- subtitle: String.localizedStringWithFormat(String.localized("n_members"), chat.contactIds.count)
- )
- if let img = chat.profileImage {
- header.setImage(img)
- } else {
- header.setBackupImage(name: chat.name, color: chat.color)
- }
- header.setVerified(isVerified: chat.isVerified)
- return header
- }()
- private lazy var archiveChatCell: ActionCell = {
- let cell = ActionCell()
- cell.actionTitle = chat.isArchived ? String.localized("menu_unarchive_chat") : String.localized("menu_archive_chat")
- cell.actionColor = UIColor.systemBlue
- cell.selectionStyle = .none
- return cell
- }()
- private lazy var leaveGroupCell: ActionCell = {
- let cell = ActionCell()
- cell.actionTitle = String.localized("menu_leave_group")
- cell.actionColor = UIColor.red
- return cell
- }()
- private lazy var deleteChatCell: ActionCell = {
- let cell = ActionCell()
- cell.actionTitle = String.localized("menu_delete_chat")
- cell.actionColor = UIColor.red
- cell.selectionStyle = .none
- return cell
- }()
- private lazy var galleryCell: UITableViewCell = {
- let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
- cell.textLabel?.text = String.localized("gallery")
- cell.accessoryType = .disclosureIndicator
- return cell
- }()
- private lazy var documentsCell: UITableViewCell = {
- let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
- cell.textLabel?.text = String.localized("documents")
- cell.accessoryType = .disclosureIndicator
- return cell
- }()
- init(chatId: Int, context: DcContext) {
- self.context = context
- chat = DcChat(id: chatId)
- super.init(nibName: nil, bundle: nil)
- setupSubviews()
- }
- required init?(coder _: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- private func setupSubviews() {
- view.addSubview(tableView)
- tableView.translatesAutoresizingMaskIntoConstraints = false
- tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
- tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
- tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
- tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
- }
- // MARK: - lifecycle
- override func viewDidLoad() {
- super.viewDidLoad()
- title = String.localized("tab_group")
- navigationItem.rightBarButtonItem = editBarButtonItem
- groupHeader.frame = CGRect(0, 0, tableView.frame.width, ContactCell.cellHeight)
- }
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- updateGroupMembers()
- tableView.reloadData() // to display updates
- editBarButtonItem.isEnabled = currentUser != nil
- updateHeader()
- //update chat object, maybe chat name was edited
- chat = DcChat(id: chat.id)
- }
- // MARK: - update
- private func updateGroupMembers() {
- groupMemberIds = chat.contactIds
- tableView.reloadData()
- }
- private func updateHeader() {
- groupHeader.updateDetails(
- title: chat.name,
- subtitle: String.localizedStringWithFormat(String.localized("n_members"), chat.contactIds.count)
- )
- }
- // MARK: - actions
- @objc func editButtonPressed() {
- coordinator?.showGroupChatEdit(chat: chat)
- }
- private func toggleArchiveChat() {
- let archivedBefore = chat.isArchived
- context.archiveChat(chatId: chat.id, archive: !archivedBefore)
- if archivedBefore {
- archiveChatCell.actionTitle = String.localized("menu_archive_chat")
- } else {
- self.navigationController?.popToRootViewController(animated: false)
- }
- self.chat = DcChat(id: chat.id)
- }
- private func getGroupMemberIdFor(_ row: Int) -> Int {
- return groupMemberIds[row - memberManagementRows]
- }
- private func isMemberManagementRow(row: Int) -> Bool {
- return row < memberManagementRows
- }
- }
- // MARK: - UITableViewDelegate, UITableViewDataSource
- extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSource {
- func numberOfSections(in _: UITableView) -> Int {
- return sections.count
- }
- func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
- let sectionType = sections[section]
- switch sectionType {
- case .attachments:
- return 2
- case .members:
- return groupMemberIds.count + memberManagementRows
- case .chatActions:
- return 3
- }
- }
- func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
- let sectionType = sections[indexPath.section]
- let row = indexPath.row
- switch sectionType {
- case .attachments, .chatActions:
- return Constants.defaultCellHeight
- case .members:
- switch row {
- case membersRowAddMembers, membersRowQrInvite:
- return Constants.defaultCellHeight
- default:
- return ContactCell.cellHeight
- }
- }
- }
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let row = indexPath.row
- let sectionType = sections[indexPath.section]
- switch sectionType {
- case .attachments:
- if row == attachmentsRowGallery {
- return galleryCell
- } else if row == attachmentsRowDocuments {
- return documentsCell
- }
- case .members:
- if row == membersRowAddMembers || row == membersRowQrInvite {
- guard let actionCell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as? ActionCell else {
- safe_fatalError("could not dequeue action cell")
- break
- }
- if row == membersRowAddMembers {
- actionCell.actionTitle = String.localized("group_add_members")
- actionCell.actionColor = UIColor.systemBlue
- } else if row == membersRowQrInvite {
- actionCell.actionTitle = String.localized("qrshow_join_group_title")
- actionCell.actionColor = UIColor.systemBlue
- }
- return actionCell
- }
- guard let contactCell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as? ContactCell else {
- safe_fatalError("could not dequeue contactCell cell")
- break
- }
- let cellData = ContactCellData(contactId: getGroupMemberIdFor(row))
- let cellViewModel = ContactCellViewModel(contactData: cellData)
- contactCell.updateCell(cellViewModel: cellViewModel)
- return contactCell
- case .chatActions:
- if row == chatActionsRowArchiveChat {
- return archiveChatCell
- } else if row == chatActionsRowLeaveGroup {
- return leaveGroupCell
- } else if row == chatActionsRowDeleteChat {
- return deleteChatCell
- }
- }
- // should never get here
- return UITableViewCell(frame: .zero)
- }
- func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
- let sectionType = sections[indexPath.section]
- let row = indexPath.row
- switch sectionType {
- case .attachments:
- if row == attachmentsRowGallery {
- coordinator?.showGallery()
- } else if row == attachmentsRowDocuments {
- coordinator?.showDocuments()
- }
- case .members:
- if row == membersRowAddMembers {
- coordinator?.showAddGroupMember(chatId: chat.id)
- } else if row == membersRowQrInvite {
- coordinator?.showQrCodeInvite(chatId: chat.id)
- } else {
- let member = getGroupMember(at: row)
- coordinator?.showContactDetail(of: member.id)
- }
- case .chatActions:
- if row == chatActionsRowArchiveChat {
- toggleArchiveChat()
- } else if row == chatActionsRowLeaveGroup {
- showLeaveGroupConfirmationAlert()
- } else if row == chatActionsRowDeleteChat {
- showDeleteChatConfirmationAlert()
- }
- }
- }
- func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
- if sections[section] == .members {
- return String.localized("tab_members")
- }
- return nil
- }
- func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
- return Constants.defaultHeaderHeight
- }
- func tableView(_: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
- guard let currentUser = self.currentUser else {
- return false
- }
- let row = indexPath.row
- let sectionType = sections[indexPath.section]
- if sectionType == .members &&
- !isMemberManagementRow(row: row) &&
- getGroupMemberIdFor(row) != currentUser.id {
- return true
- }
- return false
- }
- func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
- guard let currentUser = self.currentUser else {
- return nil
- }
- let row = indexPath.row
- let sectionType = sections[indexPath.section]
- if sectionType == .members &&
- !isMemberManagementRow(row: row) &&
- getGroupMemberIdFor(row) != currentUser.id {
- // action set for members except for current user
- let delete = UITableViewRowAction(style: .destructive, title: String.localized("remove_desktop")) { [unowned self] _, indexPath in
- let contact = self.getGroupMember(at: row)
- let title = String.localizedStringWithFormat(String.localized("ask_remove_members"), contact.nameNAddr)
- let alert = UIAlertController(title: title, message: nil, preferredStyle: .safeActionSheet)
- alert.addAction(UIAlertAction(title: String.localized("remove_desktop"), style: .destructive, handler: { _ in
- let success = dc_remove_contact_from_chat(mailboxPointer, UInt32(self.chat.id), UInt32(contact.id))
- if success == 1 {
- self.removeGroupMemberFromTableAt(indexPath)
- }
- }))
- alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
- self.present(alert, animated: true, completion: nil)
- }
- delete.backgroundColor = UIColor.red
- return [delete]
- }
- return nil
- }
- private func getGroupMember(at row: Int) -> DcContact {
- return DcContact(id: getGroupMemberIdFor(row))
- }
- private func removeGroupMemberFromTableAt(_ indexPath: IndexPath) {
- self.groupMemberIds.remove(at: indexPath.row - memberManagementRows)
- self.tableView.deleteRows(at: [indexPath], with: .automatic)
- updateHeader() // to display correct group size
- }
- }
- // MARK: - alerts
- extension GroupChatDetailViewController {
- private func showDeleteChatConfirmationAlert() {
- let alert = UIAlertController(
- title: nil,
- message: String.localized("ask_delete_chat_desktop"),
- preferredStyle: .safeActionSheet
- )
- alert.addAction(UIAlertAction(title: String.localized("menu_delete_chat"), style: .destructive, handler: { _ in
- self.coordinator?.deleteChat()
- }))
- alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
- self.present(alert, animated: true, completion: nil)
- }
- private func showLeaveGroupConfirmationAlert() {
- if let userId = currentUser?.id {
- let alert = UIAlertController(title: String.localized("ask_leave_group"), message: nil, preferredStyle: .safeActionSheet)
- alert.addAction(UIAlertAction(title: String.localized("menu_leave_group"), style: .destructive, handler: { _ in
- dc_remove_contact_from_chat(mailboxPointer, UInt32(self.chat.id), UInt32(userId))
- self.editBarButtonItem.isEnabled = false
- self.updateGroupMembers()
- }))
- alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
- present(alert, animated: true, completion: nil)
- }
- }
- }
|