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

import SignalServiceKit
import SignalUI
import SwiftUI

class BackupOnboardingKeyIntroViewController: HostingController<BackupOnboardingKeyIntroView> {
    private let onDeviceAuthSucceeded: (LocalDeviceAuthentication.AuthSuccess) -> Void
    private let viewModel: BackupsOnboardingKeyIntroViewModel

    init(onDeviceAuthSucceeded: @escaping (LocalDeviceAuthentication.AuthSuccess) -> Void) {
        self.onDeviceAuthSucceeded = onDeviceAuthSucceeded
        self.viewModel = BackupsOnboardingKeyIntroViewModel()

        super.init(wrappedView: BackupOnboardingKeyIntroView(viewModel: viewModel))

        viewModel.actionsDelegate = self
        OWSTableViewController2.removeBackButtonText(viewController: self)
    }
}

// MARK: -

extension BackupOnboardingKeyIntroViewController: BackupsOnboardingKeyIntroViewModel.ActionsDelegate {
    fileprivate func onContinue() {
        Task {
            if let authSuccess = await LocalDeviceAuthentication().performBiometricAuth() {
                onDeviceAuthSucceeded(authSuccess)
            }
        }
    }
}

// MARK: -

private class BackupsOnboardingKeyIntroViewModel {
    protocol ActionsDelegate: AnyObject {
        func onContinue()
    }

    weak var actionsDelegate: ActionsDelegate?

    func onContinue() {
        actionsDelegate?.onContinue()
    }
}

struct BackupOnboardingKeyIntroView: View {
    private struct BulletPoint: Identifiable {
        let image: UIImage
        let text: String

        var id: String { text }
    }

    fileprivate let viewModel: BackupsOnboardingKeyIntroViewModel

    private let bulletPoints: [BulletPoint] = [
        BulletPoint(
            image: .number,
            text: OWSLocalizedString(
                "BACKUP_ONBOARDING_KEY_INTRO_BULLET_1",
                comment: "Text for a bullet point in a view introducing the 'Recovery Key' during an onboarding flow.",
            ),
        ),
        BulletPoint(
            image: .lock,
            text: OWSLocalizedString(
                "BACKUP_ONBOARDING_KEY_INTRO_BULLET_2",
                comment: "Text for a bullet point in a view introducing the 'Recovery Key' during an onboarding flow.",
            ),
        ),
        BulletPoint(
            image: .errorCircle,
            text: OWSLocalizedString(
                "BACKUP_ONBOARDING_KEY_INTRO_BULLET_3",
                comment: "Text for a bullet point in a view introducing the 'Recovery Key' during an onboarding flow.",
            ),
        ),
    ]

    var body: some View {
        ScrollableContentPinnedFooterView {
            VStack {
                Spacer().frame(height: 20)

                Image(.backupsKey)
                    .frame(width: 80, height: 80)

                Spacer().frame(height: 16)

                Text(OWSLocalizedString(
                    "BACKUP_ONBOARDING_KEY_INTRO_TITLE",
                    comment: "Title for a view introducing the 'Recovery Key' during an onboarding flow.",
                ))
                .font(.title)
                .fontWeight(.semibold)
                .foregroundStyle(Color.Signal.label)

                Spacer().frame(height: 32)

                VStack(alignment: .leading, spacing: 24) {
                    ForEach(bulletPoints) { bulletPoint in
                        HStack(alignment: .top, spacing: 12) {
                            Image(uiImage: bulletPoint.image)
                                .frame(width: 24, height: 24)

                            Text(bulletPoint.text)
                        }
                        .multilineTextAlignment(.leading)
                        .foregroundStyle(Color.Signal.label)
                    }
                }
            }
            .padding(.horizontal)
        } pinnedFooter: {
            Button {
                viewModel.onContinue()
            } label: {
                Text(OWSLocalizedString(
                    "BACKUP_ONBOARDING_KEY_INTRO_CONTINUE_BUTTON_TITLE",
                    comment: "Title for a continue button for a view introducing the 'Recovery Key' during an onboarding flow.",
                ))
            }
            .buttonStyle(Registration.UI.LargePrimaryButtonStyle())
            .padding(.horizontal, 24)
        }
        .multilineTextAlignment(.center)
        .padding(.horizontal)
        .background(Color.Signal.groupedBackground)
    }
}

// MARK: -

#if DEBUG

extension BackupsOnboardingKeyIntroViewModel {
    static func forPreview() -> BackupsOnboardingKeyIntroViewModel {
        class PreviewActionsDelegate: ActionsDelegate {
            func onContinue() { print("Continuing...!") }
        }

        let viewModel = BackupsOnboardingKeyIntroViewModel()
        let actionsDelegate = PreviewActionsDelegate()
        viewModel.actionsDelegate = actionsDelegate
        ObjectRetainer.retainObject(actionsDelegate, forLifetimeOf: viewModel)

        return viewModel
    }
}

#Preview {
    BackupOnboardingKeyIntroView(viewModel: .forPreview())
}

#endif