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

import SignalServiceKit
import UIKit

public class SpamCaptchaViewController: UIViewController, CaptchaViewDelegate {

    private var captchaView: CaptchaView?

    var completionHandler: ((String?) -> Void)?

    private init() {
        super.init(nibName: nil, bundle: nil)
    }

    override public func loadView() {
        let captchaView = CaptchaView(context: .challenge)
        captchaView.delegate = self

        let view = UIView()
        view.addSubview(captchaView)
        captchaView.autoPinEdgesToSuperviewEdges()

        self.captchaView = captchaView
        self.view = view
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override public func viewDidLoad() {
        super.viewDidLoad()
        captchaView?.loadCaptcha()

        isModalInPresentation = true
        navigationItem.title = OWSLocalizedString("SPAM_CAPTCHA_VIEW_CONTROLLER", comment: "Title for the captcha view controller")
        navigationItem.leftBarButtonItem = .systemItem(.stop) { [weak self] in
            self?.completionHandler?(nil)
            self?.completionHandler = nil
        }
    }

    public func captchaView(_: CaptchaView, didCompleteCaptchaWithToken token: String) {
        completionHandler?(token)
        completionHandler = nil
    }

    public func captchaViewDidFailToCompleteCaptcha(_: CaptchaView) {
        captchaView?.loadCaptcha()
    }

    override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return UIDevice.current.isIPad ? .all : .portrait
    }
}

extension SpamCaptchaViewController {

    public static func presentActionSheet(from fromVC: UIViewController) {

        let titleLabel = UILabel()
        titleLabel.font = UIFont.dynamicTypeTitle2Clamped.semibold()
        titleLabel.textColor = Theme.primaryTextColor
        titleLabel.numberOfLines = 0
        titleLabel.lineBreakMode = .byWordWrapping
        titleLabel.textAlignment = .center
        titleLabel.text = OWSLocalizedString("SPAM_CAPTCHA_SHEET_TITLE", comment: "Title for action sheet explaining captcha requirement.")

        let bodyLabel = UILabel()
        bodyLabel.font = .dynamicTypeSubheadlineClamped
        bodyLabel.textColor = Theme.primaryTextColor
        bodyLabel.numberOfLines = 0
        bodyLabel.lineBreakMode = .byWordWrapping
        bodyLabel.text = OWSLocalizedString("SPAM_CAPTCHA_SHEET_BODY", comment: "Body for action sheet explaining captcha requirement.")

        let continueButton = OWSFlatButton()
        continueButton.setTitle(
            title: CommonStrings.continueButton,
            font: UIFont.dynamicTypeHeadlineClamped,
            titleColor: .white,
        )
        continueButton.setBackgroundColors(upColor: Theme.accentBlueColor)
        continueButton.layer.cornerRadius = 8
        continueButton.layer.masksToBounds = true
        continueButton.contentEdgeInsets = UIEdgeInsets(hMargin: 4, vMargin: 14)

        let stackView = UIStackView(arrangedSubviews: [
            titleLabel,
            bodyLabel,
            UIView.spacer(withHeight: 72),
            continueButton,
        ])
        stackView.axis = .vertical
        stackView.alignment = .center
        stackView.distribution = .fill
        stackView.spacing = 16

        let sheet = SheetViewController()
        sheet.isHandleHidden = true
        sheet.contentView.addSubview(stackView)
        sheet.dismissHandler = { sheet in
            sheet.dismiss(animated: true)

            let confirmationSheet = ActionSheetController(
                title: OWSLocalizedString(
                    "SPAM_CAPTCHA_DISMISS_CONFIRMATION_TITLE",
                    comment: "Title for confirmation dialog confirming to ignore verification.",
                ),
                message: OWSLocalizedString(
                    "SPAM_CAPTCHA_DISMISS_CONFIRMATION_MESSAGE",
                    comment: "Message for confirmation dialog confirming to ignore verification.",
                ),
            )

            confirmationSheet.addAction(
                ActionSheetAction(
                    title: OWSLocalizedString("SPAM_CAPTCHA_SKIP_VERIFICATION_ACTION", comment: "Action to skip verification"),
                    style: .destructive,
                ),
            )
            confirmationSheet.addAction(
                ActionSheetAction(
                    title: CommonStrings.cancelButton,
                    style: .cancel,
                    handler: { _ in
                        presentActionSheet(from: fromVC)
                    },
                ),
            )
            fromVC.present(confirmationSheet, animated: true, completion: nil)
        }

        continueButton.setPressedBlock {
            sheet.dismiss(animated: true)
            presentCaptchaVC(from: fromVC)
        }

        stackView.autoPinEdgesToSuperviewMargins(
            with: UIEdgeInsets(hMargin: 24, vMargin: 16),
        )
        continueButton.autoPinWidthToSuperviewMargins()

        fromVC.present(sheet, animated: true, completion: nil)
    }

    static func presentCaptchaVC(from fromVC: UIViewController) {
        let vc = SpamCaptchaViewController()
        vc.completionHandler = { token in
            if let token {
                fromVC.presentToast(
                    text: OWSLocalizedString(
                        "SPAM_CAPTCHA_COMPLETED_TOAST",
                        comment: "Text for toast presented after spam verification has been completed",
                    ),
                )
                SSKEnvironment.shared.spamChallengeResolverRef.handleIncomingCaptchaChallengeToken(token)
            }
            vc.dismiss(animated: true)
        }
        let navVC = OWSNavigationController(rootViewController: vc)
        fromVC.present(navVC, animated: true, completion: nil)
    }
}