diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index 348b53f3..3a374beb 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -3517,8 +3517,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/zcash/ZcashLightClientKit/"; requirement = { - branch = main; - kind = branch; + kind = revision; + revision = 3b2d972b2dd0c51e36d76e49fcf58273bf585c1b; }; }; 6654C7382715A38000901167 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = { diff --git a/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 665ce6c0..8f437b8c 100644 --- a/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -338,8 +338,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash/ZcashLightClientKit/", "state" : { - "branch" : "main", - "revision" : "27b99013f7dda11caee0a261361c155490966e7f" + "revision" : "3b2d972b2dd0c51e36d76e49fcf58273bf585c1b" } } ], diff --git a/secant/Dependencies/SDKSynchronizer/SDKSynchronizerInterface.swift b/secant/Dependencies/SDKSynchronizer/SDKSynchronizerInterface.swift index 6552d66e..c6175aa8 100644 --- a/secant/Dependencies/SDKSynchronizer/SDKSynchronizerInterface.swift +++ b/secant/Dependencies/SDKSynchronizer/SDKSynchronizerInterface.swift @@ -18,24 +18,11 @@ extension DependencyValues { } enum SDKSynchronizerState: Equatable { - case unknown - case transactionsUpdated - case started case progressUpdated - case statusWillUpdate - case synced + case started case stopped - case disconnected - case syncing - case downloading - case validating - case scanning - case enhancing - case fetching - case minedTransaction - case foundTransactions - case failed - case connectionStateChanged + case synced + case unknown } enum SDKSynchronizerClientError: Error { @@ -54,6 +41,8 @@ protocol SDKSynchronizerClient { func stop() func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?) func statusSnapshot() -> SyncStatusSnapshot + func isSyncing() -> Bool + func isInitialized() -> Bool func rewind(_ policy: RewindPolicy) async throws @@ -75,6 +64,8 @@ protocol SDKSynchronizerClient { to recipientAddress: Recipient, memo: Memo? ) -> EffectTask> + + func wipe() -> AnyPublisher? } extension SDKSynchronizerClient { diff --git a/secant/Dependencies/SDKSynchronizer/SDKSynchronizerLive.swift b/secant/Dependencies/SDKSynchronizer/SDKSynchronizerLive.swift index 03e7026e..76f6756f 100644 --- a/secant/Dependencies/SDKSynchronizer/SDKSynchronizerLive.swift +++ b/secant/Dependencies/SDKSynchronizer/SDKSynchronizerLive.swift @@ -75,6 +75,14 @@ class LiveSDKSynchronizerClient: SDKSynchronizerClient { func stop() { synchronizer?.stop() } + + func isSyncing() -> Bool { + latestScannedSynchronizerState?.syncStatus.isSyncing ?? false + } + + func isInitialized() -> Bool { + synchronizer != nil + } func synchronizerStarted() { stateChanged.send(.started) @@ -229,4 +237,8 @@ class LiveSDKSynchronizerClient: SDKSynchronizerClient { } } } + + func wipe() -> AnyPublisher? { + synchronizer?.wipe() + } } diff --git a/secant/Dependencies/SDKSynchronizer/SDKSynchronizerMocks.swift b/secant/Dependencies/SDKSynchronizer/SDKSynchronizerMocks.swift index 0d9c82f2..4222c575 100644 --- a/secant/Dependencies/SDKSynchronizer/SDKSynchronizerMocks.swift +++ b/secant/Dependencies/SDKSynchronizer/SDKSynchronizerMocks.swift @@ -33,6 +33,10 @@ class MockSDKSynchronizerClient: SDKSynchronizerClient { func stop() { } + func isSyncing() -> Bool { false } + + func isInitialized() -> Bool { false } + func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?) { } func statusSnapshot() -> SyncStatusSnapshot { .default } @@ -200,4 +204,6 @@ class MockSDKSynchronizerClient: SDKSynchronizerClient { return EffectTask(value: Result.success(transactionState)) } + + func wipe() -> AnyPublisher? { nil } } diff --git a/secant/Dependencies/SDKSynchronizer/SDKSynchronizerTest.swift b/secant/Dependencies/SDKSynchronizer/SDKSynchronizerTest.swift index 66b601d0..9b96b62e 100644 --- a/secant/Dependencies/SDKSynchronizer/SDKSynchronizerTest.swift +++ b/secant/Dependencies/SDKSynchronizer/SDKSynchronizerTest.swift @@ -32,6 +32,10 @@ class NoopSDKSynchronizer: SDKSynchronizerClient { func stop() { } + func isSyncing() -> Bool { false } + + func isInitialized() -> Bool { false } + func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?) { } func statusSnapshot() -> SyncStatusSnapshot { .default } @@ -70,6 +74,8 @@ class NoopSDKSynchronizer: SDKSynchronizerClient { func updateStateChanged(_ newState: SDKSynchronizerState) { stateChanged = CurrentValueSubject(newState) } + + func wipe() -> AnyPublisher? { nil } } class TestSDKSynchronizerClient: SDKSynchronizerClient { @@ -90,6 +96,10 @@ class TestSDKSynchronizerClient: SDKSynchronizerClient { func stop() { } + func isSyncing() -> Bool { false } + + func isInitialized() -> Bool { false } + func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?) { } func statusSnapshot() -> SyncStatusSnapshot { .default } @@ -235,4 +245,6 @@ class TestSDKSynchronizerClient: SDKSynchronizerClient { func updateStateChanged(_ newState: SDKSynchronizerState) { stateChanged = CurrentValueSubject(newState) } + + func wipe() -> AnyPublisher? { nil } } diff --git a/secant/Features/Root/RootDestination.swift b/secant/Features/Root/RootDestination.swift index e191eb7c..3c7dd85e 100644 --- a/secant/Features/Root/RootDestination.swift +++ b/secant/Features/Root/RootDestination.swift @@ -23,9 +23,10 @@ extension RootReducer { case welcome } + @BindingState var alert: AlertState? var internalDestination: Destination = .welcome var previousDestination: Destination? - + var destination: Destination { get { internalDestination } set { @@ -39,6 +40,7 @@ extension RootReducer { case deeplink(URL) case deeplinkHome case deeplinkSend(Zatoshi, String, String) + case dismissAlert case updateDestination(RootReducer.DestinationState.Destination) } @@ -112,7 +114,12 @@ extension RootReducer { } return EffectTask(value: .destination(.deeplink(url))) - case .home, .initialization, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .welcome, .debug: + case .destination(.dismissAlert): + state.destinationState.alert = nil + return .none + + case .home, .initialization, .onboarding, .phraseDisplay, .phraseValidation, + .sandbox, .welcome, .binding, .nukeWalletFailed, .nukeWalletSucceeded, .debug: return .none } diff --git a/secant/Features/Root/RootInitialization.swift b/secant/Features/Root/RootInitialization.swift index 24ebc285..8336266e 100644 --- a/secant/Features/Root/RootInitialization.swift +++ b/secant/Features/Root/RootInitialization.swift @@ -21,6 +21,7 @@ extension RootReducer { case initializeSDK case initialSetups case nukeWallet + case nukeWalletRequest case respondToWalletInitializationState(InitializationState) case walletConfigChanged(WalletConfig) } @@ -125,10 +126,9 @@ extension RootReducer { mnemonic: mnemonic, zcashSDKEnvironment: zcashSDKEnvironment ) - + try sdkSynchronizer.prepareWith(initializer: initializer, seedBytes: seedBytes) try sdkSynchronizer.start() - return .none } catch { // TODO: [#221] error we need to handle (https://github.com/zcash/secant-ios-wallet/issues/221) @@ -199,14 +199,57 @@ extension RootReducer { } return .none - case .initialization(.nukeWallet): - walletStorage.nukeWallet() - do { - try databaseFiles.nukeDbFilesFor(zcashSDKEnvironment.network) - } catch { - // TODO: [#221] error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221) - } + case .initialization(.nukeWalletRequest): + state.destinationState.alert = AlertState( + title: TextState("Wipe of the wallet"), + message: TextState("Are you sure?"), + buttons: [ + .destructive( + TextState("Yes"), + action: .send(.initialization(.nukeWallet)) + ), + .cancel( + TextState("No"), + action: .send(.destination(.dismissAlert)) + ) + ] + ) return .none + + case .initialization(.nukeWallet): + guard let wipePublisher = sdkSynchronizer.wipe() else { + return EffectTask(value: .nukeWalletFailed) + } + return wipePublisher + .catchToEffect() + .receive(on: mainQueue) + .replaceEmpty(with: .success(Void())) + .map { _ in return RootReducer.Action.nukeWalletSucceeded } + .replaceError(with: RootReducer.Action.nukeWalletFailed) + .eraseToEffect() + .cancellable(id: SynchronizerCancelId.self, cancelInFlight: true) + + case .nukeWalletSucceeded: + walletStorage.nukeWallet() + state.onboardingState.destination = nil + state.onboardingState.index = 0 + return .concatenate( + .cancel(id: SynchronizerCancelId.self), + EffectTask(value: .initialization(.checkWalletInitialization)) + ) + + case .nukeWalletFailed: + // TODO: [#221] error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221) + let backDestination: EffectTask + if let previousDestination = state.destinationState.previousDestination { + backDestination = EffectTask(value: .destination(.updateDestination(previousDestination))) + } else { + backDestination = EffectTask(value: .destination(.updateDestination(state.destinationState.destination))) + } + return .concatenate( + .cancel(id: SynchronizerCancelId.self), + backDestination + ) case .welcome(.debugMenuStartup), .home(.debugMenuStartup): return .concatenate( @@ -222,8 +265,8 @@ extension RootReducer { case .onboarding(.createNewWallet): return EffectTask(value: .initialization(.createNewWallet)) - - case .home, .destination, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .welcome: + + case .home, .destination, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .welcome, .binding: return .none case .initialization(.configureCrashReporter): diff --git a/secant/Features/Root/RootStore.swift b/secant/Features/Root/RootStore.swift index 68403a94..7bb17bd3 100644 --- a/secant/Features/Root/RootStore.swift +++ b/secant/Features/Root/RootStore.swift @@ -6,6 +6,7 @@ typealias RootViewStore = ViewStore struct RootReducer: ReducerProtocol { enum CancelId {} + enum SynchronizerCancelId {} struct State: Equatable { var appInitializationState: InitializationState = .uninitialized @@ -20,10 +21,13 @@ struct RootReducer: ReducerProtocol { var welcomeState: WelcomeReducer.State } - enum Action: Equatable { + enum Action: Equatable, BindableAction { + case binding(BindingAction) case destination(DestinationAction) case home(HomeReducer.Action) case initialization(InitializationAction) + case nukeWalletFailed + case nukeWalletSucceeded case onboarding(OnboardingFlowReducer.Action) case phraseDisplay(RecoveryPhraseDisplayReducer.Action) case phraseValidation(RecoveryPhraseValidationFlowReducer.Action) @@ -46,6 +50,8 @@ struct RootReducer: ReducerProtocol { @Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment var body: some ReducerProtocol { + BindingReducer() + Scope(state: \.homeState, action: /Action.home) { HomeReducer() } @@ -144,6 +150,7 @@ extension RootReducer { network: zcashSDKEnvironment.network, spendParamsURL: try databaseFiles.spendParamsURLFor(network), outputParamsURL: try databaseFiles.outputParamsURLFor(network), + saplingParamsSourceURL: SaplingParamsSourceURL.default, viewingKeys: [viewingKey], walletBirthday: birthday, loggerProxy: OSLogger_(logLevel: .debug, category: LoggerConstants.sdkLogs) diff --git a/secant/Features/Root/RootView.swift b/secant/Features/Root/RootView.swift index be2bbe81..bfdea687 100644 --- a/secant/Features/Root/RootView.swift +++ b/secant/Features/Root/RootView.swift @@ -126,7 +126,7 @@ private extension RootView { } Button("[Be careful] Nuke Wallet") { - viewStore.send(.initialization(.nukeWallet)) + viewStore.send(.initialization(.nukeWalletRequest)) } } @@ -154,6 +154,7 @@ private extension RootView { } } } + .alert(self.store.scope(state: \.destinationState.alert), dismiss: .destination(.dismissAlert)) } .navigationBarTitle("Startup") } diff --git a/secantTests/HomeTests/HomeTests.swift b/secantTests/HomeTests/HomeTests.swift index 48641e07..dca3e5c6 100644 --- a/secantTests/HomeTests/HomeTests.swift +++ b/secantTests/HomeTests/HomeTests.swift @@ -17,7 +17,7 @@ class HomeTests: XCTestCase { reducer: HomeReducer() ) - store.send(.synchronizerStateChanged(.downloading)) + store.send(.synchronizerStateChanged(.progressUpdated)) store.receive(.updateSynchronizerStatus) }