Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalUI/Utils/BlockListUIUtils.swift
1 views
//
// 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)?,
    ) {
        guard groupThread.groupModel.groupMembership.isLocalUserFullOrInvitedMember 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.
        SSKEnvironment.shared.databaseStorageRef.write(block: { tx in
            SSKEnvironment.shared.blockingManagerRef.addBlockedGroupId(
                groupThread.groupId,
                blockMode: .local,
                shouldLeave: true,
                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.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)
    }
}