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

import Foundation
import SafariServices
import SignalServiceKit
import SignalUI

class UsernameEducationViewController: OWSTableViewController2 {

    private enum Constants {
        static let headerHeight: CGFloat = 44.0
        static let itemIconSize: CGFloat = 48.0
        static let itemMargin: CGFloat = 20.0

        static let pillSize: CGSize = .init(width: 36, height: 5)
        static let pillTopMargin: CGFloat = 12.0
    }

    /// Completion called once the user taps 'Continue' in the education prompt
    var continueCompletion: (() -> Void)?

    var prefersNavigationBarHidden: Bool { true }

    // MARK: Lifecycle

    override init() {
        super.init()

        topHeader = pillHeader
        bottomFooter = footerView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = Theme.tableView2BackgroundColor
        rebuildTableContents()
        setColorsForCurrentTheme()
        tableView.alwaysBounceVertical = false
    }

    override func themeDidChange() {
        super.themeDidChange()
        setColorsForCurrentTheme()
        rebuildTableContents()
    }

    // MARK: Views

    private lazy var pillHeader: UIView = {
        let headerView = UIView()

        headerView.addSubview(pillView)
        headerView.autoSetDimension(.height, toSize: Constants.headerHeight)

        pillView.autoPinEdge(toSuperviewEdge: .top, withInset: Constants.pillTopMargin)
        pillView.autoHCenterInSuperview()

        return headerView
    }()

    private lazy var pillView: PillView = {
        let pillView = PillView()
        pillView.autoSetDimensions(to: Constants.pillSize)
        return pillView
    }()

