[#461] Migrate OnboardingFlow to ReducerProtocol (#485)

- OnboardingFlow migrated to ReducerProtocol
- unit and snapshot tests fixed
This commit is contained in:
Lukas Korba 2022-11-08 09:36:42 +01:00 committed by GitHub
parent 7f6c104d28
commit d758cf4928
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 136 additions and 190 deletions

View File

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

View File

@ -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

View File

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

View File

@ -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(

View File

@ -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 {

View File

@ -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(

View File

@ -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) {

View File

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