[285] Advanced Routing: setting a route may vary depending on the originating context (#325)

- context based routing in Home Screen
- mnemonic refactor
- backup phrase display from the profile screen
This commit is contained in:
Lukas Korba 2022-05-24 07:30:16 +02:00 committed by GitHub
parent 339965b9e9
commit b753efbc5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 142 additions and 62 deletions

View File

@ -23,10 +23,19 @@ struct AppState: Equatable {
var onboardingState: OnboardingFlowState
var phraseValidationState: RecoveryPhraseValidationFlowState
var phraseDisplayState: RecoveryPhraseDisplayState
var route: Route = .welcome
var prevRoute: Route?
var internalRoute: Route = .welcome
var sandboxState: SandboxState
var storedWallet: StoredWallet?
var welcomeState: WelcomeState
var route: Route {
get { internalRoute }
set {
prevRoute = internalRoute
internalRoute = newValue
}
}
}
// MARK: - Action
@ -53,7 +62,7 @@ enum AppAction: Equatable {
struct AppEnvironment {
let SDKSynchronizer: WrappedSDKSynchronizer
let databaseFiles: WrappedDatabaseFiles
let mnemonicSeedPhraseProvider: WrappedMnemonic
let mnemonic: WrappedMnemonic
let scheduler: AnySchedulerOf<DispatchQueue>
let walletStorage: WrappedWalletStorage
let derivationTool: WrappedDerivationTool
@ -64,7 +73,7 @@ extension AppEnvironment {
static let live = AppEnvironment(
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
databaseFiles: .live(),
mnemonicSeedPhraseProvider: .live,
mnemonic: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(),
@ -74,7 +83,7 @@ extension AppEnvironment {
static let mock = AppEnvironment(
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
databaseFiles: .live(),
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(derivationTool: DerivationTool(networkType: .mainnet)),
@ -152,7 +161,7 @@ extension AppReducer {
return .none
}
try environment.mnemonicSeedPhraseProvider.isValid(storedWallet.seedPhrase)
try environment.mnemonic.isValid(storedWallet.seedPhrase)
let birthday = state.storedWallet?.birthday ?? environment.zcashSDKEnvironment.defaultBirthday
@ -180,7 +189,7 @@ extension AppReducer {
if !storedWallet.hasUserPassedPhraseBackupTest {
do {
let phraseWords = try environment.mnemonicSeedPhraseProvider.asWords(storedWallet.seedPhrase)
let phraseWords = try environment.mnemonic.asWords(storedWallet.seedPhrase)
let recoveryPhrase = RecoveryPhrase(words: phraseWords)
state.phraseDisplayState.phrase = recoveryPhrase
@ -201,14 +210,14 @@ extension AppReducer {
case .createNewWallet:
do {
// get the random english mnemonic
let randomPhrase = try environment.mnemonicSeedPhraseProvider.randomMnemonic()
let randomPhrase = try environment.mnemonic.randomMnemonic()
let birthday = try environment.zcashSDKEnvironment.lightWalletService.latestBlockHeight()
// store the wallet to the keychain
try environment.walletStorage.importWallet(randomPhrase, birthday, .english, false)
// start the backup phrase validation test
let randomPhraseWords = try environment.mnemonicSeedPhraseProvider.asWords(randomPhrase)
let randomPhraseWords = try environment.mnemonic.asWords(randomPhrase)
let recoveryPhrase = RecoveryPhrase(words: randomPhraseWords)
state.phraseDisplayState.phrase = recoveryPhrase
state.phraseValidationState = RecoveryPhraseValidationFlowState.random(phrase: recoveryPhrase)
@ -258,7 +267,7 @@ extension AppReducer {
}
}
private static let routeReducer = AppReducer { state, action, environment in
private static let routeReducer = AppReducer { state, action, _ in
switch action {
case let .updateRoute(route):
state.route = route
@ -277,14 +286,14 @@ extension AppReducer {
state.route = .phraseDisplay
case .phraseDisplay(.finishedPressed):
// TODO: Advanced Routing: setting a route may vary depending on the originating context #285
// see https://github.com/zcash/secant-ios-wallet/issues/285
if let storedWallet = try? environment.walletStorage.exportWallet(),
storedWallet.hasUserPassedPhraseBackupTest {
state.route = .home
} else {
// user is still supposed to do the backup phrase validation test
if state.prevRoute == .welcome || state.prevRoute == .onboarding {
state.route = .phraseValidation
}
// user wanted to see the backup phrase once again (at validation finished screen)
if state.prevRoute == .phraseValidation {
state.route = .home
}
/// Default is meaningful here because there's `appReducer` handling actions and this reducer is handling only routes. We don't here plenty of unused cases.
default:
@ -299,7 +308,7 @@ extension AppReducer {
action: /AppAction.home,
environment: { environment in
HomeEnvironment(
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
mnemonic: environment.mnemonic,
scheduler: environment.scheduler,
walletStorage: environment.walletStorage,
derivationTool: environment.derivationTool,
@ -313,7 +322,7 @@ extension AppReducer {
action: /AppAction.onboarding,
environment: { environment in
OnboardingFlowEnvironment(
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
mnemonic: environment.mnemonic,
walletStorage: environment.walletStorage,
zcashSDKEnvironment: environment.zcashSDKEnvironment
)
@ -391,7 +400,7 @@ extension AppReducer {
with environment: AppEnvironment
) throws -> Initializer {
do {
let seedBytes = try environment.mnemonicSeedPhraseProvider.toSeed(seedPhrase)
let seedBytes = try environment.mnemonic.toSeed(seedPhrase)
let viewingKeys = try environment.derivationTool.deriveUnifiedViewingKeysFromSeed(seedBytes, 1)
let network = environment.zcashSDKEnvironment.network

View File

@ -51,7 +51,7 @@ enum HomeAction: Equatable {
// MARK: Environment
struct HomeEnvironment {
let mnemonicSeedPhraseProvider: WrappedMnemonic
let mnemonic: WrappedMnemonic
let scheduler: AnySchedulerOf<DispatchQueue>
let walletStorage: WrappedWalletStorage
let derivationTool: WrappedDerivationTool
@ -67,7 +67,8 @@ extension HomeReducer {
[
homeReducer,
historyReducer,
sendReducer
sendReducer,
profileReducer
]
)
.debug()
@ -168,7 +169,7 @@ extension HomeReducer {
action: /HomeAction.send,
environment: { environment in
SendFlowEnvironment(
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
mnemonic: environment.mnemonic,
scheduler: environment.scheduler,
walletStorage: environment.walletStorage,
derivationTool: environment.derivationTool,
@ -176,6 +177,17 @@ extension HomeReducer {
)
}
)
private static let profileReducer: HomeReducer = ProfileReducer.default.pullback(
state: \HomeState.profileState,
action: /HomeAction.profile,
environment: { environment in
ProfileEnvironment(
mnemonic: environment.mnemonic,
walletStorage: environment.walletStorage
)
}
)
}
// MARK: - Store
@ -261,7 +273,7 @@ extension HomeStore {
initialState: .placeholder,
reducer: .default.debug(),
environment: HomeEnvironment(
mnemonicSeedPhraseProvider: .live,
mnemonic: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(),

View File

@ -33,20 +33,20 @@ enum ImportWalletAction: Equatable, BindableAction {
// MARK: - Environment
struct ImportWalletEnvironment {
let mnemonicSeedPhraseProvider: WrappedMnemonic
let mnemonic: WrappedMnemonic
let walletStorage: WrappedWalletStorage
let zcashSDKEnvironment: ZCashSDKEnvironment
}
extension ImportWalletEnvironment {
static let live = ImportWalletEnvironment(
mnemonicSeedPhraseProvider: .live,
mnemonic: .live,
walletStorage: .live(),
zcashSDKEnvironment: .mainnet
)
static let demo = ImportWalletEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
walletStorage: .live(),
zcashSDKEnvironment: .testnet
)
@ -67,7 +67,7 @@ extension ImportWalletReducer {
case .importRecoveryPhrase:
do {
// validate the seed
try environment.mnemonicSeedPhraseProvider.isValid(state.importedSeedPhrase)
try environment.mnemonic.isValid(state.importedSeedPhrase)
// store it to the keychain
let birthday = environment.zcashSDKEnvironment.defaultBirthday

View File

@ -99,20 +99,20 @@ enum OnboardingFlowAction: Equatable {
// MARK: - Environment
struct OnboardingFlowEnvironment {
let mnemonicSeedPhraseProvider: WrappedMnemonic
let mnemonic: WrappedMnemonic
let walletStorage: WrappedWalletStorage
let zcashSDKEnvironment: ZCashSDKEnvironment
}
extension OnboardingFlowEnvironment {
static let live = OnboardingFlowEnvironment(
mnemonicSeedPhraseProvider: .live,
mnemonic: .live,
walletStorage: .live(),
zcashSDKEnvironment: .mainnet
)
static let demo = OnboardingFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
walletStorage: .live(),
zcashSDKEnvironment: .testnet
)
@ -173,7 +173,7 @@ extension OnboardingFlowReducer {
action: /OnboardingFlowAction.importWallet,
environment: { environment in
ImportWalletEnvironment(
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
mnemonic: environment.mnemonic,
walletStorage: environment.walletStorage,
zcashSDKEnvironment: environment.zcashSDKEnvironment
)

View File

@ -9,33 +9,72 @@ typealias ProfileViewStore = ViewStore<ProfileState, ProfileAction>
struct ProfileState: Equatable {
enum Route {
case phraseDisplay
case settings
case walletInfo
}
var walletInfoState: WalletInfoState
var settingsState: SettingsState
var phraseDisplayState: RecoveryPhraseDisplayState
var route: Route?
var settingsState: SettingsState
var walletInfoState: WalletInfoState
}
// MARK: - Action
enum ProfileAction: Equatable {
case phraseDisplay(RecoveryPhraseDisplayAction)
case updateRoute(ProfileState.Route?)
}
// MARK: - Environment
struct ProfileEnvironment { }
struct ProfileEnvironment {
let mnemonic: WrappedMnemonic
let walletStorage: WrappedWalletStorage
}
extension ProfileEnvironment {
static let live = ProfileEnvironment(
mnemonic: .live,
walletStorage: .live()
)
static let mock = ProfileEnvironment(
mnemonic: .mock,
walletStorage: .live()
)
}
// MARK: - Reducer
extension ProfileReducer {
static let `default` = ProfileReducer { state, action, _ in
static let `default` = ProfileReducer { state, action, environment in
switch action {
case .updateRoute(.phraseDisplay):
do {
let storedWallet = try environment.walletStorage.exportWallet()
let phraseWords = try environment.mnemonic.asWords(storedWallet.seedPhrase)
let recoveryPhrase = RecoveryPhrase(words: phraseWords)
state.phraseDisplayState.phrase = recoveryPhrase
state.route = .phraseDisplay
} catch {
// TODO: - merge with issue 201 (https://github.com/zcash/secant-ios-wallet/issues/201) and its Error States
return .none
}
return .none
case let .updateRoute(route):
state.route = route
return .none
case .phraseDisplay(.finishedPressed):
state.route = nil
return .none
case .phraseDisplay:
return .none
}
}
}
@ -63,6 +102,13 @@ extension ProfileViewStore {
embed: { $0 ? .settings : nil }
)
}
var bindingForPhraseDisplay: Binding<Bool> {
self.routeBinding.map(
extract: { $0 == .phraseDisplay },
embed: { $0 ? .phraseDisplay : nil }
)
}
}
// MARK: Placeholders
@ -70,9 +116,10 @@ extension ProfileViewStore {
extension ProfileState {
static var placeholder: Self {
.init(
walletInfoState: .init(),
phraseDisplayState: .init(),
route: nil,
settingsState: .init(),
route: nil
walletInfoState: .init()
)
}
}

View File

@ -15,6 +15,19 @@ struct ProfileView: View {
}
)
Text("Show me backup phrase")
.navigationLink(
isActive: viewStore.bindingForPhraseDisplay,
destination: {
RecoveryPhraseDisplayView(
store: store.scope(
state: \.phraseDisplayState,
action: ProfileAction.phraseDisplay
)
)
}
)
Text("Go To Settings")
.navigationLink(
isActive: viewStore.bindingForSettings,
@ -36,11 +49,12 @@ struct ProfileView_Previews: PreviewProvider {
ProfileView(
store: .init(
initialState: .init(
walletInfoState: .init(),
settingsState: .init()
phraseDisplayState: .init(),
settingsState: .init(),
walletInfoState: .init()
),
reducer: .default,
environment: .init()
environment: .live
)
)
}

View File

@ -60,9 +60,7 @@ extension SandboxReducer {
.pullback(
state: \.profileState,
action: /SandboxAction.profile,
environment: { _ in
return ProfileEnvironment()
}
environment: { _ in ProfileEnvironment.live }
)
.run(&state, action, ())
case .reset:

View File

@ -32,7 +32,7 @@ struct SandboxView: View {
)
.debug(),
environment: SendFlowEnvironment(
mnemonicSeedPhraseProvider: .live,
mnemonic: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(),

View File

@ -76,7 +76,7 @@ enum SendFlowAction: Equatable {
// MARK: - Environment
struct SendFlowEnvironment {
let mnemonicSeedPhraseProvider: WrappedMnemonic
let mnemonic: WrappedMnemonic
let scheduler: AnySchedulerOf<DispatchQueue>
let walletStorage: WrappedWalletStorage
let derivationTool: WrappedDerivationTool
@ -124,7 +124,7 @@ extension SendFlowReducer {
do {
let storedWallet = try environment.walletStorage.exportWallet()
let seedBytes = try environment.mnemonicSeedPhraseProvider.toSeed(storedWallet.seedPhrase)
let seedBytes = try environment.mnemonic.toSeed(storedWallet.seedPhrase)
guard let spendingKey = try environment.derivationTool.deriveSpendingKeys(seedBytes, 1).first else {
return Effect(value: .updateRoute(.failure))
}
@ -309,7 +309,7 @@ extension SendFlowStore {
),
reducer: .default,
environment: SendFlowEnvironment(
mnemonicSeedPhraseProvider: .live,
mnemonic: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(),

View File

@ -41,7 +41,7 @@ struct SendFLowView_Previews: PreviewProvider {
),
reducer: .default,
environment: SendFlowEnvironment(
mnemonicSeedPhraseProvider: .live,
mnemonic: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(),

View File

@ -15,7 +15,7 @@ class AppReducerTests: XCTestCase {
let testEnvironment = AppEnvironment(
SDKSynchronizer: TestWrappedSDKSynchronizer(),
databaseFiles: .throwing,
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .throwing,
derivationTool: .live(),
@ -26,7 +26,7 @@ class AppReducerTests: XCTestCase {
let uninitializedEnvironment = AppEnvironment(
SDKSynchronizer: TestWrappedSDKSynchronizer(),
databaseFiles: .throwing,
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
walletStorage: .throwing,
derivationTool: .live(),
@ -48,7 +48,7 @@ class AppReducerTests: XCTestCase {
let keysMissingEnvironment = AppEnvironment(
SDKSynchronizer: TestWrappedSDKSynchronizer(),
databaseFiles: .live(databaseFiles: DatabaseFiles(fileManager: wfmMock)),
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: Self.testScheduler.eraseToAnyScheduler(),
walletStorage: .throwing,
derivationTool: .live(),
@ -70,7 +70,7 @@ class AppReducerTests: XCTestCase {
let keysMissingEnvironment = AppEnvironment(
SDKSynchronizer: TestWrappedSDKSynchronizer(),
databaseFiles: .live(databaseFiles: DatabaseFiles(fileManager: wfmMock)),
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: Self.testScheduler.eraseToAnyScheduler(),
walletStorage: .throwing,
derivationTool: .live(),

View File

@ -32,7 +32,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: storage),
derivationTool: .live(),
@ -88,7 +88,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: storage),
derivationTool: .live(),
@ -127,7 +127,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
@ -169,7 +169,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
@ -191,7 +191,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
@ -235,7 +235,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
@ -273,7 +273,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
@ -321,7 +321,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
@ -366,7 +366,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
@ -411,7 +411,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
@ -456,7 +456,7 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),