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

import SignalServiceKit
import SignalUI

class ContactNoteSheet: OWSTableSheetViewController {
    struct Context {
        let db: any DB
        let recipientDatabaseTable: RecipientDatabaseTable
        let nicknameManager: any NicknameManager
    }

    override var sheetBackgroundColor: UIColor {
        if #available(iOS 26, *) {
            .clear
        } else {
            super.sheetBackgroundColor
        }
    }

    override var placeOnGlassIfAvailable: Bool { true }

    private let contactNoteTableViewController: ContactNoteTableViewController
    override var tableViewController: OWSTableViewController2 {
        get { contactNoteTableViewController }
        set { }
    }

    private let thread: TSContactThread
    private let context: Context

    private weak var fromViewController: UIViewController?

    func present(from viewController: UIViewController) {
        fromViewController = viewController
        viewController.present(self, animated: true)
    }

    init(thread: TSContactThread, context: Context) {
        self.thread = thread
        self.context = context
        self.contactNoteTableViewController = ContactNoteTableViewController(thread: thread, context: context)
        super.init()
        self.tableViewController.backgroundStyle = .clear
        self.tableViewController.tableView.clipsToBounds = false
        self.contactNoteTableViewController.didTapEdit = { [weak self] in
            self?.didTapEdit()
        }
    }

    override func tableContents() -> OWSTableContents {
        return contactNoteTableViewController.tableContents()
    }

    private func didTapEdit() {
        let nicknameEditor: NicknameEditorViewController? = self.context.db.read { tx in
            NicknameEditorViewController.create(
                for: self.thread.contactAddress,
                context: .init(
                    db: self.context.db,
                    nicknameManager: self.context.nicknameManager,
                ),
                tx: tx,
            )
        }
        guard let nicknameEditor else { return }
        let navigationController = OWSNavigationController(rootViewController: nicknameEditor)

        self.dismiss(animated: true) { [weak fromViewController = self.fromViewController] in
            fromViewController?.presentFormSheet(navigationController, animated: true)
        }
    }
}

private class ContactNoteTableViewController: OWSTableViewController2, TextViewWithPlaceholderDelegate {
    typealias Context = ContactNoteSheet.Context

    private let thread: TSContactThread
    private let context: Context
    var didTapEdit: (() -> Void)?

    private let noteTextView: TextViewWithPlaceholder = {
        let textView = TextViewWithPlaceholder()
        textView.isEditable = false
        textView.linkTextAttributes = [
            .underlineStyle: NSUnderlineStyle.single.rawValue,
        ]
        return textView
    }()

    init(thread: TSContactThread, context: Context) {
        self.thread = thread
        self.context = context
    }

    func tableContents() -> OWSTableContents {
        // This is trying to fake a navigation bar.
        // TODO: Make a general-purpose, navigable, self-sizing native sheet like what's used in the Call Quality Survey flow
        let header: UIView = {
            let headerContainer = UIView()
            let hMargin: CGFloat = if #available(iOS 26, *) {
                0
            } else {
                16
            }
            headerContainer.layoutMargins = .init(
                top: 0,
                left: hMargin,
                bottom: 24,
                right: hMargin,
            )

            let titleLabel = UILabel()
            headerContainer.addSubview(titleLabel)
            titleLabel.text = OWSLocalizedString(
                "CONTACT_NOTE_TITLE",
                comment: "Title for a view showing the note that has been set for a profile.",
            )
            titleLabel.font = .dynamicTypeHeadline.semibold()
            titleLabel.textColor = Theme.primaryTextColor
            titleLabel.autoCenterInSuperviewMargins()
            titleLabel.autoPinHeightToSuperviewMargins()

            var config = UIButton.Configuration.mediumSecondary(title: CommonStrings.editButton)
            config.baseForegroundColor = .Signal.label
            let editButton = UIButton(
                configuration: config,
                primaryAction: UIAction { [weak self] _ in
                    self?.didTapEdit?()
                },
            )
            headerContainer.addSubview(editButton)
            editButton.autoAlignAxis(.horizontal, toSameAxisOf: titleLabel)
            editButton.autoPinEdge(toSuperviewMargin: .trailing)
            editButton.autoPinEdge(.leading, to: .trailing, of: titleLabel, withOffset: 8, relation: .greaterThanOrEqual)

            return headerContainer
        }()

        let note: String? = self.context.db.read { tx in
            guard
                let recipient = self.context.recipientDatabaseTable.fetchRecipient(
                    address: self.thread.contactAddress,
                    tx: tx,
                ),
                let nicknameRecord = self.context.nicknameManager.fetchNickname(
                    for: recipient,
                    tx: tx,
                )
            else { return nil }
            return nicknameRecord.note
        }

        self.noteTextView.text = note

        let section = OWSTableSection(
            items: [
                self.textViewItem(
                    self.noteTextView,
                    dataDetectorTypes: .all,
                ),
            ],
            headerView: header,
        )

        return OWSTableContents(sections: [section])
    }
}