    private lazy var footerView: UIView = {
        let continueButton = UIButton(
            configuration: .largePrimary(title: OWSLocalizedString(
                "USERNAME_EDUCATION_SET_UP_BUTTON",
                comment: "Label for the 'set up' button on the username education sheet",
            )),
            primaryAction: UIAction { [weak self] _ in
                self?.didTapContinue()
            },
        )

        let dismissButton = UIButton(
            configuration: .largeSecondary(title: CommonStrings.notNowButton),
            primaryAction: UIAction { [weak self] _ in
                self?.didTapDismiss()
            },
        )

        let stackView = UIStackView.verticalButtonStack(
            buttons: [continueButton, dismissButton],
            isFullWidthButtons: true,
        )

        let container = UIView()
        container.preservesSuperviewLayoutMargins = true
        container.addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: container.topAnchor),
            stackView.leadingAnchor.constraint(equalTo: container.layoutMarginsGuide.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: container.layoutMarginsGuide.trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
        ])
        return container
    }()

    // MARK: TableView config

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

        let headerSection = OWSTableSection()
        headerSection.hasBackground = false
        headerSection.add(createTableHeader())

        let section = OWSTableSection()
        section.hasBackground = false

        section.add(createTableItem(
            iconName: "phone-48-color",
            title: OWSLocalizedString(
                "USERNAME_EDUCATION_PRIVACY_TITLE",
                comment: "Title for phone number privacy section of the username education sheet",
            ),
            description: OWSLocalizedString(
                "USERNAME_EDUCATION_PRIVACY_DESCRIPTION",
                comment: "Description of phone number privacy on the username education sheet",
            ),
        ))

        section.add(createTableItem(
            iconName: "usernames-48-color",
            title: OWSLocalizedString(
                "USERNAME_EDUCATION_USERNAME_TITLE",
                comment: "Title for usernames section on the username education sheet",
            ),
            description: OWSLocalizedString(
                "USERNAME_EDUCATION_USERNAME_DESCRIPTION",
                comment: "Description of usernames on the username education sheet",
            ),
        ))

        section.add(createTableItem(
            iconName: "qr-codes-48-color",
            title: OWSLocalizedString(
                "USERNAME_EDUCATION_LINK_TITLE",
                comment: "Title for the username links and QR codes section on the username education sheet",
            ),
            description: OWSLocalizedString(
                "USERNAME_EDUCATION_LINK_DESCRIPTION",
                comment: "Description of username links and QR codes on the username education sheet",
            ),
            isLastItem: true,
        ))

        contents.add(sections: [
            headerSection,
            section,
        ])

        self.contents = contents
    }

    private func createTableHeader() -> OWSTableItem {
        return OWSTableItem {
            let cell = OWSTableItem.newCell()

            let headerView = Self.HeaderView()
            cell.addSubview(headerView)

            headerView.autoPinEdgesToSuperviewMargins()
            return cell
        }
    }

    private func createTableItem(
        iconName: String,
        title: String,
        description: String,
        isLastItem: Bool = false,
    ) -> OWSTableItem {
        return OWSTableItem {
            let cell = OWSTableItem.newCell()
            cell.selectionStyle = .none

            let stackView = UIStackView()
            stackView.spacing = Constants.itemMargin

            cell.addSubview(stackView)

            stackView.autoPinLeadingToSuperviewMargin(withInset: Constants.itemMargin)
            stackView.autoPinTrailingToSuperviewMargin(withInset: Constants.itemMargin)
            stackView.autoPinEdge(toSuperviewEdge: .top, withInset: 0)
            stackView.autoPinEdge(
                toSuperviewEdge: .bottom,
                withInset: isLastItem ? 0 : Constants.itemMargin * 2,
            )
            stackView.alignment = .top

            let iconView = UIImageView(image: UIImage(named: iconName))
            iconView.contentMode = .scaleAspectFit
            iconView.autoPinToSquareAspectRatio()
            iconView.autoSetDimension(.height, toSize: Constants.itemIconSize)

            stackView.addArrangedSubview(iconView)

            let titleLabel = UILabel()
            titleLabel.text = title
            titleLabel.font = UIFont.dynamicTypeBody
            titleLabel.numberOfLines = 0
            titleLabel.textAlignment = .left
            titleLabel.lineBreakMode = .byWordWrapping
            titleLabel.textColor = Theme.primaryTextColor

            let bodyLabel = UILabel()
            bodyLabel.text = description
            bodyLabel.font = UIFont.dynamicTypeSubheadlineClamped
            bodyLabel.numberOfLines = 0
            bodyLabel.textAlignment = .left
            bodyLabel.lineBreakMode = .byWordWrapping
            bodyLabel.textColor = Theme.secondaryTextAndIconColor

            let textStack = UIStackView(
                arrangedSubviews: [
                    titleLabel,
                    bodyLabel,
                ],
            )
            textStack.axis = .vertical
            textStack.spacing = 4

            stackView.addArrangedSubview(textStack)

            return cell
        }
    }

    // MARK: Actions

    private func didTapContinue() {
        dismiss(animated: true) {
            self.continueCompletion?()
        }
    }

    private func didTapDismiss() {
        dismiss(animated: true)
    }

    // MARK: Theme

    private func setColorsForCurrentTheme() {
        pillView.backgroundColor = Theme.isDarkThemeEnabled ? .ows_gray80 : .ows_gray20
    }
}

extension UsernameEducationViewController {
    class HeaderView: UIView {

        // MARK: Init

        init() {
            super.init(frame: .zero)

            addSubview(usernameTitleLabel)
            usernameTitleLabel.autoPinEdgesToSuperviewEdges()

            updateFontsForCurrentPreferredContentSize()
            setColorsForCurrentTheme()
        }

        @available(*, unavailable, message: "Use other constructor")
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }

        // MARK: Views

        private lazy var usernameTitleLabel: UILabel = {
            let label = UILabel()
            label.text = OWSLocalizedString(
                "USERNAME_EDUCATION_TITLE",
                comment: "Title to set up signal username",
            )
            label.numberOfLines = 0
            label.textAlignment = .center
            return label
        }()

        // MARK: - Style views

        func updateFontsForCurrentPreferredContentSize() {
            usernameTitleLabel.font = .dynamicTypeTitle1Clamped.semibold()
        }

        func setColorsForCurrentTheme() {
            usernameTitleLabel.textColor = Theme.primaryTextColor
        }
    }
}