Signal-iOS/SignalUI/ViewControllers/Recipient Picker/RecipientPickerDelegate.swift
2022-09-29 09:35:00 -07:00

251 lines
11 KiB
Swift

//
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
public enum RecipientPickerRecipientState: Int {
case canBeSelected
case duplicateGroupMember
case userAlreadyInBlocklist
case conversationAlreadyInBlocklist
case unknownError
}
@objc
public protocol RecipientPickerDelegate: AnyObject {
func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
getRecipientState recipient: PickedRecipient) -> RecipientPickerRecipientState
func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
didSelectRecipient recipient: PickedRecipient)
func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
willRenderRecipient recipient: PickedRecipient)
// This delegate method is only used if showUseAsyncSelection is set.
func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
prepareToSelectRecipient recipient: PickedRecipient) -> AnyPromise
// This delegate method is only used if showUseAsyncSelection is set.
func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
showInvalidRecipientAlert recipient: PickedRecipient)
func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
accessoryMessageForRecipient recipient: PickedRecipient,
transaction: SDSAnyReadTransaction) -> String?
@objc
optional func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
accessoryViewForRecipient recipient: PickedRecipient,
transaction: SDSAnyReadTransaction) -> ContactCellAccessoryView?
@objc
optional func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
attributedSubtitleForRecipient recipient: PickedRecipient,
transaction: SDSAnyReadTransaction) -> NSAttributedString?
func recipientPickerTableViewWillBeginDragging(_ recipientPickerViewController: RecipientPickerViewController)
func recipientPickerNewGroupButtonWasPressed()
func recipientPickerCustomHeaderViews() -> [UIView]
}
@objc
public class PickedRecipient: NSObject {
public let identifier: Identifier
public enum Identifier: Hashable {
case address(_ address: SignalServiceAddress)
case group(_ groupThread: TSGroupThread)
}
private init(_ identifier: Identifier) {
self.identifier = identifier
}
@objc
public var isGroup: Bool {
guard case .group = identifier else { return false }
return true
}
@objc
public var address: SignalServiceAddress? {
guard case .address(let address) = identifier else { return nil }
return address
}
@objc
public static func `for`(groupThread: TSGroupThread) -> PickedRecipient {
return .init(.group(groupThread))
}
@objc
public static func `for`(address: SignalServiceAddress) -> PickedRecipient {
return .init(.address(address))
}
public override func isEqual(_ object: Any?) -> Bool {
guard let otherRecipient = object as? PickedRecipient else { return false }
return identifier == otherRecipient.identifier
}
public override var hash: Int {
return identifier.hashValue
}
}
@objc
extension RecipientPickerViewController {
func tryToSelectRecipient(_ recipient: PickedRecipient) {
if let address = recipient.address,
address.isLocalAddress,
shouldHideLocalRecipient {
return
}
guard let delegate = delegate else { return }
guard showUseAsyncSelection else {
AssertIsOnMainThread()
let recipientPickerRecipientState = delegate.recipientPicker(self, getRecipientState: recipient)
guard recipientPickerRecipientState == .canBeSelected else {
showErrorAlert(recipientPickerRecipientState: recipientPickerRecipientState)
return
}
delegate.recipientPicker(self, didSelectRecipient: recipient)
return
}
let fromViewController = self
ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] modalActivityIndicator in
guard let self = self else { return }
firstly {
delegate.recipientPicker(fromViewController,
prepareToSelectRecipient: recipient)
}.done { _ in
AssertIsOnMainThread()
modalActivityIndicator.dismiss {
AssertIsOnMainThread()
let recipientPickerRecipientState = delegate.recipientPicker(self, getRecipientState: recipient)
guard recipientPickerRecipientState == .canBeSelected else {
self.showErrorAlert(recipientPickerRecipientState: recipientPickerRecipientState)
return
}
delegate.recipientPicker(self, didSelectRecipient: recipient)
}
}.catch { error in
AssertIsOnMainThread()
owsFailDebug("Error: \(error)")
modalActivityIndicator.dismiss {
OWSActionSheets.showErrorAlert(message: error.userErrorDescription)
}
}
}
}
func showErrorAlert(recipientPickerRecipientState: RecipientPickerRecipientState) {
let errorMessage: String
switch recipientPickerRecipientState {
case .duplicateGroupMember:
errorMessage = OWSLocalizedString("GROUPS_ERROR_MEMBER_ALREADY_IN_GROUP",
comment: "Error message indicating that a member can't be added to a group because they are already in the group.")
case .userAlreadyInBlocklist:
errorMessage = OWSLocalizedString("BLOCK_LIST_ERROR_USER_ALREADY_IN_BLOCKLIST",
comment: "Error message indicating that a user can't be blocked because they are already blocked.")
case .conversationAlreadyInBlocklist:
errorMessage = OWSLocalizedString("BLOCK_LIST_ERROR_CONVERSATION_ALREADY_IN_BLOCKLIST",
comment: "Error message indicating that a conversation can't be blocked because they are already blocked.")
case .canBeSelected, .unknownError:
owsFailDebug("Unexpected value.")
errorMessage = OWSLocalizedString("RECIPIENT_PICKER_ERROR_USER_CANNOT_BE_SELECTED",
comment: "Error message indicating that a user can't be selected.")
}
OWSActionSheets.showErrorAlert(message: errorMessage)
return
}
func item(forRecipient recipient: PickedRecipient) -> OWSTableItem {
switch recipient.identifier {
case .address(let address):
return OWSTableItem(
customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let tableView = self.tableView
guard let cell = tableView.dequeueReusableCell(withIdentifier: ContactTableViewCell.reuseIdentifier) as? ContactTableViewCell else {
owsFailDebug("cell was unexpectedly nil")
return UITableViewCell()
}
if let delegate = self.delegate,
delegate.recipientPicker(self, getRecipientState: recipient) != .canBeSelected {
cell.selectionStyle = .none
}
Self.databaseStorage.read { transaction in
let configuration = ContactCellConfiguration(address: address, localUserDisplayMode: .noteToSelf)
if let delegate = self.delegate {
if let accessoryView = delegate.recipientPicker?(self,
accessoryViewForRecipient: recipient,
transaction: transaction) {
configuration.accessoryView = accessoryView
} else {
let accessoryMessage = delegate.recipientPicker(self,
accessoryMessageForRecipient: recipient,
transaction: transaction)
configuration.accessoryMessage = accessoryMessage
}
if let attributedSubtitle = delegate.recipientPicker?(self,
attributedSubtitleForRecipient: recipient,
transaction: transaction) {
configuration.attributedSubtitle = attributedSubtitle
}
}
cell.configure(configuration: configuration, transaction: transaction)
}
self.delegate?.recipientPicker(self, willRenderRecipient: recipient)
return cell
},
actionBlock: { [weak self] in
self?.tryToSelectRecipient(recipient)
}
)
case .group(let groupThread):
return OWSTableItem(
customCellBlock: { [weak self] in
let cell = GroupTableViewCell()
guard let self = self else { return cell }
if let delegate = self.delegate {
if delegate.recipientPicker(self, getRecipientState: recipient) != .canBeSelected {
cell.selectionStyle = .none
}
cell.accessoryMessage = Self.databaseStorage.read { transaction in
delegate.recipientPicker(self,
accessoryMessageForRecipient: recipient,
transaction: transaction)
}
}
cell.configure(thread: groupThread)
return cell
},
actionBlock: { [weak self] in
self?.tryToSelectRecipient(recipient)
}
)
}
}
}