- the send button is enabled only when some funds are available - the verified balance used for sufficiency of funds instead of total - changed the pending transaction logic and split into sending/receiving instead - code cleanup of unused CancelId - UI cleanup, detail of receiving transaction rendered "to" prefix with no address [#720] User can proceed to send flow without funds (#723) - totalBalance -> totalSpendableBalance - improved code syntax for the conditions in TransactionState deciding the status
This commit is contained in:
parent
b204c42a13
commit
b142609af9
|
@ -114,7 +114,7 @@ extension SDKSynchronizerClient {
|
|||
amount: $0.amount,
|
||||
fee: Zatoshi(10),
|
||||
shielded: $0.shielded,
|
||||
status: $0.amount.amount > 5 ? .pending : $0.status,
|
||||
status: $0.amount.amount > 5 ? .sending : $0.status,
|
||||
timestamp: $0.date,
|
||||
uuid: $0.uuid
|
||||
)
|
||||
|
|
|
@ -106,7 +106,9 @@ extension AlertRequest {
|
|||
case .cantStoreThatUserPassedPhraseBackupTest(let error):
|
||||
return AlertState(
|
||||
title: TextState(L10n.Root.Initialization.Alert.Failed.title),
|
||||
message: TextState(L10n.Root.Initialization.Alert.CantStoreThatUserPassedPhraseBackupTest.message(error.message, error.code.rawValue)),
|
||||
message: TextState(
|
||||
L10n.Root.Initialization.Alert.CantStoreThatUserPassedPhraseBackupTest.message(error.message, error.code.rawValue)
|
||||
),
|
||||
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
|
||||
)
|
||||
case let .failedToProcessDeeplink(url, error):
|
||||
|
|
|
@ -22,8 +22,8 @@ struct BalanceBreakdownReducer: ReducerProtocol {
|
|||
var shieldingFunds: Bool
|
||||
var transparentBalance: Balance
|
||||
|
||||
var totalBalance: Zatoshi {
|
||||
shieldedBalance.data.total + transparentBalance.data.total
|
||||
var totalSpendableBalance: Zatoshi {
|
||||
shieldedBalance.data.verified + transparentBalance.data.verified
|
||||
}
|
||||
|
||||
var isShieldableBalanceAvailable: Bool {
|
||||
|
|
|
@ -25,11 +25,11 @@ struct BalanceBreakdownView: View {
|
|||
|
||||
balanceView(
|
||||
title: L10n.BalanceBreakdown.shieldedZec(TargetConstants.tokenName),
|
||||
viewStore.shieldedBalance.data.total,
|
||||
viewStore.shieldedBalance.data.verified,
|
||||
titleColor: Asset.Colors.Mfp.fontDark.color
|
||||
)
|
||||
balanceView(title: L10n.BalanceBreakdown.transparentBalance, viewStore.transparentBalance.data.total)
|
||||
balanceView(title: L10n.BalanceBreakdown.totalBalance, viewStore.totalBalance)
|
||||
balanceView(title: L10n.BalanceBreakdown.transparentBalance, viewStore.transparentBalance.data.verified)
|
||||
balanceView(title: L10n.BalanceBreakdown.totalSpendableBalance, viewStore.totalSpendableBalance)
|
||||
|
||||
shieldButton(viewStore)
|
||||
|
||||
|
|
|
@ -10,8 +10,7 @@ typealias HomeViewStore = ViewStore<HomeReducer.State, HomeReducer.Action>
|
|||
|
||||
struct HomeReducer: ReducerProtocol {
|
||||
private enum CancelId {}
|
||||
private enum CancelEventsId {}
|
||||
|
||||
|
||||
struct State: Equatable {
|
||||
enum Destination: Equatable {
|
||||
case balanceBreakdown
|
||||
|
@ -38,7 +37,7 @@ struct HomeReducer: ReducerProtocol {
|
|||
var zecPrice = Decimal(140.0)
|
||||
|
||||
var totalCurrencyBalance: Zatoshi {
|
||||
Zatoshi.from(decimal: shieldedBalance.data.total.decimalValue.decimalValue * zecPrice)
|
||||
Zatoshi.from(decimal: shieldedBalance.data.verified.decimalValue.decimalValue * zecPrice)
|
||||
}
|
||||
|
||||
var isSyncing: Bool {
|
||||
|
@ -58,7 +57,7 @@ struct HomeReducer: ReducerProtocol {
|
|||
var isSendButtonDisabled: Bool {
|
||||
// If the destination is `.send` the button must be enabled
|
||||
// to avoid involuntary navigation pop.
|
||||
self.destination != .send && self.isSyncing
|
||||
(self.destination != .send && self.isSyncing) || shieldedBalance.data.verified.amount == 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,10 +130,7 @@ struct HomeReducer: ReducerProtocol {
|
|||
}
|
||||
|
||||
case .onDisappear:
|
||||
return .merge(
|
||||
.cancel(id: CancelId.self),
|
||||
.cancel(id: CancelEventsId.self)
|
||||
)
|
||||
return .cancel(id: CancelId.self)
|
||||
|
||||
case .resolveReviewRequest:
|
||||
if reviewRequest.canRequestReview() {
|
||||
|
@ -163,10 +159,10 @@ struct HomeReducer: ReducerProtocol {
|
|||
switch snapshot.syncStatus {
|
||||
case .error(let error):
|
||||
return EffectTask(value: .showSynchronizerErrorAlert(error.toZcashError()))
|
||||
|
||||
|
||||
case .upToDate:
|
||||
return .fireAndForget { await reviewRequest.syncFinished() }
|
||||
|
||||
|
||||
default:
|
||||
return .none
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ extension HomeView {
|
|||
Button {
|
||||
viewStore.send(.updateDestination(.balanceBreakdown))
|
||||
} label: {
|
||||
Text(L10n.balance(viewStore.shieldedBalance.data.total.decimalString(), TargetConstants.tokenName))
|
||||
Text(L10n.balance(viewStore.shieldedBalance.data.verified.decimalString(), TargetConstants.tokenName))
|
||||
.font(.system(size: 32))
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ struct SendFlowReducer: ReducerProtocol {
|
|||
}
|
||||
|
||||
var totalCurrencyBalance: Zatoshi {
|
||||
Zatoshi.from(decimal: shieldedBalance.data.total.decimalValue.decimalValue * transactionAmountInputState.zecPrice)
|
||||
Zatoshi.from(decimal: shieldedBalance.data.verified.decimalValue.decimalValue * transactionAmountInputState.zecPrice)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,7 +213,7 @@ struct SendFlowReducer: ReducerProtocol {
|
|||
case .synchronizerStateChanged(let latestState):
|
||||
let shieldedBalance = latestState.shieldedBalance
|
||||
state.shieldedBalance = shieldedBalance.redacted
|
||||
state.transactionAmountInputState.maxValue = shieldedBalance.total.amount.redacted
|
||||
state.transactionAmountInputState.maxValue = shieldedBalance.verified.amount.redacted
|
||||
return .none
|
||||
|
||||
case .memo:
|
||||
|
|
|
@ -10,7 +10,7 @@ struct CreateTransaction: View {
|
|||
return WithViewStore(store) { viewStore in
|
||||
VStack(spacing: 5) {
|
||||
VStack(spacing: 0) {
|
||||
Text(L10n.Balance.available(viewStore.shieldedBalance.data.total.decimalString(), TargetConstants.tokenName))
|
||||
Text(L10n.Balance.available(viewStore.shieldedBalance.data.verified.decimalString(), TargetConstants.tokenName))
|
||||
.font(.system(size: 26))
|
||||
.fontWeight(.bold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
|
|
@ -28,12 +28,17 @@ struct TransactionDetailView: View {
|
|||
address(mark: .inactive, viewStore: viewStore)
|
||||
memo(transaction, viewStore, mark: .highlight)
|
||||
|
||||
case .pending:
|
||||
case .sending:
|
||||
Text(L10n.Transaction.youAreSending(transaction.zecAmount.decimalString(), TargetConstants.tokenName))
|
||||
.padding()
|
||||
address(mark: .inactive, viewStore: viewStore)
|
||||
memo(transaction, viewStore, mark: .highlight)
|
||||
|
||||
|
||||
case .receiving:
|
||||
Text(L10n.Transaction.youAreReceiving(transaction.zecAmount.decimalString(), TargetConstants.tokenName))
|
||||
.padding()
|
||||
memo(transaction, viewStore, mark: .highlight)
|
||||
|
||||
case .received:
|
||||
Text(L10n.Transaction.youReceived(transaction.zecAmount.decimalString(), TargetConstants.tokenName))
|
||||
.padding()
|
||||
|
@ -66,8 +71,11 @@ extension TransactionDetailView {
|
|||
var header: some View {
|
||||
HStack {
|
||||
switch transaction.status {
|
||||
case .pending:
|
||||
Text(L10n.Transaction.pending)
|
||||
case .sending:
|
||||
Text(L10n.Transaction.sending)
|
||||
Spacer()
|
||||
case .receiving:
|
||||
Text(L10n.Transaction.receiving)
|
||||
Spacer()
|
||||
case .failed:
|
||||
Text("\(transaction.date?.asHumanReadable() ?? L10n.General.dateNotAvailable)")
|
||||
|
@ -126,7 +134,8 @@ extension TransactionDetailView {
|
|||
|
||||
extension TransactionDetailView {
|
||||
var addressPrefixText: String {
|
||||
transaction.status == .received ? L10n.Transaction.from : L10n.Transaction.to
|
||||
(transaction.status == .received || transaction.status == .receiving)
|
||||
? "" : L10n.Transaction.to
|
||||
}
|
||||
|
||||
var heightText: String {
|
||||
|
@ -216,7 +225,7 @@ struct TransactionDetail_Previews: PreviewProvider {
|
|||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(1_000_000),
|
||||
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
|
||||
status: .pending,
|
||||
status: .sending,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25_000_000)
|
||||
),
|
||||
|
|
|
@ -63,23 +63,26 @@ extension TransactionRowView {
|
|||
case .failed:
|
||||
// TODO: [#392] final text to be provided (https://github.com/zcash/secant-ios-wallet/issues/392)
|
||||
return L10n.Transaction.failed
|
||||
case .pending:
|
||||
case .sending:
|
||||
return L10n.Transaction.sending
|
||||
case .receiving:
|
||||
return L10n.Transaction.receiving
|
||||
}
|
||||
}
|
||||
|
||||
var icon: some View {
|
||||
HStack {
|
||||
let inTransaction = transaction.status == .received || transaction.status == .receiving
|
||||
return HStack {
|
||||
switch transaction.status {
|
||||
case .paid, .received, .pending:
|
||||
case .paid, .received, .sending, .receiving:
|
||||
Image(systemName: "arrow.forward")
|
||||
.resizable()
|
||||
.frame(width: 12, height: 12)
|
||||
.foregroundColor(transaction.status == .received ? .yellow : .white)
|
||||
.foregroundColor(inTransaction ? .yellow : .white)
|
||||
.padding(10)
|
||||
.background(Asset.Colors.Mfp.primary.color)
|
||||
.cornerRadius(40)
|
||||
.rotationEffect(Angle(degrees: transaction.status == .received ? 135 : -45))
|
||||
.rotationEffect(Angle(degrees: inTransaction ? 135 : -45))
|
||||
.padding(.leading, 14)
|
||||
case .failed:
|
||||
// TODO: [#392] final icon to be provided (https://github.com/zcash/secant-ios-wallet/issues/392)
|
||||
|
@ -131,7 +134,7 @@ struct TransactionRowView_Previews: PreviewProvider {
|
|||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(10),
|
||||
id: "2",
|
||||
status: .pending,
|
||||
status: .sending,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(123_000_000)
|
||||
)
|
||||
|
|
|
@ -14,7 +14,8 @@ struct TransactionState: Equatable, Identifiable {
|
|||
case paid(success: Bool)
|
||||
case received
|
||||
case failed
|
||||
case pending
|
||||
case sending
|
||||
case receiving
|
||||
}
|
||||
|
||||
var errorMessage: String?
|
||||
|
@ -36,9 +37,9 @@ struct TransactionState: Equatable, Identifiable {
|
|||
|
||||
var unarySymbol: String {
|
||||
switch status {
|
||||
case .paid, .pending:
|
||||
case .paid, .sending:
|
||||
return "-"
|
||||
case .received:
|
||||
case .received, .receiving:
|
||||
return "+"
|
||||
case .failed:
|
||||
return ""
|
||||
|
@ -74,11 +75,18 @@ extension TransactionState {
|
|||
minedHeight = transaction.minedHeight
|
||||
fee = transaction.fee ?? .zero
|
||||
id = transaction.rawID.toHexStringTxId()
|
||||
status = transaction.isPending(currentHeight: latestBlockHeight ?? 0) ? .pending
|
||||
: transaction.isSentTransaction ? .paid(success: minedHeight ?? 0 > 0) : .received
|
||||
timestamp = transaction.blockTime
|
||||
zecAmount = transaction.isSentTransaction ? Zatoshi(-transaction.value.amount) : transaction.value
|
||||
self.memos = memos
|
||||
|
||||
let isSent = transaction.isSentTransaction
|
||||
let isPending = transaction.isPending(currentHeight: latestBlockHeight ?? 0)
|
||||
switch (isSent, isPending) {
|
||||
case (true, true): status = .sending
|
||||
case (true, false): status = .paid(success: minedHeight ?? 0 > 0)
|
||||
case (false, true): status = .receiving
|
||||
case (false, false): status = .received
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,11 +76,12 @@ private extension WalletEvent {
|
|||
}
|
||||
|
||||
static func mockedWalletEventState(atIndex: Int) -> WalletEvent.WalletEventState {
|
||||
switch atIndex % 4 {
|
||||
switch atIndex % 5 {
|
||||
case 0: return .transaction(.statePlaceholder(.received))
|
||||
case 1: return .transaction(.statePlaceholder(.failed))
|
||||
case 2: return .transaction(.statePlaceholder(.pending))
|
||||
case 3: return .transaction(.placeholder)
|
||||
case 2: return .transaction(.statePlaceholder(.sending))
|
||||
case 3: return .transaction(.statePlaceholder(.receiving))
|
||||
case 4: return .transaction(.placeholder)
|
||||
default: return .transaction(.placeholder)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ internal enum L10n {
|
|||
/// Shielding funds
|
||||
internal static let shieldingFunds = L10n.tr("Localizable", "balanceBreakdown.shieldingFunds", fallback: "Shielding funds")
|
||||
/// TOTAL BALANCE
|
||||
internal static let totalBalance = L10n.tr("Localizable", "balanceBreakdown.totalBalance", fallback: "TOTAL BALANCE")
|
||||
internal static let totalSpendableBalance = L10n.tr("Localizable", "balanceBreakdown.totalSpendableBalance", fallback: "TOTAL BALANCE")
|
||||
/// TRANSPARENT BALANCE
|
||||
internal static let transparentBalance = L10n.tr("Localizable", "balanceBreakdown.transparentBalance", fallback: "TRANSPARENT BALANCE")
|
||||
internal enum Alert {
|
||||
|
@ -592,14 +592,12 @@ internal enum L10n {
|
|||
}
|
||||
/// Failed
|
||||
internal static let failed = L10n.tr("Localizable", "transaction.failed", fallback: "Failed")
|
||||
/// from
|
||||
internal static let from = L10n.tr("Localizable", "transaction.from", fallback: "from")
|
||||
/// PENDING
|
||||
internal static let pending = L10n.tr("Localizable", "transaction.pending", fallback: "PENDING")
|
||||
/// Received
|
||||
internal static let received = L10n.tr("Localizable", "transaction.received", fallback: "Received")
|
||||
/// Sending
|
||||
internal static let sending = L10n.tr("Localizable", "transaction.sending", fallback: "Sending")
|
||||
/// RECEIVING
|
||||
internal static let receiving = L10n.tr("Localizable", "transaction.receiving", fallback: "RECEIVING")
|
||||
/// SENDING
|
||||
internal static let sending = L10n.tr("Localizable", "transaction.sending", fallback: "SENDING")
|
||||
/// Sent
|
||||
internal static let sent = L10n.tr("Localizable", "transaction.sent", fallback: "Sent")
|
||||
/// to
|
||||
|
@ -608,6 +606,10 @@ internal enum L10n {
|
|||
internal static let unconfirmed = L10n.tr("Localizable", "transaction.unconfirmed", fallback: "unconfirmed")
|
||||
/// With memo:
|
||||
internal static let withMemo = L10n.tr("Localizable", "transaction.withMemo", fallback: "With memo:")
|
||||
/// You are receiving %@ %@
|
||||
internal static func youAreReceiving(_ p1: Any, _ p2: Any) -> String {
|
||||
return L10n.tr("Localizable", "transaction.youAreReceiving", String(describing: p1), String(describing: p2), fallback: "You are receiving %@ %@")
|
||||
}
|
||||
/// You are sending %@ %@
|
||||
internal static func youAreSending(_ p1: Any, _ p2: Any) -> String {
|
||||
return L10n.tr("Localizable", "transaction.youAreSending", String(describing: p1), String(describing: p2), fallback: "You are sending %@ %@")
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
"balanceBreakdown.blockId" = "Block: %@";
|
||||
"balanceBreakdown.shieldedZec" = "SHIELDED %@ (SPENDABLE)";
|
||||
"balanceBreakdown.transparentBalance" = "TRANSPARENT BALANCE";
|
||||
"balanceBreakdown.totalBalance" = "TOTAL BALANCE";
|
||||
"balanceBreakdown.totalSpendableBalance" = "TOTAL BALANCE";
|
||||
"balanceBreakdown.autoShieldingThreshold" = "Shielding Threshold: %@ %@";
|
||||
"balanceBreakdown.shieldFunds" = "Shield funds";
|
||||
"balanceBreakdown.shieldingFunds" = "Shielding funds";
|
||||
|
@ -143,18 +143,20 @@
|
|||
"transactions.title" = "Transactions";
|
||||
"transaction.sent" = "Sent";
|
||||
"transaction.sending" = "Sending";
|
||||
"transaction.receiving" = "Receiving";
|
||||
"transaction.received" = "Received";
|
||||
"transaction.failed" = "Failed";
|
||||
"transaction.youSent" = "You sent %@ %@";
|
||||
"transaction.youAreSending" = "You are sending %@ %@";
|
||||
"transaction.youAreReceiving" = "You are receiving %@ %@";
|
||||
"transaction.youReceived" = "You received %@ %@";
|
||||
"transaction.youDidNotSent" = "You DID NOT send %@ %@";
|
||||
"transaction.pending" = "PENDING";
|
||||
"transaction.sending" = "SENDING";
|
||||
"transaction.receiving" = "RECEIVING";
|
||||
"transaction.confirmed" = "Confirmed";
|
||||
"transaction.confirmedTimes" = "%@ times";
|
||||
"transaction.confirming" = "Confirming ~%@mins";
|
||||
"transaction.withMemo" = "With memo:";
|
||||
"transaction.from" = "from";
|
||||
"transaction.to" = "to";
|
||||
"transaction.unconfirmed" = "unconfirmed";
|
||||
"transactionDetail.title" = "Transaction detail";
|
||||
|
|
|
@ -14,7 +14,7 @@ class HomeSnapshotTests: XCTestCase {
|
|||
func testHomeSnapshot() throws {
|
||||
let transactionsHelper: [TransactionStateMockHelper] = [
|
||||
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(1), status: .paid(success: true), uuid: "1"),
|
||||
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(2), status: .pending, uuid: "2"),
|
||||
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(2), status: .sending, uuid: "2"),
|
||||
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(3), status: .received, uuid: "3"),
|
||||
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(4), status: .failed, uuid: "4")
|
||||
]
|
||||
|
|
|
@ -150,7 +150,7 @@ class WalletEventsSnapshotTests: XCTestCase {
|
|||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(1_000_000),
|
||||
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
|
||||
status: .pending,
|
||||
status: .sending,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25_000_000)
|
||||
)
|
||||
|
|
|
@ -57,7 +57,7 @@ class WalletEventsTests: XCTestCase {
|
|||
amount: $0.amount,
|
||||
fee: Zatoshi(10),
|
||||
shielded: $0.shielded,
|
||||
status: $0.amount.amount > 5 ? .pending : $0.status,
|
||||
status: $0.amount.amount > 5 ? .sending : $0.status,
|
||||
timestamp: $0.date,
|
||||
uuid: $0.uuid
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue