[#362] [scaffold] Pending Transaction Details (#381)

- transaction detail has been cut into pieces that can be composed to various versions of the detail
- handling all 4 cases (sent, received, pending, failed)

[362] [scaffold] Pending Transaction Details

- confirmations counting
- pending transaction status fixed

[362] [scaffold] Pending Transaction Details

- tests fixed

[362] [scaffold] Pending Transaction Details

- failed transactions

[362] [scaffold] Pending Transaction Details (381)

- snapshot tests

[362] [scaffold] Pending Transaction Details (381)

- comments resolved
This commit is contained in:
Lukas Korba 2022-06-30 13:59:03 +02:00 committed by GitHub
parent df2c721d32
commit 3d615a32d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 647 additions and 153 deletions

View File

@ -10,42 +10,46 @@ import ZcashLightClientKit
// swiftlint:disable:next private_over_fileprivate strict_fileprivate // swiftlint:disable:next private_over_fileprivate strict_fileprivate
fileprivate enum ZcashSDKConstants { fileprivate enum ZcashSDKConstants {
static let defaultBlockHeight = 1_629_724
static let endpointMainnetAddress = "lightwalletd.electriccoin.co" static let endpointMainnetAddress = "lightwalletd.electriccoin.co"
static let endpointTestnetAddress = "lightwalletd.testnet.electriccoin.co" static let endpointTestnetAddress = "lightwalletd.testnet.electriccoin.co"
static let endpointPort = 9067 static let endpointPort = 9067
static let defaultBlockHeight = 1_629_724
static let mnemonicWordsMaxCount = 24 static let mnemonicWordsMaxCount = 24
static let requiredTransactionConfirmations = 10
} }
struct ZCashSDKEnvironment { struct ZCashSDKEnvironment {
let defaultBirthday: BlockHeight let defaultBirthday: BlockHeight
let endpoint: LightWalletEndpoint let endpoint: LightWalletEndpoint
let lightWalletService: LightWalletService
let network: ZcashNetwork
let mnemonicWordsMaxCount: Int
let isMainnet: () -> Bool let isMainnet: () -> Bool
let lightWalletService: LightWalletService
let mnemonicWordsMaxCount: Int
let network: ZcashNetwork
let requiredTransactionConfirmations: Int
} }
extension ZCashSDKEnvironment { extension ZCashSDKEnvironment {
static let mainnet = ZCashSDKEnvironment( static let mainnet = ZCashSDKEnvironment(
defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight), defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight),
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort), endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort),
isMainnet: { true },
lightWalletService: LightWalletGRPCService( lightWalletService: LightWalletGRPCService(
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort) endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort)
), ),
network: ZcashNetworkBuilder.network(for: .mainnet),
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount, mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
isMainnet: { true } network: ZcashNetworkBuilder.network(for: .mainnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations
) )
static let testnet = ZCashSDKEnvironment( static let testnet = ZCashSDKEnvironment(
defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight), defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight),
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort), endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort),
isMainnet: { false },
lightWalletService: LightWalletGRPCService( lightWalletService: LightWalletGRPCService(
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort) endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort)
), ),
network: ZcashNetworkBuilder.network(for: .testnet),
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount, mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
isMainnet: { false } network: ZcashNetworkBuilder.network(for: .testnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations
) )
} }

View File

@ -367,7 +367,8 @@ extension AppReducer {
mnemonic: environment.mnemonic, mnemonic: environment.mnemonic,
scheduler: environment.scheduler, scheduler: environment.scheduler,
SDKSynchronizer: environment.SDKSynchronizer, SDKSynchronizer: environment.SDKSynchronizer,
walletStorage: environment.walletStorage walletStorage: environment.walletStorage,
zcashSDKEnvironment: environment.zcashSDKEnvironment
) )
} }
) )

View File

@ -61,6 +61,7 @@ struct HomeEnvironment {
let scheduler: AnySchedulerOf<DispatchQueue> let scheduler: AnySchedulerOf<DispatchQueue>
let SDKSynchronizer: WrappedSDKSynchronizer let SDKSynchronizer: WrappedSDKSynchronizer
let walletStorage: WrappedWalletStorage let walletStorage: WrappedWalletStorage
let zcashSDKEnvironment: ZCashSDKEnvironment
} }
extension HomeEnvironment { extension HomeEnvironment {
@ -71,7 +72,8 @@ extension HomeEnvironment {
mnemonic: .mock, mnemonic: .mock,
scheduler: DispatchQueue.main.eraseToAnyScheduler(), scheduler: DispatchQueue.main.eraseToAnyScheduler(),
SDKSynchronizer: MockWrappedSDKSynchronizer(), SDKSynchronizer: MockWrappedSDKSynchronizer(),
walletStorage: .throwing walletStorage: .throwing,
zcashSDKEnvironment: .testnet
) )
} }
@ -180,9 +182,10 @@ extension HomeReducer {
action: /HomeAction.walletEvents, action: /HomeAction.walletEvents,
environment: { environment in environment: { environment in
WalletEventsFlowEnvironment( WalletEventsFlowEnvironment(
pasteboard: .live,
scheduler: environment.scheduler, scheduler: environment.scheduler,
SDKSynchronizer: environment.SDKSynchronizer, SDKSynchronizer: environment.SDKSynchronizer,
pasteboard: .live zcashSDKEnvironment: environment.zcashSDKEnvironment
) )
} }
) )
@ -315,7 +318,8 @@ extension HomeStore {
mnemonic: .live, mnemonic: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(), scheduler: DispatchQueue.main.eraseToAnyScheduler(),
SDKSynchronizer: LiveWrappedSDKSynchronizer(), SDKSynchronizer: LiveWrappedSDKSynchronizer(),
walletStorage: .live() walletStorage: .live(),
zcashSDKEnvironment: .testnet
) )
) )
} }

