Add "replace admin" flow.
This commit is contained in:
parent
b5c069ed95
commit
ef53148b33
@ -194,6 +194,7 @@
|
||||
347B83F92436820C0019A52C /* GroupViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347B83F82436820C0019A52C /* GroupViewHelper.swift */; };
|
||||
347B83FB243779270019A52C /* OWSFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347B83FA243779270019A52C /* OWSFormat.swift */; };
|
||||
347B83FD24378DDF0019A52C /* PendingGroupMembersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347B83FC24378DDE0019A52C /* PendingGroupMembersViewController.swift */; };
|
||||
348433DF243CA94600C7F64A /* ReplaceAdminViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348433DE243CA94500C7F64A /* ReplaceAdminViewController.swift */; };
|
||||
34843B2421432293004DED45 /* SignalBaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34843B2221432292004DED45 /* SignalBaseTest.m */; };
|
||||
34843B26214327C9004DED45 /* OWSOrphanDataCleanerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34843B25214327C9004DED45 /* OWSOrphanDataCleanerTest.m */; };
|
||||
34843B2C214FE296004DED45 /* MockEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 34843B2A214FE295004DED45 /* MockEnvironment.m */; };
|
||||
@ -1072,6 +1073,7 @@
|
||||
347B83F82436820C0019A52C /* GroupViewHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupViewHelper.swift; sourceTree = "<group>"; };
|
||||
347B83FA243779270019A52C /* OWSFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSFormat.swift; sourceTree = "<group>"; };
|
||||
347B83FC24378DDE0019A52C /* PendingGroupMembersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingGroupMembersViewController.swift; sourceTree = "<group>"; };
|
||||
348433DE243CA94500C7F64A /* ReplaceAdminViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceAdminViewController.swift; sourceTree = "<group>"; };
|
||||
34843B2221432292004DED45 /* SignalBaseTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalBaseTest.m; sourceTree = "<group>"; };
|
||||
34843B2321432293004DED45 /* SignalBaseTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalBaseTest.h; sourceTree = "<group>"; };
|
||||
34843B25214327C9004DED45 /* OWSOrphanDataCleanerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOrphanDataCleanerTest.m; sourceTree = "<group>"; };
|
||||
@ -1979,6 +1981,7 @@
|
||||
3425648723E214BC0016905B /* OWSConversationSettingsViewController.swift */,
|
||||
340FC899204DAC8D007AEB0F /* OWSConversationSettingsViewDelegate.h */,
|
||||
347B83FC24378DDE0019A52C /* PendingGroupMembersViewController.swift */,
|
||||
348433DE243CA94500C7F64A /* ReplaceAdminViewController.swift */,
|
||||
340FC89E204DAC8D007AEB0F /* ShowGroupMembersViewController.h */,
|
||||
340FC8A6204DAC8D007AEB0F /* ShowGroupMembersViewController.m */,
|
||||
340FC8A3204DAC8D007AEB0F /* UpdateGroupViewController.h */,
|
||||
@ -4544,6 +4547,7 @@
|
||||
340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */,
|
||||
8806F21B23E90B0B00692638 /* MessageRequests.swift in Sources */,
|
||||
3434AE1E22AEF5A4002EE04E /* CircularProgressView.swift in Sources */,
|
||||
348433DF243CA94600C7F64A /* ReplaceAdminViewController.swift in Sources */,
|
||||
34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */,
|
||||
887EEC1F23F0B20600F8C26D /* PinReminderMegaphone.swift in Sources */,
|
||||
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
|
||||
|
||||
@ -89,12 +89,6 @@ class ConversationSettingsViewController: OWSTableViewController {
|
||||
groupViewHelper.delegate = self
|
||||
}
|
||||
|
||||
@available(*, unavailable, message:"use other constructor instead.")
|
||||
@objc
|
||||
public required init(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
private func observeNotifications() {
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(identityStateDidChange(notification:)),
|
||||
@ -523,11 +517,22 @@ class ConversationSettingsViewController: OWSTableViewController {
|
||||
}
|
||||
|
||||
func didTapLeaveGroup() {
|
||||
if isLastAdminInV2Group {
|
||||
showReplaceAdminAlert()
|
||||
} else {
|
||||
showLeaveGroupConfirmAlert()
|
||||
}
|
||||
}
|
||||
|
||||
let alert = ActionSheetController(title: NSLocalizedString("CONFIRM_LEAVE_GROUP_TITLE", comment: "Alert title"),
|
||||
message: NSLocalizedString("CONFIRM_LEAVE_GROUP_DESCRIPTION", comment: "Alert body"))
|
||||
func showLeaveGroupConfirmAlert() {
|
||||
|
||||
let leaveAction = ActionSheetAction(title: NSLocalizedString("LEAVE_BUTTON_TITLE", comment: "Confirmation button within contextual alert"),
|
||||
let alert = ActionSheetController(title: NSLocalizedString("CONFIRM_LEAVE_GROUP_TITLE",
|
||||
comment: "Alert title"),
|
||||
message: NSLocalizedString("CONFIRM_LEAVE_GROUP_DESCRIPTION",
|
||||
comment: "Alert body"))
|
||||
|
||||
let leaveAction = ActionSheetAction(title: NSLocalizedString("LEAVE_BUTTON_TITLE",
|
||||
comment: "Confirmation button within contextual alert"),
|
||||
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "leave_group_confirm"),
|
||||
style: .destructive) { _ in
|
||||
self.leaveGroup()
|
||||
@ -538,6 +543,74 @@ class ConversationSettingsViewController: OWSTableViewController {
|
||||
presentActionSheet(alert)
|
||||
}
|
||||
|
||||
func showReplaceAdminAlert() {
|
||||
let candidates = self.replacementAdminCandidates
|
||||
guard !candidates.isEmpty else {
|
||||
// TODO: We could offer a "delete group locally" option here.
|
||||
OWSActionSheets.showErrorAlert(message: NSLocalizedString("GROUPS_CANT_REPLACE_ADMIN_ALERT_MESSAGE",
|
||||
comment: "Message for the 'can't replace group admin' alert."))
|
||||
return
|
||||
}
|
||||
|
||||
let alert = ActionSheetController(title: NSLocalizedString("GROUPS_REPLACE_ADMIN_ALERT_TITLE",
|
||||
comment: "Title for the 'replace group admin' alert."),
|
||||
message: NSLocalizedString("GROUPS_REPLACE_ADMIN_ALERT_MESSAGE",
|
||||
comment: "Message for the 'replace group admin' alert."))
|
||||
|
||||
alert.addAction(ActionSheetAction(title: NSLocalizedString("GROUPS_REPLACE_ADMIN_BUTTON",
|
||||
comment: "Label for the 'replace group admin' button."),
|
||||
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "replace_admin_alert"),
|
||||
style: .destructive) { _ in
|
||||
self.showReplaceAdminView(candidates: candidates)
|
||||
})
|
||||
alert.addAction(OWSActionSheets.cancelAction)
|
||||
presentActionSheet(alert)
|
||||
}
|
||||
|
||||
func showReplaceAdminView(candidates: Set<SignalServiceAddress>) {
|
||||
assert(!candidates.isEmpty)
|
||||
let replaceAdminViewController = ReplaceAdminViewController(candidates: candidates,
|
||||
replaceAdminViewControllerDelegate: self)
|
||||
navigationController?.pushViewController(replaceAdminViewController, animated: true)
|
||||
}
|
||||
|
||||
private var isLastAdminInV2Group: Bool {
|
||||
guard let groupThread = thread as? TSGroupThread else {
|
||||
owsFailDebug("Invalid thread.")
|
||||
return false
|
||||
}
|
||||
guard let groupModelV2 = groupThread.groupModel as? TSGroupModelV2 else {
|
||||
return false
|
||||
}
|
||||
guard let localAddress = tsAccountManager.localAddress else {
|
||||
owsFailDebug("missing local address")
|
||||
return false
|
||||
}
|
||||
let groupMembership = groupModelV2.groupMembership
|
||||
guard groupMembership.isNonPendingMember(localAddress),
|
||||
groupMembership.isAdministrator(localAddress) else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private var replacementAdminCandidates: Set<SignalServiceAddress> {
|
||||
guard let groupThread = thread as? TSGroupThread else {
|
||||
owsFailDebug("Invalid thread.")
|
||||
return []
|
||||
}
|
||||
guard let groupModelV2 = groupThread.groupModel as? TSGroupModelV2 else {
|
||||
return []
|
||||
}
|
||||
guard let localAddress = tsAccountManager.localAddress else {
|
||||
owsFailDebug("missing local address")
|
||||
return []
|
||||
}
|
||||
var candidates = groupModelV2.groupMembership.nonPendingAdministrators
|
||||
candidates.remove(localAddress)
|
||||
return candidates
|
||||
}
|
||||
|
||||
private func leaveGroup() {
|
||||
guard let groupThread = thread as? TSGroupThread else {
|
||||
owsFailDebug("Invalid thread.")
|
||||
@ -933,3 +1006,11 @@ extension ConversationSettingsViewController: GroupViewHelperDelegate {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension ConversationSettingsViewController: ReplaceAdminViewControllerDelegate {
|
||||
func replaceAdmin(address: SignalServiceAddress) {
|
||||
// TODO:
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
protocol ReplaceAdminViewControllerDelegate: class {
|
||||
func replaceAdmin(address: SignalServiceAddress)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
class ReplaceAdminViewController: OWSTableViewController {
|
||||
|
||||
// MARK: - Dependencies
|
||||
|
||||
private var contactsManager: OWSContactsManager {
|
||||
return Environment.shared.contactsManager
|
||||
}
|
||||
|
||||
private var databaseStorage: SDSDatabaseStorage {
|
||||
return SDSDatabaseStorage.shared
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
weak var replaceAdminViewControllerDelegate: ReplaceAdminViewControllerDelegate?
|
||||
|
||||
private let candidates: Set<SignalServiceAddress>
|
||||
|
||||
required init(candidates: Set<SignalServiceAddress>,
|
||||
replaceAdminViewControllerDelegate: ReplaceAdminViewControllerDelegate) {
|
||||
assert(!candidates.isEmpty)
|
||||
|
||||
self.candidates = candidates
|
||||
self.replaceAdminViewControllerDelegate = replaceAdminViewControllerDelegate
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
@objc
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
title = NSLocalizedString("REPLACE_ADMIN_VIEW_TITLE",
|
||||
comment: "The title for the 'replace group admin' view.")
|
||||
|
||||
view.backgroundColor = Theme.backgroundColor
|
||||
tableView.backgroundColor = Theme.backgroundColor
|
||||
tableView.separatorColor = .clear
|
||||
|
||||
updateTableContents()
|
||||
}
|
||||
|
||||
private func updateTableContents() {
|
||||
let contents = OWSTableContents()
|
||||
|
||||
let section = OWSTableSection()
|
||||
|
||||
let sortedCandidates = databaseStorage.uiRead { transaction in
|
||||
self.contactsManager.sortSignalServiceAddresses(Array(self.candidates), transaction: transaction)
|
||||
}
|
||||
for address in sortedCandidates {
|
||||
section.add(OWSTableItem(customCellBlock: {
|
||||
let cell = ContactTableViewCell()
|
||||
|
||||
let imageView = UIImageView()
|
||||
imageView.setTemplateImageName("empty-circle-outline-24", tintColor: .ows_gray25)
|
||||
cell.ows_setAccessoryView(imageView)
|
||||
|
||||
cell.configure(withRecipientAddress: address)
|
||||
|
||||
return cell
|
||||
},
|
||||
customRowHeight: UITableView.automaticDimension,
|
||||
actionBlock: { [weak self] in
|
||||
self?.replaceAdminViewControllerDelegate?.replaceAdmin(address: address)
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
contents.addSection(section)
|
||||
|
||||
self.contents = contents
|
||||
}
|
||||
}
|
||||
@ -154,7 +154,7 @@ extension SignalCall: CallManagerCallReference { }
|
||||
return AppEnvironment.shared.notificationPresenter
|
||||
}
|
||||
|
||||
var databaseStorage: SDSDatabaseStorage {
|
||||
private var databaseStorage: SDSDatabaseStorage {
|
||||
return SDSDatabaseStorage.shared
|
||||
}
|
||||
|
||||
|
||||
@ -1562,6 +1562,9 @@
|
||||
/* Message indicating that the local user left the group. */
|
||||
"GROUP_YOU_LEFT" = "You have left the group.";
|
||||
|
||||
/* Message for the 'can't replace group admin' alert. */
|
||||
"GROUPS_CANT_REPLACE_ADMIN_ALERT_MESSAGE" = "Choose a new admin for this group before you leave.";
|
||||
|
||||
/* Error indicating that an error occurred while accepting an invite. */
|
||||
"GROUPS_INVITE_ACCEPT_INVITE_FAILED" = "Could not accept invite.";
|
||||
|
||||
@ -1574,6 +1577,15 @@
|
||||
/* Label for 'block inviter' button in group invite view. Embeds {{name of user who invited you}}. */
|
||||
"GROUPS_INVITE_BLOCK_INVITER_FORMAT" = "Block %@";
|
||||
|
||||
/* Message for the 'replace group admin' alert. */
|
||||
"GROUPS_REPLACE_ADMIN_ALERT_MESSAGE" = "Before you leave, choose a new admin for this group.";
|
||||
|
||||
/* Title for the 'replace group admin' alert. */
|
||||
"GROUPS_REPLACE_ADMIN_ALERT_TITLE" = "Choose New Admin";
|
||||
|
||||
/* Label for the 'replace group admin' button. */
|
||||
"GROUPS_REPLACE_ADMIN_BUTTON" = "Choose new admin";
|
||||
|
||||
/* Label for 'archived conversations' button. */
|
||||
"HOME_VIEW_ARCHIVED_CONVERSATIONS" = "Archived Conversations";
|
||||
|
||||
@ -2912,6 +2924,9 @@
|
||||
/* Alert title after wrong guess for 'two-factor auth pin' reminder activity */
|
||||
"REMINDER_2FA_WRONG_PIN_ALERT_TITLE" = "That is not the correct PIN.";
|
||||
|
||||
/* The title for the 'replace group admin' view. */
|
||||
"REPLACE_ADMIN_VIEW_TITLE" = "Choose New Admin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"REREGISTER_FOR_PUSH" = "Re-register for push notifications";
|
||||
|
||||
|
||||
@ -361,6 +361,8 @@ public class GroupsV2ChangeSetImpl: NSObject, GroupsV2ChangeSet {
|
||||
Logger.verbose("Version: \(oldVersion) -> \(newVersion)")
|
||||
actionsBuilder.setVersion(newVersion)
|
||||
|
||||
var nonPendingAdministratorCount: Int = currentGroupModel.groupMembership.nonPendingAdministrators.count
|
||||
|
||||
var didChange = false
|
||||
|
||||
if let title = self.title {
|
||||
@ -400,7 +402,7 @@ public class GroupsV2ChangeSetImpl: NSObject, GroupsV2ChangeSet {
|
||||
continue
|
||||
}
|
||||
guard let profileKeyCredential = profileKeyCredentialMap[uuid] else {
|
||||
throw OWSAssertionError("Missing profile key credential]: \(uuid)")
|
||||
throw OWSAssertionError("Missing profile key credential: \(uuid)")
|
||||
}
|
||||
let actionBuilder = GroupsProtoGroupChangeActionsAddMemberAction.builder()
|
||||
actionBuilder.setAdded(try GroupsV2Protos.buildMemberProto(profileKeyCredential: profileKeyCredential,
|
||||
@ -408,6 +410,10 @@ public class GroupsV2ChangeSetImpl: NSObject, GroupsV2ChangeSet {
|
||||
groupV2Params: groupV2Params))
|
||||
actionsBuilder.addAddMembers(try actionBuilder.build())
|
||||
didChange = true
|
||||
|
||||
if role == .administrator {
|
||||
nonPendingAdministratorCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
for (uuid, role) in self.pendingMembersToAdd {
|
||||
@ -433,6 +439,10 @@ public class GroupsV2ChangeSetImpl: NSObject, GroupsV2ChangeSet {
|
||||
actionBuilder.setDeletedUserID(userId)
|
||||
actionsBuilder.addDeleteMembers(try actionBuilder.build())
|
||||
didChange = true
|
||||
|
||||
if currentGroupMembership.isAdministrator(SignalServiceAddress(uuid: uuid)) {
|
||||
nonPendingAdministratorCount -= 1
|
||||
}
|
||||
} else if currentGroupMembership.isPendingMember(uuid) {
|
||||
let actionBuilder = GroupsProtoGroupChangeActionsDeletePendingMemberAction.builder()
|
||||
let userId = try groupV2Params.userId(forUuid: uuid)
|
||||
@ -447,12 +457,13 @@ public class GroupsV2ChangeSetImpl: NSObject, GroupsV2ChangeSet {
|
||||
}
|
||||
}
|
||||
|
||||
for (uuid, role) in self.membersToChangeRole {
|
||||
for (uuid, newRole) in self.membersToChangeRole {
|
||||
guard currentGroupMembership.isNonPendingMember(uuid) else {
|
||||
// User is no longer a member.
|
||||
throw GroupsV2Error.conflictingChange
|
||||
}
|
||||
guard currentGroupMembership.role(for: SignalServiceAddress(uuid: uuid)) != role else {
|
||||
let currentRole = currentGroupMembership.role(for: SignalServiceAddress(uuid: uuid))
|
||||
guard currentRole != newRole else {
|
||||
// Another user has already modifed the role of this member.
|
||||
// We don't treat that as a conflict.
|
||||
continue
|
||||
@ -460,9 +471,15 @@ public class GroupsV2ChangeSetImpl: NSObject, GroupsV2ChangeSet {
|
||||
let actionBuilder = GroupsProtoGroupChangeActionsModifyMemberRoleAction.builder()
|
||||
let userId = try groupV2Params.userId(forUuid: uuid)
|
||||
actionBuilder.setUserID(userId)
|
||||
actionBuilder.setRole(role.asProtoRole)
|
||||
actionBuilder.setRole(newRole.asProtoRole)
|
||||
actionsBuilder.addModifyMemberRoles(try actionBuilder.build())
|
||||
didChange = true
|
||||
|
||||
if currentRole == .administrator {
|
||||
nonPendingAdministratorCount -= 1
|
||||
} else if newRole == .administrator {
|
||||
nonPendingAdministratorCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
let currentAccess = currentGroupModel.access
|
||||
@ -493,7 +510,7 @@ public class GroupsV2ChangeSetImpl: NSObject, GroupsV2ChangeSet {
|
||||
throw GroupsV2Error.redundantChange
|
||||
}
|
||||
guard let profileKeyCredential = profileKeyCredentialMap[localUuid] else {
|
||||
throw OWSAssertionError("Missing profile key credential]: \(localUuid)")
|
||||
throw OWSAssertionError("Missing profile key credential: \(localUuid)")
|
||||
}
|
||||
let actionBuilder = GroupsProtoGroupChangeActionsPromotePendingMemberAction.builder()
|
||||
actionBuilder.setPresentation(try GroupsV2Protos.presentationData(profileKeyCredential: profileKeyCredential,
|
||||
@ -503,6 +520,15 @@ public class GroupsV2ChangeSetImpl: NSObject, GroupsV2ChangeSet {
|
||||
}
|
||||
|
||||
if self.shouldLeaveGroupDeclineInvite {
|
||||
let isLastAdminInV2Group = (currentGroupMembership.isNonPendingMember(localAddress) &&
|
||||
currentGroupMembership.isAdministrator(localAddress) &&
|
||||
nonPendingAdministratorCount == 1)
|
||||
guard !isLastAdminInV2Group else {
|
||||
// This could happen if the last two admins leave at the same time
|
||||
// and race.
|
||||
throw GroupsV2Error.lastAdminCantLeaveGroup
|
||||
}
|
||||
|
||||
// Check that we are still invited or in group.
|
||||
if currentGroupMembership.pendingMembers.contains(localAddress) {
|
||||
// Decline invite
|
||||
@ -544,7 +570,7 @@ public class GroupsV2ChangeSetImpl: NSObject, GroupsV2ChangeSet {
|
||||
|
||||
if shouldUpdateLocalProfileKey {
|
||||
guard let profileKeyCredential = profileKeyCredentialMap[localUuid] else {
|
||||
throw OWSAssertionError("Missing profile key credential]: \(localUuid)")
|
||||
throw OWSAssertionError("Missing profile key credential: \(localUuid)")
|
||||
}
|
||||
let actionBuilder = GroupsProtoGroupChangeActionsModifyMemberProfileKeyAction.builder()
|
||||
actionBuilder.setPresentation(try GroupsV2Protos.presentationData(profileKeyCredential: profileKeyCredential,
|
||||
|
||||
@ -107,8 +107,8 @@ public class GroupsV2Test: NSObject {
|
||||
throw OWSAssertionError("Unexpected members: \(groupMembership.nonPendingMembers).")
|
||||
}
|
||||
let expectedAdministrators = expectedMembers
|
||||
guard groupMembership.administrators == expectedAdministrators else {
|
||||
throw OWSAssertionError("Unexpected administrators: \(groupMembership.administrators).")
|
||||
guard groupMembership.nonPendingAdministrators == expectedAdministrators else {
|
||||
throw OWSAssertionError("Unexpected administrators: \(groupMembership.nonPendingAdministrators).")
|
||||
}
|
||||
guard groupV2Snapshot.accessControlForMembers == .member else {
|
||||
throw OWSAssertionError("Unexpected accessControlForMembers: \(groupV2Snapshot.accessControlForMembers).")
|
||||
@ -120,7 +120,7 @@ public class GroupsV2Test: NSObject {
|
||||
}.then(on: .global()) { (groupId: Data) throws -> Promise<TSGroupThread> in
|
||||
let (groupThread, dmConfiguration) = try self.fetchGroupThread(groupId: groupId)
|
||||
let oldGroupModel = groupThread.groupModel as! TSGroupModelV2
|
||||
guard oldGroupModel.groupMembership.administrators == localAddressSet else {
|
||||
guard oldGroupModel.groupMembership.nonPendingAdministrators == localAddressSet else {
|
||||
throw OWSAssertionError("Unexpected groupMembership.")
|
||||
}
|
||||
guard oldGroupModel.groupMembership.nonAdminMembers.isEmpty else {
|
||||
@ -196,8 +196,8 @@ public class GroupsV2Test: NSObject {
|
||||
throw OWSAssertionError("Unexpected members: \(groupMembership.nonPendingMembers).")
|
||||
}
|
||||
let expectedAdministrators = localAddressSet
|
||||
guard groupMembership.administrators == expectedAdministrators else {
|
||||
throw OWSAssertionError("Unexpected administrators: \(groupMembership.administrators).")
|
||||
guard groupMembership.nonPendingAdministrators == expectedAdministrators else {
|
||||
throw OWSAssertionError("Unexpected administrators: \(groupMembership.nonPendingAdministrators).")
|
||||
}
|
||||
guard groupV2Snapshot.accessControlForMembers == .member else {
|
||||
throw OWSAssertionError("Unexpected accessControlForMembers: \(groupV2Snapshot.accessControlForMembers).")
|
||||
@ -211,7 +211,7 @@ public class GroupsV2Test: NSObject {
|
||||
guard let oldGroupModel = groupThread.groupModel as? TSGroupModelV2 else {
|
||||
throw OWSAssertionError("Invalid group model.")
|
||||
}
|
||||
guard oldGroupModel.groupMembership.administrators == localAddressSet else {
|
||||
guard oldGroupModel.groupMembership.nonPendingAdministrators == localAddressSet else {
|
||||
throw OWSAssertionError("Unexpected groupMembership.")
|
||||
}
|
||||
guard oldGroupModel.groupMembership.nonAdminMembers == otherAddresses else {
|
||||
@ -285,8 +285,8 @@ public class GroupsV2Test: NSObject {
|
||||
throw OWSAssertionError("Unexpected members: \(groupMembership.nonPendingMembers).")
|
||||
}
|
||||
let expectedAdministrators = localAddressSet
|
||||
guard groupMembership.administrators == expectedAdministrators else {
|
||||
throw OWSAssertionError("Unexpected administrators: \(groupMembership.administrators).")
|
||||
guard groupMembership.nonPendingAdministrators == expectedAdministrators else {
|
||||
throw OWSAssertionError("Unexpected administrators: \(groupMembership.nonPendingAdministrators).")
|
||||
}
|
||||
guard groupV2Snapshot.accessControlForMembers == .member else {
|
||||
throw OWSAssertionError("Unexpected accessControlForMembers: \(groupV2Snapshot.accessControlForMembers).")
|
||||
@ -298,7 +298,7 @@ public class GroupsV2Test: NSObject {
|
||||
}.map(on: .global()) { (groupId: Data) throws -> Data in
|
||||
let (groupThread, dmConfiguration) = try self.fetchGroupThread(groupId: groupId)
|
||||
let groupModel = groupThread.groupModel as! TSGroupModelV2
|
||||
guard groupModel.groupMembership.administrators == localAddressSet else {
|
||||
guard groupModel.groupMembership.nonPendingAdministrators == localAddressSet else {
|
||||
throw OWSAssertionError("Unexpected groupMembership.")
|
||||
}
|
||||
guard groupModel.groupMembership.nonAdminMembers.isEmpty else {
|
||||
|
||||
@ -86,7 +86,7 @@ public class GroupMembership: MTLModel {
|
||||
return Set(memberStateMap.filter { !$0.value.isAdministrator && !$0.value.isPending }.keys)
|
||||
}
|
||||
@objc
|
||||
public var administrators: Set<SignalServiceAddress> {
|
||||
public var nonPendingAdministrators: Set<SignalServiceAddress> {
|
||||
return Set(memberStateMap.filter { $0.value.isAdministrator && !$0.value.isPending }.keys)
|
||||
}
|
||||
@objc
|
||||
|
||||
@ -17,6 +17,7 @@ public enum GroupsV2Error: Error {
|
||||
case timeout
|
||||
case localUserNotInGroup
|
||||
case conflictingChange
|
||||
case lastAdminCantLeaveGroup
|
||||
}
|
||||
|
||||
@objc
|
||||
|
||||
Loading…
Reference in New Issue
Block a user