Updated on 2026-08-14
This commit is contained in:
parent
eacf096c6f
commit
933003fac6
7 changed files with 112 additions and 63 deletions
|
|
@ -204,6 +204,16 @@ internal object YieldSupplyDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyEnterStatusFlowUseCase(
|
||||
yieldSupplyRepository: YieldSupplyRepository,
|
||||
): YieldSupplyEnterStatusFlowUseCase {
|
||||
return YieldSupplyEnterStatusFlowUseCase(
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyGetShouldShowMainPromoUseCase(
|
||||
|
|
|
|||
|
|
@ -27,9 +27,10 @@ import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
|
|||
import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
internal class DefaultYieldSupplyRepository(
|
||||
private val yieldSupplyApi: YieldSupplyApi,
|
||||
|
|
@ -40,7 +41,7 @@ internal class DefaultYieldSupplyRepository(
|
|||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : YieldSupplyRepository {
|
||||
|
||||
private val statusMap: MutableMap<String, YieldSupplyPendingStatus> = ConcurrentHashMap()
|
||||
private val statusMapFlow = MutableStateFlow<Map<String, YieldSupplyPendingStatus>>(emptyMap())
|
||||
|
||||
override suspend fun getCachedMarkets(): List<YieldMarketToken>? = withContext(dispatchers.io) {
|
||||
val cache = store.getSyncOrNull().orEmpty()
|
||||
|
|
@ -129,10 +130,12 @@ internal class DefaultYieldSupplyRepository(
|
|||
yieldSupplyPendingStatus: YieldSupplyPendingStatus?,
|
||||
) {
|
||||
val key = getTokenProtocolStatusKey(userWalletId, cryptoCurrency)
|
||||
if (yieldSupplyPendingStatus != null) {
|
||||
statusMap[key] = yieldSupplyPendingStatus
|
||||
} else {
|
||||
statusMap.remove(key)
|
||||
statusMapFlow.update { currentMap ->
|
||||
if (yieldSupplyPendingStatus != null) {
|
||||
currentMap + (key to yieldSupplyPendingStatus)
|
||||
} else {
|
||||
currentMap - key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +156,15 @@ internal class DefaultYieldSupplyRepository(
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): YieldSupplyPendingStatus? {
|
||||
return statusMap[getTokenProtocolStatusKey(userWalletId, cryptoCurrency)]
|
||||
return statusMapFlow.value[getTokenProtocolStatusKey(userWalletId, cryptoCurrency)]
|
||||
}
|
||||
|
||||
override fun getTokenProtocolPendingStatusFlow(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Flow<YieldSupplyPendingStatus?> {
|
||||
val key = getTokenProtocolStatusKey(userWalletId, cryptoCurrency)
|
||||
return statusMapFlow.map { it[key] }
|
||||
}
|
||||
|
||||
private fun List<YieldMarketToken>.enrichNetworkIds(): List<YieldMarketToken> {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,18 @@ interface YieldSupplyRepository {
|
|||
cryptoCurrency: CryptoCurrency,
|
||||
): YieldSupplyPendingStatus?
|
||||
|
||||
/**
|
||||
* Observe the pending status for the given wallet and currency as a [Flow].
|
||||
*
|
||||
* @param userWalletId the wallet to observe
|
||||
* @param cryptoCurrency the currency or token to observe
|
||||
* @return a [Flow] emitting the current pending status or null if none exists
|
||||
*/
|
||||
fun getTokenProtocolPendingStatusFlow(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Flow<YieldSupplyPendingStatus?>
|
||||
|
||||
fun getShouldShowYieldPromoBanner(): Flow<Boolean>
|
||||
|
||||
suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class YieldSupplyEnterStatusFlowUseCase(
|
||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Flow<YieldSupplyPendingStatus?> {
|
||||
return yieldSupplyRepository.getTokenProtocolPendingStatusFlow(userWalletId, cryptoCurrency)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,28 +24,24 @@ class YieldSupplyEnterStatusUseCase(
|
|||
.toSet()
|
||||
val hasPendingTx = status?.txIds?.any { it in pendingTxHashes } == true
|
||||
|
||||
if (hasPendingTx) {
|
||||
status
|
||||
val isActive = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true
|
||||
val isExpired = status != null &&
|
||||
System.currentTimeMillis() - status.createdAt > STATUS_EXPIRATION_MS
|
||||
val shouldClearStatus = when {
|
||||
isActive && status is YieldSupplyPendingStatus.Enter -> true
|
||||
!isActive && status is YieldSupplyPendingStatus.Exit -> true
|
||||
isExpired && !hasPendingTx -> true
|
||||
else -> false
|
||||
}
|
||||
if (shouldClearStatus) {
|
||||
yieldSupplyRepository.saveTokenProtocolPendingStatus(
|
||||
userWalletId,
|
||||
cryptoCurrencyStatus.currency,
|
||||
null,
|
||||
)
|
||||
null
|
||||
} else {
|
||||
val isActive = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true
|
||||
val isExpired = status != null &&
|
||||
System.currentTimeMillis() - status.createdAt > STATUS_EXPIRATION_MS
|
||||
val shouldClearStatus = when {
|
||||
isExpired -> true
|
||||
isActive && status is YieldSupplyPendingStatus.Exit -> false
|
||||
!isActive && status is YieldSupplyPendingStatus.Enter -> false
|
||||
else -> true
|
||||
}
|
||||
if (shouldClearStatus) {
|
||||
yieldSupplyRepository.saveTokenProtocolPendingStatus(
|
||||
userWalletId,
|
||||
cryptoCurrencyStatus.currency,
|
||||
null,
|
||||
)
|
||||
null
|
||||
} else {
|
||||
status
|
||||
}
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,16 +91,13 @@ class YieldSupplyEnterStatusUseCaseTest {
|
|||
coEvery {
|
||||
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus.currency)
|
||||
} returns emptyList()
|
||||
coEvery {
|
||||
yieldSupplyRepository.saveTokenProtocolPendingStatus(userWalletId, token, null)
|
||||
} returns Unit
|
||||
|
||||
val result = useCase(userWalletId, cryptoStatus)
|
||||
|
||||
assertThat(result.isRight()).isTrue()
|
||||
val value = (result as Either.Right).value
|
||||
assertThat(value).isNull()
|
||||
coVerify { yieldSupplyRepository.saveTokenProtocolPendingStatus(userWalletId, token, null) }
|
||||
coVerify(exactly = 0) { yieldSupplyRepository.saveTokenProtocolPendingStatus(any(), any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -147,7 +144,7 @@ class YieldSupplyEnterStatusUseCaseTest {
|
|||
@Test
|
||||
fun `GIVEN exit status with pending tx WHEN invoke THEN returns status`() = runTest {
|
||||
val token = createToken()
|
||||
val cryptoStatus = createStatus(token)
|
||||
val cryptoStatus = createStatus(token, isActive = true)
|
||||
val pendingTxHash = "0xexit456"
|
||||
val status = YieldSupplyPendingStatus.Exit(txIds = listOf(pendingTxHash))
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,6 @@ import com.tangem.features.yield.supply.impl.R
|
|||
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
|
||||
import com.tangem.features.yield.supply.impl.main.model.transformers.YieldSupplyTokenStatusSuccessTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -57,6 +55,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
|
||||
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
|
||||
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
|
||||
private val yieldSupplyEnterStatusFlowUseCase: YieldSupplyEnterStatusFlowUseCase,
|
||||
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
|
||||
private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
|
||||
) : Model(), YieldSupplyClickIntents {
|
||||
|
|
@ -71,8 +70,6 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
var userWallet: UserWallet by Delegates.notNull()
|
||||
private var latestCryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
|
||||
private val loadStatusJobHolder = JobHolder()
|
||||
|
||||
private val isFirstCryptoCurrencyStatusEmission = AtomicBoolean(true)
|
||||
|
||||
init {
|
||||
|
|
@ -84,7 +81,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
|
||||
val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency)
|
||||
if (isAvailable) {
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
loadUserWalletData()
|
||||
singleNetworkStatusFetcher(
|
||||
params = SingleNetworkStatusFetcher.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
|
|
@ -95,30 +92,12 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates() {
|
||||
private fun loadUserWalletData() {
|
||||
modelScope.launch {
|
||||
getUserWalletUseCase(params.userWalletId).fold(
|
||||
ifRight = { wallet ->
|
||||
userWallet = wallet
|
||||
|
||||
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
|
||||
userWalletId = params.userWalletId,
|
||||
currencyId = cryptoCurrency.id,
|
||||
isSingleWalletWithTokens = false,
|
||||
).onEach { maybeCryptoCurrency ->
|
||||
maybeCryptoCurrency.fold(
|
||||
ifRight = { cryptoCurrencyStatus ->
|
||||
latestCryptoCurrencyStatus = cryptoCurrencyStatus
|
||||
if (isFirstCryptoCurrencyStatusEmission.compareAndSet(true, false)) {
|
||||
sendInfoAboutProtocolStatus(cryptoCurrencyStatus)
|
||||
}
|
||||
onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus)
|
||||
},
|
||||
ifLeft = {
|
||||
Timber.w(it.toString())
|
||||
},
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
},
|
||||
ifLeft = {
|
||||
Timber.w(it.toString())
|
||||
|
|
@ -128,6 +107,36 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates() {
|
||||
combine(
|
||||
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
|
||||
userWalletId = params.userWalletId,
|
||||
currencyId = cryptoCurrency.id,
|
||||
isSingleWalletWithTokens = false,
|
||||
),
|
||||
yieldSupplyEnterStatusFlowUseCase(
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
) { maybeCryptoCurrency, _ ->
|
||||
maybeCryptoCurrency
|
||||
}.flowOn(dispatchers.io)
|
||||
.onEach { maybeCryptoCurrency ->
|
||||
maybeCryptoCurrency.fold(
|
||||
ifRight = { cryptoCurrencyStatus ->
|
||||
latestCryptoCurrencyStatus = cryptoCurrencyStatus
|
||||
if (isFirstCryptoCurrencyStatusEmission.compareAndSet(true, false)) {
|
||||
sendInfoAboutProtocolStatus(cryptoCurrencyStatus)
|
||||
}
|
||||
onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus)
|
||||
},
|
||||
ifLeft = {
|
||||
Timber.w(it.toString())
|
||||
},
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private suspend fun loadTokenStatus() {
|
||||
val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
|
||||
yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken)
|
||||
|
|
@ -168,13 +177,11 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) = modelScope.launch(
|
||||
dispatchers.default,
|
||||
) {
|
||||
private suspend fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val isCryptoCurrencyStatusFromCache = cryptoCurrencyStatus.value.sources.networkSource != StatusSource.ACTUAL
|
||||
val processing = uiState.value is YieldSupplyUM.Processing
|
||||
if (isCryptoCurrencyStatusFromCache && processing) {
|
||||
return@launch
|
||||
return
|
||||
}
|
||||
|
||||
val pendingStatus = yieldSupplyEnterStatusUseCase(
|
||||
|
|
@ -187,7 +194,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
} else {
|
||||
loadStatus(cryptoCurrencyStatus)
|
||||
}
|
||||
}.saveIn(loadStatusJobHolder)
|
||||
}
|
||||
|
||||
private fun showProcessing(status: YieldSupplyPendingStatus) {
|
||||
uiState.update {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue