Updated on 2026-08-14
This commit is contained in:
parent
3b2a867959
commit
cf0659d207
23 changed files with 679 additions and 316 deletions
|
|
@ -45,8 +45,7 @@ import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Par
|
||||||
import com.tangem.features.tokendetails.TokenDetailsComponent
|
import com.tangem.features.tokendetails.TokenDetailsComponent
|
||||||
import com.tangem.features.wallet.WalletEntryComponent
|
import com.tangem.features.wallet.WalletEntryComponent
|
||||||
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
|
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
|
||||||
import com.tangem.features.yield.supply.api.YieldSupplyActiveComponent
|
import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent
|
||||||
import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent
|
|
||||||
import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent
|
import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent
|
||||||
import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent
|
import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
|
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
|
||||||
|
|
@ -118,8 +117,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
|
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
|
||||||
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
|
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
|
||||||
private val kycComponentFactory: KycComponent.Factory,
|
private val kycComponentFactory: KycComponent.Factory,
|
||||||
private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory,
|
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
|
||||||
private val yieldSupplyActiveComponentFactory: YieldSupplyActiveComponent.Factory,
|
|
||||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||||
private val feedEntryComponentFactory: FeedEntryComponent.Factory,
|
private val feedEntryComponentFactory: FeedEntryComponent.Factory,
|
||||||
private val feedFeatureToggle: FeedFeatureToggle,
|
private val feedFeatureToggle: FeedFeatureToggle,
|
||||||
|
|
@ -706,25 +704,15 @@ internal class ChildFactory @Inject constructor(
|
||||||
componentFactory = kycComponentFactory,
|
componentFactory = kycComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is AppRoute.YieldSupplyPromo -> {
|
is AppRoute.YieldSupplyEntry -> {
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = YieldSupplyPromoComponent.Params(
|
params = YieldSupplyEntryComponent.Params(
|
||||||
userWalletId = route.userWalletId,
|
|
||||||
currency = route.cryptoCurrency,
|
|
||||||
apy = route.apy,
|
|
||||||
),
|
|
||||||
componentFactory = yieldSupplyPromoComponentFactory,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is AppRoute.YieldSupplyActive -> {
|
|
||||||
createComponentChild(
|
|
||||||
context = context,
|
|
||||||
params = YieldSupplyActiveComponent.Params(
|
|
||||||
userWalletId = route.userWalletId,
|
userWalletId = route.userWalletId,
|
||||||
cryptoCurrency = route.cryptoCurrency,
|
cryptoCurrency = route.cryptoCurrency,
|
||||||
|
apy = route.apy,
|
||||||
),
|
),
|
||||||
componentFactory = yieldSupplyActiveComponentFactory,
|
componentFactory = yieldSupplyEntryComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is AppRoute.NewsDetails -> {
|
is AppRoute.NewsDetails -> {
|
||||||
|
|
|
||||||
|
|
@ -456,18 +456,11 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
data class Kyc(val userWalletId: UserWalletId) : AppRoute(path = "/kyc")
|
data class Kyc(val userWalletId: UserWalletId) : AppRoute(path = "/kyc")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class YieldSupplyPromo(
|
data class YieldSupplyEntry(
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
val cryptoCurrency: CryptoCurrency,
|
val cryptoCurrency: CryptoCurrency,
|
||||||
val apy: String,
|
val apy: String,
|
||||||
) : AppRoute(path = "/yield_supply_promo/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
|
) : AppRoute(path = "/yield_supply_entry/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class YieldSupplyActive(
|
|
||||||
val userWalletId: UserWalletId,
|
|
||||||
val cryptoCurrency: CryptoCurrency,
|
|
||||||
val apy: String,
|
|
||||||
) : AppRoute(path = "/yield_supply_active/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId")
|
data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.data.yield.supply
|
package com.tangem.data.yield.supply
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.blockchain.common.TransactionStatus
|
||||||
import com.tangem.blockchain.yieldsupply.YieldSupplyProvider
|
import com.tangem.blockchain.yieldsupply.YieldSupplyProvider
|
||||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||||
|
|
@ -19,19 +20,16 @@ import com.tangem.datasource.local.preferences.utils.store
|
||||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.network.TxInfo
|
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||||
import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus
|
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
|
||||||
import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData
|
import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import com.tangem.utils.coroutines.runSuspendCatching
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
internal class DefaultYieldSupplyRepository(
|
internal class DefaultYieldSupplyRepository(
|
||||||
|
|
@ -43,7 +41,7 @@ internal class DefaultYieldSupplyRepository(
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
) : YieldSupplyRepository {
|
) : YieldSupplyRepository {
|
||||||
|
|
||||||
private val statusMap: MutableMap<String, YieldSupplyEnterStatus> = ConcurrentHashMap()
|
private val statusMap: MutableMap<String, YieldSupplyPendingStatus> = ConcurrentHashMap()
|
||||||
|
|
||||||
override suspend fun getCachedMarkets(): List<YieldMarketToken>? = withContext(dispatchers.io) {
|
override suspend fun getCachedMarkets(): List<YieldMarketToken>? = withContext(dispatchers.io) {
|
||||||
val cache = store.getSyncOrNull().orEmpty()
|
val cache = store.getSyncOrNull().orEmpty()
|
||||||
|
|
@ -126,23 +124,39 @@ internal class DefaultYieldSupplyRepository(
|
||||||
).getOrThrow().isActive
|
).getOrThrow().isActive
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun saveTokenProtocolStatus(
|
override suspend fun saveTokenProtocolPendingStatus(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
cryptoCurrency: CryptoCurrency,
|
cryptoCurrency: CryptoCurrency,
|
||||||
yieldSupplyEnterStatus: YieldSupplyEnterStatus?,
|
yieldSupplyPendingStatus: YieldSupplyPendingStatus?,
|
||||||
) {
|
) {
|
||||||
val key = getTokenProtocolStatusKey(userWalletId, cryptoCurrency)
|
val key = getTokenProtocolStatusKey(userWalletId, cryptoCurrency)
|
||||||
if (yieldSupplyEnterStatus != null) {
|
if (yieldSupplyPendingStatus != null) {
|
||||||
statusMap[key] = yieldSupplyEnterStatus
|
statusMap[key] = yieldSupplyPendingStatus
|
||||||
} else {
|
} else {
|
||||||
statusMap.remove(key)
|
statusMap.remove(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTokenProtocolStatus(
|
override suspend fun getPendingTxHashes(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
): List<String> {
|
||||||
|
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
network = cryptoCurrencyStatus.currency.network,
|
||||||
|
) ?: return emptyList()
|
||||||
|
|
||||||
|
return walletManager.wallet.recentTransactions
|
||||||
|
.filter { it.status == TransactionStatus.Unconfirmed }
|
||||||
|
.map {
|
||||||
|
it.hash.orEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTokenProtocolPendingStatus(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
cryptoCurrency: CryptoCurrency,
|
cryptoCurrency: CryptoCurrency,
|
||||||
): YieldSupplyEnterStatus? {
|
): YieldSupplyPendingStatus? {
|
||||||
return statusMap[getTokenProtocolStatusKey(userWalletId, cryptoCurrency)]
|
return statusMap[getTokenProtocolStatusKey(userWalletId, cryptoCurrency)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,32 +167,6 @@ internal class DefaultYieldSupplyRepository(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTokenPendingStatus(
|
|
||||||
userWalletId: UserWalletId,
|
|
||||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
|
||||||
): YieldSupplyEnterStatus? = runSuspendCatching {
|
|
||||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
|
||||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
blockchain = cryptoCurrency.network.toBlockchain(),
|
|
||||||
derivationPath = cryptoCurrency.network.derivationPath.value,
|
|
||||||
) ?: error("Wallet manager not found")
|
|
||||||
|
|
||||||
val pendingTxs = cryptoCurrencyStatus.value.pendingTransactions
|
|
||||||
|
|
||||||
val yieldAddress = walletManager.calculateYieldModuleAddress()
|
|
||||||
val hasRecentYieldEnterTxs = pendingTxs.hasYieldEnterTransactions(yieldAddress)
|
|
||||||
val hasRecentYieldExitTxs = pendingTxs.hasYieldExitTransactions()
|
|
||||||
|
|
||||||
when {
|
|
||||||
hasRecentYieldEnterTxs -> YieldSupplyEnterStatus.Enter
|
|
||||||
hasRecentYieldExitTxs -> YieldSupplyEnterStatus.Exit
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}.onFailure { exception ->
|
|
||||||
Timber.w(exception, "Failed to get pending yield supply status")
|
|
||||||
}.getOrNull()
|
|
||||||
|
|
||||||
override fun getShouldShowYieldPromoBanner(): Flow<Boolean> {
|
override fun getShouldShowYieldPromoBanner(): Flow<Boolean> {
|
||||||
return appPreferencesStore.get(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, true)
|
return appPreferencesStore.get(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, true)
|
||||||
}
|
}
|
||||||
|
|
@ -187,16 +175,6 @@ internal class DefaultYieldSupplyRepository(
|
||||||
appPreferencesStore.store(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, shouldShow)
|
appPreferencesStore.store(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, shouldShow)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Set<TxInfo>.hasYieldEnterTransactions(yieldAddress: String) = any {
|
|
||||||
it.type == TxInfo.TransactionType.YieldSupply.Enter ||
|
|
||||||
it.type == TxInfo.TransactionType.Approve &&
|
|
||||||
(it.interactionAddressType as? TxInfo.InteractionAddressType.Contract)?.address == yieldAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Set<TxInfo>.hasYieldExitTransactions() = any {
|
|
||||||
it.type == TxInfo.TransactionType.YieldSupply.Exit
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getTokenProtocolStatusKey(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String =
|
private fun getTokenProtocolStatusKey(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String =
|
||||||
"${userWalletId}_${cryptoCurrency.id.value}"
|
"${userWalletId}_${cryptoCurrency.id.value}"
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package com.tangem.domain.yield.supply.models
|
|
||||||
|
|
||||||
enum class YieldSupplyEnterStatus {
|
|
||||||
Enter,
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.tangem.domain.yield.supply.models
|
||||||
|
|
||||||
|
sealed class YieldSupplyPendingStatus(open val txIds: List<String>) {
|
||||||
|
data class Enter(override val txIds: List<String>) : YieldSupplyPendingStatus(txIds)
|
||||||
|
data class Exit(override val txIds: List<String>) : YieldSupplyPendingStatus(txIds)
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||||
import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus
|
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
|
||||||
import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData
|
import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
@ -72,14 +72,26 @@ interface YieldSupplyRepository {
|
||||||
*
|
*
|
||||||
* @param userWalletId the wallet the action was performed with
|
* @param userWalletId the wallet the action was performed with
|
||||||
* @param cryptoCurrency the currency or token the action relates to
|
* @param cryptoCurrency the currency or token the action relates to
|
||||||
* @param yieldSupplyEnterStatus the last action intent: [YieldSupplyEnterStatus.Enter] or [YieldSupplyEnterStatus.Exit]
|
* @param yieldSupplyPendingStatus the last action intent: [YieldSupplyPendingStatus.Enter] or [YieldSupplyPendingStatus.Exit]
|
||||||
*/
|
*/
|
||||||
suspend fun saveTokenProtocolStatus(
|
suspend fun saveTokenProtocolPendingStatus(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
cryptoCurrency: CryptoCurrency,
|
cryptoCurrency: CryptoCurrency,
|
||||||
yieldSupplyEnterStatus: YieldSupplyEnterStatus?,
|
yieldSupplyPendingStatus: YieldSupplyPendingStatus?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the hashes of pending (unconfirmed) transactions for the given wallet and currency.
|
||||||
|
*
|
||||||
|
* @param userWalletId the wallet to query
|
||||||
|
* @param cryptoCurrencyStatus the currency status containing network information
|
||||||
|
* @return list of transaction hashes that are still unconfirmed, or an empty list if none exist
|
||||||
|
*/
|
||||||
|
suspend fun getPendingTxHashes(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
): List<String>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the last saved user‑initiated yield protocol action for the given wallet and currency, if any.
|
* Get the last saved user‑initiated yield protocol action for the given wallet and currency, if any.
|
||||||
*
|
*
|
||||||
|
|
@ -91,23 +103,10 @@ interface YieldSupplyRepository {
|
||||||
* @param cryptoCurrency the currency or token to query
|
* @param cryptoCurrency the currency or token to query
|
||||||
* @return the last action intent or null if nothing has been recorded
|
* @return the last action intent or null if nothing has been recorded
|
||||||
*/
|
*/
|
||||||
fun getTokenProtocolStatus(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): YieldSupplyEnterStatus?
|
fun getTokenProtocolPendingStatus(
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the pending status of the yield protocol action for the given wallet and 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. The value is
|
|
||||||
* transient (in‑memory only) and is not persisted across app restarts.
|
|
||||||
*
|
|
||||||
* @param userWalletId the wallet to query
|
|
||||||
* @param cryptoCurrencyStatus the currency status to query
|
|
||||||
* @return the pending action intent or null if nothing has been recorded
|
|
||||||
*/
|
|
||||||
suspend fun getTokenPendingStatus(
|
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
cryptoCurrency: CryptoCurrency,
|
||||||
): YieldSupplyEnterStatus?
|
): YieldSupplyPendingStatus?
|
||||||
|
|
||||||
fun getShouldShowYieldPromoBanner(): Flow<Boolean>
|
fun getShouldShowYieldPromoBanner(): Flow<Boolean>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,36 @@ import arrow.core.Either
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||||
import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus
|
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
|
||||||
|
|
||||||
class YieldSupplyEnterStatusUseCase(
|
class YieldSupplyEnterStatusUseCase(
|
||||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
operator fun invoke(
|
suspend operator fun invoke(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
): Either<Throwable, YieldSupplyEnterStatus?> {
|
): Either<Throwable, YieldSupplyPendingStatus?> {
|
||||||
return Either.catch {
|
return Either.catch {
|
||||||
yieldSupplyRepository.getTokenProtocolStatus(
|
val status = yieldSupplyRepository.getTokenProtocolPendingStatus(
|
||||||
userWalletId,
|
userWalletId,
|
||||||
cryptoCurrencyStatus.currency,
|
cryptoCurrencyStatus.currency,
|
||||||
)
|
)
|
||||||
|
val pendingTxHashes = yieldSupplyRepository
|
||||||
|
.getPendingTxHashes(userWalletId, cryptoCurrencyStatus)
|
||||||
|
.toSet()
|
||||||
|
val hasPendingTx = status?.txIds?.any { it in pendingTxHashes } == true
|
||||||
|
|
||||||
|
if (hasPendingTx) {
|
||||||
|
status
|
||||||
|
} else {
|
||||||
|
yieldSupplyRepository.saveTokenProtocolPendingStatus(
|
||||||
|
userWalletId,
|
||||||
|
cryptoCurrencyStatus.currency,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,242 @@
|
||||||
|
package com.tangem.domain.yield.supply.usecase
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.models.network.Network
|
||||||
|
import com.tangem.domain.models.network.NetworkAddress
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||||
|
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class YieldSupplyEnterStatusUseCaseTest {
|
||||||
|
|
||||||
|
private val yieldSupplyRepository: YieldSupplyRepository = mockk()
|
||||||
|
|
||||||
|
private lateinit var useCase: YieldSupplyEnterStatusUseCase
|
||||||
|
|
||||||
|
private val userWalletId = UserWalletId("abcdef012345")
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
useCase = YieldSupplyEnterStatusUseCase(
|
||||||
|
yieldSupplyRepository = yieldSupplyRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN pending tx exists in status WHEN invoke THEN returns status`() = runTest {
|
||||||
|
val token = createToken()
|
||||||
|
val cryptoStatus = createStatus(token)
|
||||||
|
val pendingTxHash = "0xabc123"
|
||||||
|
val status = YieldSupplyPendingStatus.Enter(txIds = listOf(pendingTxHash))
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
|
||||||
|
} returns status
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus)
|
||||||
|
} returns listOf(pendingTxHash)
|
||||||
|
|
||||||
|
val result = useCase(userWalletId, cryptoStatus)
|
||||||
|
|
||||||
|
assertThat(result.isRight()).isTrue()
|
||||||
|
val value = (result as Either.Right).value
|
||||||
|
assertThat(value).isEqualTo(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN no pending tx matches status WHEN invoke THEN clears status and returns null`() = runTest {
|
||||||
|
val token = createToken()
|
||||||
|
val cryptoStatus = createStatus(token)
|
||||||
|
val status = YieldSupplyPendingStatus.Enter(txIds = listOf("0xabc123"))
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
|
||||||
|
} returns status
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus)
|
||||||
|
} returns listOf("0xdifferent")
|
||||||
|
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) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN status is null WHEN invoke THEN returns null`() = runTest {
|
||||||
|
val token = createToken()
|
||||||
|
val cryptoStatus = createStatus(token)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
|
||||||
|
} returns null
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus)
|
||||||
|
} 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN empty pending tx list WHEN invoke THEN clears status and returns null`() = runTest {
|
||||||
|
val token = createToken()
|
||||||
|
val cryptoStatus = createStatus(token)
|
||||||
|
val status = YieldSupplyPendingStatus.Enter(txIds = listOf("0xabc123"))
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
|
||||||
|
} returns status
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus)
|
||||||
|
} 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) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN repository throws exception WHEN invoke THEN returns left with error`() = runTest {
|
||||||
|
val token = createToken()
|
||||||
|
val cryptoStatus = createStatus(token)
|
||||||
|
val exception = RuntimeException("Network error")
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
|
||||||
|
} throws exception
|
||||||
|
|
||||||
|
val result = useCase(userWalletId, cryptoStatus)
|
||||||
|
|
||||||
|
assertThat(result.isLeft()).isTrue()
|
||||||
|
val error = (result as Either.Left).value
|
||||||
|
assertThat(error).isEqualTo(exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN exit status with pending tx WHEN invoke THEN returns status`() = runTest {
|
||||||
|
val token = createToken()
|
||||||
|
val cryptoStatus = createStatus(token)
|
||||||
|
val pendingTxHash = "0xexit456"
|
||||||
|
val status = YieldSupplyPendingStatus.Exit(txIds = listOf(pendingTxHash))
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
|
||||||
|
} returns status
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus)
|
||||||
|
} returns listOf(pendingTxHash)
|
||||||
|
|
||||||
|
val result = useCase(userWalletId, cryptoStatus)
|
||||||
|
|
||||||
|
assertThat(result.isRight()).isTrue()
|
||||||
|
val value = (result as Either.Right).value
|
||||||
|
assertThat(value).isEqualTo(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN multiple tx ids with one matching WHEN invoke THEN returns status`() = runTest {
|
||||||
|
val token = createToken()
|
||||||
|
val cryptoStatus = createStatus(token)
|
||||||
|
val matchingTxHash = "0xmatch"
|
||||||
|
val status = YieldSupplyPendingStatus.Enter(txIds = listOf("0xfirst", matchingTxHash, "0xlast"))
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
|
||||||
|
} returns status
|
||||||
|
coEvery {
|
||||||
|
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus)
|
||||||
|
} returns listOf(matchingTxHash)
|
||||||
|
|
||||||
|
val result = useCase(userWalletId, cryptoStatus)
|
||||||
|
|
||||||
|
assertThat(result.isRight()).isTrue()
|
||||||
|
val value = (result as Either.Right).value
|
||||||
|
assertThat(value).isEqualTo(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createToken(): CryptoCurrency.Token {
|
||||||
|
val rawNetworkId = "ethereum"
|
||||||
|
val derivationPath = Network.DerivationPath.None
|
||||||
|
val network = Network(
|
||||||
|
id = Network.ID(value = rawNetworkId, derivationPath = derivationPath),
|
||||||
|
backendId = rawNetworkId,
|
||||||
|
name = rawNetworkId,
|
||||||
|
currencySymbol = rawNetworkId.take(3).uppercase(),
|
||||||
|
derivationPath = derivationPath,
|
||||||
|
isTestnet = false,
|
||||||
|
standardType = Network.StandardType.Unspecified("UNSPECIFIED"),
|
||||||
|
hasFiatFeeRate = true,
|
||||||
|
canHandleTokens = true,
|
||||||
|
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||||
|
nameResolvingType = Network.NameResolvingType.NONE,
|
||||||
|
)
|
||||||
|
|
||||||
|
return CryptoCurrency.Token(
|
||||||
|
id = CryptoCurrency.ID(
|
||||||
|
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||||
|
body = CryptoCurrency.ID.Body.NetworkId(rawNetworkId),
|
||||||
|
suffix = CryptoCurrency.ID.Suffix.RawID(rawNetworkId),
|
||||||
|
),
|
||||||
|
network = network,
|
||||||
|
name = "TEST_TOKEN",
|
||||||
|
symbol = "TTK",
|
||||||
|
decimals = 18,
|
||||||
|
iconUrl = null,
|
||||||
|
isCustom = false,
|
||||||
|
contractAddress = "0xToken",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createStatus(token: CryptoCurrency.Token): CryptoCurrencyStatus {
|
||||||
|
return CryptoCurrencyStatus(
|
||||||
|
currency = token,
|
||||||
|
value = CryptoCurrencyStatus.Custom(
|
||||||
|
amount = BigDecimal.ZERO,
|
||||||
|
fiatAmount = BigDecimal.ZERO,
|
||||||
|
fiatRate = BigDecimal.ONE,
|
||||||
|
priceChange = BigDecimal.ZERO,
|
||||||
|
stakingBalance = null,
|
||||||
|
yieldSupplyStatus = null,
|
||||||
|
hasCurrentNetworkTransactions = false,
|
||||||
|
pendingTransactions = emptySet(),
|
||||||
|
networkAddress = NetworkAddress.Single(
|
||||||
|
defaultAddress = NetworkAddress.Address(
|
||||||
|
value = "0x0000000000000000000000000000000000000000",
|
||||||
|
type = NetworkAddress.Address.Type.Primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sources = CryptoCurrencyStatus.Sources(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,8 +20,6 @@ import com.tangem.domain.onramp.model.OnrampSource
|
||||||
import com.tangem.domain.redux.ReduxStateHolder
|
import com.tangem.domain.redux.ReduxStateHolder
|
||||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||||
import com.tangem.domain.tokens.model.TokenActionsState
|
import com.tangem.domain.tokens.model.TokenActionsState
|
||||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase
|
|
||||||
import com.tangem.features.feed.components.market.details.portfolio.impl.loader.PortfolioData
|
import com.tangem.features.feed.components.market.details.portfolio.impl.loader.PortfolioData
|
||||||
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.TokenActionsBSContentUM
|
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.TokenActionsBSContentUM
|
||||||
import com.tangem.features.feed.impl.R
|
import com.tangem.features.feed.impl.R
|
||||||
|
|
@ -43,7 +41,6 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
||||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||||
private val messageSender: UiMessageSender,
|
private val messageSender: UiMessageSender,
|
||||||
private val shareManager: ShareManager,
|
private val shareManager: ShareManager,
|
||||||
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val disabledActionsInDemoMode = buildSet {
|
private val disabledActionsInDemoMode = buildSet {
|
||||||
|
|
@ -186,41 +183,13 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
||||||
val yieldSupplyApy = cryptoCurrencyData.actions.filterIsInstance<TokenActionsState.ActionState.YieldMode>()
|
val yieldSupplyApy = cryptoCurrencyData.actions.filterIsInstance<TokenActionsState.ActionState.YieldMode>()
|
||||||
.firstOrNull()?.apy ?: return
|
.firstOrNull()?.apy ?: return
|
||||||
|
|
||||||
val (userWalletId, cryptoCurrencyStatus) = cryptoCurrencyData.let { currencyData ->
|
router.push(
|
||||||
currencyData.userWallet.walletId to currencyData.status
|
AppRoute.YieldSupplyEntry(
|
||||||
}
|
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||||
val tokenEnterStatus = yieldSupplyEnterStatusUseCase(userWalletId, cryptoCurrencyStatus).getOrNull()
|
cryptoCurrency = cryptoCurrencyData.status.currency,
|
||||||
val isActiveYield = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true
|
apy = yieldSupplyApy,
|
||||||
|
),
|
||||||
when {
|
)
|
||||||
tokenEnterStatus != null -> router.push(
|
|
||||||
AppRoute.CurrencyDetails(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
currency = cryptoCurrencyStatus.currency,
|
|
||||||
navigationAction = NavigationAction.YieldSupply(
|
|
||||||
isActive = isActiveYield,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
isActiveYield -> {
|
|
||||||
router.push(
|
|
||||||
AppRoute.YieldSupplyActive(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
|
||||||
apy = yieldSupplyApy,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
router.push(
|
|
||||||
AppRoute.YieldSupplyPromo(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
|
||||||
apy = yieldSupplyApy,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@ import com.tangem.domain.onramp.model.OnrampSource
|
||||||
import com.tangem.domain.redux.ReduxStateHolder
|
import com.tangem.domain.redux.ReduxStateHolder
|
||||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||||
import com.tangem.domain.tokens.model.TokenActionsState
|
import com.tangem.domain.tokens.model.TokenActionsState
|
||||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase
|
|
||||||
import com.tangem.features.markets.impl.R
|
import com.tangem.features.markets.impl.R
|
||||||
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
|
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
|
||||||
import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM
|
import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM
|
||||||
|
|
@ -43,7 +41,6 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
||||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||||
private val messageSender: UiMessageSender,
|
private val messageSender: UiMessageSender,
|
||||||
private val shareManager: ShareManager,
|
private val shareManager: ShareManager,
|
||||||
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val disabledActionsInDemoMode = buildSet {
|
private val disabledActionsInDemoMode = buildSet {
|
||||||
|
|
@ -186,41 +183,13 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
||||||
val yieldSupplyApy = cryptoCurrencyData.actions.filterIsInstance<TokenActionsState.ActionState.YieldMode>()
|
val yieldSupplyApy = cryptoCurrencyData.actions.filterIsInstance<TokenActionsState.ActionState.YieldMode>()
|
||||||
.firstOrNull()?.apy ?: return
|
.firstOrNull()?.apy ?: return
|
||||||
|
|
||||||
val (userWalletId, cryptoCurrencyStatus) = cryptoCurrencyData.let { currencyData ->
|
router.push(
|
||||||
currencyData.userWallet.walletId to currencyData.status
|
AppRoute.YieldSupplyEntry(
|
||||||
}
|
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||||
val tokenEnterStatus = yieldSupplyEnterStatusUseCase(userWalletId, cryptoCurrencyStatus).getOrNull()
|
cryptoCurrency = cryptoCurrencyData.status.currency,
|
||||||
val isActiveYield = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true
|
apy = yieldSupplyApy,
|
||||||
|
),
|
||||||
when {
|
)
|
||||||
tokenEnterStatus != null -> router.push(
|
|
||||||
AppRoute.CurrencyDetails(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
currency = cryptoCurrencyStatus.currency,
|
|
||||||
navigationAction = NavigationAction.YieldSupply(
|
|
||||||
isActive = isActiveYield,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
isActiveYield -> {
|
|
||||||
router.push(
|
|
||||||
AppRoute.YieldSupplyActive(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
|
||||||
apy = yieldSupplyApy,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
router.push(
|
|
||||||
AppRoute.YieldSupplyPromo(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
|
||||||
apy = yieldSupplyApy,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import com.tangem.domain.tokens.model.TokenActionsState
|
||||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase
|
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase
|
||||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||||
|
|
@ -103,7 +102,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||||
private val accountDependencies: AccountDependencies,
|
private val accountDependencies: AccountDependencies,
|
||||||
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
|
|
||||||
private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase,
|
private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase,
|
||||||
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||||
) : BaseWalletClickIntents(), WalletContentClickIntents {
|
) : BaseWalletClickIntents(), WalletContentClickIntents {
|
||||||
|
|
@ -199,7 +197,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
||||||
is NavigationAction.YieldSupply -> openYieldSupply(
|
is NavigationAction.YieldSupply -> openYieldSupply(
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
cryptoCurrencyStatus = currencyStatus,
|
cryptoCurrencyStatus = currencyStatus,
|
||||||
navigationAction = navigationAction,
|
|
||||||
apy = apy,
|
apy = apy,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -242,32 +239,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
||||||
accountDependencies.expandedAccountsHolder.collapseAccount(account.accountId)
|
accountDependencies.expandedAccountsHolder.collapseAccount(account.accountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openYieldSupply(
|
private fun openYieldSupply(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, apy: String) {
|
||||||
userWalletId: UserWalletId,
|
router.openYieldSupplyEntryScreen(
|
||||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
userWalletId = userWalletId,
|
||||||
navigationAction: NavigationAction.YieldSupply,
|
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||||
apy: String,
|
apy = apy,
|
||||||
) {
|
)
|
||||||
modelScope.launch {
|
|
||||||
val tokenEnterStatus = yieldSupplyEnterStatusUseCase(userWalletId, cryptoCurrencyStatus).getOrNull()
|
|
||||||
when {
|
|
||||||
tokenEnterStatus != null -> router.openTokenDetails(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
currencyStatus = cryptoCurrencyStatus,
|
|
||||||
navigationAction = navigationAction,
|
|
||||||
)
|
|
||||||
navigationAction.isActive -> router.openYieldSupplyActiveScreen(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
|
||||||
apy = apy,
|
|
||||||
)
|
|
||||||
else -> router.openYieldSupplyPromoScreen(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
|
||||||
apy = apy,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendApyLabelClickAnalytics(navigationAction: NavigationAction, currencyStatus: CryptoCurrencyStatus) {
|
private fun sendApyLabelClickAnalytics(navigationAction: NavigationAction, currencyStatus: CryptoCurrencyStatus) {
|
||||||
|
|
|
||||||
|
|
@ -133,19 +133,9 @@ internal class DefaultWalletRouter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openYieldSupplyActiveScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String) {
|
override fun openYieldSupplyEntryScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String) {
|
||||||
router.push(
|
router.push(
|
||||||
AppRoute.YieldSupplyActive(
|
AppRoute.YieldSupplyEntry(
|
||||||
userWalletId = userWalletId,
|
|
||||||
cryptoCurrency = cryptoCurrency,
|
|
||||||
apy = apy,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openYieldSupplyPromoScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String) {
|
|
||||||
router.push(
|
|
||||||
AppRoute.YieldSupplyPromo(
|
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
apy = apy,
|
apy = apy,
|
||||||
|
|
|
||||||
|
|
@ -75,9 +75,6 @@ internal interface InnerWalletRouter {
|
||||||
onWarningAcknowledged: (TokenAction) -> Unit,
|
onWarningAcknowledged: (TokenAction) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Open yield supply active screen */
|
/** Open yield supply entry screen */
|
||||||
fun openYieldSupplyActiveScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String)
|
fun openYieldSupplyEntryScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String)
|
||||||
|
|
||||||
/** Open yield supply promo screen */
|
|
||||||
fun openYieldSupplyPromoScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String)
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.tangem.features.yield.supply.api
|
||||||
|
|
||||||
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
|
||||||
|
interface YieldSupplyEntryComponent : ComposableContentComponent {
|
||||||
|
|
||||||
|
data class Params(
|
||||||
|
val userWalletId: UserWalletId,
|
||||||
|
val cryptoCurrency: CryptoCurrency,
|
||||||
|
val apy: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface Factory : ComponentFactory<Params, YieldSupplyEntryComponent>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.tangem.features.yield.supply.api.entry
|
||||||
|
|
||||||
|
import com.tangem.core.decompose.navigation.Route
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route for switching yield supply promo and active flows.
|
||||||
|
*/
|
||||||
|
sealed class YieldSupplyEntryRoute : Route {
|
||||||
|
|
||||||
|
/** Initial empty route with no UI */
|
||||||
|
data object Empty : YieldSupplyEntryRoute()
|
||||||
|
|
||||||
|
/** Route to yield supply promo screen */
|
||||||
|
data class Promo(
|
||||||
|
val cryptoCurrency: CryptoCurrency,
|
||||||
|
val apy: String,
|
||||||
|
) : YieldSupplyEntryRoute()
|
||||||
|
|
||||||
|
/** Route to yield supply active screen */
|
||||||
|
data class Active(
|
||||||
|
val cryptoCurrency: CryptoCurrency,
|
||||||
|
) : YieldSupplyEntryRoute()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package com.tangem.features.yield.supply.impl.entry
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.arkivanov.decompose.extensions.compose.stack.Children
|
||||||
|
import com.arkivanov.decompose.extensions.compose.stack.animation.fade
|
||||||
|
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
|
||||||
|
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||||
|
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||||
|
import com.arkivanov.decompose.router.stack.childStack
|
||||||
|
import com.arkivanov.decompose.router.stack.pop
|
||||||
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
|
import com.tangem.core.decompose.context.childByContext
|
||||||
|
import com.tangem.core.decompose.model.getOrCreateModel
|
||||||
|
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||||
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
import com.tangem.features.yield.supply.api.YieldSupplyActiveComponent
|
||||||
|
import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent
|
||||||
|
import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent
|
||||||
|
import com.tangem.features.yield.supply.api.entry.YieldSupplyEntryRoute
|
||||||
|
import com.tangem.features.yield.supply.impl.entry.model.YieldSupplyEntryModel
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
|
||||||
|
internal class DefaultYieldSupplyEntryComponent @AssistedInject constructor(
|
||||||
|
@Assisted appComponentContext: AppComponentContext,
|
||||||
|
@Assisted private val params: YieldSupplyEntryComponent.Params,
|
||||||
|
private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory,
|
||||||
|
private val yieldSupplyActiveComponentFactory: YieldSupplyActiveComponent.Factory,
|
||||||
|
) : YieldSupplyEntryComponent, AppComponentContext by appComponentContext {
|
||||||
|
|
||||||
|
private val stackNavigation = StackNavigation<YieldSupplyEntryRoute>()
|
||||||
|
|
||||||
|
private val innerRouter = InnerRouter<YieldSupplyEntryRoute>(
|
||||||
|
stackNavigation = stackNavigation,
|
||||||
|
popCallback = { onChildBack() },
|
||||||
|
)
|
||||||
|
|
||||||
|
private val model: YieldSupplyEntryModel = getOrCreateModel(
|
||||||
|
params = params,
|
||||||
|
router = innerRouter,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val childStack = childStack(
|
||||||
|
key = "yieldSupplyEntryStack",
|
||||||
|
source = stackNavigation,
|
||||||
|
serializer = null,
|
||||||
|
initialConfiguration = model.initialRoute,
|
||||||
|
handleBackButton = true,
|
||||||
|
childFactory = { configuration, factoryContext ->
|
||||||
|
getChildComponent(
|
||||||
|
configuration = configuration,
|
||||||
|
factoryContext = childByContext(
|
||||||
|
componentContext = factoryContext,
|
||||||
|
router = innerRouter,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content(modifier: Modifier) {
|
||||||
|
val childStackValue by childStack.subscribeAsState()
|
||||||
|
|
||||||
|
Children(
|
||||||
|
stack = childStackValue,
|
||||||
|
modifier = modifier,
|
||||||
|
animation = stackAnimation { fade() },
|
||||||
|
) { child ->
|
||||||
|
child.instance.Content(Modifier.fillMaxSize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getChildComponent(
|
||||||
|
configuration: YieldSupplyEntryRoute,
|
||||||
|
factoryContext: AppComponentContext,
|
||||||
|
): ComposableContentComponent = when (configuration) {
|
||||||
|
is YieldSupplyEntryRoute.Empty -> EmptyComponent
|
||||||
|
is YieldSupplyEntryRoute.Promo -> yieldSupplyPromoComponentFactory.create(
|
||||||
|
context = factoryContext,
|
||||||
|
params = YieldSupplyPromoComponent.Params(
|
||||||
|
userWalletId = params.userWalletId,
|
||||||
|
currency = configuration.cryptoCurrency,
|
||||||
|
apy = configuration.apy,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
is YieldSupplyEntryRoute.Active -> yieldSupplyActiveComponentFactory.create(
|
||||||
|
context = factoryContext,
|
||||||
|
params = YieldSupplyActiveComponent.Params(
|
||||||
|
userWalletId = params.userWalletId,
|
||||||
|
cryptoCurrency = configuration.cryptoCurrency,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private object EmptyComponent : ComposableContentComponent {
|
||||||
|
@Composable
|
||||||
|
override fun Content(modifier: Modifier) {
|
||||||
|
Box(modifier = modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onChildBack() {
|
||||||
|
if (childStack.value.backStack.isEmpty() ||
|
||||||
|
childStack.value.backStack.last().configuration is YieldSupplyEntryRoute.Empty
|
||||||
|
) {
|
||||||
|
router.pop()
|
||||||
|
} else {
|
||||||
|
stackNavigation.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : YieldSupplyEntryComponent.Factory {
|
||||||
|
override fun create(
|
||||||
|
context: AppComponentContext,
|
||||||
|
params: YieldSupplyEntryComponent.Params,
|
||||||
|
): DefaultYieldSupplyEntryComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.tangem.features.yield.supply.impl.entry.di
|
||||||
|
|
||||||
|
import com.tangem.core.decompose.di.ModelComponent
|
||||||
|
import com.tangem.core.decompose.model.Model
|
||||||
|
import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent
|
||||||
|
import com.tangem.features.yield.supply.impl.entry.DefaultYieldSupplyEntryComponent
|
||||||
|
import com.tangem.features.yield.supply.impl.entry.model.YieldSupplyEntryModel
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import dagger.multibindings.ClassKey
|
||||||
|
import dagger.multibindings.IntoMap
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal interface YieldSupplyEntryBindsModule {
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun provideYieldSupplyEntryComponentFactory(
|
||||||
|
impl: DefaultYieldSupplyEntryComponent.Factory,
|
||||||
|
): YieldSupplyEntryComponent.Factory
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(ModelComponent::class)
|
||||||
|
internal interface YieldSupplyEntryModelModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ClassKey(YieldSupplyEntryModel::class)
|
||||||
|
fun provideYieldSupplyEntryModel(impl: YieldSupplyEntryModel): Model
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package com.tangem.features.yield.supply.impl.entry.model
|
||||||
|
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
|
import com.tangem.core.decompose.model.Model
|
||||||
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
|
import com.tangem.core.decompose.navigation.Router
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||||
|
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||||
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase
|
||||||
|
import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent
|
||||||
|
import com.tangem.features.yield.supply.api.entry.YieldSupplyEntryRoute
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ModelScoped
|
||||||
|
internal class YieldSupplyEntryModel @Inject constructor(
|
||||||
|
paramsContainer: ParamsContainer,
|
||||||
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
private val router: Router,
|
||||||
|
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
|
||||||
|
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||||
|
) : Model() {
|
||||||
|
|
||||||
|
private val params = paramsContainer.require<YieldSupplyEntryComponent.Params>()
|
||||||
|
|
||||||
|
val initialRoute: YieldSupplyEntryRoute = YieldSupplyEntryRoute.Empty
|
||||||
|
|
||||||
|
init {
|
||||||
|
navigateToInitialRoute()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToInitialRoute() {
|
||||||
|
val userWalletId = params.userWalletId
|
||||||
|
val cryptoCurrency = params.cryptoCurrency
|
||||||
|
modelScope.launch(dispatchers.default) {
|
||||||
|
getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
cryptoCurrencyId = cryptoCurrency.id,
|
||||||
|
).onLeft { error ->
|
||||||
|
Timber.e("Failed to get CryptoCurrencyStatus: $error")
|
||||||
|
router.pop()
|
||||||
|
}.onRight { cryptoCurrencyStatus ->
|
||||||
|
val route = getInitialRoute(cryptoCurrencyStatus)
|
||||||
|
if (route != null) {
|
||||||
|
router.replaceCurrent(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getInitialRoute(cryptoCurrencyStatus: CryptoCurrencyStatus): YieldSupplyEntryRoute? {
|
||||||
|
val userWalletId = params.userWalletId
|
||||||
|
val cryptoCurrency = params.cryptoCurrency
|
||||||
|
val token = cryptoCurrency as? CryptoCurrency.Token
|
||||||
|
if (token == null) {
|
||||||
|
router.pop()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val tokenEnterStatus = yieldSupplyEnterStatusUseCase(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
|
).getOrNull()
|
||||||
|
val isActiveYield = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true
|
||||||
|
|
||||||
|
if (tokenEnterStatus != null) {
|
||||||
|
router.replaceCurrent(
|
||||||
|
AppRoute.CurrencyDetails(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
currency = token,
|
||||||
|
navigationAction = NavigationAction.YieldSupply(
|
||||||
|
isActive = isActiveYield,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (isActiveYield) {
|
||||||
|
YieldSupplyEntryRoute.Active(cryptoCurrency = token)
|
||||||
|
} else {
|
||||||
|
YieldSupplyEntryRoute.Promo(cryptoCurrency = token, apy = params.apy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,6 @@ package com.tangem.features.yield.supply.impl.main.model
|
||||||
|
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import android.os.SystemClock
|
|
||||||
import com.tangem.common.routing.AppRoute.YieldSupplyPromo
|
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
|
|
@ -25,8 +23,7 @@ import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
|
||||||
import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus
|
|
||||||
import com.tangem.domain.yield.supply.usecase.*
|
import com.tangem.domain.yield.supply.usecase.*
|
||||||
import com.tangem.features.yield.supply.api.YieldSupplyComponent
|
import com.tangem.features.yield.supply.api.YieldSupplyComponent
|
||||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||||
|
|
@ -63,7 +60,7 @@ internal class YieldSupplyModel @Inject constructor(
|
||||||
private val yieldSupplyIsAvailableUseCase: YieldSupplyIsAvailableUseCase,
|
private val yieldSupplyIsAvailableUseCase: YieldSupplyIsAvailableUseCase,
|
||||||
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
|
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
|
||||||
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
|
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
|
||||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
|
||||||
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
|
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
|
||||||
private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
|
private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
|
||||||
) : Model(), YieldSupplyClickIntents {
|
) : Model(), YieldSupplyClickIntents {
|
||||||
|
|
@ -76,11 +73,11 @@ internal class YieldSupplyModel @Inject constructor(
|
||||||
private val cryptoCurrency = params.cryptoCurrency
|
private val cryptoCurrency = params.cryptoCurrency
|
||||||
private var appCurrency: AppCurrency = AppCurrency.Default
|
private var appCurrency: AppCurrency = AppCurrency.Default
|
||||||
var userWallet: UserWallet by Delegates.notNull()
|
var userWallet: UserWallet by Delegates.notNull()
|
||||||
|
private var latestCryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||||
|
|
||||||
private val fetchCurrencyJobHolder = JobHolder()
|
private val fetchCurrencyJobHolder = JobHolder()
|
||||||
private val loadStatusJobHolder = JobHolder()
|
private val loadStatusJobHolder = JobHolder()
|
||||||
|
|
||||||
private var lastStatusCheckTimestamp = 0L
|
|
||||||
private val isFirstCryptoCurrencyStatusEmission = AtomicBoolean(true)
|
private val isFirstCryptoCurrencyStatusEmission = AtomicBoolean(true)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -116,6 +113,7 @@ internal class YieldSupplyModel @Inject constructor(
|
||||||
).onEach { maybeCryptoCurrency ->
|
).onEach { maybeCryptoCurrency ->
|
||||||
maybeCryptoCurrency.fold(
|
maybeCryptoCurrency.fold(
|
||||||
ifRight = { cryptoCurrencyStatus ->
|
ifRight = { cryptoCurrencyStatus ->
|
||||||
|
latestCryptoCurrencyStatus = cryptoCurrencyStatus
|
||||||
if (isFirstCryptoCurrencyStatusEmission.compareAndSet(true, false)) {
|
if (isFirstCryptoCurrencyStatusEmission.compareAndSet(true, false)) {
|
||||||
sendInfoAboutProtocolStatus(cryptoCurrencyStatus)
|
sendInfoAboutProtocolStatus(cryptoCurrencyStatus)
|
||||||
}
|
}
|
||||||
|
|
@ -152,96 +150,55 @@ internal class YieldSupplyModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartEarningClick() {
|
override fun onStartEarningClick() {
|
||||||
val apy = when (val yieldSupplyUM = uiState.value) {
|
navigateToYieldSupplyEntry()
|
||||||
is YieldSupplyUM.Available -> yieldSupplyUM.apy
|
|
||||||
is YieldSupplyUM.Content -> yieldSupplyUM.apy
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
appRouter.push(
|
|
||||||
YieldSupplyPromo(
|
|
||||||
userWalletId = params.userWalletId,
|
|
||||||
cryptoCurrency = params.cryptoCurrency,
|
|
||||||
apy = apy,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActiveClick() {
|
override fun onActiveClick() {
|
||||||
|
navigateToYieldSupplyEntry()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToYieldSupplyEntry() {
|
||||||
|
val cryptoCurrencyStatus = latestCryptoCurrencyStatus ?: return
|
||||||
val apy = when (val yieldSupplyUM = uiState.value) {
|
val apy = when (val yieldSupplyUM = uiState.value) {
|
||||||
is YieldSupplyUM.Available -> yieldSupplyUM.apy
|
is YieldSupplyUM.Available -> yieldSupplyUM.apy
|
||||||
is YieldSupplyUM.Content -> yieldSupplyUM.apy
|
is YieldSupplyUM.Content -> yieldSupplyUM.apy
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
appRouter.push(
|
appRouter.push(
|
||||||
AppRoute.YieldSupplyActive(
|
AppRoute.YieldSupplyEntry(
|
||||||
userWalletId = params.userWalletId,
|
userWalletId = params.userWalletId,
|
||||||
cryptoCurrency = params.cryptoCurrency,
|
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||||
apy = apy,
|
apy = apy,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MaximumLineLength")
|
|
||||||
private fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) = modelScope.launch(
|
private fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) = modelScope.launch(
|
||||||
dispatchers.default,
|
dispatchers.default,
|
||||||
) {
|
) {
|
||||||
val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
|
|
||||||
val tokenProtocolStatus = yieldSupplyRepository.getTokenProtocolStatus(
|
|
||||||
userWallet.walletId,
|
|
||||||
cryptoCurrency,
|
|
||||||
)
|
|
||||||
val tokenPendingStatus = yieldSupplyRepository.getTokenPendingStatus(
|
|
||||||
userWallet.walletId,
|
|
||||||
cryptoCurrencyStatus,
|
|
||||||
)
|
|
||||||
|
|
||||||
val isActive = yieldSupplyStatus?.isActive == true
|
|
||||||
val isCryptoCurrencyStatusFromCache = cryptoCurrencyStatus.value.sources.networkSource != StatusSource.ACTUAL
|
val isCryptoCurrencyStatusFromCache = cryptoCurrencyStatus.value.sources.networkSource != StatusSource.ACTUAL
|
||||||
val processing = uiState.value is YieldSupplyUM.Processing
|
val processing = uiState.value is YieldSupplyUM.Processing
|
||||||
Timber.d(
|
|
||||||
"YIELD " +
|
|
||||||
"yieldSupplyStatus $yieldSupplyStatus " +
|
|
||||||
"tokenProtocolStatus $tokenProtocolStatus " +
|
|
||||||
"tokenPendingStatus $tokenPendingStatus " +
|
|
||||||
"isActive $isActive " +
|
|
||||||
"processing $processing " +
|
|
||||||
"isCryptoCurrencyStatusFromCache $isCryptoCurrencyStatusFromCache",
|
|
||||||
)
|
|
||||||
if (isCryptoCurrencyStatusFromCache && processing) {
|
if (isCryptoCurrencyStatusFromCache && processing) {
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
when {
|
val pendingStatus = yieldSupplyEnterStatusUseCase(
|
||||||
tokenProtocolStatus != null && tokenPendingStatus != null -> {
|
userWalletId = userWallet.walletId,
|
||||||
showProcessing(tokenPendingStatus)
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
lastStatusCheckTimestamp = 0L
|
).getOrNull()
|
||||||
}
|
|
||||||
tokenProtocolStatus == YieldSupplyEnterStatus.Exit && isActive ||
|
if (pendingStatus != null) {
|
||||||
tokenProtocolStatus == YieldSupplyEnterStatus.Enter && !isActive -> {
|
showProcessing(pendingStatus)
|
||||||
if (lastStatusCheckTimestamp != 0L) {
|
} else {
|
||||||
if (SystemClock.elapsedRealtime() - lastStatusCheckTimestamp > MAX_STATUS_CHECK_LIMIT) {
|
loadStatus(cryptoCurrencyStatus)
|
||||||
loadStatus(cryptoCurrencyStatus)
|
|
||||||
lastStatusCheckTimestamp = 0L
|
|
||||||
} else {
|
|
||||||
showProcessing(tokenProtocolStatus)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showProcessing(tokenProtocolStatus)
|
|
||||||
lastStatusCheckTimestamp = SystemClock.elapsedRealtime()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
loadStatus(cryptoCurrencyStatus)
|
|
||||||
lastStatusCheckTimestamp = 0L
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.saveIn(loadStatusJobHolder)
|
}.saveIn(loadStatusJobHolder)
|
||||||
|
|
||||||
private fun showProcessing(status: YieldSupplyEnterStatus) {
|
private fun showProcessing(status: YieldSupplyPendingStatus) {
|
||||||
uiState.update {
|
uiState.update {
|
||||||
when (status) {
|
when (status) {
|
||||||
YieldSupplyEnterStatus.Enter -> YieldSupplyUM.Processing.Enter
|
is YieldSupplyPendingStatus.Enter -> YieldSupplyUM.Processing.Enter
|
||||||
YieldSupplyEnterStatus.Exit -> YieldSupplyUM.Processing.Exit
|
is YieldSupplyPendingStatus.Exit -> YieldSupplyUM.Processing.Exit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchCurrencyWithDelay()
|
fetchCurrencyWithDelay()
|
||||||
|
|
@ -249,11 +206,6 @@ internal class YieldSupplyModel @Inject constructor(
|
||||||
|
|
||||||
private suspend fun loadStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
private suspend fun loadStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||||
val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
|
val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
|
||||||
yieldSupplyRepository.saveTokenProtocolStatus(
|
|
||||||
userWalletId = userWallet.walletId,
|
|
||||||
cryptoCurrency = cryptoCurrency,
|
|
||||||
yieldSupplyEnterStatus = null,
|
|
||||||
)
|
|
||||||
if (yieldSupplyStatus?.isActive == true) {
|
if (yieldSupplyStatus?.isActive == true) {
|
||||||
loadActiveState(
|
loadActiveState(
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
|
|
@ -385,6 +337,5 @@ internal class YieldSupplyModel @Inject constructor(
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val PROCESSING_UPDATE_DELAY = 10_000L
|
const val PROCESSING_UPDATE_DELAY = 10_000L
|
||||||
const val MAX_STATUS_CHECK_LIMIT = 10_000L
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ import com.tangem.domain.transaction.error.GetFeeError
|
||||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||||
import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus
|
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
|
||||||
import com.tangem.domain.yield.supply.usecase.*
|
import com.tangem.domain.yield.supply.usecase.*
|
||||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||||
import com.tangem.features.yield.supply.impl.R
|
import com.tangem.features.yield.supply.impl.R
|
||||||
|
|
@ -245,18 +245,21 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
ifRight = {
|
ifRight = { txsData ->
|
||||||
onStartEarningTransactionSuccess(yieldSupplyFeeUM)
|
onStartEarningTransactionSuccess(yieldSupplyFeeUM, txsData)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onStartEarningTransactionSuccess(yieldSupplyFeeUM: YieldSupplyFeeUM.Content) {
|
private suspend fun onStartEarningTransactionSuccess(
|
||||||
yieldSupplyRepository.saveTokenProtocolStatus(
|
yieldSupplyFeeUM: YieldSupplyFeeUM.Content,
|
||||||
userWalletId,
|
txsData: List<String>,
|
||||||
cryptoCurrency,
|
) {
|
||||||
YieldSupplyEnterStatus.Enter,
|
yieldSupplyRepository.saveTokenProtocolPendingStatus(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
cryptoCurrency = cryptoCurrency,
|
||||||
|
yieldSupplyPendingStatus = YieldSupplyPendingStatus.Enter(txsData),
|
||||||
)
|
)
|
||||||
val event = AnalyticsParam.TxSentFrom.Earning(
|
val event = AnalyticsParam.TxSentFrom.Earning(
|
||||||
blockchain = cryptoCurrency.network.name,
|
blockchain = cryptoCurrency.network.name,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||||
import com.tangem.domain.yield.supply.INCREASE_GAS_LIMIT_FOR_SUPPLY
|
import com.tangem.domain.yield.supply.INCREASE_GAS_LIMIT_FOR_SUPPLY
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||||
import com.tangem.domain.yield.supply.increaseGasLimitBy
|
import com.tangem.domain.yield.supply.increaseGasLimitBy
|
||||||
import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus
|
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase
|
||||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||||
|
|
@ -167,18 +167,18 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
|
||||||
)
|
)
|
||||||
params.callback.onTransactionProgress(false)
|
params.callback.onTransactionProgress(false)
|
||||||
},
|
},
|
||||||
ifRight = {
|
ifRight = { txData ->
|
||||||
onStopEarningTransactionSuccess()
|
onStopEarningTransactionSuccess(txData)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onStopEarningTransactionSuccess() {
|
private suspend fun onStopEarningTransactionSuccess(txId: String) {
|
||||||
yieldSupplyRepository.saveTokenProtocolStatus(
|
yieldSupplyRepository.saveTokenProtocolPendingStatus(
|
||||||
userWallet.walletId,
|
userWalletId = userWallet.walletId,
|
||||||
cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
YieldSupplyEnterStatus.Exit,
|
yieldSupplyPendingStatus = YieldSupplyPendingStatus.Exit(listOf(txId)),
|
||||||
)
|
)
|
||||||
analytics.send(
|
analytics.send(
|
||||||
YieldSupplyAnalytics.FundsWithdrawn(
|
YieldSupplyAnalytics.FundsWithdrawn(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
# https://github.com/tangem/tangem-sdk-android/
|
# https://github.com/tangem/tangem-sdk-android/
|
||||||
# https://github.com/tangem/vico
|
# https://github.com/tangem/vico
|
||||||
|
|
||||||
tangemBlockchainSdk = "develop-1365"
|
tangemBlockchainSdk = "develop-1366"
|
||||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||||
tangemCardSdk = "develop-573"
|
tangemCardSdk = "develop-573"
|
||||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ internal class WalletManagerFactoryCreator @Inject constructor(
|
||||||
accountCreator = accountCreator,
|
accountCreator = accountCreator,
|
||||||
featureToggles = BlockchainFeatureToggles(
|
featureToggles = BlockchainFeatureToggles(
|
||||||
isYieldSupplyEnabled = featureTogglesManager.isFeatureEnabled("YIELD_SUPPLY_FEATURE_ENABLED"),
|
isYieldSupplyEnabled = featureTogglesManager.isFeatureEnabled("YIELD_SUPPLY_FEATURE_ENABLED"),
|
||||||
|
isPendingTransactionsEnabled = featureTogglesManager.isFeatureEnabled(
|
||||||
|
"YIELD_SUPPLY_PENDING_TRANSACTIONS_ENABLED",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
blockchainDataStorage = blockchainDataStorage,
|
blockchainDataStorage = blockchainDataStorage,
|
||||||
loggers = listOf(blockchainSDKLogger),
|
loggers = listOf(blockchainSDKLogger),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue