Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/Calls/UserInterface/CallLinkApprovalRequestDetailsSheet.swift
1 views
//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import LibSignalClient
import SignalServiceKit
import SignalUI
import UIKit

// MARK: - CallLinkApprovalRequestDetailsSheet

class CallLinkApprovalRequestDetailsSheet: OWSTableSheetViewController {

    private struct Deps {
        let contactsManager: any ContactManager
        let db: any DB
    }

    private let deps = Deps(
        contactsManager: SSKEnvironment.shared.contactManagerRef,
        db: DependenciesBridge.shared.db,
    )

    let approvalRequest: CallLinkApprovalRequest
    let approvalViewModel: CallLinkApprovalViewModel

    override var handleBackgroundColor: UIColor {
        UIColor.Signal.transparentSeparator
    }

    init(
        approvalRequest: CallLinkApprovalRequest,
        approvalViewModel: CallLinkApprovalViewModel,
    ) {
        self.approvalRequest = approvalRequest
        self.approvalViewModel = approvalViewModel
        super.init()

        self.overrideUserInterfaceStyle = .dark
        self.tableViewController.forceDarkMode = true
    }

    private weak var fromViewController: UIViewController?

    func present(
        from viewController: UIViewController,
        dismissalDelegate: (any SheetDismissalDelegate)? = nil,
        animated: Bool = true,
    ) {
        self.fromViewController = viewController
        self.dismissalDelegate = dismissalDelegate
        viewController.present(self, animated: animated)
    }

    // MARK: Table contents

    override func tableContents() -> OWSTableContents {
        let contents = OWSTableContents()

        contents.add(.init(
            items: [
                // TODO: It would be nice to eventually make OWSTableItem's default values not dependent on Theme and instead use dynamic colors
                .item(
                    icon: .checkCircle,
                    tintColor: UIColor.Signal.label,
                    name: OWSLocalizedString(
                        "CALL_LINK_JOIN_REQUEST_APPROVE_BUTTON",
                        comment: "Button on an action sheet to approve a request to join a call link.",
                    ),
                    textColor: UIColor.Signal.label,
                ) { [weak self] in
                    guard let self else { return }
                    self.dismiss(animated: true)
                    self.approvalViewModel.performRequestAction.send((.approve, self.approvalRequest))
                },
                .item(
                    icon: .xCircle,
                    tintColor: UIColor.Signal.label,
                    name: OWSLocalizedString(
                        "CALL_LINK_JOIN_REQUEST_DENY_BUTTON",
                        comment: "Button on an action sheet to deny a request to join a call link.",
                    ),
                    textColor: UIColor.Signal.label,
                ) { [weak self] in
                    guard let self else { return }
                    self.dismiss(animated: true)
                    self.approvalViewModel.performRequestAction.send((.deny, self.approvalRequest))
                },
            ],
            headerView: self.buildHeader(),
        ))

        return contents
    }

    // MARK: Header

    private func buildHeader() -> UIView {
        let vStack = UIStackView()
        vStack.axis = .vertical
        vStack.spacing = 8
        vStack.alignment = .center
        vStack.isLayoutMarginsRelativeArrangement = true
        vStack.layoutMargins = .init(
            top: 20,
            left: 0,
            bottom: 36,
            right: 0,
        )

        // [CallLink] TODO: This should expand to a full-screen preview when tapped
        let avatarView = ConversationAvatarView(
            sizeClass: .eightyEight,
            localUserDisplayMode: .asLocalUser,
            badged: true,
        )

        let (contactTitle, mutualThreads): (NSAttributedString, [TSGroupThread]) = self.deps.db.read { tx in
            avatarView.update(tx) { config in
                config.dataSource = .address(self.approvalRequest.address)
            }

            let isSystemContact = self.deps.contactsManager.fetchSignalAccount(
                for: self.approvalRequest.address,
                transaction: tx,
            ) != nil

            let mutualThreads = TSGroupThread.groupThreads(
                with: self.approvalRequest.address,
                transaction: tx,
            )
            .filter(\.groupModel.groupMembership.isLocalUserFullMember)
            .filter(\.shouldThreadBeVisible)
            .filter { !$0.isTerminatedGroup }

            let contactTitle = ConversationHeaderBuilder.threadAttributedString(
                threadName: self.approvalRequest.name,
                isNoteToSelf: false,
                isSystemContact: isSystemContact,
                canTap: true,
                tx: tx,
            )

            return (contactTitle, mutualThreads)
        }

        vStack.addArrangedSubview(avatarView)

        let nameButton = OWSButton { [weak self] in
            self?.didTapName()
        }
        nameButton.dimsWhenHighlighted = true
        nameButton.titleLabel?.numberOfLines = 0
        nameButton.titleLabel?.textAlignment = .center
        nameButton.setAttributedTitle(contactTitle, for: .normal)
        vStack.addArrangedSubview(nameButton)

        let mutualGroupsLabel = UILabel()
        mutualGroupsLabel.text = ProfileDetailLabel.mutualGroupsString(isInGroupContext: false, mutualGroups: mutualThreads)
        mutualGroupsLabel.font = .dynamicTypeSubheadline
        mutualGroupsLabel.textColor = UIColor.Signal.secondaryLabel

        vStack.addArrangedSubview(mutualGroupsLabel)

        return vStack
    }

    private func didTapName() {
        guard let fromViewController else {
            owsFailDebug("Parent view controller missing")
            return
        }
        let thread = TSContactThread.getOrCreateThread(contactAddress: approvalRequest.address)
        let sheet = ContactAboutSheet(thread: thread, spoilerState: .init())
        sheet.overrideUserInterfaceStyle = .dark
        self.dismiss(animated: true) {
            sheet.present(from: fromViewController, dismissalDelegate: self.dismissalDelegate)
        }
    }
}

// MARK: - Previews

#if DEBUG
@available(iOS 17.0, *)
#Preview {
    SheetPreviewViewController { viewController, animated in
        CallLinkApprovalRequestDetailsSheet(
            approvalRequest: .init(aci: .init(fromUUID: UUID()), name: "Candice"),
            approvalViewModel: CallLinkApprovalViewModel(),
        )
        .present(from: viewController, animated: animated)
    }
}
#endif