Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-20 12:39:06 +03:00
commit b49d43c685
10 changed files with 98 additions and 30 deletions

View file

@ -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") }

View file

@ -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<SavedSwapTransactionListModel>? = 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<List<SavedSwapTransactionListModel>?> {
val txStatuses = appPreferencesStore.getObjectMap<ExchangeStatusModel>(
key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
)
return appPreferencesStore.getObjectList<SavedSwapTransactionListModel>(
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<SavedSwapTransactionListModel>? = 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<ExchangeStatusModel>(
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<SavedLastSwappedCryptoCurrency>(
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<ExchangeStatusModel>(
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,
)
}
}
}
}

View file

@ -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()

View file

@ -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,
),
),
)
}

View file

@ -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<List<SavedSwapTransactionListModel>?>
@ -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?

View file

@ -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,
)
}

View file

@ -722,7 +722,6 @@ internal class StateBuilder(
fun createSuccessState(
uiState: SwapStateHolder,
txState: TxState.TxSent,
txUrl: String,
dataState: SwapProcessDataState,
onExploreClick: () -> Unit,
onStatusClick: () -> Unit,
@ -740,7 +739,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,

View file

@ -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),

View file

@ -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
},
)
}

View file

@ -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<SwapTransactionsState>) {
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.