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) }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|