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

import Foundation
import SafariServices
import SignalServiceKit

public class NewPrivateStoryConfirmViewController: OWSTableViewController2 {

    let recipientSet: OrderedSet<PickedRecipient>
    let selectItemsInParent: (([StoryConversationItem]) -> Void)?
    var allowsReplies = true

    init(recipientSet: OrderedSet<PickedRecipient>, selectItemsInParent: (([StoryConversationItem]) -> Void)? = nil) {
        self.recipientSet = recipientSet
        self.selectItemsInParent = selectItemsInParent

        super.init()

        self.shouldAvoidKeyboard = true
    }

    // MARK: - View Lifecycle

    override public func viewDidLoad() {
        super.viewDidLoad()

        title = OWSLocalizedString(
            "NEW_PRIVATE_STORY_CONFIRM_TITLE",
            comment: "Title for the 'new private story' confirmation view",
        )

        navigationItem.rightBarButtonItem = UIBarButtonItem(
            title: OWSLocalizedString(
                "NEW_PRIVATE_STORY_CREATE_BUTTON",
                comment: "Button to create a new private story",
            ),
            style: .plain,
            target: self,
            action: #selector(didTapCreate),
        )

        tableView.register(ContactTableViewCell.self, forCellReuseIdentifier: ContactTableViewCell.reuseIdentifier)

        updateTableContents()
    }

    private var lastViewSize = CGSize.zero
    override public func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        guard view.frame.size != lastViewSize else { return }
        lastViewSize = view.frame.size
        updateTableContents()
    }

    override public func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        nameTextField.becomeFirstResponder()
    }

    // MARK: -

    private lazy var nameTextField: UITextField = {
        let textField = UITextField()

        textField.font = .dynamicTypeBody
        textField.backgroundColor = .clear
        textField.placeholder = OWSLocalizedString(
            "NEW_PRIVATE_STORY_NAME_PLACEHOLDER",
            comment: "Placeholder text for a new private story name",
        )

        return textField
    }()

    override public func themeDidChange() {
        super.themeDidChange()

        nameTextField.textColor = Theme.primaryTextColor
    }

    private func updateTableContents() {
        let contents = OWSTableContents()

        let nameAndAvatarSection = OWSTableSection()
        nameAndAvatarSection.footerTitle = OWSLocalizedString(
            "NEW_PRIVATE_STORY_NAME_FOOTER",
            comment: "Section footer for the name text field on the 'new private story' creation view",
        )
        nameAndAvatarSection.add(.init(
            customCellBlock: { [weak self] in
                let cell = OWSTableItem.newCell()
                cell.selectionStyle = .none
                guard let self else { return cell }

                cell.contentView.addSubview(self.nameTextField)
                self.nameTextField.autoPinEdgesToSuperviewMargins()

                return cell
            },
            actionBlock: {},
        ))
        contents.add(nameAndAvatarSection)

        let repliesSection = OWSTableSection()
        repliesSection.headerTitle = StoryStrings.repliesAndReactionsHeader
        repliesSection.footerTitle = StoryStrings.repliesAndReactionsFooter
        contents.add(repliesSection)

        repliesSection.add(.switch(
            withText: StoryStrings.repliesAndReactionsToggle,
            isOn: { [allowsReplies] in allowsReplies },
            target: self,
            selector: #selector(didToggleReplies),
        ))

        let viewerAddresses = SSKEnvironment.shared.databaseStorageRef.read { transaction in
            return BaseMemberViewController.sortedMemberAddresses(
                recipientSet: self.recipientSet,
                tx: transaction,
            )
        }

        let viewersSection = OWSTableSection()
        viewersSection.headerTitle = OWSLocalizedString(
            "NEW_PRIVATE_STORY_VIEWERS_HEADER",
            comment: "Header for the 'viewers' section of the 'new private story' view",
        )

        for address in viewerAddresses {
            viewersSection.add(OWSTableItem(
                dequeueCellBlock: { tableView in
                    guard let cell = tableView.dequeueReusableCell(withIdentifier: ContactTableViewCell.reuseIdentifier) as? ContactTableViewCell else {
                        owsFailDebug("Missing cell.")
                        return UITableViewCell()
                    }

                    cell.selectionStyle = .none

                    SSKEnvironment.shared.databaseStorageRef.read { transaction in
                        let configuration = ContactCellConfiguration(address: address, localUserDisplayMode: .asUser)
                        cell.configure(configuration: configuration, transaction: transaction)
                    }
                    return cell
                },
            ))
        }
        contents.add(viewersSection)

        self.contents = contents
    }

    // MARK: - Actions

    @objc
    private func didTapCreate() {
        AssertIsOnMainThread()

        guard let name = nameTextField.text?.filterForDisplay.nilIfEmpty else {
            return showMissingNameAlert()
        }

        let allowsReplies = self.allowsReplies
        let serviceIds = self.recipientSet.orderedMembers.compactMap { $0.address?.serviceId }

        Task {
            let databaseStorage = SSKEnvironment.shared.databaseStorageRef
            let threadUniqueId = await databaseStorage.awaitableWrite { tx in
                let newStory = TSPrivateStoryThread(
                    name: name,
                    allowsReplies: allowsReplies,
                    viewMode: .explicit,
                )
                newStory.anyInsert(transaction: tx)

                let recipientFetcher = DependenciesBridge.shared.recipientFetcher
                let recipientIds = serviceIds.map { recipientFetcher.fetchOrCreate(serviceId: $0, tx: tx).id }

                let storyRecipientManager = DependenciesBridge.shared.storyRecipientManager
                failIfThrows {
                    try storyRecipientManager.setRecipientIds(
                        recipientIds,
                        for: newStory,
                        shouldUpdateStorageService: true,
                        tx: tx,
                    )
                }

                return newStory.uniqueId
            }
            self.dismiss(animated: true)
            self.selectItemsInParent?(
                [StoryConversationItem(
                    backingItem: .privateStory(.init(storyThreadId: threadUniqueId, isMyStory: false)),
                    storyState: nil,
                )],
            )
        }
    }

    @objc
    private func didToggleReplies() {
        allowsReplies = !allowsReplies
    }

    public func showMissingNameAlert() {
        AssertIsOnMainThread()

        OWSActionSheets.showActionSheet(
            title: OWSLocalizedString(
                "NEW_PRIVATE_STORY_MISSING_NAME_ALERT_TITLE",
                comment: "Title for error alert indicating that a story name is required.",
            ),
            message: OWSLocalizedString(
                "NEW_PRIVATE_STORY_MISSING_NAME_ALERT_MESSAGE",
                comment: "Message for error alert indicating that a story name is required.",
            ),
        )
    }
}