Creates a special data source enum for ConversationAvatarView data providers. Before, ContactCell and ConversationAvatarView used the data source, neither applying to each perfectly
617 lines
31 KiB
Swift
617 lines
31 KiB
Swift
//
|
|
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
protocol GroupMemberRequestsAndInvitesViewControllerDelegate: AnyObject {
|
|
func requestsAndInvitesViewDidUpdate()
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
@objc
|
|
public class GroupMemberRequestsAndInvitesViewController: OWSTableViewController2 {
|
|
|
|
weak var groupMemberRequestsAndInvitesViewControllerDelegate: GroupMemberRequestsAndInvitesViewControllerDelegate?
|
|
|
|
private let oldGroupThread: TSGroupThread
|
|
|
|
private var groupModel: TSGroupModel
|
|
|
|
private let groupViewHelper: GroupViewHelper
|
|
|
|
private enum Mode: Int, CaseIterable {
|
|
case memberRequests = 0
|
|
case pendingInvites = 1
|
|
}
|
|
|
|
private let segmentedControl = UISegmentedControl()
|
|
|
|
required init(groupThread: TSGroupThread, groupViewHelper: GroupViewHelper) {
|
|
self.oldGroupThread = groupThread
|
|
self.groupModel = groupThread.groupModel
|
|
self.groupViewHelper = groupViewHelper
|
|
|
|
super.init()
|
|
}
|
|
|
|
// MARK: - View Lifecycle
|
|
|
|
@objc
|
|
public override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
if RemoteConfig.groupsV2InviteLinks {
|
|
title = NSLocalizedString("GROUP_REQUESTS_AND_INVITES_VIEW_TITLE",
|
|
comment: "The title for the 'group requests and invites' view.")
|
|
} else {
|
|
title = NSLocalizedString("GROUP_INVITES_VIEW_TITLE",
|
|
comment: "The title for the 'group invites' view.")
|
|
}
|
|
|
|
defaultSeparatorInsetLeading = Self.cellHInnerMargin + CGFloat(AvatarBuilder.smallAvatarSizePoints) + ContactCellView.avatarTextHSpacing
|
|
|
|
configureSegmentedControl()
|
|
|
|
tableView.register(ContactTableViewCell.self, forCellReuseIdentifier: ContactTableViewCell.reuseIdentifier)
|
|
|
|
updateTableContents()
|
|
}
|
|
|
|
private func configureSegmentedControl() {
|
|
for mode in Mode.allCases {
|
|
assert(mode.rawValue == segmentedControl.numberOfSegments)
|
|
|
|
var title: String
|
|
switch mode {
|
|
case .memberRequests:
|
|
title = NSLocalizedString("GROUP_REQUESTS_AND_INVITES_VIEW_MEMBER_REQUESTS_MODE",
|
|
comment: "Label for the 'member requests' mode of the 'group requests and invites' view.")
|
|
if groupModel.groupMembership.requestingMembers.count > 0 {
|
|
title.append(" (\(OWSFormat.formatInt(groupModel.groupMembership.requestingMembers.count)))")
|
|
}
|
|
case .pendingInvites:
|
|
title = NSLocalizedString("GROUP_REQUESTS_AND_INVITES_VIEW_PENDING_INVITES_MODE",
|
|
comment: "Label for the 'pending invites' mode of the 'group requests and invites' view.")
|
|
if groupModel.groupMembership.invitedMembers.count > 0 {
|
|
title.append(" (\(OWSFormat.formatInt(groupModel.groupMembership.invitedMembers.count)))")
|
|
}
|
|
}
|
|
|
|
segmentedControl.insertSegment(withTitle: title, at: mode.rawValue, animated: false)
|
|
}
|
|
segmentedControl.selectedSegmentIndex = 0
|
|
segmentedControl.addTarget(self, action: #selector(segmentedControlDidChange), for: .valueChanged)
|
|
}
|
|
|
|
@objc
|
|
func segmentedControlDidChange(_ sender: UISwitch) {
|
|
updateTableContents()
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
private func updateTableContents() {
|
|
let contents = OWSTableContents()
|
|
|
|
var mode = Mode.pendingInvites
|
|
if RemoteConfig.groupsV2InviteLinks {
|
|
let modeSection = OWSTableSection()
|
|
let modeHeader = UIStackView(arrangedSubviews: [segmentedControl])
|
|
modeHeader.axis = .vertical
|
|
modeHeader.alignment = .fill
|
|
modeHeader.layoutMargins = UIEdgeInsets(top: 20, leading: 20, bottom: 0, trailing: 20)
|
|
modeHeader.isLayoutMarginsRelativeArrangement = true
|
|
modeSection.customHeaderView = modeHeader
|
|
contents.addSection(modeSection)
|
|
|
|
guard let parsedMode = Mode(rawValue: segmentedControl.selectedSegmentIndex) else {
|
|
owsFailDebug("Invalid mode.")
|
|
return
|
|
}
|
|
mode = parsedMode
|
|
}
|
|
|
|
switch mode {
|
|
case .memberRequests:
|
|
addContentsForMemberRequests(contents: contents)
|
|
case .pendingInvites:
|
|
addContentsForPendingInvites(contents: contents)
|
|
}
|
|
|
|
self.contents = contents
|
|
}
|
|
|
|
private func addContentsForMemberRequests(contents: OWSTableContents) {
|
|
|
|
let canApproveMemberRequests = groupViewHelper.canApproveMemberRequests
|
|
|
|
let groupMembership = groupModel.groupMembership
|
|
let requestingMembersSorted = databaseStorage.read { transaction in
|
|
self.contactsManagerImpl.sortSignalServiceAddresses(Array(groupMembership.requestingMembers),
|
|
transaction: transaction)
|
|
}
|
|
|
|
let section = OWSTableSection()
|
|
let footerFormat = NSLocalizedString("PENDING_GROUP_MEMBERS_SECTION_FOOTER_PENDING_MEMBER_REQUESTS_FORMAT",
|
|
comment: "Footer for the 'pending member requests' section of the 'member requests and invites' view. Embeds {{ the name of the group }}.")
|
|
let groupName = self.contactsManager.displayNameWithSneakyTransaction(thread: oldGroupThread)
|
|
section.footerTitle = String(format: footerFormat, groupName)
|
|
|
|
if !requestingMembersSorted.isEmpty {
|
|
for address in requestingMembersSorted {
|
|
section.add(OWSTableItem(customCellBlock: { [weak self] in
|
|
guard let self = self else {
|
|
owsFailDebug("Missing self")
|
|
return OWSTableItem.newCell()
|
|
}
|
|
|
|
let cell = ContactTableViewCell(style: .default, reuseIdentifier: nil)
|
|
|
|
Self.databaseStorage.read { transaction in
|
|
let configuration = ContactCellConfiguration(address: address, localUserDisplayMode: .asLocalUser)
|
|
configuration.allowUserInteraction = true
|
|
|
|
if canApproveMemberRequests {
|
|
configuration.accessoryView = self.buildMemberRequestButtons(address: address)
|
|
}
|
|
|
|
if address.isLocalAddress {
|
|
cell.selectionStyle = .none
|
|
} else {
|
|
cell.selectionStyle = .default
|
|
}
|
|
|
|
cell.configure(configuration: configuration, transaction: transaction)
|
|
}
|
|
return cell
|
|
}) { [weak self] in
|
|
self?.showMemberActionSheet(for: address)
|
|
})
|
|
}
|
|
} else {
|
|
section.add(OWSTableItem.softCenterLabel(withText: NSLocalizedString("PENDING_GROUP_MEMBERS_NO_PENDING_MEMBER_REQUESTS",
|
|
comment: "Label indicating that a group has no pending member requests."),
|
|
customRowHeight: UITableView.automaticDimension))
|
|
}
|
|
contents.addSection(section)
|
|
}
|
|
|
|
private func buildMemberRequestButtons(address: SignalServiceAddress) -> ContactCellAccessoryView {
|
|
let buttonHeight: CGFloat = 28
|
|
|
|
let denyButton = OWSButton()
|
|
denyButton.layer.cornerRadius = buttonHeight / 2
|
|
denyButton.clipsToBounds = true
|
|
denyButton.setBackgroundImage(UIImage(color: Theme.secondaryBackgroundColor), for: .normal)
|
|
denyButton.setTemplateImageName("x-20", tintColor: Theme.primaryIconColor)
|
|
denyButton.accessibilityIdentifier = "member-request-deny"
|
|
denyButton.block = { [weak self] in
|
|
self?.denyMemberRequest(address: address)
|
|
}
|
|
|
|
let approveButton = OWSButton()
|
|
approveButton.layer.cornerRadius = buttonHeight / 2
|
|
approveButton.clipsToBounds = true
|
|
approveButton.setBackgroundImage(UIImage(color: Theme.secondaryBackgroundColor), for: .normal)
|
|
approveButton.setTemplateImageName("check-20", tintColor: Theme.primaryIconColor)
|
|
approveButton.accessibilityIdentifier = "member-request-approveButton"
|
|
approveButton.block = { [weak self] in
|
|
self?.approveMemberRequest(address: address)
|
|
}
|
|
|
|
let denyWrapper = ManualLayoutView.wrapSubviewUsingIOSAutoLayout(denyButton)
|
|
let approveWrapper = ManualLayoutView.wrapSubviewUsingIOSAutoLayout(approveButton)
|
|
|
|
let denyButtonSize = CGSize.square(buttonHeight)
|
|
let approveButtonSize = CGSize.square(buttonHeight)
|
|
|
|
let stackView = ManualStackView(name: "stackView")
|
|
let stackConfig = CVStackViewConfig(axis: .horizontal,
|
|
alignment: .center,
|
|
spacing: 16,
|
|
layoutMargins: .zero)
|
|
let stackMeasurement = stackView.configure(config: stackConfig,
|
|
subviews: [denyWrapper, approveWrapper],
|
|
subviewInfos: [
|
|
denyButtonSize.asManualSubviewInfo,
|
|
approveButtonSize.asManualSubviewInfo
|
|
])
|
|
let stackSize = stackMeasurement.measuredSize
|
|
return ContactCellAccessoryView(accessoryView: stackView, size: stackSize)
|
|
}
|
|
|
|
private func approveMemberRequest(address: SignalServiceAddress) {
|
|
showAcceptMemberRequestUI(address: address)
|
|
}
|
|
|
|
private func denyMemberRequest(address: SignalServiceAddress) {
|
|
showDenyMemberRequestUI(address: address)
|
|
}
|
|
|
|
private func addContentsForPendingInvites(contents: OWSTableContents) {
|
|
guard let localAddress = tsAccountManager.localAddress else {
|
|
owsFailDebug("missing local address")
|
|
return
|
|
}
|
|
|
|
let groupMembership = groupModel.groupMembership
|
|
let allPendingMembersSorted = databaseStorage.read { transaction in
|
|
self.contactsManagerImpl.sortSignalServiceAddresses(Array(groupMembership.invitedMembers),
|
|
transaction: transaction)
|
|
}
|
|
|
|
// Note that these collections retain their sorting from above.
|
|
var membersInvitedByLocalUser = [SignalServiceAddress]()
|
|
var membersInvitedByOtherUsers = [SignalServiceAddress: [SignalServiceAddress]]()
|
|
for invitedAddress in allPendingMembersSorted {
|
|
guard let inviterUuid = groupMembership.addedByUuid(forInvitedMember: invitedAddress) else {
|
|
owsFailDebug("Missing inviter.")
|
|
continue
|
|
}
|
|
let inviterAddress = SignalServiceAddress(uuid: inviterUuid)
|
|
if inviterAddress == localAddress {
|
|
membersInvitedByLocalUser.append(invitedAddress)
|
|
} else {
|
|
var invitedMembers: [SignalServiceAddress] = membersInvitedByOtherUsers[inviterAddress] ?? []
|
|
invitedMembers.append(invitedAddress)
|
|
membersInvitedByOtherUsers[inviterAddress] = invitedMembers
|
|
}
|
|
}
|
|
|
|
// Only admins can revoke invites.
|
|
let canRevokeInvites = groupViewHelper.canRevokePendingInvites
|
|
|
|
// MARK: - People You Invited
|
|
|
|
let localSection = OWSTableSection()
|
|
localSection.headerTitle = NSLocalizedString("PENDING_GROUP_MEMBERS_SECTION_TITLE_PEOPLE_YOU_INVITED",
|
|
comment: "Title for the 'people you invited' section of the 'member requests and invites' view.")
|
|
if membersInvitedByLocalUser.count > 0 {
|
|
for address in membersInvitedByLocalUser {
|
|
localSection.add(OWSTableItem(dequeueCellBlock: { tableView in
|
|
guard let cell = tableView.dequeueReusableCell(withIdentifier: ContactTableViewCell.reuseIdentifier) as? ContactTableViewCell else {
|
|
owsFailDebug("Missing cell.")
|
|
return UITableViewCell()
|
|
}
|
|
|
|
cell.selectionStyle = canRevokeInvites ? .default : .none
|
|
cell.configureWithSneakyTransaction(address: address,
|
|
localUserDisplayMode: .asUser)
|
|
return cell
|
|
}) { [weak self] in
|
|
self?.inviteFromLocalUserWasTapped(address,
|
|
canRevoke: canRevokeInvites)
|
|
})
|
|
}
|
|
} else {
|
|
localSection.add(OWSTableItem.softCenterLabel(withText: NSLocalizedString("PENDING_GROUP_MEMBERS_NO_PENDING_MEMBERS",
|
|
comment: "Label indicating that a group has no pending members."),
|
|
customRowHeight: UITableView.automaticDimension))
|
|
}
|
|
contents.addSection(localSection)
|
|
|
|
// MARK: - Other Users
|
|
|
|
let otherUsersSection = OWSTableSection()
|
|
otherUsersSection.headerTitle = NSLocalizedString("PENDING_GROUP_MEMBERS_SECTION_TITLE_INVITES_FROM_OTHER_MEMBERS",
|
|
comment: "Title for the 'invites by other group members' section of the 'member requests and invites' view.")
|
|
otherUsersSection.footerTitle = NSLocalizedString("PENDING_GROUP_MEMBERS_SECTION_FOOTER_INVITES_FROM_OTHER_MEMBERS",
|
|
comment: "Footer for the 'invites by other group members' section of the 'member requests and invites' view.")
|
|
|
|
if membersInvitedByOtherUsers.count > 0 {
|
|
let inviterAddresses = databaseStorage.read { transaction in
|
|
self.contactsManagerImpl.sortSignalServiceAddresses(Array(membersInvitedByOtherUsers.keys),
|
|
transaction: transaction)
|
|
}
|
|
for inviterAddress in inviterAddresses {
|
|
guard let invitedAddresses = membersInvitedByOtherUsers[inviterAddress] else {
|
|
owsFailDebug("Missing invited addresses.")
|
|
continue
|
|
}
|
|
|
|
otherUsersSection.add(OWSTableItem(dequeueCellBlock: { tableView in
|
|
guard let cell = tableView.dequeueReusableCell(withIdentifier: ContactTableViewCell.reuseIdentifier) as? ContactTableViewCell else {
|
|
owsFailDebug("Missing cell.")
|
|
return UITableViewCell()
|
|
}
|
|
|
|
cell.selectionStyle = canRevokeInvites ? .default : .none
|
|
|
|
Self.databaseStorage.read { transaction in
|
|
let configuration = ContactCellConfiguration(address: inviterAddress, localUserDisplayMode: .asUser)
|
|
let inviterName = Self.contactsManager.displayName(for: inviterAddress,
|
|
transaction: transaction)
|
|
if invitedAddresses.count > 1 {
|
|
let format = NSLocalizedString("PENDING_GROUP_MEMBERS_MEMBER_INVITED_N_USERS_FORMAT",
|
|
comment: "Format for label indicating the a group member has invited N other users to the group. Embeds {{ %1$@ name of the inviting group member, %2$@ the number of users they have invited. }}.")
|
|
configuration.customName = String(format: format,
|
|
inviterName,
|
|
OWSFormat.formatInt(invitedAddresses.count))
|
|
} else {
|
|
let format = NSLocalizedString("PENDING_GROUP_MEMBERS_MEMBER_INVITED_1_USER_FORMAT",
|
|
comment: "Format for label indicating the a group member has invited 1 other user to the group. Embeds {{ the name of the inviting group member. }}.")
|
|
configuration.customName = String(format: format, inviterName)
|
|
}
|
|
cell.configure(configuration: configuration, transaction: transaction)
|
|
}
|
|
|
|
return cell
|
|
}) { [weak self] in
|
|
self?.invitesFromOtherUserWasTapped(invitedAddresses: invitedAddresses,
|
|
inviterAddress: inviterAddress,
|
|
canRevoke: canRevokeInvites)
|
|
})
|
|
}
|
|
} else {
|
|
otherUsersSection.add(OWSTableItem.softCenterLabel(withText: NSLocalizedString("PENDING_GROUP_MEMBERS_NO_PENDING_MEMBERS",
|
|
comment: "Label indicating that a group has no pending members."),
|
|
customRowHeight: UITableView.automaticDimension))
|
|
}
|
|
contents.addSection(otherUsersSection)
|
|
|
|
// MARK: - Invalid Invites
|
|
|
|
let invalidInvitesCount = groupMembership.invalidInvites.count
|
|
if canRevokeInvites, invalidInvitesCount > 0 {
|
|
let invalidInvitesSection = OWSTableSection()
|
|
invalidInvitesSection.headerTitle = NSLocalizedString("PENDING_GROUP_MEMBERS_SECTION_TITLE_INVALID_INVITES",
|
|
comment: "Title for the 'invalid invites' section of the 'member requests and invites' view.")
|
|
|
|
let cellTitle: String
|
|
if invalidInvitesCount > 1 {
|
|
let format = NSLocalizedString("PENDING_GROUP_MEMBERS_REVOKE_INVALID_INVITES_N_FORMAT",
|
|
comment: "Format for 'revoke invalid N invites' item. Embeds {{ the number of invalid invites. }}.")
|
|
cellTitle = String(format: format, OWSFormat.formatInt(invalidInvitesCount))
|
|
} else {
|
|
cellTitle = NSLocalizedString("PENDING_GROUP_MEMBERS_REVOKE_INVALID_INVITE_1",
|
|
comment: "Format for 'revoke invalid 1 invite' item.")
|
|
}
|
|
|
|
invalidInvitesSection.add(OWSTableItem.disclosureItem(withText: cellTitle) { [weak self] in
|
|
self?.revokeInvalidInvites()
|
|
})
|
|
contents.addSection(invalidInvitesSection)
|
|
}
|
|
}
|
|
|
|
fileprivate func reloadContent(groupThread: TSGroupThread?) {
|
|
groupMemberRequestsAndInvitesViewControllerDelegate?.requestsAndInvitesViewDidUpdate()
|
|
|
|
guard let newModel = { () -> TSGroupModel? in
|
|
if let groupThread = groupThread {
|
|
return groupThread.groupModel
|
|
}
|
|
return databaseStorage.read { (transaction) -> TSGroupModel? in
|
|
guard let groupThread = TSGroupThread.fetch(groupId: self.groupModel.groupId,
|
|
transaction: transaction) else {
|
|
owsFailDebug("Missing group thread.")
|
|
return nil
|
|
}
|
|
return groupThread.groupModel
|
|
}
|
|
}() else {
|
|
navigationController?.popViewController(animated: true)
|
|
return
|
|
}
|
|
|
|
groupModel = newModel
|
|
updateTableContents()
|
|
}
|
|
|
|
private func showRevokePendingInviteFromLocalUserConfirmation(invitedAddress: SignalServiceAddress) {
|
|
|
|
let invitedName = contactsManager.displayName(for: invitedAddress)
|
|
let format = NSLocalizedString("PENDING_GROUP_MEMBERS_REVOKE_LOCAL_INVITE_CONFIRMATION_TITLE_1_FORMAT",
|
|
comment: "Format for title of 'revoke invite' confirmation alert. Embeds {{ the name of the invited group member. }}.")
|
|
let alertTitle = String(format: format, invitedName)
|
|
let actionSheet = ActionSheetController(title: alertTitle)
|
|
actionSheet.addAction(ActionSheetAction(title: NSLocalizedString("PENDING_GROUP_MEMBERS_REVOKE_INVITE_1_BUTTON",
|
|
comment: "Title of 'revoke invite' button."),
|
|
style: .destructive) { _ in
|
|
self.revokePendingInvites(addresses: [invitedAddress])
|
|
})
|
|
actionSheet.addAction(OWSActionSheets.cancelAction)
|
|
presentActionSheet(actionSheet)
|
|
}
|
|
|
|
private func showRevokePendingInviteFromOtherUserConfirmation(invitedAddresses: [SignalServiceAddress],
|
|
inviterAddress: SignalServiceAddress) {
|
|
|
|
let isPlural = invitedAddresses.count != 1
|
|
let inviterName = contactsManager.displayName(for: inviterAddress)
|
|
let alertTitle: String
|
|
if isPlural {
|
|
let format = NSLocalizedString("PENDING_GROUP_MEMBERS_REVOKE_INVITE_CONFIRMATION_TITLE_N_FORMAT",
|
|
comment: "Format for title of 'revoke invite' confirmation alert. Embeds {{ %1$@ the number of users they have invited, %2$@ name of the inviting group member. }}.")
|
|
alertTitle = String(format: format, OWSFormat.formatInt(invitedAddresses.count), inviterName)
|
|
} else {
|
|
let format = NSLocalizedString("PENDING_GROUP_MEMBERS_REVOKE_INVITE_CONFIRMATION_TITLE_1_FORMAT",
|
|
comment: "Format for title of 'revoke invite' confirmation alert. Embeds {{ the name of the inviting group member. }}.")
|
|
alertTitle = String(format: format, inviterName)
|
|
}
|
|
let actionSheet = ActionSheetController(title: alertTitle)
|
|
let actionTitle = (isPlural
|
|
? NSLocalizedString("PENDING_GROUP_MEMBERS_REVOKE_INVITE_N_BUTTON",
|
|
comment: "Title of 'revoke invites' button.")
|
|
: NSLocalizedString("PENDING_GROUP_MEMBERS_REVOKE_INVITE_1_BUTTON",
|
|
comment: "Title of 'revoke invite' button."))
|
|
actionSheet.addAction(ActionSheetAction(title: actionTitle,
|
|
style: .destructive) { _ in
|
|
self.revokePendingInvites(addresses: invitedAddresses)
|
|
})
|
|
actionSheet.addAction(OWSActionSheets.cancelAction)
|
|
presentActionSheet(actionSheet)
|
|
}
|
|
|
|
private func inviteFromLocalUserWasTapped(_ address: SignalServiceAddress,
|
|
canRevoke: Bool) {
|
|
if canRevoke {
|
|
self.showRevokePendingInviteFromLocalUserConfirmation(invitedAddress: address)
|
|
}
|
|
}
|
|
|
|
private func invitesFromOtherUserWasTapped(invitedAddresses: [SignalServiceAddress],
|
|
inviterAddress: SignalServiceAddress,
|
|
canRevoke: Bool) {
|
|
if canRevoke {
|
|
self.showRevokePendingInviteFromOtherUserConfirmation(invitedAddresses: invitedAddresses,
|
|
inviterAddress: inviterAddress)
|
|
}
|
|
}
|
|
|
|
private func showMemberActionSheet(for address: SignalServiceAddress) {
|
|
let memberActionSheet = MemberActionSheet(address: address, groupViewHelper: groupViewHelper)
|
|
memberActionSheet.present(from: self)
|
|
}
|
|
|
|
private func presentRequestApprovedToast(address: SignalServiceAddress) {
|
|
let format = NSLocalizedString("PENDING_GROUP_MEMBERS_REQUEST_APPROVED_FORMAT",
|
|
comment: "Message indicating that a request to join the group was successfully approved. Embeds {{ the name of the approved user }}.")
|
|
let userName = contactsManager.displayName(for: address)
|
|
let text = String(format: format, userName)
|
|
presentToast(text: text)
|
|
}
|
|
|
|
private func presentRequestDeniedToast(address: SignalServiceAddress) {
|
|
let format = NSLocalizedString("PENDING_GROUP_MEMBERS_REQUEST_DENIED_FORMAT",
|
|
comment: "Message indicating that a request to join the group was successfully denied. Embeds {{ the name of the denied user }}.")
|
|
let userName = contactsManager.displayName(for: address)
|
|
let text = String(format: format, userName)
|
|
presentToast(text: text)
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
private extension GroupMemberRequestsAndInvitesViewController {
|
|
|
|
func revokePendingInvites(addresses: [SignalServiceAddress]) {
|
|
GroupViewUtils.updateGroupWithActivityIndicator(fromViewController: self,
|
|
updatePromiseBlock: {
|
|
self.revokePendingInvitesPromise(addresses: addresses)
|
|
},
|
|
completion: { [weak self] groupThread in
|
|
self?.reloadContent(groupThread: groupThread)
|
|
})
|
|
}
|
|
|
|
func revokePendingInvitesPromise(addresses: [SignalServiceAddress]) -> Promise<TSGroupThread> {
|
|
guard let groupModelV2 = groupModel as? TSGroupModelV2 else {
|
|
return Promise(error: OWSAssertionError("Invalid group model."))
|
|
}
|
|
let uuids = addresses.compactMap { $0.uuid }
|
|
guard !uuids.isEmpty else {
|
|
return Promise(error: OWSAssertionError("Invalid addresses."))
|
|
}
|
|
|
|
return firstly { () -> Promise<Void> in
|
|
return GroupManager.messageProcessingPromise(for: groupModel,
|
|
description: self.logTag)
|
|
}.then(on: .global()) { _ in
|
|
GroupManager.removeFromGroupOrRevokeInviteV2(groupModel: groupModelV2, uuids: uuids)
|
|
}
|
|
}
|
|
|
|
func revokeInvalidInvites() {
|
|
GroupViewUtils.updateGroupWithActivityIndicator(fromViewController: self,
|
|
updatePromiseBlock: {
|
|
self.revokeInvalidInvitesPromise()
|
|
},
|
|
completion: { [weak self] groupThread in
|
|
self?.reloadContent(groupThread: groupThread)
|
|
})
|
|
}
|
|
|
|
func revokeInvalidInvitesPromise() -> Promise<TSGroupThread> {
|
|
guard let groupModelV2 = groupModel as? TSGroupModelV2 else {
|
|
return Promise(error: OWSAssertionError("Invalid group model."))
|
|
}
|
|
|
|
return firstly { () -> Promise<Void> in
|
|
return GroupManager.messageProcessingPromise(for: groupModel,
|
|
description: self.logTag)
|
|
}.then(on: .global()) { _ in
|
|
GroupManager.revokeInvalidInvites(groupModel: groupModelV2)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
fileprivate extension GroupMemberRequestsAndInvitesViewController {
|
|
|
|
func showAcceptMemberRequestUI(address: SignalServiceAddress) {
|
|
|
|
let username = contactsManager.displayName(for: address)
|
|
let format = NSLocalizedString("PENDING_GROUP_MEMBERS_ACCEPT_REQUEST_CONFIRMATION_TITLE_FORMAT",
|
|
comment: "Title of 'accept member request to join group' confirmation alert. Embeds {{ the name of the requesting group member. }}.")
|
|
let alertTitle = String(format: format, username)
|
|
let actionSheet = ActionSheetController(title: alertTitle)
|
|
|
|
let actionTitle = NSLocalizedString("PENDING_GROUP_MEMBERS_ACCEPT_REQUEST_BUTTON",
|
|
comment: "Title of 'accept member request to join group' button.")
|
|
actionSheet.addAction(ActionSheetAction(title: actionTitle,
|
|
style: .destructive) { _ in
|
|
self.acceptOrDenyMemberRequests(address: address, shouldAccept: true)
|
|
})
|
|
|
|
actionSheet.addAction(OWSActionSheets.cancelAction)
|
|
presentActionSheet(actionSheet)
|
|
}
|
|
|
|
func showDenyMemberRequestUI(address: SignalServiceAddress) {
|
|
|
|
let username = contactsManager.displayName(for: address)
|
|
let format = NSLocalizedString("PENDING_GROUP_MEMBERS_DENY_REQUEST_CONFIRMATION_TITLE_FORMAT",
|
|
comment: "Title of 'deny member request to join group' confirmation alert. Embeds {{ the name of the requesting group member. }}.")
|
|
let alertTitle = String(format: format, username)
|
|
let actionSheet = ActionSheetController(title: alertTitle)
|
|
|
|
let actionTitle = NSLocalizedString("PENDING_GROUP_MEMBERS_DENY_REQUEST_BUTTON",
|
|
comment: "Title of 'deny member request to join group' button.")
|
|
actionSheet.addAction(ActionSheetAction(title: actionTitle,
|
|
style: .destructive) { _ in
|
|
self.acceptOrDenyMemberRequests(address: address, shouldAccept: false)
|
|
})
|
|
|
|
actionSheet.addAction(OWSActionSheets.cancelAction)
|
|
presentActionSheet(actionSheet)
|
|
}
|
|
|
|
func acceptOrDenyMemberRequests(address: SignalServiceAddress, shouldAccept: Bool) {
|
|
GroupViewUtils.updateGroupWithActivityIndicator(fromViewController: self,
|
|
updatePromiseBlock: {
|
|
self.acceptOrDenyMemberRequestsPromise(addresses: [address],
|
|
shouldAccept: shouldAccept)
|
|
},
|
|
completion: { [weak self] groupThread in
|
|
guard let self = self else { return }
|
|
if shouldAccept {
|
|
self.presentRequestApprovedToast(address: address)
|
|
} else {
|
|
self.presentRequestDeniedToast(address: address)
|
|
}
|
|
self.reloadContent(groupThread: groupThread)
|
|
})
|
|
}
|
|
|
|
func acceptOrDenyMemberRequestsPromise(addresses: [SignalServiceAddress], shouldAccept: Bool) -> Promise<TSGroupThread> {
|
|
guard let groupModelV2 = groupModel as? TSGroupModelV2 else {
|
|
return Promise(error: OWSAssertionError("Invalid group model."))
|
|
}
|
|
let uuids = addresses.compactMap { $0.uuid }
|
|
guard !uuids.isEmpty else {
|
|
return Promise(error: OWSAssertionError("Invalid addresses."))
|
|
}
|
|
|
|
return firstly { () -> Promise<Void> in
|
|
return GroupManager.messageProcessingPromise(for: groupModel,
|
|
description: self.logTag)
|
|
}.then(on: .global()) { _ in
|
|
GroupManager.acceptOrDenyMemberRequestsV2(groupModel: groupModelV2, uuids: uuids, shouldAccept: shouldAccept)
|
|
}
|
|
}
|
|
}
|