View File

@ -49,9 +49,10 @@ extension SandboxReducer {
&state.walletEventsState, &state.walletEventsState,
walletEventsAction, walletEventsAction,
WalletEventsFlowEnvironment( WalletEventsFlowEnvironment(
pasteboard: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(), scheduler: DispatchQueue.main.eraseToAnyScheduler(),
SDKSynchronizer: LiveWrappedSDKSynchronizer(), SDKSynchronizer: LiveWrappedSDKSynchronizer(),
pasteboard: .live zcashSDKEnvironment: .testnet
) )
) )
.map(SandboxAction.walletEvents) .map(SandboxAction.walletEvents)

View File

@ -121,6 +121,7 @@ extension SendFlowReducer {
case .updateRoute(.confirmation): case .updateRoute(.confirmation):
state.amount = Zatoshi(amount: state.transactionAmountInputState.amount) state.amount = Zatoshi(amount: state.transactionAmountInputState.amount)
state.address = state.transactionAddressInputState.textFieldState.text state.address = state.transactionAddressInputState.textFieldState.text
state.route = .confirmation
return .none return .none
case let .updateRoute(route): case let .updateRoute(route):

View File

@ -2,72 +2,160 @@ import SwiftUI
import ComposableArchitecture import ComposableArchitecture
struct TransactionDetailView: View { struct TransactionDetailView: View {
enum RowMark {
case neutral
case success
case fail
case inactive
case highlight
}
var transaction: TransactionState var transaction: TransactionState
var viewStore: WalletEventsFlowViewStore var viewStore: WalletEventsFlowViewStore
var body: some View { var body: some View {
ScrollView { ScrollView {
HStack { header
switch transaction.status {
case .paid(success: _):
plainText("You sent \(transaction.zecAmount.decimalString()) ZEC")
plainText("fee \(transaction.fee.decimalString()) ZEC", mark: .inactive)
plainText("total amount \(transaction.totalAmount.decimalString()) ZEC", mark: .inactive)
address(mark: .inactive)
if let text = transaction.memo { memo(text, viewStore, mark: .highlight) }
confirmed(mark: .success)
case .pending:
plainText("You are sending \(transaction.zecAmount.decimalString()) ZEC")
plainText("Includes network fee \(transaction.fee.decimalString()) ZEC", mark: .inactive)
plainText("total amount \(transaction.totalAmount.decimalString()) ZEC", mark: .inactive)
if let text = transaction.memo { memo(text, viewStore, mark: .inactive) }
confirming(mark: .highlight)
case .received:
plainText("You received \(transaction.zecAmount.decimalString()) ZEC")
plainText("fee \(transaction.fee.decimalString()) ZEC")
plainText("total amount \(transaction.totalAmount.decimalString()) ZEC")
address(mark: .inactive)
if let text = transaction.memo { memo(text, viewStore, mark: .highlight) }
confirmed(mark: .success)
case .failed:
plainText("You DID NOT send \(transaction.zecAmount.decimalString()) ZEC", mark: .fail)
plainText("Includes network fee \(transaction.fee.decimalString()) ZEC", mark: .inactive)
plainText("total amount \(transaction.totalAmount.decimalString()) ZEC", mark: .inactive)
if let text = transaction.memo { memo(text, viewStore, mark: .inactive) }
if let errorMessage = transaction.errorMessage {
plainTwoColumnText(left: "Failed", right: errorMessage, mark: .fail)
}
}
Spacer()
footer
}
.applyScreenBackground()
.navigationTitle("Transaction detail")
}
}
extension TransactionDetailView {
var header: some View {
HStack {
switch transaction.status {
case .pending:
Text("PENDING")
Spacer()
case .failed:
Text("\(transaction.date.asHumanReadable())")
Spacer()
Text("FAILED")
default:
Text("\(transaction.date.asHumanReadable())") Text("\(transaction.date.asHumanReadable())")
Spacer() Spacer()
Text("HEIGHT \(heightText)") Text("HEIGHT \(heightText)")
} }
.padding() }
.padding()
}
func plainText(_ text: String, mark: RowMark = .neutral) -> some View {
Text(text)
.transactionDetailRow(mark: mark)
}
Text("\(amountPrefixText) \(transaction.zecAmount.decimalString()) ZEC") func plainTwoColumnText(left: String, right: String, mark: RowMark = .neutral) -> some View {
.transactionDetailRow() HStack {
Text(left)
Text("fee \(transaction.fee.decimalString()) ZEC") Spacer()
.transactionDetailRow() Text(right)
}
.transactionDetailRow(mark: mark)
}
Text("total amount \(transaction.totalAmount.decimalString()) ZEC") func address(mark: RowMark = .neutral) -> some View {
.transactionDetailRow() Button {
viewStore.send(.copyToPastboard(transaction.address))
Button { } label: {
viewStore.send(.copyToPastboard(transaction.address)) Text("\(addressPrefixText) \(transaction.address)")
} label: { .lineLimit(1)
Text("\(addressPrefixText) \(transaction.address)") .truncationMode(.middle)
.lineLimit(1) .transactionDetailRow(mark: mark)
.truncationMode(.middle) }
.transactionDetailRow() }
}
func memo(
if let memo = transaction.memo { _ memo: String,
Button { _ viewStore: WalletEventsFlowViewStore,
viewStore.send(.copyToPastboard(memo)) mark: RowMark = .neutral
} label: { ) -> some View {
VStack { Button {
Text("\(memo)") viewStore.send(.copyToPastboard(memo))
.multilineTextAlignment(.leading) } label: {
VStack {
HStack { HStack {
Text("reply-to address included") Text("\(memo)")
Spacer() .multilineTextAlignment(.leading)
Button { Spacer()
viewStore.send(.replyTo(transaction.address)) }
} label: {
Text("reply now") HStack {
.padding(5) Text("reply-to address included")
.overlay( Spacer()
RoundedRectangle(cornerRadius: 6) Button {
.stroke(Asset.Colors.Text.transactionDetailText.color, lineWidth: 1) viewStore.send(.replyTo(transaction.address))
) } label: {
} Text("reply now")
} .padding(5)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(Asset.Colors.Text.transactionDetailText.color, lineWidth: 1)
)
} }
.transactionDetailRow()
} }
} }
.transactionDetailRow(mark: mark)
HStack { }
Text("Confirmed") }
Spacer()
Text("\(transaction.confirmations) times") func confirmed(mark: RowMark = .neutral) -> some View {
} HStack {
.transactionDetailRow() Text("Confirmed")
Spacer() Spacer()
Text("\(transaction.confirmationsWith(viewStore.latestMinedHeight)) times")
}
.transactionDetailRow(mark: mark)
}
func confirming(mark: RowMark = .neutral) -> some View {
HStack {
Text("Confirming ~\(viewStore.requiredTransactionConfirmations)mins")
Spacer()
Text("\(transaction.confirmationsWith(viewStore.latestMinedHeight))/\(viewStore.requiredTransactionConfirmations)")
}
.transactionDetailRow(mark: mark)
}
var footer: some View {
VStack {
Button { Button {
viewStore.send(.copyToPastboard(transaction.id)) viewStore.send(.copyToPastboard(transaction.id))
} label: { } label: {
@ -81,7 +169,7 @@ struct TransactionDetailView: View {
.background(Asset.Colors.BackgroundColors.numberedChip.color) .background(Asset.Colors.BackgroundColors.numberedChip.color)
.padding(.vertical, 30) .padding(.vertical, 30)
} }
Button { } label: { Button { } label: {
// TODO: Warn users that they will leave the App when they follow a Block explorer // TODO: Warn users that they will leave the App when they follow a Block explorer
// https://github.com/zcash/secant-ios-wallet/issues/379 // https://github.com/zcash/secant-ios-wallet/issues/379
@ -93,16 +181,10 @@ struct TransactionDetailView: View {
.frame(height: 50) .frame(height: 50)
.padding(.horizontal, 30) .padding(.horizontal, 30)
} }
.applyScreenBackground()
.navigationTitle("Transaction detail")
} }
} }
extension TransactionDetailView { extension TransactionDetailView {
var amountPrefixText: String {
transaction.status == .received ? "You received" : "You sent"
}
var addressPrefixText: String { var addressPrefixText: String {
transaction.status == .received ? "from" : "to" transaction.status == .received ? "from" : "to"
} }
@ -112,11 +194,13 @@ extension TransactionDetailView {
} }
} }
// MARK: - Row modifier
struct TransactionDetailRow: ViewModifier { struct TransactionDetailRow: ViewModifier {
let tint: Color let mark: TransactionDetailView.RowMark
let textColor: Color let textColor: Color
let backgroundColor: Color let backgroundColor: Color
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
.foregroundColor(textColor) .foregroundColor(textColor)
@ -124,21 +208,35 @@ struct TransactionDetailRow: ViewModifier {
.padding() .padding()
.background(backgroundColor) .background(backgroundColor)
.padding(.leading, 20) .padding(.leading, 20)
.background(tint) .background(markColor(mark))
}
private func markColor(_ mark: TransactionDetailView.RowMark) -> Color {
let markColor: Color
switch mark {
case .neutral: markColor = Asset.Colors.TransactionDetail.neutralMark.color
case .success: markColor = Asset.Colors.TransactionDetail.succeededMark.color
case .fail: markColor = Asset.Colors.TransactionDetail.failedMark.color
case .inactive: markColor = Asset.Colors.TransactionDetail.inactiveMark.color
case .highlight: markColor = Asset.Colors.TransactionDetail.highlightMark.color
}
return markColor
} }
} }
extension View { extension View {
func transactionDetailRow( func transactionDetailRow(
_ tint: Color = Asset.Colors.BackgroundColors.red.color, mark: TransactionDetailView.RowMark = .neutral
_ textColor: Color = Asset.Colors.Text.transactionDetailText.color,
_ backgroundColor: Color = Asset.Colors.BackgroundColors.numberedChip.color
) -> some View { ) -> some View {
modifier( modifier(
TransactionDetailRow( TransactionDetailRow(
tint: tint, mark: mark,
textColor: textColor, textColor: mark == .inactive ?
backgroundColor: backgroundColor Asset.Colors.TransactionDetail.inactiveMark.color :
Asset.Colors.Text.transactionDetailText.color,
backgroundColor: Asset.Colors.BackgroundColors.numberedChip.color
) )
) )
} }
@ -152,6 +250,7 @@ struct TransactionDetail_Previews: PreviewProvider {
TransactionDetailView( TransactionDetailView(
transaction: transaction:
TransactionState( TransactionState(
errorMessage: "possible roll back",
memo: memo:
""" """
Testing some long memo so I can see many lines of text \ Testing some long memo so I can see many lines of text \
@ -163,7 +262,6 @@ struct TransactionDetail_Previews: PreviewProvider {
fee: Zatoshi(amount: 1_000_000), fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8", id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .paid(success: true), status: .paid(success: true),
subtitle: "",
timestamp: 1234567, timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000) zecAmount: Zatoshi(amount: 25_000_000)
), ),
@ -173,9 +271,10 @@ struct TransactionDetail_Previews: PreviewProvider {
reducer: .default, reducer: .default,
environment: environment:
WalletEventsFlowEnvironment( WalletEventsFlowEnvironment(
pasteboard: .test,
scheduler: DispatchQueue.main.eraseToAnyScheduler(), scheduler: DispatchQueue.main.eraseToAnyScheduler(),
SDKSynchronizer: MockWrappedSDKSynchronizer(), SDKSynchronizer: MockWrappedSDKSynchronizer(),
pasteboard: .test zcashSDKEnvironment: .testnet
) )
) )
) )

View File

@ -1,5 +1,6 @@
import ComposableArchitecture import ComposableArchitecture
import SwiftUI import SwiftUI
import ZcashLightClientKit
typealias WalletEventsFlowReducer = Reducer<WalletEventsFlowState, WalletEventsFlowAction, WalletEventsFlowEnvironment> typealias WalletEventsFlowReducer = Reducer<WalletEventsFlowState, WalletEventsFlowAction, WalletEventsFlowEnvironment>
typealias WalletEventsFlowStore = Store<WalletEventsFlowState, WalletEventsFlowAction> typealias WalletEventsFlowStore = Store<WalletEventsFlowState, WalletEventsFlowAction>
@ -16,7 +17,9 @@ struct WalletEventsFlowState: Equatable {
var route: Route? var route: Route?
var latestMinedHeight: BlockHeight?
var isScrollable = false var isScrollable = false
var requiredTransactionConfirmations = 0
var walletEvents = IdentifiedArrayOf<WalletEvent>.placeholder var walletEvents = IdentifiedArrayOf<WalletEvent>.placeholder
} }
@ -35,9 +38,10 @@ enum WalletEventsFlowAction: Equatable {
// MARK: - Environment // MARK: - Environment
struct WalletEventsFlowEnvironment { struct WalletEventsFlowEnvironment {
let pasteboard: WrappedPasteboard
let scheduler: AnySchedulerOf<DispatchQueue> let scheduler: AnySchedulerOf<DispatchQueue>
let SDKSynchronizer: WrappedSDKSynchronizer let SDKSynchronizer: WrappedSDKSynchronizer
let pasteboard: WrappedPasteboard let zcashSDKEnvironment: ZCashSDKEnvironment
} }
// MARK: - Reducer // MARK: - Reducer
@ -48,6 +52,7 @@ extension WalletEventsFlowReducer {
static let `default` = WalletEventsFlowReducer { state, action, environment in static let `default` = WalletEventsFlowReducer { state, action, environment in
switch action { switch action {
case .onAppear: case .onAppear:
state.requiredTransactionConfirmations = environment.zcashSDKEnvironment.requiredTransactionConfirmations
return environment.SDKSynchronizer.stateChanged return environment.SDKSynchronizer.stateChanged
.map(WalletEventsFlowAction.synchronizerStateChanged) .map(WalletEventsFlowAction.synchronizerStateChanged)
.eraseToEffect() .eraseToEffect()
@ -57,6 +62,9 @@ extension WalletEventsFlowReducer {
return Effect.cancel(id: CancelId()) return Effect.cancel(id: CancelId())
case .synchronizerStateChanged(.synced): case .synchronizerStateChanged(.synced):
if let latestMinedHeight = environment.SDKSynchronizer.synchronizer?.latestScannedHeight {
state.latestMinedHeight = latestMinedHeight
}
return environment.SDKSynchronizer.getAllTransactions() return environment.SDKSynchronizer.getAllTransactions()
.receive(on: environment.scheduler) .receive(on: environment.scheduler)
.map(WalletEventsFlowAction.updateWalletEvents) .map(WalletEventsFlowAction.updateWalletEvents)
@ -110,7 +118,6 @@ extension TransactionState {
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
id: "2", id: "2",
status: .paid(success: true), status: .paid(success: true),
subtitle: "",
timestamp: 1234567, timestamp: 1234567,
zecAmount: Zatoshi(amount: 25) zecAmount: Zatoshi(amount: 25)
) )
@ -133,9 +140,10 @@ extension WalletEventsFlowStore {
initialState: .placeHolder, initialState: .placeHolder,
reducer: .default, reducer: .default,
environment: WalletEventsFlowEnvironment( environment: WalletEventsFlowEnvironment(
pasteboard: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(), scheduler: DispatchQueue.main.eraseToAnyScheduler(),
SDKSynchronizer: LiveWrappedSDKSynchronizer(), SDKSynchronizer: LiveWrappedSDKSynchronizer(),
pasteboard: .live zcashSDKEnvironment: .testnet
) )
) )
} }
@ -149,7 +157,6 @@ extension IdentifiedArrayOf where Element == TransactionState {
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
id: String($0), id: String($0),
status: .paid(success: true), status: .paid(success: true),
subtitle: "",
timestamp: 1234567, timestamp: 1234567,
zecAmount: Zatoshi(amount: 25) zecAmount: Zatoshi(amount: 25)
) )

View File

@ -14,9 +14,10 @@ struct TransactionState: Equatable, Identifiable {
case paid(success: Bool) case paid(success: Bool)
case received case received
case failed case failed
case pending
} }
var confirmations = 0 var errorMessage: String?
var expirationHeight = -1 var expirationHeight = -1
var memo: String? var memo: String?
var minedHeight = -1 var minedHeight = -1
@ -26,7 +27,6 @@ struct TransactionState: Equatable, Identifiable {
var fee: Zatoshi var fee: Zatoshi
var id: String var id: String
var status: Status var status: Status
var subtitle: String
var timestamp: TimeInterval var timestamp: TimeInterval
var zecAmount: Zatoshi var zecAmount: Zatoshi
@ -36,6 +36,14 @@ struct TransactionState: Equatable, Identifiable {
var viewOnlineURL: URL? { var viewOnlineURL: URL? {
URL(string: "https://blockchair.com/zcash/transaction/\(id)") URL(string: "https://blockchair.com/zcash/transaction/\(id)")
} }
func confirmationsWith(_ latestMinedHeight: BlockHeight?) -> BlockHeight {
guard let latestMinedHeight = latestMinedHeight, minedHeight > 0, latestMinedHeight > 0 else {
return 0
}
return latestMinedHeight - minedHeight
}
} }
extension TransactionState { extension TransactionState {
@ -44,7 +52,6 @@ extension TransactionState {
id = confirmedTransaction.transactionEntity.transactionId.toHexStringTxId() id = confirmedTransaction.transactionEntity.transactionId.toHexStringTxId()
shielded = true shielded = true
status = sent ? .paid(success: confirmedTransaction.minedHeight > 0) : .received status = sent ? .paid(success: confirmedTransaction.minedHeight > 0) : .received
subtitle = "sent"
zAddress = confirmedTransaction.toAddress zAddress = confirmedTransaction.toAddress
zecAmount = sent ? Zatoshi(amount: -Int64(confirmedTransaction.value)) : Zatoshi(amount: Int64(confirmedTransaction.value)) zecAmount = sent ? Zatoshi(amount: -Int64(confirmedTransaction.value)) : Zatoshi(amount: Int64(confirmedTransaction.value))
fee = Zatoshi(amount: 10) fee = Zatoshi(amount: 10)
@ -58,9 +65,11 @@ extension TransactionState {
timestamp = pendingTransaction.createTime timestamp = pendingTransaction.createTime
id = pendingTransaction.rawTransactionId?.toHexStringTxId() ?? String(pendingTransaction.createTime) id = pendingTransaction.rawTransactionId?.toHexStringTxId() ?? String(pendingTransaction.createTime)
shielded = true shielded = true
status = .paid(success: pendingTransaction.isSubmitSuccess) status = pendingTransaction.errorMessage != nil ? .failed :
pendingTransaction.minedHeight > 0 ?
.paid(success: pendingTransaction.isSubmitSuccess) :
.pending
expirationHeight = pendingTransaction.expiryHeight expirationHeight = pendingTransaction.expiryHeight
subtitle = "pending"
zAddress = pendingTransaction.toAddress zAddress = pendingTransaction.toAddress
zecAmount = Zatoshi(amount: -Int64(pendingTransaction.value)) zecAmount = Zatoshi(amount: -Int64(pendingTransaction.value))
fee = Zatoshi(amount: 10) fee = Zatoshi(amount: 10)
@ -68,6 +77,7 @@ extension TransactionState {
self.memo = memo.asZcashTransactionMemo() self.memo = memo.asZcashTransactionMemo()
} }
minedHeight = pendingTransaction.minedHeight minedHeight = pendingTransaction.minedHeight
errorMessage = pendingTransaction.errorMessage
} }
} }
@ -79,7 +89,6 @@ extension TransactionState {
fee: Zatoshi, fee: Zatoshi,
shielded: Bool = true, shielded: Bool = true,
status: Status = .received, status: Status = .received,
subtitle: String = "",
timestamp: TimeInterval, timestamp: TimeInterval,
uuid: String = UUID().debugDescription uuid: String = UUID().debugDescription
) -> TransactionState { ) -> TransactionState {
@ -92,7 +101,6 @@ extension TransactionState {
fee: fee, fee: fee,
id: uuid, id: uuid,
status: status, status: status,
subtitle: subtitle,
timestamp: timestamp, timestamp: timestamp,
zecAmount: status == .received ? amount : Zatoshi(amount: -amount.amount) zecAmount: status == .received ? amount : Zatoshi(amount: -amount.amount)
) )
@ -104,6 +112,5 @@ struct TransactionStateMockHelper {
var amount: Zatoshi var amount: Zatoshi
var shielded = true var shielded = true
var status: TransactionState.Status = .received var status: TransactionState.Status = .received
var subtitle = "cleared"
var uuid = "" var uuid = ""
} }

View File

@ -32,14 +32,11 @@ struct WalletEvent: Equatable, Identifiable {
extension WalletEvent { extension WalletEvent {
@ViewBuilder func rowView(_ viewStore: WalletEventsFlowViewStore) -> some View { @ViewBuilder func rowView(_ viewStore: WalletEventsFlowViewStore) -> some View {
switch state { switch state {
case .send(let transaction): case .send(let transaction),
.pending(let transaction),
.received(let transaction),
.failed(let transaction):
TransactionRowView(transaction: transaction) TransactionRowView(transaction: transaction)
case .pending:
Text("pending wallet event")
case .received:
Text("received wallet event")
case .failed:
Text("failed wallet event")
case .shielded(let zatoshi): case .shielded(let zatoshi):
Text("shielded wallet event \(zatoshi.decimalString())") Text("shielded wallet event \(zatoshi.decimalString())")
case .walletImport: case .walletImport:
@ -53,14 +50,11 @@ extension WalletEvent {
extension WalletEvent { extension WalletEvent {
@ViewBuilder func detailView(_ viewStore: WalletEventsFlowViewStore) -> some View { @ViewBuilder func detailView(_ viewStore: WalletEventsFlowViewStore) -> some View {
switch state { switch state {
case .send(let transaction): case .send(let transaction),
.pending(let transaction),
.received(let transaction),
.failed(let transaction):
TransactionDetailView(transaction: transaction, viewStore: viewStore) TransactionDetailView(transaction: transaction, viewStore: viewStore)
case .pending:
Text("pending transaction detail")
case .received:
Text("received transaction detail")
case .failed:
Text("failed transaction detail")
case .shielded(let zatoshi): case .shielded(let zatoshi):
Text("shielded \(zatoshi.decimalString()) detail") Text("shielded \(zatoshi.decimalString()) detail")
case .walletImport: case .walletImport:

View File

@ -5,9 +5,9 @@
"color-space" : "srgb", "color-space" : "srgb",
"components" : { "components" : {
"alpha" : "1.000", "alpha" : "1.000",
"blue" : "0.173", "blue" : "0x2C",
"green" : "0.047", "green" : "0x0B",
"red" : "0.780" "red" : "0xC6"
} }
}, },
"idiom" : "universal" "idiom" : "universal"

View File

@ -23,9 +23,9 @@
"color-space" : "srgb", "color-space" : "srgb",
"components" : { "components" : {
"alpha" : "1.000", "alpha" : "1.000",
"blue" : "0.000", "blue" : "0x00",
"green" : "0.810", "green" : "0xCE",
"red" : "1.000" "red" : "0xFF"
} }
}, },
"idiom" : "universal" "idiom" : "universal"

View File

@ -23,9 +23,9 @@
"color-space" : "srgb", "color-space" : "srgb",
"components" : { "components" : {
"alpha" : "1.000", "alpha" : "1.000",
"blue" : "97", "blue" : "0x61",
"green" : "172", "green" : "0xAC",
"red" : "42" "red" : "0x2A"
} }
}, },
"idiom" : "universal" "idiom" : "universal"

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x2C",
"green" : "0x0B",
"red" : "0xC6"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.173",
"green" : "0.043",
"red" : "0.776"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0xCE",
"red" : "0xFF"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0xCE",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xAA",
"green" : "0xAA",
"red" : "0xAA"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xAA",
"green" : "0xAA",
"red" : "0xAA"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x61",
"green" : "0xAC",
"red" : "0x2A"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x61",
"green" : "0xAC",
"red" : "0x2A"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -125,6 +125,13 @@ internal enum Asset {
internal static let purple = ColorAsset(name: "Purple") internal static let purple = ColorAsset(name: "Purple")
} }
} }
internal enum TransactionDetail {
internal static let failedMark = ColorAsset(name: "FailedMark")
internal static let highlightMark = ColorAsset(name: "HighlightMark")
internal static let inactiveMark = ColorAsset(name: "InactiveMark")
internal static let neutralMark = ColorAsset(name: "NeutralMark")
internal static let succeededMark = ColorAsset(name: "SucceededMark")
}
internal enum ZcashBadge { internal enum ZcashBadge {
internal static let zcashLogoFill = ColorAsset(name: "ZcashLogoFill") internal static let zcashLogoFill = ColorAsset(name: "ZcashLogoFill")
internal static let innerCircle = ColorAsset(name: "innerCircle") internal static let innerCircle = ColorAsset(name: "innerCircle")

View File

@ -310,7 +310,6 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
shielded: $0.shielded, shielded: $0.shielded,
status: $0.status, status: $0.status,
subtitle: $0.subtitle,
timestamp: $0.date, timestamp: $0.date,
uuid: $0.uuid uuid: $0.uuid
) )
@ -321,10 +320,10 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func getAllPendingTransactions() -> Effect<[WalletEvent], Never> { func getAllPendingTransactions() -> Effect<[WalletEvent], Never> {
let mocked: [TransactionStateMockHelper] = [ let mocked: [TransactionStateMockHelper] = [
TransactionStateMockHelper(date: 1651039606, amount: Zatoshi(amount: 6), status: .paid(success: false), subtitle: "pending"), TransactionStateMockHelper(date: 1651039606, amount: Zatoshi(amount: 6), status: .paid(success: false)),
TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(amount: 7), subtitle: "pending"), TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(amount: 7)),
TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(amount: 8), status: .paid(success: true), subtitle: "pending"), TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(amount: 8), status: .paid(success: true)),
TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(amount: 9), subtitle: "pending") TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(amount: 9))
] ]
return Effect( return Effect(
@ -335,7 +334,6 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
shielded: $0.shielded, shielded: $0.shielded,
status: $0.status, status: $0.status,
subtitle: $0.subtitle,
timestamp: $0.date timestamp: $0.date
) )
return WalletEvent(id: transaction.id, state: .pending(transaction), timestamp: transaction.timestamp) return WalletEvent(id: transaction.id, state: .pending(transaction), timestamp: transaction.timestamp)
@ -373,7 +371,6 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
id: "id", id: "id",
status: .paid(success: true), status: .paid(success: true),
subtitle: "sub",
timestamp: 1234567, timestamp: 1234567,
zecAmount: Zatoshi(amount: 10) zecAmount: Zatoshi(amount: 10)
) )
@ -423,7 +420,6 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
shielded: $0.shielded, shielded: $0.shielded,
status: $0.status, status: $0.status,
subtitle: $0.subtitle,
timestamp: $0.date, timestamp: $0.date,
uuid: $0.uuid uuid: $0.uuid
) )
@ -438,12 +434,11 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
date: 1651039606, date: 1651039606,
amount: Zatoshi(amount: 6), amount: Zatoshi(amount: 6),
status: .paid(success: false), status: .paid(success: false),
subtitle: "pending",
uuid: "ff66" uuid: "ff66"
), ),
TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(amount: 7), subtitle: "pending", uuid: "gg77"), TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(amount: 7), uuid: "gg77"),
TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(amount: 8), status: .paid(success: true), subtitle: "pending", uuid: "hh88"), TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(amount: 8), status: .paid(success: true), uuid: "hh88"),
TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(amount: 9), subtitle: "pending", uuid: "ii99") TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(amount: 9), uuid: "ii99")
] ]
return Effect( return Effect(
@ -453,8 +448,7 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
amount: $0.amount, amount: $0.amount,
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
shielded: $0.shielded, shielded: $0.shielded,
status: $0.status, status: $0.amount.amount > 5 ? .pending : $0.status,
subtitle: $0.subtitle,
timestamp: $0.date, timestamp: $0.date,
uuid: $0.uuid uuid: $0.uuid
) )

View File

@ -21,7 +21,8 @@ class HomeTests: XCTestCase {
mnemonic: .mock, mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(), scheduler: testScheduler.eraseToAnyScheduler(),
SDKSynchronizer: MockWrappedSDKSynchronizer(), SDKSynchronizer: MockWrappedSDKSynchronizer(),
walletStorage: .throwing walletStorage: .throwing,
zcashSDKEnvironment: .testnet
) )
let store = TestStore( let store = TestStore(
@ -50,7 +51,8 @@ class HomeTests: XCTestCase {
mnemonic: .mock, mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(), scheduler: testScheduler.eraseToAnyScheduler(),
SDKSynchronizer: MockWrappedSDKSynchronizer(), SDKSynchronizer: MockWrappedSDKSynchronizer(),
walletStorage: .throwing walletStorage: .throwing,
zcashSDKEnvironment: .testnet
) )
let store = TestStore( let store = TestStore(
@ -80,7 +82,6 @@ class HomeTests: XCTestCase {
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
shielded: $0.shielded, shielded: $0.shielded,
status: $0.status, status: $0.status,
subtitle: $0.subtitle,
timestamp: $0.date, timestamp: $0.date,
uuid: $0.uuid uuid: $0.uuid
) )
@ -109,7 +110,8 @@ class HomeTests: XCTestCase {
mnemonic: .mock, mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(), scheduler: testScheduler.eraseToAnyScheduler(),
SDKSynchronizer: MockWrappedSDKSynchronizer(), SDKSynchronizer: MockWrappedSDKSynchronizer(),
walletStorage: .throwing walletStorage: .throwing,
zcashSDKEnvironment: .testnet
) )
let homeState = HomeState( let homeState = HomeState(
@ -151,7 +153,8 @@ class HomeTests: XCTestCase {
mnemonic: .mock, mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(), scheduler: testScheduler.eraseToAnyScheduler(),
SDKSynchronizer: MockWrappedSDKSynchronizer(), SDKSynchronizer: MockWrappedSDKSynchronizer(),
walletStorage: .throwing walletStorage: .throwing,
zcashSDKEnvironment: .testnet
) )
let homeState = HomeState( let homeState = HomeState(
@ -195,7 +198,8 @@ class HomeTests: XCTestCase {
mnemonic: .mock, mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(), scheduler: testScheduler.eraseToAnyScheduler(),
SDKSynchronizer: MockWrappedSDKSynchronizer(), SDKSynchronizer: MockWrappedSDKSynchronizer(),
walletStorage: .throwing walletStorage: .throwing,
zcashSDKEnvironment: .testnet
) )
let store = TestStore( let store = TestStore(

View File

@ -67,7 +67,6 @@ class SendTests: XCTestCase {
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
id: "id", id: "id",
status: .paid(success: true), status: .paid(success: true),
subtitle: "sub",
timestamp: 1234567, timestamp: 1234567,
zecAmount: Zatoshi(amount: 10) zecAmount: Zatoshi(amount: 10)
) )

View File

@ -25,7 +25,6 @@ class HomeSnapshotTests: XCTestCase {
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
shielded: $0.shielded, shielded: $0.shielded,
status: $0.status, status: $0.status,
subtitle: $0.subtitle,
timestamp: $0.date, timestamp: $0.date,
uuid: $0.uuid uuid: $0.uuid
) )
@ -77,7 +76,6 @@ class HomeSnapshotTests: XCTestCase {
fee: Zatoshi(amount: 1_000_000), fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8", id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .paid(success: true), status: .paid(success: true),
subtitle: "",
timestamp: 1234567, timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000) zecAmount: Zatoshi(amount: 25_000_000)
) )
@ -103,9 +101,10 @@ class HomeSnapshotTests: XCTestCase {
// wallet event detail // wallet event detail
let testEnvironment = WalletEventsFlowEnvironment( let testEnvironment = WalletEventsFlowEnvironment(
pasteboard: .test,
scheduler: DispatchQueue.test.eraseToAnyScheduler(), scheduler: DispatchQueue.test.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(), SDKSynchronizer: TestWrappedSDKSynchronizer(),
pasteboard: .test zcashSDKEnvironment: .testnet
) )
ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent)))) ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent))))

