Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-17 23:45:25 +05:00
parent fbfd7476fa
commit c1ca73561d
9 changed files with 101 additions and 129 deletions

View file

@ -22,4 +22,6 @@ 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_TOS_URL = "https://aave.com/terms-of-service"
const val YIELD_SUPPLY_PRIVACY_URL = "https://aave.com/privacy-policy"
}

View file

@ -5,15 +5,18 @@ import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
@Suppress("UnusedPrivateProperty")
class NeedShowYieldSupplyDepositedWarningUseCase(
private val yieldSupplyWarningsViewedRepository: YieldSupplyWarningsViewedRepository,
private val dispatchers: CoroutineDispatcherProvider,
) {
suspend operator fun invoke(cryptoCurrencyStatus: CryptoCurrencyStatus?): Boolean = withContext(dispatchers.io) {
val hasActiveLending = cryptoCurrencyStatus?.value?.yieldSupplyStatus?.isActive == true
if (!hasActiveLending) return@withContext false
val showedWarnings = yieldSupplyWarningsViewedRepository.getViewedWarnings()
return@withContext !showedWarnings.contains(cryptoCurrencyStatus?.currency?.name)
// TEMPORARY REQUIREMENTS
return@withContext false
// val hasActiveLending = cryptoCurrencyStatus?.value?.yieldSupplyStatus?.isActive == true
// if (!hasActiveLending) return@withContext false
// val showedWarnings = yieldSupplyWarningsViewedRepository.getViewedWarnings()
// return@withContext !showedWarnings.contains(cryptoCurrencyStatus?.currency?.name)
}
}

View file

@ -1,107 +0,0 @@
package com.tangem.domain.tokens
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.serialization.SerializedBigDecimal
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
import com.tangem.domain.tokens.mock.MockTokens
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit5.MockKExtension
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@OptIn(ExperimentalCoroutinesApi::class)
@ExtendWith(MockKExtension::class)
class NeedShowYieldSupplyDepositedWarningUseCaseTest {
@RelaxedMockK
private lateinit var repository: YieldSupplyWarningsViewedRepository
private lateinit var dispatchers: TestingCoroutineDispatcherProvider
@BeforeEach
fun setup() {
dispatchers = TestingCoroutineDispatcherProvider()
}
@Test
fun `GIVEN null status WHEN invoke THEN returns false`() = runTest {
val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers)
val result = useCase.invoke(null)
assertThat(result).isFalse()
coVerify(exactly = 0) { repository.getViewedWarnings() }
}
@Test
fun `GIVEN inactive lending WHEN invoke THEN returns false`() = runTest {
val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers)
val status = createStatus(isActive = false)
val result = useCase.invoke(status)
assertThat(result).isFalse()
coVerify(exactly = 0) { repository.getViewedWarnings() }
}
@Test
fun `GIVEN active lending and not viewed WHEN invoke THEN returns true`() = runTest {
val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers)
val status = createStatus(isActive = true)
coEvery { repository.getViewedWarnings() } returns emptySet()
val result = useCase.invoke(status)
assertThat(result).isTrue()
coVerify(exactly = 1) { repository.getViewedWarnings() }
}
@Test
fun `GIVEN active lending and already viewed WHEN invoke THEN returns false`() = runTest {
val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers)
val status = createStatus(isActive = true)
coEvery { repository.getViewedWarnings() } returns setOf(status.currency.name)
val result = useCase.invoke(status)
assertThat(result).isFalse()
coVerify(exactly = 1) { repository.getViewedWarnings() }
}
private fun createStatus(isActive: Boolean): CryptoCurrencyStatus {
val currency = MockTokens.token1
val yieldSupplyStatus = YieldSupplyStatus(
isActive = isActive,
isInitialized = true,
isAllowedToSpend = true,
)
val value = CryptoCurrencyStatus.NoQuote(
amount = SerializedBigDecimal.ZERO,
yieldBalance = null,
yieldSupplyStatus = yieldSupplyStatus,
hasCurrentNetworkTransactions = false,
pendingTransactions = emptySet(),
networkAddress = NetworkAddress.Single(
defaultAddress = NetworkAddress.Address(
value = "address",
type = NetworkAddress.Address.Type.Primary,
),
),
sources = CryptoCurrencyStatus.Sources(),
)
return CryptoCurrencyStatus(
currency = currency,
value = value,
)
}
}

View file

@ -0,0 +1,39 @@
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<Unit>
val exitProtocolTriggerFlow: Flow<Unit>
}
@Singleton
internal class DefaultYieldSupplyProtocolTrigger @Inject constructor() :
YieldSupplyProtocolTrigger,
YieldSupplyProtocolListener {
override val enterProtocolTriggerFlow = MutableSharedFlow<Unit>()
override val exitProtocolTriggerFlow = MutableSharedFlow<Unit>()
override suspend fun onEnterProtocol() {
enterProtocolTriggerFlow.emit(Unit)
}
override suspend fun onExitProtocol() {
exitProtocolTriggerFlow.emit(Unit)
}
}

