diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 91ea88b0f..0e46f9f48 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -5,7 +5,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega private let thread: TSGroupThread private var name = "" private var zombies: Set = [] - private var members: [String] = [] { didSet { handleMembersChanged() } } + private var membersAndZombies: [String] = [] { didSet { handleMembersChanged() } } private var isEditingGroupName = false { didSet { handleIsEditingGroupNameChanged() } } private var tableViewHeightConstraint: NSLayoutConstraint! @@ -78,7 +78,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega setUpViewHierarchy() // Always show zombies at the bottom zombies = Storage.shared.getZombieMembers(for: groupPublicKey) - members = GroupUtilities.getClosedGroupMembers(thread).sorted { getDisplayName(for: $0) < getDisplayName(for: $1) } + membersAndZombies = GroupUtilities.getClosedGroupMembers(thread).sorted { getDisplayName(for: $0) < getDisplayName(for: $1) } + zombies.sorted { getDisplayName(for: $0) < getDisplayName(for: $1) } updateNavigationBarButtons() name = thread.groupModel.groupName! @@ -107,7 +107,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega membersLabel.font = .systemFont(ofSize: Values.mediumFontSize) membersLabel.text = "Members" // Add members button - let hasContactsToAdd = !Set(ContactUtilities.getAllContacts()).subtracting(self.members).isEmpty + let hasContactsToAdd = !Set(ContactUtilities.getAllContacts()).subtracting(self.membersAndZombies).isEmpty if (!hasContactsToAdd) { addMembersButton.isUserInteractionEnabled = false let disabledColor = Colors.text.withAlphaComponent(Values.mediumOpacity) @@ -147,29 +147,31 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega // MARK: Table View Data Source / Delegate func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return members.count + return membersAndZombies.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell") as! UserCell - let publicKey = members[indexPath.row] + let publicKey = membersAndZombies[indexPath.row] cell.publicKey = publicKey cell.isZombie = zombies.contains(publicKey) - cell.accessory = !canBeRemoved(publicKey) ? .lock : .none + let userPublicKey = getUserHexEncodedPublicKey() + let isCurrentUserAdmin = thread.groupModel.groupAdminIds.contains(userPublicKey) + cell.accessory = !isCurrentUserAdmin ? .lock : .none cell.update() return cell } func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - let publicKey = members[indexPath.row] - return canBeRemoved(publicKey) + let userPublicKey = getUserHexEncodedPublicKey() + return thread.groupModel.groupAdminIds.contains(userPublicKey) } func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { - let publicKey = members[indexPath.row] + let publicKey = membersAndZombies[indexPath.row] let removeAction = UITableViewRowAction(style: .destructive, title: "Remove") { [weak self] _, _ in - guard let self = self, let index = self.members.firstIndex(of: publicKey) else { return } - self.members.remove(at: index) + guard let self = self, let index = self.membersAndZombies.firstIndex(of: publicKey) else { return } + self.membersAndZombies.remove(at: index) } removeAction.backgroundColor = Colors.destructive return [ removeAction ] @@ -190,7 +192,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega } private func handleMembersChanged() { - tableViewHeightConstraint.constant = CGFloat(members.count) * 67 + tableViewHeightConstraint.constant = CGFloat(membersAndZombies.count) * 67 tableView.reloadData() } @@ -239,15 +241,15 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega @objc private func addMembers() { let title = "Add Members" - let userSelectionVC = UserSelectionVC(with: title, excluding: Set(members)) { [weak self] selectedUsers in + let userSelectionVC = UserSelectionVC(with: title, excluding: Set(membersAndZombies)) { [weak self] selectedUsers in guard let self = self else { return } - var members = self.members + var members = self.membersAndZombies members.append(contentsOf: selectedUsers) func getDisplayName(for publicKey: String) -> String { return Storage.shared.getContact(with: publicKey)?.displayName(for: .regular) ?? publicKey } - self.members = members.sorted { getDisplayName(for: $0) < getDisplayName(for: $1) } - let hasContactsToAdd = !Set(ContactUtilities.getAllContacts()).subtracting(self.members).isEmpty + self.membersAndZombies = members.sorted { getDisplayName(for: $0) < getDisplayName(for: $1) } + let hasContactsToAdd = !Set(ContactUtilities.getAllContacts()).subtracting(self.membersAndZombies).isEmpty self.addMembersButton.isUserInteractionEnabled = hasContactsToAdd let color = hasContactsToAdd ? Colors.accent : Colors.text.withAlphaComponent(Values.mediumOpacity) self.addMembersButton.layer.borderColor = color.cgColor @@ -264,9 +266,11 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega editVC.navigationController!.popViewController(animated: true) } } - let members = Set(self.members) + let storage = SNMessagingKitConfiguration.shared.storage + let members = Set(self.membersAndZombies) let name = self.name - guard members != Set(thread.groupModel.groupMemberIds) || name != thread.groupModel.groupName else { + let zombies = storage.getZombieMembers(for: groupPublicKey) + guard members != Set(thread.groupModel.groupMemberIds + zombies) || name != thread.groupModel.groupName else { return popToConversationVC(self) } if !members.contains(getUserHexEncodedPublicKey()) { @@ -305,10 +309,4 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)) presentAlert(alert) } - - private func canBeRemoved(_ publicKey: String) -> Bool { - let userPublicKey = getUserHexEncodedPublicKey() - let isCurrentUserAdmin = thread.groupModel.groupAdminIds.contains(userPublicKey) - return isCurrentUserAdmin || (publicKey == userPublicKey) - } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift index b9700ceb5..8192849e4 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift @@ -108,8 +108,9 @@ extension MessageSender { let addedMembers = members.subtracting(group.groupMemberIds) if !addedMembers.isEmpty { promises.append(addMembers(addedMembers, to: groupPublicKey, using: transaction)) } // Remove members if needed - let removedMembers = Set(group.groupMemberIds).subtracting(members) - if !removedMembers.isEmpty { promises.append(removeMembers(removedMembers, to: groupPublicKey, using: transaction)) } + let zombies = SNMessagingKitConfiguration.shared.storage.getZombieMembers(for: groupPublicKey) + let removedMembers = Set(group.groupMemberIds + zombies).subtracting(members) + if !removedMembers.isEmpty{ promises.append(removeMembers(removedMembers, to: groupPublicKey, using: transaction)) } // Return return when(fulfilled: promises).map2 { _ in } } @@ -198,11 +199,12 @@ extension MessageSender { let userPublicKey = getUserHexEncodedPublicKey() let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) let threadID = TSGroupThread.threadId(fromGroupId: groupID) + let storage = SNMessagingKitConfiguration.shared.storage guard let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { SNLog("Can't remove members from nonexistent closed group.") return Promise(error: Error.noThread) } - guard !membersToRemove.isEmpty else { + guard !membersToRemove.isEmpty || !storage.getZombieMembers(for: groupPublicKey).isEmpty else { SNLog("Invalid closed group update.") return Promise(error: Error.invalidClosedGroupUpdate) } @@ -217,7 +219,6 @@ extension MessageSender { } let members = Set(group.groupMemberIds).subtracting(membersToRemove) // Update zombie list - let storage = SNMessagingKitConfiguration.shared.storage let zombies = storage.getZombieMembers(for: groupPublicKey).subtracting(membersToRemove) storage.setZombieMembers(for: groupPublicKey, to: zombies, using: transaction) // Send the update to the group and generate + distribute a new encryption key pair