diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountIgnoreReduceTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountIgnoreReduceTransformer.kt new file mode 100644 index 0000000000..2255b4af03 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountIgnoreReduceTransformer.kt @@ -0,0 +1,11 @@ +package com.tangem.common.ui.amountScreen.converters + +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.utils.transformer.Transformer + +object AmountIgnoreReduceTransformer : Transformer { + override fun transform(prevState: AmountState): AmountState { + val state = prevState as? AmountState.Data ?: return prevState + return state.copy(isIgnoreReduce = true) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt index 4fbf0d991a..e52898094a 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt @@ -62,6 +62,7 @@ class AmountReduceByTransformer( val isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided return prevState.copy( isPrimaryButtonEnabled = !isZero && !isCheckFailed, + reduceAmountBy = value.reduceAmountBy, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt index 4094aba847..cbfc6907e8 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt @@ -31,7 +31,6 @@ class AmountFieldChangeTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val maxEnterAmount: EnterAmountBoundary, private val minimumTransactionAmount: EnterAmountBoundary?, - private val reduceAmountBy: BigDecimal = BigDecimal.ZERO, private val value: String, ) : Transformer { @@ -68,7 +67,7 @@ class AmountFieldChangeTransformer( val isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided return prevState.copy( isPrimaryButtonEnabled = !isZero && !isCheckFailed, - reduceAmountBy = reduceAmountBy, + reduceAmountBy = BigDecimal.ZERO, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, @@ -76,10 +75,9 @@ class AmountFieldChangeTransformer( error = when { isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance) isLessThanMinimumIfProvided -> { - val minimumAmount = - minimumTransactionAmount.amount.format { - crypto(cryptoCurrencyStatus.currency) - } + val minimumAmount = minimumTransactionAmount?.amount.format { + crypto(cryptoCurrencyStatus.currency) + } resourceReference( R.string.transfer_notification_invalid_minimum_transaction_amount_text, diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt index 62280c0ebe..72dcac8ac9 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt @@ -27,7 +27,6 @@ class AmountFieldSetMaxAmountTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val maxAmount: EnterAmountBoundary, private val minAmount: EnterAmountBoundary?, - private val reduceAmountBy: BigDecimal = BigDecimal.ZERO, ) : Transformer { override fun transform(prevState: AmountState): AmountState { @@ -47,7 +46,7 @@ class AmountFieldSetMaxAmountTransformer( val isLessThanMinimumIfProvided = minAmount?.amount?.let { decimalCryptoValue < it } == true return prevState.copy( isPrimaryButtonEnabled = !isLessThanMinimumIfProvided, - reduceAmountBy = reduceAmountBy, + reduceAmountBy = BigDecimal.ZERO, amountTextField = amountTextField.copy( isValuePasted = true, value = cryptoValue, @@ -55,7 +54,7 @@ class AmountFieldSetMaxAmountTransformer( isError = isLessThanMinimumIfProvided, error = when { isLessThanMinimumIfProvided -> { - val minimumAmount = minAmount.amount.format { crypto(cryptoCurrencyStatus.currency) } + val minimumAmount = minAmount?.amount.format { crypto(cryptoCurrencyStatus.currency) } resourceReference( R.string.transfer_notification_invalid_minimum_transaction_amount_text, wrappedList(minimumAmount, minimumAmount), diff --git a/features/send-v2/impl/build.gradle.kts b/features/send-v2/impl/build.gradle.kts index eff04c059b..6858492736 100644 --- a/features/send-v2/impl/build.gradle.kts +++ b/features/send-v2/impl/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { implementation(projects.core.analytics) implementation(projects.core.configToggles) implementation(projects.core.navigation) + api(projects.core.pagination) /** Tangem SDK */ implementation(tangemDeps.blockchain) @@ -51,6 +52,8 @@ dependencies { implementation(projects.domain.settings) implementation(projects.domain.feedback) implementation(projects.domain.txhistory) + implementation(projects.domain.balanceHiding.models) + implementation(projects.domain.balanceHiding) /** Compose libraries */ implementation(deps.compose.foundation) @@ -64,6 +67,7 @@ dependencies { /** Other dependencies */ implementation(deps.kotlin.immutable.collections) implementation(deps.timber) + implementation(deps.androidx.paging.runtime) /** DI */ implementation(deps.hilt.android) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/NavigationUM.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/NavigationUM.kt index 149d4a5a5e..46b298b83c 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/NavigationUM.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/NavigationUM.kt @@ -1,9 +1,11 @@ package com.tangem.features.send.v2.common import androidx.annotation.DrawableRes +import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference import com.tangem.features.send.v2.send.ui.state.ButtonsUM +@Immutable internal sealed class NavigationUM { data class Content( val title: TextReference, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt index d26a2ec9c3..9d9d7a4778 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt @@ -2,11 +2,17 @@ package com.tangem.features.send.v2.send import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +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.arkivanov.decompose.value.ObserveLifecycleMode +import com.arkivanov.decompose.value.subscribe import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel @@ -14,10 +20,14 @@ import com.tangem.core.decompose.navigation.inner.InnerRouter import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.send.v2.api.SendComponent import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents +import com.tangem.features.send.v2.send.confirm.SendConfirmComponent +import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM import com.tangem.features.send.v2.send.model.SendModel +import com.tangem.features.send.v2.send.ui.SendContent import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponent import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent +import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponentParams import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponent import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams @@ -26,10 +36,12 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.launch internal class DefaultSendComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted private val params: SendComponent.Params, + private val analyticsEventHandler: AnalyticsEventHandler, ) : SendComponent, AppComponentContext by appComponentContext { private val stackNavigation = StackNavigation() @@ -65,9 +77,44 @@ internal class DefaultSendComponent @AssistedInject constructor( }, ) + init { + childStack.subscribe( + lifecycle = lifecycle, + mode = ObserveLifecycleMode.CREATE_DESTROY, + ) { stack -> + componentScope.launch { + when (val activeComponent = stack.active.instance) { + is SendConfirmComponent -> if (currentRoute.value.isEditMode) { + analyticsEventHandler.send(SendAnalyticEvents.ConfirmationScreenOpened) + activeComponent.updateState(model.uiState.value) + } + is SendAmountComponent -> { + analyticsEventHandler.send(SendAnalyticEvents.AmountScreenOpened) + activeComponent.updateState(model.uiState.value.amountUM) + } + is SendDestinationComponent -> { + analyticsEventHandler.send(SendAnalyticEvents.AddressScreenOpened) + activeComponent.updateState(model.uiState.value.destinationUM) + } + is SendFeeComponent -> { + analyticsEventHandler.send(SendAnalyticEvents.FeeScreenOpened) + } + } + currentRoute.emit(stack.active.configuration) + } + } + } + @Composable override fun Content(modifier: Modifier) { + val stackState by childStack.subscribeAsState() + val state by model.uiState.collectAsStateWithLifecycle() + BackHandler(onBack = ::onChildBack) + SendContent( + state = state, + stackState = stackState, + ) } private fun createChild(route: SendRoute, factoryContext: AppComponentContext) = when (route) { @@ -75,7 +122,7 @@ internal class DefaultSendComponent @AssistedInject constructor( is SendRoute.Destination -> getDestinationComponent(factoryContext, route) is SendRoute.Amount -> getAmountComponent(factoryContext, route) is SendRoute.Fee -> getFeeComponent(factoryContext) - SendRoute.Confirm -> getConfirmComponent() + SendRoute.Confirm -> getConfirmComponent(factoryContext) } private fun getDestinationComponent(factoryContext: AppComponentContext, route: SendRoute) = @@ -84,6 +131,7 @@ internal class DefaultSendComponent @AssistedInject constructor( params = SendDestinationComponentParams.DestinationParams( state = model.uiState.value.destinationUM, currentRoute = currentRoute.filterIsInstance(), + isBalanceHidingFlow = model.isBalanceHiddenFlow, analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY, userWallet = model.userWallet, cryptoCurrency = params.currency, @@ -97,6 +145,7 @@ internal class DefaultSendComponent @AssistedInject constructor( params = SendAmountComponentParams.AmountParams( state = model.uiState.value.amountUM, currentRoute = currentRoute.filterIsInstance(), + isBalanceHidingFlow = model.isBalanceHiddenFlow, analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY, userWallet = model.userWallet, appCurrency = model.appCurrency, @@ -132,12 +181,46 @@ internal class DefaultSendComponent @AssistedInject constructor( } } - private fun getConfirmComponent() = getStubComponent() // todo + private fun getConfirmComponent(factoryContext: AppComponentContext): SendConfirmComponent { + val predefinedAmount = params.amount + val predefinedTxId = params.transactionId + val predefinedAddress = params.destinationAddress + val predefinedValues = + if (predefinedAmount != null && predefinedTxId != null && predefinedAddress != null) { + SendConfirmComponent.Params.PredefinedValues.Content( + amount = predefinedAmount, + address = predefinedAddress, + tag = params.tag, + transactionId = predefinedTxId, + ) + } else { + SendConfirmComponent.Params.PredefinedValues.Empty + } + return SendConfirmComponent( + appComponentContext = factoryContext, + params = SendConfirmComponent.Params( + state = model.uiState.value, + userWallet = model.userWallet, + currentRoute = currentRoute.filterIsInstance(), + isBalanceHidingFlow = model.isBalanceHiddenFlow, + analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY, + cryptoCurrencyStatus = model.cryptoCurrencyStatus, + feeCryptoCurrencyStatus = model.feeCryptoCurrencyStatus, + appCurrency = model.appCurrency, + callback = model, + predefinedValues = predefinedValues, + ), + ) + } private fun getStubComponent() = ComposableContentComponent { } private fun onChildBack() { - if (childStack.value.active.configuration == SendRoute.Empty || childStack.value.backStack.isEmpty()) { + val isEmptyRoute = childStack.value.active.configuration == SendRoute.Empty + val isEmptyStack = childStack.value.backStack.isEmpty() + val isSuccess = model.uiState.value.confirmUM is ConfirmUM.Success + + if (isEmptyRoute || isEmptyStack || isSuccess) { router.pop() } else { stackNavigation.pop() diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt index 6f43c416f5..6d4753a5df 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt @@ -489,7 +489,7 @@ internal class SendConfirmModel @Inject constructor( flow = uiState, flow2 = params.currentRoute, transform = { state, route -> state to route }, - ).onEach { (state, _) -> + ).distinctUntilChanged().onEach { (state, _) -> val amountUM = state.amountUM as? AmountState.Data val confirmUM = state.confirmUM params.callback.onResult( diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt index 93df96b047..a1d123dc42 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt @@ -1,49 +1,108 @@ package com.tangem.features.send.v2.send.model import androidx.compose.runtime.Stable +import arrow.core.Either +import arrow.core.getOrElse import com.tangem.common.ui.amountScreen.models.AmountState 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.core.ui.utils.parseBigDecimal +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.feedback.GetCardInfoUseCase +import com.tangem.domain.feedback.SaveBlockchainErrorUseCase +import com.tangem.domain.feedback.SendFeedbackEmailUseCase +import com.tangem.domain.feedback.models.BlockchainErrorInfo +import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.qrscanning.models.SourceType +import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase +import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase +import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase +import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.features.send.v2.api.SendComponent +import com.tangem.features.send.v2.common.NavigationUM +import com.tangem.features.send.v2.send.SendRoute +import com.tangem.features.send.v2.send.confirm.model.SendConfirmAlertFactory import com.tangem.features.send.v2.send.confirm.SendConfirmComponent import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM import com.tangem.features.send.v2.send.ui.state.SendUM import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponent import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent +import com.tangem.features.send.v2.subcomponents.destination.model.transformers.SendDestinationInitialStateTransformer import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponent import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.properties.Delegates +internal interface SendComponentCallback : + SendAmountComponent.ModelCallback, + SendFeeComponent.ModelCallback, + SendDestinationComponent.ModelCallback, + SendConfirmComponent.ModelCallback + @Stable @ModelScoped @Suppress("LongParameterList") internal class SendModel @Inject constructor( + paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, -) : Model(), - SendDestinationComponent.ModelCallback, - SendAmountComponent.ModelCallback, - SendFeeComponent.ModelCallback, - SendConfirmComponent.ModelCallback { + private val router: Router, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, + private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val listenToQrScanningUseCase: ListenToQrScanningUseCase, + private val parseQrCodeUseCase: ParseQrCodeUseCase, + private val sendConfirmAlertFactory: SendConfirmAlertFactory, + private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase, + private val getCardInfoUseCase: GetCardInfoUseCase, + private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, + private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, +) : Model(), SendComponentCallback { + + private val params: SendComponent.Params = paramsContainer.require() + private val userWalletId = params.userWalletId + private val cryptoCurrency = params.currency private val _uiState = MutableStateFlow(initialState()) val uiState = _uiState.asStateFlow() + private val _isBalanceHiddenFlow = MutableStateFlow(false) + val isBalanceHiddenFlow = _isBalanceHiddenFlow.asStateFlow() + var userWallet: UserWallet by Delegates.notNull() var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() var feeCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() var appCurrency: AppCurrency = AppCurrency.Default var predefinedAmountValue: String? = null - override fun onResult(sendUM: SendUM) { - _uiState.value = sendUM + private var balanceHidingJobHolder = JobHolder() + + init { + subscribeOnBalanceHidden() + subscribeOnQRScannerResult() + subscribeOnCurrencyStatusUpdates() + initAppCurrency() + } + + override fun onNavigationResult(navigationUM: NavigationUM) { + _uiState.update { it.copy(navigationUM = navigationUM) } } override fun onDestinationResult(destinationUM: DestinationUM) { @@ -58,10 +117,146 @@ internal class SendModel @Inject constructor( _uiState.update { it.copy(feeUM = feeUM) } } + override fun onResult(sendUM: SendUM) { + _uiState.update { sendUM } + } + + private fun initAppCurrency() { + modelScope.launch { + appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default } + } + } + + private fun subscribeOnCurrencyStatusUpdates() { + modelScope.launch { + getUserWalletUseCase(params.userWalletId).fold( + ifRight = { wallet -> + userWallet = wallet + + val isSingleWalletWithToken = wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() + val isMultiCurrency = wallet.isMultiCurrency + getCurrenciesStatusUpdates( + isSingleWalletWithToken = isSingleWalletWithToken, + isMultiCurrency = isMultiCurrency, + ) + }, + ifLeft = { + sendConfirmAlertFactory.getGenericErrorState(::onFailedTxEmailClick) + return@launch + }, + ) + } + } + + private fun subscribeOnBalanceHidden() { + getBalanceHidingSettingsUseCase() + .conflate() + .distinctUntilChanged() + .onEach { + _isBalanceHiddenFlow.value = it.isBalanceHidden + } + .launchIn(modelScope) + .saveIn(balanceHidingJobHolder) + } + + private fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean, isMultiCurrency: Boolean) { + getCurrencyStatus( + isSingleWalletWithToken = isSingleWalletWithToken, + isMultiCurrency = isMultiCurrency, + ).onEach { maybeCryptoCurrency -> + maybeCryptoCurrency.fold( + ifRight = { cryptoCurrencyStatus -> + onDataLoaded( + currencyStatus = cryptoCurrencyStatus, + feeCurrencyStatus = getFeeCurrencyStatus(cryptoCurrencyStatus, isMultiCurrency), + ) + }, + ifLeft = { + sendConfirmAlertFactory.getGenericErrorState { + onFailedTxEmailClick(it.toString()) + } + }, + ) + }.launchIn(modelScope) + } + + private fun getCurrencyStatus( + isSingleWalletWithToken: Boolean, + isMultiCurrency: Boolean, + ): Flow> { + return if (isMultiCurrency) { + getCurrencyStatusUpdatesUseCase( + userWalletId = userWalletId, + currencyId = cryptoCurrency.id, + isSingleWalletWithTokens = isSingleWalletWithToken, + ) + } else { + getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWalletId) + } + } + + private suspend fun getFeeCurrencyStatus( + cryptoCurrencyStatus: CryptoCurrencyStatus, + isMultiCurrency: Boolean, + ): CryptoCurrencyStatus { + return if (isMultiCurrency) { + getFeePaidCryptoCurrencyStatusSyncUseCase( + userWalletId = userWalletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + ).getOrNull() ?: cryptoCurrencyStatus + } else { + cryptoCurrencyStatus + } + } + + private fun onDataLoaded(currencyStatus: CryptoCurrencyStatus, feeCurrencyStatus: CryptoCurrencyStatus) { + cryptoCurrencyStatus = currencyStatus + feeCryptoCurrencyStatus = feeCurrencyStatus + + if (params.amount != null) { + router.replaceAll(SendRoute.Confirm) + } + } + + private fun subscribeOnQRScannerResult() { + listenToQrScanningUseCase(SourceType.SEND) + .getOrElse { emptyFlow() } + .onEach(::onQrCodeScanned) + .launchIn(modelScope) + } + + private fun onQrCodeScanned(address: String) { + val parsedQrCode = parseQrCodeUseCase(address, cryptoCurrency).getOrNull() + predefinedAmountValue = parsedQrCode?.amount?.parseBigDecimal(cryptoCurrency.decimals) + } + + private fun onFailedTxEmailClick(errorMessage: String? = null) { + saveBlockchainErrorUseCase( + error = BlockchainErrorInfo( + errorMessage = errorMessage.orEmpty(), + blockchainId = cryptoCurrency.network.id.value, + derivationPath = cryptoCurrency.network.derivationPath.value, + destinationAddress = "", + tokenSymbol = "", + amount = "", + fee = "", + ), + ) + + val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrNull() ?: return + + modelScope.launch { + sendFeedbackEmailUseCase(type = FeedbackEmailType.TransactionSendingProblem(cardInfo = cardInfo)) + } + } + private fun initialState(): SendUM = SendUM( amountUM = AmountState.Empty(), - destinationUM = DestinationUM.Empty(), + destinationUM = SendDestinationInitialStateTransformer( + cryptoCurrency = cryptoCurrency, + ).transform(DestinationUM.Empty()), feeUM = FeeUM.Empty(), confirmUM = ConfirmUM.Empty, + navigationUM = NavigationUM.Empty, ) } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/ui/SendContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/ui/SendContent.kt new file mode 100644 index 0000000000..8451e8d50d --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/ui/SendContent.kt @@ -0,0 +1,55 @@ +package com.tangem.features.send.v2.send.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation +import com.arkivanov.decompose.router.stack.ChildStack +import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.send.v2.send.SendRoute +import com.tangem.features.send.v2.common.NavigationUM +import com.tangem.features.send.v2.send.ui.state.SendUM + +@Composable +internal fun SendContent(state: SendUM, stackState: ChildStack) { + Column( + modifier = Modifier.Companion + .background(color = TangemTheme.colors.background.tertiary) + .fillMaxSize() + .imePadding() + .systemBarsPadding(), + horizontalAlignment = Alignment.Companion.CenterHorizontally, + ) { + SendAppBar(navigationUM = state.navigationUM) + Children( + stack = stackState, + animation = stackAnimation(slide()), + modifier = Modifier.weight(1f), + ) { + it.instance.Content(Modifier.weight(1f)) + } + SendNavigationButtons(navigationUM = state.navigationUM) + } +} + +@Composable +private fun SendAppBar(navigationUM: NavigationUM) { + val navigationUM = navigationUM as? NavigationUM.Content ?: return + AppBarWithBackButtonAndIcon( + text = navigationUM.title.resolveReference(), + subtitle = navigationUM.subtitle?.resolveReference(), + onBackClick = navigationUM.backIconClick, + onIconClick = navigationUM.additionalIconClick, + backIconRes = navigationUM.backIconRes, + iconRes = navigationUM.additionalIconRes, + backgroundColor = TangemTheme.colors.background.tertiary, + modifier = Modifier.height(TangemTheme.dimens.size56), + ) +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountBlockComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountBlockComponent.kt index 01594d7266..b616b506f7 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountBlockComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountBlockComponent.kt @@ -1,6 +1,7 @@ package com.tangem.features.send.v2.subcomponents.amount import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.common.ui.amountScreen.models.AmountState @@ -20,7 +21,7 @@ internal class SendAmountBlockComponent( val onClick: () -> Unit, ) : ComposableContentComponent, AppComponentContext by appComponentContext { - private val model: SendAmountModel = getOrCreateModel(params = params, router = router) + private val model: SendAmountModel = getOrCreateModel(params = params) init { model.uiState.onEach { @@ -32,14 +33,13 @@ internal class SendAmountBlockComponent( @Composable override fun Content(modifier: Modifier) { - val state = model.uiState.collectAsStateWithLifecycle() - val isClickEnabled = params.blockClickEnableFlow.collectAsStateWithLifecycle() - val isEditingDisabled = params.blockEditDisabledFlow.collectAsStateWithLifecycle() + val state by model.uiState.collectAsStateWithLifecycle() + val isClickEnabled by params.blockClickEnableFlow.collectAsStateWithLifecycle() AmountBlock( - amountState = state.value, - isClickDisabled = !isClickEnabled.value, - isEditingDisabled = isEditingDisabled.value, + amountState = state, + isClickDisabled = !isClickEnabled, + isEditingDisabled = params.isPredefinedValues, onClick = onClick, ) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt index 6e35e058f5..f79c417846 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt @@ -27,10 +27,11 @@ internal class SendAmountComponent( @Composable override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() + val isBalanceHidden by params.isBalanceHidingFlow.collectAsStateWithLifecycle() AmountScreenContent( amountState = state, - isBalanceHidden = false, + isBalanceHidden = isBalanceHidden, clickIntents = model, modifier = Modifier.background(TangemTheme.colors.background.tertiary), ) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponentParams.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponentParams.kt index e3bafeb146..7185402604 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponentParams.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponentParams.kt @@ -27,6 +27,7 @@ internal sealed class SendAmountComponentParams { val callback: ModelCallback, val currentRoute: Flow, val predefinedAmountValue: String?, + val isBalanceHidingFlow: StateFlow, ) : SendAmountComponentParams() data class AmountBlockParams( @@ -35,7 +36,8 @@ internal sealed class SendAmountComponentParams { override val userWallet: UserWallet, override val appCurrency: AppCurrency, override val cryptoCurrencyStatus: CryptoCurrencyStatus, - val blockEditDisabledFlow: StateFlow, val blockClickEnableFlow: StateFlow, + val predefinedAmountValue: String?, + val isPredefinedValues: Boolean, ) : SendAmountComponentParams() } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt index 4c4f1acb33..a168fe3532 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt @@ -13,6 +13,7 @@ import javax.inject.Singleton interface SendAmountReduceTrigger { suspend fun triggerReduceBy(reduceBy: ReduceByData) suspend fun triggerReduceTo(reduceTo: BigDecimal) + suspend fun triggerIgnoreReduce() } /** @@ -21,6 +22,7 @@ interface SendAmountReduceTrigger { interface SendAmountReduceListener { val reduceToTriggerFlow: Flow val reduceByTriggerFlow: Flow + val ignoreReduceTriggerFlow: Flow } @Singleton @@ -29,6 +31,7 @@ internal class DefaultSendAmountReduceTrigger @Inject constructor() : SendAmountReduceListener { override val reduceToTriggerFlow = MutableSharedFlow() override val reduceByTriggerFlow = MutableSharedFlow() + override val ignoreReduceTriggerFlow = MutableSharedFlow() override suspend fun triggerReduceBy(reduceBy: ReduceByData) { reduceByTriggerFlow.emit(reduceBy) @@ -37,4 +40,8 @@ internal class DefaultSendAmountReduceTrigger @Inject constructor() : override suspend fun triggerReduceTo(reduceTo: BigDecimal) { reduceToTriggerFlow.emit(reduceTo) } + + override suspend fun triggerIgnoreReduce() { + ignoreReduceTriggerFlow.emit(Unit) + } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt index e0588bbc0e..d0817e99ca 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt @@ -14,11 +14,14 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase import com.tangem.features.send.v2.common.NavigationUM import com.tangem.features.send.v2.impl.R import com.tangem.features.send.v2.send.SendRoute +import com.tangem.features.send.v2.send.ui.state.ButtonsUM import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams import com.tangem.features.send.v2.subcomponents.amount.SendAmountReduceListener import com.tangem.features.send.v2.subcomponents.amount.analytics.SendAmountAnalyticEvents @@ -60,6 +63,7 @@ internal class SendAmountModel @Inject constructor( initMinBoundary() subscribeOnAmountReduceByTriggerUpdates() subscribeOnAmountReduceToTriggerUpdates() + subscribeOnAmountIgnoreReduceTriggerUpdates() } private fun initMinBoundary() { @@ -79,7 +83,6 @@ internal class SendAmountModel @Inject constructor( } private fun initialState() { - val predefinedAmountValue = (params as? SendAmountComponentParams.AmountParams)?.predefinedAmountValue if (uiState.value is AmountState.Empty) { _uiState.update { AmountStateConverterV2( @@ -95,8 +98,9 @@ internal class SendAmountModel @Inject constructor( ), ) } - if (predefinedAmountValue != null) { - onAmountValueChange(predefinedAmountValue) + val params = params as? SendAmountComponentParams.AmountBlockParams + if (params?.predefinedAmountValue != null) { + onAmountValueChange(params.predefinedAmountValue) } } } @@ -191,6 +195,12 @@ internal class SendAmountModel @Inject constructor( .launchIn(modelScope) } + private fun subscribeOnAmountIgnoreReduceTriggerUpdates() { + sendAmountReduceListener.ignoreReduceTriggerFlow + .onEach { _uiState.update(AmountIgnoreReduceTransformer::transform) } + .launchIn(modelScope) + } + private fun saveResult() { val params = params as? SendAmountComponentParams.AmountParams ?: return params.callback.onAmountResult(uiState.value) @@ -202,7 +212,7 @@ internal class SendAmountModel @Inject constructor( flow = uiState, flow2 = params.currentRoute, transform = { state, route -> state to route }, - ).onEach { (state, route) -> + ).distinctUntilChanged().onEach { (state, route) -> params.callback.onNavigationResult( NavigationUM.Content( title = resourceReference(R.string.send_amount_label), diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponent.kt index 02ec170b2d..f1b52451d8 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponent.kt @@ -29,7 +29,6 @@ internal class SendDestinationComponent( SendDestinationContent(state = state, clickIntents = model, isBalanceHidden = isBalanceHidden) } - interface ModelCallback : SendNavigationModelCallback { fun onDestinationResult(destinationUM: DestinationUM) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt index b4d297cca8..5622ae7e32 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt @@ -357,7 +357,7 @@ internal class SendDestinationModel @Inject constructor( flow = uiState, flow2 = params.currentRoute, transform = { state, route -> state to route }, - ).onEach { (state, route) -> + ).distinctUntilChanged().onEach { (state, route) -> params.callback.onNavigationResult( NavigationUM.Content( title = resourceReference(R.string.send_recipient_label), diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationPredefinedStateTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationPredefinedStateTransformer.kt new file mode 100644 index 0000000000..dc7532f5cf --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationPredefinedStateTransformer.kt @@ -0,0 +1,19 @@ +package com.tangem.features.send.v2.subcomponents.destination.model.transformers + +import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM +import com.tangem.utils.transformer.Transformer + +internal class SendDestinationPredefinedStateTransformer( + private val address: String, + private val memo: String?, +) : Transformer { + + override fun transform(prevState: DestinationUM): DestinationUM { + val state = prevState as? DestinationUM.Content ?: return prevState + + return state.copy( + addressTextField = state.addressTextField.copy(value = address, isValuePasted = false), + memoTextField = memo?.let { state.memoTextField?.copy(value = it, isValuePasted = false) }, + ) + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/state/DestinationTextFieldUM.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/state/DestinationTextFieldUM.kt index b2c3ac8b6e..ccc6266ae2 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/state/DestinationTextFieldUM.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/state/DestinationTextFieldUM.kt @@ -10,15 +10,11 @@ internal sealed class DestinationTextFieldUM { /** Current value */ abstract val value: String - /** Lambda be invoked when value is been changed */ - abstract val onValueChange: (String) -> Unit - /** Keyboard options */ abstract val keyboardOptions: KeyboardOptions data class RecipientAddress( override val value: String, - override val onValueChange: (String) -> Unit, override val keyboardOptions: KeyboardOptions, val placeholder: TextReference, val label: TextReference, @@ -29,7 +25,6 @@ internal sealed class DestinationTextFieldUM { data class RecipientMemo( override val value: String, - override val onValueChange: (String) -> Unit, override val keyboardOptions: KeyboardOptions, val placeholder: TextReference, val label: TextReference, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/state/DestinationUM.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/state/DestinationUM.kt index 7be35bbc1b..06935a8368 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/state/DestinationUM.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/state/DestinationUM.kt @@ -16,7 +16,7 @@ internal sealed class DestinationUM { val wallets: ImmutableList, val networkName: String, val isValidating: Boolean = false, - val isEditingDisabled: Boolean = false, + val isInitialized: Boolean = false, ) : DestinationUM() data class Empty( diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/SendFeeAlertFactory.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/SendFeeAlertFactory.kt index 6526312b27..107c608bb2 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/SendFeeAlertFactory.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/SendFeeAlertFactory.kt @@ -77,7 +77,7 @@ internal class SendFeeAlertFactory @Inject constructor( return isFeeTooHigh } - fun getFeeUpdatedAlert(newFee: TransactionFee, feeUM: FeeUM, onFeeNotIncreased: () -> Unit) { + fun getFeeUpdatedAlert(newFee: TransactionFee, feeUM: FeeUM, proceedAction: () -> Unit, stopAction: () -> Unit) { if (feeUM !is FeeUM.Content) return val feeSelectorUM = feeUM.feeSelectorUM as? FeeSelectorUM.Content ?: return val newFee = when (newFee) { @@ -100,12 +100,16 @@ internal class SendFeeAlertFactory @Inject constructor( DialogMessage( message = resourceReference(id = R.string.send_notification_high_fee_title), dismissOnFirstAction = true, - firstActionBuilder = { okAction() }, - secondActionBuilder = { cancelAction() }, + firstActionBuilder = { + okAction { proceedAction(); onDismissRequest() } + }, + secondActionBuilder = { + cancelAction { stopAction(); onDismissRequest() } + }, ), ) } else { - onFeeNotIncreased() + proceedAction() } } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/SendFeeModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/SendFeeModel.kt index 6d1d982599..861798a122 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/SendFeeModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/SendFeeModel.kt @@ -8,12 +8,12 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.navigation.url.UrlOpener -import com.tangem.features.send.v2.send.ui.state.ButtonsUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase import com.tangem.features.send.v2.common.NavigationUM import com.tangem.features.send.v2.impl.R +import com.tangem.features.send.v2.send.ui.state.ButtonsUM import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadTrigger import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams import com.tangem.features.send.v2.subcomponents.fee.SendFeeReloadTrigger @@ -208,7 +208,20 @@ internal class SendFeeModel @Inject constructor( modelScope.launch { callFeeUseCase().fold( ifRight = { - feeCheckReloadTrigger.callbackCheckResult(true) + sendFeeAlertFactory.getFeeUpdatedAlert( + newFee = it, + feeUM = uiState.value, + proceedAction = { + modelScope.launch { + feeCheckReloadTrigger.callbackCheckResult(true) + } + }, + stopAction = { + modelScope.launch { + feeCheckReloadTrigger.callbackCheckResult(false) + } + }, + ) _uiState.update( SendFeeLoadedTransformer( fees = it, @@ -223,7 +236,7 @@ internal class SendFeeModel @Inject constructor( ifLeft = { feeError -> feeCheckReloadTrigger.callbackCheckResult(false) _uiState.update(SendFeeFailedTransformer(feeError)) - sendFeeAlertFactory.getFeeUnreachableErrorState(::checkLoadFee) + sendFeeAlertFactory.getFeeUnreachableErrorState(::loadFee) updateFeeNotifications() }, ) @@ -265,7 +278,7 @@ internal class SendFeeModel @Inject constructor( flow = uiState, flow2 = params.currentRoute, transform = { state, route -> state to route }, - ).onEach { (state, _) -> + ).distinctUntilChanged().onEach { (state, _) -> params.callback.onNavigationResult( NavigationUM.Content( title = resourceReference(R.string.common_fee_selector_title),