montana/Монтана-iOS/Sources/MontanaApp/UI/Onboarding/OnboardingView.swift

167 lines
5.6 KiB
Swift
Raw Permalink Normal View History

import SwiftUI
struct OnboardingView: View {
@State private var screen: Screen = .welcome
@EnvironmentObject private var identityManager: IdentityManager
enum Screen {
case welcome
case createNew
case restore
}
var body: some View {
NavigationStack {
switch screen {
case .welcome:
WelcomeView(onCreate: { screen = .createNew }, onRestore: { screen = .restore })
case .createNew:
CreateMnemonicView(onBack: { screen = .welcome })
case .restore:
RestoreMnemonicView(onBack: { screen = .welcome })
}
}
}
}
private struct WelcomeView: View {
let onCreate: () -> Void
let onRestore: () -> Void
var body: some View {
VStack(spacing: 32) {
Spacer()
Image(systemName: "infinity.circle.fill")
.font(.system(size: 80))
.foregroundStyle(.tint)
Text("Montana")
.font(.system(size: 48, weight: .bold))
Text("Цифровая собственность.\nПостквантовая криптография.\nТвой ключ — твоя идентичность.")
.font(.body)
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
.padding(.horizontal)
Spacer()
VStack(spacing: 12) {
Button(action: onCreate) {
Text("Создать новую идентичность")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
Button(action: onRestore) {
Text("Восстановить из мнемоники")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
.controlSize(.large)
}
.padding(.horizontal)
Spacer().frame(height: 24)
}
}
}
private struct CreateMnemonicView: View {
let onBack: () -> Void
@EnvironmentObject private var identityManager: IdentityManager
@State private var mnemonic: String = ""
@State private var confirmedSaved = false
@State private var error: String?
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text("Запишите 24 слова в надёжное место")
.font(.title2.bold())
Text("Это единственный способ восстановить идентичность. Никому не показывайте.")
.font(.footnote)
.foregroundStyle(.secondary)
if !mnemonic.isEmpty {
MnemonicGridView(mnemonic: mnemonic)
}
if let error {
Text(error)
.foregroundStyle(.red)
.font(.footnote)
}
Toggle("Я записал(-а) все 24 слова", isOn: $confirmedSaved)
.padding(.top)
Button {
do {
try identityManager.restore(from: mnemonic)
} catch {
self.error = String(describing: error)
}
} label: {
Text("Завершить настройку")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.disabled(!confirmedSaved || mnemonic.isEmpty)
}
.padding()
}
.navigationTitle("Новая идентичность")
.toolbar {
ToolbarItem(placement: .topBarLeading) { Button("Назад", action: onBack) }
}
.task {
if mnemonic.isEmpty {
mnemonic = Mnemonic.generate()
}
}
}
}
private struct RestoreMnemonicView: View {
let onBack: () -> Void
@EnvironmentObject private var identityManager: IdentityManager
@State private var mnemonicInput: String = ""
@State private var error: String?
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Введите 24 слова мнемоники через пробел")
.font(.title3)
TextEditor(text: $mnemonicInput)
.font(.body.monospaced())
.frame(minHeight: 160)
.border(.gray.opacity(0.4))
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
if let error {
Text(error).foregroundStyle(.red).font(.footnote)
}
Button {
do {
try identityManager.restore(from: mnemonicInput)
} catch {
self.error = String(describing: error)
}
} label: {
Text("Восстановить")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.disabled(mnemonicInput.split(whereSeparator: { $0.isWhitespace }).count != 24)
Spacer()
}
.padding()
.navigationTitle("Восстановление")
.toolbar {
ToolbarItem(placement: .topBarLeading) { Button("Назад", action: onBack) }
}
}
}