- OnboardingFlow migrated to ReducerProtocol - unit and snapshot tests fixed
This commit is contained in:
parent
7f6c104d28
commit
d758cf4928
|
@ -7,9 +7,14 @@ typealias AppStore = Store<AppState, AppAction>
|
|||
typealias AppViewStore = ViewStore<AppState, AppAction>
|
||||
|
||||
typealias AnyRecoveryPhraseDisplayReducer = AnyReducer<RecoveryPhraseDisplayReducer.State, RecoveryPhraseDisplayReducer.Action, AppEnvironment>
|
||||
typealias AnyRecoveryPhraseValidationFlowReducer = AnyReducer<RecoveryPhraseValidationFlowReducer.State, RecoveryPhraseValidationFlowReducer.Action, AppEnvironment>
|
||||
typealias AnyRecoveryPhraseValidationFlowReducer = AnyReducer<
|
||||
RecoveryPhraseValidationFlowReducer.State,
|
||||
RecoveryPhraseValidationFlowReducer.Action,
|
||||
AppEnvironment
|
||||
>
|
||||
typealias AnyWelcomeReducer = AnyReducer<WelcomeReducer.State, WelcomeReducer.Action, AppEnvironment>
|
||||
typealias AnySandboxReducer = AnyReducer<SandboxReducer.State, SandboxReducer.Action, AppEnvironment>
|
||||
typealias AnyOnboardingFlowReducer = AnyReducer<OnboardingFlowReducer.State, OnboardingFlowReducer.Action, AppEnvironment>
|
||||
|
||||
// MARK: - State
|
||||
|
||||
|
@ -26,7 +31,7 @@ struct AppState: Equatable {
|
|||
|
||||
var appInitializationState: InitializationState = .uninitialized
|
||||
var homeState: HomeState
|
||||
var onboardingState: OnboardingFlowState
|
||||
var onboardingState: OnboardingFlowReducer.State
|
||||
var phraseValidationState: RecoveryPhraseValidationFlowReducer.State
|
||||
var phraseDisplayState: RecoveryPhraseDisplayReducer.State
|
||||
var prevRoute: Route?
|
||||
|
@ -57,7 +62,7 @@ enum AppAction: Equatable {
|
|||
case home(HomeAction)
|
||||
case initializeSDK
|
||||
case nukeWallet
|
||||
case onboarding(OnboardingFlowAction)
|
||||
case onboarding(OnboardingFlowReducer.Action)
|
||||
case phraseDisplay(RecoveryPhraseDisplayReducer.Action)
|
||||
case phraseValidation(RecoveryPhraseValidationFlowReducer.Action)
|
||||
case respondToWalletInitializationState(InitializationState)
|
||||
|
@ -382,18 +387,15 @@ extension AppReducer {
|
|||
}
|
||||
)
|
||||
|
||||
private static let onboardingReducer: AppReducer = OnboardingFlowReducer.default.pullback(
|
||||
private static let onboardingReducer: AppReducer = AnyOnboardingFlowReducer { _ in
|
||||
OnboardingFlowReducer()
|
||||
}
|
||||
.pullback(
|
||||
state: \AppState.onboardingState,
|
||||
action: /AppAction.onboarding,
|
||||
environment: { environment in
|
||||
OnboardingFlowEnvironment(
|
||||
mnemonic: environment.mnemonic,
|
||||
walletStorage: environment.walletStorage,
|
||||
zcashSDKEnvironment: environment.zcashSDKEnvironment
|
||||
)
|
||||
}
|
||||
environment: { $0 }
|
||||
)
|
||||
|
||||
|
||||
private static let phraseValidationReducer: AppReducer = AnyRecoveryPhraseValidationFlowReducer { _ in
|
||||
RecoveryPhraseValidationFlowReducer()
|
||||
}
|
||||
|
|
|
@ -9,49 +9,102 @@ import Foundation
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
typealias OnboardingFlowReducer = Reducer<OnboardingFlowState, OnboardingFlowAction, OnboardingFlowEnvironment>
|
||||
typealias OnboardingFlowStore = Store<OnboardingFlowState, OnboardingFlowAction>
|
||||
typealias OnboardingFlowViewStore = ViewStore<OnboardingFlowState, OnboardingFlowAction>
|
||||
typealias OnboardingFlowStore = Store<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>
|
||||
typealias OnboardingFlowViewStore = ViewStore<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>
|
||||
|
||||
typealias AnyImportWalletReducer = AnyReducer<ImportWalletReducer.State, ImportWalletReducer.Action, OnboardingFlowEnvironment>
|
||||
struct OnboardingFlowReducer: ReducerProtocol {
|
||||
struct State: Equatable {
|
||||
enum Route: Equatable, CaseIterable {
|
||||
case createNewWallet
|
||||
case importExistingWallet
|
||||
}
|
||||
|
||||
struct Step: Equatable, Identifiable {
|
||||
let id: UUID
|
||||
let title: LocalizedStringKey
|
||||
let description: LocalizedStringKey
|
||||
let background: Image
|
||||
let badge: Badge
|
||||
}
|
||||
|
||||
// MARK: - State
|
||||
var steps: IdentifiedArrayOf<Step> = Self.onboardingSteps
|
||||
var index = 0
|
||||
var skippedAtindex: Int?
|
||||
var route: Route?
|
||||
|
||||
struct OnboardingFlowState: Equatable {
|
||||
enum Route: Equatable, CaseIterable {
|
||||
var currentStep: Step { steps[index] }
|
||||
var isFinalStep: Bool { steps.count == index + 1 }
|
||||
var isInitialStep: Bool { index == 0 }
|
||||
var progress: Int { ((index + 1) * 100) / (steps.count) }
|
||||
var offset: CGFloat {
|
||||
let maxOffset = CGFloat(-60)
|
||||
let stepOffset = CGFloat(maxOffset / CGFloat(steps.count - 1))
|
||||
guard index != 0 else { return .zero }
|
||||
return stepOffset * CGFloat(index)
|
||||
}
|
||||
|
||||
/// Import Wallet
|
||||
var importWalletState: ImportWalletReducer.State
|
||||
}
|
||||
|
||||
enum Action: Equatable {
|
||||
case next
|
||||
case back
|
||||
case skip
|
||||
case updateRoute(OnboardingFlowReducer.State.Route?)
|
||||
case createNewWallet
|
||||
case importExistingWallet
|
||||
case importWallet(ImportWalletReducer.Action)
|
||||
}
|
||||
|
||||
struct Step: Equatable, Identifiable {
|
||||
let id: UUID
|
||||
let title: LocalizedStringKey
|
||||
let description: LocalizedStringKey
|
||||
let background: Image
|
||||
let badge: Badge
|
||||
}
|
||||
var body: some ReducerProtocol<State, Action> {
|
||||
Scope(state: \.importWalletState, action: /Action.importWallet) {
|
||||
ImportWalletReducer()
|
||||
}
|
||||
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .back:
|
||||
guard state.index > 0 else { return .none }
|
||||
if let skippedFrom = state.skippedAtindex {
|
||||
state.index = skippedFrom
|
||||
state.skippedAtindex = nil
|
||||
} else {
|
||||
state.index -= 1
|
||||
}
|
||||
return .none
|
||||
|
||||
case .next:
|
||||
guard state.index < state.steps.count - 1 else { return .none }
|
||||
state.index += 1
|
||||
return .none
|
||||
|
||||
case .skip:
|
||||
guard state.skippedAtindex == nil else { return .none }
|
||||
state.skippedAtindex = state.index
|
||||
state.index = state.steps.count - 1
|
||||
return .none
|
||||
|
||||
case .updateRoute(let route):
|
||||
state.route = route
|
||||
return .none
|
||||
|
||||
var steps: IdentifiedArrayOf<Step> = Self.onboardingSteps
|
||||
var index = 0
|
||||
var skippedAtindex: Int?
|
||||
var route: Route?
|
||||
case .createNewWallet:
|
||||
state.route = .createNewWallet
|
||||
return .none
|
||||
|
||||
var currentStep: Step { steps[index] }
|
||||
var isFinalStep: Bool { steps.count == index + 1 }
|
||||
var isInitialStep: Bool { index == 0 }
|
||||
var progress: Int { ((index + 1) * 100) / (steps.count) }
|
||||
var offset: CGFloat {
|
||||
let maxOffset = CGFloat(-60)
|
||||
let stepOffset = CGFloat(maxOffset / CGFloat(steps.count - 1))
|
||||
guard index != 0 else { return .zero }
|
||||
return stepOffset * CGFloat(index)
|
||||
case .importExistingWallet:
|
||||
state.route = .importExistingWallet
|
||||
return .none
|
||||
|
||||
case .importWallet:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Import Wallet
|
||||
var importWalletState: ImportWalletReducer.State
|
||||
}
|
||||
|
||||
extension OnboardingFlowState {
|
||||
extension OnboardingFlowReducer.State {
|
||||
static let onboardingSteps = IdentifiedArray(
|
||||
uniqueElements: [
|
||||
Step(
|
||||
|
@ -86,104 +139,10 @@ extension OnboardingFlowState {
|
|||
)
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
|
||||
enum OnboardingFlowAction: Equatable {
|
||||
case next
|
||||
case back
|
||||
case skip
|
||||
case updateRoute(OnboardingFlowState.Route?)
|
||||
case createNewWallet
|
||||
case importExistingWallet
|
||||
case importWallet(ImportWalletReducer.Action)
|
||||
}
|
||||
|
||||
// MARK: - Environment
|
||||
|
||||
struct OnboardingFlowEnvironment {
|
||||
let mnemonic: WrappedMnemonic
|
||||
let walletStorage: WrappedWalletStorage
|
||||
let zcashSDKEnvironment: ZCashSDKEnvironment
|
||||
}
|
||||
|
||||
extension OnboardingFlowEnvironment {
|
||||
static let live = OnboardingFlowEnvironment(
|
||||
mnemonic: .live,
|
||||
walletStorage: .live(),
|
||||
zcashSDKEnvironment: .mainnet
|
||||
)
|
||||
|
||||
static let demo = OnboardingFlowEnvironment(
|
||||
mnemonic: .mock,
|
||||
walletStorage: .live(),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Reducer
|
||||
|
||||
extension OnboardingFlowReducer {
|
||||
static let `default` = OnboardingFlowReducer.combine(
|
||||
[
|
||||
onboardingReducer,
|
||||
importWalletReducer
|
||||
]
|
||||
)
|
||||
|
||||
private static let onboardingReducer = OnboardingFlowReducer { state, action, _ in
|
||||
switch action {
|
||||
case .back:
|
||||
guard state.index > 0 else { return .none }
|
||||
if let skippedFrom = state.skippedAtindex {
|
||||
state.index = skippedFrom
|
||||
state.skippedAtindex = nil
|
||||
} else {
|
||||
state.index -= 1
|
||||
}
|
||||
return .none
|
||||
|
||||
case .next:
|
||||
guard state.index < state.steps.count - 1 else { return .none }
|
||||
state.index += 1
|
||||
return .none
|
||||
|
||||
case .skip:
|
||||
guard state.skippedAtindex == nil else { return .none }
|
||||
state.skippedAtindex = state.index
|
||||
state.index = state.steps.count - 1
|
||||
return .none
|
||||
|
||||
case .updateRoute(let route):
|
||||
state.route = route
|
||||
return .none
|
||||
|
||||
case .createNewWallet:
|
||||
state.route = .createNewWallet
|
||||
return .none
|
||||
|
||||
case .importExistingWallet:
|
||||
state.route = .importExistingWallet
|
||||
return .none
|
||||
|
||||
case .importWallet(let route):
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
private static let importWalletReducer: OnboardingFlowReducer = AnyImportWalletReducer { _ in
|
||||
ImportWalletReducer()
|
||||
}
|
||||
.pullback(
|
||||
state: \OnboardingFlowState.importWalletState,
|
||||
action: /OnboardingFlowAction.importWallet,
|
||||
environment: { $0 }
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - ViewStore
|
||||
|
||||
extension OnboardingFlowViewStore {
|
||||
func bindingForRoute(_ route: OnboardingFlowState.Route) -> Binding<Bool> {
|
||||
func bindingForRoute(_ route: OnboardingFlowReducer.State.Route) -> Binding<Bool> {
|
||||
self.binding(
|
||||
get: { $0.route == route },
|
||||
send: { isActive in
|
||||
|
|
|
@ -9,7 +9,7 @@ import SwiftUI
|
|||
import ComposableArchitecture
|
||||
|
||||
struct OnboardingScreen: View {
|
||||
let store: Store<OnboardingFlowState, OnboardingFlowAction>
|
||||
let store: Store<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
|
@ -52,11 +52,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
|
|||
static var previews: some View {
|
||||
OnboardingScreen(
|
||||
store: Store(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: (.demo)
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
)
|
||||
.preferredColorScheme(.light)
|
||||
|
@ -64,11 +63,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
|
|||
|
||||
OnboardingScreen(
|
||||
store: Store(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: (.demo)
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
)
|
||||
.preferredColorScheme(.light)
|
||||
|
@ -76,11 +74,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
|
|||
|
||||
OnboardingScreen(
|
||||
store: Store(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: (.demo)
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
)
|
||||
.preferredColorScheme(.light)
|
||||
|
@ -88,11 +85,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
|
|||
|
||||
OnboardingScreen(
|
||||
store: Store(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: (.demo)
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
)
|
||||
.preferredColorScheme(.dark)
|
||||
|
@ -100,11 +96,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
|
|||
|
||||
OnboardingScreen(
|
||||
store: Store(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: (.demo)
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
)
|
||||
.preferredColorScheme(.dark)
|
||||
|
@ -112,11 +107,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
|
|||
|
||||
OnboardingScreen(
|
||||
store: Store(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: (.demo)
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
)
|
||||
.preferredColorScheme(.dark)
|
||||
|
|
|
@ -9,7 +9,7 @@ import SwiftUI
|
|||
import ComposableArchitecture
|
||||
|
||||
struct OnboardingContentView: View {
|
||||
let store: Store<OnboardingFlowState, OnboardingFlowAction>
|
||||
let store: Store<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>
|
||||
let width: Double
|
||||
let height: Double
|
||||
|
||||
|
@ -76,12 +76,11 @@ extension OnboardingContentView {
|
|||
struct OnboardingContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let store = Store(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
index: 0,
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: (.demo)
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
|
||||
OnboardingContentView_Previews.example(store)
|
||||
|
@ -98,7 +97,7 @@ struct OnboardingContentView_Previews: PreviewProvider {
|
|||
// MARK: - Previews
|
||||
|
||||
extension OnboardingContentView_Previews {
|
||||
static func example(_ store: Store<OnboardingFlowState, OnboardingFlowAction>) -> some View {
|
||||
static func example(_ store: Store<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>) -> some View {
|
||||
GeometryReader { proxy in
|
||||
ZStack {
|
||||
OnboardingHeaderView(
|
||||
|
|
|
@ -9,7 +9,7 @@ import SwiftUI
|
|||
import ComposableArchitecture
|
||||
|
||||
struct OnboardingFooterView: View {
|
||||
let store: Store<OnboardingFlowState, OnboardingFlowAction>
|
||||
let store: Store<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>
|
||||
let animationDuration: CGFloat = 0.8
|
||||
|
||||
var body: some View {
|
||||
|
@ -52,7 +52,7 @@ struct OnboardingFooterView: View {
|
|||
ImportWalletView(
|
||||
store: store.scope(
|
||||
state: \.importWalletState,
|
||||
action: OnboardingFlowAction.importWallet
|
||||
action: OnboardingFlowReducer.Action.importWallet
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -81,13 +81,12 @@ extension View {
|
|||
|
||||
struct OnboardingFooterView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let store = Store<OnboardingFlowState, OnboardingFlowAction>(
|
||||
initialState: OnboardingFlowState(
|
||||
let store = Store<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
index: 3,
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: (.demo)
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
|
||||
Group {
|
||||
|
|
|
@ -60,13 +60,12 @@ struct OnboardingHeaderView: View {
|
|||
|
||||
struct OnboardingHeaderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let store = Store<OnboardingFlowState, OnboardingFlowAction>(
|
||||
initialState: OnboardingFlowState(
|
||||
let store = Store<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
index: 0,
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: (.demo)
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
|
||||
OnboardingHeaderView(
|
||||
|
|
|
@ -12,11 +12,10 @@ import ComposableArchitecture
|
|||
class OnboardingStoreTests: XCTestCase {
|
||||
func testIncrementingOnboarding() {
|
||||
let store = TestStore(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: .demo
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
|
||||
store.send(.next) {
|
||||
|
@ -52,12 +51,11 @@ class OnboardingStoreTests: XCTestCase {
|
|||
|
||||
func testIncrementingPastTotalStepsDoesNothing() {
|
||||
let store = TestStore(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
index: 3,
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: .demo
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
|
||||
store.send(.next)
|
||||
|
@ -66,12 +64,11 @@ class OnboardingStoreTests: XCTestCase {
|
|||
|
||||
func testDecrementingOnboarding() {
|
||||
let store = TestStore(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
index: 2,
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: .demo
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
|
||||
store.send(.back) {
|
||||
|
@ -97,11 +94,10 @@ class OnboardingStoreTests: XCTestCase {
|
|||
|
||||
func testDecrementingPastFirstStepDoesNothing() {
|
||||
let store = TestStore(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: .demo
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
|
||||
store.send(.back)
|
||||
|
@ -112,12 +108,11 @@ class OnboardingStoreTests: XCTestCase {
|
|||
let initialIndex = 1
|
||||
|
||||
let store = TestStore(
|
||||
initialState: OnboardingFlowState(
|
||||
initialState: OnboardingFlowReducer.State(
|
||||
index: initialIndex,
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
reducer: OnboardingFlowReducer.default,
|
||||
environment: .demo
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
|
||||
store.send(.skip) {
|
||||
|
|
|
@ -12,9 +12,8 @@ import ComposableArchitecture
|
|||
class OnboardingSnapshotTests: XCTestCase {
|
||||
func testOnboardingFlowSnapshot() throws {
|
||||
let store = OnboardingFlowStore(
|
||||
initialState: OnboardingFlowState(importWalletState: .placeholder),
|
||||
reducer: .default,
|
||||
environment: .demo
|
||||
initialState: OnboardingFlowReducer.State(importWalletState: .placeholder),
|
||||
reducer: OnboardingFlowReducer()
|
||||
)
|
||||
let viewStore = ViewStore(store)
|
||||
|
||||
|
|
Loading…
Reference in New Issue