Path: blob/main/Signal/src/ViewControllers/ThreadSettings/GroupViewHelper+MemberActionSheet.swift
1 views
//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import LibSignalClient
import SignalServiceKit
import SignalUI
extension GroupViewHelper {
// This group of member actions follow a similar pattern:
//
// * Show a confirmation alert.
// * Present a modal activity indicator.
// * Perform the action using a promise.
// * Reload the group model and update the table content.
@MainActor
private func showMemberActionConfirmationActionSheet<T: ServiceId>(
address: SignalServiceAddress,
titleFormat: String,
message: String?,
actionTitle: String,
updateDescription: String,
updateBlock: @escaping (TSGroupModelV2, T) async throws -> Void,
) {
guard
let fromViewController,
let oldGroupModel = delegate?.currentGroupModel as? TSGroupModelV2,
oldGroupModel.groupMembership.isMemberOfAnyKind(address),
let serviceId = address.serviceId as? T
else {
GroupViewUtils.showUpdateErrorUI(error: OWSAssertionError("Invalid parameters for update: \(updateDescription)"))
return
}
let actionBlock = { @MainActor in
GroupViewUtils.updateGroupWithActivityIndicator(
fromViewController: fromViewController,
updateBlock: { try await updateBlock(oldGroupModel, serviceId) },
completion: { [weak self] in
self?.delegate?.groupViewHelperDidUpdateGroup()
},
)
}
let title = String.nonPluralLocalizedStringWithFormat(titleFormat, SSKEnvironment.shared.databaseStorageRef.read { tx in
return SSKEnvironment.shared.contactManagerRef.displayName(for: address, tx: tx).resolvedValue()
})
let actionSheet = ActionSheetController(title: title, message: message)
actionSheet.addAction(ActionSheetAction(title: actionTitle, style: .default, handler: { _ in actionBlock() }))
actionSheet.addAction(OWSActionSheets.cancelAction)
fromViewController.presentActionSheet(actionSheet)
}
// MARK: - Make Group Admin
func memberActionSheetCanMakeGroupAdmin(address: SignalServiceAddress) -> Bool {
guard
let groupThread = thread as? TSGroupThread,
groupThread.isGroupV2Thread
else {
return false
}
guard let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction?.aciAddress else {
owsFailDebug("Missing localAddress.")
return false
}
let isLocalUserAdmin = groupThread.groupModel.groupMembership.isFullMemberAndAdministrator(localAddress)
let groupMembership = groupThread.groupModel.groupMembership
let canBecomeAdmin = (
groupMembership.isFullMember(address) &&
!groupMembership.isFullMemberAndAdministrator(address),
)
let isGroupEnded = (groupThread.groupModel as? TSGroupModelV2)?.isTerminated ?? false
return canEditConversationMembership && isLocalUserAdmin && canBecomeAdmin && !isGroupEnded
}
@MainActor
func memberActionSheetMakeGroupAdminWasSelected(address: SignalServiceAddress) {
let titleFormat = OWSLocalizedString(
"CONVERSATION_SETTINGS_MAKE_GROUP_ADMIN_TITLE_FORMAT",
comment: "Format for title for 'make group admin' confirmation alert. Embeds {user to make an admin}.",
)
let actionTitle = OWSLocalizedString(
"CONVERSATION_SETTINGS_MAKE_GROUP_ADMIN_BUTTON",
comment: "Label for 'make group admin' button in conversation settings view.",
)
showMemberActionConfirmationActionSheet(
address: address,
titleFormat: titleFormat,
message: nil,
actionTitle: actionTitle,
updateDescription: "Make group admin",
updateBlock: { (oldGroupModel, aci: Aci) in
try await GroupManager.changeMemberRoleV2(groupModel: oldGroupModel, aci: aci, role: .administrator)
},
)
}
// MARK: - Revoke Group Admin
func memberActionSheetCanRevokeGroupAdmin(address: SignalServiceAddress) -> Bool {
guard
let groupThread = thread as? TSGroupThread,
groupThread.isGroupV2Thread
else {
return false
}
guard let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction?.aciAddress else {
owsFailDebug("Missing localAddress.")
return false
}
let groupMembership = groupThread.groupModel.groupMembership
let isLocalUserAdmin = groupMembership.isFullMemberAndAdministrator(localAddress)
let canRevokeAdmin = groupMembership.isFullMemberAndAdministrator(address)
return canEditConversationMembership && isLocalUserAdmin && canRevokeAdmin && !groupThread.isTerminatedGroup
}
@MainActor
func memberActionSheetRevokeGroupAdminWasSelected(address: SignalServiceAddress, hasMemberLabel: Bool) {
let titleFormat = OWSLocalizedString(
"CONVERSATION_SETTINGS_REVOKE_GROUP_ADMIN_TITLE_FORMAT",
comment: "Format for title for 'revoke group admin' confirmation alert. Embeds {user to revoke admin status from}.",
)
let actionTitle = OWSLocalizedString(
"CONVERSATION_SETTINGS_REVOKE_GROUP_ADMIN_BUTTON",
comment: "Label for 'revoke group admin' button in conversation settings view.",
)
var message: String?
if
BuildFlags.MemberLabel.send,
hasMemberLabel,
let groupModel = (thread as? TSGroupThread)?.groupModel as? TSGroupModelV2,
groupModel.access.memberLabels == .administrator
{
message = OWSLocalizedString(
"CONVERSATION_SETTINGS_REVOKE_GROUP_ADMIN_MESSAGE",
comment: "Message for 'revoke group admin' confirmation alert.",
)
}
showMemberActionConfirmationActionSheet(
address: address,
titleFormat: titleFormat,
message: message,
actionTitle: actionTitle,
updateDescription: "Revoke group admin",
updateBlock: { (oldGroupModel, aci: Aci) in
try await GroupManager.changeMemberRoleV2(groupModel: oldGroupModel, aci: aci, role: .normal)
},
)
}
// MARK: - Remove From Group
// This action can be used to remove members _or_ revoke invites.
func canRemoveFromGroup(address: SignalServiceAddress) -> Bool {
guard
let groupThread = thread as? TSGroupThread,
groupThread.isGroupV2Thread
else {
return false
}
guard let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction?.aciAddress else {
owsFailDebug("Missing localAddress.")
return false
}
// Only admins can kick out other members.
let groupMembership = groupThread.groupModel.groupMembership
let isLocalUserAdmin = groupMembership.isFullMemberAndAdministrator(localAddress)
let isAddressInGroup = groupMembership.isMemberOfAnyKind(address)
let isRemovalTargetLocalAdress = address.isLocalAddress
let isGroupEnded = (groupThread.groupModel as? TSGroupModelV2)?.isTerminated ?? false
return canEditConversationMembership && isLocalUserAdmin && isAddressInGroup && !isRemovalTargetLocalAdress && !isGroupEnded
}
@MainActor
func presentRemoveFromGroupActionSheet(address: SignalServiceAddress) {
let titleFormat = OWSLocalizedString(
"CONVERSATION_SETTINGS_REMOVE_FROM_GROUP_TITLE_FORMAT",
comment: "Format for title for 'remove from group' confirmation alert. Embeds {user to remove from the group}.",
)
let actionTitle = OWSLocalizedString(
"CONVERSATION_SETTINGS_REMOVE_FROM_GROUP_BUTTON",
comment: "Label for 'remove from group' button in conversation settings view.",
)
showMemberActionConfirmationActionSheet(
address: address,
titleFormat: titleFormat,
message: nil,
actionTitle: actionTitle,
updateDescription: "Remove user from group",
updateBlock: { (oldGroupModel, serviceId: ServiceId) in
try await GroupManager.removeFromGroupOrRevokeInviteV2(groupModel: oldGroupModel, serviceIds: [serviceId])
},
)
}
}