// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import SignalServiceKit public class BlockListUIUtils: Dependencies { 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 = databaseStorage.read { tx in contactsManager.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( format: 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"), accessibilityIdentifier: "BlockListUIUtils.block", style: .destructive, handler: { _ in blockAddress(address, displayName: displayName, from: viewController) { _ in completion?(true) } } )) actionSheet.addAction(ActionSheetAction( title: CommonStrings.cancelButton, accessibilityIdentifier: "BlockListUIUtils.dismiss", style: .cancel, handler: { _ in completion?(false) } )) viewController.presentActionSheet(actionSheet) } private static func showBlockGroupActionSheet( _ groupThread: TSGroupThread, from viewController: UIViewController, completion: Completion? ) { let actionSheetTitle = String( format: 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"), accessibilityIdentifier: "BlockListUIUtils.block", style: .destructive, handler: { _ in blockGroup(groupThread, from: viewController) { _ in completion?(true) } } )) actionSheet.addAction(ActionSheetAction( title: CommonStrings.cancelButton, accessibilityIdentifier: "BlockListUIUtils.dismiss", 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) databaseStorage.write { tx in self.blockingManager.addBlockedAddress(address, blockMode: .localShouldLeaveGroups, transaction: tx) } showOkActionSheet( title: OWSLocalizedString( "BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE", comment: "The title of the 'user blocked' alert." ), message: String( format: 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 ) } private static func blockGroup( _ groupThread: TSGroupThread, from viewController: UIViewController, completion: ((ActionSheetAction) -> Void)? ) { guard groupThread.isLocalUserMemberOfAnyKind else { blockGroupStep2(groupThread, from: viewController, completion: completion) return } GroupManager.leaveGroupOrDeclineInviteAsyncWithUI( groupThread: groupThread, fromViewController: viewController, success: { blockGroupStep2(groupThread, from: viewController, completion: completion) } ) } private static func blockGroupStep2( _ groupThread: TSGroupThread, from viewController: UIViewController, completion: ((ActionSheetAction) -> Void)? ) { // block the group regardless of the ability to deliver the // "leave group" message. databaseStorage.write(block: { tx in self.blockingManager.addBlockedGroup( groupModel: groupThread.groupModel, blockMode: .localShouldLeaveGroups, transaction: 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(format: 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(groupThread.groupModel, from: viewController, completion: completion) return } owsFailDebug("unexpected thread type: \(thread.self)") } public static func showUnblockAddressActionSheet( _ address: SignalServiceAddress, from viewController: UIViewController, completion: Completion? ) { let displayName = databaseStorage.read { tx in contactsManager.displayName(for: address, tx: tx).resolvedValue() } showUnblockAddressesActionSheet([address], displayName: displayName, from: viewController, completion: completion) } private static func showUnblockAddressesActionSheet( _ addresses: [SignalServiceAddress], displayName: String, from viewController: UIViewController, completion: Completion? ) { owsAssertDebug(!addresses.isEmpty) owsAssertDebug(!displayName.isEmpty) let actionSheetTitle = String( format: 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"), accessibilityIdentifier: "BlockListUIUtils.unblock", style: .destructive, handler: { _ in unblockAddresses(addresses, displayName: displayName, from: viewController) { _ in completion?(false) } } )) actionSheet.addAction(ActionSheetAction( title: CommonStrings.cancelButton, accessibilityIdentifier: "BlockListUIUtils.dismiss", style: .cancel, handler: { _ in completion?(true) } )) viewController.presentActionSheet(actionSheet) } public static func showUnblockGroupActionSheet( _ groupModel: TSGroupModel, 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"), accessibilityIdentifier: "BlockListUIUtils.unblock", style: .destructive, handler: { _ in unblockGroup(groupModel, from: viewController) { _ in completion?(false) } } )) actionSheet.addAction(ActionSheetAction( title: CommonStrings.cancelButton, accessibilityIdentifier: "BlockListUIUtils.dismiss", style: .cancel, handler: { _ in completion?(true) } )) viewController.presentActionSheet(actionSheet) } private static func unblockAddresses( _ addresses: [SignalServiceAddress], displayName: String, from viewController: UIViewController, completion: ((ActionSheetAction) -> Void)? ) { owsAssertDebug(!addresses.isEmpty) owsAssertDebug(!displayName.isEmpty) databaseStorage.write { tx in for address in addresses { owsAssertDebug(address.isValid) self.blockingManager.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(format: actionSheetTitleFormat, displayName.formattedForActionSheetTitle()) showOkActionSheet(title: actionSheetTitle, message: nil, from: viewController, completion: completion) } private static func unblockGroup( _ groupModel: TSGroupModel, from viewController: UIViewController, completion: ((ActionSheetAction) -> Void)? ) { databaseStorage.write { tx in self.blockingManager.removeBlockedGroup(groupId: groupModel.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(format: actionSheetTitleFormat, groupModel.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, accessibilityIdentifier: "BlockListUIUtils.ok", style: .default, handler: completion )) viewController.presentActionSheet(actionSheet) } }