// // Copyright (c) 2020 Open Whisper Systems. All rights reserved. // import Foundation import PromiseKit protocol AddGroupMembersViewControllerDelegate: class { func addGroupMembersViewDidUpdate() } // MARK: - @objc public class AddGroupMembersViewController: BaseGroupMemberViewController { // MARK: - Dependencies fileprivate var databaseStorage: SDSDatabaseStorage { return SDSDatabaseStorage.shared } fileprivate var tsAccountManager: TSAccountManager { return .sharedInstance() } private var contactsManager: OWSContactsManager { return Environment.shared.contactsManager } // MARK: - weak var addGroupMembersViewControllerDelegate: AddGroupMembersViewControllerDelegate? private let groupThread: TSGroupThread private let oldGroupModel: TSGroupModel private var newRecipientSet = OrderedSet() public required init(groupThread: TSGroupThread) { self.groupThread = groupThread self.oldGroupModel = groupThread.groupModel super.init() groupMemberViewDelegate = self } // MARK: - View Lifecycle @objc public override func viewDidLoad() { super.viewDidLoad() title = NSLocalizedString("ADD_GROUP_MEMBERS_VIEW_TITLE", comment: "The title for the 'add group members' view.") } // MARK: - fileprivate func updateNavbar() { if hasUnsavedChanges { navigationItem.rightBarButtonItem = UIBarButtonItem(title: NSLocalizedString("EDIT_GROUP_UPDATE_BUTTON", comment: "The title for the 'update group' button."), style: .plain, target: self, action: #selector(updateGroupPressed), accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "update")) } else { navigationItem.rightBarButtonItem = nil } } @objc func updateGroupPressed() { showConfirmationAlert() } private func showConfirmationAlert() { let newMemberCount = newRecipientSet.count guard newMemberCount > 0 else { owsFailDebug("No new members.") return } let groupName = contactsManager.displayNameWithSneakyTransaction(thread: groupThread) let alertTitle: String let alertMessage: String let actionTitle: String if newMemberCount > 1 { let messageFormat = NSLocalizedString("ADD_GROUP_MEMBERS_VIEW_CONFIRM_ALERT_MESSAGE_N_FORMAT", comment: "Format for the message for the 'add group members' confirmation alert. Embeds {{ %1$@ number of new members, %2$@ name of the group. }}.") alertMessage = String(format: messageFormat, OWSFormat.formatInt(newRecipientSet.count), groupName) alertTitle = NSLocalizedString("ADD_GROUP_MEMBERS_VIEW_CONFIRM_ALERT_TITLE_N", comment: "Title for the 'add group members' confirmation alert.") actionTitle = NSLocalizedString("ADD_GROUP_MEMBERS_ACTION_TITLE_N", comment: "Label for the 'add group members' button.") } else { let messageFormat = NSLocalizedString("ADD_GROUP_MEMBERS_VIEW_CONFIRM_ALERT_MESSAGE_1_FORMAT", comment: "Format for the message for the 'add group member' confirmation alert. Embeds {{ the name of the group. }}.") alertMessage = String(format: messageFormat, groupName) alertTitle = NSLocalizedString("ADD_GROUP_MEMBERS_VIEW_CONFIRM_ALERT_TITLE_1", comment: "Title for the 'add group member' confirmation alert.") actionTitle = NSLocalizedString("ADD_GROUP_MEMBERS_ACTION_TITLE_1", comment: "Label for the 'add group member' button.") } let actionSheet = ActionSheetController(title: alertTitle, message: alertMessage) actionSheet.addAction(ActionSheetAction(title: actionTitle, style: .default, handler: { [weak self] _ in self?.updateGroupThreadAndDismiss() })) actionSheet.addAction(OWSActionSheets.cancelAction) self.presentActionSheet(actionSheet) } } // MARK: - private extension AddGroupMembersViewController { func buildNewGroupModel() -> TSGroupModel? { do { return try databaseStorage.read { transaction in var builder = self.oldGroupModel.asBuilder let oldGroupMembership = self.oldGroupModel.groupMembership var groupMembershipBuilder = oldGroupMembership.asBuilder let addresses = self.newRecipientSet.orderedMembers.compactMap { $0.address } guard !addresses.isEmpty else { owsFailDebug("No valid recipients.") return nil } for address in addresses { guard !oldGroupMembership.isPendingOrNonPendingMember(address) else { owsFailDebug("Recipient is already in group.") continue } // GroupManager will separate out members as pending if necessary. groupMembershipBuilder.addNonPendingMember(address, role: .normal) } builder.groupMembership = groupMembershipBuilder.build() return try builder.build(transaction: transaction) } } catch { owsFailDebug("Error: \(error)") return nil } } func updateGroupThreadAndDismiss() { let dismissAndUpdateDelegate = { [weak self] in guard let self = self else { return } self.addGroupMembersViewControllerDelegate?.addGroupMembersViewDidUpdate() self.navigationController?.popViewController(animated: true) } guard hasUnsavedChanges else { owsFailDebug("!hasUnsavedChanges.") return dismissAndUpdateDelegate() } let oldGroupModel = self.oldGroupModel guard let newGroupModel = buildNewGroupModel() else { let error = OWSAssertionError("Couldn't build group model.") GroupViewUtils.showUpdateErrorUI(error: error) return } GroupViewUtils.updateGroupWithActivityIndicator(fromViewController: self, updatePromiseBlock: { self.updateGroupThreadPromise(oldGroupModel: oldGroupModel, newGroupModel: newGroupModel) }, completion: { dismissAndUpdateDelegate() }) } func updateGroupThreadPromise(oldGroupModel: TSGroupModel, newGroupModel: TSGroupModel) -> Promise { guard let localAddress = tsAccountManager.localAddress else { return Promise(error: OWSAssertionError("Missing localAddress.")) } return firstly { () -> Promise in return GroupManager.messageProcessingPromise(for: oldGroupModel, description: self.logTag) }.then(on: .global()) { _ in // dmConfiguration: nil means don't change disappearing messages configuration. GroupManager.localUpdateExistingGroup(groupModel: newGroupModel, dmConfiguration: nil, groupUpdateSourceAddress: localAddress) }.asVoid() } } // MARK: - extension AddGroupMembersViewController: GroupMemberViewDelegate { var groupMemberViewRecipientSet: OrderedSet { return newRecipientSet } var groupMemberViewHasUnsavedChanges: Bool { return !newRecipientSet.isEmpty } func groupMemberViewRemoveRecipient(_ recipient: PickedRecipient) { newRecipientSet.remove(recipient) updateNavbar() } func groupMemberViewAddRecipient(_ recipient: PickedRecipient) { newRecipientSet.append(recipient) updateNavbar() } func groupMemberViewCanAddRecipient(_ recipient: PickedRecipient) -> Bool { guard let address = recipient.address else { owsFailDebug("Invalid recipient.") return false } return databaseStorage.read { transaction in return GroupManager.doesUserSupportGroupsV2(address: address, transaction: transaction) } } func groupMemberViewIsPreExistingMember(_ recipient: PickedRecipient) -> Bool { guard let address = recipient.address else { owsFailDebug("Invalid recipient.") return false } return oldGroupModel.groupMembership.isPendingOrNonPendingMember(address) } func groupMemberViewDismiss() { navigationController?.popViewController(animated: true) } }