167 lines
5.6 KiB
Swift
167 lines
5.6 KiB
Swift
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) }
|
||
}
|
||
}
|
||
}
|