Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/src/ViewControllers/AppSettings/Privacy/BlockListViewController.swift
1 views
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import SignalServiceKit
import SignalUI

class BlockListViewController: OWSTableViewController2 {

    override init() {
        super.init()

        title = NSLocalizedString(
            "SETTINGS_BLOCK_LIST_TITLE",
            comment: "Label for the block list section of the settings view",
        )
    }

    override func viewDidLoad() {
        updateContactList(reloadTableView: false)

        super.viewDidLoad()

        SUIEnvironment.shared.contactsViewHelperRef.addObserver(self)

        tableView.estimatedRowHeight = 60
        tableView.register(ContactTableViewCell.self, forCellReuseIdentifier: ContactTableViewCell.reuseIdentifier)
    }

    private func updateContactList(reloadTableView: Bool) {
        let contents = OWSTableContents()

        // "Add" section
        let sectionAddContact = OWSTableSection(items: [
            OWSTableItem.disclosureItem(
                withText: NSLocalizedString(
                    "SETTINGS_BLOCK_LIST_ADD_BUTTON",
                    comment: "A label for the 'add phone number' button in the block list table.",
                ),
                actionBlock: { [weak self] in
                    let viewController = AddToBlockListViewController()
                    viewController.delegate = self
                    self?.navigationController?.pushViewController(viewController, animated: true)
                },
            ),
        ])
        sectionAddContact.footerTitle = NSLocalizedString(
            "BLOCK_USER_BEHAVIOR_EXPLANATION",
            comment: "An explanation of the consequences of blocking another user.",
        )
        contents.add(sectionAddContact)

        let addresses: [SignalServiceAddress]
        let groups: [(groupId: Data, groupName: String, groupModel: TSGroupModel?, groupAvatarImage: UIImage?)]
        (addresses, groups) = SSKEnvironment.shared.databaseStorageRef.read { transaction in
            let avatarBuilder = SSKEnvironment.shared.avatarBuilderRef
            let blockingManager = SSKEnvironment.shared.blockingManagerRef
            let contactManager = SSKEnvironment.shared.contactManagerRef
            let addresses = contactManager.sortSignalServiceAddresses(
                blockingManager.blockedAddresses(transaction: transaction),
                transaction: transaction,
            )
            let groups: [(groupId: Data, groupName: String, groupModel: TSGroupModel?, groupAvatarImage: UIImage?)]
            groups = blockingManager.blockedGroupIds(transaction: transaction).map { groupId in
                let groupModel = TSGroupThread.fetch(groupId: groupId, transaction: transaction)?.groupModel
                let groupName = groupModel?.groupNameOrDefault ?? TSGroupThread.defaultGroupName
                let groupAvatarImage: UIImage? = {
                    if let avatarData = groupModel?.avatarDataState.dataIfPresent {
                        return UIImage(data: avatarData)
                    }

                    return avatarBuilder.defaultAvatarImage(
                        forGroupId: groupId,
                        diameterPoints: AvatarBuilder.standardAvatarSizePoints,
                        transaction: transaction,
                    )
                }()
                return (groupId, groupName, groupModel, groupAvatarImage)
            }.sorted(by: {
                switch $0.groupName.localizedCaseInsensitiveCompare($1.groupName) {
                case .orderedAscending:
                    return true
                case .orderedDescending:
                    return false
                case .orderedSame:
                    return $0.groupId.hexadecimalString < $0.groupId.hexadecimalString
                }
            })
            return (addresses, groups)
        }

        // Contacts
        let contactsSectionItems = addresses.map { address in
            OWSTableItem(
                dequeueCellBlock: { [weak self] tableView in
                    let cell = tableView.dequeueReusableCell(withIdentifier: ContactTableViewCell.reuseIdentifier) as! ContactTableViewCell
                    let config = ContactCellConfiguration(
                        address: address,
                        localUserDisplayMode: .asUser,
                    )
                    if self != nil {
                        SSKEnvironment.shared.databaseStorageRef.read { transaction in
                            cell.configure(configuration: config, transaction: transaction)
                        }
                    }
                    cell.accessibilityIdentifier = "BlockListViewController.user"
                    return cell
                },
                actionBlock: { [weak self] in
                    guard let self else { return }
                    BlockListUIUtils.showUnblockAddressActionSheet(address, from: self) { isBlocked in
                        if !isBlocked {
                            // Reload if unblocked.
                            self.updateContactList(reloadTableView: true)
                        }
                    }
                },
            )
        }
        if !contactsSectionItems.isEmpty {
            contents.add(OWSTableSection(
                title: NSLocalizedString(
                    "BLOCK_LIST_BLOCKED_USERS_SECTION",
                    comment: "Section header for users that have been blocked",
                ),
                items: contactsSectionItems,
            ))
        }

        // Groups
        let groupsSectionItems = groups.map { groupId, groupName, groupModel, groupAvatarImage in
            return OWSTableItem(
                customCellBlock: {
                    let cell = AvatarTableViewCell()
                    cell.configure(image: groupAvatarImage, text: groupName)
                    return cell
                },
                actionBlock: { [weak self] in
                    guard let self else { return }
                    BlockListUIUtils.showUnblockGroupActionSheet(groupId: groupId, groupNameOrDefault: groupName, from: self) { isBlocked in
                        if !isBlocked {
                            self.updateContactList(reloadTableView: true)
                        }
                    }
                },
            )
        }
        if !groupsSectionItems.isEmpty {
            contents.add(OWSTableSection(
                title: NSLocalizedString(
                    "BLOCK_LIST_BLOCKED_GROUPS_SECTION",
                    comment: "Section header for groups that have been blocked",
                ),
                items: groupsSectionItems,
            ))
        }

        setContents(contents, shouldReload: reloadTableView)
    }
}

extension BlockListViewController: ContactsViewHelperObserver {

    func contactsViewHelperDidUpdateContacts() {
        updateContactList(reloadTableView: true)
    }
}

extension BlockListViewController: AddToBlockListDelegate {

    func addToBlockListComplete() {
        navigationController?.popToViewController(self, animated: true)
    }
}