diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt index 532c2f1621..4937c3c0af 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt @@ -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 } } \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt index a6ab368dc5..186a7c5bdc 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt @@ -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() @@ -303,7 +306,10 @@ 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 showInfoIconPrevState = when (uiState) { + is YieldSupplyUM.Content -> uiState.showInfoIcon + else -> false + } if (!yieldSupplyStatus.isAllowedToSpend) { analyticsEventsHandler.send( YieldSupplyAnalytics.NoticeApproveNeeded( @@ -331,10 +337,11 @@ internal class YieldSupplyModel @Inject constructor( ), onClick = ::onActiveClick, showWarningIcon = showWarningIcon, - showInfoIcon = showInfoIcon, + showInfoIcon = showInfoIconPrevState, apy = tokenStatus.apy.toString(), ) } + computeAndApplyShowInfoIcon(cryptoCurrencyStatus) }.onLeft { Timber.e(it) uiState.update { @@ -348,14 +355,36 @@ internal class YieldSupplyModel @Inject constructor( rewardsApy = TextReference.EMPTY, onClick = ::onActiveClick, showWarningIcon = showWarningIcon, - showInfoIcon = showInfoIcon, + showInfoIcon = showInfoIconPrevState, apy = "", ) } + computeAndApplyShowInfoIcon(cryptoCurrencyStatus) } } } + private fun computeAndApplyShowInfoIcon(cryptoCurrencyStatus: CryptoCurrencyStatus) { + modelScope.launch(dispatchers.default) { + val showInfoIcon = 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 = showInfoIcon) + else -> state + } + } + } + } + private fun sendInfoAboutProtocolStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) { if (lastYieldSupplyStatus == cryptoCurrencyStatus.value.yieldSupplyStatus) return val token = cryptoCurrency as? CryptoCurrency.Token ?: return diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt index 414cd7a29d..094fa330fa 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt @@ -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") @@ -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 { - 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 { diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt index 91fa089b91..1bea08e058 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt @@ -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 { 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 { + 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 + } + } } \ No newline at end of file