View File

@ -10,7 +10,7 @@ import XCTest
import ComposableArchitecture import ComposableArchitecture
class WalletEventsSnapshotTests: XCTestCase { class WalletEventsSnapshotTests: XCTestCase {
func testWalletEventDetailSnapshot() throws { func testWalletEventDetailSnapshot_sent() throws {
let transaction = TransactionState( let transaction = TransactionState(
memo: memo:
""" """
@ -23,7 +23,6 @@ class WalletEventsSnapshotTests: XCTestCase {
fee: Zatoshi(amount: 1_000_000), fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8", id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .paid(success: true), status: .paid(success: true),
subtitle: "",
timestamp: 1234567, timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000) zecAmount: Zatoshi(amount: 25_000_000)
) )
@ -49,9 +48,187 @@ class WalletEventsSnapshotTests: XCTestCase {
// wallet event detail // wallet event detail
let testEnvironment = WalletEventsFlowEnvironment( let testEnvironment = WalletEventsFlowEnvironment(
pasteboard: .test,
scheduler: DispatchQueue.test.eraseToAnyScheduler(), scheduler: DispatchQueue.test.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(), SDKSynchronizer: TestWrappedSDKSynchronizer(),
pasteboard: .test zcashSDKEnvironment: .testnet
)
ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent))))
let walletEventsStore = WalletEventsFlowStore(
initialState: .placeHolder,
reducer: .default,
environment: testEnvironment
)
addAttachments(
name: "\(#function)_WalletEventDetail",
TransactionDetailView(transaction: transaction, viewStore: ViewStore(walletEventsStore))
)
}
func testWalletEventDetailSnapshot_received() throws {
let transaction = TransactionState(
memo:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .received,
timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000)
)
let walletEvent = WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
let balance = Balance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
synchronizerStatus: "",
totalBalance: Zatoshi(amount: balance.total),
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])),
verifiedBalance: Zatoshi(amount: balance.verified)
),
reducer: .default,
environment: .demo
)
// wallet event detail
let testEnvironment = WalletEventsFlowEnvironment(
pasteboard: .test,
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(),
zcashSDKEnvironment: .testnet
)
ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent))))
let walletEventsStore = WalletEventsFlowStore(
initialState: .placeHolder,
reducer: .default,
environment: testEnvironment
)
addAttachments(
name: "\(#function)_WalletEventDetail",
TransactionDetailView(transaction: transaction, viewStore: ViewStore(walletEventsStore))
)
}
func testWalletEventDetailSnapshot_pending() throws {
let transaction = TransactionState(
memo:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .pending,
timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000)
)
let walletEvent = WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
let balance = Balance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
synchronizerStatus: "",
totalBalance: Zatoshi(amount: balance.total),
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])),
verifiedBalance: Zatoshi(amount: balance.verified)
),
reducer: .default,
environment: .demo
)
// wallet event detail
let testEnvironment = WalletEventsFlowEnvironment(
pasteboard: .test,
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(),
zcashSDKEnvironment: .testnet
)
let walletEventsState = WalletEventsFlowState(
requiredTransactionConfirmations: 10,
walletEvents: .placeholder
)
ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent))))
let walletEventsStore = WalletEventsFlowStore(
initialState: walletEventsState,
reducer: .default,
environment: testEnvironment
)
addAttachments(
name: "\(#function)_WalletEventDetail",
TransactionDetailView(transaction: transaction, viewStore: ViewStore(walletEventsStore))
)
}
func testWalletEventDetailSnapshot_failed() throws {
let transaction = TransactionState(
errorMessage: "possible roll back",
memo:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .failed,
timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000)
)
let walletEvent = WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
let balance = Balance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
synchronizerStatus: "",
totalBalance: Zatoshi(amount: balance.total),
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])),
verifiedBalance: Zatoshi(amount: balance.verified)
),
reducer: .default,
environment: .demo
)
// wallet event detail
let testEnvironment = WalletEventsFlowEnvironment(
pasteboard: .test,
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(),
zcashSDKEnvironment: .testnet
) )
ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent)))) ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent))))

View File

@ -13,9 +13,10 @@ class WalletEventsTests: XCTestCase {
static let testScheduler = DispatchQueue.test static let testScheduler = DispatchQueue.test
let testEnvironment = WalletEventsFlowEnvironment( let testEnvironment = WalletEventsFlowEnvironment(
pasteboard: .test,
scheduler: testScheduler.eraseToAnyScheduler(), scheduler: testScheduler.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(), SDKSynchronizer: TestWrappedSDKSynchronizer(),
pasteboard: .test zcashSDKEnvironment: .testnet
) )
func testSynchronizerSubscription() throws { func testSynchronizerSubscription() throws {
@ -48,12 +49,11 @@ class WalletEventsTests: XCTestCase {
date: 1651039606, date: 1651039606,
amount: Zatoshi(amount: 6), amount: Zatoshi(amount: 6),
status: .paid(success: false), status: .paid(success: false),
subtitle: "pending",
uuid: "ff66" uuid: "ff66"
), ),
TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(amount: 7), subtitle: "pending", uuid: "gg77"), TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(amount: 7), uuid: "gg77"),
TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(amount: 8), status: .paid(success: true), subtitle: "pending", uuid: "hh88"), TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(amount: 8), status: .paid(success: true), uuid: "hh88"),
TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(amount: 9), subtitle: "pending", uuid: "ii99") TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(amount: 9), uuid: "ii99")
] ]
let walletEvents: [WalletEvent] = mocked.map { let walletEvents: [WalletEvent] = mocked.map {
@ -61,14 +61,13 @@ class WalletEventsTests: XCTestCase {
amount: $0.amount, amount: $0.amount,
fee: Zatoshi(amount: 10), fee: Zatoshi(amount: 10),
shielded: $0.shielded, shielded: $0.shielded,
status: $0.status, status: $0.amount.amount > 5 ? .pending : $0.status,
subtitle: $0.subtitle,
timestamp: $0.date, timestamp: $0.date,
uuid: $0.uuid uuid: $0.uuid
) )
return WalletEvent( return WalletEvent(
id: transaction.id, id: transaction.id,
state: transaction.subtitle == "pending" ? .pending(transaction) : .send(transaction), state: transaction.status == .pending ? .pending(transaction) : .send(transaction),
timestamp: transaction.timestamp timestamp: transaction.timestamp
) )
} }
@ -106,9 +105,10 @@ class WalletEventsTests: XCTestCase {
let pasteboard = WrappedPasteboard.test let pasteboard = WrappedPasteboard.test
let testEnvironment = WalletEventsFlowEnvironment( let testEnvironment = WalletEventsFlowEnvironment(
pasteboard: pasteboard,
scheduler: DispatchQueue.test.eraseToAnyScheduler(), scheduler: DispatchQueue.test.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(), SDKSynchronizer: TestWrappedSDKSynchronizer(),
pasteboard: pasteboard zcashSDKEnvironment: .testnet
) )
let store = TestStore( let store = TestStore(