* UI + stores/reducers updated * simple send done cleanup flag for sending deny any attempt to send when sending is still in flight cleanup unit tests
This commit is contained in:
parent
f71350f610
commit
e54ea3aa18
|
@ -103,6 +103,8 @@
|
|||
9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */; };
|
||||
9E5BF63C2818305D00BA3F17 /* TransactionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF63B2818305D00BA3F17 /* TransactionState.swift */; };
|
||||
9E5BF63F2819542C00BA3F17 /* TransactionHistoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF63E2819542C00BA3F17 /* TransactionHistoryTests.swift */; };
|
||||
9E5BF641281FD7B600BA3F17 /* TransactionFailedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF640281FD7B600BA3F17 /* TransactionFailedView.swift */; };
|
||||
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF643281FEC9900BA3F17 /* SendTests.swift */; };
|
||||
9E69A24D27FB002800A55317 /* Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* Welcome.swift */; };
|
||||
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */; };
|
||||
9EAFEB822805793200199FC9 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB812805793200199FC9 /* AppReducerTests.swift */; };
|
||||
|
@ -142,9 +144,9 @@
|
|||
F9971A6C27680E1000A2DB75 /* WalletInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9971A6A27680E1000A2DB75 /* WalletInfoView.swift */; };
|
||||
F9C165B4274031F600592F76 /* Bindings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165B3274031F600592F76 /* Bindings.swift */; };
|
||||
F9C165BF2740403600592F76 /* SendStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165B72740403600592F76 /* SendStore.swift */; };
|
||||
F9C165C02740403600592F76 /* ApproveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165B92740403600592F76 /* ApproveView.swift */; };
|
||||
F9C165C22740403600592F76 /* CreateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165BB2740403600592F76 /* CreateView.swift */; };
|
||||
F9C165C42740403600592F76 /* SentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165BD2740403600592F76 /* SentView.swift */; };
|
||||
F9C165C02740403600592F76 /* TransactionConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165B92740403600592F76 /* TransactionConfirmationView.swift */; };
|
||||
F9C165C22740403600592F76 /* CreateTransactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165BB2740403600592F76 /* CreateTransactionView.swift */; };
|
||||
F9C165C42740403600592F76 /* TransactionSentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165BD2740403600592F76 /* TransactionSentView.swift */; };
|
||||
F9C165CB2741AB5D00592F76 /* SendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165CA2741AB5D00592F76 /* SendView.swift */; };
|
||||
F9EEB8162742C2210032EEB8 /* WithStateBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9EEB8152742C2210032EEB8 /* WithStateBinding.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
@ -268,6 +270,8 @@
|
|||
9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecantButtonStyles.swift; sourceTree = "<group>"; };
|
||||
9E5BF63B2818305D00BA3F17 /* TransactionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionState.swift; sourceTree = "<group>"; };
|
||||
9E5BF63E2819542C00BA3F17 /* TransactionHistoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryTests.swift; sourceTree = "<group>"; };
|
||||
9E5BF640281FD7B600BA3F17 /* TransactionFailedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionFailedView.swift; sourceTree = "<group>"; };
|
||||
9E5BF643281FEC9900BA3F17 /* SendTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTests.swift; sourceTree = "<group>"; };
|
||||
9E69A24C27FB002800A55317 /* Welcome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Welcome.swift; sourceTree = "<group>"; };
|
||||
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorage.swift; sourceTree = "<group>"; };
|
||||
9EAFEB812805793200199FC9 /* AppReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducerTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -306,9 +310,9 @@
|
|||
F9971A6A27680E1000A2DB75 /* WalletInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletInfoView.swift; sourceTree = "<group>"; };
|
||||
F9C165B3274031F600592F76 /* Bindings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bindings.swift; sourceTree = "<group>"; };
|
||||
F9C165B72740403600592F76 /* SendStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendStore.swift; sourceTree = "<group>"; };
|
||||
F9C165B92740403600592F76 /* ApproveView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApproveView.swift; sourceTree = "<group>"; };
|
||||
F9C165BB2740403600592F76 /* CreateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateView.swift; sourceTree = "<group>"; };
|
||||
F9C165BD2740403600592F76 /* SentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentView.swift; sourceTree = "<group>"; };
|
||||
F9C165B92740403600592F76 /* TransactionConfirmationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionConfirmationView.swift; sourceTree = "<group>"; };
|
||||
F9C165BB2740403600592F76 /* CreateTransactionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateTransactionView.swift; sourceTree = "<group>"; };
|
||||
F9C165BD2740403600592F76 /* TransactionSentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSentView.swift; sourceTree = "<group>"; };
|
||||
F9C165CA2741AB5D00592F76 /* SendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendView.swift; sourceTree = "<group>"; };
|
||||
F9EEB8152742C2210032EEB8 /* WithStateBinding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithStateBinding.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -464,6 +468,7 @@
|
|||
0D4E7A1926B364180058B01E /* secantTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E5BF642281FEC8700BA3F17 /* SendTests */,
|
||||
9E5BF63D281953F900BA3F17 /* TransactionHistoryTests */,
|
||||
9EAFEB802805791400199FC9 /* AppReducerTests */,
|
||||
9EF8135927ECC25E0075AF48 /* UtilTests */,
|
||||
|
@ -792,6 +797,14 @@
|
|||
path = TransactionHistoryTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E5BF642281FEC8700BA3F17 /* SendTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E5BF643281FEC9900BA3F17 /* SendTests.swift */,
|
||||
);
|
||||
path = SendTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9EAFEB802805791400199FC9 /* AppReducerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -988,9 +1001,10 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
F9C165CA2741AB5D00592F76 /* SendView.swift */,
|
||||
F9C165BB2740403600592F76 /* CreateView.swift */,
|
||||
F9C165B92740403600592F76 /* ApproveView.swift */,
|
||||
F9C165BD2740403600592F76 /* SentView.swift */,
|
||||
F9C165BB2740403600592F76 /* CreateTransactionView.swift */,
|
||||
F9C165B92740403600592F76 /* TransactionConfirmationView.swift */,
|
||||
F9C165BD2740403600592F76 /* TransactionSentView.swift */,
|
||||
9E5BF640281FD7B600BA3F17 /* TransactionFailedView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1216,6 +1230,7 @@
|
|||
2EB1C5E827D77F6100BC43D7 /* TextFieldStore.swift in Sources */,
|
||||
9EAFEB8A2806F48100199FC9 /* ZCashSDKEnvironment.swift in Sources */,
|
||||
0D8A43C4272AEEDE005A6414 /* SecantTextStyles.swift in Sources */,
|
||||
9E5BF641281FD7B600BA3F17 /* TransactionFailedView.swift in Sources */,
|
||||
9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */,
|
||||
0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */,
|
||||
0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift in Sources */,
|
||||
|
@ -1268,7 +1283,7 @@
|
|||
663FAB9C271D874D00E495F8 /* ActiveButton.swift in Sources */,
|
||||
9E2F1C842809B606004E65FE /* DebugMenu.swift in Sources */,
|
||||
9E02B5C3280458D2005B809B /* WrappedDerivationTool.swift in Sources */,
|
||||
F9C165C02740403600592F76 /* ApproveView.swift in Sources */,
|
||||
F9C165C02740403600592F76 /* TransactionConfirmationView.swift in Sources */,
|
||||
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */,
|
||||
9EAFEB84280597B700199FC9 /* WrappedSecItem.swift in Sources */,
|
||||
9E2AC10327DA28200042AA47 /* WalletStorage.swift in Sources */,
|
||||
|
@ -1281,7 +1296,7 @@
|
|||
0D8A43C6272B129C005A6414 /* WordChipGrid.swift in Sources */,
|
||||
66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */,
|
||||
0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */,
|
||||
F9C165C22740403600592F76 /* CreateView.swift in Sources */,
|
||||
F9C165C22740403600592F76 /* CreateTransactionView.swift in Sources */,
|
||||
F9C165B4274031F600592F76 /* Bindings.swift in Sources */,
|
||||
2E35F99A27B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift in Sources */,
|
||||
9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */,
|
||||
|
@ -1301,7 +1316,7 @@
|
|||
0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */,
|
||||
2E8719CB27FB09990082C926 /* TransactionTextField.swift in Sources */,
|
||||
6654C7412715A47300901167 /* Onboarding.swift in Sources */,
|
||||
F9C165C42740403600592F76 /* SentView.swift in Sources */,
|
||||
F9C165C42740403600592F76 /* TransactionSentView.swift in Sources */,
|
||||
F9971A5927680DDE00A2DB75 /* RequestStore.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -1313,6 +1328,7 @@
|
|||
0DFE93DF272C6D4B000FCCA5 /* RecoveryPhraseBackupTests.swift in Sources */,
|
||||
6654C7442715A4AC00901167 /* OnboardingStoreTests.swift in Sources */,
|
||||
9EAFEB862805A23100199FC9 /* WrappedSecItemTests.swift in Sources */,
|
||||
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */,
|
||||
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
|
||||
9EAFEB822805793200199FC9 /* AppReducerTests.swift in Sources */,
|
||||
9E5BF63F2819542C00BA3F17 /* TransactionHistoryTests.swift in Sources */,
|
||||
|
|
|
@ -52,7 +52,7 @@ struct AppEnvironment {
|
|||
|
||||
extension AppEnvironment {
|
||||
static let live = AppEnvironment(
|
||||
wrappedSDKSynchronizer: MockWrappedSDKSynchronizer(),
|
||||
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer(),
|
||||
databaseFiles: .live(),
|
||||
mnemonicSeedPhraseProvider: .live,
|
||||
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
|
||||
|
@ -291,7 +291,10 @@ extension AppReducer {
|
|||
action: /AppAction.home,
|
||||
environment: { environment in
|
||||
HomeEnvironment(
|
||||
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
|
||||
scheduler: environment.scheduler,
|
||||
walletStorage: environment.walletStorage,
|
||||
wrappedDerivationTool: environment.wrappedDerivationTool,
|
||||
wrappedSDKSynchronizer: environment.wrappedSDKSynchronizer
|
||||
)
|
||||
}
|
||||
|
|
|
@ -38,7 +38,10 @@ enum HomeAction: Equatable {
|
|||
}
|
||||
|
||||
struct HomeEnvironment {
|
||||
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
|
||||
let scheduler: AnySchedulerOf<DispatchQueue>
|
||||
let walletStorage: WalletStorageInteractor
|
||||
let wrappedDerivationTool: WrappedDerivationTool
|
||||
let wrappedSDKSynchronizer: WrappedSDKSynchronizer
|
||||
}
|
||||
|
||||
|
@ -52,7 +55,8 @@ extension HomeReducer {
|
|||
static let `default` = HomeReducer.combine(
|
||||
[
|
||||
homeReducer,
|
||||
historyReducer
|
||||
historyReducer,
|
||||
sendReducer
|
||||
]
|
||||
)
|
||||
.debug()
|
||||
|
@ -111,9 +115,6 @@ extension HomeReducer {
|
|||
case .request(let action):
|
||||
return .none
|
||||
|
||||
case .send(let action):
|
||||
return .none
|
||||
|
||||
case .scan(let action):
|
||||
return .none
|
||||
|
||||
|
@ -125,6 +126,12 @@ extension HomeReducer {
|
|||
|
||||
case .transactionHistory(let historyAction):
|
||||
return .none
|
||||
|
||||
case .send(.updateRoute(.done)):
|
||||
return Effect(value: .updateRoute(nil))
|
||||
|
||||
case .send(let action):
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +145,20 @@ extension HomeReducer {
|
|||
)
|
||||
}
|
||||
)
|
||||
|
||||
private static let sendReducer: HomeReducer = SendReducer.default.pullback(
|
||||
state: \HomeState.sendState,
|
||||
action: /HomeAction.send,
|
||||
environment: { environment in
|
||||
SendEnvironment(
|
||||
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
|
||||
scheduler: environment.scheduler,
|
||||
walletStorage: environment.walletStorage,
|
||||
wrappedDerivationTool: environment.wrappedDerivationTool,
|
||||
wrappedSDKSynchronizer: environment.wrappedSDKSynchronizer
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - HomeViewStore
|
||||
|
|
|
@ -14,9 +14,6 @@ struct HomeView: View {
|
|||
|
||||
sendButton(viewStore)
|
||||
|
||||
requestButton(viewStore)
|
||||
.padding(.top, 140)
|
||||
|
||||
VStack {
|
||||
Text("balance: \(viewStore.totalBalance)")
|
||||
.accessDebugMenuWithHiddenGesture {
|
||||
|
@ -72,35 +69,6 @@ extension HomeView {
|
|||
}
|
||||
}
|
||||
|
||||
func requestButton(_ viewStore: HomeViewStore) -> some View {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
Text("home.request")
|
||||
.shadow(color: Asset.Colors.Buttons.buttonsTitleShadow.color, radius: 2, x: 0, y: 2)
|
||||
.frame(
|
||||
minWidth: 0,
|
||||
maxWidth: .infinity,
|
||||
minHeight: 0,
|
||||
maxHeight: .infinity
|
||||
)
|
||||
.foregroundColor(Asset.Colors.Text.secondaryButtonText.color)
|
||||
.background(Asset.Colors.Buttons.secondaryButton.color)
|
||||
.cornerRadius(12)
|
||||
.frame(height: 60)
|
||||
.padding(.horizontal, 50)
|
||||
.neumorphicButton()
|
||||
.navigationLink(
|
||||
isActive: viewStore.bindingForRoute(.request),
|
||||
destination: {
|
||||
RequestView(store: store.requestStore())
|
||||
}
|
||||
)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
func sendButton(_ viewStore: HomeViewStore) -> some View {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
@ -161,7 +129,10 @@ extension HomeStore {
|
|||
initialState: .placeholder,
|
||||
reducer: .default.debug(),
|
||||
environment: HomeEnvironment(
|
||||
mnemonicSeedPhraseProvider: .live,
|
||||
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
|
||||
walletStorage: .live(),
|
||||
wrappedDerivationTool: .live(),
|
||||
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
|
||||
)
|
||||
)
|
||||
|
|
|
@ -19,6 +19,7 @@ struct SandboxView: View {
|
|||
case .history:
|
||||
TransactionHistoryView(store: store.historyStore())
|
||||
case .send:
|
||||
EmptyView()
|
||||
SendView(
|
||||
store: .init(
|
||||
initialState: .placeholder,
|
||||
|
@ -26,7 +27,13 @@ struct SandboxView: View {
|
|||
whenDone: { SandboxViewStore(store).send(.updateRoute(nil)) }
|
||||
)
|
||||
.debug(),
|
||||
environment: ()
|
||||
environment: SendEnvironment(
|
||||
mnemonicSeedPhraseProvider: .live,
|
||||
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
|
||||
walletStorage: .live(),
|
||||
wrappedDerivationTool: .live(),
|
||||
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
|
||||
)
|
||||
)
|
||||
)
|
||||
case .recoveryPhraseDisplay:
|
||||
|
|
|
@ -1,41 +1,126 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
|
||||
struct Transaction: Equatable {
|
||||
var amount: Int64
|
||||
var memo: String
|
||||
var toAddress: String
|
||||
|
||||
var amountString: String {
|
||||
get { amount == 0 ? "" : String(format: "%.7f", amount.asHumanReadableZecBalance()) }
|
||||
set { amount = Int64((newValue as NSString).doubleValue * 100_000_000) }
|
||||
}
|
||||
}
|
||||
|
||||
extension Transaction {
|
||||
static var placeholder: Self {
|
||||
.init(
|
||||
amount: 0,
|
||||
memo: "",
|
||||
toAddress: ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct SendState: Equatable {
|
||||
enum Route: Equatable {
|
||||
case showConfirmation
|
||||
case showSent
|
||||
case success
|
||||
case failure
|
||||
case done
|
||||
}
|
||||
|
||||
var route: Route?
|
||||
|
||||
var isSendingTransaction = false
|
||||
var transaction: Transaction
|
||||
var route: SendView.Route?
|
||||
}
|
||||
|
||||
enum SendAction: Equatable {
|
||||
case sendConfirmationPressed
|
||||
case sendTransactionResult(Result<TransactionState, NSError>)
|
||||
case updateTransaction(Transaction)
|
||||
case updateRoute(SendView.Route?)
|
||||
case updateRoute(SendState.Route?)
|
||||
}
|
||||
|
||||
struct SendEnvironment {
|
||||
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
|
||||
let scheduler: AnySchedulerOf<DispatchQueue>
|
||||
let walletStorage: WalletStorageInteractor
|
||||
let wrappedDerivationTool: WrappedDerivationTool
|
||||
let wrappedSDKSynchronizer: WrappedSDKSynchronizer
|
||||
}
|
||||
|
||||
// MARK: - SendReducer
|
||||
|
||||
typealias SendReducer = Reducer<SendState, SendAction, Void>
|
||||
typealias SendReducer = Reducer<SendState, SendAction, SendEnvironment>
|
||||
|
||||
extension SendReducer {
|
||||
private struct SyncStatusUpdatesID: Hashable {}
|
||||
|
||||
static let `default` = Reducer<SendState, SendAction, Void> { state, action, _ in
|
||||
static let `default` = Reducer<SendState, SendAction, SendEnvironment> { state, action, environment in
|
||||
switch action {
|
||||
case let .updateTransaction(transaction):
|
||||
state.transaction = transaction
|
||||
return .none
|
||||
|
||||
case .updateRoute(.failure):
|
||||
state.route = .failure
|
||||
state.isSendingTransaction = false
|
||||
return .none
|
||||
|
||||
case let .updateRoute(route):
|
||||
state.route = route
|
||||
return .none
|
||||
|
||||
case .sendConfirmationPressed:
|
||||
guard !state.isSendingTransaction else {
|
||||
return .none
|
||||
}
|
||||
|
||||
do {
|
||||
let storedWallet = try environment.walletStorage.exportWallet()
|
||||
let seedBytes = try environment.mnemonicSeedPhraseProvider.toSeed(storedWallet.seedPhrase)
|
||||
guard let spendingKey = try environment.wrappedDerivationTool.deriveSpendingKeys(seedBytes, 1).first else {
|
||||
return Effect(value: .updateRoute(.failure))
|
||||
}
|
||||
|
||||
state.isSendingTransaction = true
|
||||
|
||||
return environment.wrappedSDKSynchronizer.sendTransaction(
|
||||
with: spendingKey,
|
||||
zatoshi: Int64(state.transaction.amount),
|
||||
to: state.transaction.toAddress,
|
||||
memo: state.transaction.memo,
|
||||
from: 0
|
||||
)
|
||||
.receive(on: environment.scheduler)
|
||||
.map(SendAction.sendTransactionResult)
|
||||
.eraseToEffect()
|
||||
} catch {
|
||||
return Effect(value: .updateRoute(.failure))
|
||||
}
|
||||
|
||||
case .sendTransactionResult(let result):
|
||||
state.isSendingTransaction = false
|
||||
do {
|
||||
let transaction = try result.get()
|
||||
return Effect(value: .updateRoute(.success))
|
||||
} catch {
|
||||
return Effect(value: .updateRoute(.failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static func `default`(whenDone: @escaping () -> Void) -> SendReducer {
|
||||
SendReducer { state, action, _ in
|
||||
SendReducer { state, action, environment in
|
||||
switch action {
|
||||
case let .updateRoute(route) where route == .done:
|
||||
return Effect.fireAndForget(whenDone)
|
||||
default:
|
||||
return Self.default.run(&state, action, ())
|
||||
return Self.default.run(&state, action, environment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,31 +142,38 @@ extension SendViewStore {
|
|||
)
|
||||
}
|
||||
|
||||
var routeBinding: Binding<SendView.Route?> {
|
||||
var routeBinding: Binding<SendState.Route?> {
|
||||
self.binding(
|
||||
get: \.route,
|
||||
send: SendAction.updateRoute
|
||||
)
|
||||
}
|
||||
|
||||
var bindingForApprove: Binding<Bool> {
|
||||
var bindingForConfirmation: Binding<Bool> {
|
||||
self.routeBinding.map(
|
||||
extract: { $0 == .showApprove || self.bindingForSent.wrappedValue },
|
||||
embed: { $0 ? SendView.Route.showApprove : nil }
|
||||
extract: { $0 == .showConfirmation || self.bindingForSuccess.wrappedValue || self.bindingForFailure.wrappedValue },
|
||||
embed: { $0 ? SendState.Route.showConfirmation : nil }
|
||||
)
|
||||
}
|
||||
|
||||
var bindingForSent: Binding<Bool> {
|
||||
var bindingForSuccess: Binding<Bool> {
|
||||
self.routeBinding.map(
|
||||
extract: { $0 == .showSent || self.bindingForDone.wrappedValue },
|
||||
embed: { $0 ? SendView.Route.showSent : SendView.Route.showApprove }
|
||||
extract: { $0 == .success || self.bindingForDone.wrappedValue },
|
||||
embed: { $0 ? SendState.Route.success : SendState.Route.showConfirmation }
|
||||
)
|
||||
}
|
||||
|
||||
var bindingForFailure: Binding<Bool> {
|
||||
self.routeBinding.map(
|
||||
extract: { $0 == .failure || self.bindingForDone.wrappedValue },
|
||||
embed: { $0 ? SendState.Route.failure : SendState.Route.showConfirmation }
|
||||
)
|
||||
}
|
||||
|
||||
var bindingForDone: Binding<Bool> {
|
||||
self.routeBinding.map(
|
||||
extract: { $0 == .done },
|
||||
embed: { $0 ? SendView.Route.done : SendView.Route.showSent }
|
||||
embed: { $0 ? SendState.Route.done : SendState.Route.showConfirmation }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +182,17 @@ extension SendViewStore {
|
|||
|
||||
extension SendState {
|
||||
static var placeholder: Self {
|
||||
.init(transaction: .placeholder, route: nil)
|
||||
.init(route: nil, transaction: .placeholder)
|
||||
}
|
||||
|
||||
static var emptyPlaceholder: Self {
|
||||
.init(
|
||||
route: nil,
|
||||
transaction: .init(
|
||||
amount: 0,
|
||||
memo: "",
|
||||
toAddress: ""
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct Approve: View {
|
||||
let transaction: Transaction
|
||||
@Binding var isComplete: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Button(
|
||||
action: { isComplete = true },
|
||||
label: { Text("Go to sent") }
|
||||
)
|
||||
.primaryButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding()
|
||||
|
||||
Text("\(String(dumping: transaction))")
|
||||
Text("\(String(dumping: isComplete))")
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle(Text("2. Approve"))
|
||||
}
|
||||
}
|
||||
|
||||
struct Approve_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
StateContainer(initialState: (Transaction.placeholder, false)) {
|
||||
Approve(
|
||||
transaction: $0.0.wrappedValue,
|
||||
isComplete: $0.1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct CreateTransaction: View {
|
||||
@Binding var transaction: Transaction
|
||||
@Binding var isComplete: Bool
|
||||
|
||||
var body: some View {
|
||||
UITextView.appearance().backgroundColor = .clear
|
||||
|
||||
return VStack {
|
||||
VStack {
|
||||
Text("ZEC Amount")
|
||||
|
||||
TextField(
|
||||
"ZEC Amount",
|
||||
text: $transaction.amountString
|
||||
)
|
||||
.padding()
|
||||
.background(Color.white)
|
||||
.foregroundColor(Asset.Colors.Text.importSeedEditor.color)
|
||||
}
|
||||
.padding()
|
||||
|
||||
VStack {
|
||||
Text("To Address")
|
||||
|
||||
TextField(
|
||||
"Address",
|
||||
text: $transaction.toAddress
|
||||
)
|
||||
.font(.system(size: 14))
|
||||
.padding()
|
||||
.background(Color.white)
|
||||
.foregroundColor(Asset.Colors.Text.importSeedEditor.color)
|
||||
}
|
||||
.padding()
|
||||
|
||||
VStack {
|
||||
Text("Memo")
|
||||
|
||||
TextEditor(text: $transaction.memo)
|
||||
.frame(maxWidth: .infinity, maxHeight: 150, alignment: .center)
|
||||
.importSeedEditorModifier()
|
||||
}
|
||||
.padding()
|
||||
|
||||
Button(
|
||||
action: { isComplete = true },
|
||||
label: { Text("Send") }
|
||||
)
|
||||
.activeButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.applyScreenBackground()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct Create_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
StateContainer(
|
||||
initialState: (
|
||||
Transaction.placeholder,
|
||||
false
|
||||
)
|
||||
) {
|
||||
CreateTransaction(
|
||||
transaction: $0.0,
|
||||
isComplete: $0.1
|
||||
)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
extension SendStore {
|
||||
static var placeholder: SendStore {
|
||||
return SendStore(
|
||||
initialState: .init(
|
||||
route: nil,
|
||||
transaction: .placeholder
|
||||
),
|
||||
reducer: .default,
|
||||
environment: SendEnvironment(
|
||||
mnemonicSeedPhraseProvider: .live,
|
||||
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
|
||||
walletStorage: .live(),
|
||||
wrappedDerivationTool: .live(),
|
||||
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,73 +0,0 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct Create: View {
|
||||
@Binding var transaction: Transaction
|
||||
@Binding var isComplete: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Button(
|
||||
action: { isComplete = true },
|
||||
label: { Text("Go To Approve") }
|
||||
)
|
||||
.primaryButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding()
|
||||
|
||||
TextField(
|
||||
"Amount",
|
||||
text: $transaction
|
||||
.amount
|
||||
.compactMap(
|
||||
extract: String.init,
|
||||
embed: UInt.init
|
||||
)
|
||||
)
|
||||
.padding()
|
||||
|
||||
TextField(
|
||||
"Address",
|
||||
text: $transaction.toAddress
|
||||
)
|
||||
|
||||
Text("\(String(dumping: transaction))")
|
||||
Text("\(String(dumping: isComplete))")
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.navigationTitle(Text("1. Create"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct Create_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
StateContainer(initialState: (Transaction.placeholder, false)) {
|
||||
Create(
|
||||
transaction: $0.0,
|
||||
isComplete: $0.1
|
||||
)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
extension SendStore {
|
||||
static var placeholder: SendStore {
|
||||
return SendStore(
|
||||
initialState: .init(
|
||||
transaction: .placeholder,
|
||||
route: nil
|
||||
),
|
||||
reducer: .default,
|
||||
environment: ()
|
||||
)
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,57 +1,27 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct Transaction: Identifiable, Equatable, Hashable {
|
||||
var id: Int
|
||||
var amount: UInt
|
||||
var memo: String
|
||||
var toAddress: String
|
||||
var fromAddress: String
|
||||
}
|
||||
|
||||
extension Transaction {
|
||||
static var placeholder: Self {
|
||||
.init(
|
||||
id: 2,
|
||||
amount: 123,
|
||||
memo: "defaultMemo",
|
||||
toAddress: "ToAddress",
|
||||
fromAddress: "FromAddress"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct SendView: View {
|
||||
enum Route: Equatable {
|
||||
case showApprove
|
||||
case showSent
|
||||
case done
|
||||
}
|
||||
|
||||
let store: Store<SendState, SendAction>
|
||||
|
||||
var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
Create(
|
||||
CreateTransaction(
|
||||
transaction: viewStore.bindingForTransaction,
|
||||
isComplete: viewStore.bindingForApprove
|
||||
isComplete: viewStore.bindingForConfirmation
|
||||
)
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForApprove,
|
||||
isActive: viewStore.bindingForConfirmation,
|
||||
destination: {
|
||||
Approve(
|
||||
transaction: viewStore.transaction,
|
||||
isComplete: viewStore.bindingForSent
|
||||
)
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForSent,
|
||||
destination: {
|
||||
Sent(
|
||||
transaction: viewStore.transaction,
|
||||
isComplete: viewStore.bindingForDone
|
||||
)
|
||||
}
|
||||
)
|
||||
TransactionConfirmation(viewStore: viewStore)
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForSuccess,
|
||||
destination: { TransactionSent(viewStore: viewStore) }
|
||||
)
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForFailure,
|
||||
destination: { TransactionFailed(viewStore: viewStore) }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -64,11 +34,17 @@ struct SendView_Previews: PreviewProvider {
|
|||
SendView(
|
||||
store: .init(
|
||||
initialState: .init(
|
||||
transaction: .placeholder,
|
||||
route: nil
|
||||
route: nil,
|
||||
transaction: .placeholder
|
||||
),
|
||||
reducer: .default,
|
||||
environment: ()
|
||||
environment: SendEnvironment(
|
||||
mnemonicSeedPhraseProvider: .live,
|
||||
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
|
||||
walletStorage: .live(),
|
||||
wrappedDerivationTool: .live(),
|
||||
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
|
||||
)
|
||||
)
|
||||
)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct Sent: View {
|
||||
var transaction: Transaction
|
||||
@Binding var isComplete: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Button(
|
||||
action: {
|
||||
isComplete = true
|
||||
},
|
||||
label: { Text("Done") }
|
||||
)
|
||||
.primaryButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding()
|
||||
|
||||
Text("\(String(dumping: transaction))")
|
||||
Text("\(String(dumping: isComplete))")
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle(Text("3. Sent"))
|
||||
}
|
||||
}
|
||||
|
||||
struct Done_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Sent(transaction: .placeholder, isComplete: .constant(false))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct TransactionConfirmation: View {
|
||||
let viewStore: SendViewStore
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Send \(String(format: "%.7f", Int64(viewStore.transaction.amount).asHumanReadableZecBalance())) ZEC")
|
||||
.padding()
|
||||
|
||||
Text("To \(viewStore.transaction.toAddress)")
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(
|
||||
action: { viewStore.send(.sendConfirmationPressed) },
|
||||
label: { Text("Confirm") }
|
||||
)
|
||||
.activeButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.applyScreenBackground()
|
||||
}
|
||||
}
|
||||
|
||||
struct Confirmation_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
StateContainer(
|
||||
initialState: (
|
||||
Transaction.placeholder,
|
||||
false
|
||||
)
|
||||
) { _ in
|
||||
TransactionConfirmation(
|
||||
viewStore: ViewStore(.placeholder)
|
||||
)
|
||||
}
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct TransactionFailed: View {
|
||||
let viewStore: SendViewStore
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Sending transaction failed")
|
||||
|
||||
Button(
|
||||
action: {
|
||||
viewStore.send(.updateRoute(.done))
|
||||
},
|
||||
label: { Text("Close") }
|
||||
)
|
||||
.primaryButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.applyScreenBackground()
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
struct TransactionFailed_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
TransactionFailed(viewStore: ViewStore(.placeholder))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct TransactionSent: View {
|
||||
let viewStore: SendViewStore
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Sending transaction succeeded")
|
||||
|
||||
Button(
|
||||
action: {
|
||||
viewStore.send(.updateRoute(.done))
|
||||
},
|
||||
label: { Text("Close") }
|
||||
)
|
||||
.primaryButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding()
|
||||
|
||||
Text("\(String(dumping: viewStore.transaction))")
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.applyScreenBackground()
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
struct TransactionSent_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
TransactionSent(viewStore: ViewStore(.placeholder))
|
||||
}
|
||||
}
|
|
@ -53,6 +53,14 @@ protocol WrappedSDKSynchronizer {
|
|||
|
||||
func getTransparentAddress(account: Int) -> TransparentAddress?
|
||||
func getShieldedAddress(account: Int) -> SaplingShieldedAddress?
|
||||
|
||||
func sendTransaction(
|
||||
with spendingKey: String,
|
||||
zatoshi: Int64,
|
||||
to recipientAddress: String,
|
||||
memo: String?,
|
||||
from account: Int
|
||||
) -> Effect<Result<TransactionState, NSError>, Never>
|
||||
}
|
||||
|
||||
extension WrappedSDKSynchronizer {
|
||||
|
@ -139,13 +147,31 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
}
|
||||
|
||||
func getAllTransactions() -> Effect<[TransactionState], Never> {
|
||||
return .merge(
|
||||
getAllClearedTransactions(),
|
||||
getAllPendingTransactions()
|
||||
)
|
||||
.flatMap(Publishers.Sequence.init(sequence:))
|
||||
.collect()
|
||||
.eraseToEffect()
|
||||
if let pendingTransactions = try? synchronizer?.allPendingTransactions(),
|
||||
let clearedTransactions = try? synchronizer?.allClearedTransactions(),
|
||||
let syncedBlockHeight = synchronizer?.latestScannedHeight {
|
||||
let clearedTxs = clearedTransactions.map {
|
||||
TransactionState.init(confirmedTransaction: $0, sent: ($0.toAddress != nil))
|
||||
}
|
||||
let pendingTxs = pendingTransactions.map {
|
||||
TransactionState.init(pendingTransaction: $0, latestBlockHeight: syncedBlockHeight)
|
||||
}
|
||||
|
||||
let txs = clearedTxs.filter { cleared in
|
||||
pendingTxs.first { pending in
|
||||
pending.id == cleared.id
|
||||
} == nil
|
||||
}
|
||||
return .merge(
|
||||
Effect(value: txs),
|
||||
Effect(value: pendingTxs)
|
||||
)
|
||||
.flatMap(Publishers.Sequence.init(sequence:))
|
||||
.collect()
|
||||
.eraseToEffect()
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
func getTransparentAddress(account: Int) -> TransparentAddress? {
|
||||
|
@ -155,6 +181,34 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? {
|
||||
synchronizer?.getShieldedAddress(accountIndex: account)
|
||||
}
|
||||
|
||||
func sendTransaction(
|
||||
with spendingKey: String,
|
||||
zatoshi: Int64,
|
||||
to recipientAddress: String,
|
||||
memo: String?,
|
||||
from account: Int
|
||||
) -> Effect<Result<TransactionState, NSError>, Never> {
|
||||
Deferred {
|
||||
Future { [weak self] promise in
|
||||
self?.synchronizer?.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: zatoshi,
|
||||
toAddress: recipientAddress,
|
||||
memo: memo,
|
||||
from: account) { result in
|
||||
switch result {
|
||||
case .failure(let error as NSError):
|
||||
promise(.failure(error))
|
||||
case .success(let pendingTx):
|
||||
promise(.success(TransactionState(pendingTransaction: pendingTx)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.mapError { $0 as NSError }
|
||||
.catchToEffect()
|
||||
}
|
||||
}
|
||||
|
||||
class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
||||
|
@ -257,6 +311,29 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
func getTransparentAddress(account: Int) -> TransparentAddress? { nil }
|
||||
|
||||
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? { nil }
|
||||
|
||||
func sendTransaction(
|
||||
with spendingKey: String,
|
||||
zatoshi: Int64,
|
||||
to recipientAddress: String,
|
||||
memo: String?,
|
||||
from account: Int
|
||||
) -> Effect<Result<TransactionState, NSError>, Never> {
|
||||
let transactionState = TransactionState(
|
||||
expirationHeight: 40,
|
||||
memo: "test",
|
||||
minedHeight: 50,
|
||||
shielded: true,
|
||||
zAddress: "tteafadlamnelkqe",
|
||||
date: Date.init(timeIntervalSince1970: 1234567),
|
||||
id: "id",
|
||||
status: .paid(success: true),
|
||||
subtitle: "sub",
|
||||
zecAmount: 10
|
||||
)
|
||||
|
||||
return Effect(value: Result.success(transactionState))
|
||||
}
|
||||
}
|
||||
|
||||
class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
||||
|
@ -339,4 +416,14 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
func getTransparentAddress(account: Int) -> TransparentAddress? { nil }
|
||||
|
||||
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? { nil }
|
||||
|
||||
func sendTransaction(
|
||||
with spendingKey: String,
|
||||
zatoshi: Int64,
|
||||
to recipientAddress: String,
|
||||
memo: String?,
|
||||
from account: Int
|
||||
) -> Effect<Result<TransactionState, NSError>, Never> {
|
||||
return Effect(value: Result.failure(SynchronizerError.criticalError as NSError))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
//
|
||||
// SendTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 02.05.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import secant_testnet
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
|
||||
class SendTests: XCTestCase {
|
||||
var storage = WalletStorage(secItem: .live)
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
storage.zcashStoredWalletPrefix = "test_"
|
||||
storage.deleteData(forKey: WalletStorage.Constants.zcashStoredWallet)
|
||||
}
|
||||
|
||||
func testSendSucceeded() throws {
|
||||
// the test needs to pass the exportWallet() so we simulate some in the keychain
|
||||
try storage.importWallet(bip39: "one two three", birthday: nil)
|
||||
|
||||
// setup the store and environment to be fully mocked
|
||||
let testScheduler = DispatchQueue.test
|
||||
|
||||
let testEnvironment = SendEnvironment(
|
||||
mnemonicSeedPhraseProvider: .mock,
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: storage),
|
||||
wrappedDerivationTool: .live(),
|
||||
wrappedSDKSynchronizer: MockWrappedSDKSynchronizer()
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: .placeholder,
|
||||
reducer: SendReducer.default,
|
||||
environment: testEnvironment
|
||||
)
|
||||
|
||||
// simulate the sending confirmation button to be pressed
|
||||
store.send(.sendConfirmationPressed) { state in
|
||||
// once sending is confirmed, the attemts to try to send again by pressing the button
|
||||
// needs to be eliminated, indicated by the flag `isSendingTransaction`, need to be true
|
||||
state.isSendingTransaction = true
|
||||
}
|
||||
|
||||
testScheduler.advance(by: 0.01)
|
||||
|
||||
let transactionState = TransactionState(
|
||||
expirationHeight: 40,
|
||||
memo: "test",
|
||||
minedHeight: 50,
|
||||
shielded: true,
|
||||
zAddress: "tteafadlamnelkqe",
|
||||
date: Date.init(timeIntervalSince1970: 1234567),
|
||||
id: "id",
|
||||
status: .paid(success: true),
|
||||
subtitle: "sub",
|
||||
zecAmount: 10
|
||||
)
|
||||
|
||||
// check the success transaction to be received back
|
||||
store.receive(.sendTransactionResult(Result.success(transactionState))) { state in
|
||||
// from this moment on the sending next transaction is allowed again
|
||||
// the 'isSendingTransaction' needs to be false again
|
||||
state.isSendingTransaction = false
|
||||
}
|
||||
|
||||
// all went well, the success screen is triggered
|
||||
store.receive(.updateRoute(.success)) { state in
|
||||
state.route = .success
|
||||
}
|
||||
}
|
||||
|
||||
func testSendFailed() throws {
|
||||
// the test needs to pass the exportWallet() so we simulate some in the keychain
|
||||
try storage.importWallet(bip39: "one two three", birthday: nil)
|
||||
|
||||
// setup the store and environment to be fully mocked
|
||||
let testScheduler = DispatchQueue.test
|
||||
|
||||
let testEnvironment = SendEnvironment(
|
||||
mnemonicSeedPhraseProvider: .mock,
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: storage),
|
||||
wrappedDerivationTool: .live(),
|
||||
wrappedSDKSynchronizer: TestWrappedSDKSynchronizer()
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: .placeholder,
|
||||
reducer: SendReducer.default,
|
||||
environment: testEnvironment
|
||||
)
|
||||
|
||||
// simulate the sending confirmation button to be pressed
|
||||
store.send(.sendConfirmationPressed) { state in
|
||||
// once sending is confirmed, the attemts to try to send again by pressing the button
|
||||
// needs to be eliminated, indicated by the flag `isSendingTransaction`, need to be true
|
||||
state.isSendingTransaction = true
|
||||
}
|
||||
|
||||
testScheduler.advance(by: 0.01)
|
||||
|
||||
// check the failure transaction to be received back
|
||||
store.receive(.sendTransactionResult(Result.failure(SynchronizerError.criticalError as NSError))) { state in
|
||||
// from this moment on the sending next transaction is allowed again
|
||||
// the 'isSendingTransaction' needs to be false again
|
||||
state.isSendingTransaction = false
|
||||
}
|
||||
|
||||
// the failure screen is triggered as expected
|
||||
store.receive(.updateRoute(.failure)) { state in
|
||||
state.route = .failure
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue