Updated on 2026-08-14
This commit is contained in:
commit
9f68fc1511
19 changed files with 277 additions and 77 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 27ddebaece01b8e13a9bbafd51f5ff1d64efc2a6
|
||||
Subproject commit 7c568701ed57a31b0f1086820435dfc5761714c1
|
||||
|
|
@ -12,10 +12,13 @@ import com.tangem.domain.transaction.TransactionRepository
|
|||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
|
|
@ -46,6 +49,7 @@ internal object TransactionDomainModule {
|
|||
walletManagersFacade: WalletManagersFacade,
|
||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): SendTransactionUseCase {
|
||||
return SendTransactionUseCase(
|
||||
demoConfig = DemoConfig,
|
||||
|
|
@ -53,6 +57,7 @@ internal object TransactionDomainModule {
|
|||
transactionRepository = transactionRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.io),
|
||||
getHotWalletSigner = tangemHotWalletSignerFactory::create,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,4 +175,13 @@ interface TangemTechApi {
|
|||
@Header("Cache-Control") cacheControl: String = "max-age=600",
|
||||
): ApiResponse<PromoBannerResponse>
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* Stores transaction hash in cache to prevent duplicate push
|
||||
* notifications for yield operations (deposit, withdraw, send).
|
||||
* Used when yield operations generate intermediate transactions
|
||||
* that should not trigger notifications.
|
||||
*/
|
||||
@POST("v1/transaction-events")
|
||||
suspend fun transactionEvents(@Body name: TransactionEventBody): ApiResponse<Unit>
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TransactionEventBody(
|
||||
@Json(name = "transactionId") val transactionId: String,
|
||||
@Json(name = "operationType") val operationType: OperationType,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class OperationType {
|
||||
|
||||
@Json(name = "YIELD_DEPOSIT")
|
||||
YIELD_DEPOSIT,
|
||||
|
||||
@Json(name = "YIELD_WITHDRAW")
|
||||
YIELD_WITHDRAW,
|
||||
|
||||
@Json(name = "YIELD_SEND")
|
||||
YIELD_SEND,
|
||||
}
|
||||
|
|
@ -20,13 +20,19 @@ import com.tangem.blockchain.common.transaction.Fee
|
|||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.datasource.api.common.response.fold
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.OperationType
|
||||
import com.tangem.datasource.api.tangemTech.models.TransactionEventBody
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.models.EventTransactionTypeDto
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -34,6 +40,7 @@ import java.math.BigInteger
|
|||
|
||||
@Suppress("LargeClass")
|
||||
internal class DefaultTransactionRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val walletManagersStore: WalletManagersStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -415,6 +422,31 @@ internal class DefaultTransactionRepository(
|
|||
preparer.prepareAndSignMultiple(transactionData, signer)
|
||||
}
|
||||
|
||||
override suspend fun sendTransactionHash(hash: String, transactionType: EventTransactionTypeDto) {
|
||||
runCatching(dispatchers.io) {
|
||||
val operationType = when (transactionType) {
|
||||
EventTransactionTypeDto.DEPOSIT -> OperationType.YIELD_DEPOSIT
|
||||
EventTransactionTypeDto.WITHDRAW -> OperationType.YIELD_WITHDRAW
|
||||
EventTransactionTypeDto.SEND -> OperationType.YIELD_SEND
|
||||
}
|
||||
val body = TransactionEventBody(
|
||||
operationType = operationType,
|
||||
transactionId = hash,
|
||||
)
|
||||
val response = tangemTechApi.transactionEvents(body)
|
||||
response.fold(
|
||||
onSuccess = {
|
||||
Timber.d("Successfully sent yield supply transaction hash: $hash")
|
||||
},
|
||||
onError = { error ->
|
||||
Timber.e(error, "Failed to send yield supply transaction hash: $hash")
|
||||
},
|
||||
)
|
||||
}.onFailure { error ->
|
||||
Timber.e(error, "Failed to send yield supply transaction hash: $hash")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getPreparer(network: Network, userWalletId: UserWalletId): TransactionPreparer {
|
||||
val blockchain = network.toBlockchain()
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.data.transaction.DefaultFeeRepository
|
|||
import com.tangem.data.transaction.DefaultTransactionRepository
|
||||
import com.tangem.data.transaction.DefaultWalletAddressServiceRepository
|
||||
import com.tangem.data.transaction.error.DefaultFeeErrorResolver
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
|
|
@ -25,11 +26,13 @@ internal object TransactionDataModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesTransactionRepository(
|
||||
tangemTechApi: TangemTechApi,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
walletManagersStore: WalletManagersStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): TransactionRepository {
|
||||
return DefaultTransactionRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
walletManagersStore = walletManagersStore,
|
||||
dispatchers = dispatchers,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ dependencies {
|
|||
/** Core */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.analytics)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.yieldSupply)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import com.tangem.blockchain.yieldsupply.YieldSupplyProvider
|
|||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.data.yield.supply.converters.YieldMarketTokenConverter
|
||||
import com.tangem.data.yield.supply.converters.YieldTokenChartConverter
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
|
|
@ -32,6 +34,7 @@ internal class DefaultYieldSupplyRepository(
|
|||
private val store: YieldMarketsStore,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
) : YieldSupplyRepository {
|
||||
|
||||
private val statusMap: MutableMap<String, YieldSupplyEnterStatus> = ConcurrentHashMap()
|
||||
|
|
@ -74,7 +77,15 @@ internal class DefaultYieldSupplyRepository(
|
|||
userWalletId = userWalletId,
|
||||
blockchain = cryptoCurrency.network.toBlockchain(),
|
||||
derivationPath = cryptoCurrency.network.derivationPath.value,
|
||||
) ?: error("Wallet manager not found")
|
||||
)
|
||||
if (walletManager == null) {
|
||||
analyticsExceptionHandler.sendException(
|
||||
ExceptionAnalyticsEvent(
|
||||
exception = IllegalStateException("Wallet manager not found"),
|
||||
),
|
||||
)
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
(walletManager as? YieldSupplyProvider)?.isSupported() ?: false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.data.yield.supply.di
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyRepository
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyErrorResolver
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyTransactionRepository
|
||||
|
|
@ -39,12 +40,14 @@ internal object YieldSupplyDataModule {
|
|||
store: YieldMarketsStore,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
): YieldSupplyRepository {
|
||||
return DefaultYieldSupplyRepository(
|
||||
yieldSupplyApi = yieldSupplyApi,
|
||||
store = store,
|
||||
dispatchers = dispatchers,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
analyticsExceptionHandler = analyticsExceptionHandler,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,33 @@
|
|||
package com.tangem.domain.models.currency
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
fun CryptoCurrency.Token.yieldSupplyKey(): String {
|
||||
return "${network.backendId}_$contractAddress"
|
||||
}
|
||||
|
||||
fun CryptoCurrencyStatus.yieldSupplyNotAllAmountSupplied(): Boolean {
|
||||
if (this.currency !is CryptoCurrency.Token) return false
|
||||
fun CryptoCurrencyStatus.hasNotSuppliedAmount(): Boolean {
|
||||
val notSupplied = notSuppliedAmountOrNull() ?: return false
|
||||
return notSupplied > BigDecimal.ZERO
|
||||
}
|
||||
|
||||
fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount: BigDecimal): Boolean {
|
||||
val notSupplied = notSuppliedAmountOrNull() ?: return false
|
||||
return notSupplied >= minAmount
|
||||
}
|
||||
|
||||
fun CryptoCurrencyStatus.notSuppliedAmountOrNull(): BigDecimal? {
|
||||
if (this.currency !is CryptoCurrency.Token) return null
|
||||
|
||||
val supplyStatus = this.value.yieldSupplyStatus
|
||||
if (supplyStatus?.isActive != true) return false
|
||||
if (supplyStatus?.isActive != true) return null
|
||||
|
||||
val protocolBalance = supplyStatus.effectiveProtocolBalance
|
||||
val amount = this.value.amount
|
||||
|
||||
return if (protocolBalance != null && amount != null) {
|
||||
amount > protocolBalance
|
||||
amount.minus(protocolBalance)
|
||||
} else {
|
||||
false
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.transaction.models
|
||||
|
||||
/**
|
||||
* DTO representing the type of a transaction event on tangem backend to send
|
||||
* info about transaction happens and its hash
|
||||
*/
|
||||
enum class EventTransactionTypeDto {
|
||||
DEPOSIT,
|
||||
WITHDRAW,
|
||||
SEND,
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import com.tangem.blockchain.nft.models.NFTAsset
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.transaction.models.EventTransactionTypeDto
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
|
|
@ -123,4 +124,6 @@ interface TransactionRepository {
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): com.tangem.blockchain.extensions.Result<List<ByteArray>>
|
||||
|
||||
suspend fun sendTransactionHash(hash: String, transactionType: EventTransactionTypeDto)
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.domain.transaction.usecase
|
|||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionSender
|
||||
|
|
@ -10,28 +11,38 @@ import com.tangem.blockchain.common.TransactionSigner
|
|||
import com.tangem.blockchain.common.transaction.TransactionsSendResult
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.network.ResultChecker
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyExitCallData
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplySendCallData
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.simple
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.card.models.TwinKey
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.demo.DemoTransactionSender
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
import com.tangem.domain.transaction.error.parseWrappedError
|
||||
import com.tangem.domain.transaction.models.EventTransactionTypeDto
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class SendTransactionUseCase(
|
||||
private val demoConfig: DemoConfig,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val transactionRepository: TransactionRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
private val parallelUpdatingScope: CoroutineScope,
|
||||
private val getHotWalletSigner: (UserWallet.Hot) -> TransactionSigner,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
|
|
@ -100,7 +111,10 @@ class SendTransactionUseCase(
|
|||
)
|
||||
}
|
||||
.fold(
|
||||
ifRight = { result -> result.hashes.right() },
|
||||
ifRight = { result ->
|
||||
processSentTransactionsHashes(txsData, result.hashes)
|
||||
result.hashes.right()
|
||||
},
|
||||
ifLeft = { it.left() },
|
||||
)
|
||||
}
|
||||
|
|
@ -114,6 +128,36 @@ class SendTransactionUseCase(
|
|||
.map { it.first() }
|
||||
}
|
||||
|
||||
private fun processSentTransactionsHashes(transactions: List<TransactionData>, hashes: List<String>) {
|
||||
parallelUpdatingScope.launch {
|
||||
withContext(NonCancellable) {
|
||||
transactions.forEachIndexed { ind, tx ->
|
||||
sendHashToBackendIfNeeded(tx, hashes[ind])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends tx hash to backend for specific transaction types
|
||||
*/
|
||||
private suspend fun sendHashToBackendIfNeeded(transaction: TransactionData, txHash: String) {
|
||||
(transaction as? TransactionData.Uncompiled)?.let { tx ->
|
||||
val extras = tx.extras
|
||||
when (extras) {
|
||||
is EthereumTransactionExtras -> {
|
||||
val txType = when (extras.callData) {
|
||||
is EthereumYieldSupplyEnterCallData -> EventTransactionTypeDto.DEPOSIT
|
||||
is EthereumYieldSupplySendCallData -> EventTransactionTypeDto.SEND
|
||||
is EthereumYieldSupplyExitCallData -> EventTransactionTypeDto.WITHDRAW
|
||||
else -> return
|
||||
}
|
||||
transactionRepository.sendTransactionHash(txHash, txType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendDemo(
|
||||
userWallet: UserWallet,
|
||||
network: Network,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
|||
import com.tangem.domain.models.StatusSource
|
||||
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.currency.hasNotSuppliedAmount
|
||||
import com.tangem.domain.models.currency.shouldShowNotSuppliedInfoIcon
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
|
|
@ -29,6 +30,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase
|
|||
import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyIsAvailableUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyComponent
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
|
|
@ -64,6 +66,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
|
||||
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
|
||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
|
||||
) : Model(), YieldSupplyClickIntents {
|
||||
|
||||
private val params = paramsContainer.require<YieldSupplyComponent.Params>()
|
||||
|
|
@ -303,7 +306,11 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
private fun loadActiveState(cryptoCurrencyStatus: CryptoCurrencyStatus, yieldSupplyStatus: YieldSupplyStatus) {
|
||||
val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
|
||||
val showWarningIcon = !yieldSupplyStatus.isAllowedToSpend
|
||||
val showInfoIcon = cryptoCurrencyStatus.yieldSupplyNotAllAmountSupplied()
|
||||
val state = uiState.value
|
||||
val isShowInfoIconPrevState = when (state) {
|
||||
is YieldSupplyUM.Content -> state.showInfoIcon
|
||||
else -> false
|
||||
}
|
||||
if (!yieldSupplyStatus.isAllowedToSpend) {
|
||||
analyticsEventsHandler.send(
|
||||
YieldSupplyAnalytics.NoticeApproveNeeded(
|
||||
|
|
@ -331,12 +338,13 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
),
|
||||
onClick = ::onActiveClick,
|
||||
showWarningIcon = showWarningIcon,
|
||||
showInfoIcon = showInfoIcon,
|
||||
showInfoIcon = isShowInfoIconPrevState,
|
||||
apy = tokenStatus.apy.toString(),
|
||||
)
|
||||
}
|
||||
}.onLeft {
|
||||
Timber.e(it)
|
||||
computeAndApplyShowInfoIcon(cryptoCurrencyStatus)
|
||||
}.onLeft { t ->
|
||||
Timber.e(t)
|
||||
uiState.update {
|
||||
YieldSupplyUM.Content(
|
||||
title = resourceReference(
|
||||
|
|
@ -348,14 +356,36 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
rewardsApy = TextReference.EMPTY,
|
||||
onClick = ::onActiveClick,
|
||||
showWarningIcon = showWarningIcon,
|
||||
showInfoIcon = showInfoIcon,
|
||||
showInfoIcon = isShowInfoIconPrevState,
|
||||
apy = "",
|
||||
)
|
||||
}
|
||||
computeAndApplyShowInfoIcon(cryptoCurrencyStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeAndApplyShowInfoIcon(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
val isShowInfoIcon = if (cryptoCurrencyStatus.hasNotSuppliedAmount()) {
|
||||
val minAmount = yieldSupplyMinAmountUseCase(userWallet, cryptoCurrencyStatus).getOrNull()
|
||||
if (minAmount != null) {
|
||||
cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
uiState.update { state ->
|
||||
when (state) {
|
||||
is YieldSupplyUM.Content -> state.copy(showInfoIcon = isShowInfoIcon)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendInfoAboutProtocolStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
if (lastYieldSupplyStatus == cryptoCurrencyStatus.value.yieldSupplyStatus) return
|
||||
val token = cryptoCurrency as? CryptoCurrency.Token ?: return
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, modifier: Modifier =
|
|||
Text(
|
||||
modifier = Modifier.weight(1.0f, fill = false),
|
||||
text = supplyUM.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
|
|
@ -119,12 +119,12 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, modifier: Modifier =
|
|||
) {
|
||||
Text(
|
||||
text = StringsSigns.DOT,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
Text(
|
||||
text = supplyUM.rewardsApy.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
maxLines = 1,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ private fun ColumnScope.Content(yieldSupplyPromoUM: YieldSupplyPromoUM, clickInt
|
|||
style = LabelStyle.REGULAR,
|
||||
icon = R.drawable.ic_information_24,
|
||||
onClick = clickIntents::onApyInfoClick,
|
||||
onIconClick = clickIntents::onApyInfoClick,
|
||||
),
|
||||
)
|
||||
SpacerH32()
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
package com.tangem.features.yield.supply.impl.subcomponents.active.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
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.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -16,7 +14,6 @@ import com.tangem.core.ui.format.bigdecimal.format
|
|||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetProtocolBalanceUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase
|
||||
|
|
@ -31,13 +28,10 @@ import com.tangem.features.yield.supply.impl.subcomponents.active.model.transfor
|
|||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -68,7 +62,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
providerTitle = resourceReference(R.string.yield_module_provider),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.yield_module_earn_sheet_provider_description,
|
||||
formatArgs = wrappedList(cryptoCurrency.symbol, cryptoCurrency.symbol),
|
||||
formatArgs = wrappedList(cryptoCurrency.symbol, AAVEV3_PREFIX + cryptoCurrency.symbol),
|
||||
),
|
||||
subtitleLink = resourceReference(R.string.common_read_more),
|
||||
notifications = persistentListOf(),
|
||||
|
|
@ -121,7 +115,6 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
|
||||
uiState.update {
|
||||
it.copy(
|
||||
notifications = getNotifications(cryptoCurrencyStatus),
|
||||
availableBalance = stringReference(
|
||||
protocolBalance.format {
|
||||
crypto(
|
||||
|
|
@ -156,54 +149,6 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getNotifications(cryptoCurrencyStatus: CryptoCurrencyStatus): ImmutableList<NotificationUM> {
|
||||
val approvalNotification = if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isAllowedToSpend != true) {
|
||||
NotificationUM.Error(
|
||||
title = resourceReference(R.string.yield_module_approve_needed_notification_title),
|
||||
subtitle = resourceReference(R.string.yield_module_approve_needed_notification_description),
|
||||
iconResId = R.drawable.ic_alert_triangle_20,
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.yield_module_approve_needed_notification_cta),
|
||||
onClick = params.callback::onApprove,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val notSuppliedNotification = getNotSuppliedNotification(cryptoCurrencyStatus)
|
||||
return listOfNotNull(
|
||||
approvalNotification,
|
||||
notSuppliedNotification,
|
||||
).toPersistentList()
|
||||
}
|
||||
|
||||
private fun getNotSuppliedNotification(cryptoCurrencyStatus: CryptoCurrencyStatus): NotificationUM? {
|
||||
val value = cryptoCurrencyStatus.value
|
||||
val isActive = value.yieldSupplyStatus?.isActive == true
|
||||
val effectiveProtocolBalance = value.yieldSupplyStatus?.effectiveProtocolBalance ?: null
|
||||
val amount = value.amount
|
||||
|
||||
if (!isActive || effectiveProtocolBalance == null || amount == null) return null
|
||||
|
||||
val notDepositedAmount = amount.minus(effectiveProtocolBalance)
|
||||
return if (notDepositedAmount > BigDecimal.ZERO) {
|
||||
val formattedAmount =
|
||||
notDepositedAmount.format { crypto(symbol = "", decimals = cryptoCurrencyStatus.currency.decimals) }
|
||||
analyticsHandler.send(
|
||||
YieldSupplyAnalytics.NoticeAmountNotDeposited(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
NotificationUM.Info.YieldSupplyNotAllAmountSupplied(
|
||||
formattedAmount = formattedAmount,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadMinAmount() {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
yieldSupplyMinAmountUseCase(
|
||||
|
|
@ -215,6 +160,8 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value,
|
||||
appCurrency = appCurrency,
|
||||
minAmount = minAmount,
|
||||
analyticsHandler = analyticsHandler,
|
||||
onApprove = params.callback::onApprove,
|
||||
),
|
||||
)
|
||||
}.onLeft {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.features.yield.supply.impl.subcomponents.active.model.transformers
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
|
|
@ -8,18 +11,37 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.notSuppliedAmountOrNull
|
||||
import com.tangem.domain.models.currency.shouldShowNotSuppliedInfoIcon
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSupplyActiveContentUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Computes and sets minimum amount (fiat) and fee description note
|
||||
* Transformer that populates minimum supply amount and related hints for the active Yield Supply screen.
|
||||
*
|
||||
* - Sets the displayed minimum amount in fiat and crypto.
|
||||
* - Builds the fee policy note text using the minimum amount.
|
||||
* - Adds contextual notifications:
|
||||
* - Approval required notification when spending is not yet allowed (emits analytics on CTA).
|
||||
* - "Not all amount supplied" info when wallet balance exceeds the supplied balance by more than [minAmount].
|
||||
*
|
||||
* @property cryptoCurrencyStatus Current currency status used to calculate values and flags.
|
||||
* @property appCurrency Preferred fiat currency for formatting.
|
||||
* @property minAmount Protocol-required minimal amount to deposit/supply (in crypto units).
|
||||
* @property analyticsHandler Analytics reporter for user actions.
|
||||
* @property onApprove Action invoked when the "Approve" notification button is tapped.
|
||||
*/
|
||||
internal class YieldSupplyActiveMinAmountTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val minAmount: BigDecimal,
|
||||
private val analyticsHandler: AnalyticsEventHandler,
|
||||
private val onApprove: () -> Unit,
|
||||
) : Transformer<YieldSupplyActiveContentUM> {
|
||||
|
||||
override fun transform(prevState: YieldSupplyActiveContentUM): YieldSupplyActiveContentUM {
|
||||
|
|
@ -41,6 +63,49 @@ internal class YieldSupplyActiveMinAmountTransformer(
|
|||
return prevState.copy(
|
||||
minAmount = stringReference(minAmountFiatText),
|
||||
minFeeDescription = minFeeNoteValue,
|
||||
notifications = getNotifications(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNotifications(): ImmutableList<NotificationUM> {
|
||||
val approvalNotification = if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isAllowedToSpend != true) {
|
||||
NotificationUM.Error(
|
||||
title = resourceReference(R.string.yield_module_approve_needed_notification_title),
|
||||
subtitle = resourceReference(R.string.yield_module_approve_needed_notification_description),
|
||||
iconResId = R.drawable.ic_alert_triangle_20,
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.yield_module_approve_needed_notification_cta),
|
||||
onClick = onApprove,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val notSuppliedNotification = getNotSuppliedNotification(cryptoCurrencyStatus)
|
||||
return listOfNotNull(
|
||||
approvalNotification,
|
||||
notSuppliedNotification,
|
||||
).toPersistentList()
|
||||
}
|
||||
|
||||
private fun getNotSuppliedNotification(cryptoCurrencyStatus: CryptoCurrencyStatus): NotificationUM? {
|
||||
return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)) {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val notDepositedAmount = cryptoCurrencyStatus.notSuppliedAmountOrNull()
|
||||
val formattedAmount =
|
||||
notDepositedAmount.format { crypto(symbol = "", decimals = cryptoCurrencyStatus.currency.decimals) }
|
||||
analyticsHandler.send(
|
||||
YieldSupplyAnalytics.NoticeAmountNotDeposited(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
NotificationUM.Info.YieldSupplyNotAllAmountSupplied(
|
||||
formattedAmount = formattedAmount,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
# https://github.com/tangem/tangem-sdk-android/
|
||||
# https://github.com/tangem/vico
|
||||
|
||||
tangemBlockchainSdk = "develop-1308"
|
||||
tangemBlockchainSdk = "develop-1309"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-564"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue