Transaction code cleanup (#1793)

* Transaction code cleanup

* Code cleanup

* Code cleanup
This commit is contained in:
Milan 2025-02-28 21:11:32 +01:00 committed by GitHub
parent 2c37d086f0
commit 79f756b580
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 421 additions and 307 deletions

View File

@ -2,16 +2,10 @@ package co.electriccoin.zcash.ui.common.mapper
import cash.z.ecc.android.sdk.model.TransactionPool
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.repository.TransactionData
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVE_FAILED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SENDING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SEND_FAILED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SENT
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDING_FAILED
import co.electriccoin.zcash.ui.common.repository.ReceiveTransaction
import co.electriccoin.zcash.ui.common.repository.SendTransaction
import co.electriccoin.zcash.ui.common.repository.ShieldTransaction
import co.electriccoin.zcash.ui.common.repository.Transaction
import co.electriccoin.zcash.ui.common.usecase.ListTransactionData
import co.electriccoin.zcash.ui.design.util.StringResource
import co.electriccoin.zcash.ui.design.util.StringResourceColor
@ -22,80 +16,72 @@ import co.electriccoin.zcash.ui.screen.transactionhistory.TransactionState
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
class TransactionHistoryMapper {
fun createTransactionState(
transaction: ListTransactionData,
data: ListTransactionData,
restoreTimestamp: Instant,
onTransactionClick: (TransactionData) -> Unit
onTransactionClick: (Transaction) -> Unit
): TransactionState {
val transactionDate =
transaction.data.overview.blockTimeEpochSeconds
?.let { blockTimeEpochSeconds ->
Instant.ofEpochSecond(blockTimeEpochSeconds).atZone(ZoneId.systemDefault())
}
return TransactionState(
key = transaction.data.overview.txId.txIdString(),
icon = getIcon(transaction),
title = getTitle(transaction),
subtitle = getSubtitle(transactionDate),
isShielded = isShielded(transaction),
value = getValue(transaction),
onClick = { onTransactionClick(transaction.data) },
isUnread = isUnread(transaction, transactionDate, restoreTimestamp)
key = data.transaction.id.txIdString(),
icon = getIcon(data),
title = getTitle(data),
subtitle = getSubtitle(data),
isShielded = isShielded(data),
value = getValue(data),
onClick = { onTransactionClick(data.transaction) },
isUnread = isUnread(data, restoreTimestamp)
)
}
private fun isUnread(
transaction: ListTransactionData,
transactionDateTime: ZonedDateTime?,
data: ListTransactionData,
restoreTimestamp: Instant,
): Boolean {
val hasMemo = transaction.data.overview.memoCount > 0
val transactionDateTime = data.transaction.timestamp?.atZone(ZoneId.systemDefault())
val hasMemo = data.transaction.memoCount > 0
val transactionDate = transactionDateTime?.toLocalDate() ?: LocalDate.now()
val restoreDate = restoreTimestamp.atZone(ZoneId.systemDefault()).toLocalDate()
return if (hasMemo && transactionDate < restoreDate) {
false
} else {
val transactionMetadata = transaction.metadata
val transactionMetadata = data.metadata
hasMemo && (transactionMetadata == null || transactionMetadata.isRead.not())
}
}
private fun getIcon(transaction: ListTransactionData) =
when (transaction.data.state) {
SENT -> R.drawable.ic_transaction_sent
SENDING -> R.drawable.ic_transaction_send_pending
SEND_FAILED -> R.drawable.ic_transaction_send_failed
RECEIVED -> R.drawable.ic_transaction_received
RECEIVING -> R.drawable.ic_transaction_receive_pending
RECEIVE_FAILED -> R.drawable.ic_transaction_receive_pending
SHIELDED -> R.drawable.ic_transaction_shielded
SHIELDING -> R.drawable.ic_transaction_shield_pending
SHIELDING_FAILED -> R.drawable.ic_transaction_shield_failed
private fun getIcon(data: ListTransactionData) =
when (data.transaction) {
is SendTransaction.Success -> R.drawable.ic_transaction_sent
is SendTransaction.Pending -> R.drawable.ic_transaction_send_pending
is SendTransaction.Failed -> R.drawable.ic_transaction_send_failed
is ReceiveTransaction.Success -> R.drawable.ic_transaction_received
is ReceiveTransaction.Pending -> R.drawable.ic_transaction_receive_pending
is ReceiveTransaction.Failed -> R.drawable.ic_transaction_receive_pending
is ShieldTransaction.Success -> R.drawable.ic_transaction_shielded
is ShieldTransaction.Pending -> R.drawable.ic_transaction_shield_pending
is ShieldTransaction.Failed -> R.drawable.ic_transaction_shield_failed
}
private fun getTitle(transaction: ListTransactionData) =
when (transaction.data.state) {
SENT -> stringRes(R.string.transaction_history_sent)
SENDING -> stringRes(R.string.transaction_history_sending)
SEND_FAILED -> stringRes(R.string.transaction_history_sending_failed)
RECEIVED -> stringRes(R.string.transaction_history_received)
RECEIVING -> stringRes(R.string.transaction_history_receiving)
RECEIVE_FAILED -> stringRes(R.string.transaction_history_receiving_failed)
SHIELDED -> stringRes(R.string.transaction_history_shielded)
SHIELDING -> stringRes(R.string.transaction_history_shielding)
SHIELDING_FAILED -> stringRes(R.string.transaction_history_shielding_failed)
private fun getTitle(data: ListTransactionData) =
when (data.transaction) {
is SendTransaction.Success -> stringRes(R.string.transaction_history_sent)
is SendTransaction.Pending -> stringRes(R.string.transaction_history_sending)
is SendTransaction.Failed -> stringRes(R.string.transaction_history_sending_failed)
is ReceiveTransaction.Success -> stringRes(R.string.transaction_history_received)
is ReceiveTransaction.Pending -> stringRes(R.string.transaction_history_receiving)
is ReceiveTransaction.Failed -> stringRes(R.string.transaction_history_receiving_failed)
is ShieldTransaction.Success -> stringRes(R.string.transaction_history_shielded)
is ShieldTransaction.Pending -> stringRes(R.string.transaction_history_shielding)
is ShieldTransaction.Failed -> stringRes(R.string.transaction_history_shielding_failed)
}
private fun getSubtitle(transactionDate: ZonedDateTime?): StringResource? {
if (transactionDate == null) return null
private fun getSubtitle(data: ListTransactionData): StringResource? {
val timestamp = data.transaction.timestamp ?: return null
val transactionDate = timestamp.atZone(ZoneId.systemDefault())
val daysBetween = ChronoUnit.DAYS.between(transactionDate.toLocalDate(), LocalDate.now())
return when {
LocalDate.now() == transactionDate.toLocalDate() ->
@ -111,71 +97,72 @@ class TransactionHistoryMapper {
}
}
private fun isShielded(transaction: ListTransactionData) =
transaction.data.transactionOutputs
private fun isShielded(data: ListTransactionData) =
data.transaction
.transactionOutputs
.none { output -> output.pool == TransactionPool.TRANSPARENT } &&
!transaction.data.overview.isShielding
data.transaction !is ShieldTransaction
private fun getValue(transaction: ListTransactionData) =
when (transaction.data.state) {
SENT,
SENDING ->
private fun getValue(data: ListTransactionData) =
when (data.transaction) {
is SendTransaction.Success,
is SendTransaction.Pending ->
StyledStringResource(
stringRes(
R.string.transaction_history_minus,
stringRes(transaction.data.overview.netValue)
stringRes(data.transaction.amount)
)
)
SEND_FAILED ->
is SendTransaction.Failed ->
StyledStringResource(
stringRes(
R.string.transaction_history_minus,
stringRes(transaction.data.overview.netValue)
stringRes(data.transaction.amount)
),
StringResourceColor.NEGATIVE
)
RECEIVED ->
is ReceiveTransaction.Success ->
StyledStringResource(
stringRes(
R.string.transaction_history_plus,
stringRes(transaction.data.overview.netValue)
stringRes(data.transaction.amount)
),
StringResourceColor.POSITIVE
)
RECEIVING ->
is ReceiveTransaction.Pending ->
StyledStringResource(
stringRes(
R.string.transaction_history_plus,
stringRes(transaction.data.overview.netValue)
stringRes(data.transaction.amount)
),
)
RECEIVE_FAILED ->
is ReceiveTransaction.Failed ->
StyledStringResource(
stringRes(
R.string.transaction_history_plus,
stringRes(transaction.data.overview.netValue)
stringRes(data.transaction.amount)
),
StringResourceColor.NEGATIVE
)
SHIELDED,
SHIELDING ->
is ShieldTransaction.Success,
is ShieldTransaction.Pending ->
StyledStringResource(
stringRes(
R.string.transaction_history_plus,
stringRes(transaction.data.overview.totalSpent)
stringRes(data.transaction.amount)
)
)
SHIELDING_FAILED ->
is ShieldTransaction.Failed ->
StyledStringResource(
stringRes(
R.string.transaction_history_plus,
stringRes(transaction.data.overview.totalSpent)
stringRes(data.transaction.amount)
),
StringResourceColor.NEGATIVE
)

View File

@ -3,17 +3,12 @@ package co.electriccoin.zcash.ui.common.repository
import cash.z.ecc.android.sdk.model.TransactionId
import cash.z.ecc.android.sdk.model.TransactionOutput
import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionState.Confirmed
import cash.z.ecc.android.sdk.model.TransactionState.Expired
import cash.z.ecc.android.sdk.model.TransactionState.Pending
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.common.datasource.AccountDataSource
import co.electriccoin.zcash.ui.common.provider.SynchronizerProvider
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVE_FAILED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SENDING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SEND_FAILED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SENT
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDING_FAILED
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -38,22 +33,22 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.ZonedDateTime
import java.time.Instant
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
interface TransactionRepository {
val currentTransactions: Flow<List<TransactionData>?>
val currentTransactions: Flow<List<Transaction>?>
suspend fun getMemos(transactionData: TransactionData): List<String>
suspend fun getMemos(transaction: Transaction): List<String>
suspend fun getRecipients(transactionData: TransactionData): String?
suspend fun getRecipients(transaction: Transaction): String?
fun observeTransaction(txId: String): Flow<TransactionData?>
fun observeTransaction(txId: String): Flow<Transaction?>
fun observeTransactionsByMemo(memo: String): Flow<List<TransactionId>?>
suspend fun getTransactions(): List<TransactionData>
suspend fun getTransactions(): List<Transaction>
}
class TransactionRepositoryImpl(
@ -63,7 +58,7 @@ class TransactionRepositoryImpl(
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
@OptIn(ExperimentalCoroutinesApi::class)
override val currentTransactions: Flow<List<TransactionData>?> =
override val currentTransactions: Flow<List<Transaction>?> =
combine(
synchronizerProvider.synchronizer,
accountDataSource.selectedAccount.map { it?.sdkAccount }
@ -73,20 +68,141 @@ class TransactionRepositoryImpl(
if (synchronizer == null || account == null) {
flowOf(null)
} else {
channelFlow<List<TransactionData>?> {
channelFlow<List<Transaction>?> {
send(null)
launch {
synchronizer.getTransactions(account.accountUuid)
synchronizer
.getTransactions(account.accountUuid)
.mapLatest { transactions ->
transactions.map { transaction ->
TransactionData(
overview = transaction,
transactionOutputs = synchronizer.getTransactionOutputs(transaction),
state = transaction.getExtendedState()
)
when (transaction.transactionState) {
Expired ->
when {
transaction.isShielding ->
ShieldTransaction.Failed(
timestamp = createTimestamp(transaction) ?: Instant.now(),
transactionOutputs =
synchronizer.getTransactionOutputs
(transaction),
amount = transaction.totalSpent,
id = transaction.txId,
memoCount = transaction.memoCount,
fee = transaction.netValue,
overview = transaction
)
transaction.isSentTransaction ->
SendTransaction.Failed(
timestamp = createTimestamp(transaction) ?: Instant.now(),
transactionOutputs =
synchronizer.getTransactionOutputs
(transaction),
amount = transaction.netValue,
id = transaction.txId,
memoCount = transaction.memoCount,
fee = transaction.feePaid,
overview = transaction
)
else ->
ReceiveTransaction.Failed(
timestamp = createTimestamp(transaction) ?: Instant.now(),
transactionOutputs =
synchronizer.getTransactionOutputs(transaction),
amount = transaction.netValue,
id = transaction.txId,
memoCount = transaction.memoCount,
overview = transaction
)
}
Confirmed ->
when {
transaction.isShielding ->
ShieldTransaction.Success(
timestamp = createTimestamp(transaction) ?: Instant.now(),
transactionOutputs =
synchronizer.getTransactionOutputs
(transaction),
amount = transaction.totalSpent,
id = transaction.txId,
memoCount = transaction.memoCount,
fee = transaction.netValue,
overview = transaction
)
transaction.isSentTransaction ->
SendTransaction.Success(
timestamp = createTimestamp(transaction) ?: Instant.now(),
transactionOutputs =
synchronizer.getTransactionOutputs
(transaction),
amount = transaction.netValue,
id = transaction.txId,
memoCount = transaction.memoCount,
fee = transaction.feePaid,
overview = transaction
)
else ->
ReceiveTransaction.Success(
timestamp = createTimestamp(transaction) ?: Instant.now(),
transactionOutputs =
synchronizer.getTransactionOutputs
(transaction),
amount = transaction.netValue,
id = transaction.txId,
memoCount = transaction.memoCount,
overview = transaction
)
}
Pending ->
when {
transaction.isShielding ->
ShieldTransaction.Pending(
timestamp = createTimestamp(transaction),
transactionOutputs =
synchronizer.getTransactionOutputs
(transaction),
amount = transaction.totalSpent,
id = transaction.txId,
memoCount = transaction.memoCount,
fee = transaction.netValue,
overview = transaction
)
transaction.isSentTransaction ->
SendTransaction.Pending(
timestamp = createTimestamp(transaction),
transactionOutputs =
synchronizer.getTransactionOutputs
(transaction),
amount = transaction.netValue,
id = transaction.txId,
memoCount = transaction.memoCount,
fee = transaction.feePaid,
overview = transaction
)
else ->
ReceiveTransaction.Pending(
timestamp = createTimestamp(transaction),
transactionOutputs =
synchronizer.getTransactionOutputs
(transaction),
amount = transaction.netValue,
id = transaction.txId,
memoCount = transaction.memoCount,
overview = transaction
)
}
else -> error("Unexpected transaction stat")
}
}.sortedByDescending { transaction ->
transaction.overview.blockTimeEpochSeconds ?: ZonedDateTime.now().toEpochSecond()
transaction.timestamp ?: Instant.now()
}
}
.collect {
@ -105,19 +221,23 @@ class TransactionRepositoryImpl(
initialValue = null
)
override suspend fun getMemos(transactionData: TransactionData): List<String> =
private fun createTimestamp(transaction: TransactionOverview): Instant? {
return transaction.blockTimeEpochSeconds?.let { Instant.ofEpochSecond(it) }
}
override suspend fun getMemos(transaction: Transaction): List<String> =
withContext(Dispatchers.IO) {
synchronizerProvider.getSynchronizer().getMemos(transactionData.overview)
synchronizerProvider.getSynchronizer().getMemos(transaction.overview)
.mapNotNull { memo -> memo.takeIf { it.isNotEmpty() } }
.toList()
}
override suspend fun getRecipients(transactionData: TransactionData): String? =
override suspend fun getRecipients(transaction: Transaction): String? =
withContext(Dispatchers.IO) {
if (transactionData.overview.isSentTransaction) {
if (transaction is SendTransaction) {
synchronizerProvider
.getSynchronizer()
.getRecipients(transactionData.overview)
.getRecipients(transaction.overview)
.firstOrNull()
?.addressValue
} else {
@ -125,10 +245,10 @@ class TransactionRepositoryImpl(
}
}
override fun observeTransaction(txId: String): Flow<TransactionData?> =
override fun observeTransaction(txId: String): Flow<Transaction?> =
currentTransactions
.map { transactions ->
transactions?.find { it.overview.txId.txIdString() == txId }
transactions?.find { it.id.txIdString() == txId }
}
@OptIn(ExperimentalCoroutinesApi::class)
@ -140,52 +260,111 @@ class TransactionRepositoryImpl(
}
.distinctUntilChanged()
override suspend fun getTransactions(): List<TransactionData> {
return currentTransactions.filterNotNull().first()
}
override suspend fun getTransactions(): List<Transaction> = currentTransactions.filterNotNull().first()
}
data class TransactionData(
val overview: TransactionOverview,
val transactionOutputs: List<TransactionOutput>,
val state: TransactionExtendedState,
)
enum class TransactionExtendedState {
SENT,
SENDING,
SEND_FAILED,
RECEIVED,
RECEIVING,
RECEIVE_FAILED,
SHIELDED,
SHIELDING,
SHIELDING_FAILED
sealed interface Transaction {
val id: TransactionId
val amount: Zatoshi
val memoCount: Int
val timestamp: Instant?
val transactionOutputs: List<TransactionOutput>
val overview: TransactionOverview
val fee: Zatoshi?
}
private fun TransactionOverview.getExtendedState(): TransactionExtendedState {
return when (transactionState) {
cash.z.ecc.android.sdk.model.TransactionState.Expired ->
when {
isShielding -> SHIELDING_FAILED
isSentTransaction -> SEND_FAILED
else -> RECEIVE_FAILED
}
sealed interface SendTransaction : Transaction {
data class Success(
override val id: TransactionId,
override val amount: Zatoshi,
override val timestamp: Instant,
override val memoCount: Int,
override val fee: Zatoshi?,
override val transactionOutputs: List<TransactionOutput>,
override val overview: TransactionOverview,
) : SendTransaction
cash.z.ecc.android.sdk.model.TransactionState.Confirmed ->
when {
isShielding -> SHIELDED
isSentTransaction -> SENT
else -> RECEIVED
}
data class Pending(
override val id: TransactionId,
override val amount: Zatoshi,
override val timestamp: Instant?,
override val memoCount: Int,
override val fee: Zatoshi?,
override val transactionOutputs: List<TransactionOutput>,
override val overview: TransactionOverview,
) : SendTransaction
cash.z.ecc.android.sdk.model.TransactionState.Pending ->
when {
isShielding -> SHIELDING
isSentTransaction -> SENDING
else -> RECEIVING
}
else -> error("Unexpected transaction state found while calculating its extended state.")
}
data class Failed(
override val id: TransactionId,
override val amount: Zatoshi,
override val timestamp: Instant,
override val memoCount: Int,
override val fee: Zatoshi?,
override val transactionOutputs: List<TransactionOutput>,
override val overview: TransactionOverview,
) : SendTransaction
}
sealed interface ReceiveTransaction : Transaction {
override val fee: Zatoshi?
get() = null
data class Success(
override val id: TransactionId,
override val amount: Zatoshi,
override val timestamp: Instant,
override val memoCount: Int,
override val transactionOutputs: List<TransactionOutput>,
override val overview: TransactionOverview,
) : ReceiveTransaction
data class Pending(
override val id: TransactionId,
override val amount: Zatoshi,
override val timestamp: Instant?,
override val memoCount: Int,
override val transactionOutputs: List<TransactionOutput>,
override val overview: TransactionOverview,
) : ReceiveTransaction
data class Failed(
override val id: TransactionId,
override val amount: Zatoshi,
override val timestamp: Instant,
override val memoCount: Int,
override val transactionOutputs: List<TransactionOutput>,
override val overview: TransactionOverview,
) : ReceiveTransaction
}
sealed interface ShieldTransaction : Transaction {
data class Success(
override val id: TransactionId,
override val amount: Zatoshi,
override val timestamp: Instant,
override val memoCount: Int,
override val fee: Zatoshi?,
override val transactionOutputs: List<TransactionOutput>,
override val overview: TransactionOverview,
) : ShieldTransaction
data class Pending(
override val id: TransactionId,
override val amount: Zatoshi,
override val timestamp: Instant?,
override val memoCount: Int,
override val fee: Zatoshi?,
override val transactionOutputs: List<TransactionOutput>,
override val overview: TransactionOverview,
) : ShieldTransaction
data class Failed(
override val id: TransactionId,
override val amount: Zatoshi,
override val memoCount: Int,
override val timestamp: Instant,
override val transactionOutputs: List<TransactionOutput>,
override val fee: Zatoshi?,
override val overview: TransactionOverview,
) : ShieldTransaction
}

View File

@ -8,15 +8,9 @@ import co.electriccoin.zcash.ui.common.datasource.AccountDataSource
import co.electriccoin.zcash.ui.common.model.KeystoneAccount
import co.electriccoin.zcash.ui.common.model.ZashiAccount
import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVE_FAILED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SENDING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SEND_FAILED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SENT
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDING_FAILED
import co.electriccoin.zcash.ui.common.repository.ReceiveTransaction
import co.electriccoin.zcash.ui.common.repository.SendTransaction
import co.electriccoin.zcash.ui.common.repository.ShieldTransaction
import co.electriccoin.zcash.ui.common.repository.TransactionRepository
import co.electriccoin.zcash.ui.design.util.getString
import co.electriccoin.zcash.ui.design.util.stringRes
@ -25,7 +19,6 @@ import co.electriccoin.zcash.ui.util.FileShareUtil.ZASHI_INTERNAL_DATA_MIME_TYPE
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.time.Instant
import java.time.Year
import java.time.ZoneId
import java.time.ZoneOffset
@ -122,12 +115,7 @@ class ExportTaxUseCase(
.mapNotNull { transaction ->
val previousYear = Year.now().minusYears(1)
val date =
transaction.overview.blockTimeEpochSeconds
?.let {
Instant.ofEpochSecond(it).atZone(ZoneId.of("UTC"))
} ?: return@mapNotNull null
val date = transaction.timestamp?.atZone(ZoneId.of("UTC")) ?: return@mapNotNull null
if (date.year != previousYear.value) return@mapNotNull null
val dateString =
@ -139,16 +127,16 @@ class ExportTaxUseCase(
formatter.format(date)
} ?: return@mapNotNull null
when (transaction.state) {
SENT,
SENDING -> {
val fee = transaction.overview.feePaid
when (transaction) {
is SendTransaction.Success,
is SendTransaction.Pending -> {
val fee = transaction.fee
val sentQuantity =
if (fee != null) {
stringRes(transaction.overview.netValue - fee)
stringRes(transaction.amount - fee)
} else {
stringRes(transaction.overview.netValue)
stringRes(transaction.amount)
}
CsvEntry(
@ -163,12 +151,12 @@ class ExportTaxUseCase(
)
}
SEND_FAILED -> null
RECEIVED,
RECEIVING -> {
is SendTransaction.Failed -> null
is ReceiveTransaction.Success,
is ReceiveTransaction.Pending -> {
CsvEntry(
date = dateString,
receivedQuantity = stringRes(transaction.overview.netValue).getString(context),
receivedQuantity = stringRes(transaction.amount).getString(context),
receivedCurrency = ZEC_SYMBOL,
sentQuantity = "",
sentCurrency = "",
@ -178,10 +166,10 @@ class ExportTaxUseCase(
)
}
RECEIVE_FAILED -> null
SHIELDED,
SHIELDING,
SHIELDING_FAILED -> null
is ReceiveTransaction.Failed -> null
is ShieldTransaction.Success,
is ShieldTransaction.Pending,
is ShieldTransaction.Failed -> null
}
}
}

View File

@ -6,7 +6,9 @@ import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSource
import co.electriccoin.zcash.ui.common.model.AddressBookContact
import co.electriccoin.zcash.ui.common.repository.AddressBookRepository
import co.electriccoin.zcash.ui.common.repository.MetadataRepository
import co.electriccoin.zcash.ui.common.repository.TransactionData
import co.electriccoin.zcash.ui.common.repository.SendTransaction
import co.electriccoin.zcash.ui.common.repository.ShieldTransaction
import co.electriccoin.zcash.ui.common.repository.Transaction
import co.electriccoin.zcash.ui.common.repository.TransactionFilter
import co.electriccoin.zcash.ui.common.repository.TransactionFilterRepository
import co.electriccoin.zcash.ui.common.repository.TransactionMetadata
@ -57,7 +59,7 @@ class GetCurrentFilteredTransactionsUseCase(
if (recipient == null) {
metadataRepository.observeTransactionMetadataByTxId(
transaction.overview.txId.txIdString()
transaction.id.txIdString()
).map {
FilterTransactionData(
transaction = transaction,
@ -70,7 +72,7 @@ class GetCurrentFilteredTransactionsUseCase(
combine(
addressBookRepository.observeContactByAddress(recipient),
metadataRepository.observeTransactionMetadataByTxId(
txId = transaction.overview.txId.txIdString(),
txId = transaction.id.txIdString(),
)
) { contact, transactionMetadata ->
FilterTransactionData(
@ -148,7 +150,7 @@ class GetCurrentFilteredTransactionsUseCase(
}
?.map { transaction ->
ListTransactionData(
data = transaction.transaction,
transaction = transaction.transaction,
metadata = transaction.transactionMetadata
)
}
@ -167,7 +169,7 @@ class GetCurrentFilteredTransactionsUseCase(
): Boolean {
val memoPass =
if (filters.contains(TransactionFilter.MEMOS)) {
transaction.transaction.overview.memoCount > 0
transaction.transaction.memoCount > 0
} else {
true
}
@ -201,12 +203,11 @@ class GetCurrentFilteredTransactionsUseCase(
return if (filters.contains(TransactionFilter.SENT) || filters.contains(TransactionFilter.RECEIVED)) {
when {
filters.contains(TransactionFilter.SENT) &&
transaction.transaction.overview.isSentTransaction &&
!transaction.transaction.overview.isShielding -> true
transaction.transaction is SendTransaction -> true
filters.contains(TransactionFilter.RECEIVED) &&
!transaction.transaction.overview.isSentTransaction &&
!transaction.transaction.overview.isShielding -> true
transaction.transaction !is SendTransaction &&
transaction.transaction !is ShieldTransaction -> true
else -> false
}
@ -220,12 +221,10 @@ class GetCurrentFilteredTransactionsUseCase(
restoreTimestamp: Instant,
): Boolean {
val transactionDate =
transaction.transaction.overview.blockTimeEpochSeconds
?.let { blockTimeEpochSeconds ->
Instant.ofEpochSecond(blockTimeEpochSeconds).atZone(ZoneId.systemDefault()).toLocalDate()
} ?: LocalDate.now()
transaction.transaction.timestamp?.atZone(ZoneId.systemDefault())?.toLocalDate()
?: LocalDate.now()
val hasMemo = transaction.transaction.overview.memoCount > 0
val hasMemo = transaction.transaction.memoCount > 0
val restoreDate = restoreTimestamp.atZone(ZoneId.systemDefault()).toLocalDate()
return if (hasMemo && transactionDate < restoreDate) {
@ -261,7 +260,7 @@ class GetCurrentFilteredTransactionsUseCase(
transaction: FilterTransactionData,
fulltextFilter: String
): Boolean {
val text = stringRes(transaction.transaction.overview.netValue).getString(context)
val text = stringRes(transaction.transaction.amount).getString(context)
return text.contains(fulltextFilter, ignoreCase = true)
}
@ -282,11 +281,11 @@ class GetCurrentFilteredTransactionsUseCase(
private fun hasMemoInFilteredIds(
memoTxIds: List<TransactionId>?,
transaction: FilterTransactionData
) = memoTxIds?.contains(transaction.transaction.overview.txId) ?: false
) = memoTxIds?.contains(transaction.transaction.id) ?: false
}
private data class FilterTransactionData(
val transaction: TransactionData,
val transaction: Transaction,
val contact: AddressBookContact?,
val recipientAddress: String?,
val transactionMetadata: TransactionMetadata?

View File

@ -1,7 +1,7 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.common.repository.MetadataRepository
import co.electriccoin.zcash.ui.common.repository.TransactionData
import co.electriccoin.zcash.ui.common.repository.Transaction
import co.electriccoin.zcash.ui.common.repository.TransactionMetadata
import co.electriccoin.zcash.ui.common.repository.TransactionRepository
import co.electriccoin.zcash.ui.util.combineToFlow
@ -25,10 +25,10 @@ class GetCurrentTransactionsUseCase(
} else {
transactions
.map {
metadataRepository.observeTransactionMetadataByTxId(it.overview.txId.txIdString())
metadataRepository.observeTransactionMetadataByTxId(it.id.txIdString())
.mapLatest { metadata ->
ListTransactionData(
data = it,
transaction = it,
metadata = metadata
)
}
@ -39,6 +39,6 @@ class GetCurrentTransactionsUseCase(
}
data class ListTransactionData(
val data: TransactionData,
val transaction: Transaction,
val metadata: TransactionMetadata?
)

View File

@ -6,7 +6,7 @@ import co.electriccoin.zcash.ui.common.model.AddressBookContact
import co.electriccoin.zcash.ui.common.provider.SynchronizerProvider
import co.electriccoin.zcash.ui.common.repository.AddressBookRepository
import co.electriccoin.zcash.ui.common.repository.MetadataRepository
import co.electriccoin.zcash.ui.common.repository.TransactionData
import co.electriccoin.zcash.ui.common.repository.Transaction
import co.electriccoin.zcash.ui.common.repository.TransactionMetadata
import co.electriccoin.zcash.ui.common.repository.TransactionRepository
import kotlinx.coroutines.Dispatchers
@ -82,7 +82,7 @@ class GetTransactionDetailByIdUseCase(
}
data class DetailedTransactionData(
val transaction: TransactionData,
val transaction: Transaction,
val memos: List<String>?,
val contact: AddressBookContact?,
val recipientAddress: WalletAddress?,

View File

@ -236,13 +236,8 @@ internal fun WrapSend(
)
)
val fee = it.transaction.overview.feePaid
val value =
if (fee == null) {
it.transaction.overview.netValue
} else {
it.transaction.overview.netValue - fee
}
val fee = it.transaction.fee
val value = if (fee == null) it.transaction.amount else it.transaction.amount - fee
setAmountState(
AmountState.newFromZec(

View File

@ -7,15 +7,9 @@ import cash.z.ecc.android.sdk.model.WalletAddress
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVE_FAILED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.RECEIVING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SENDING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SEND_FAILED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SENT
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDED
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDING
import co.electriccoin.zcash.ui.common.repository.TransactionExtendedState.SHIELDING_FAILED
import co.electriccoin.zcash.ui.common.repository.ReceiveTransaction
import co.electriccoin.zcash.ui.common.repository.SendTransaction
import co.electriccoin.zcash.ui.common.repository.ShieldTransaction
import co.electriccoin.zcash.ui.common.usecase.CopyToClipboardUseCase
import co.electriccoin.zcash.ui.common.usecase.DetailedTransactionData
import co.electriccoin.zcash.ui.common.usecase.FlipTransactionBookmarkUseCase
@ -49,7 +43,6 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.Instant
import java.time.ZoneId
@Suppress("TooManyFunctions")
@ -110,7 +103,7 @@ class TransactionDetailViewModel(
viewModelScope.launch {
withContext(Dispatchers.Default) {
val transaction = transaction.filterNotNull().first()
if (transaction.transaction.overview.memoCount > 0) {
if (transaction.transaction.memoCount > 0) {
markTxMemoAsRead(transactionDetail.transactionId)
}
}
@ -122,10 +115,10 @@ class TransactionDetailViewModel(
}
private fun createTransactionInfoState(transaction: DetailedTransactionData): TransactionDetailInfoState {
return when (transaction.transaction.state) {
SENT,
SENDING,
SEND_FAILED -> {
return when (transaction.transaction) {
is SendTransaction.Success,
is SendTransaction.Pending,
is SendTransaction.Failed -> {
if (transaction.recipientAddress is WalletAddress.Transparent) {
SendTransparentState(
contact = transaction.contact?.let { stringRes(it.name) },
@ -133,11 +126,11 @@ class TransactionDetailViewModel(
addressAbbreviated = createAbbreviatedAddressStringRes(transaction),
transactionId =
stringResByTransactionId(
value = transaction.transaction.overview.txId.txIdString(),
value = transaction.transaction.id.txIdString(),
abbreviated = true
),
onTransactionIdClick = {
onCopyToClipboard(transaction.transaction.overview.txId.txIdString())
onCopyToClipboard(transaction.transaction.id.txIdString())
},
onTransactionAddressClick = { onCopyToClipboard(transaction.recipientAddress.address) },
fee = createFeeStringRes(transaction),
@ -151,11 +144,11 @@ class TransactionDetailViewModel(
addressAbbreviated = createAbbreviatedAddressStringRes(transaction),
transactionId =
stringResByTransactionId(
value = transaction.transaction.overview.txId.txIdString(),
value = transaction.transaction.id.txIdString(),
abbreviated = true
),
onTransactionIdClick = {
onCopyToClipboard(transaction.transaction.overview.txId.txIdString())
onCopyToClipboard(transaction.transaction.id.txIdString())
},
onTransactionAddressClick = {
onCopyToClipboard(transaction.recipientAddress?.address.orEmpty())
@ -177,18 +170,18 @@ class TransactionDetailViewModel(
}
}
RECEIVED,
RECEIVING,
RECEIVE_FAILED -> {
is ReceiveTransaction.Success,
is ReceiveTransaction.Pending,
is ReceiveTransaction.Failed -> {
if (transaction.transaction.transactionOutputs.all { it.pool == TransactionPool.TRANSPARENT }) {
ReceiveTransparentState(
transactionId =
stringResByTransactionId(
value = transaction.transaction.overview.txId.txIdString(),
value = transaction.transaction.id.txIdString(),
abbreviated = true
),
onTransactionIdClick = {
onCopyToClipboard(transaction.transaction.overview.txId.txIdString())
onCopyToClipboard(transaction.transaction.id.txIdString())
},
completedTimestamp = createTimestampStringRes(transaction),
note = transaction.metadata?.note?.let { stringRes(it) }
@ -197,11 +190,11 @@ class TransactionDetailViewModel(
ReceiveShieldedState(
transactionId =
stringResByTransactionId(
value = transaction.transaction.overview.txId.txIdString(),
value = transaction.transaction.id.txIdString(),
abbreviated = true
),
onTransactionIdClick = {
onCopyToClipboard(transaction.transaction.overview.txId.txIdString())
onCopyToClipboard(transaction.transaction.id.txIdString())
},
completedTimestamp = createTimestampStringRes(transaction),
memo =
@ -219,17 +212,17 @@ class TransactionDetailViewModel(
}
}
SHIELDED,
SHIELDING,
SHIELDING_FAILED -> {
is ShieldTransaction.Success,
is ShieldTransaction.Pending,
is ShieldTransaction.Failed -> {
ShieldingState(
transactionId =
stringResByTransactionId(
value = transaction.transaction.overview.txId.txIdString(),
value = transaction.transaction.id.txIdString(),
abbreviated = true
),
onTransactionIdClick = {
onCopyToClipboard(transaction.transaction.overview.txId.txIdString())
onCopyToClipboard(transaction.transaction.id.txIdString())
},
completedTimestamp = createTimestampStringRes(transaction),
fee = createFeeStringRes(transaction),
@ -239,19 +232,10 @@ class TransactionDetailViewModel(
}
}
private fun createFeeStringRes(transaction: DetailedTransactionData): StringResource {
private fun createFeeStringRes(data: DetailedTransactionData): StringResource {
val feePaid =
when (transaction.transaction.state) {
SENT,
SENDING,
SEND_FAILED,
RECEIVED,
RECEIVING,
RECEIVE_FAILED -> transaction.transaction.overview.feePaid
SHIELDED,
SHIELDING,
SHIELDING_FAILED -> transaction.transaction.overview.netValue
} ?: return stringRes(R.string.transaction_detail_fee_minimal)
data.transaction.fee.takeIf { data.transaction !is ReceiveTransaction }
?: return stringRes(R.string.transaction_detail_fee_minimal)
return if (feePaid.value < MIN_FEE_THRESHOLD) {
stringRes(R.string.transaction_detail_fee_minimal)
@ -272,11 +256,8 @@ class TransactionDetailViewModel(
abbreviated = true
)
private fun createTimestampStringRes(transaction: DetailedTransactionData) =
transaction.transaction.overview.blockTimeEpochSeconds
?.let { blockTimeEpochSeconds ->
Instant.ofEpochSecond(blockTimeEpochSeconds)
}
private fun createTimestampStringRes(data: DetailedTransactionData) =
data.transaction.timestamp
?.atZone(ZoneId.systemDefault())
?.let {
stringResByDateTime(
@ -292,27 +273,27 @@ class TransactionDetailViewModel(
)
}
private fun createPrimaryButtonState(transaction: DetailedTransactionData) =
if (transaction.contact == null) {
when (transaction.transaction.state) {
SENT,
SENDING,
SEND_FAILED ->
private fun createPrimaryButtonState(data: DetailedTransactionData) =
if (data.contact == null) {
when (data.transaction) {
is SendTransaction.Success,
is SendTransaction.Pending,
is SendTransaction.Failed ->
ButtonState(
text = stringRes(R.string.transaction_detail_save_address),
onClick = { onSaveAddressClick(transaction) }
onClick = { onSaveAddressClick(data) }
)
else -> null
}
} else {
when (transaction.transaction.state) {
SENT,
SENDING,
SEND_FAILED ->
when (data.transaction) {
is ReceiveTransaction.Success,
is ReceiveTransaction.Pending,
is ReceiveTransaction.Failed ->
ButtonState(
text = stringRes(R.string.transaction_detail_send_again),
onClick = { onSendAgainClick(transaction) }
onClick = { onSendAgainClick(data) }
)
else -> null
@ -329,32 +310,22 @@ class TransactionDetailViewModel(
sendTransactionAgain(transaction)
}
private fun createTransactionHeaderState(transaction: DetailedTransactionData) =
private fun createTransactionHeaderState(data: DetailedTransactionData) =
TransactionDetailHeaderState(
title =
when (transaction.transaction.state) {
SENT -> stringRes(R.string.transaction_detail_sent)
SENDING -> stringRes(R.string.transaction_detail_sending)
SEND_FAILED -> stringRes(R.string.transaction_detail_send_failed)
RECEIVED -> stringRes(R.string.transaction_detail_received)
RECEIVING -> stringRes(R.string.transaction_detail_receiving)
RECEIVE_FAILED -> stringRes(R.string.transaction_detail_receive_failed)
SHIELDED -> stringRes(R.string.transaction_detail_shielded)
SHIELDING -> stringRes(R.string.transaction_detail_shielding)
SHIELDING_FAILED -> stringRes(R.string.transaction_detail_shielding_failed)
when (data.transaction) {
is SendTransaction.Success -> stringRes(R.string.transaction_detail_sent)
is SendTransaction.Pending -> stringRes(R.string.transaction_detail_sending)
is SendTransaction.Failed -> stringRes(R.string.transaction_detail_send_failed)
is ReceiveTransaction.Success -> stringRes(R.string.transaction_detail_received)
is ReceiveTransaction.Pending -> stringRes(R.string.transaction_detail_receiving)
is ReceiveTransaction.Failed -> stringRes(R.string.transaction_detail_receive_failed)
is ShieldTransaction.Success -> stringRes(R.string.transaction_detail_shielded)
is ShieldTransaction.Pending -> stringRes(R.string.transaction_detail_shielding)
is ShieldTransaction.Failed -> stringRes(R.string.transaction_detail_shielding_failed)
},
amount =
when (transaction.transaction.state) {
SENT,
SENDING,
SEND_FAILED,
RECEIVED,
RECEIVING,
RECEIVE_FAILED -> stringRes(transaction.transaction.overview.netValue)
SHIELDED,
SHIELDING,
SHIELDING_FAILED -> stringRes(transaction.transaction.overview.totalSpent)
}
stringRes(data.transaction.amount)
)
private fun onBack() {

View File

@ -7,7 +7,7 @@ import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSource
import co.electriccoin.zcash.ui.common.mapper.TransactionHistoryMapper
import co.electriccoin.zcash.ui.common.repository.TransactionData
import co.electriccoin.zcash.ui.common.repository.Transaction
import co.electriccoin.zcash.ui.common.repository.TransactionFilterRepository
import co.electriccoin.zcash.ui.common.usecase.ApplyTransactionFulltextFiltersUseCase
import co.electriccoin.zcash.ui.common.usecase.GetCurrentFilteredTransactionsUseCase
@ -109,12 +109,7 @@ class TransactionHistoryViewModel(
transactions
.groupBy {
val other =
it.data.overview.blockTimeEpochSeconds?.let { sec ->
Instant
.ofEpochSecond(sec)
.atZone(ZoneId.systemDefault())
.toLocalDate()
} ?: now
it.transaction.timestamp?.atZone(ZoneId.systemDefault())?.toLocalDate() ?: now
when {
now == other ->
stringRes(R.string.transaction_history_today) to "today"
@ -146,7 +141,7 @@ class TransactionHistoryViewModel(
TransactionHistoryItem.Transaction(
state =
transactionHistoryMapper.createTransactionState(
transaction = transaction,
data = transaction,
onTransactionClick = ::onTransactionClick,
restoreTimestamp = restoreTimestamp
)
@ -215,8 +210,8 @@ class TransactionHistoryViewModel(
navigationRouter.back()
}
private fun onTransactionClick(transactionData: TransactionData) {
navigationRouter.forward(TransactionDetail(transactionData.overview.txId.txIdString()))
private fun onTransactionClick(transaction: Transaction) {
navigationRouter.forward(TransactionDetail(transaction.id.txIdString()))
}
private fun onTransactionFiltersClicked() = navigationRouter.forward(TransactionFilters)

View File

@ -9,7 +9,7 @@ import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSource
import co.electriccoin.zcash.ui.common.mapper.TransactionHistoryMapper
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.repository.TransactionData
import co.electriccoin.zcash.ui.common.repository.Transaction
import co.electriccoin.zcash.ui.common.usecase.GetCurrentTransactionsUseCase
import co.electriccoin.zcash.ui.common.usecase.GetWalletRestoringStateUseCase
import co.electriccoin.zcash.ui.design.component.ButtonState
@ -67,7 +67,7 @@ class TransactionHistoryWidgetViewModel(
.take(MAX_TRANSACTION_COUNT)
.map { transaction ->
transactionHistoryMapper.createTransactionState(
transaction = transaction,
data = transaction,
restoreTimestamp = restoreTimestampDataSource.getOrCreate(),
onTransactionClick = ::onTransactionClick,
)
@ -81,8 +81,8 @@ class TransactionHistoryWidgetViewModel(
TransactionHistoryWidgetState.Loading
)
private fun onTransactionClick(transactionData: TransactionData) {
navigationRouter.forward(TransactionDetail(transactionData.overview.txId.txIdString()))
private fun onTransactionClick(transaction: Transaction) {
navigationRouter.forward(TransactionDetail(transaction.id.txIdString()))
}
private fun onSeeAllTransactionsClick() {