diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index fbbc8953ab..490e25e8e1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -3,7 +3,6 @@ package com.tangem.features.send.impl.presentation.state import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent -import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory @@ -91,15 +90,6 @@ internal class SendEventStateFactory( fun getFeeTooLowAlert(onConsume: () -> Unit): SendUiState { val state = currentStateProvider() - val feeSelectorState = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return state - val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return state - val minimumValue = multipleFees.minimum.amount.value ?: return state - val customAmount = feeSelectorState.customValues.firstOrNull() ?: return state - val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - - val isFeeTooLow = feeSelectorState.selectedFee == FeeType.Custom && minimumValue > customValue - if (!isFeeTooLow) return state - return state.copy( event = triggeredEvent( data = SendEvent.ShowAlert( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index 34e2ddd3fe..7572a37ae9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -101,8 +101,8 @@ data class SendUiCurrentScreen( enum class SendUiStateType { None, - Amount, Recipient, + Amount, Fee, Send, } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index b9a182691f..1a57669334 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -1,5 +1,7 @@ package com.tangem.features.send.impl.presentation.state.fee +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState @@ -11,4 +13,17 @@ internal fun checkFeeCoverage(state: SendUiState, cryptoCurrencyStatus: CryptoCu val fee = state.feeState?.fee?.amount?.value ?: return false val amount = state.amountState?.amountTextField?.cryptoAmount?.value ?: return false return balance <= amount + fee +} + +/** + * Check if custom fee is too low + */ +internal fun checkIfFeeTooLow(state: SendUiState): Boolean { + val feeSelectorState = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false + val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return false + val minimumValue = multipleFees.minimum.amount.value ?: return false + val customAmount = feeSelectorState.customValues.firstOrNull() ?: return false + val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) + + return feeSelectorState.selectedFee == FeeType.Custom && minimumValue > customValue } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index d928d3c091..4154ae14a3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -57,7 +57,6 @@ internal class FeeStateFactory( val feeState = state.feeState ?: return state val feeSelectorState = (feeState.feeSelectorState as? FeeSelectorState.Content)?.copy( fees = fees, - customValues = customFeeFieldConverter.convert(fees.normal), ) ?: FeeSelectorState.Content( fees = fees, customValues = customFeeFieldConverter.convert(fees.normal), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index 2af0c26442..5c0dfc787b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.runtime.Composable -import androidx.compose.runtime.State import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -33,7 +32,7 @@ import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType @Composable -internal fun SendNavigationButtons(uiState: SendUiState, currentState: State) { +internal fun SendNavigationButtons(uiState: SendUiState, currentState: SendUiCurrentScreen) { Row( modifier = Modifier .fillMaxWidth() @@ -54,11 +53,10 @@ internal fun SendNavigationButtons(uiState: SendUiState, currentState: State) { +private fun SendSecondaryNavigationButton(uiState: SendUiState, currentState: SendUiCurrentScreen) { val isEditingDisabled = uiState.isEditingDisabled - val isFromConfirmation = currentState.value.isFromConfirmation - val isCorrectScreen = - currentState.value.type == SendUiStateType.Amount || currentState.value.type == SendUiStateType.Fee + val isFromConfirmation = currentState.isFromConfirmation + val isCorrectScreen = currentState.type == SendUiStateType.Amount || currentState.type == SendUiStateType.Fee AnimatedVisibility( visible = !isEditingDisabled && isCorrectScreen && !isFromConfirmation, enter = expandHorizontally(expandFrom = Alignment.End), @@ -83,7 +81,7 @@ private fun SendSecondaryNavigationButton(uiState: SendUiState, currentState: St @Composable private fun SendPrimaryNavigationButton( uiState: SendUiState, - currentState: State, + currentState: SendUiCurrentScreen, modifier: Modifier = Modifier, ) { val isSuccess = uiState.sendState.isSuccess @@ -107,7 +105,7 @@ private fun SendPrimaryNavigationButton( modifier = modifier, ) { textId -> when { - currentState.value.type == SendUiStateType.Send && !isSuccess -> { + currentState.type == SendUiStateType.Send && !isSuccess -> { val hapticFeedback = rememberHapticFeedback(state = currentState, onAction = buttonClick) PrimaryButtonIconEnd( text = stringResource(textId), @@ -117,7 +115,7 @@ private fun SendPrimaryNavigationButton( showProgress = isSending, ) } - currentState.value.type == SendUiStateType.Send && isSuccess -> { + currentState.type == SendUiStateType.Send && isSuccess -> { PrimaryButtonsDone( textRes = textId, txUrl = txUrl, @@ -184,15 +182,15 @@ private fun PrimaryButtonsDone( private fun getButtonData( uiState: SendUiState, - currentState: State, + currentState: SendUiCurrentScreen, isSuccess: Boolean, ): Pair Unit> { - return when (currentState.value.type) { + return when (currentState.type) { SendUiStateType.None, SendUiStateType.Amount, SendUiStateType.Recipient, SendUiStateType.Fee, - -> if (currentState.value.isFromConfirmation) { + -> if (currentState.isFromConfirmation) { R.string.common_continue to uiState.clickIntents::onNextClick } else { R.string.common_next to uiState.clickIntents::onNextClick @@ -205,8 +203,8 @@ private fun getButtonData( } } -private fun isButtonEnabled(currentState: State, uiState: SendUiState): Boolean { - return when (currentState.value.type) { +private fun isButtonEnabled(currentState: SendUiCurrentScreen, uiState: SendUiState): Boolean { + return when (currentState.type) { SendUiStateType.Amount -> uiState.amountState?.isPrimaryButtonEnabled ?: false SendUiStateType.Recipient -> uiState.recipientState?.isPrimaryButtonEnabled ?: false SendUiStateType.Fee -> uiState.feeState?.isPrimaryButtonEnabled ?: false diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index 2e0a451ec3..7cd9b84daf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -1,16 +1,15 @@ package com.tangem.features.send.impl.presentation.ui import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.* +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -63,11 +62,11 @@ internal fun SendScreen(uiState: SendUiState, currentStateFlow: StateFlow, - modifier: Modifier = Modifier, -) { +private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentScreen, modifier: Modifier = Modifier) { + var lastState by remember { mutableIntStateOf(currentState.type.ordinal) } + val direction = remember(currentState.type.ordinal) { + if (lastState < currentState.type.ordinal) { + AnimatedContentTransitionScope.SlideDirection.Start + } else { + AnimatedContentTransitionScope.SlideDirection.End + } + } AnimatedContent( - targetState = currentState.value, + targetState = currentState, label = "Send Scree Navigation", modifier = modifier, + transitionSpec = { + lastState = currentState.type.ordinal + slideIntoContainer(towards = direction, animationSpec = tween()) + .togetherWith(slideOutOfContainer(towards = direction, animationSpec = tween())) + }, ) { state -> when (state.type) { SendUiStateType.Amount -> SendAmountContent( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt index 207b6ee43e..92d9b44d51 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.ui.fee +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -20,7 +21,10 @@ internal fun SendCustomFeeEthereum( selectedFee: FeeType, modifier: Modifier = Modifier, ) { - if (selectedFee == FeeType.Custom && customValues.isNotEmpty()) { + AnimatedVisibility( + visible = selectedFee == FeeType.Custom && customValues.isNotEmpty(), + label = "Custom Fee Selected Animation", + ) { Column( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), modifier = modifier, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index bbd3af4660..621edfae39 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -40,10 +40,7 @@ import com.tangem.features.send.impl.presentation.analytics.utils.SendOnNextScre import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory -import com.tangem.features.send.impl.presentation.state.fee.FeeNotificationFactory -import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.state.fee.checkFeeCoverage +import com.tangem.features.send.impl.presentation.state.fee.* import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -367,7 +364,7 @@ internal class SendViewModel @Inject constructor( stateRouter.currentState .onEach { when (it.type) { - SendUiStateType.Fee -> if (!it.isFromConfirmation) loadFee() + SendUiStateType.Fee -> loadFee() SendUiStateType.Send -> sendIdleTimer = System.currentTimeMillis() else -> Unit } @@ -402,9 +399,6 @@ internal class SendViewModel @Inject constructor( val currentState = stateRouter.currentState.value val isCurrentFee = currentState.type == SendUiStateType.Fee if (isCurrentFee) { - uiState = eventStateFactory.getFeeTooLowAlert( - onConsume = { uiState = eventStateFactory.onConsumeEventState() }, - ) val isFeeCoverage = checkFeeCoverage(uiState, cryptoCurrencyStatus) if (isAmountSubtractAvailable && isFeeCoverage) { uiState = eventStateFactory.getFeeCoverageAlert( @@ -415,6 +409,12 @@ internal class SendViewModel @Inject constructor( uiState = stateFactory.onSubtractSelect(false) analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(false)) } + if (checkIfFeeTooLow(uiState)) { + uiState = eventStateFactory.getFeeTooLowAlert( + onConsume = { uiState = eventStateFactory.onConsumeEventState() }, + ) + return + } } sendOnNextScreenAnalyticSender.send(currentState.type, uiState) @@ -555,7 +555,9 @@ internal class SendViewModel @Inject constructor( private fun loadFee() { viewModelScope.launch(dispatchers.main) { - uiState = feeStateFactory.onFeeOnLoadingState() + if (uiState.feeState?.fee == null) { + uiState = feeStateFactory.onFeeOnLoadingState() + } uiState = callFeeUseCase()?.fold( ifRight = feeStateFactory::onFeeOnLoadedState, ifLeft = { feeStateFactory.onFeeOnErrorState() }, @@ -600,24 +602,18 @@ internal class SendViewModel @Inject constructor( } override fun showAmount() { - uiState = stateFactory.onSubtractSelect(false) stateRouter.showAmount(isFromConfirmation = true) analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Amount)) - analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(false)) } override fun showRecipient() { - uiState = stateFactory.onSubtractSelect(false) stateRouter.showRecipient(isFromConfirmation = true) analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Address)) - analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(false)) } override fun showFee() { - uiState = stateFactory.onSubtractSelect(false) stateRouter.showFee(isFromConfirmation = true) analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee)) - analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(false)) } override fun showSend() {