Restore Wallet Coord Flow

- Restore wallet flow redesign made as rewired navigation to a whole new flow
This commit is contained in:
Lukas Korba 2025-03-27 15:23:09 +01:00
parent 02dfcb41b6
commit ab3226aced
13 changed files with 250 additions and 65 deletions

View File

@ -96,7 +96,7 @@ let package = Package(
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.18.0"),
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.6"),
.package(url: "https://github.com/pointfreeco/swift-url-routing", from: "0.6.2"),
.package(url: "https://github.com/zcash-hackworks/MnemonicSwift", from: "2.2.4"),
.package(url: "https://github.com/zcash-hackworks/MnemonicSwift", from: "2.2.5"),
.package(url: "https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk", from: "2.2.11"),
.package(url: "https://github.com/flexa/flexa-ios.git", from: "1.0.9"),
.package(url: "https://github.com/pacu/zcash-swift-payment-uri", from: "0.1.0-beta.10"),
@ -501,6 +501,7 @@ let package = Package(
.target(
name: "OnboardingFlow",
dependencies: [
"CoordFlows",
"Generated",
"ImportWallet",
"Models",

View File

@ -26,4 +26,6 @@ public struct MnemonicClient {
public var asWords: (String) -> [String] = { _ in [] }
/// Validates whether the given mnemonic is correct
public var isValid: (String) throws -> Void
/// Suggests mnemonic words for a given prefix
public var suggestWords: (String) -> [String] = { _ in [] }
}

View File

@ -26,6 +26,9 @@ extension MnemonicClient: DependencyKey {
},
isValid: { mnemonic in
try Mnemonic.validate(mnemonic: mnemonic)
},
suggestWords: { prefix in
MnemonicLanguageType.english.words().filter { $0.hasPrefix(prefix) }
}
)
}

View File

@ -44,6 +44,7 @@ extension MnemonicClient {
return mnemonic.components(separatedBy: " ")
},
isValid: { _ in }
isValid: { _ in },
suggestWords: { _ in [] }
)
}

View File

@ -14,7 +14,8 @@ extension MnemonicClient: TestDependencyKey {
randomMnemonicWords: unimplemented("\(Self.self).randomMnemonicWords", placeholder: []),
toSeed: unimplemented("\(Self.self).toSeed", placeholder: []),
asWords: unimplemented("\(Self.self).asWords", placeholder: []),
isValid: unimplemented("\(Self.self).isValid", placeholder: {}())
isValid: unimplemented("\(Self.self).isValid", placeholder: {}()),
suggestWords: unimplemented("\(Self.self).suggestWords", placeholder: [])
)
}
@ -24,6 +25,7 @@ extension MnemonicClient {
randomMnemonicWords: { [] },
toSeed: { _ in [] },
asWords: { _ in [] },
isValid: { _ in }
isValid: { _ in },
suggestWords: { _ in [] }
)
}

View File

@ -0,0 +1,27 @@
//
// RestoreWalletCoordFlowCoordinator.swift
// Zashi
//
// Created by Lukáš Korba on 27-03-2025.
//
import ComposableArchitecture
import Generated
// Path
extension RestoreWalletCoordFlow {
public func coordinatorReduce() -> Reduce<RestoreWalletCoordFlow.State, RestoreWalletCoordFlow.Action> {
Reduce { state, action in
switch action {
// MARK: - Self
// case .path(.element(id: _, action: .requestZec(.requestTapped))):
// state.path.append(.requestZecSummary(state.requestZecState))
// return .none
default: return .none
}
}
}
}

View File

@ -0,0 +1,49 @@
//
// RestoreWalletCoordFlowStore.swift
// Zashi
//
// Created by Lukáš Korba on 27-03-2025.
//
import SwiftUI
import ComposableArchitecture
import ZcashLightClientKit
// Path
@Reducer
public struct RestoreWalletCoordFlow {
@Reducer
public enum Path {
}
@ObservableState
public struct State {
public var path = StackState<Path.State>()
// public var zecKeyboardState = ZecKeyboard.State.initial
public init() { }
}
public enum Action {
case path(StackActionOf<Path>)
// case zecKeyboard(ZecKeyboard.Action)
}
public init() { }
public var body: some Reducer<State, Action> {
coordinatorReduce()
// Scope(state: \.zecKeyboardState, action: \.zecKeyboard) {
// ZecKeyboard()
// }
Reduce { state, action in
switch action {
default: return .none
}
}
.forEach(\.path, action: \.path)
}
}

View File

@ -0,0 +1,71 @@
//
// RestoreWalletCoordFlowView.swift
// Zashi
//
// Created by Lukáš Korba on 27-03-2025.
//
import SwiftUI
import ComposableArchitecture
import UIComponents
import Generated
// Path
public struct RestoreWalletCoordFlowView: View {
@Environment(\.colorScheme) var colorScheme
@Perception.Bindable var store: StoreOf<RestoreWalletCoordFlow>
public init(store: StoreOf<RestoreWalletCoordFlow>) {
self.store = store
}
public var body: some View {
WithPerceptionTracking {
NavigationStack(path: $store.scope(state: \.path, action: \.path)) {
Text("RestoreWalletCoordFlowView")
// ZecKeyboardView(
// store:
// store.scope(
// state: \.zecKeyboardState,
// action: \.zecKeyboard
// ),
// tokenName: tokenName
// )
// .navigationBarHidden(true)
} destination: { store in
// switch store.case {
// case let .requestZec(store):
// RequestZecView(store: store, tokenName: tokenName)
// }
}
.navigationBarHidden(!store.path.isEmpty)
}
.padding(.horizontal, 4)
.applyScreenBackground()
.zashiBack()
.screenTitle(L10n.General.request)
}
}
#Preview {
NavigationView {
RestoreWalletCoordFlowView(store: RestoreWalletCoordFlow.placeholder)
}
}
// MARK: - Placeholders
extension RestoreWalletCoordFlow.State {
public static let initial = RestoreWalletCoordFlow.State()
}
extension RestoreWalletCoordFlow {
public static let placeholder = StoreOf<RestoreWalletCoordFlow>(
initialState: .initial
) {
RestoreWalletCoordFlow()
}
}

View File

