From 7f6c104d281f4f8bc1f9ff0fed9e63fca7b17dd6 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Tue, 8 Nov 2022 09:36:23 +0100 Subject: [PATCH] [#462] Migrate Profile to ReducerProtocol (#484) - Profile migrated to ReducerProtocol - unit and snapshot tests fixed - sandbox feature still works with all the TCA pullback/scope navigation --- secant/Dependencies/AppVersionHandler.swift | 13 ++ secant/Features/Home/HomeStore.swift | 20 +- secant/Features/Profile/ProfileStore.swift | 172 +++++++----------- secant/Features/Profile/ProfileView.swift | 3 +- secant/Features/Sandbox/SandboxStore.swift | 16 +- secantTests/ProfileTests/ProfileTests.swift | 12 +- .../ProfileSnapshotTests.swift | 12 +- 7 files changed, 93 insertions(+), 155 deletions(-) diff --git a/secant/Dependencies/AppVersionHandler.swift b/secant/Dependencies/AppVersionHandler.swift index 76f215e..fbde7e6 100644 --- a/secant/Dependencies/AppVersionHandler.swift +++ b/secant/Dependencies/AppVersionHandler.swift @@ -6,6 +6,7 @@ // import Foundation +import ComposableArchitecture struct AppVersionHandler { let appVersion: () -> String @@ -23,3 +24,15 @@ extension AppVersionHandler { appBuild: { "31" } ) } + +private enum AppVersionHandlerKey: DependencyKey { + static let liveValue = AppVersionHandler.live + static let testValue = AppVersionHandler.test +} + +extension DependencyValues { + var appVersionHandler: AppVersionHandler { + get { self[AppVersionHandlerKey.self] } + set { self[AppVersionHandlerKey.self] = newValue } + } +} diff --git a/secant/Features/Home/HomeStore.swift b/secant/Features/Home/HomeStore.swift index 4ed4ffd..85e9324 100644 --- a/secant/Features/Home/HomeStore.swift +++ b/secant/Features/Home/HomeStore.swift @@ -12,6 +12,7 @@ typealias HomeViewStore = ViewStore typealias AnyBalanceBreakdownReducer = AnyReducer typealias AnyScanReducer = AnyReducer typealias AnyWalletEventsFlowReducer = AnyReducer +typealias AnyProfileReducer = AnyReducer // MARK: State @@ -29,7 +30,7 @@ struct HomeState: Equatable { var balanceBreakdown: BalanceBreakdownReducer.State var drawerOverlay: DrawerOverlay - var profileState: ProfileState + var profileState: ProfileReducer.State var requestState: RequestReducer.State var requiredTransactionConfirmations = 0 var scanState: ScanReducer.State @@ -66,7 +67,7 @@ enum HomeAction: Equatable { case debugMenuStartup case onAppear case onDisappear - case profile(ProfileAction) + case profile(ProfileReducer.Action) case request(RequestReducer.Action) case send(SendFlowAction) case scan(ScanReducer.Action) @@ -269,18 +270,13 @@ extension HomeReducer { environment: { $0 } ) - private static let profileReducer: HomeReducer = ProfileReducer.default.pullback( + private static let profileReducer: HomeReducer = AnyProfileReducer { _ in + ProfileReducer() + } + .pullback( state: \HomeState.profileState, action: /HomeAction.profile, - environment: { environment in - ProfileEnvironment( - appVersionHandler: .live, - mnemonic: environment.mnemonic, - SDKSynchronizer: environment.SDKSynchronizer, - walletStorage: environment.walletStorage, - zcashSDKEnvironment: environment.zcashSDKEnvironment - ) - } + environment: { $0 } ) private static let balanceBreakdownReducer: HomeReducer = AnyBalanceBreakdownReducer { _ in diff --git a/secant/Features/Profile/ProfileStore.swift b/secant/Features/Profile/ProfileStore.swift index 2ca54f5..4b4fa65 100644 --- a/secant/Features/Profile/ProfileStore.swift +++ b/secant/Features/Profile/ProfileStore.swift @@ -1,120 +1,70 @@ import ComposableArchitecture import SwiftUI -typealias ProfileReducer = Reducer -typealias ProfileStore = Store -typealias ProfileViewStore = ViewStore +typealias ProfileStore = Store +typealias ProfileViewStore = ViewStore -typealias AnySettingsReducer = AnyReducer -typealias AnyAddressDetailsReducer = AnyReducer +struct ProfileReducer: ReducerProtocol { + struct State: Equatable { + enum Route { + case addressDetails + case settings + } -// MARK: - State - -struct ProfileState: Equatable { - enum Route { - case addressDetails - case settings + var address = "" + var addressDetailsState: AddressDetailsReducer.State + var appBuild = "" + var appVersion = "" + var route: Route? + var sdkVersion = "" + var settingsState: SettingsReducer.State } - var address = "" - var addressDetailsState: AddressDetailsReducer.State - var appBuild = "" - var appVersion = "" - var route: Route? - var sdkVersion = "" - var settingsState: SettingsReducer.State -} - -// MARK: - Action - -enum ProfileAction: Equatable { - case addressDetails(AddressDetailsReducer.Action) - case back - case onAppear - case settings(SettingsReducer.Action) - case updateRoute(ProfileState.Route?) -} - -// MARK: - Environment - -struct ProfileEnvironment { - let appVersionHandler: AppVersionHandler - let mnemonic: WrappedMnemonic - let SDKSynchronizer: WrappedSDKSynchronizer - let walletStorage: WrappedWalletStorage - let zcashSDKEnvironment: ZCashSDKEnvironment -} - -extension ProfileEnvironment { - static let live = ProfileEnvironment( - appVersionHandler: .live, - mnemonic: .live, - SDKSynchronizer: LiveWrappedSDKSynchronizer(), - walletStorage: .live(), - zcashSDKEnvironment: .mainnet - ) - - static let mock = ProfileEnvironment( - appVersionHandler: .test, - mnemonic: .mock, - SDKSynchronizer: MockWrappedSDKSynchronizer(), - walletStorage: .live(), - zcashSDKEnvironment: .testnet - ) -} - -// MARK: - Reducer - -extension ProfileReducer { - static let `default` = ProfileReducer.combine( - [ - profileReducer, - addressDetailsReducer, - settingsReducer - ] - ) - - private static let profileReducer = ProfileReducer { state, action, environment in - switch action { - case .onAppear: - state.address = environment.SDKSynchronizer.getShieldedAddress() ?? "" - state.appBuild = environment.appVersionHandler.appBuild() - state.appVersion = environment.appVersionHandler.appVersion() - state.sdkVersion = environment.zcashSDKEnvironment.sdkVersion - return .none - - case .back: - return .none - - case let .updateRoute(route): - state.route = route - return .none - - case .addressDetails: - return .none - - case .settings: - return .none - } + enum Action: Equatable { + case addressDetails(AddressDetailsReducer.Action) + case back + case onAppear + case settings(SettingsReducer.Action) + case updateRoute(ProfileReducer.State.Route?) } - private static let addressDetailsReducer: ProfileReducer = AnyAddressDetailsReducer { _ in - AddressDetailsReducer() - } - .pullback( - state: \ProfileState.addressDetailsState, - action: /ProfileAction.addressDetails, - environment: { $0 } - ) + @Dependency(\.sdkSynchronizer) var sdkSynchronizer + @Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment + @Dependency(\.appVersionHandler) var appVersionHandler - private static let settingsReducer: ProfileReducer = AnySettingsReducer { _ in - SettingsReducer() + var body: some ReducerProtocol { + Scope(state: \.addressDetailsState, action: /Action.addressDetails) { + AddressDetailsReducer() + } + + Scope(state: \.settingsState, action: /Action.settings) { + SettingsReducer() + } + + Reduce { state, action in + switch action { + case .onAppear: + state.address = sdkSynchronizer.getShieldedAddress() ?? "" + state.appBuild = appVersionHandler.appBuild() + state.appVersion = appVersionHandler.appVersion() + state.sdkVersion = zcashSDKEnvironment.sdkVersion + return .none + + case .back: + return .none + + case let .updateRoute(route): + state.route = route + return .none + + case .addressDetails: + return .none + + case .settings: + return .none + } + } } - .pullback( - state: \ProfileState.settingsState, - action: /ProfileAction.settings, - environment: { $0 } - ) } // MARK: - Store @@ -123,7 +73,7 @@ extension ProfileStore { func settingsStore() -> SettingsStore { self.scope( state: \.settingsState, - action: ProfileAction.settings + action: ProfileReducer.Action.settings ) } } @@ -131,10 +81,10 @@ extension ProfileStore { // MARK: - ViewStore extension ProfileViewStore { - var routeBinding: Binding { + var routeBinding: Binding { self.binding( get: \.route, - send: ProfileAction.updateRoute + send: ProfileReducer.Action.updateRoute ) } @@ -153,9 +103,9 @@ extension ProfileViewStore { } } -// MARK: Placeholders +// MARK: - Placeholders -extension ProfileState { +extension ProfileReducer.State { static var placeholder: Self { .init( addressDetailsState: .placeholder, diff --git a/secant/Features/Profile/ProfileView.swift b/secant/Features/Profile/ProfileView.swift index d1e5684..d4e307e 100644 --- a/secant/Features/Profile/ProfileView.swift +++ b/secant/Features/Profile/ProfileView.swift @@ -116,8 +116,7 @@ struct ProfileView_Previews: PreviewProvider { addressDetailsState: .placeholder, settingsState: .placeholder ), - reducer: .default, - environment: .live + reducer: ProfileReducer() ) ) } diff --git a/secant/Features/Sandbox/SandboxStore.swift b/secant/Features/Sandbox/SandboxStore.swift index 26850ac..b266b80 100644 --- a/secant/Features/Sandbox/SandboxStore.swift +++ b/secant/Features/Sandbox/SandboxStore.swift @@ -15,14 +15,14 @@ struct SandboxReducer: ReducerProtocol { case request } var walletEventsState: WalletEventsFlowReducer.State - var profileState: ProfileState + var profileState: ProfileReducer.State var route: Route? } enum Action: Equatable { case updateRoute(SandboxReducer.State.Route?) case walletEvents(WalletEventsFlowReducer.Action) - case profile(ProfileAction) + case profile(ProfileReducer.Action) case reset } @@ -38,14 +38,10 @@ struct SandboxReducer: ReducerProtocol { .map(SandboxReducer.Action.walletEvents) case .profile: - return ProfileReducer - .default - .pullback( - state: \.profileState, - action: /SandboxReducer.Action.profile, - environment: { _ in ProfileEnvironment.live } - ) - .run(&state, action, ()) + return Scope(state: \SandboxReducer.State.profileState, action: /SandboxReducer.Action.profile) { + ProfileReducer() + } + .reduce(into: &state, action: action) case .reset: return .none diff --git a/secantTests/ProfileTests/ProfileTests.swift b/secantTests/ProfileTests/ProfileTests.swift index 82044de..cc495ad 100644 --- a/secantTests/ProfileTests/ProfileTests.swift +++ b/secantTests/ProfileTests/ProfileTests.swift @@ -11,18 +11,10 @@ import ComposableArchitecture class ProfileTests: XCTestCase { func testSynchronizerStateChanged_AnyButSynced() throws { - let testEnvironment = ProfileEnvironment( - appVersionHandler: .test, - mnemonic: .mock, - SDKSynchronizer: TestWrappedSDKSynchronizer(), - walletStorage: .throwing, - zcashSDKEnvironment: .testnet - ) - let store = TestStore( initialState: .placeholder, - reducer: ProfileReducer.default, - environment: testEnvironment + reducer: ProfileReducer() + .dependency(\.sdkSynchronizer, TestWrappedSDKSynchronizer()) ) store.send(.onAppear) { state in diff --git a/secantTests/SnapshotTests/ProfileSnapshotTests/ProfileSnapshotTests.swift b/secantTests/SnapshotTests/ProfileSnapshotTests/ProfileSnapshotTests.swift index f2cc36a..56bdc1f 100644 --- a/secantTests/SnapshotTests/ProfileSnapshotTests/ProfileSnapshotTests.swift +++ b/secantTests/SnapshotTests/ProfileSnapshotTests/ProfileSnapshotTests.swift @@ -12,18 +12,10 @@ import SwiftUI class ProfileSnapshotTests: XCTestCase { func testProfileSnapshot_sent() throws { - let testEnvironment = ProfileEnvironment( - appVersionHandler: .test, - mnemonic: .mock, - SDKSynchronizer: TestWrappedSDKSynchronizer(), - walletStorage: .throwing, - zcashSDKEnvironment: .testnet - ) - let store = Store( initialState: .placeholder, - reducer: ProfileReducer.default, - environment: testEnvironment + reducer: ProfileReducer() + .dependency(\.sdkSynchronizer, TestWrappedSDKSynchronizer()) ) ViewStore(store).send(.onAppear)