From cd72a6a2726f622d97e0ce83843a470bb68e8db3 Mon Sep 17 00:00:00 2001 From: Alexander Cyon <116169792+CyonAlexRDX@users.noreply.github.com> Date: Tue, 19 Sep 2023 15:21:45 +0200 Subject: [PATCH] Bug fix transfer after network switch (#774) Co-authored-by: kugel3 Co-authored-by: Gustaf Kugelberg <123396602+kugel3@users.noreply.github.com> --- .../DeveloperDisclaimerBanner.swift | 17 +-- .../Core/FeaturePrelude/FeatureReducer.swift | 3 + Sources/Features/AppFeature/App+View.swift | 109 ++++++++---------- .../AssetTransfer+View.swift | 33 +++--- .../AppFeatureTests/AppFeatureTests.swift | 8 +- 5 files changed, 78 insertions(+), 92 deletions(-) diff --git a/Sources/Core/DesignSystem/Components/DeveloperDisclaimerBanner.swift b/Sources/Core/DesignSystem/Components/DeveloperDisclaimerBanner.swift index ad5d2e0c5a..9a8e0168f3 100644 --- a/Sources/Core/DesignSystem/Components/DeveloperDisclaimerBanner.swift +++ b/Sources/Core/DesignSystem/Components/DeveloperDisclaimerBanner.swift @@ -1,12 +1,15 @@ +import ComposableArchitecture import Resources import SwiftUI extension View { - @ViewBuilder - public func showDeveloperDisclaimerBanner(_ show: Bool) -> some View { + @MainActor + public func showDeveloperDisclaimerBanner(_ store: Store) -> some View { VStack(spacing: 0) { - if show { - DeveloperDisclaimerBanner() + WithViewStore(store, observe: { $0 }) { viewStore in + if viewStore.state { + DeveloperDisclaimerBanner() + } } self } @@ -14,8 +17,8 @@ extension View { } // MARK: - DeveloperDisclaimerBanner -struct DeveloperDisclaimerBanner: View { - var body: some View { +public struct DeveloperDisclaimerBanner: View { + public var body: some View { Text(L10n.Common.developerDisclaimerText) .frame(maxWidth: .infinity, alignment: .center) .padding(.small3) @@ -23,5 +26,5 @@ struct DeveloperDisclaimerBanner: View { .textStyle(.body2HighImportance) } - init() {} + public init() {} } diff --git a/Sources/Core/FeaturePrelude/FeatureReducer.swift b/Sources/Core/FeaturePrelude/FeatureReducer.swift index 9f2be56cc6..4fa9ae49d2 100644 --- a/Sources/Core/FeaturePrelude/FeatureReducer.swift +++ b/Sources/Core/FeaturePrelude/FeatureReducer.swift @@ -92,3 +92,6 @@ extension FeatureAction: Hashable where Feature.ViewAction: Hashable, Feature.Ch } } } + +/// For scoping to an actionless childstore +public func actionless(never: Never) -> T {} diff --git a/Sources/Features/AppFeature/App+View.swift b/Sources/Features/AppFeature/App+View.swift index ad01bbd735..c5c3f0cf22 100644 --- a/Sources/Features/AppFeature/App+View.swift +++ b/Sources/Features/AppFeature/App+View.swift @@ -6,34 +6,20 @@ import OnboardingFeature import SplashFeature extension App.State { - var viewState: App.ViewState { - .init( - isOnMainnet: isOnMainnet, - hasMainnetEverBeenLive: hasMainnetEverBeenLive, - isCurrentlyOnboardingUser: isCurrentlyOnboardingUser - ) + public var showIsUsingTestnetBanner: Bool { + guard hasMainnetEverBeenLive else { + return false + } + if isCurrentlyOnboardingUser { + return false + } + + return !isOnMainnet } } // MARK: - App.View extension App { - public struct ViewState: Equatable { - let isOnMainnet: Bool - let hasMainnetEverBeenLive: Bool - let isCurrentlyOnboardingUser: Bool - - var showIsUsingTestnetBanner: Bool { - guard hasMainnetEverBeenLive else { - return false - } - if isCurrentlyOnboardingUser { - return false - } - - return !isOnMainnet - } - } - @MainActor public struct View: SwiftUI.View { private let store: StoreOf @@ -43,49 +29,48 @@ extension App { } public var body: some SwiftUI.View { - WithViewStore(store, observe: \.viewState, send: { .view($0) }) { viewStore in - SwitchStore(store.scope(state: \.root, action: Action.child)) { state in - switch state { - case .main: - CaseLet( - /App.State.Root.main, - action: App.ChildAction.main, - then: { Main.View(store: $0) } - ) + let bannerStore = store.scope(state: \.showIsUsingTestnetBanner, action: actionless) + SwitchStore(store.scope(state: \.root, action: Action.child)) { state in + switch state { + case .main: + CaseLet( + /App.State.Root.main, + action: App.ChildAction.main, + then: { Main.View(store: $0) } + ) - case .onboardingCoordinator: - CaseLet( - /App.State.Root.onboardingCoordinator, - action: App.ChildAction.onboardingCoordinator, - then: { OnboardingCoordinator.View(store: $0) } - ) + case .onboardingCoordinator: + CaseLet( + /App.State.Root.onboardingCoordinator, + action: App.ChildAction.onboardingCoordinator, + then: { OnboardingCoordinator.View(store: $0) } + ) - case .splash: - CaseLet( - /App.State.Root.splash, - action: App.ChildAction.splash, - then: { Splash.View(store: $0) } - ) - case .onboardTestnetUserToMainnet: - CaseLet( - /App.State.Root.onboardTestnetUserToMainnet, - action: App.ChildAction.onboardTestnetUserToMainnet, - then: { CreateAccountCoordinator.View(store: $0) } - ) - } - } - .tint(.app.gray1) - .alert( - store: store.scope(state: \.$alert, action: { .view(.alert($0)) }), - state: /App.Alerts.State.incompatibleProfileErrorAlert, - action: App.Alerts.Action.incompatibleProfileErrorAlert - ) - .task { @MainActor in - await viewStore.send(.task).finish() + case .splash: + CaseLet( + /App.State.Root.splash, + action: App.ChildAction.splash, + then: { Splash.View(store: $0) } + ) + case .onboardTestnetUserToMainnet: + CaseLet( + /App.State.Root.onboardTestnetUserToMainnet, + action: App.ChildAction.onboardTestnetUserToMainnet, + then: { CreateAccountCoordinator.View(store: $0) } + ) } - .showDeveloperDisclaimerBanner(viewStore.showIsUsingTestnetBanner) - .presentsLoadingViewOverlay() } + .tint(.app.gray1) + .alert( + store: store.scope(state: \.$alert, action: { .view(.alert($0)) }), + state: /App.Alerts.State.incompatibleProfileErrorAlert, + action: App.Alerts.Action.incompatibleProfileErrorAlert + ) + .task { @MainActor in + await store.send(.view(.task)).finish() + } + .showDeveloperDisclaimerBanner(bannerStore) + .presentsLoadingViewOverlay() } } } diff --git a/Sources/Features/AssetTransferFeature/AssetTransfer+View.swift b/Sources/Features/AssetTransferFeature/AssetTransfer+View.swift index bbef09004a..1baf1b4904 100644 --- a/Sources/Features/AssetTransferFeature/AssetTransfer+View.swift +++ b/Sources/Features/AssetTransferFeature/AssetTransfer+View.swift @@ -4,26 +4,22 @@ extension AssetTransfer.State { var viewState: AssetTransfer.ViewState { .init( canSendTransferRequest: canSendTransferRequest, - message: message, - isMainnetAccount: isMainnetAccount, - hasMainnetEverBeenLive: hasMainnetEverBeenLive + message: message ) } + + var showIsUsingTestnetBanner: Bool { + guard hasMainnetEverBeenLive else { + return false + } + return !isMainnetAccount + } } extension AssetTransfer { public struct ViewState: Equatable { let canSendTransferRequest: Bool let message: AssetTransferMessage.State? - let isMainnetAccount: Bool - let hasMainnetEverBeenLive: Bool - - var showIsUsingTestnetBanner: Bool { - guard hasMainnetEverBeenLive else { - return false - } - return !isMainnetAccount - } } @MainActor @@ -104,14 +100,13 @@ extension AssetTransfer { } public var body: some SwiftUI.View { - WithViewStore(store, observe: \.viewState) { viewStore in - WithNavigationBar { - viewStore.send(.view(.closeButtonTapped)) - } content: { - View(store: store) - } - .showDeveloperDisclaimerBanner(viewStore.showIsUsingTestnetBanner) + let bannerStore = store.scope(state: \.showIsUsingTestnetBanner, action: actionless) + WithNavigationBar { + store.send(.view(.closeButtonTapped)) + } content: { + View(store: store) } + .showDeveloperDisclaimerBanner(bannerStore) } } } diff --git a/Tests/Features/AppFeatureTests/AppFeatureTests.swift b/Tests/Features/AppFeatureTests/AppFeatureTests.swift index 25b1d1c178..c3583312e3 100644 --- a/Tests/Features/AppFeatureTests/AppFeatureTests.swift +++ b/Tests/Features/AppFeatureTests/AppFeatureTests.swift @@ -31,7 +31,7 @@ final class AppFeatureTests: TestCase { await store.receive(.internal(.toOnboarding(hasMainnetEverBeenLive: false))) { $0.root = .onboardingCoordinator(.init(hasMainnetEverBeenLive: false)) } - XCTAssertFalse(store.state.viewState.showIsUsingTestnetBanner) + XCTAssertFalse(store.state.showIsUsingTestnetBanner) } func test_splash__GIVEN__an_existing_profile__WHEN__existing_profile_loaded__THEN__we_navigate_to_main() async throws { @@ -75,7 +75,7 @@ final class AppFeatureTests: TestCase { // then await store.receive(.internal(.currentGatewayChanged(to: .default))) - XCTAssertFalse(store.state.viewState.showIsUsingTestnetBanner) + XCTAssertFalse(store.state.showIsUsingTestnetBanner) await store.send(.child(.splash(.delegate(.completed(.newUser, accountRecoveryNeeded: false, hasMainnetEverBeenLive: false))))) { $0.root = .onboardingCoordinator(.init(hasMainnetEverBeenLive: false)) } @@ -109,7 +109,7 @@ final class AppFeatureTests: TestCase { // then await store.receive(.internal(.currentGatewayChanged(to: .default))) - XCTAssertFalse(store.state.viewState.showIsUsingTestnetBanner) + XCTAssertFalse(store.state.showIsUsingTestnetBanner) await store.send(.child(.splash(.delegate(.completed(outcome, accountRecoveryNeeded: false, hasMainnetEverBeenLive: false))))) { $0.root = .onboardingCoordinator(.init(hasMainnetEverBeenLive: false)) } @@ -190,7 +190,7 @@ final class AppFeatureTests: TestCase { let outcome = LoadProfileOutcome.usersExistingProfileCouldNotBeLoaded(failure: .profileVersionOutdated(json: Data([0xDE, 0xAD]), version: badVersion)) await store.receive(.internal(.currentGatewayChanged(to: .default))) - XCTAssertFalse(store.state.viewState.showIsUsingTestnetBanner) + XCTAssertFalse(store.state.showIsUsingTestnetBanner) await store.send(.child(.splash(.delegate(.completed(outcome, accountRecoveryNeeded: false, hasMainnetEverBeenLive: false))))) { $0.alert = .incompatibleProfileErrorAlert( .init(