@ -13,11 +13,12 @@ import Models
import ImportWallet
import SecurityWarning
import ZcashLightClientKit
import CoordFlows
@Reducer
public struct OnboardingFlow {
@ObservableState
public struct State: Equatable {
public struct State {
public enum Destination: Equatable, CaseIterable {
case createNewWallet
case importExistingWallet
@ -25,27 +26,31 @@ public struct OnboardingFlow {
public var destination: Destination?
public var walletConfig: WalletConfig
public var importWalletState: ImportWallet.State
// public var importWalletState: ImportWallet.State
public var securityWarningState: SecurityWarning.State
// Path
public var restoreWalletCoordFlowState = RestoreWalletCoordFlow.State.initial
public init(
destination: Destination? = nil,
walletConfig: WalletConfig,
importWalletState: ImportWallet.State,
// importWalletState: ImportWallet.State,
securityWarningState: SecurityWarning.State
) {
self.destination = destination
self.walletConfig = walletConfig
self.importWalletState = importWalletState
// self.importWalletState = importWalletState
self.securityWarningState = securityWarningState
}
}
public enum Action: Equatable {
public enum Action {
case createNewWallet
case importExistingWallet
case importWallet(ImportWallet.Action)
// case importWallet(ImportWallet.Action)
case onAppear
case restoreWalletCoordFlow(RestoreWalletCoordFlow.Action)
case securityWarning(SecurityWarning.Action)
case updateDestination(OnboardingFlow.State.Destination?)
}
@ -53,14 +58,18 @@ public struct OnboardingFlow {
public init() { }
public var body: some Reducer<State, Action> {
Scope(state: \.importWalletState, action: \.importWallet) {
ImportWallet()
}
// Scope(state: \.importWalletState, action: \.importWallet) {
// ImportWallet()
// }
Scope(state: \.securityWarningState, action: \.securityWarning) {
SecurityWarning()
}
Scope(state: \.restoreWalletCoordFlowState, action: \.restoreWalletCoordFlow) {
RestoreWalletCoordFlow()
}
Reduce { state, action in
switch action {
case .onAppear:
@ -78,11 +87,14 @@ public struct OnboardingFlow {
state.destination = .importExistingWallet
return .none
case .importWallet:
return .none
// case .importWallet:
// return .none
case .securityWarning:
return .none
case .restoreWalletCoordFlow:
return .none
}
}
}

View File

@ -12,6 +12,7 @@ import ImportWallet
import SecurityWarning
import ZcashLightClientKit
import UIComponents
import CoordFlows
public struct PlainOnboardingView: View {
@Perception.Bindable var store: StoreOf<OnboardingFlow>
@ -49,17 +50,17 @@ public struct PlainOnboardingView: View {
}
.padding(.bottom, 24)
}
.navigationLinkEmpty(
isActive: store.bindingFor(.importExistingWallet),
destination: {
ImportWalletView(
store: store.scope(
state: \.importWalletState,
action: \.importWallet
)
)
}
)
// .navigationLinkEmpty(
// isActive: store.bindingFor(.importExistingWallet),
// destination: {
// ImportWalletView(
// store: store.scope(
// state: \.importWalletState,
// action: \.importWallet
// )
// )
// }
// )
.navigationLinkEmpty(
isActive: store.bindingFor(.createNewWallet),
destination: {
@ -71,6 +72,17 @@ public struct PlainOnboardingView: View {
)
}
)
.navigationLinkEmpty(
isActive: store.bindingFor(.importExistingWallet),
destination: {
RestoreWalletCoordFlowView(
store: store.scope(
state: \.restoreWalletCoordFlowState,
action: \.restoreWalletCoordFlow
)
)
}
)
}
.navigationBarTitleDisplayMode(.inline)
.screenHorizontalPadding()
@ -84,7 +96,6 @@ public struct PlainOnboardingView: View {
Store(
initialState: OnboardingFlow.State(
walletConfig: .initial,
importWalletState: .initial,
securityWarningState: .initial
)
) {
@ -110,7 +121,6 @@ extension OnboardingFlow.State {
public static var initial: Self {
.init(
walletConfig: .initial,
importWalletState: .initial,
securityWarningState: .initial
)
}

View File

@ -60,9 +60,10 @@ extension Root {
|| (state.destinationState.destination == .deeplinkWarning && destination == .home) else {
return .none
}
guard state.destinationState.destination != .onboarding && state.onboardingState.destination != .importExistingWallet && state.onboardingState.importWalletState.destination != .restoreInfo else {
return .none
}
// RESTORE
// guard state.destinationState.destination != .onboarding && state.onboardingState.destination != .importExistingWallet && state.onboardingState.importWalletState.destination != .restoreInfo else {
// return .none
// }
state.destinationState.destination = destination
return .none

View File

@ -555,10 +555,12 @@ extension Root {
}
if state.appInitializationState == .keysMissing && state.onboardingState.destination == .importExistingWallet {
state.appInitializationState = .uninitialized
return .concatenate(
.cancel(id: SynchronizerCancelId),
.send(.onboarding(.importWallet(.updateDestination(.birthday))))
)
// RESTORE
return .cancel(id: SynchronizerCancelId)
// return .concatenate(
// .cancel(id: SynchronizerCancelId),
// .send(.onboarding(.importWallet(.updateDestination(.birthday))))
// )
} else if state.appInitializationState == .keysMissing && state.onboardingState.destination == .createNewWallet {
state.appInitializationState = .uninitialized
return .concatenate(
@ -645,39 +647,43 @@ extension Root {
await send(.onboarding(.importExistingWallet))
}
case .onboarding(.importWallet(.nextTapped)):
if state.appInitializationState == .keysMissing {
let seedPhrase = state.onboardingState.importWalletState.importedSeedPhrase
return .run { send in
do {
let seedBytes = try mnemonic.toSeed(seedPhrase)
let result = try await sdkSynchronizer.isSeedRelevantToAnyDerivedAccount(seedBytes)
await send(.initialization(.seedValidationResult(result)))
} catch {
await send(.initialization(.seedValidationResult(false)))
}
}
} else {
state.onboardingState.importWalletState.destination = .birthday
return .none
}
// RESTORE
// case .onboarding(.importWallet(.nextTapped)):
// if state.appInitializationState == .keysMissing {
// let seedPhrase = state.onboardingState.importWalletState.importedSeedPhrase
// return .run { send in
// do {
// let seedBytes = try mnemonic.toSeed(seedPhrase)
// let result = try await sdkSynchronizer.isSeedRelevantToAnyDerivedAccount(seedBytes)
// await send(.initialization(.seedValidationResult(result)))
// } catch {
// await send(.initialization(.seedValidationResult(false)))
// }
// }
// } else {
// state.onboardingState.importWalletState.destination = .birthday
// return .none
// }
case .onboarding(.importWallet(.restoreInfo(.gotItTapped))):
state.destinationState.destination = .home
return .none
// RESTORE
// case .onboarding(.importWallet(.restoreInfo(.gotItTapped))):
// state.destinationState.destination = .home
// return .none
case .onboarding(.importWallet(.initializeSDK)):
state.isRestoringWallet = true
userDefaults.setValue(true, Constants.udIsRestoringWallet)
state.$walletStatus.withLock { $0 = .restoring }
return .concatenate(
.send(.initialization(.initializeSDK(.restoreWallet))),
.send(.initialization(.checkBackupPhraseValidation))
)
// RESTORE
// case .onboarding(.importWallet(.initializeSDK)):
// state.isRestoringWallet = true
// userDefaults.setValue(true, Constants.udIsRestoringWallet)
// state.$walletStatus.withLock { $0 = .restoring }
// return .concatenate(
// .send(.initialization(.initializeSDK(.restoreWallet))),
// .send(.initialization(.checkBackupPhraseValidation))
// )
case .initialization(.seedValidationResult(let validSeed)):
if validSeed {
state.onboardingState.importWalletState.destination = .birthday
// RESTORE
// state.onboardingState.importWalletState.destination = .birthday
} else {
state.alert = AlertState.differentSeed()
}

View File

@ -131,8 +131,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/MnemonicSwift",
"state" : {
"revision" : "716a2c32ac2bbd8a1499ac834077df42b75edc85",
"version" : "2.2.4"
"revision" : "2d7f3e76e904621e111efada6cc9575f39543bb2",
"version" : "2.2.5"
}
},
{