Updated on 2026-08-14
This commit is contained in:
parent
cf49333c22
commit
c35da7186d
9 changed files with 99 additions and 131 deletions
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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<String, YieldSupplyEnterStatus> = ConcurrentHashMap()
|
||||
|
||||
override suspend fun getCachedMarkets(): List<YieldMarketToken>? = 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<YieldMarketToken>.enrichNetworkIds(): List<YieldMarketToken> {
|
||||
val chainIdMap = Blockchain.entries.associate { it.getChainId() to it.toNetworkId() }
|
||||
return this.map { token ->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.yield.supply.models
|
||||
|
||||
enum class YieldSupplyEnterStatus {
|
||||
Enter,
|
||||
Exit,
|
||||
}
|
||||
|
|
@ -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?
|
||||
}
|
||||
|
|
@ -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<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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<YieldSupplyComponent.Params>()
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue