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

import SignalServiceKit
import SignalUI

// MARK: - CallQualitySurveyNavigationController

final class CallQualitySurveyNavigationController: UINavigationController {
    let callQualitySurveyManager: CallQualitySurveyManager

    init(callQualitySurveyManager: CallQualitySurveyManager) {
        self.callQualitySurveyManager = callQualitySurveyManager
        let vc = CallQualitySurveyRatingViewController()
        super.init(rootViewController: vc)
        vc.navigationItem.rightBarButtonItem = .cancelButton(dismissingFrom: self)
    }

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

    override func viewDidLoad() {
        super.viewDidLoad()
        if #unavailable(iOS 26) {
            view.backgroundColor = .systemGroupedBackground
        }
    }

    override func viewIsAppearing(_ animated: Bool) {
        super.viewIsAppearing(animated)
        // The presentation jumps if you try to set the height here,
        // pre-iOS 26 jumps if you don't set it here 🤷‍♀️
        if #unavailable(iOS 26) {
            reloadHeight()
        }
    }

    private var hasSetUpDetents = false
    private func setUpDetents() {
        hasSetUpDetents = true
        if #available(iOS 16.0, *) {
            sheetPresentationController?.detents = [.custom(identifier: .medium, resolver: { [weak self] context in
                min(
                    self?.topViewHeight() ?? context.maximumDetentValue,
                    context.maximumDetentValue,
                )
            })]
        } else {
            sheetPresentationController?.detents = [.medium(), .large()]
        }
    }

    @available(iOS 16.0, *)
    private func topViewHeight() -> CGFloat? {
        (viewControllers.last as? CallQualitySurveySheetViewController)?.customSheetHeight()
    }

    private func addFadeTransition() {
        let transition: CATransition = CATransition()
        transition.duration = 0.3
        transition.type = CATransitionType.fade
        transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        view.layer.add(transition, forKey: nil)
    }

    func didTapHadIssues() {
        let vc = CallQualitySurveyIssuesViewController()
        vc.navigationItem.rightBarButtonItem = .cancelButton(dismissingFrom: self)
        vc.navigationItem.leftBarButtonItem = makeBackButton()
        addFadeTransition()
        pushViewController(vc, animated: false)
    }

    func doneSelectingIssues(rating: CallQualitySurvey.Rating) {
        let vc = SurveyDebugLogViewController(rating: rating)
        vc.navigationItem.rightBarButtonItem = .cancelButton(dismissingFrom: self)
        vc.navigationItem.leftBarButtonItem = makeBackButton()
        addFadeTransition()
        pushViewController(vc, animated: false)
    }

    func submit(rating: CallQualitySurvey.Rating, shouldSubmitDebugLogs: Bool) {
        callQualitySurveyManager.submit(
            rating: rating,
            shouldSubmitDebugLogs: shouldSubmitDebugLogs,
        )
        let host = presentingViewController
        dismiss(animated: true) {
            host?.presentToast(text: OWSLocalizedString(
                "CALL_QUALITY_SURVEY_COMPLETION_TOAST",
                comment: "Title for toast which appears after submitting a call quality survey",
            ))
        }
    }

    private func makeBackButton() -> UIBarButtonItem {
        UIBarButtonItem.button(
            image: UIImage(resource: .chevronLeftBold28),
            style: .plain,
        ) { [weak self] in
            self?.didTapBack()
        }
    }

    func didTapBack() {
        addFadeTransition()
        popViewController(animated: false)
    }

    func reloadHeight() {
        guard hasSetUpDetents else {
            setUpDetents()
            return
        }

        guard #available(iOS 16, *), let sheet = sheetPresentationController else { return }
        sheet.animateChanges {
            sheet.invalidateDetents()
        }
    }
}

// MARK: - CallQualitySurveySheetViewController

class CallQualitySurveySheetViewController: UIViewController {
    var sheetNav: CallQualitySurveyNavigationController? {
        navigationController as? CallQualitySurveyNavigationController
    }

    @available(iOS 16.0, *)
    func customSheetHeight() -> CGFloat? {
        // Override this in subclasses
        owsFailDebug("customSheetHeight not set")
        return nil
    }

    override func viewIsAppearing(_ animated: Bool) {
        super.viewIsAppearing(animated)
        DispatchQueue.main.async {
            self.reloadHeight()
        }
    }

    func reloadHeight() {
        sheetNav?.reloadHeight()
    }
}