diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt index f195b173f7..633173fb97 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt @@ -2,8 +2,10 @@ package com.tangem.data.txhistory.repository.paging import androidx.paging.PagingSource import androidx.paging.PagingState +import com.tangem.blockchain.common.Blockchain import com.tangem.data.common.cache.CacheRegistry import com.tangem.datasource.local.txhistory.TxHistoryItemsStore +import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem @@ -20,6 +22,8 @@ internal class TxHistoryPagingSource( private val storeKey = TxHistoryItemsStore.Key(sourceParams.userWalletId, sourceParams.currency) + override val keyReuseSupported: Boolean get() = true + override fun getRefreshKey(state: PagingState): Int? { return state.anchorPosition?.let { anchorPosition -> val anchorPage = state.closestPageToPosition(anchorPosition) @@ -70,14 +74,54 @@ internal class TxHistoryPagingSource( } private suspend fun fetch(pageToLoad: Int, pageSize: Int) { - val wrappedItems = walletManagersFacade.getTxHistoryItems( + var wrappedItems = walletManagersFacade.getTxHistoryItems( userWalletId = sourceParams.userWalletId, currency = sourceParams.currency, page = pageToLoad, pageSize = pageSize, ) - txHistoryItemsStore.store(storeKey, wrappedItems) + if (pageToLoad == 1) { + wrappedItems = wrappedItems.addRecentTransactions() + } + + txHistoryItemsStore.store(key = storeKey, value = wrappedItems) + } + + private suspend fun PaginationWrapper.addRecentTransactions(): PaginationWrapper { + val recentTxHistoryItems = Blockchain.fromNetworkId(sourceParams.currency.network.backendId)?.let { + walletManagersFacade.getRecentTransactions( + userWalletId = sourceParams.userWalletId, + blockchain = it, + derivationPath = sourceParams.currency.network.derivationPath.value, + ) + .filterUnconfirmedTransaction() + .filterIfApiKnowsAboutTx(apiItems = items) + } ?: emptyList() + + return if (recentTxHistoryItems.isEmpty()) { + Timber.d("Nothing to add to TxHistory") + this + } else { + Timber.d( + "Recent transactions were added to TxHistory: %s", + recentTxHistoryItems.joinToString( + prefix = "[", + postfix = "]", + transform = TxHistoryItem::txHash, + ), + ) + + return copy(items = recentTxHistoryItems + items) + } + } + + private fun List.filterUnconfirmedTransaction(): List { + return filter { it.status == TxHistoryItem.TransactionStatus.Unconfirmed } + } + + private fun List.filterIfApiKnowsAboutTx(apiItems: List): List { + return filter { item -> apiItems.none { it.txHash == item.txHash } } } private fun getTxHistoryPageKey(page: Int): String { diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 7a72566f75..62a1b15160 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -38,7 +38,7 @@ import timber.log.Timber import java.math.BigDecimal import java.util.EnumSet -@Suppress("LargeClass") +@Suppress("LargeClass", "TooManyFunctions") // FIXME: Move to its own module and make internal @Deprecated("Inject the WalletManagerFacade interface using DI instead") class DefaultWalletManagersFacade( @@ -481,6 +481,29 @@ class DefaultWalletManagersFacade( return (walletManager as TransactionSender).send(txData, signer) } + override suspend fun getRecentTransactions( + userWalletId: UserWalletId, + blockchain: Blockchain, + derivationPath: String?, + ): List { + val walletManager = getOrCreateWalletManager( + userWalletId = userWalletId, + blockchain = blockchain, + derivationPath = derivationPath, + ) + + if (walletManager == null) { + Timber.e("Unable to get a wallet manager for blockchain: $blockchain") + return emptyList() + } + + val transactionDataConverter = TransactionDataToTxHistoryItemConverter( + walletAddresses = SdkAddressToAddressConverter.convertList(walletManager.wallet.addresses).toSet(), + ) + + return walletManager.wallet.recentTransactions.mapNotNull(transactionDataConverter::convert) + } + private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set) { if (tokens.isEmpty()) return diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index dba5d089eb..cd614532f9 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -25,6 +25,7 @@ import java.util.EnumSet /** * A facade for managing wallets. */ +@Suppress("TooManyFunctions") interface WalletManagersFacade { /** @@ -218,4 +219,11 @@ interface WalletManagersFacade { userWalletId: UserWalletId, network: Network, ): SimpleResult + + /** Get recent transactions of [userWalletId] for [blockchain] with [derivationPath] */ + suspend fun getRecentTransactions( + userWalletId: UserWalletId, + blockchain: Blockchain, + derivationPath: String?, + ): List } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkAddressToAddressConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkAddressToAddressConverter.kt new file mode 100644 index 0000000000..72e04bd0b6 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkAddressToAddressConverter.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.walletmanager.utils + +import com.tangem.blockchain.common.address.AddressType +import com.tangem.domain.walletmanager.model.Address +import com.tangem.utils.converter.Converter +import com.tangem.blockchain.common.address.Address as SdkAddress + +/** + * Convert [SdkAddress] to [Address] + * +[REDACTED_AUTHOR] + */ +internal object SdkAddressToAddressConverter : Converter { + + override fun convert(value: SdkAddress): Address { + return Address( + value = value.value, + type = when (value.type) { + AddressType.Default -> Address.Type.Primary + AddressType.Legacy -> Address.Type.Secondary + }, + ) + } +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt new file mode 100644 index 0000000000..c9bf25084a --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt @@ -0,0 +1,58 @@ +package com.tangem.domain.walletmanager.utils + +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionStatus +import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.domain.walletmanager.model.Address +import com.tangem.utils.converter.Converter +import timber.log.Timber +import java.math.BigDecimal + +/** + * Convert [TransactionData] to [TxHistoryItem] + * + * @property walletAddresses wallet addresses + * +[REDACTED_AUTHOR] + */ +internal class TransactionDataToTxHistoryItemConverter( + private val walletAddresses: Set
, +) : Converter { + + override fun convert(value: TransactionData): TxHistoryItem? { + val hash = value.hash ?: return null + val millis = value.date?.timeInMillis ?: return null + val amount = getTransactionAmountValue(value.amount) ?: return null + val isOutgoing = value.sourceAddress in walletAddresses.map(Address::value) + + return TxHistoryItem( + txHash = hash, + timestampInMillis = millis, + isOutgoing = isOutgoing, + destinationType = TxHistoryItem.DestinationType.Single( + addressType = TxHistoryItem.AddressType.User(value.destinationAddress), + ), + sourceType = TxHistoryItem.SourceType.Single(value.sourceAddress), + interactionAddressType = TxHistoryItem.InteractionAddressType.User( + address = if (isOutgoing) value.destinationAddress else value.sourceAddress, + ), + status = when (value.status) { + TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed + TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed + }, + type = TxHistoryItem.TransactionType.Transfer, + amount = amount, + ) + } + + private fun getTransactionAmountValue(amount: Amount): BigDecimal? { + val value = amount.value + + if (value == null) { + Timber.w("Transaction amount must not be null: ${amount.currencySymbol}") + } + + return value + } +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt index 7ebefa793c..64600a36df 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt @@ -1,9 +1,7 @@ package com.tangem.domain.walletmanager.utils import com.tangem.blockchain.common.* -import com.tangem.blockchain.common.address.AddressType import com.tangem.domain.common.extensions.amountToCreateAccount -import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.walletmanager.model.Address import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction @@ -111,13 +109,14 @@ internal class UpdateWalletManagerResultFactory { walletAddresses: Set
, data: TransactionData, ): CryptoCurrencyTransaction? { + val txHistoryItemConverter = TransactionDataToTxHistoryItemConverter(walletAddresses) return when (val type = data.amount.type) { is AmountType.Coin -> { - val txHistoryItem = createTxHistoryItem(walletAddresses, data) ?: return null + val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null CryptoCurrencyTransaction.Coin(txHistoryItem) } is AmountType.Token -> { - val txHistoryItem = createTxHistoryItem(walletAddresses, data) ?: return null + val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null CryptoCurrencyTransaction.Token( tokenId = type.token.id, tokenContractAddress = type.token.contractAddress, @@ -128,42 +127,8 @@ internal class UpdateWalletManagerResultFactory { } } - private fun createTxHistoryItem(walletAddresses: Set
, data: TransactionData): TxHistoryItem? { - val hash = data.hash ?: return null - val millis = data.date?.timeInMillis ?: return null - val amount = getTransactionAmountValue(data.amount) ?: return null - val isOutgoing = data.sourceAddress in walletAddresses.map { it.value } - - return TxHistoryItem( - txHash = hash, - timestampInMillis = millis, - isOutgoing = isOutgoing, - destinationType = TxHistoryItem.DestinationType.Single( - TxHistoryItem.AddressType.User(data.destinationAddress), - ), - sourceType = TxHistoryItem.SourceType.Single(data.sourceAddress), - interactionAddressType = TxHistoryItem.InteractionAddressType.User( - if (isOutgoing) data.destinationAddress else data.sourceAddress, - ), - status = when (data.status) { - TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed - TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed - }, - type = TxHistoryItem.TransactionType.Transfer, - amount = amount, - ) - } - private fun getAvailableAddresses(addresses: Set): Set
{ - return addresses.mapTo(hashSetOf()) { sdkAddress -> - Address( - value = sdkAddress.value, - type = when (sdkAddress.type) { - AddressType.Default -> Address.Type.Primary - AddressType.Legacy -> Address.Type.Secondary - }, - ) - } + return SdkAddressToAddressConverter.convertList(addresses).toSet() } private fun getCurrencyAmountValue(amount: Amount): BigDecimal? { @@ -175,14 +140,4 @@ internal class UpdateWalletManagerResultFactory { return value } - - private fun getTransactionAmountValue(amount: Amount): BigDecimal? { - val value = amount.value - - if (value == null) { - Timber.w("Transaction amount must not be null: ${amount.currencySymbol}") - } - - return value - } } \ No newline at end of file