View file

@ -3,6 +3,10 @@ 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
@ -19,3 +23,16 @@ internal object YieldSupplyFeatureModule {
return DefaultYieldSupplyFeatureToggles(featureTogglesManager)
}
}
@InstallIn(SingletonComponent::class)
@Module
internal interface YieldSupplyProtocolModuleBinds {
@Singleton
@Binds
fun bindYieldSupplyProtocolTrigger(impl: DefaultYieldSupplyProtocolTrigger): YieldSupplyProtocolTrigger
@Singleton
@Binds
fun bindYieldSupplyProtocolListener(impl: DefaultYieldSupplyProtocolTrigger): YieldSupplyProtocolListener
}

View file

@ -27,6 +27,7 @@ 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
@ -56,6 +57,7 @@ internal class YieldSupplyModel @Inject constructor(
private val yieldSupplyIsAvailableUseCase: YieldSupplyIsAvailableUseCase,
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
private val yieldSupplyProtocolListener: YieldSupplyProtocolListener,
) : Model(), YieldSupplyClickIntents {
private val params = paramsContainer.require<YieldSupplyComponent.Params>()
@ -83,6 +85,28 @@ 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)
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id)
}
}.launchIn(modelScope)
yieldSupplyProtocolListener.enterProtocolTriggerFlow.onEach {
uiState.update {
YieldSupplyUM.Processing.Enter
}
coroutineScope.launch(dispatchers.io) {
delay(PROCESSING_UPDATE_DELAY)
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id)
}
}.launchIn(modelScope)
}
private fun checkIfYieldSupplyIsAvailable() {

View file

@ -15,6 +15,7 @@ import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.promo.YieldSupplyPromoConfig
import com.tangem.features.yield.supply.impl.promo.entity.YieldSupplyPromoUM
import com.tangem.utils.TangemBlogUrlBuilder
import com.tangem.utils.TangemBlogUrlBuilder.YIELD_SUPPLY_HOW_IT_WORKS_URL
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import javax.inject.Inject
@ -31,8 +32,8 @@ internal class YieldSupplyPromoModel @Inject constructor(
val params: YieldSupplyPromoComponent.Params = paramsContainer.require()
val uiState: YieldSupplyPromoUM = YieldSupplyPromoUM(
tosLink = "https://tangem.com/terms-of-service/", // TODO replace with real link
policyLink = "https://tangem.com/privacy-policy/", // TODO replace with real link
tosLink = TangemBlogUrlBuilder.YIELD_SUPPLY_TOS_URL,
policyLink = TangemBlogUrlBuilder.YIELD_SUPPLY_PRIVACY_URL,
title = resourceReference(R.string.yield_module_promo_screen_title),
subtitle = resourceReference(
R.string.yield_module_promo_screen_variable_rate_info,

View file

@ -15,7 +15,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.transaction.error.GetFeeError
@ -29,6 +28,7 @@ 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
@ -41,7 +41,6 @@ import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.tr
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
import com.tangem.utils.transformer.update
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
@ -63,11 +62,11 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
private val yieldSupplyEstimateEnterFeeUseCase: YieldSupplyEstimateEnterFeeUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
private val yieldSupplyProtocolTrigger: YieldSupplyProtocolTrigger,
) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require()
@ -253,14 +252,9 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
)
},
ifRight = {
yieldSupplyActivateUseCase(cryptoCurrency)
modelScope.launch(NonCancellable) {
fetchCurrencyStatusUseCase(
userWalletId = userWallet.walletId,
cryptoCurrency.id,
)
}
yieldSupplyProtocolTrigger.onEnterProtocol()
analytics.send(YieldSupplyAnalytics.FundsEarned)
yieldSupplyActivateUseCase(cryptoCurrency)
modelScope.launch {
params.callback.onTransactionSent()
}

View file

@ -13,7 +13,6 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
@ -22,6 +21,7 @@ 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
@ -35,7 +35,6 @@ import com.tangem.utils.TangemBlogUrlBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
import com.tangem.utils.transformer.update
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
@ -56,7 +55,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger,
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val yieldSupplyProtocolTrigger: YieldSupplyProtocolTrigger,
) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
private val params: YieldSupplyStopEarningComponent.Params = paramsContainer.require()
@ -159,6 +158,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
)
},
ifRight = {
yieldSupplyProtocolTrigger.onExitProtocol()
analytics.send(
YieldSupplyAnalytics.FundsWithdrawn(
token = cryptoCurrency.symbol,
@ -166,10 +166,9 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
),
)
yieldSupplyDeactivateUseCase(cryptoCurrency)
modelScope.launch(NonCancellable) {
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id)
modelScope.launch {
params.callback.onTransactionSent()
}
params.callback.onTransactionSent()
},
)
}