diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt index 9d21d90396..7f0264f917 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt @@ -76,4 +76,6 @@ internal interface StakingClickIntents : AmountScreenClickIntents { fun onActivateTonAccountNotificationShow() fun onActivateTonAccountClick() + + fun onAmountReduceByFeeClick(reduceAmount: BigDecimal, notification: Class) } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index c7556c4f23..30806bf543 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -6,6 +6,7 @@ import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.routing.AppRouter import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer +import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.common.ui.bottomsheet.permission.state.ApproveType @@ -1081,6 +1082,17 @@ internal class StakingModel @Inject constructor( } } + override fun onAmountReduceByFeeClick(reduceAmount: BigDecimal, notification: Class) { + stateController.update( + AmountReduceByStateTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + value = ReduceByData(reduceAmount, reduceAmount), + minimumTransactionAmount = null, + ), + ) + onNotificationCancel(notification) + } + private suspend fun setupApprovalNeeded() { val approval = StakingIntegrationID.create(currencyId = cryptoCurrencyStatus.currency.id)?.approval ?: StakingApproval.Empty diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt index b5485b47ba..f1b90ef243 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt @@ -89,9 +89,20 @@ internal object StakingNotification { subtitle = subtitleText, ) - data object StakeEntireBalance : Info( + data class StakeEntireBalance( + private val reduceAmountValue: TextReference?, + private val onReduceClick: () -> Unit, + ) : Info( title = resourceReference(R.string.common_network_fee_title), subtitle = resourceReference(R.string.staking_notification_stake_entire_balance_text), + buttonsState = if (reduceAmountValue != null) { + NotificationConfig.ButtonsState.PrimaryButtonConfig( + text = reduceAmountValue, + onClick = onReduceClick, + ) + } else { + null + }, ) data class Unstake( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index 5b91544cd2..1584adb3a1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -84,4 +84,6 @@ internal object StakingClickIntentsStub : StakingClickIntents { override fun onActivateTonAccountNotificationShow() {} override fun onActivateTonAccountClick() {} + + override fun onAmountReduceByFeeClick(reduceAmount: BigDecimal, notification: Class) {} } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt index 9912043b5d..636649a4ad 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt @@ -33,6 +33,7 @@ import com.tangem.lib.crypto.BlockchainUtils.isTon import com.tangem.utils.Provider import com.tangem.utils.extensions.orZero import com.tangem.utils.transformer.Transformer +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal @@ -56,29 +57,44 @@ internal class AddStakingNotificationsTransformer( isSubtractAvailable = isSubtractAvailable, ) - @Suppress("LongMethod") override fun transform(prevState: StakingUiState): StakingUiState { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val balance = cryptoCurrencyStatus.value.amount.orZero() - val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState val amountState = prevState.amountState as? AmountState.Data ?: return prevState - val feeState = confirmationState.feeState as? FeeState.Content - val amountValue = amountState.amountTextField.cryptoAmount.value.orZero() - val feeValue = feeState?.fee?.amount?.value.orZero() - val reduceAmountBy = confirmationState.reduceAmountBy.orZero() - - val isEnterAction = prevState.actionType is StakingActionCommonType.Enter - val isFeeCoverage = checkFeeCoverage( - amountValue = amountValue, - feeValue = feeValue, - balance = balance, - isSubtractAvailable = isSubtractAvailable, - reduceAmountBy = reduceAmountBy, + val sendingAmount = calculateSendingAmount( + prevState = prevState, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amountState = amountState, + confirmationState = confirmationState, ) - val minimumRequirement = integration.enterMinimumAmount.orZero() - val sendingAmount = if (isEnterAction) { + val notifications = buildNotifications( + prevState = prevState, + amountState = amountState, + confirmationState = confirmationState, + sendingAmount = sendingAmount, + ) + val isActualSources = areSourcesActual(cryptoCurrencyStatus) + + return prevState.copy( + confirmationState = confirmationState.copy( + notifications = notifications, + isPrimaryButtonEnabled = isPrimaryButtonEnabled(notifications, isActualSources), + ), + ) + } + + private fun calculateSendingAmount( + prevState: StakingUiState, + cryptoCurrencyStatus: CryptoCurrencyStatus, + amountState: AmountState.Data, + confirmationState: StakingStates.ConfirmationState.Data, + ): BigDecimal { + val isEnterAction = prevState.actionType is StakingActionCommonType.Enter + return if (isEnterAction) { + val amountValue = amountState.amountTextField.cryptoAmount.value.orZero() + val feeValue = (confirmationState.feeState as? FeeState.Content)?.fee?.amount?.value.orZero() + val reduceAmountBy = confirmationState.reduceAmountBy.orZero() checkAndCalculateSubtractedAmount( isAmountSubtractAvailable = isSubtractAvailable, cryptoCurrencyStatus = cryptoCurrencyStatus, @@ -87,63 +103,89 @@ internal class AddStakingNotificationsTransformer( reduceAmountBy = reduceAmountBy, ) } else { - // No amount is taken from account balance on exit or pending actions BigDecimal.ZERO } - - val notifications = if (isAccountInitializedProvider.invoke()) { - buildList { - // errors - addErrorNotifications( - prevState = prevState, - feeError = feeError, - sendingAmount = sendingAmount, - onReload = prevState.clickIntents::getFee, - feeValue = feeValue, - ) - addStakingErrorNotifications(stakingError = stakingError, onReload = prevState.clickIntents::getFee) - // warnings - addWarningNotifications( - prevState = prevState, - amountState = amountState, - feeState = feeState, - sendingAmount = sendingAmount, - isFeeCoverage = isFeeCoverage && isEnterAction && !sendingAmount.equals(minimumRequirement), - ) - - stakingInfoNotificationsFactory.addInfoNotifications( - notifications = this, - prevState = prevState, - sendingAmount = sendingAmount, - actionAmount = amountValue, - feeValue = feeValue, - tonBalanceExtraFeeThreshold = TON_BALANCE_EXTRA_FEE_THRESHOLD, - ) - }.toImmutableList() - } else { - buildList { - addTonInitializeAccountNotification(prevState) - }.toImmutableList() - } - - val isActualSources = with(cryptoCurrencyStatus.value) { - sources.stakingBalanceSource.isActual() && sources.networkSource.isActual() - } - - return prevState.copy( - confirmationState = confirmationState.copy( - notifications = notifications.toImmutableList(), - isPrimaryButtonEnabled = notifications.none { - it is StakingNotification.Error || - it is NotificationUM.Error || - it is NotificationUM.Warning.NetworkFeeUnreachable || - it is StakingNotification.Warning.TransactionInProgress || - it is StakingNotification.Warning.InitializeTonAccount - } && isActualSources, - ), - ) } + private fun buildNotifications( + prevState: StakingUiState, + amountState: AmountState.Data, + confirmationState: StakingStates.ConfirmationState.Data, + sendingAmount: BigDecimal, + ) = if (isAccountInitializedProvider.invoke()) { + buildInitializedAccountNotifications( + prevState = prevState, + amountState = amountState, + confirmationState = confirmationState, + sendingAmount = sendingAmount, + ) + } else { + buildList { addTonInitializeAccountNotification(prevState) }.toImmutableList() + } + + private fun buildInitializedAccountNotifications( + prevState: StakingUiState, + amountState: AmountState.Data, + confirmationState: StakingStates.ConfirmationState.Data, + sendingAmount: BigDecimal, + ) = buildList { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val balance = cryptoCurrencyStatus.value.amount.orZero() + val feeState = confirmationState.feeState as? FeeState.Content + val amountValue = amountState.amountTextField.cryptoAmount.value.orZero() + val feeValue = feeState?.fee?.amount?.value.orZero() + val reduceAmountBy = confirmationState.reduceAmountBy.orZero() + val isEnterAction = prevState.actionType is StakingActionCommonType.Enter + val minimumRequirement = integration.enterMinimumAmount.orZero() + + val isFeeCoverage = checkFeeCoverage( + amountValue = amountValue, + feeValue = feeValue, + balance = balance, + isSubtractAvailable = isSubtractAvailable, + reduceAmountBy = reduceAmountBy, + ) + + addErrorNotifications( + prevState = prevState, + feeError = feeError, + sendingAmount = sendingAmount, + onReload = prevState.clickIntents::getFee, + feeValue = feeValue, + ) + addStakingErrorNotifications(stakingError = stakingError, onReload = prevState.clickIntents::getFee) + addWarningNotifications( + prevState = prevState, + amountState = amountState, + feeState = feeState, + sendingAmount = sendingAmount, + isFeeCoverage = isFeeCoverage && isEnterAction && !sendingAmount.equals(minimumRequirement), + ) + stakingInfoNotificationsFactory.addInfoNotifications( + notifications = this, + prevState = prevState, + sendingAmount = sendingAmount, + actionAmount = amountValue, + feeAmount = feeState?.fee?.amount, + isFeeApproximate = feeState?.isFeeApproximate == true, + onAmountReduceByFeeClick = prevState.clickIntents::onAmountReduceByFeeClick, + tonBalanceExtraFeeThreshold = TON_BALANCE_EXTRA_FEE_THRESHOLD, + ) + }.toImmutableList() + + private fun areSourcesActual(cryptoCurrencyStatus: CryptoCurrencyStatus) = with(cryptoCurrencyStatus.value) { + sources.stakingBalanceSource.isActual() && sources.networkSource.isActual() + } + + private fun isPrimaryButtonEnabled(notifications: ImmutableList, isActualSources: Boolean) = + notifications.none { + it is StakingNotification.Error || + it is NotificationUM.Error || + it is NotificationUM.Warning.NetworkFeeUnreachable || + it is StakingNotification.Warning.TransactionInProgress || + it is StakingNotification.Warning.InitializeTonAccount + } && isActualSources + private fun MutableList.addStakingErrorNotifications( stakingError: StakingError?, onReload: () -> Unit, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/DismissStakingNotificationsStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/DismissStakingNotificationsStateTransformer.kt index ba9f953c0d..7c05c0ad2e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/DismissStakingNotificationsStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/DismissStakingNotificationsStateTransformer.kt @@ -13,7 +13,7 @@ internal class DismissStakingNotificationsStateTransformer( override fun transform(prevState: StakingUiState): StakingUiState { val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data val updatedNotifications = confirmationState?.notifications - ?.filterNot { it::class == notification }?.toPersistentList() + ?.filterNot { it::class.java == notification }?.toPersistentList() ?: persistentListOf() return prevState.copy( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt index 905ca35ddb..908f550629 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt @@ -1,9 +1,13 @@ package com.tangem.features.staking.impl.presentation.state.transformers.notifications +import com.tangem.blockchain.common.Amount import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.extensions.pluralReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fee +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.staking.BalanceType import com.tangem.domain.models.staking.StakingBalance @@ -35,7 +39,10 @@ internal class StakingInfoNotificationsFactory( * @param prevState current screen state to update * @param sendingAmount amount being transferred from user account * @param actionAmount any amount being transferred or used action - * @param feeValue fee amount payed from user account + * @param feeAmount fee amount payed from user account + * @param tonBalanceExtraFeeThreshold threshold for TON extra fee notification + * @param isFeeApproximate whether fee is approximate + * @param onAmountReduceByFeeClick callback to reduce amount by fee */ @Suppress("LongParameterList") fun addInfoNotifications( @@ -43,17 +50,24 @@ internal class StakingInfoNotificationsFactory( prevState: StakingUiState, sendingAmount: BigDecimal, actionAmount: BigDecimal, - feeValue: BigDecimal, + feeAmount: Amount?, tonBalanceExtraFeeThreshold: BigDecimal, + isFeeApproximate: Boolean, + onAmountReduceByFeeClick: (BigDecimal, notification: Class) -> Unit, ) = with(notifications) { addStakingLowBalanceNotification(prevState, actionAmount) addTonExtraFeeInfoNotification(tonBalanceExtraFeeThreshold) when (prevState.actionType) { - is StakingActionCommonType.Enter -> addEnterInfoNotifications(sendingAmount, feeValue) + is StakingActionCommonType.Enter -> addEnterInfoNotifications( + sendingAmount = sendingAmount, + feeAmount = feeAmount, + isFeeApproximate = isFeeApproximate, + onAmountReduceByFeeClick = onAmountReduceByFeeClick, + ) is StakingActionCommonType.Exit -> addExitInfoNotifications(prevState) is StakingActionCommonType.Pending -> { - addCardanoRestakeMinimumAmountNotification(feeValue) + addCardanoRestakeMinimumAmountNotification(feeAmount?.value.orZero()) addPendingInfoNotifications(prevState) addTonHaveToUnstakeAllNotification(prevState) } @@ -67,12 +81,19 @@ internal class StakingInfoNotificationsFactory( private fun MutableList.addEnterInfoNotifications( sendingAmount: BigDecimal, - feeValue: BigDecimal, + feeAmount: Amount?, + isFeeApproximate: Boolean, + onAmountReduceByFeeClick: (BigDecimal, notification: Class) -> Unit, ) { - addCardanoStakeMinimumAmountNotification(feeValue) + addCardanoStakeMinimumAmountNotification(feeAmount?.value.orZero()) addTronRevoteNotification() addCardanoStakeNotification() - addStakingEntireBalanceNotification(sendingAmount, feeValue) + addStakingEntireBalanceNotification( + sendingAmount = sendingAmount, + feeAmount = feeAmount, + isFeeApproximate = isFeeApproximate, + onAmountReduceByFeeClick = onAmountReduceByFeeClick, + ) } private fun MutableList.addPendingInfoNotifications(prevState: StakingUiState) { @@ -194,15 +215,46 @@ internal class StakingInfoNotificationsFactory( private fun MutableList.addStakingEntireBalanceNotification( sendingAmount: BigDecimal, - feeValue: BigDecimal, + feeAmount: Amount?, + isFeeApproximate: Boolean, + onAmountReduceByFeeClick: (BigDecimal, notification: Class) -> Unit, ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val balance = cryptoCurrencyStatus.value.amount.orZero() + val feeValue = feeAmount?.value.orZero() val isEntireBalance = sendingAmount.plus(feeValue) == balance if (isEntireBalance && isSubtractAvailable && !isCardano(cryptoCurrencyStatus.currency.network.rawId)) { - add(StakingNotification.Info.StakeEntireBalance) + val value = feeValue.multiply(FEE_DECIMALS_MULTIPLIER) + + val reduceAmountValue = feeAmount + ?.takeIf { feeValue != BigDecimal.ZERO } + ?.let { amount -> + resourceReference( + R.string.send_notification_reduce_by, + wrappedList( + value.format { + crypto( + symbol = amount.currencySymbol, + decimals = amount.decimals, + ).fee(canBeLower = isFeeApproximate) + }, + ), + ) + } + + add( + StakingNotification.Info.StakeEntireBalance( + reduceAmountValue, + { + onAmountReduceByFeeClick.invoke( + value, + StakingNotification.Info.StakeEntireBalance::class.java, + ) + }, + ), + ) } } @@ -286,5 +338,6 @@ internal class StakingInfoNotificationsFactory( private companion object { val MINIMUM_STAKE_BALANCE = "5".toBigDecimal() val MINIMUM_RESTAKE_BALANCE = "3".toBigDecimal() + val FEE_DECIMALS_MULTIPLIER = "3".toBigDecimal() } } \ No newline at end of file