// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // public import SignalServiceKit public class BlockListUIUtils { public typealias Completion = (Bool) -> Void private init() {} // MARK: Block public static func showBlockThreadActionSheet( _ thread: TSThread, from viewController: UIViewController, completion: Completion?, ) { if let contactThread = thread as? TSContactThread { showBlockAddressActionSheet(contactThread.contactAddress, from: viewController, completion: completion) return } if let groupThread = thread as? TSGroupThread { showBlockGroupActionSheet(groupThread, from: viewController, completion: completion) return } owsFailDebug("Unexpected thread type: \(thread.self)") } public static func showBlockAddressActionSheet( _ address: SignalServiceAddress, from viewController: UIViewController, completion: Completion?, ) { let displayName = SSKEnvironment.shared.databaseStorageRef.read { tx in SSKEnvironment.shared.contactManagerRef.displayName(for: address, tx: tx).resolvedValue() } showBlockAddressesActionSheet(address, displayName: displayName, from: viewController, completion: completion) } private static func showBlockAddressesActionSheet( _ address: SignalServiceAddress, displayName: String, from viewController: UIViewController, completion: Completion?, ) { owsAssertDebug(address.isValid) owsAssertDebug(!displayName.isEmpty) if address.isLocalAddress { showOkActionSheet( title: OWSLocalizedString( "BLOCK_LIST_VIEW_CANT_BLOCK_SELF_ALERT_TITLE", comment: "The title of the 'You can't block yourself' alert.", ), message: OWSLocalizedString( "BLOCK_LIST_VIEW_CANT_BLOCK_SELF_ALERT_MESSAGE", comment: "The message of the 'You can't block yourself' alert.", ), from: viewController, completion: { _ in completion?(false) }, ) return } let actionSheetTitle = String.nonPluralLocalizedStringWithFormat( OWSLocalizedString( "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT", comment: "A format for the 'block user' action sheet title. Embeds {{the blocked user's name or phone number}}.", ), displayName.formattedForActionSheetTitle(), ) let actionSheet = ActionSheetController( title: actionSheetTitle, message: OWSLocalizedString( "BLOCK_USER_BEHAVIOR_EXPLANATION", comment: "An explanation of the consequences of blocking another user.", ), ) actionSheet.addAction(ActionSheetAction( title: OWSLocalizedString("BLOCK_LIST_BLOCK_BUTTON", comment: "Button label for the 'block' button"), style: .destructive, handler: { _ in blockAddress(address, displayName: displayName, from: viewController) { _ in completion?(true) } }, )) actionSheet.addAction(ActionSheetAction( title: CommonStrings.cancelButton, style: .cancel, handler: { _ in completion?(false) }, )) viewController.presentActionSheet(actionSheet) } private static func showBlockGroupActionSheet( _ groupThread: TSGroupThread, from viewController: UIViewController, completion: Completion?, ) { let actionSheetTitle = String.nonPluralLocalizedStringWithFormat( OWSLocalizedString( "BLOCK_LIST_BLOCK_GROUP_TITLE_FORMAT", comment: "A format for the 'block group' action sheet title. Embeds the {{group name}}.", ), groupThread.groupNameOrDefault.formattedForActionSheetTitle(), ) let actionSheet = ActionSheetController( title: actionSheetTitle, message: OWSLocalizedString( "BLOCK_GROUP_BEHAVIOR_EXPLANATION", comment: "An explanation of the consequences of blocking a group.", ), ) actionSheet.addAction(ActionSheetAction( title: OWSLocalizedString("BLOCK_LIST_BLOCK_BUTTON", comment: "Button label for the 'block' button"), style: .destructive, handler: { _ in blockGroup(groupThread, from: viewController) { _ in completion?(true) } }, )) actionSheet.addAction(ActionSheetAction( title: CommonStrings.cancelButton, style: .cancel, handler: { _ in completion?(false) }, )) viewController.presentActionSheet(actionSheet) } private static func blockAddress( _ address: SignalServiceAddress, displayName: String, from viewController: UIViewController, completion: ((ActionSheetAction) -> Void)?, ) { owsAssertDebug(!displayName.isEmpty) owsAssertDebug(address.isValid) SSKEnvironment.shared.databaseStorageRef.write { tx in SSKEnvironment.shared.blockingManagerRef.addBlockedAddress(address, blockMode: .local, transaction: tx) } showOkActionSheet( title: OWSLocalizedString( "BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE", comment: "The title of the 'user blocked' alert.", ), message: String.nonPluralLocalizedStringWithFormat( OWSLocalizedString( "BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT", comment: "The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}.", ), displayName.formattedForActionSheetMessage(), ), from: viewController, completion: completion, ) } @MainActor private static func blockGroup( _ groupThread: TSGroupThread, from viewController: UIViewController, completion: ((ActionSheetAction) -> Void)?, ) { let blockingManager = SSKEnvironment.shared.blockingManagerRef let databaseStorage = SSKEnvironment.shared.databaseStorageRef databaseStorage.write(block: { tx in // block the group regardless of the ability to deliver the // "leave group" message. blockingManager.addBlockedGroupId( groupThread.groupId, blockMode: .local, transaction: tx, ) if groupThread.groupModel.groupMembership.isLocalUserFullOrInvitedMember { // We don't wait for this because it's durably enqeueued and may take up to // 24 hours to complete. _ = GroupManager.localLeaveGroupOrDeclineInvite( groupThread: groupThread, waitForMessageProcessing: true, tx: tx, ) } }) let actionSheetTitle = OWSLocalizedString( "BLOCK_LIST_VIEW_BLOCKED_GROUP_ALERT_TITLE", comment: "The title of the 'group blocked' alert.", ) let actionSheetMessageFormat = OWSLocalizedString( "BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT", comment: "The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}.", ) let actionSheetMessage = String.nonPluralLocalizedStringWithFormat(actionSheetMessageFormat, groupThread.groupNameOrDefault.formattedForActionSheetMessage()) showOkActionSheet(title: actionSheetTitle, message: actionSheetMessage, from: viewController, completion: completion) } // MARK: Unblock public static func showUnblockThreadActionSheet( _ thread: TSThread, from viewController: UIViewController, completion: Completion?, ) { if let contactThread = thread as? TSContactThread { showUnblockAddressActionSheet(contactThread.contactAddress, from: viewController, completion: completion) return } if let groupThread = thread as? TSGroupThread { showUnblockGroupActionSheet( groupId: groupThread.groupModel.groupId, groupNameOrDefault: groupThread.groupModel.groupNameOrDefault, from: viewController, completion: completion, ) return } owsFailDebug("unexpected thread type: \(thread.self)") } public static func showUnblockAddressActionSheet( _ address: SignalServiceAddress, from viewController: UIViewController, completion: Completion?, ) { let displayName = SSKEnvironment.shared.databaseStorageRef.read { tx in SSKEnvironment.shared.contactManagerRef.displayName(for: address, tx: tx).resolvedValue() } owsAssertDebug(!displayName.isEmpty) let actionSheetTitle = String.nonPluralLocalizedStringWithFormat( OWSLocalizedString( "BLOCK_LIST_UNBLOCK_TITLE_FORMAT", comment: "A format for the 'unblock conversation' action sheet title. Embeds the {{conversation title}}.", ), displayName.formattedForActionSheetTitle(), ) let actionSheet = ActionSheetController(title: actionSheetTitle) actionSheet.addAction(ActionSheetAction( title: OWSLocalizedString("BLOCK_LIST_UNBLOCK_BUTTON", comment: "Button label for the 'unblock' button"), style: .destructive, handler: { _ in unblockAddress(address, displayName: displayName, from: viewController) { _ in completion?(false) } }, )) actionSheet.addAction(ActionSheetAction( title: CommonStrings.cancelButton, style: .cancel, handler: { _ in completion?(true) }, )) viewController.presentActionSheet(actionSheet) } public static func showUnblockGroupActionSheet( groupId: Data, groupNameOrDefault: String, from viewController: UIViewController, completion: Completion?, ) { let actionSheetTitle = OWSLocalizedString( "BLOCK_LIST_UNBLOCK_GROUP_TITLE", comment: "Action sheet title when confirming you want to unblock a group.", ) let actionSheetMessage = OWSLocalizedString( "BLOCK_LIST_UNBLOCK_GROUP_BODY", comment: "Action sheet body when confirming you want to unblock a group", ) let actionSheet = ActionSheetController(title: actionSheetTitle, message: actionSheetMessage) actionSheet.addAction(ActionSheetAction( title: OWSLocalizedString("BLOCK_LIST_UNBLOCK_BUTTON", comment: "Button label for the 'unblock' button"), style: .destructive, handler: { _ in unblockGroup(groupId: groupId, groupNameOrDefault: groupNameOrDefault, from: viewController) { _ in completion?(false) } }, )) actionSheet.addAction(ActionSheetAction( title: CommonStrings.cancelButton, style: .cancel, handler: { _ in completion?(true) }, )) viewController.presentActionSheet(actionSheet) } private static func unblockAddress( _ address: SignalServiceAddress, displayName: String, from viewController: UIViewController, completion: ((ActionSheetAction) -> Void)?, ) { owsAssertDebug(address.isValid) owsAssertDebug(!displayName.isEmpty) SSKEnvironment.shared.databaseStorageRef.write { tx in SSKEnvironment.shared.blockingManagerRef.removeBlockedAddress(address, wasLocallyInitiated: true, transaction: tx) } let actionSheetTitleFormat = OWSLocalizedString( "BLOCK_LIST_VIEW_UNBLOCKED_ALERT_TITLE_FORMAT", comment: "Alert title after unblocking a group or 1:1 chat. Embeds the {{conversation title}}.", ) let actionSheetTitle = String.nonPluralLocalizedStringWithFormat(actionSheetTitleFormat, displayName.formattedForActionSheetTitle()) showOkActionSheet(title: actionSheetTitle, message: nil, from: viewController, completion: completion) } private static func unblockGroup( groupId: Data, groupNameOrDefault: String, from viewController: UIViewController, completion: ((ActionSheetAction) -> Void)?, ) { SSKEnvironment.shared.databaseStorageRef.write { tx in SSKEnvironment.shared.blockingManagerRef.removeBlockedGroup(groupId: groupId, wasLocallyInitiated: true, transaction: tx) } let actionSheetTitleFormat = OWSLocalizedString( "BLOCK_LIST_VIEW_UNBLOCKED_ALERT_TITLE_FORMAT", comment: "Alert title after unblocking a group or 1:1 chat. Embeds the {{conversation title}}.", ) let actionSheetTitle = String.nonPluralLocalizedStringWithFormat(actionSheetTitleFormat, groupNameOrDefault.formattedForActionSheetMessage()) let actionSheetMessage = OWSLocalizedString( "BLOCK_LIST_VIEW_UNBLOCKED_GROUP_ALERT_BODY", comment: "Alert body after unblocking a group.", ) showOkActionSheet(title: actionSheetTitle, message: actionSheetMessage, from: viewController, completion: completion) } // MARK: UI Utils private static func showOkActionSheet( title: String, message: String?, from viewController: UIViewController, completion: ((ActionSheetAction) -> Void)?, ) { let actionSheet = ActionSheetController(title: title, message: message) actionSheet.addAction(ActionSheetAction( title: CommonStrings.okButton, style: .default, handler: completion, )) viewController.presentActionSheet(actionSheet) } }