Signal-iOS/Signal/src/ViewControllers/ThreadSettings/AddGroupMembersViewController.swift
2022-07-12 10:23:12 -07:00

253 lines
9.0 KiB
Swift

//
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
protocol AddGroupMembersViewControllerDelegate: AnyObject {
func addGroupMembersViewDidUpdate()
}
// MARK: -
@objc
public class AddGroupMembersViewController: BaseGroupMemberViewController {
weak var addGroupMembersViewControllerDelegate: AddGroupMembersViewControllerDelegate?
private let groupThread: TSGroupThread
private let oldGroupModel: TSGroupModel
private var newRecipientSet = OrderedSet<PickedRecipient>()
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
let messageFormat = NSLocalizedString("ADD_GROUP_MEMBERS_VIEW_CONFIRM_ALERT_MESSAGE_%d", tableName: "PluralAware",
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.localizedStringWithFormat(messageFormat, newRecipientSet.count, groupName)
alertTitle = String.localizedStringWithFormat(NSLocalizedString("ADD_GROUP_MEMBERS_VIEW_CONFIRM_ALERT_TITLE_%d", tableName: "PluralAware",
comment: "Title for the 'add group members' confirmation alert."), newRecipientSet.count)
actionTitle = String.localizedStringWithFormat(NSLocalizedString("ADD_GROUP_MEMBERS_ACTION_TITLE_%d", tableName: "PluralAware",
comment: "Label for the 'add group members' button."), newRecipientSet.count)
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 buildGroupUpdate() -> GroupManager.GroupUpdate? {
let newUuids = newRecipientSet.orderedMembers
.compactMap { recipient -> UUID? in
if let uuid = recipient.address?.uuid,
!oldGroupModel.groupMembership.isFullMember(uuid) {
owsFailDebug("Missing UUID, or recipient is already in group!")
return uuid
}
return nil
}
guard !newUuids.isEmpty else {
owsFailDebug("No valid recipients")
return nil
}
return .addNormalMembers(uuids: newUuids)
}
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()
}
guard let groupUpdateToPerform = buildGroupUpdate() else {
let error = OWSAssertionError("Couldn't build group update.")
GroupViewUtils.showUpdateErrorUI(error: error)
return
}
GroupViewUtils.updateGroupWithActivityIndicator(fromViewController: self,
updatePromiseBlock: {
self.updateGroupThreadPromise(update: groupUpdateToPerform)
},
completion: { _ in
dismissAndUpdateDelegate()
})
}
func updateGroupThreadPromise(update: GroupManager.GroupUpdate) -> Promise<Void> {
firstly { () -> Promise<Void> in
return GroupManager.messageProcessingPromise(for: oldGroupModel,
description: self.logTag)
}.then(on: .global()) { _ in
GroupManager.updateExistingGroup(existingGroupModel: self.oldGroupModel,
update: update)
}.asVoid()
}
}
// MARK: -
extension AddGroupMembersViewController: GroupMemberViewDelegate {
var groupMemberViewRecipientSet: OrderedSet<PickedRecipient> {
newRecipientSet
}
var groupMemberViewHasUnsavedChanges: Bool {
!newRecipientSet.isEmpty
}
var shouldTryToEnableGroupsV2ForMembers: Bool {
groupThread.isGroupV2Thread
}
func groupMemberViewRemoveRecipient(_ recipient: PickedRecipient) {
newRecipientSet.remove(recipient)
updateNavbar()
}
func groupMemberViewAddRecipient(_ recipient: PickedRecipient) {
newRecipientSet.append(recipient)
updateNavbar()
}
func groupMemberViewCanAddRecipient(_ recipient: PickedRecipient) -> Bool {
guard groupThread.isGroupV2Thread else {
return true
}
guard let address = recipient.address else {
owsFailDebug("Invalid recipient.")
return false
}
return GroupManager.doesUserSupportGroupsV2(address: address)
}
func groupMemberViewShouldShowMemberCount() -> Bool {
groupThread.isGroupV2Thread
}
func groupMemberViewGroupMemberCountForDisplay() -> Int {
(oldGroupModel.groupMembership.allMembersOfAnyKind.count +
newRecipientSet.count)
}
func groupMemberViewIsGroupFull_HardLimit() -> Bool {
groupMemberViewGroupMemberCountForDisplay() >= GroupManager.groupsV2MaxGroupSizeHardLimit
}
func groupMemberViewIsGroupFull_RecommendedLimit() -> Bool {
groupMemberViewGroupMemberCountForDisplay() >= GroupManager.groupsV2MaxGroupSizeRecommended
}
func groupMemberViewIsPreExistingMember(_ recipient: PickedRecipient,
transaction: SDSAnyReadTransaction) -> Bool {
guard let address = recipient.address else {
owsFailDebug("Invalid recipient.")
return false
}
let groupMembership = oldGroupModel.groupMembership
if groupMembership.isFullMember(address) {
return true
}
if groupMembership.isInvitedMember(address) ||
groupMembership.isRequestingMember(address) {
// We can "add" pending or requesting members if they support gv2
// and we know their profile key credential.
let canAddMember: Bool = {
guard GroupManager.doesUserSupportGroupsV2(address: address) else {
return false
}
return self.groupsV2.hasProfileKeyCredential(for: address, transaction: transaction)
}()
return !canAddMember
}
return false
}
func groupMemberViewIsGroupsV2Required() -> Bool {
groupThread.isGroupV2Thread
}
func groupMemberViewDismiss() {
navigationController?.popViewController(animated: true)
}
var isNewGroup: Bool {
false
}
var groupThreadForGroupMemberView: TSGroupThread? {
groupThread
}
}