diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 26d5b3cf08..02432a3d03 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -35,6 +35,8 @@ object PreferencesKeys { val SWAP_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "swapTransactions") } + val SWAP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "swapTransactionsStatuses") } + val WALLETS_SCROLL_PREVIEW_KEY by lazy { booleanPreferencesKey(name = "walletsScrollPreview") } val SENT_ONE_TIME_EVENTS_KEY by lazy { stringPreferencesKey(name = "sentOneTimeEvents") } diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt index 97b9a1e2b2..f617305f17 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt @@ -4,9 +4,11 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectList import com.tangem.datasource.local.preferences.utils.getObjectListSync +import com.tangem.datasource.local.preferences.utils.getObjectMap import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.SwapTransactionRepository +import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel import com.tangem.feature.swap.domain.models.domain.SavedLastSwappedCryptoCurrency import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel @@ -24,11 +26,11 @@ class DefaultSwapTransactionRepository( toCryptoCurrencyId: CryptoCurrency.ID, transaction: SavedSwapTransactionModel, ) { + transaction.status?.let { storeTransactionState(transaction.txId, it) } appPreferencesStore.editData { mutablePreferences -> val savedTransactions: List? = mutablePreferences.getObjectList( key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, ) - val tokenTransactions = savedTransactions ?.firstOrNull { it.checkId( @@ -62,14 +64,17 @@ class DefaultSwapTransactionRepository( } } - override fun getTransactions( + override suspend fun getTransactions( userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, ): Flow?> { + val txStatuses = appPreferencesStore.getObjectMap( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + ) return appPreferencesStore.getObjectList( key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, ).map { savedTransactions -> - savedTransactions + val currencyTxs = savedTransactions ?.filter { it.userWalletId == userWalletId.stringValue && ( @@ -77,6 +82,14 @@ class DefaultSwapTransactionRepository( it.fromCryptoCurrencyId == cryptoCurrencyId.value ) } + + currencyTxs?.map { currencyTx -> + currencyTx.copy( + transactions = currencyTx.transactions.map { tx -> + tx.copy(status = txStatuses[tx.txId]) + }, + ) + } } } @@ -86,6 +99,7 @@ class DefaultSwapTransactionRepository( toCryptoCurrencyId: CryptoCurrency.ID, txId: String, ) { + clearTransactionsStatuses(txId = txId) appPreferencesStore.editData { mutablePreferences -> val savedList: List? = mutablePreferences.getObjectList( key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, @@ -130,6 +144,22 @@ class DefaultSwapTransactionRepository( } } + override suspend fun storeTransactionState(txId: String, status: ExchangeStatusModel) { + appPreferencesStore.editData { mutablePreferences -> + val savedMap = mutablePreferences.getObjectMap( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + ) + + val updatesMap = savedMap?.toMutableMap() ?: mutableMapOf() + updatesMap[txId] = status + + mutablePreferences.setObjectMap( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + value = updatesMap, + ) + } + } + override suspend fun getLastSwappedCryptoCurrencyId(userWalletId: UserWalletId): String? { val lastSwappedCurrencies = appPreferencesStore.getObjectListSync( key = PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY, @@ -194,4 +224,22 @@ class DefaultSwapTransactionRepository( }, ) } + + private suspend fun clearTransactionsStatuses(txId: String) { + appPreferencesStore.editData { mutablePreferences -> + val savedList = mutablePreferences.getObjectMap( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + ) + val editedList = savedList?.filterNot { it.key == txId } + + if (editedList.isNullOrEmpty()) { + mutablePreferences.remove(key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY) + } else { + mutablePreferences.setObjectMap( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + value = editedList, + ) + } + } + } } \ No newline at end of file diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt index 526b5b2a73..132327e4cb 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt @@ -11,6 +11,7 @@ sealed class TxState { val toAmountValue: BigDecimal? = null, val txAddress: String, val txExternalUrl: String? = null, + val txUrl: String? = null, val timestamp: Long, ) : TxState() diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 2fd8561359..be755e8764 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -54,6 +54,7 @@ internal class SwapInteractorImpl @Inject constructor( private val dispatcher: CoroutineDispatcherProvider, private val swapTransactionRepository: SwapTransactionRepository, private val initialToCurrencyResolver: InitialToCurrencyResolver, + private val blockchainInteractor: BlockchainInteractor, ) : SwapInteractor { private val estimateFeeUseCase by lazy(LazyThreadSafetyMode.NONE) { @@ -539,6 +540,10 @@ internal class SwapInteractorImpl @Inject constructor( }, ifRight = { val timestamp = System.currentTimeMillis() + val txUrl = blockchainInteractor.getExplorerTransactionLink( + networkId = currencyToSend.currency.network.backendId, + txAddress = exchangeData.transaction.txTo, + ) storeSwapTransaction( currencyToSend = currencyToSend, currencyToGet = currencyToGet, @@ -546,6 +551,7 @@ internal class SwapInteractorImpl @Inject constructor( swapProvider = swapProvider, swapDataModel = exchangeData, timestamp = timestamp, + txUrl = txUrl, ) storeLastCryptoCurrencyId(currencyToGet.currency) TxState.TxSent( @@ -564,6 +570,7 @@ internal class SwapInteractorImpl @Inject constructor( derivationPath, ).orEmpty(), txExternalUrl = externalUrl, + txUrl = txUrl, timestamp = timestamp, ) }, @@ -597,6 +604,7 @@ internal class SwapInteractorImpl @Inject constructor( swapProvider: SwapProvider, swapDataModel: SwapDataModel, timestamp: Long, + txUrl: String, ) { swapTransactionRepository.storeTransaction( userWalletId = UserWalletId(userWalletManager.getWalletId()), @@ -608,6 +616,12 @@ internal class SwapInteractorImpl @Inject constructor( timestamp = timestamp, fromCryptoAmount = amount.value, toCryptoAmount = swapDataModel.toTokenAmount.value, + status = ExchangeStatusModel( + providerId = swapProvider.providerId, + status = ExchangeStatus.New, + txId = swapDataModel.transaction.txId, + txUrl = txUrl, + ), ), ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt index 2db58ee01d..e00dadb2a9 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt @@ -2,6 +2,7 @@ package com.tangem.feature.swap.domain import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel import kotlinx.coroutines.flow.Flow @@ -15,7 +16,7 @@ interface SwapTransactionRepository { transaction: SavedSwapTransactionModel, ) - fun getTransactions( + suspend fun getTransactions( userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, ): Flow?> @@ -27,6 +28,8 @@ interface SwapTransactionRepository { txId: String, ) + suspend fun storeTransactionState(txId: String, status: ExchangeStatusModel) + suspend fun storeLastSwappedCryptoCurrencyId(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID) suspend fun getLastSwappedCryptoCurrencyId(userWalletId: UserWalletId): String? diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index d299f7824e..518d7c9fc8 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -42,6 +42,7 @@ class SwapDomainModule { walletManagersFacade: WalletManagersFacade, coroutineDispatcherProvider: CoroutineDispatcherProvider, initialToCurrencyResolver: InitialToCurrencyResolver, + blockchainInteractor: BlockchainInteractor, ): SwapInteractor { return SwapInteractorImpl( transactionManager = transactionManager, @@ -56,6 +57,7 @@ class SwapDomainModule { dispatcher = coroutineDispatcherProvider, swapTransactionRepository = swapTransactionRepository, initialToCurrencyResolver = initialToCurrencyResolver, + blockchainInteractor = blockchainInteractor, ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index c74bd436f7..07fd38fad3 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -717,7 +717,6 @@ internal class StateBuilder( fun createSuccessState( uiState: SwapStateHolder, txState: TxState.TxSent, - txUrl: String, dataState: SwapProcessDataState, onExploreClick: () -> Unit, onStatusClick: () -> Unit, @@ -735,7 +734,7 @@ internal class StateBuilder( return uiState.copy( successState = SwapSuccessStateHolder( timestamp = txState.timestamp, - txUrl = txUrl, + txUrl = txState.txUrl.orEmpty(), providerName = stringReference(providerState.name), providerType = stringReference(providerState.type), showStatusButton = providerState.type == ExchangeProviderType.CEX.name, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index fe3d608266..b9235b75a5 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -447,19 +447,14 @@ internal class SwapViewModel @Inject constructor( }.onSuccess { when (it) { is TxState.TxSent -> { - val url = blockchainInteractor.getExplorerTransactionLink( - networkId = fromCurrency.currency.network.backendId, - txAddress = it.txAddress, - ) uiState = stateBuilder.createSuccessState( uiState = uiState, txState = it, dataState = dataState, - txUrl = url, onExploreClick = { - val txHash = it.txAddress - if (txHash.isNotEmpty()) { - swapRouter.openUrl(url) + val txUrl = it.txUrl + if (!txUrl.isNullOrBlank()) { + swapRouter.openUrl(txUrl) } analyticsEventHandler.send( event = SwapEvents.ButtonExplore(initialCryptoCurrency.symbol), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt index 7ef7622a82..4c78d11155 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt @@ -54,7 +54,7 @@ internal class ExchangeStatusFactory( ) } - operator fun invoke() = combine( + suspend operator fun invoke() = combine( flow = swapTransactionRepository.getTransactions(userWalletId, cryptoCurrency.id), flow2 = getWalletCryptoCurrencies().conflate(), ) { savedTransactions, cryptoCurrenciesStatusList -> @@ -95,9 +95,10 @@ internal class ExchangeStatusFactory( return swapRepository.getExchangeStatus(txId) .fold( ifLeft = { null }, - ifRight = { - sendStatusUpdateAnalytics(it) - it + ifRight = { statusModel -> + sendStatusUpdateAnalytics(statusModel) + swapTransactionRepository.storeTransactionState(txId, statusModel) + statusModel }, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 1e23353d4a..1c3f77a105 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -222,6 +222,7 @@ internal class TokenDetailsViewModel @Inject constructor( .distinctUntilChanged() .filterNot { it.isEmpty() } .onEach { swapTxs -> + updateSwapTx(swapTxs) swapTxStatusTaskScheduler.scheduleTask( viewModelScope, PeriodicTask( @@ -231,18 +232,8 @@ internal class TokenDetailsViewModel @Inject constructor( exchangeStatusFactory.updateSwapTxStatuses(swapTxs) } }, - onSuccess = { updatedTxs -> - val config = uiState.bottomSheetConfig - val exchangeBottomSheet = config?.content as? ExchangeStatusBottomSheetConfig - val currentTx = updatedTxs.firstOrNull { it.txId == exchangeBottomSheet?.value?.txId } - uiState = uiState.copy( - swapTxs = updatedTxs, - bottomSheetConfig = currentTx?.let( - stateFactory::updateStateWithExchangeStatusBottomSheet, - ) ?: config, - ) - }, - onError = {}, + onSuccess = ::updateSwapTx, + onError = { /* no-op */ }, ), ) } @@ -252,6 +243,18 @@ internal class TokenDetailsViewModel @Inject constructor( } } + private fun updateSwapTx(swapTxs: PersistentList) { + val config = uiState.bottomSheetConfig + val exchangeBottomSheet = config?.content as? ExchangeStatusBottomSheetConfig + val currentTx = swapTxs.firstOrNull { it.txId == exchangeBottomSheet?.value?.txId } + uiState = uiState.copy( + swapTxs = swapTxs, + bottomSheetConfig = currentTx?.let( + stateFactory::updateStateWithExchangeStatusBottomSheet, + ) ?: config, + ) + } + /** * @param refresh - invalidate cache and get data from remote * @param showItemsLoading - show loading items placeholder.