[#698] RootView to use SwitchStore (#699)

- all alerts refactored and connected
- unit tests fixed
This commit is contained in:
Lukas Korba 2023-03-31 07:25:13 +02:00 committed by GitHub
parent c61b406372
commit 36d2090654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 630 additions and 358 deletions

View File

@ -370,6 +370,13 @@
9E2F1C842809B606004E65FE /* DebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2F1C832809B606004E65FE /* DebugMenu.swift */; };
9E2F1C8C280ED6A7004E65FE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9E2F1C8B280ED6A7004E65FE /* LaunchScreen.storyboard */; };
9E2F1C8F280EDE09004E65FE /* Drawer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2F1C8E280EDE09004E65FE /* Drawer.swift */; };
9E33ECD429D5D99000708DE4 /* AlertRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E33ECD029D4CCB600708DE4 /* AlertRequest.swift */; };
9E33ECD529D5D99700708DE4 /* AlertReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9CEA3229D32D5100599DF5 /* AlertReducer.swift */; };
9E33ECD629D5D99A00708DE4 /* AlertStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E33ECD229D4D1FB00708DE4 /* AlertStates.swift */; };
9E33ECD729D5E30200708DE4 /* AlertReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9CEA3229D32D5100599DF5 /* AlertReducer.swift */; };
9E33ECD829D5E30200708DE4 /* AlertStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E33ECD229D4D1FB00708DE4 /* AlertStates.swift */; };
9E33ECD929D5E30200708DE4 /* AlertRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E33ECD029D4CCB600708DE4 /* AlertRequest.swift */; };
9E33ECDA29D5E30700708DE4 /* OnChangeReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9CEA3D29D47BE000599DF5 /* OnChangeReducer.swift */; };
9E34519529C4A4BF00177D16 /* AddressDetailsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E207C382966EF87003E2C9B /* AddressDetailsTests.swift */; };
9E34519629C4A4D800177D16 /* secantUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4E7A2526B364180058B01E /* secantUITests.swift */; };
9E34519729C4A51100177D16 /* RecoveryPhraseBackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFE93DE272C6D4B000FCCA5 /* RecoveryPhraseBackupTests.swift */; };
@ -466,6 +473,7 @@
9E852D6229B098F400CF4AC1 /* RootDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E852D6029B098F400CF4AC1 /* RootDebug.swift */; };
9E9ADA7D2938F4C00071767B /* RootInitialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9ADA7C2938F4C00071767B /* RootInitialization.swift */; };
9E9ADA7F2938F5EC0071767B /* RootDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9ADA7E2938F5EC0071767B /* RootDestination.swift */; };
9E9CEA3E29D47BE000599DF5 /* OnChangeReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9CEA3D29D47BE000599DF5 /* OnChangeReducer.swift */; };
9EAB466D285A0468002904A0 /* Parsing in Frameworks */ = {isa = PBXBuildFile; productRef = 9EAB466C285A0468002904A0 /* Parsing */; };
9EAB4671285A1C77002904A0 /* Deeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAB4670285A1C77002904A0 /* Deeplink.swift */; };
9EAB46782860A1D2002904A0 /* WalletEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAB46772860A1D2002904A0 /* WalletEvent.swift */; };
@ -711,6 +719,8 @@
9E2F1C832809B606004E65FE /* DebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugMenu.swift; sourceTree = "<group>"; };
9E2F1C8B280ED6A7004E65FE /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
9E2F1C8E280EDE09004E65FE /* Drawer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Drawer.swift; sourceTree = "<group>"; };
9E33ECD029D4CCB600708DE4 /* AlertRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertRequest.swift; sourceTree = "<group>"; };
9E33ECD229D4D1FB00708DE4 /* AlertStates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertStates.swift; sourceTree = "<group>"; };
9E37A2B727C8F59F00AE57B3 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
9E391123283E4CAC0073DD9A /* ImportWalletTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportWalletTests.swift; sourceTree = "<group>"; };
9E39112D283F91600073DD9A /* ZatoshiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZatoshiTests.swift; sourceTree = "<group>"; };
@ -772,6 +782,8 @@
9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownSnapshotTests.swift; sourceTree = "<group>"; };
9E9ADA7C2938F4C00071767B /* RootInitialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootInitialization.swift; sourceTree = "<group>"; };
9E9ADA7E2938F5EC0071767B /* RootDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootDestination.swift; sourceTree = "<group>"; };
9E9CEA3229D32D5100599DF5 /* AlertReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertReducer.swift; sourceTree = "<group>"; };
9E9CEA3D29D47BE000599DF5 /* OnChangeReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnChangeReducer.swift; sourceTree = "<group>"; };
9E9ECC8C28589E150099D5A2 /* HomeSnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeSnapshotTests.swift; sourceTree = "<group>"; };
9E9ECC8E28589E150099D5A2 /* WelcomeSnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeSnapshotTests.swift; sourceTree = "<group>"; };
9E9ECC9028589E150099D5A2 /* RecoveryPhraseDisplaySnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplaySnapshotTests.swift; sourceTree = "<group>"; };
@ -1260,6 +1272,7 @@
6654C73B2715A3F000901167 /* Features */ = {
isa = PBXGroup;
children = (
9E33ECCF29D4CC9900708DE4 /* Alerts */,
9E7CB61B2874140900A02233 /* AddressDetails */,
9E6713F2289BC51200A6796F /* BalanceBreakdown */,
34C5657F29B60BDF002F3A7C /* ExportLogs */,
@ -1421,6 +1434,16 @@
path = Drawer;
sourceTree = "<group>";
};
9E33ECCF29D4CC9900708DE4 /* Alerts */ = {
isa = PBXGroup;
children = (
9E33ECD029D4CCB600708DE4 /* AlertRequest.swift */,
9E9CEA3229D32D5100599DF5 /* AlertReducer.swift */,
9E33ECD229D4D1FB00708DE4 /* AlertStates.swift */,
);
path = Alerts;
sourceTree = "<group>";
};
9E391122283E4C970073DD9A /* ImportWalletTests */ = {
isa = PBXGroup;
children = (
@ -1648,6 +1671,7 @@
9E7FE0BB282D1DC200C374E8 /* Utils */ = {
isa = PBXGroup;
children = (
9E9CEA3C29D47BD100599DF5 /* Reducers */,
9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */,
F9C165B3274031F600592F76 /* Bindings.swift */,
0DACFA7E27208CE00039EEA5 /* Clamped.swift */,
@ -1804,6 +1828,14 @@
path = BalanceBreakdownSnapshotTests;
sourceTree = "<group>";
};
9E9CEA3C29D47BD100599DF5 /* Reducers */ = {
isa = PBXGroup;
children = (
9E9CEA3D29D47BE000599DF5 /* OnChangeReducer.swift */,
);
path = Reducers;
sourceTree = "<group>";
};
9E9ECC8B28589E150099D5A2 /* HomeSnapshotTests */ = {
isa = PBXGroup;
children = (
@ -2615,6 +2647,7 @@
0D26AEC4299E8196005260EE /* TCALogging.swift in Sources */,
0D26AEC5299E8196005260EE /* RecoveryPhraseValidationFlowStore.swift in Sources */,
9E486DFA29BA09C2003E6945 /* UIKit+Extensions.swift in Sources */,
9E33ECDA29D5E30700708DE4 /* OnChangeReducer.swift in Sources */,
0D26AEC6299E8196005260EE /* ImportWalletView.swift in Sources */,
0D26AEC7299E8196005260EE /* RootInitialization.swift in Sources */,
0D26AEC8299E8196005260EE /* LogsHandlerLive.swift in Sources */,
@ -2641,6 +2674,7 @@
9E486DE629B637AF003E6945 /* ImportBirthdayView.swift in Sources */,
3467319629AE265300974482 /* SupportDataGenerator.swift in Sources */,
0D26AEDC299E8196005260EE /* CheckCircle.swift in Sources */,
9E33ECD929D5E30200708DE4 /* AlertRequest.swift in Sources */,
0D26AEDD299E8196005260EE /* LogStore.swift in Sources */,
0D26AEDE299E8196005260EE /* RecoveryPhraseRandomizer.swift in Sources */,
3467319D29AE374A00974482 /* SupportDataGeneratorLiveKey.swift in Sources */,
@ -2742,6 +2776,7 @@
0D26AF3F299E8196005260EE /* WordChipGrid.swift in Sources */,
0D26AF40299E8196005260EE /* RootDestination.swift in Sources */,
0D26AF41299E8196005260EE /* OnboardingProgressIndicator.swift in Sources */,
9E33ECD829D5E30200708DE4 /* AlertStates.swift in Sources */,
0D26AF42299E8196005260EE /* CaptureDeviceInterface.swift in Sources */,
0D26AF43299E8196005260EE /* FeedbackGeneratorLiveKey.swift in Sources */,
0D26AF44299E8196005260EE /* Memo+toString.swift in Sources */,
@ -2776,6 +2811,7 @@
0D26AF61299E8196005260EE /* WalletStorageTestKey.swift in Sources */,
0D26AF62299E8196005260EE /* WelcomeView.swift in Sources */,
0D26AF63299E8196005260EE /* DeeplinkLiveKey.swift in Sources */,
9E33ECD729D5E30200708DE4 /* AlertReducer.swift in Sources */,
0D26AF64299E8196005260EE /* SettingsStore.swift in Sources */,
0D26AF65299E8196005260EE /* InitializationState.swift in Sources */,
0D26AF66299E8196005260EE /* ZcashSymbol.swift in Sources */,
@ -2795,6 +2831,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9E33ECD629D5D99A00708DE4 /* AlertStates.swift in Sources */,
2EB660E02747EAB900A06A07 /* OnboardingFlowView.swift in Sources */,
9E7FE0DF282D2DD600C374E8 /* ZcashBadge.swift in Sources */,
34F682F229A764120022C079 /* WalletConfigProviderLiveKey.swift in Sources */,
@ -2809,6 +2846,7 @@
34DA414728E4385800F8CC61 /* TransactionSendingView.swift in Sources */,
F96B41E9273B501F0021B49A /* WalletEventsFlowView.swift in Sources */,
9E4AA4F829BF76BB00752BB3 /* About.swift in Sources */,
9E33ECD429D5D99000708DE4 /* AlertRequest.swift in Sources */,
9EBDF96E291ECED4000A1A05 /* CaptureDeviceLiveKey.swift in Sources */,
3467319929AE374300974482 /* SupportDataGeneratorInterface.swift in Sources */,
9EBDF968291ECDA2000A1A05 /* AudioServicesInterface.swift in Sources */,
@ -2839,6 +2877,7 @@
9EB8638C2922CD4A003D0F8B /* FeedbackGeneratorTestKey.swift in Sources */,
9E0F5741297E7F1D005304FA /* TCALogging.swift in Sources */,
0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidationFlowStore.swift in Sources */,
9E9CEA3E29D47BE000599DF5 /* OnChangeReducer.swift in Sources */,
9E486DF929BA09C2003E6945 /* UIKit+Extensions.swift in Sources */,
9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */,
9E9ADA7D2938F4C00071767B /* RootInitialization.swift in Sources */,
@ -2851,6 +2890,7 @@
9EB863A1292398A8003D0F8B /* URIParserInterface.swift in Sources */,
2E6CF8DD27D78319004DCD7A /* CurrencySelectionStore.swift in Sources */,
9E153A7729216EFB00112F41 /* UserDefaultsTestKey.swift in Sources */,
9E33ECD529D5D99700708DE4 /* AlertReducer.swift in Sources */,
9EBEF87A27CE369800B4F343 /* RecoveryPhraseValidationFlowView.swift in Sources */,
9E6713F7289BC58C00A6796F /* BalanceBreakdownStore.swift in Sources */,
9E66122C2877188700C75B70 /* SyncStatusSnapshot.swift in Sources */,

View File

@ -17,7 +17,7 @@ extension DependencyValues {
struct CaptureDeviceClient {
enum CaptureDeviceClientError: Error {
case captureDeviceFailed
case lockFailed
case lockForConfigurationFailed
case torchUnavailable
}

View File

@ -31,7 +31,7 @@ extension CaptureDeviceClient: DependencyKey {
videoCaptureDevice.torchMode = isTorchOn ? .on : .off
videoCaptureDevice.unlockForConfiguration()
} catch {
throw CaptureDeviceClientError.lockFailed
throw CaptureDeviceClientError.lockForConfigurationFailed
}
}
)

View File

@ -0,0 +1,118 @@
//
// AlertReducer.swift
// secant
//
// Created by Lukáš Korba on 28.03.2023.
//
import ComposableArchitecture
extension ReducerProtocol<RootReducer.State, RootReducer.Action> {
func alerts() -> some ReducerProtocol<RootReducer.State, RootReducer.Action> {
UniAlert(base: self)
}
}
private struct UniAlert<Base: ReducerProtocol<RootReducer.State, RootReducer.Action>>: ReducerProtocol {
let base: Base
var body: some ReducerProtocol<RootReducer.State, RootReducer.Action> {
base
catchAlertRequests
passAlertActions
}
// Catching the alert side effects
@ReducerBuilder<State, Action>
var catchAlertRequests: some ReducerProtocol<State, Action> {
Reduce { state, action in
switch action {
case .alert(let alert):
state.uniAlert = alert.alertState()
return .none
case .exportLogs(.alert(let alert)):
state.uniAlert = alert.alertState()
return .none
case .home(.settings(.exportLogs(.alert(let alert)))):
state.uniAlert = alert.alertState()
return .none
case .home(.alert(let alert)):
state.uniAlert = alert.alertState()
return .none
case .home(.balanceBreakdown(.alert(let alert))):
state.uniAlert = alert.alertState()
return .none
case .home(.send(.scan(.alert(let alert)))):
state.uniAlert = alert.alertState()
return .none
case .onboarding(.importWallet(.alert(let alert))):
state.uniAlert = alert.alertState()
return .none
case .home(.settings(.alert(let alert))):
state.uniAlert = alert.alertState()
return .none
case .home(.walletEvents(.alert(let alert))):
state.uniAlert = alert.alertState()
return .none
default: return .none
}
}
}
// Passing alert actions back to the children
@ReducerBuilder<State, Action>
var passAlertActions: some ReducerProtocol<State, Action> {
Reduce { state, action in
switch action {
case .dismissAlert:
state.uniAlert = nil
return .none
case .uniAlert(.balanceBreakdown(let action)):
guard let action else { return .none }
return EffectTask(value: .home(.balanceBreakdown(action)))
case .uniAlert(.exportLogs(let action)):
guard let action else { return .none }
return .concatenate(
EffectTask(value: .exportLogs(action)),
EffectTask(value: .home(.settings(.exportLogs(action))))
)
case .uniAlert(.home(let action)):
guard let action else { return .none }
return EffectTask(value: .home(action))
case .uniAlert(.importWallet(let action)):
guard let action else { return .none }
return EffectTask(value: .onboarding(.importWallet(action)))
case .uniAlert(.root(let action)):
guard let action else { return .none }
return EffectTask(value: action)
case .uniAlert(.scan(let action)):
guard let action else { return .none }
return EffectTask(value: .home(.send(.scan(action))))
case .uniAlert(.settings(let action)):
guard let action else { return .none }
return EffectTask(value: .home(.settings(action)))
case .uniAlert(.walletEvents(let action)):
guard let action else { return .none }
return EffectTask(value: .home(.walletEvents(action)))
default: return .none
}
}
}
}

View File

@ -0,0 +1,98 @@
//
// AlertRequest.swift
// secant-testnet
//
// Created by Lukáš Korba on 29.03.2023.
//
import Foundation
import ComposableArchitecture
extension RootReducer {
indirect enum AlertAction: Equatable {
case balanceBreakdown(BalanceBreakdownReducer.Action?)
case exportLogs(ExportLogsReducer.Action?)
case home(HomeReducer.Action?)
case importWallet(ImportWalletReducer.Action?)
case root(RootReducer.Action?)
case scan(ScanReducer.Action?)
case settings(SettingsReducer.Action?)
case walletEvents(WalletEventsFlowReducer.Action?)
}
}
enum AlertRequest: Equatable {
enum BalanceBreakdown: Equatable {
case shieldFundsSuccess
case shieldFundsFailure(String)
}
enum ExportLogs: Equatable {
case failed(String)
}
enum Home: Equatable {
case syncFailed(String, String)
}
enum ImportWallet: Equatable {
case succeed
case failed(String)
}
enum Root: Equatable {
case cantCreateNewWallet(String)
case cantLoadSeedPhrase
case cantStartSync(String)
case cantStoreThatUserPassedPhraseBackupTest(String)
case failedToProcessDeeplink(URL, String)
case initializationFailed(String)
case rewindFailed(String)
case walletStateFailed(InitializationState)
case wipeFailed
case wipeRequest
}
enum Scan: Equatable {
case cantInitializeCamera(String)
}
enum Settings: Equatable {
case cantBackupWallet(String)
case sendSupportMail
}
enum WalletEvents: Equatable {
case warnBeforeLeavingApp(URL?)
}
case balanceBreakdown(BalanceBreakdown)
case exportLogs(ExportLogs)
case home(Home)
case importWallet(ImportWallet)
case root(Root)
case scan(Scan)
case settings(Settings)
case walletEvents(WalletEvents)
func alertState() -> AlertState<RootReducer.Action> {
switch self {
case .balanceBreakdown(let balanceBreakdown):
return balanceBreakdownAlertState(balanceBreakdown)
case .exportLogs(let exportLogs):
return exportLogsAlertState(exportLogs)
case .home(let home):
return homeAlertState(home)
case .importWallet(let importWallet):
return importWalletAlertState(importWallet)
case .root(let root):
return rootAlertState(root)
case .scan(let scan):
return scanAlertState(scan)
case .settings(let settings):
return settingsAlertState(settings)
case .walletEvents(let walletEvents):
return walletEventsAlertState(walletEvents)
}
}
}

View File

@ -0,0 +1,211 @@
//
// AlertStates.swift
// secant-testnet
//
// Created by Lukáš Korba on 29.03.2023.
//
import ComposableArchitecture
// MARK: - Balance Breakdown
extension AlertRequest {
func balanceBreakdownAlertState(_ balanceBreakdown: BalanceBreakdown) -> AlertState<RootReducer.Action> {
switch balanceBreakdown {
case .shieldFundsFailure(let errorDescription):
return AlertState(
title: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Failure.title),
message: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Failure.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case .shieldFundsSuccess:
return AlertState(
title: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Success.title),
message: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Success.message),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
}
}
}
// MARK: - Export Logs
extension AlertRequest {
func exportLogsAlertState(_ exportLogs: ExportLogs) -> AlertState<RootReducer.Action> {
switch exportLogs {
case .failed(let errorDescription):
return AlertState(
title: TextState(L10n.ExportLogs.Alert.Failed.title),
message: TextState(L10n.ExportLogs.Alert.Failed.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
}
}
}
// MARK: - Home
extension AlertRequest {
func homeAlertState(_ home: Home) -> AlertState<RootReducer.Action> {
switch home {
case let .syncFailed(message, secondaryButtonTitle):
return AlertState(
title: TextState(L10n.Home.SyncFailed.title),
message: TextState(message),
primaryButton: .default(TextState(L10n.Home.SyncFailed.retry), action: .send(.uniAlert(.home(.retrySync)))),
secondaryButton: .default(TextState(secondaryButtonTitle), action: .send(.dismissAlert))
)
}
}
}
// MARK: - Import Wallet
extension AlertRequest {
func importWalletAlertState(_ importWallet: ImportWallet) -> AlertState<RootReducer.Action> {
switch importWallet {
case .succeed:
return AlertState(
title: TextState(L10n.General.success),
message: TextState(L10n.ImportWallet.Alert.Success.message),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.uniAlert(.importWallet(.successfullyRecovered))))
)
case .failed(let errorDescription):
return AlertState(
title: TextState(L10n.ImportWallet.Alert.Failed.title),
message: TextState(L10n.ImportWallet.Alert.Failed.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
}
}
}
// MARK: - Root
extension AlertRequest {
func rootAlertState(_ root: Root) -> AlertState<RootReducer.Action> {
switch root {
case .cantCreateNewWallet(let errorDescription):
return AlertState(
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
message: TextState(L10n.Root.Initialization.Alert.CantCreateNewWallet.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case .cantLoadSeedPhrase:
return AlertState(
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
message: TextState(L10n.Root.Initialization.Alert.CantLoadSeedPhrase.message),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case .cantStartSync(let errorDescription):
return AlertState(
title: TextState(L10n.Root.Debug.Alert.Rewind.CantStartSync.title),
message: TextState(L10n.Root.Debug.Alert.Rewind.CantStartSync.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case .cantStoreThatUserPassedPhraseBackupTest(let errorDescription):
return AlertState(
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
message: TextState(L10n.Root.Initialization.Alert.CantStoreThatUserPassedPhraseBackupTest.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case let .failedToProcessDeeplink(url, errorDescription):
return AlertState(
title: TextState(L10n.Root.Destination.Alert.FailedToProcessDeeplink.title),
message: TextState(L10n.Root.Destination.Alert.FailedToProcessDeeplink.message(url, errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case .initializationFailed(let errorDescription):
return AlertState(
title: TextState(L10n.Root.Initialization.Alert.SdkInitFailed.title),
message: TextState(L10n.Root.Initialization.Alert.Error.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case .rewindFailed(let errorDescription):
return AlertState(
title: TextState(L10n.Root.Debug.Alert.Rewind.Failed.title),
message: TextState(L10n.Root.Debug.Alert.Rewind.Failed.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case .walletStateFailed(let walletState):
return AlertState(
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
message: TextState(L10n.Root.Initialization.Alert.WalletStateFailed.message(walletState)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case .wipeFailed:
return AlertState(
title: TextState(L10n.Root.Initialization.Alert.WipeFailed.title),
message: TextState(""),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case .wipeRequest:
return AlertState(
title: TextState(L10n.Root.Initialization.Alert.Wipe.title),
message: TextState(L10n.Root.Initialization.Alert.Wipe.message),
buttons: [
.destructive(TextState(L10n.General.yes), action: .send(.initialization(.nukeWallet))),
.cancel(TextState(L10n.General.no), action: .send(.dismissAlert))
]
)
}
}
}
// MARK: - Scan
extension AlertRequest {
func scanAlertState(_ scan: Scan) -> AlertState<RootReducer.Action> {
switch scan {
case .cantInitializeCamera(let errorDescription):
return AlertState(
title: TextState(L10n.Scan.Alert.CantInitializeCamera.title),
message: TextState(L10n.Scan.Alert.CantInitializeCamera.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
}
}
}
// MARK: - Settings
extension AlertRequest {
func settingsAlertState(_ settings: Settings) -> AlertState<RootReducer.Action> {
switch settings {
case .cantBackupWallet(let message):
return AlertState<RootReducer.Action>(
title: TextState(L10n.Settings.Alert.CantBackupWallet.title),
message: TextState(L10n.Settings.Alert.CantBackupWallet.message(message)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
case .sendSupportMail:
return AlertState<RootReducer.Action>(
title: TextState(L10n.Settings.Alert.CantSendEmail.title),
message: TextState(L10n.Settings.Alert.CantSendEmail.message),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.uniAlert(.settings(.sendSupportMailFinished))))
)
}
}
}
// MARK: - Wallet Events
extension AlertRequest {
func walletEventsAlertState(_ walletEvents: WalletEvents) -> AlertState<RootReducer.Action> {
switch walletEvents {
case .warnBeforeLeavingApp(let blockExplorerURL):
return AlertState(
title: TextState(L10n.WalletEvent.Alert.LeavingApp.title),
message: TextState(L10n.WalletEvent.Alert.LeavingApp.message),
primaryButton: .cancel(
TextState(L10n.WalletEvent.Alert.LeavingApp.Button.nevermind),
action: .send(.dismissAlert)
),
secondaryButton: .default(
TextState(L10n.WalletEvent.Alert.LeavingApp.Button.seeOnline),
action: .send(.uniAlert(.walletEvents(.openBlockExplorer(blockExplorerURL))))
)
)
}
}
}

View File

@ -16,7 +16,6 @@ struct BalanceBreakdownReducer: ReducerProtocol {
private enum CancelId {}
struct State: Equatable {
@BindingState var alert: AlertState<BalanceBreakdownReducer.Action>?
var autoShieldingThreshold: Zatoshi
var latestBlock: String
var shieldedBalance: Balance
@ -36,9 +35,8 @@ struct BalanceBreakdownReducer: ReducerProtocol {
}
}
enum Action: Equatable, BindableAction {
case binding(BindingAction<BalanceBreakdownReducer.State>)
case dismissAlert
enum Action: Equatable {
case alert(AlertRequest)
case onAppear
case onDisappear
case shieldFunds
@ -56,15 +54,9 @@ struct BalanceBreakdownReducer: ReducerProtocol {
@Dependency(\.walletStorage) var walletStorage
var body: some ReducerProtocol<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .binding:
return .none
case .dismissAlert:
state.alert = nil
case .alert:
return .none
case .onAppear:
@ -95,21 +87,11 @@ struct BalanceBreakdownReducer: ReducerProtocol {
case .shieldFundsSuccess:
state.shieldingFunds = false
state.alert = AlertState(
title: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Success.title),
message: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Success.message),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return .none
return EffectTask(value: .alert(.balanceBreakdown(.shieldFundsSuccess)))
case let .shieldFundsFailure(errorDescription):
state.shieldingFunds = false
state.alert = AlertState(
title: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Failure.title),
message: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Failure.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return .none
return EffectTask(value: .alert(.balanceBreakdown(.shieldFundsFailure(errorDescription))))
case .synchronizerStateChanged(let latestState):
state.shieldedBalance = latestState.shieldedBalance.redacted

View File

@ -47,7 +47,6 @@ struct BalanceBreakdownView: View {
Spacer()
}
.alert(self.store.scope(state: \.alert), dismiss: .dismissAlert)
.onAppear { viewStore.send(.onAppear) }
.onDisappear { viewStore.send(.onDisappear) }
}

View File

@ -15,15 +15,13 @@ typealias ExportLogsViewStore = ViewStore<ExportLogsReducer.State, ExportLogsRed
struct ExportLogsReducer: ReducerProtocol {
struct State: Equatable {
@BindingState var alert: AlertState<ExportLogsReducer.Action>?
var exportLogsDisabled = false
var isSharingLogs = false
var zippedLogsURLs: [URL] = []
}
indirect enum Action: Equatable, BindableAction {
case binding(BindingAction<ExportLogsReducer.State>)
case dismissAlert
indirect enum Action: Equatable {
case alert(AlertRequest)
case start
case finished(URL?)
case failed(String)
@ -33,17 +31,9 @@ struct ExportLogsReducer: ReducerProtocol {
@Dependency(\.logsHandler) var logsHandler
var body: some ReducerProtocol<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .binding:
return .none
case .dismissAlert:
state.exportLogsDisabled = false
state.isSharingLogs = false
state.alert = nil
case .alert:
return .none
case .start:
@ -66,13 +56,9 @@ struct ExportLogsReducer: ReducerProtocol {
return .none
case let .failed(errorDescription):
// TODO: [#527] address the error here https://github.com/zcash/secant-ios-wallet/issues/527
state.alert = AlertState(
title: TextState(L10n.ExportLogs.Alert.Failed.title),
message: TextState(L10n.ExportLogs.Alert.Failed.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return .none
state.exportLogsDisabled = false
state.isSharingLogs = false
return EffectTask(value: .alert(.exportLogs(.failed(errorDescription))))
case .shareFinished:
state.isSharingLogs = false

View File

@ -21,7 +21,6 @@ struct HomeReducer: ReducerProtocol {
case transactionHistory
}
@BindingState var alert: AlertState<HomeReducer.Action>?
var balanceBreakdownState: BalanceBreakdownReducer.State
var destination: Destination?
var profileState: ProfileReducer.State
@ -62,9 +61,9 @@ struct HomeReducer: ReducerProtocol {
}
enum Action: Equatable {
case alert(AlertRequest)
case balanceBreakdown(BalanceBreakdownReducer.Action)
case debugMenuStartup
case dismissAlert
case onAppear
case onDisappear
case profile(ProfileReducer.Action)
@ -183,8 +182,7 @@ struct HomeReducer: ReducerProtocol {
}
case .showSynchronizerErrorAlert(let snapshot):
state.alert = HomeStore.syncErrorAlert(with: snapshot)
return .none
return EffectTask(value: .alert(.home(.syncFailed(snapshot.message, L10n.Home.SyncFailed.dismiss))))
case .balanceBreakdown(.onDisappear):
state.destination = nil
@ -195,18 +193,11 @@ struct HomeReducer: ReducerProtocol {
case .debugMenuStartup:
return .none
case .dismissAlert:
state.alert = nil
return .none
case .syncFailed(let errorMessage):
state.alert = AlertState<HomeReducer.Action>(
title: TextState(L10n.Home.SyncFailed.title),
message: TextState(errorMessage),
primaryButton: .default(TextState(L10n.Home.SyncFailed.retry), action: .send(.retrySync)),
secondaryButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return EffectTask(value: .alert(.home(.syncFailed(errorMessage, L10n.General.ok))))
case .alert:
return .none
}
}
@ -265,18 +256,6 @@ extension HomeViewStore {
}
}
// MARK: Alerts
extension HomeStore {
static func syncErrorAlert(with snapshot: SyncStatusSnapshot) -> AlertState<HomeReducer.Action> {
AlertState<HomeReducer.Action>(
title: TextState(L10n.Home.SyncFailed.title),
message: TextState(snapshot.message),
primaryButton: .default(TextState(L10n.Home.SyncFailed.retry), action: .send(.retrySync)),
secondaryButton: .default(TextState(L10n.Home.SyncFailed.dismiss), action: .send(.dismissAlert))
)
}
}
// MARK: Placeholders
extension HomeReducer.State {
@ -306,11 +285,6 @@ extension HomeStore {
static var error: HomeStore {
HomeStore(
initialState: .init(
alert: HomeStore.syncErrorAlert(
with: SyncStatusSnapshot.snapshotFor(
state: .error(SynchronizerError.networkTimeout)
)
),
balanceBreakdownState: .placeholder,
profileState: .placeholder,
scanState: .placeholder,

View File

@ -29,7 +29,6 @@ struct HomeView: View {
.navigationBarTitleDisplayMode(.inline)
.onAppear(perform: { viewStore.send(.onAppear) })
.onDisappear(perform: { viewStore.send(.onDisappear) })
.alert(self.store.scope(state: \.alert), dismiss: .dismissAlert)
.navigationLinkEmpty(
isActive: viewStore.bindingForDestination(.balanceBreakdown),
destination: { BalanceBreakdownView(store: store.balanceBreakdownStore()) }

View File

@ -43,7 +43,6 @@ struct ImportBirthdayView: View {
.applyScreenBackground()
.scrollableWhenScaledUp()
.onAppear(perform: { viewStore.send(.onAppear) })
.alert(self.store.scope(state: \.alert), dismiss: .dismissAlert)
}
}
}

View File

@ -18,7 +18,6 @@ struct ImportWalletReducer: ReducerProtocol {
case birthday
}
@BindingState var alert: AlertState<ImportWalletReducer.Action>?
var birthdayHeight = "".redacted
var birthdayHeightValue: RedactableBlockHeight?
var destination: Destination?
@ -43,10 +42,9 @@ struct ImportWalletReducer: ReducerProtocol {
}
}
enum Action: Equatable, BindableAction {
case binding(BindingAction<ImportWalletReducer.State>)
enum Action: Equatable {
case alert(AlertRequest)
case birthdayInputChanged(RedactableString)
case dismissAlert
case restoreWallet
case importPrivateOrViewingKey
case initializeSDK
@ -61,8 +59,6 @@ struct ImportWalletReducer: ReducerProtocol {
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
var body: some ReducerProtocol<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .onAppear:
@ -95,11 +91,7 @@ struct ImportWalletReducer: ReducerProtocol {
}
return .none
case .binding:
return .none
case .dismissAlert:
state.alert = nil
case .alert:
return .none
case .restoreWallet:
@ -116,31 +108,14 @@ struct ImportWalletReducer: ReducerProtocol {
try walletStorage.markUserPassedPhraseBackupTest(true)
// notify user
// TODO: [#221] Proper Error/Success handling (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState(L10n.General.success),
message: TextState(L10n.ImportWallet.Alert.Success.message),
dismissButton: .default(
TextState(L10n.General.ok),
action: .send(.successfullyRecovered)
)
return .concatenate(
EffectTask(value: .alert(.importWallet(.succeed))),
EffectTask(value: .initializeSDK)
)
return EffectTask(value: .initializeSDK)
} catch {
// TODO: [#221] Proper Error/Success handling (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState(L10n.ImportWallet.Alert.Failed.title),
message: TextState(L10n.ImportWallet.Alert.Failed.message(error.localizedDescription)),
dismissButton: .default(
TextState(L10n.General.ok),
action: .send(.dismissAlert)
)
)
return EffectTask(value: .alert(.importWallet(.failed(error.localizedDescription))))
}
return .none
case .updateDestination(let destination):
state.destination = destination
return .none
@ -149,7 +124,7 @@ struct ImportWalletReducer: ReducerProtocol {
return .none
case .successfullyRecovered:
return EffectTask(value: .dismissAlert)
return .none
case .initializeSDK:
return .none

View File

@ -39,7 +39,6 @@ struct ImportWalletView: View {
.applyScreenBackground()
.scrollableWhenScaledUp()
.onAppear(perform: { viewStore.send(.onAppear) })
.alert(self.store.scope(state: \.alert), dismiss: .dismissAlert)
.navigationLinkEmpty(
isActive: viewStore.bindingForDestination(.birthday),
destination: { ImportBirthdayView(store: store) }

View File

@ -64,12 +64,7 @@ extension RootReducer {
case let .debug(.rewindDone(errorDescription, _)):
if let errorDescription {
// TODO: [#221] Handle error more properly (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState(L10n.Root.Debug.Alert.Rewind.Failed.title),
message: TextState(L10n.Root.Debug.Alert.Rewind.Failed.message(errorDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return EffectTask(value: .alert(.root(.rewindFailed(errorDescription))))
} else {
return .run { send in
do {
@ -79,8 +74,6 @@ extension RootReducer {
}
}
}
return .none
case let .debug(.updateFlag(flag, isEnabled)):
return walletConfigProvider.update(flag, !isEnabled)
@ -100,13 +93,7 @@ extension RootReducer {
return EffectTask(value: .updateStateAfterConfigUpdate(walletConfig))
case .debug(.cantStartSync(let errorMessage)):
// TODO: [#221] Handle error more properly (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState(L10n.Root.Debug.Alert.Rewind.CantStartSync.title),
message: TextState(L10n.Root.Debug.Alert.Rewind.CantStartSync.message(errorMessage)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return .none
return EffectTask(value: .alert(.root(.cantStartSync(errorMessage))))
default: return .none
}

View File

@ -23,7 +23,6 @@ extension RootReducer {
case welcome
}
@BindingState var alert: AlertState<RootReducer.Action>?
var internalDestination: Destination = .welcome
var previousDestination: Destination?
@ -41,7 +40,6 @@ extension RootReducer {
case deeplinkHome
case deeplinkSend(Zatoshi, String, String)
case deeplinkFailed(URL, String)
case dismissAlert
case updateDestination(RootReducer.DestinationState.Destination)
}
@ -113,16 +111,7 @@ extension RootReducer {
return .none
case let .destination(.deeplinkFailed(url, errorDescription)):
// TODO: [#221] Handle error more properly (https://github.com/zcash/secant-ios-wallet/issues/221)
state.destinationState.alert = AlertState(
title: TextState(L10n.Root.Destination.Alert.FailedToProcessDeeplink.title),
message: TextState(L10n.Root.Destination.Alert.FailedToProcessDeeplink.message(url, errorDescription)),
dismissButton: .default(
TextState(L10n.General.ok),
action: .send(.destination(.dismissAlert))
)
)
return .none
return EffectTask(value: .alert(.root(.failedToProcessDeeplink(url, errorDescription))))
case .home(.walletEvents(.replyTo(let address))):
guard let url = URL(string: "zcash:\(address)") else {
@ -130,12 +119,8 @@ extension RootReducer {
}
return EffectTask(value: .destination(.deeplink(url)))
case .destination(.dismissAlert):
state.destinationState.alert = nil
return .none
case .home, .initialization, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .updateStateAfterConfigUpdate,
.welcome, .binding, .nukeWalletFailed, .nukeWalletSucceeded, .debug, .walletConfigLoaded, .dismissAlert, .exportLogs:
case .home, .initialization, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .updateStateAfterConfigUpdate, .alert,
.welcome, .binding, .nukeWalletFailed, .nukeWalletSucceeded, .debug, .walletConfigLoaded, .dismissAlert, .exportLogs, .uniAlert:
return .none
}

View File

@ -80,22 +80,11 @@ extension RootReducer {
case .initialization(.respondToWalletInitializationState(let walletState)):
switch walletState {
case .failed:
// TODO: [#221] Handle error more properly (https://github.com/zcash/secant-ios-wallet/issues/221)
state.appInitializationState = .failed
state.alert = AlertState(
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
message: TextState(L10n.Root.Initialization.Alert.WalletStateFailed.message(walletState)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return EffectTask(value: .alert(.root(.walletStateFailed(walletState))))
case .keysMissing:
// TODO: [#221] Handle error more properly (https://github.com/zcash/secant-ios-wallet/issues/221)
state.appInitializationState = .keysMissing
state.alert = AlertState(
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
message: TextState(L10n.Root.Initialization.Alert.WalletStateFailed.message(walletState)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return EffectTask(value: .alert(.root(.walletStateFailed(walletState))))
case .initialized, .filesMissing:
if walletState == .filesMissing {
state.appInitializationState = .filesMissing
@ -112,8 +101,6 @@ extension RootReducer {
.cancellable(id: CancelId.self, cancelInFlight: true)
}
return .none
/// Stored wallet is present, database files may or may not be present, trying to initialize app state variables and environments.
/// When initialization succeeds user is taken to the home screen.
case .initialization(.initializeSDK):
@ -122,13 +109,7 @@ extension RootReducer {
guard let storedWallet = state.storedWallet else {
state.appInitializationState = .failed
// TODO: [#221] Handle fatal error more properly (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
message: TextState(L10n.Root.Initialization.Alert.CantLoadSeedPhrase.message),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return .none
return EffectTask(value: .alert(.root(.cantLoadSeedPhrase)))
}
let birthday = state.storedWallet?.birthday?.value() ?? zcashSDKEnvironment.latestCheckpoint
@ -153,13 +134,7 @@ extension RootReducer {
case .initialization(.checkBackupPhraseValidation):
guard let storedWallet = state.storedWallet else {
state.appInitializationState = .failed
// TODO: [#221] Handle fatal error more properly (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
message: TextState(L10n.Root.Initialization.Alert.CantLoadSeedPhrase.message),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return .none
return EffectTask(value: .alert(.root(.cantLoadSeedPhrase)))
}
var landingDestination = RootReducer.DestinationState.Destination.home
@ -200,47 +175,19 @@ extension RootReducer {
EffectTask(value: .phraseValidation(.displayBackedUpPhrase))
)
} catch {
// TODO: [#201] - merge with issue 221 (https://github.com/zcash/secant-ios-wallet/issues/221) and its Error States
state.alert = AlertState(
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
message: TextState(L10n.Root.Initialization.Alert.CantCreateNewWallet.message(error.localizedDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return EffectTask(value: .alert(.root(.cantCreateNewWallet(error.localizedDescription))))
}
return .none
case .phraseValidation(.succeed):
do {
try walletStorage.markUserPassedPhraseBackupTest(true)
return .none
} catch {
// TODO: [#221] error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
message: TextState(
L10n.Root.Initialization.Alert.CantStoreThatUserPassedPhraseBackupTest.message(error.localizedDescription)
),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return EffectTask(value: .alert(.root(.cantStoreThatUserPassedPhraseBackupTest(error.localizedDescription))))
}
return .none
case .initialization(.nukeWalletRequest):
state.destinationState.alert = AlertState(
title: TextState(L10n.Root.Initialization.Alert.Wipe.title),
message: TextState(L10n.Root.Initialization.Alert.Wipe.message),
buttons: [
.destructive(
TextState(L10n.General.yes),
action: .send(.initialization(.nukeWallet))
),
.cancel(
TextState(L10n.General.no),
action: .send(.destination(.dismissAlert))
)
]
)
return .none
return EffectTask(value: .alert(.root(.wipeRequest)))
case .initialization(.nukeWallet):
guard let wipePublisher = sdkSynchronizer.wipe() else {
@ -264,13 +211,6 @@ extension RootReducer {
)
case .nukeWalletFailed:
// TODO: [#221] error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState(L10n.Root.Initialization.Alert.WipeFailed.title),
message: TextState(""),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
let backDestination: EffectTask<RootReducer.Action>
if let previousDestination = state.destinationState.previousDestination {
backDestination = EffectTask(value: .destination(.updateDestination(previousDestination)))
@ -278,6 +218,7 @@ extension RootReducer {
backDestination = EffectTask(value: .destination(.updateDestination(state.destinationState.destination)))
}
return .concatenate(
EffectTask(value: .alert(.root(.wipeFailed))),
.cancel(id: SynchronizerCancelId.self),
backDestination
)
@ -311,20 +252,10 @@ extension RootReducer {
case .initialization(.initializationFailed(let errorMessage)):
state.appInitializationState = .failed
// TODO: [#221] Handle error more properly (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState(L10n.Root.Initialization.Alert.SdkInitFailed.title),
message: TextState(L10n.Root.Initialization.Alert.Error.message(errorMessage)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return .none
return EffectTask(value: .alert(.root(.initializationFailed(errorMessage))))
case .dismissAlert:
state.alert = nil
return .none
case .home, .destination, .onboarding, .phraseDisplay, .phraseValidation, .sandbox,
.welcome, .binding, .debug, .exportLogs:
.welcome, .binding, .debug, .exportLogs, .uniAlert, .dismissAlert, .alert:
return .none
}
}

View File

@ -10,7 +10,6 @@ struct RootReducer: ReducerProtocol {
enum WalletConfigCancelId {}
struct State: Equatable {
@BindingState var alert: AlertState<RootReducer.Action>?
var appInitializationState: InitializationState = .uninitialized
var debugState: DebugState
var destinationState: DestinationState
@ -21,11 +20,13 @@ struct RootReducer: ReducerProtocol {
var phraseDisplayState: RecoveryPhraseDisplayReducer.State
var sandboxState: SandboxReducer.State
var storedWallet: StoredWallet?
@BindingState var uniAlert: AlertState<RootReducer.Action>?
var walletConfig: WalletConfig
var welcomeState: WelcomeReducer.State
}
enum Action: Equatable, BindableAction {
case alert(AlertRequest)
case binding(BindingAction<RootReducer.State>)
case debug(DebugAction)
case dismissAlert
@ -39,6 +40,7 @@ struct RootReducer: ReducerProtocol {
case phraseDisplay(RecoveryPhraseDisplayReducer.Action)
case phraseValidation(RecoveryPhraseValidationFlowReducer.Action)
case sandbox(SandboxReducer.Action)
case uniAlert(AlertAction)
case updateStateAfterConfigUpdate(WalletConfig)
case walletConfigLoaded(WalletConfig)
case welcome(WelcomeReducer.Action)
@ -57,7 +59,8 @@ struct RootReducer: ReducerProtocol {
@Dependency(\.walletStorage) var walletStorage
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
var body: some ReducerProtocol<State, Action> {
@ReducerBuilder<State, Action>
var core: some ReducerProtocol<State, Action> {
BindingReducer()
Scope(state: \.homeState, action: /Action.home) {
@ -94,6 +97,11 @@ struct RootReducer: ReducerProtocol {
debugReduce()
}
var body: some ReducerProtocol<State, Action> {
self.core
.alerts()
}
}
extension RootReducer {

View File

@ -89,7 +89,7 @@ struct RootView: View {
}
}
.onOpenURL(perform: { viewStore.goToDeeplink($0) })
.alert(self.store.scope(state: \.alert), dismiss: .dismissAlert)
.alert(store.scope(state: \.uniAlert), dismiss: .dismissAlert)
shareLogsView(viewStore)
}
@ -197,14 +197,6 @@ private extension RootView {
}
}
}
.alert(self.store.scope(state: \.destinationState.alert), dismiss: .destination(.dismissAlert))
.alert(
self.store.scope(
state: \.exportLogsState.alert,
action: { (_: ExportLogsReducer.Action) in return .exportLogs(.dismissAlert) }
),
dismiss: .dismissAlert
)
.confirmationDialog(
store.scope(state: \.debugState.rescanDialog),
dismiss: .debug(.cancelRescan)

View File

@ -21,7 +21,6 @@ struct ScanReducer: ReducerProtocol {
case unknown
}
@BindingState var alert: AlertState<ScanReducer.Action>?
var isTorchAvailable = false
var isTorchOn = false
var scanStatus: ScanStatus = .unknown
@ -47,7 +46,7 @@ struct ScanReducer: ReducerProtocol {
@Dependency(\.uriParser) var uriParser
enum Action: Equatable {
case dismissAlert
case alert(AlertRequest)
case onAppear
case onDisappear
case found(RedactableString)
@ -59,8 +58,7 @@ struct ScanReducer: ReducerProtocol {
// swiftlint:disable:next cyclomatic_complexity
func reduce(into state: inout State, action: Action) -> ComposableArchitecture.EffectTask<Action> {
switch action {
case .dismissAlert:
state.alert = nil
case .alert:
return .none
case .onAppear:
@ -70,15 +68,10 @@ struct ScanReducer: ReducerProtocol {
// check the torch availability
do {
state.isTorchAvailable = try captureDevice.isTorchAvailable()
return .none
} catch {
// TODO: [#322] Handle error more properly (https://github.com/zcash/secant-ios-wallet/issues/322)
state.alert = AlertState(
title: TextState(L10n.Scan.Alert.CantInitializeCamera.title),
message: TextState(L10n.Scan.Alert.CantInitializeCamera.message(error.localizedDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return EffectTask(value: .alert(.scan(.cantInitializeCamera(error.localizedDescription))))
}
return .none
case .onDisappear:
return .cancel(id: CancelId.self)
@ -115,15 +108,10 @@ struct ScanReducer: ReducerProtocol {
do {
try captureDevice.torch(!state.isTorchOn)
state.isTorchOn.toggle()
return .none
} catch {
// TODO: [#322] handle torch errors (https://github.com/zcash/secant-ios-wallet/issues/322)
state.alert = AlertState(
title: TextState(L10n.Scan.Alert.CantInitializeCamera.title),
message: TextState(L10n.Scan.Alert.CantInitializeCamera.message(error.localizedDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return EffectTask(value: .alert(.scan(.cantInitializeCamera(error.localizedDescription))))
}
return .none
}
}
}

View File

@ -50,7 +50,6 @@ struct ScanView: View {
.applyScreenBackground()
.onAppear { viewStore.send(.onAppear) }
.onDisappear { viewStore.send(.onDisappear) }
.alert(self.store.scope(state: \.alert), dismiss: .dismissAlert)
}
.ignoresSafeArea()
}

View File

@ -12,7 +12,6 @@ struct SettingsReducer: ReducerProtocol {
case backupPhrase
}
@BindingState var alert: AlertState<SettingsReducer.Action>?
var appVersion = ""
var appBuild = ""
var destination: Destination?
@ -23,10 +22,10 @@ struct SettingsReducer: ReducerProtocol {
}
enum Action: BindableAction, Equatable {
case alert(AlertRequest)
case backupWallet
case backupWalletAccessRequest
case binding(BindingAction<SettingsReducer.State>)
case dismissAlert
case exportLogs(ExportLogsReducer.Action)
case onAppear
case phraseDisplay(RecoveryPhraseDisplayReducer.Action)
@ -67,13 +66,7 @@ struct SettingsReducer: ReducerProtocol {
state.phraseDisplayState.phrase = recoveryPhrase
return EffectTask(value: .updateDestination(.backupPhrase))
} catch {
// TODO: [#221] - merge with issue 221 (https://github.com/zcash/secant-ios-wallet/issues/221) and its Error States
state.alert = AlertState(
title: TextState(L10n.Settings.Alert.CantBackupWallet.title),
message: TextState(L10n.Settings.Alert.CantBackupWallet.message(error.localizedDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
return .none
return EffectTask(value: .alert(.settings(.cantBackupWallet(error.localizedDescription))))
}
case .binding(\.$isCrashReportingOn):
@ -86,10 +79,6 @@ struct SettingsReducer: ReducerProtocol {
return .run { [state] _ in
await userStoredPreferences.setIsUserOptedOutOfCrashReporting(state.isCrashReportingOn)
}
case .dismissAlert:
state.alert = nil
return .none
case .exportLogs:
return .none
@ -108,19 +97,17 @@ struct SettingsReducer: ReducerProtocol {
case .sendSupportMail:
if MFMailComposeViewController.canSendMail() {
state.supportData = SupportDataGenerator.generate()
return .none
} else {
state.alert = AlertState(
title: TextState(L10n.Settings.Alert.CantSendEmail.title),
message: TextState(L10n.Settings.Alert.CantSendEmail.message),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.sendSupportMailFinished))
)
return EffectTask(value: .alert(.settings(.sendSupportMail)))
}
return .none
case .sendSupportMailFinished:
state.supportData = nil
return .none
case .alert:
return .none
}
}

View File

@ -71,14 +71,6 @@ struct SettingsView: View {
}
)
.onAppear { viewStore.send(.onAppear) }
.alert(self.store.scope(state: \.alert), dismiss: .dismissAlert)
.alert(
self.store.scope(
state: \.exportLogsState.alert,
action: { (_: ExportLogsReducer.Action) in return .exportLogs(.dismissAlert) }
),
dismiss: .dismissAlert
)
shareLogsView(viewStore)

View File

@ -17,7 +17,6 @@ struct WalletEventsFlowReducer: ReducerProtocol {
var destination: Destination?
@BindingState var alert: AlertState<WalletEventsFlowReducer.Action>?
var latestMinedHeight: BlockHeight?
var isScrollable = true
var requiredTransactionConfirmations = 0
@ -26,8 +25,8 @@ struct WalletEventsFlowReducer: ReducerProtocol {
}
enum Action: Equatable {
case alert(AlertRequest)
case copyToPastboard(RedactableString)
case dismissAlert
case onAppear
case onDisappear
case openBlockExplorer(URL?)
@ -97,27 +96,13 @@ struct WalletEventsFlowReducer: ReducerProtocol {
case .replyTo:
return .none
case .dismissAlert:
state.alert = nil
case .alert:
return .none
case .warnBeforeLeavingApp(let blockExplorerURL):
state.alert = AlertState(
title: TextState(L10n.WalletEvent.Alert.LeavingApp.title),
message: TextState(L10n.WalletEvent.Alert.LeavingApp.message),
primaryButton: .cancel(
TextState(L10n.WalletEvent.Alert.LeavingApp.Button.nevermind),
action: .send(.dismissAlert)
),
secondaryButton: .default(
TextState(L10n.WalletEvent.Alert.LeavingApp.Button.seeOnline),
action: .send(.openBlockExplorer(blockExplorerURL))
)
)
return .none
return EffectTask(value: .alert(.walletEvents(.warnBeforeLeavingApp(blockExplorerURL))))
case .openBlockExplorer(let blockExplorerURL):
state.alert = nil
if let url = blockExplorerURL {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}

View File

@ -0,0 +1,57 @@
//
// OnChangeReducer.swift
// secant-testnet
//
// Created by Lukáš Korba on 29.03.2023.
//
import ComposableArchitecture
extension ReducerProtocol {
@inlinable
public func onChange<ChildState: Equatable>(
of toLocalState: @escaping (State) -> ChildState,
perform additionalEffects: @escaping (ChildState, inout State, Action) -> EffectPublisher<Action, Never>
) -> some ReducerProtocol<State, Action> {
self.onChange(of: toLocalState) { additionalEffects($1, &$2, $3) }
}
@inlinable
public func onChange<ChildState: Equatable>(
of toLocalState: @escaping (State) -> ChildState,
perform additionalEffects: @escaping (ChildState, ChildState, inout State, Action) -> EffectPublisher<Action, Never>
) -> some ReducerProtocol<State, Action> {
ChangeReducer(base: self, toLocalState: toLocalState, perform: additionalEffects)
}
}
@usableFromInline
struct ChangeReducer<Base: ReducerProtocol, ChildState: Equatable>: ReducerProtocol {
@usableFromInline let base: Base
@usableFromInline let toLocalState: (Base.State) -> ChildState
@usableFromInline let perform: (ChildState, ChildState, inout Base.State, Base.Action) -> EffectPublisher<Base.Action, Never>
@usableFromInline
init(
base: Base,
toLocalState: @escaping (Base.State) -> ChildState,
perform: @escaping (ChildState, ChildState, inout Base.State, Base.Action) -> EffectPublisher<Base.Action, Never>
) {
self.base = base
self.toLocalState = toLocalState
self.perform = perform
}
@inlinable
public func reduce(into state: inout Base.State, action: Base.Action) -> EffectPublisher<Base.Action, Never> {
let previousLocalState = self.toLocalState(state)
let effects = self.base.reduce(into: &state, action: action)
let localState = self.toLocalState(state)
return previousLocalState != localState
? .merge(effects, self.perform(previousLocalState, localState, &state, action))
: effects
}
}

View File

@ -52,13 +52,10 @@ class BalanceBreakdownTests: XCTestCase {
}
await store.receive(.shieldFundsSuccess) { state in
state.shieldingFunds = false
state.alert = AlertState(
title: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Success.title),
message: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Success.message),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
}
await store.receive(.alert(.balanceBreakdown(.shieldFundsSuccess)))
// long-living (cancelable) effects need to be properly canceled.
// the .onDisappear action cancels the observer of the synchronizer status change.
await store.send(.onDisappear)
@ -81,13 +78,10 @@ class BalanceBreakdownTests: XCTestCase {
}
await store.receive(.shieldFundsFailure(SynchronizerError.criticalError.localizedDescription)) { state in
state.shieldingFunds = false
state.alert = AlertState(
title: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Failure.title),
message: TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Failure.message(SynchronizerError.criticalError.localizedDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
}
await store.receive(.alert(.balanceBreakdown(.shieldFundsFailure("A critical Error Occurred"))))
// long-living (cancelable) effects need to be properly canceled.
// the .onDisappear action cancels the observer of the synchronizer status change.
await store.send(.onDisappear)

View File

@ -109,8 +109,8 @@ class HomeTests: XCTestCase {
state.synchronizerStatusSnapshot = errorSnapshot
}
store.receive(.showSynchronizerErrorAlert(errorSnapshot)) { state in
state.alert = HomeStore.syncErrorAlert(with: errorSnapshot)
}
store.receive(.showSynchronizerErrorAlert(errorSnapshot))
store.receive(.alert(.home(.syncFailed("Error: Synchronizer failed", "Dismiss"))))
}
}

View File

@ -116,28 +116,7 @@ class ImportWalletTests: XCTestCase {
state.birthdayHeightValue = RedactableBlockHeight(1_700_000)
}
}
func testDismissAlert() throws {
let store = TestStore(
initialState:
ImportWalletReducer.State(
alert: AlertState(
title: TextState("Success"),
message: TextState("The wallet has been successfully recovered."),
dismissButton: .default(
TextState("Ok"),
action: .send(.successfullyRecovered)
)
)
),
reducer: ImportWalletReducer()
)
store.send(.dismissAlert) { state in
state.alert = nil
}
}
func testFormValidity_validBirthday_invalidMnemonic() throws {
let store = TestStore(
initialState: ImportWalletReducer.State(maxWordsCount: 24),
@ -288,7 +267,6 @@ class ImportWalletTests: XCTestCase {
func testRestoreWallet() throws {
let store = TestStore(
initialState: ImportWalletReducer.State(
alert: nil,
birthdayHeight: "1700000".redacted,
birthdayHeightValue: RedactableBlockHeight(1_700_000),
importedSeedPhrase: """
@ -308,17 +286,10 @@ class ImportWalletTests: XCTestCase {
store.dependencies.mnemonic = .noOp
store.dependencies.walletStorage = .noOp
store.send(.restoreWallet) { state in
state.alert = AlertState(
title: TextState("Success"),
message: TextState("The wallet has been successfully recovered."),
dismissButton: .default(
TextState("Ok"),
action: .send(.successfullyRecovered)
)
)
}
store.send(.restoreWallet)
store.receive(.alert(.importWallet(.succeed)))
store.receive(.initializeSDK)
}
}

View File

@ -168,7 +168,10 @@ class AppInitializationTests: XCTestCase {
await store.receive(.initialization(.respondToWalletInitializationState(.keysMissing))) { state in
state.appInitializationState = .keysMissing
state.alert = AlertState(
}
await store.receive(.alert(.root(.walletStateFailed(.keysMissing)))) { state in
state.uniAlert = AlertState(
title: TextState("Wallet initialisation failed."),
message: TextState("App initialisation state: keysMissing."),
dismissButton: .default(TextState("Ok"), action: .send(.dismissAlert))

View File

@ -118,7 +118,10 @@ class RootTests: XCTestCase {
store.send(.initialization(.respondToWalletInitializationState(.keysMissing))) { state in
state.appInitializationState = .keysMissing
state.alert = AlertState(
}
store.receive(.alert(.root(.walletStateFailed(.keysMissing)))) { state in
state.uniAlert = AlertState(
title: TextState("Wallet initialisation failed."),
message: TextState("App initialisation state: keysMissing."),
dismissButton: .default(TextState("Ok"), action: .send(.dismissAlert))
@ -140,21 +143,26 @@ class RootTests: XCTestCase {
store.send(.initialization(.respondToWalletInitializationState(.filesMissing))) { state in
state.appInitializationState = .filesMissing
}
store.receive(.initialization(.initializeSDK))
store.receive(.initialization(.initializeSDK))
store.receive(.initialization(.checkBackupPhraseValidation)) { state in
// failed is expected because environment is throwing errors
state.appInitializationState = .failed
state.alert = AlertState(
}
store.receive(.initialization(.initializationFailed(walletStorageError.localizedDescription)))
store.receive(.alert(.root(.cantLoadSeedPhrase))) { state in
state.uniAlert = AlertState(
title: TextState("Wallet initialisation failed."),
message: TextState("Can't load seed phrase from local storage."),
dismissButton: .default(TextState("Ok"), action: .send(.dismissAlert))
)
}
store.receive(.initialization(.initializationFailed(walletStorageError.localizedDescription))) { state in
state.alert = AlertState(
store.receive(.alert(.root(.initializationFailed("The operation couldnt be completed. (Swift.String error 1.)")))) { state in
state.uniAlert = AlertState(
title: TextState("Failed to initialize the SDK"),
message: TextState("Error: \(walletStorageError.localizedDescription)"),
dismissButton: .default(TextState("Ok"), action: .send(.dismissAlert))
@ -179,15 +187,20 @@ class RootTests: XCTestCase {
store.receive(.initialization(.checkBackupPhraseValidation)) { state in
// failed is expected because environment is throwing errors
state.appInitializationState = .failed
state.alert = AlertState(
}
store.receive(.initialization(.initializationFailed(walletStorageError.localizedDescription)))
store.receive(.alert(.root(.cantLoadSeedPhrase))) { state in
state.uniAlert = AlertState(
title: TextState("Wallet initialisation failed."),
message: TextState("Can't load seed phrase from local storage."),
dismissButton: .default(TextState("Ok"), action: .send(.dismissAlert))
)
}
store.receive(.initialization(.initializationFailed(walletStorageError.localizedDescription))) { state in
state.alert = AlertState(
store.receive(.alert(.root(.initializationFailed("The operation couldnt be completed. (Swift.String error 1.)")))) { state in
state.uniAlert = AlertState(
title: TextState("Failed to initialize the SDK"),
message: TextState("Error: \(walletStorageError.localizedDescription)"),
dismissButton: .default(TextState("Ok"), action: .send(.dismissAlert))