[#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
fileprivate enum ZcashSDKConstants {
static let defaultBlockHeight = 1_629_724
static let endpointMainnetAddress = "lightwalletd.electriccoin.co"
static let endpointTestnetAddress = "lightwalletd.testnet.electriccoin.co"
static let endpointPort = 9067
static let defaultBlockHeight = 1_629_724
static let mnemonicWordsMaxCount = 24
static let requiredTransactionConfirmations = 10
}
struct ZCashSDKEnvironment {
let defaultBirthday: BlockHeight
let endpoint: LightWalletEndpoint
let lightWalletService: LightWalletService
let network: ZcashNetwork
let mnemonicWordsMaxCount: Int
let isMainnet: () -> Bool
let lightWalletService: LightWalletService
let mnemonicWordsMaxCount: Int
let network: ZcashNetwork
let requiredTransactionConfirmations: Int
}
extension ZCashSDKEnvironment {
static let mainnet = ZCashSDKEnvironment(
defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight),
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort),
isMainnet: { true },
lightWalletService: LightWalletGRPCService(
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort)
),
network: ZcashNetworkBuilder.network(for: .mainnet),
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
isMainnet: { true }
network: ZcashNetworkBuilder.network(for: .mainnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations
)
static let testnet = ZCashSDKEnvironment(
defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight),
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort),
isMainnet: { false },
lightWalletService: LightWalletGRPCService(
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort)
),
network: ZcashNetworkBuilder.network(for: .testnet),
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
isMainnet: { false }
network: ZcashNetworkBuilder.network(for: .testnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations
)
}

View File

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

View File

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

View File

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

View File

@ -2,72 +2,160 @@ import SwiftUI
import ComposableArchitecture
struct TransactionDetailView: View {
enum RowMark {
case neutral
case success
case fail
case inactive
case highlight
}
var transaction: TransactionState
var viewStore: WalletEventsFlowViewStore
var body: some View {
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())")
Spacer()
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")
.transactionDetailRow()
Text("fee \(transaction.fee.decimalString()) ZEC")
.transactionDetailRow()
func plainTwoColumnText(left: String, right: String, mark: RowMark = .neutral) -> some View {
HStack {
Text(left)
Spacer()
Text(right)
}
.transactionDetailRow(mark: mark)
}
Text("total amount \(transaction.totalAmount.decimalString()) ZEC")
.transactionDetailRow()
Button {
viewStore.send(.copyToPastboard(transaction.address))
} label: {
Text("\(addressPrefixText) \(transaction.address)")
.lineLimit(1)
.truncationMode(.middle)
.transactionDetailRow()
}
if let memo = transaction.memo {
Button {
viewStore.send(.copyToPastboard(memo))
} label: {
VStack {
Text("\(memo)")
.multilineTextAlignment(.leading)
HStack {
Text("reply-to address included")
Spacer()
Button {
viewStore.send(.replyTo(transaction.address))
} label: {
Text("reply now")
.padding(5)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(Asset.Colors.Text.transactionDetailText.color, lineWidth: 1)
)
}
}
func address(mark: RowMark = .neutral) -> some View {
Button {
viewStore.send(.copyToPastboard(transaction.address))
} label: {
Text("\(addressPrefixText) \(transaction.address)")
.lineLimit(1)
.truncationMode(.middle)
.transactionDetailRow(mark: mark)
}
}
func memo(
_ memo: String,
_ viewStore: WalletEventsFlowViewStore,
mark: RowMark = .neutral
) -> some View {
Button {
viewStore.send(.copyToPastboard(memo))
} label: {
VStack {
HStack {
Text("\(memo)")
.multilineTextAlignment(.leading)
Spacer()
}
HStack {
Text("reply-to address included")
Spacer()
Button {
viewStore.send(.replyTo(transaction.address))
} label: {
Text("reply now")
.padding(5)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(Asset.Colors.Text.transactionDetailText.color, lineWidth: 1)
)
}
.transactionDetailRow()
}
}
HStack {
Text("Confirmed")
Spacer()
Text("\(transaction.confirmations) times")
}
.transactionDetailRow()
.transactionDetailRow(mark: mark)
}
}
func confirmed(mark: RowMark = .neutral) -> some View {
HStack {
Text("Confirmed")
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 {
viewStore.send(.copyToPastboard(transaction.id))
} label: {
@ -81,7 +169,7 @@ struct TransactionDetailView: View {
.background(Asset.Colors.BackgroundColors.numberedChip.color)
.padding(.vertical, 30)
}
Button { } label: {
// TODO: Warn users that they will leave the App when they follow a Block explorer
// https://github.com/zcash/secant-ios-wallet/issues/379
@ -93,16 +181,10 @@ struct TransactionDetailView: View {
.frame(height: 50)
.padding(.horizontal, 30)
}
.applyScreenBackground()
.navigationTitle("Transaction detail")
}
}
extension TransactionDetailView {
var amountPrefixText: String {
transaction.status == .received ? "You received" : "You sent"
}
var addressPrefixText: String {
transaction.status == .received ? "from" : "to"
}
@ -112,11 +194,13 @@ extension TransactionDetailView {
}
}
// MARK: - Row modifier
struct TransactionDetailRow: ViewModifier {
let tint: Color
let mark: TransactionDetailView.RowMark
let textColor: Color
let backgroundColor: Color
func body(content: Content) -> some View {
content
.foregroundColor(textColor)
@ -124,21 +208,35 @@ struct TransactionDetailRow: ViewModifier {
.padding()
.background(backgroundColor)
.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 {
func transactionDetailRow(
_ tint: Color = Asset.Colors.BackgroundColors.red.color,
_ textColor: Color = Asset.Colors.Text.transactionDetailText.color,
_ backgroundColor: Color = Asset.Colors.BackgroundColors.numberedChip.color
mark: TransactionDetailView.RowMark = .neutral
) -> some View {
modifier(
TransactionDetailRow(
tint: tint,
textColor: textColor,
backgroundColor: backgroundColor
mark: mark,
textColor: mark == .inactive ?
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(
transaction:
TransactionState(
errorMessage: "possible roll back",
memo:
"""
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),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .paid(success: true),
subtitle: "",
timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000)
),
@ -173,9 +271,10 @@ struct TransactionDetail_Previews: PreviewProvider {
reducer: .default,
environment:
WalletEventsFlowEnvironment(
pasteboard: .test,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
SDKSynchronizer: MockWrappedSDKSynchronizer(),
pasteboard: .test
zcashSDKEnvironment: .testnet
)
)
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "97",
"green" : "172",
"red" : "42"
"blue" : "0x61",
"green" : "0xAC",
"red" : "0x2A"
}
},
"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 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 static let zcashLogoFill = ColorAsset(name: "ZcashLogoFill")
internal static let innerCircle = ColorAsset(name: "innerCircle")

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ import XCTest
import ComposableArchitecture
class WalletEventsSnapshotTests: XCTestCase {
func testWalletEventDetailSnapshot() throws {
func testWalletEventDetailSnapshot_sent() throws {
let transaction = TransactionState(
memo:
"""
@ -23,7 +23,6 @@ class WalletEventsSnapshotTests: XCTestCase {
fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .paid(success: true),
subtitle: "",
timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000)
)
@ -49,9 +48,187 @@ class WalletEventsSnapshotTests: XCTestCase {
// wallet event detail
let testEnvironment = WalletEventsFlowEnvironment(
pasteboard: .test,
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
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))))

View File

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