diff --git a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt index d74844c082..c893ae8a08 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt @@ -204,6 +204,16 @@ internal object YieldSupplyDomainModule { ) } + @Provides + @Singleton + fun provideYieldSupplyEnterStatusFlowUseCase( + yieldSupplyRepository: YieldSupplyRepository, + ): YieldSupplyEnterStatusFlowUseCase { + return YieldSupplyEnterStatusFlowUseCase( + yieldSupplyRepository = yieldSupplyRepository, + ) + } + @Provides @Singleton fun provideYieldSupplyGetShouldShowMainPromoUseCase( diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt index 3565a65952..22f5b7a42e 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt @@ -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 = ConcurrentHashMap() + private val statusMapFlow = MutableStateFlow>(emptyMap()) override suspend fun getCachedMarkets(): List? = 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 { + val key = getTokenProtocolStatusKey(userWalletId, cryptoCurrency) + return statusMapFlow.map { it[key] } } private fun List.enrichNetworkIds(): List { diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt index c8841c32c5..19dd17d7ee 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt @@ -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 + fun getShouldShowYieldPromoBanner(): Flow suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean) diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusFlowUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusFlowUseCase.kt new file mode 100644 index 0000000000..094b852d20 --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusFlowUseCase.kt @@ -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 { + return yieldSupplyRepository.getTokenProtocolPendingStatusFlow(userWalletId, cryptoCurrency) + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCase.kt index 07ea5dabf4..bb6bec283f 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCase.kt @@ -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 } } } diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt index 7d5e4ec84d..d3f2581274 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt @@ -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)) diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt index 6aef4d48d4..381a2c9fab 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt @@ -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 {