diff --git a/core/utils/src/main/java/com/tangem/utils/TangemBlogUrlBuilder.kt b/core/utils/src/main/java/com/tangem/utils/TangemBlogUrlBuilder.kt index 3bb43d00e2..5d8f4ba63f 100644 --- a/core/utils/src/main/java/com/tangem/utils/TangemBlogUrlBuilder.kt +++ b/core/utils/src/main/java/com/tangem/utils/TangemBlogUrlBuilder.kt @@ -21,7 +21,7 @@ object TangemBlogUrlBuilder { const val RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP = "https://tangem.com/en/blog/post/give-revoke-permission/" - const val YIELD_SUPPLY_HOW_IT_WORKS_URL = "https://tangem.com/en/blog/post/savings-account" + const val YIELD_SUPPLY_HOW_IT_WORKS_URL = "https://tangem.com/en/blog/post/yield-mode" const val YIELD_SUPPLY_TOS_URL = "https://aave.com/terms-of-service" const val YIELD_SUPPLY_PRIVACY_URL = "https://aave.com/privacy-policy" } \ No newline at end of file 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 8d8613e976..8f5fe85b65 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 @@ -16,11 +16,13 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.yield.supply.YieldSupplyRepository import com.tangem.domain.yield.supply.models.YieldMarketToken +import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext +import java.util.concurrent.ConcurrentHashMap import kotlin.collections.map internal class DefaultYieldSupplyRepository( @@ -30,6 +32,8 @@ internal class DefaultYieldSupplyRepository( private val dispatchers: CoroutineDispatcherProvider, ) : YieldSupplyRepository { + private val statusMap: MutableMap = ConcurrentHashMap() + override suspend fun getCachedMarkets(): List? = withContext(dispatchers.io) { val cache = store.getSyncOrNull().orEmpty() val domain = cache.map(YieldMarketTokenConverter::convert) @@ -97,6 +101,17 @@ internal class DefaultYieldSupplyRepository( ).getOrThrow().isActive } + override suspend fun saveTokenProtocolStatus( + cryptoCurrency: CryptoCurrency, + yieldSupplyEnterStatus: YieldSupplyEnterStatus, + ) { + statusMap[cryptoCurrency.id.value] = yieldSupplyEnterStatus + } + + override fun getTokenProtocolStatus(cryptoCurrencyToken: CryptoCurrency): YieldSupplyEnterStatus? { + return statusMap[cryptoCurrencyToken.id.value] + } + private fun List.enrichNetworkIds(): List { val chainIdMap = Blockchain.entries.associate { it.getChainId() to it.toNetworkId() } return this.map { token -> diff --git a/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyEnterStatus.kt b/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyEnterStatus.kt new file mode 100644 index 0000000000..4ecdb12732 --- /dev/null +++ b/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyEnterStatus.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.yield.supply.models + +enum class YieldSupplyEnterStatus { + Enter, + Exit, +} \ No newline at end of file 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 c721f9e942..a9ce7c69dd 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 @@ -3,6 +3,7 @@ package com.tangem.domain.yield.supply import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.yield.supply.models.YieldMarketToken +import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData import kotlinx.coroutines.flow.Flow @@ -56,4 +57,30 @@ interface YieldSupplyRepository { */ @Throws suspend fun deactivateProtocol(cryptoCurrencyToken: CryptoCurrency.Token): Boolean + + /** + * Save the last user-initiated yield protocol action for the given currency. + * + * The saved value helps the UI render an intermediate "processing" state while waiting + * for the definitive protocol status to be fetched from the network. This information + * is transient and not intended to be persisted across app restarts. + * + * @param cryptoCurrencyToken the currency or token the action relates to + * @param yieldSupplyEnterStatus the last action intent: [YieldSupplyEnterStatus.Enter] or [YieldSupplyEnterStatus.Exit] + */ + suspend fun saveTokenProtocolStatus( + cryptoCurrencyToken: CryptoCurrency, + yieldSupplyEnterStatus: YieldSupplyEnterStatus, + ) + + /** + * Get the last saved user-initiated yield protocol action for the given currency, if any. + * + * Used to determine whether the UI should display an intermediate "processing" state + * until the protocol status retrieved from backend reflects the change. + * + * @param cryptoCurrencyToken the currency or token to query + * @return the last action intent or null if nothing has been recorded + */ + fun getTokenProtocolStatus(cryptoCurrencyToken: CryptoCurrency): YieldSupplyEnterStatus? } \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/YieldSupplyTrigger.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/YieldSupplyTrigger.kt deleted file mode 100644 index 691095237a..0000000000 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/YieldSupplyTrigger.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.features.yield.supply.impl.common - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import javax.inject.Inject -import javax.inject.Singleton - -/** - * Trigger for entering/exiting protocol from other components - */ -interface YieldSupplyProtocolTrigger { - suspend fun onEnterProtocol() - suspend fun onExitProtocol() -} - -/** - * Listener to observe entering/exiting protocol events - */ -interface YieldSupplyProtocolListener { - val enterProtocolTriggerFlow: Flow - val exitProtocolTriggerFlow: Flow -} - -@Singleton -internal class DefaultYieldSupplyProtocolTrigger @Inject constructor() : - YieldSupplyProtocolTrigger, - YieldSupplyProtocolListener { - - override val enterProtocolTriggerFlow = MutableSharedFlow() - override val exitProtocolTriggerFlow = MutableSharedFlow() - - override suspend fun onEnterProtocol() { - enterProtocolTriggerFlow.emit(Unit) - } - - override suspend fun onExitProtocol() { - exitProtocolTriggerFlow.emit(Unit) - } -} \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/di/YieldSupplyFeatureModule.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/di/YieldSupplyFeatureModule.kt index 9226bcfa8e..45b8806bd6 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/di/YieldSupplyFeatureModule.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/di/YieldSupplyFeatureModule.kt @@ -3,10 +3,6 @@ package com.tangem.features.yield.supply.impl.di import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.features.yield.supply.impl.DefaultYieldSupplyFeatureToggles import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles -import com.tangem.features.yield.supply.impl.common.DefaultYieldSupplyProtocolTrigger -import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolListener -import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolTrigger -import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -22,17 +18,4 @@ internal object YieldSupplyFeatureModule { fun provideYieldFeatureToggles(featureTogglesManager: FeatureTogglesManager): YieldSupplyFeatureToggles { return DefaultYieldSupplyFeatureToggles(featureTogglesManager) } -} - -@InstallIn(SingletonComponent::class) -@Module -internal interface YieldSupplyProtocolModuleBinds { - - @Singleton - @Binds - fun bindYieldSupplyProtocolTrigger(impl: DefaultYieldSupplyProtocolTrigger): YieldSupplyProtocolTrigger - - @Singleton - @Binds - fun bindYieldSupplyProtocolListener(impl: DefaultYieldSupplyProtocolTrigger): YieldSupplyProtocolListener } \ No newline at end of file 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 70ab6a6a95..a14713f98d 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 @@ -16,12 +16,13 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.yieldSupplyNotAllAmountSupplied -import com.tangem.domain.models.network.TxInfo import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.yield.supply.YieldSupplyStatus import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.domain.yield.supply.YieldSupplyRepository +import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase @@ -29,7 +30,6 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyIsAvailableUseCase import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.api.YieldSupplyComponent import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics -import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolListener 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 @@ -59,7 +59,7 @@ internal class YieldSupplyModel @Inject constructor( private val yieldSupplyIsAvailableUseCase: YieldSupplyIsAvailableUseCase, private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase, private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase, - private val yieldSupplyProtocolListener: YieldSupplyProtocolListener, + private val yieldSupplyRepository: YieldSupplyRepository, ) : Model(), YieldSupplyClickIntents { private val params = paramsContainer.require() @@ -87,38 +87,6 @@ internal class YieldSupplyModel @Inject constructor( init { checkIfYieldSupplyIsAvailable() - observeProtocolEvents() - } - - private fun observeProtocolEvents() { - yieldSupplyProtocolListener.exitProtocolTriggerFlow.onEach { - uiState.update { - YieldSupplyUM.Processing.Exit - } - coroutineScope.launch(dispatchers.io) { - delay(PROCESSING_UPDATE_DELAY) - singleNetworkStatusFetcher( - params = SingleNetworkStatusFetcher.Params( - userWalletId = userWallet.walletId, - network = cryptoCurrency.network, - ), - ) - } - }.launchIn(modelScope) - yieldSupplyProtocolListener.enterProtocolTriggerFlow.onEach { - uiState.update { - YieldSupplyUM.Processing.Enter - } - coroutineScope.launch(dispatchers.io) { - delay(PROCESSING_UPDATE_DELAY) - singleNetworkStatusFetcher( - params = SingleNetworkStatusFetcher.Params( - userWalletId = userWallet.walletId, - network = cryptoCurrency.network, - ), - ) - } - }.launchIn(modelScope) } private fun checkIfYieldSupplyIsAvailable() { @@ -142,7 +110,6 @@ internal class YieldSupplyModel @Inject constructor( currencyId = cryptoCurrency.id, isSingleWalletWithTokens = false, ).onEach { maybeCryptoCurrency -> - Timber.tag("getSingleCryptoCurrencyStatusUseCase").d("update $maybeCryptoCurrency") maybeCryptoCurrency.fold( ifRight = { cryptoCurrencyStatus -> cryptoCurrencyStatusFlow.update { cryptoCurrencyStatus } @@ -175,7 +142,7 @@ internal class YieldSupplyModel @Inject constructor( ) }.onLeft { Timber.e(it) - uiState.update { YieldSupplyUM.Unavailable } + uiState.update { YieldSupplyUM.Initial } } } } @@ -214,39 +181,46 @@ internal class YieldSupplyModel @Inject constructor( @Suppress("MaximumLineLength") private fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) { val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus - val hasActiveTransaction = cryptoCurrencyStatus.value.hasCurrentNetworkTransactions - val yieldTransaction = cryptoCurrencyStatus.value.pendingTransactions.firstOrNull { - it.type is TxInfo.TransactionType.YieldSupply - }?.type as? TxInfo.TransactionType.YieldSupply + + if ((yieldSupplyStatus == null || yieldSupplyStatus.isActive == false) && + yieldSupplyRepository.getTokenProtocolStatus(cryptoCurrency) == YieldSupplyEnterStatus.Enter) { + uiState.update { + YieldSupplyUM.Processing.Enter + } + fetchCurrencyWithDelay() + return + } + + if (yieldSupplyStatus?.isActive == true && + yieldSupplyRepository.getTokenProtocolStatus(cryptoCurrency) == YieldSupplyEnterStatus.Exit) { + uiState.update { + YieldSupplyUM.Processing.Exit + } + fetchCurrencyWithDelay() + return + } + sendInfoAboutProtocolStatus(cryptoCurrencyStatus) - when { - hasActiveTransaction && yieldTransaction != null -> { - coroutineScope.launch(dispatchers.io) { - delay(PROCESSING_UPDATE_DELAY) - singleNetworkStatusFetcher( - params = SingleNetworkStatusFetcher.Params( - userWalletId = userWallet.walletId, - network = cryptoCurrency.network, - ), - ) - } - uiState.update { - when (yieldTransaction) { - TxInfo.TransactionType.YieldSupply.Enter -> YieldSupplyUM.Processing.Enter - TxInfo.TransactionType.YieldSupply.Exit -> YieldSupplyUM.Processing.Exit - } - } - } - yieldSupplyStatus?.isActive == true -> { - loadActiveState( - cryptoCurrencyStatus = cryptoCurrencyStatus, - yieldSupplyStatus = yieldSupplyStatus, - ) - } - else -> { - loadTokenStatus() - } + if (yieldSupplyStatus?.isActive == true) { + loadActiveState( + cryptoCurrencyStatus = cryptoCurrencyStatus, + yieldSupplyStatus = yieldSupplyStatus, + ) + } else { + loadTokenStatus() + } + } + + private fun fetchCurrencyWithDelay() { + coroutineScope.launch(dispatchers.io) { + delay(PROCESSING_UPDATE_DELAY) + singleNetworkStatusFetcher( + params = SingleNetworkStatusFetcher.Params( + userWalletId = userWallet.walletId, + network = cryptoCurrency.network, + ), + ) } } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt index aaa41615e2..8e409ddc3d 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt @@ -20,6 +20,8 @@ import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.domain.yield.supply.YieldSupplyRepository +import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase @@ -28,7 +30,6 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics import com.tangem.features.yield.supply.impl.common.YieldSupplyAlertFactory -import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolTrigger import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM import com.tangem.features.yield.supply.impl.common.entity.transformer.YieldSupplyTransactionInProgressTransformer @@ -66,7 +67,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor( private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase, private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase, private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase, - private val yieldSupplyProtocolTrigger: YieldSupplyProtocolTrigger, + private val yieldSupplyRepository: YieldSupplyRepository, ) : Model(), YieldSupplyNotificationsComponent.ModelCallback { private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require() @@ -252,7 +253,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor( ) }, ifRight = { - yieldSupplyProtocolTrigger.onEnterProtocol() + yieldSupplyRepository.saveTokenProtocolStatus(cryptoCurrency, YieldSupplyEnterStatus.Enter) analytics.send(YieldSupplyAnalytics.FundsEarned) yieldSupplyActivateUseCase(cryptoCurrency) modelScope.launch { diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt index 268ab35f31..4ab5059e24 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt @@ -16,12 +16,13 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase +import com.tangem.domain.yield.supply.YieldSupplyRepository +import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics import com.tangem.features.yield.supply.impl.common.YieldSupplyAlertFactory -import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolTrigger import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM import com.tangem.features.yield.supply.impl.common.entity.transformer.YieldSupplyTransactionInProgressTransformer @@ -55,7 +56,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor( private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger, private val yieldSupplyAlertFactory: YieldSupplyAlertFactory, private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase, - private val yieldSupplyProtocolTrigger: YieldSupplyProtocolTrigger, + private val yieldSupplyRepository: YieldSupplyRepository, ) : Model(), YieldSupplyNotificationsComponent.ModelCallback { private val params: YieldSupplyStopEarningComponent.Params = paramsContainer.require() @@ -158,7 +159,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor( ) }, ifRight = { - yieldSupplyProtocolTrigger.onExitProtocol() + yieldSupplyRepository.saveTokenProtocolStatus(cryptoCurrency, YieldSupplyEnterStatus.Exit) analytics.send( YieldSupplyAnalytics.FundsWithdrawn( token = cryptoCurrency.symbol,