From e7b7c086bbd2096308711100e127edea1f6230e4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Jun 2026 18:31:37 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../pushes/TokenDetailsPushHandlerTest.kt | 2 +- ...pProblematicWalletForAddressUseCaseTest.kt | 2 +- .../models/errors}/GetUserWalletError.kt | 2 +- .../usecase/GetSelectedWalletSyncUseCase.kt | 2 +- .../usecase/GetSelectedWalletUseCase.kt | 2 +- .../wallets/usecase/GetUserWalletUseCase.kt | 2 +- .../model/WalletBackupModelTest.kt | 2 +- .../api/subcomponents/amount/AmountRoute.kt | 8 + .../amount/SendAmountBlockComponent.kt | 19 ++ .../amount/SendAmountComponent.kt | 21 ++ .../amount/SendAmountComponentParams.kt | 8 +- .../amount/SendAmountReduceTrigger.kt | 39 ++++ .../features/send/common/CommonSendRoute.kt | 3 +- .../entrypoint/model/SendEntryPointModel.kt | 2 +- .../send/send/DefaultSendComponent.kt | 15 +- .../send/send/confirm/SendConfirmComponent.kt | 6 +- .../send/confirm/model/SendConfirmModel.kt | 2 +- .../send/confirm/ui/SendConfirmContent.kt | 6 +- .../features/send/send/model/SendModel.kt | 6 +- ....kt => DefaultSendAmountBlockComponent.kt} | 30 ++- ...onent.kt => DefaultSendAmountComponent.kt} | 29 +-- ...r.kt => DefaultSendAmountReduceTrigger.kt} | 39 +--- .../amount/di/SendAmountModule.kt | 17 +- .../amount/model/SendAmountModel.kt | 28 ++- .../amount/model/SendAmountNavigationTest.kt | 179 ++++++++++++++++++ .../DefaultTokenDetailsDeepLinkHandlerTest.kt | 2 +- .../DefaultPromoDeeplinkHandlerTest.kt | 2 +- 27 files changed, 373 insertions(+), 102 deletions(-) rename domain/wallets/{src/main/java/com/tangem/domain/wallets/models => models/src/main/java/com/tangem/domain/wallets/models/errors}/GetUserWalletError.kt (66%) create mode 100644 features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/AmountRoute.kt create mode 100644 features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountBlockComponent.kt create mode 100644 features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponent.kt rename features/send/{impl/src/main/java/com/tangem/features/send => api/src/main/java/com/tangem/features/send/api}/subcomponents/amount/SendAmountComponentParams.kt (92%) create mode 100644 features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountReduceTrigger.kt rename features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/{SendAmountBlockComponent.kt => DefaultSendAmountBlockComponent.kt} (55%) rename features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/{SendAmountComponent.kt => DefaultSendAmountComponent.kt} (55%) rename features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/{SendAmountReduceTrigger.kt => DefaultSendAmountReduceTrigger.kt} (55%) create mode 100644 features/send/impl/src/test/java/com/tangem/features/send/subcomponents/amount/model/SendAmountNavigationTest.kt diff --git a/app/src/test/kotlin/com/tangem/tap/common/pushes/TokenDetailsPushHandlerTest.kt b/app/src/test/kotlin/com/tangem/tap/common/pushes/TokenDetailsPushHandlerTest.kt index 84779067f5..01710b9388 100644 --- a/app/src/test/kotlin/com/tangem/tap/common/pushes/TokenDetailsPushHandlerTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/common/pushes/TokenDetailsPushHandlerTest.kt @@ -14,7 +14,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.models.wallet.isMultiCurrency -import com.tangem.domain.wallets.models.GetUserWalletError +import com.tangem.domain.wallets.models.errors.GetUserWalletError import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.utils.coroutines.AppCoroutineScope diff --git a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/GetBackupProblematicWalletForAddressUseCaseTest.kt b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/GetBackupProblematicWalletForAddressUseCaseTest.kt index f20cd59184..3c682fac25 100644 --- a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/GetBackupProblematicWalletForAddressUseCaseTest.kt +++ b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/GetBackupProblematicWalletForAddressUseCaseTest.kt @@ -9,7 +9,7 @@ import com.tangem.domain.account.status.model.AccountCryptoCurrency import com.tangem.domain.card.IsWalletBackupProblematicUseCase import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.wallets.models.GetUserWalletError +import com.tangem.domain.wallets.models.errors.GetUserWalletError import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import io.mockk.clearMocks import io.mockk.coEvery diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/GetUserWalletError.kt b/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/errors/GetUserWalletError.kt similarity index 66% rename from domain/wallets/src/main/java/com/tangem/domain/wallets/models/GetUserWalletError.kt rename to domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/errors/GetUserWalletError.kt index 3e7e62a562..745399666a 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/GetUserWalletError.kt +++ b/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/errors/GetUserWalletError.kt @@ -1,4 +1,4 @@ -package com.tangem.domain.wallets.models +package com.tangem.domain.wallets.models.errors sealed class GetUserWalletError { diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt index 071bc36c87..754a063118 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt @@ -4,7 +4,7 @@ import arrow.core.Either import arrow.core.raise.either import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.wallets.models.GetUserWalletError +import com.tangem.domain.wallets.models.errors.GetUserWalletError /** * Use case for getting selected wallet. diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt index 19a253810c..30e17a1483 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt @@ -4,7 +4,7 @@ import arrow.core.Either import arrow.core.raise.either import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.wallets.models.GetUserWalletError +import com.tangem.domain.wallets.models.errors.GetUserWalletError import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterNotNull diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt index 577f7d8b93..14c5077096 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt @@ -10,7 +10,7 @@ import com.tangem.domain.common.wallets.requireUserWalletsSync import com.tangem.domain.core.utils.EitherFlow import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.wallets.models.GetUserWalletError +import com.tangem.domain.wallets.models.errors.GetUserWalletError import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.transformLatest diff --git a/features/hot-wallet/impl/src/test/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModelTest.kt b/features/hot-wallet/impl/src/test/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModelTest.kt index ddf4d1d425..b8d9a569c8 100644 --- a/features/hot-wallet/impl/src/test/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModelTest.kt +++ b/features/hot-wallet/impl/src/test/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModelTest.kt @@ -14,7 +14,7 @@ import com.tangem.core.ui.message.DialogMessage import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents -import com.tangem.domain.wallets.models.GetUserWalletError +import com.tangem.domain.wallets.models.errors.GetUserWalletError import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.UnlockHotWalletContextualUseCase import com.tangem.features.hotwallet.WalletBackupComponent diff --git a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/AmountRoute.kt b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/AmountRoute.kt new file mode 100644 index 0000000000..afcdebbae2 --- /dev/null +++ b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/AmountRoute.kt @@ -0,0 +1,8 @@ +package com.tangem.features.send.api.subcomponents.amount + +/** + * Common route for amount + */ +interface AmountRoute { + val isEditMode: Boolean +} \ No newline at end of file diff --git a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountBlockComponent.kt b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountBlockComponent.kt new file mode 100644 index 0000000000..ebed359d91 --- /dev/null +++ b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountBlockComponent.kt @@ -0,0 +1,19 @@ +package com.tangem.features.send.api.subcomponents.amount + +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface SendAmountBlockComponent : ComposableContentComponent { + + fun updateState(amountUM: AmountState) + + interface Factory { + fun create( + context: AppComponentContext, + params: SendAmountComponentParams.AmountBlockParams, + onClick: () -> Unit, + onResult: (AmountState) -> Unit, + ): SendAmountBlockComponent + } +} \ No newline at end of file diff --git a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponent.kt b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponent.kt new file mode 100644 index 0000000000..79e3d44a57 --- /dev/null +++ b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponent.kt @@ -0,0 +1,21 @@ +package com.tangem.features.send.api.subcomponents.amount + +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.navigationButtons.NavigationModelCallback +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.errors.GetUserWalletError + +interface SendAmountComponent : ComposableContentComponent { + + fun updateState(amountUM: AmountState) + + interface ModelCallback : NavigationModelCallback { + fun onAmountResult(amountUM: AmountState, isResetPredefined: Boolean) + fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean) + fun resetSendNavigation() + fun onError(error: GetUserWalletError) + } + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountComponentParams.kt b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponentParams.kt similarity index 92% rename from features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountComponentParams.kt rename to features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponentParams.kt index 88427bed4c..bda588bfee 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountComponentParams.kt +++ b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponentParams.kt @@ -1,4 +1,4 @@ -package com.tangem.features.send.subcomponents.amount +package com.tangem.features.send.api.subcomponents.amount import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.domain.appcurrency.model.AppCurrency @@ -9,10 +9,10 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents import com.tangem.features.send.api.entity.PredefinedValues -import com.tangem.features.send.common.CommonSendRoute +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -internal sealed class SendAmountComponentParams { +sealed class SendAmountComponentParams { abstract val state: AmountState abstract val analyticsCategoryName: String @@ -39,7 +39,7 @@ internal sealed class SendAmountComponentParams { override val accountFlow: StateFlow, override val isAccountModeFlow: StateFlow, val callback: SendAmountComponent.ModelCallback, - val currentRoute: StateFlow, + val currentRoute: Flow, ) : SendAmountComponentParams() data class AmountBlockParams( diff --git a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountReduceTrigger.kt b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountReduceTrigger.kt new file mode 100644 index 0000000000..5f923b8d46 --- /dev/null +++ b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountReduceTrigger.kt @@ -0,0 +1,39 @@ +package com.tangem.features.send.api.subcomponents.amount + +import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer +import kotlinx.coroutines.flow.Flow +import java.math.BigDecimal + +/** + * Trigger for reducing amount from another component + */ +interface SendAmountReduceTrigger { + suspend fun triggerReduceBy(reduceBy: AmountReduceByTransformer.ReduceByData) + suspend fun triggerReduceTo(reduceTo: BigDecimal) + suspend fun triggerIgnoreReduce() +} + +/** + * Trigger for reducing amount from another component + */ +interface SendAmountReduceListener { + val reduceToTriggerFlow: Flow + val reduceByTriggerFlow: Flow + val ignoreReduceTriggerFlow: Flow +} + +/** + * Trigger amount change from another component. + * Different from another triggers because it takes raw string instead of BigDecimal + */ +interface SendAmountUpdateTrigger { + suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean?) +} + +/** + * Trigger amount change from another component. + * Different from another triggers because it takes raw string instead of BigDecimal + */ +interface SendAmountUpdateListener { + val updateAmountTriggerFlow: Flow> +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/common/CommonSendRoute.kt b/features/send/impl/src/main/java/com/tangem/features/send/common/CommonSendRoute.kt index 398176faea..c098d9b414 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/common/CommonSendRoute.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/common/CommonSendRoute.kt @@ -1,6 +1,7 @@ package com.tangem.features.send.common import com.tangem.core.decompose.navigation.Route +import com.tangem.features.send.api.subcomponents.amount.AmountRoute import com.tangem.features.send.api.subcomponents.destination.DestinationRoute import kotlinx.serialization.Serializable @@ -31,5 +32,5 @@ internal sealed class CommonSendRoute : Route { @Serializable data class Amount( override val isEditMode: Boolean, - ) : CommonSendRoute() + ) : CommonSendRoute(), AmountRoute } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/entrypoint/model/SendEntryPointModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/entrypoint/model/SendEntryPointModel.kt index 0572ea6bd3..c759ab49fe 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/entrypoint/model/SendEntryPointModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/entrypoint/model/SendEntryPointModel.kt @@ -10,7 +10,7 @@ import com.tangem.features.managetokens.component.ChooseManagedTokensComponent import com.tangem.features.send.api.SendComponent import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents import com.tangem.features.send.api.entry.SendEntryRoute -import com.tangem.features.send.subcomponents.amount.SendAmountUpdateTrigger +import com.tangem.features.send.api.subcomponents.amount.SendAmountUpdateTrigger import com.tangem.features.swap.v2.api.SendWithSwapComponent import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger import com.tangem.utils.coroutines.CoroutineDispatcherProvider diff --git a/features/send/impl/src/main/java/com/tangem/features/send/send/DefaultSendComponent.kt b/features/send/impl/src/main/java/com/tangem/features/send/send/DefaultSendComponent.kt index 398b98b780..a44f29903c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/send/DefaultSendComponent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/send/DefaultSendComponent.kt @@ -30,6 +30,8 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.models.account.derivationIndex import com.tangem.features.send.api.SendComponent import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents +import com.tangem.features.send.api.subcomponents.amount.SendAmountComponent +import com.tangem.features.send.api.subcomponents.amount.SendAmountComponentParams import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponent import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM @@ -41,14 +43,12 @@ import com.tangem.features.send.impl.R import com.tangem.features.send.send.confirm.SendConfirmComponent import com.tangem.features.send.send.model.SendModel import com.tangem.features.send.send.success.SendConfirmSuccessComponent -import com.tangem.features.send.subcomponents.amount.SendAmountComponent -import com.tangem.features.send.subcomponents.amount.SendAmountComponentParams +import com.tangem.features.send.subcomponents.amount.DefaultSendAmountComponent import com.tangem.features.send.subcomponents.destination.DefaultSendDestinationBlockComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.launch @@ -57,6 +57,7 @@ internal class DefaultSendComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted private val params: SendComponent.Params, private val analyticsEventHandler: AnalyticsEventHandler, + private val amountComponentFactory: SendAmountComponent.Factory, private val feeSelectorComponentFactory: FeeSelectorBlockComponent.Factory, private val sendDestinationComponentFactory: SendDestinationComponent.Factory, ) : SendComponent, AppComponentContext by appComponentContext { @@ -113,7 +114,7 @@ internal class DefaultSendComponent @AssistedInject constructor( activeComponent.updateState(model.uiState.value) } } - is SendAmountComponent -> { + is DefaultSendAmountComponent -> { analyticsEventHandler.send( CommonSendAnalyticEvents.AmountScreenOpened( categoryName = model.analyticCategoryName, @@ -180,11 +181,11 @@ internal class DefaultSendComponent @AssistedInject constructor( ) private fun getAmountComponent(factoryContext: AppComponentContext): ComposableContentComponent { - return SendAmountComponent( - appComponentContext = factoryContext, + return amountComponentFactory.create( + context = factoryContext, params = SendAmountComponentParams.AmountParams( state = model.uiState.value.amountUM, - currentRoute = model.currentRoute.asStateFlow(), + currentRoute = model.currentRoute.filterIsInstance(), isBalanceHidingFlow = model.isBalanceHiddenFlow, analyticsCategoryName = model.analyticCategoryName, appCurrency = model.appCurrency, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/SendConfirmComponent.kt b/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/SendConfirmComponent.kt index 24b5e89ce0..6e369f2e78 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/SendConfirmComponent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/SendConfirmComponent.kt @@ -27,8 +27,8 @@ import com.tangem.features.send.common.ui.state.ConfirmUM import com.tangem.features.send.send.confirm.model.SendConfirmModel import com.tangem.features.send.send.confirm.ui.SendConfirmContent import com.tangem.features.send.send.ui.state.SendUM -import com.tangem.features.send.subcomponents.amount.SendAmountBlockComponent -import com.tangem.features.send.subcomponents.amount.SendAmountComponentParams +import com.tangem.features.send.subcomponents.amount.DefaultSendAmountBlockComponent +import com.tangem.features.send.api.subcomponents.amount.SendAmountComponentParams import com.tangem.features.send.subcomponents.destination.DefaultSendDestinationBlockComponent import com.tangem.features.send.subcomponents.notifications.DefaultSendNotificationsComponent import com.tangem.utils.extensions.orZero @@ -60,7 +60,7 @@ internal class SendConfirmComponent( onClick = model::showEditDestination, ) - private val amountBlockComponent = SendAmountBlockComponent( + private val amountBlockComponent = DefaultSendAmountBlockComponent( appComponentContext = child("sendConfirmAmountBlock"), params = SendAmountComponentParams.AmountBlockParams( state = model.uiState.value.amountUM, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/model/SendConfirmModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/model/SendConfirmModel.kt index 2eded657e2..bf529097bc 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/model/SendConfirmModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/model/SendConfirmModel.kt @@ -45,6 +45,7 @@ import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.utils.convertToSdkAmount import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents.SendScreenSource +import com.tangem.features.send.api.subcomponents.amount.SendAmountReduceTrigger import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorCheckReloadListener import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorCheckReloadTrigger @@ -69,7 +70,6 @@ import com.tangem.features.send.send.confirm.model.transformers.SendConfirmSendi import com.tangem.features.send.send.confirm.model.transformers.SendConfirmSentStateTransformer import com.tangem.features.send.send.confirm.model.transformers.SendConfirmationNotificationsTransformerV2 import com.tangem.features.send.send.ui.state.SendUM -import com.tangem.features.send.subcomponents.amount.SendAmountReduceTrigger import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.orZero import com.tangem.utils.extensions.stripZeroPlainString diff --git a/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/ui/SendConfirmContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/ui/SendConfirmContent.kt index 443ef0407d..7da1330172 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/ui/SendConfirmContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/send/confirm/ui/SendConfirmContent.kt @@ -18,7 +18,7 @@ import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorBlockCo import com.tangem.features.send.common.ui.state.ConfirmUM import com.tangem.features.send.common.ui.tapHelp import com.tangem.features.send.send.ui.state.SendUM -import com.tangem.features.send.subcomponents.amount.SendAmountBlockComponent +import com.tangem.features.send.subcomponents.amount.DefaultSendAmountBlockComponent import com.tangem.features.send.subcomponents.destination.DefaultSendDestinationBlockComponent import com.tangem.features.send.subcomponents.notifications import com.tangem.features.send.subcomponents.notifications.DefaultSendNotificationsComponent @@ -31,7 +31,7 @@ private const val BLOCKS_KEY = "BLOCKS_KEY" internal fun SendConfirmContent( sendUM: SendUM, destinationBlockComponent: DefaultSendDestinationBlockComponent, - amountBlockComponent: SendAmountBlockComponent, + amountBlockComponent: DefaultSendAmountBlockComponent, feeSelectorBlockComponent: FeeSelectorBlockComponent, notificationsComponent: DefaultSendNotificationsComponent, notificationsUM: ImmutableList, @@ -73,7 +73,7 @@ internal fun SendConfirmContent( private fun LazyListScope.blocks( destinationBlockComponent: DefaultSendDestinationBlockComponent, - amountBlockComponent: SendAmountBlockComponent, + amountBlockComponent: DefaultSendAmountBlockComponent, feeSelectorBlockComponent: FeeSelectorBlockComponent, ) { item(key = BLOCKS_KEY) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/send/model/SendModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/send/model/SendModel.kt index c1b9d32985..643b2cfae1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/send/model/SendModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/send/model/SendModel.kt @@ -41,13 +41,15 @@ import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.gasless.GetFeeForGaslessUseCase import com.tangem.domain.transaction.usecase.gasless.GetFeeForTokenUseCase import com.tangem.domain.utils.convertToSdkAmount -import com.tangem.domain.wallets.models.GetUserWalletError +import com.tangem.domain.wallets.models.errors.GetUserWalletError import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.send.api.SendComponent import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents.SendScreenSource import com.tangem.features.send.api.entity.PredefinedValues import com.tangem.features.send.api.entity.isFromMainScreenQr +import com.tangem.features.send.api.subcomponents.amount.SendAmountComponent +import com.tangem.features.send.api.subcomponents.amount.SendAmountUpdateTrigger import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponent import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM @@ -58,8 +60,6 @@ import com.tangem.features.send.send.analytics.SendAnalyticEvents import com.tangem.features.send.send.confirm.SendConfirmComponent import com.tangem.features.send.send.success.SendConfirmSuccessComponent import com.tangem.features.send.send.ui.state.SendUM -import com.tangem.features.send.subcomponents.amount.SendAmountComponent -import com.tangem.features.send.subcomponents.amount.SendAmountUpdateTrigger import com.tangem.features.send.subcomponents.destination.model.transformers.SendDestinationInitialStateTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder diff --git a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountBlockComponent.kt b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountBlockComponent.kt similarity index 55% rename from features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountBlockComponent.kt rename to features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountBlockComponent.kt index bb842f8b59..d0ee3243ac 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountBlockComponent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountBlockComponent.kt @@ -8,18 +8,22 @@ import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.ui.AmountBlockV2 import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.send.api.entity.PredefinedValues +import com.tangem.features.send.api.subcomponents.amount.SendAmountBlockComponent +import com.tangem.features.send.api.subcomponents.amount.SendAmountComponentParams import com.tangem.features.send.subcomponents.amount.model.SendAmountModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -internal class SendAmountBlockComponent( - appComponentContext: AppComponentContext, - private val params: SendAmountComponentParams.AmountBlockParams, - val onResult: (AmountState) -> Unit, - val onClick: () -> Unit, -) : ComposableContentComponent, AppComponentContext by appComponentContext { +internal class DefaultSendAmountBlockComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted private val params: SendAmountComponentParams.AmountBlockParams, + @Assisted val onResult: (AmountState) -> Unit, + @Assisted val onClick: () -> Unit, +) : SendAmountBlockComponent, AppComponentContext by appComponentContext { private val model: SendAmountModel = getOrCreateModel(params = params) @@ -29,7 +33,7 @@ internal class SendAmountBlockComponent( }.launchIn(componentScope) } - fun updateState(amountUM: AmountState) = model.updateState(amountUM) + override fun updateState(amountUM: AmountState) = model.updateState(amountUM) @Composable override fun Content(modifier: Modifier) { @@ -44,4 +48,14 @@ internal class SendAmountBlockComponent( modifier = modifier, ) } + + @AssistedFactory + interface Factory : SendAmountBlockComponent.Factory { + override fun create( + context: AppComponentContext, + params: SendAmountComponentParams.AmountBlockParams, + onClick: () -> Unit, + onResult: (AmountState) -> Unit, + ): DefaultSendAmountBlockComponent + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountComponent.kt b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountComponent.kt similarity index 55% rename from features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountComponent.kt rename to features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountComponent.kt index b86140b9db..0732ec6492 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountComponent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountComponent.kt @@ -5,22 +5,24 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.navigationButtons.NavigationModelCallback import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.wallets.models.GetUserWalletError +import com.tangem.features.send.api.subcomponents.amount.SendAmountComponent +import com.tangem.features.send.api.subcomponents.amount.SendAmountComponentParams import com.tangem.features.send.subcomponents.amount.model.SendAmountModel import com.tangem.features.send.subcomponents.amount.ui.SendAmountContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject -internal class SendAmountComponent( - appComponentContext: AppComponentContext, - private val params: SendAmountComponentParams.AmountParams, -) : ComposableContentComponent, AppComponentContext by appComponentContext { +internal class DefaultSendAmountComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted private val params: SendAmountComponentParams.AmountParams, +) : SendAmountComponent, AppComponentContext by appComponentContext { private val model: SendAmountModel = getOrCreateModel(params = params) - fun updateState(amountUM: AmountState) = model.updateState(amountUM) + override fun updateState(amountUM: AmountState) = model.updateState(amountUM) @Composable override fun Content(modifier: Modifier) { @@ -35,10 +37,11 @@ internal class SendAmountComponent( ) } - interface ModelCallback : NavigationModelCallback { - fun onAmountResult(amountUM: AmountState, isResetPredefined: Boolean) - fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean) - fun resetSendNavigation() - fun onError(error: GetUserWalletError) + @AssistedFactory + interface Factory : SendAmountComponent.Factory { + override fun create( + context: AppComponentContext, + params: SendAmountComponentParams.AmountParams, + ): DefaultSendAmountComponent } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountReduceTrigger.kt b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountReduceTrigger.kt similarity index 55% rename from features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountReduceTrigger.kt rename to features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountReduceTrigger.kt index fab7f0ba43..48aea6c92e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/SendAmountReduceTrigger.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountReduceTrigger.kt @@ -1,46 +1,15 @@ package com.tangem.features.send.subcomponents.amount import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData -import kotlinx.coroutines.flow.Flow +import com.tangem.features.send.api.subcomponents.amount.SendAmountReduceListener +import com.tangem.features.send.api.subcomponents.amount.SendAmountReduceTrigger +import com.tangem.features.send.api.subcomponents.amount.SendAmountUpdateListener +import com.tangem.features.send.api.subcomponents.amount.SendAmountUpdateTrigger import kotlinx.coroutines.flow.MutableSharedFlow import java.math.BigDecimal import javax.inject.Inject import javax.inject.Singleton -/** - * Trigger for reducing amount from another component - */ -interface SendAmountReduceTrigger { - suspend fun triggerReduceBy(reduceBy: ReduceByData) - suspend fun triggerReduceTo(reduceTo: BigDecimal) - suspend fun triggerIgnoreReduce() -} - -/** - * Trigger for reducing amount from another component - */ -interface SendAmountReduceListener { - val reduceToTriggerFlow: Flow - val reduceByTriggerFlow: Flow - val ignoreReduceTriggerFlow: Flow -} - -/** - * Trigger amount change from another component. - * Different from another triggers because it takes raw string instead of BigDecimal - */ -interface SendAmountUpdateTrigger { - suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean?) -} - -/** - * Trigger amount change from another component. - * Different from another triggers because it takes raw string instead of BigDecimal - */ -interface SendAmountUpdateListener { - val updateAmountTriggerFlow: Flow> -} - @Singleton internal class DefaultSendAmountReduceTrigger @Inject constructor() : SendAmountReduceTrigger, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/di/SendAmountModule.kt b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/di/SendAmountModule.kt index 644c391e76..6b99065e38 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/di/SendAmountModule.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/di/SendAmountModule.kt @@ -1,10 +1,9 @@ package com.tangem.features.send.subcomponents.amount.di +import com.tangem.features.send.api.subcomponents.amount.* +import com.tangem.features.send.subcomponents.amount.DefaultSendAmountBlockComponent +import com.tangem.features.send.subcomponents.amount.DefaultSendAmountComponent import com.tangem.features.send.subcomponents.amount.DefaultSendAmountReduceTrigger -import com.tangem.features.send.subcomponents.amount.SendAmountReduceListener -import com.tangem.features.send.subcomponents.amount.SendAmountReduceTrigger -import com.tangem.features.send.subcomponents.amount.SendAmountUpdateListener -import com.tangem.features.send.subcomponents.amount.SendAmountUpdateTrigger import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -15,6 +14,16 @@ import javax.inject.Singleton @Module internal interface SendAmountModule { + @Singleton + @Binds + fun provideSendAmountComponentFactory(impl: DefaultSendAmountComponent.Factory): SendAmountComponent.Factory + + @Singleton + @Binds + fun provideSendAmountBlockComponentFactory( + impl: DefaultSendAmountBlockComponent.Factory, + ): SendAmountBlockComponent.Factory + @Singleton @Binds fun provideSendAmountReduceTrigger(impl: DefaultSendAmountReduceTrigger): SendAmountReduceTrigger diff --git a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/model/SendAmountModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/model/SendAmountModel.kt index bd11a2fc81..0b2b8231da 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/model/SendAmountModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/model/SendAmountModel.kt @@ -9,13 +9,13 @@ import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmoun import com.tangem.common.ui.amountScreen.models.AmountParameters import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary +import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.common.ui.navigationButtons.NavigationButton import com.tangem.common.ui.navigationButtons.NavigationUM 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.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.extensions.WrappedList import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase @@ -29,19 +29,21 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.send.api.entity.PredefinedValues import com.tangem.features.send.api.entity.isFromMainScreenQr +import com.tangem.features.send.api.subcomponents.amount.AmountRoute +import com.tangem.features.send.api.subcomponents.amount.SendAmountComponentParams +import com.tangem.features.send.api.subcomponents.amount.SendAmountReduceListener +import com.tangem.features.send.api.subcomponents.amount.SendAmountUpdateListener import com.tangem.features.send.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents import com.tangem.features.send.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents.SelectedCurrencyType import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorReloadTrigger import com.tangem.features.send.common.CommonSendRoute -import com.tangem.features.send.subcomponents.amount.SendAmountComponentParams -import com.tangem.features.send.subcomponents.amount.SendAmountReduceListener -import com.tangem.features.send.subcomponents.amount.SendAmountUpdateListener import com.tangem.features.send.impl.R import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.orZero import com.tangem.utils.isNullOrZero import com.tangem.utils.transformer.update import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.properties.Delegates @@ -271,13 +273,17 @@ internal class SendAmountModel @Inject constructor( override fun onConvertToAnotherToken() { val amountParams = params as? SendAmountComponentParams.AmountParams ?: return - if (amountParams.currentRoute.value.isEditMode) { - sendAmountAlertFactory.showResetSendingAlert { - params.callback.resetSendNavigation() + modelScope.launch { + var isEditMode = false + amountParams.currentRoute.collect { route -> isEditMode = route.isEditMode } + if (isEditMode) { + sendAmountAlertFactory.showResetSendingAlert { + params.callback.resetSendNavigation() + confirmConvertToToken() + } + } else { confirmConvertToToken() } - } else { - confirmConvertToToken() } } @@ -361,7 +367,9 @@ internal class SendAmountModel @Inject constructor( val params = params as? SendAmountComponentParams.AmountParams ?: return combine( flow = uiState, - flow2 = params.currentRoute.filterIsInstance(), + // Filter on the public AmountRoute interface (not the internal CommonSendRoute.Amount) so an + // external host (e.g. staking) that supplies its own AmountRoute is not silently dropped here. + flow2 = params.currentRoute.filterIsInstance(), transform = { state, route -> state to route }, ).onEach { (state, route) -> setSendWithSwapAvailability() diff --git a/features/send/impl/src/test/java/com/tangem/features/send/subcomponents/amount/model/SendAmountNavigationTest.kt b/features/send/impl/src/test/java/com/tangem/features/send/subcomponents/amount/model/SendAmountNavigationTest.kt new file mode 100644 index 0000000000..837e9d2ee3 --- /dev/null +++ b/features/send/impl/src/test/java/com/tangem/features/send/subcomponents/amount/model/SendAmountNavigationTest.kt @@ -0,0 +1,179 @@ +package com.tangem.features.send.subcomponents.amount.model + +import arrow.core.right +import com.google.common.truth.Truth.assertThat +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.navigationButtons.NavigationUM +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.model.MutableParamsContainer +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents +import com.tangem.features.send.api.entity.PredefinedValues +import com.tangem.features.send.api.subcomponents.amount.AmountRoute +import com.tangem.features.send.api.subcomponents.amount.SendAmountComponent +import com.tangem.features.send.api.subcomponents.amount.SendAmountComponentParams +import com.tangem.features.send.api.subcomponents.amount.SendAmountReduceListener +import com.tangem.features.send.api.subcomponents.amount.SendAmountUpdateListener +import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorReloadTrigger +import com.tangem.features.send.impl.R +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.math.BigDecimal + +/** + * Guards the route-decoupling fix: `SendAmountModel.configAmountNavigation()` filters its route flow on + * the public `AmountRoute` interface, not the `internal CommonSendRoute.Amount`. The test feeds a + * foreign `AmountRoute` (which is NOT a `CommonSendRoute.Amount`) and asserts the navigation result is + * still produced — before the fix the `combine`'s `filterIsInstance()` dropped + * it and `onNavigationResult` never fired, leaving an external host (e.g. staking) with a dead Next + * button. Also checks the `isEditMode` → back-icon / primary-button mapping is unaffected. + */ +@OptIn(ExperimentalCoroutinesApi::class) +internal class SendAmountNavigationTest { + + private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase = mockk() + private val sendAmountReduceListener: SendAmountReduceListener = mockk() + private val sendAmountUpdateListener: SendAmountUpdateListener = mockk() + private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true) + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase = mockk() + private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger = mockk(relaxed = true) + private val getUserWalletUseCase: GetUserWalletUseCase = mockk() + private val sendAmountAlertFactory: SendAmountAlertFactory = mockk(relaxed = true) + private val getWalletsUseCase: GetWalletsUseCase = mockk(relaxed = true) + + private val callback: SendAmountComponent.ModelCallback = mockk(relaxed = true) + private val cryptoCurrency = MockCryptoCurrencyFactory().createCoin(Blockchain.Ethereum) + + private var model: SendAmountModel? = null + + @BeforeEach + fun setup() { + clearMocks( + getMinimumTransactionAmountSyncUseCase, + sendAmountReduceListener, + sendAmountUpdateListener, + getSelectedAppCurrencyUseCase, + getUserWalletUseCase, + callback, + ) + // No wallet → the model stays on AmountState.Empty (the heavy AmountStateConverter path is skipped), + // which is all the navigation block needs to emit. + every { getUserWalletUseCase.invokeFlow(any()) } returns emptyFlow() + every { sendAmountReduceListener.reduceToTriggerFlow } returns emptyFlow() + every { sendAmountReduceListener.reduceByTriggerFlow } returns emptyFlow() + every { sendAmountReduceListener.ignoreReduceTriggerFlow } returns emptyFlow() + every { sendAmountUpdateListener.updateAmountTriggerFlow } returns emptyFlow() + coEvery { getSelectedAppCurrencyUseCase.invokeSync() } returns AppCurrency.Default.right() + coEvery { getMinimumTransactionAmountSyncUseCase(any(), any()) } returns BigDecimal.ZERO.right() + } + + @AfterEach + fun tearDown() { + // Cancel modelScope so the long-lived navigation/status collectors stop between tests. + model?.onDestroy() + model = null + } + + @Test + fun `GIVEN a foreign AmountRoute WHEN model created THEN navigation produced with close icon and next button`() = + runTest { + // Arrange — a route that is NOT CommonSendRoute.Amount (the impl type the model used to filter on). + val navSlot = slot() + + // Act + createModel(testScope = this, route = TestAmountRoute(isEditMode = false)) + advanceUntilIdle() + + // Assert — before the fix this never fired for a non-CommonSendRoute.Amount route. + verify(atLeast = 1) { callback.onNavigationResult(capture(navSlot)) } + val content = navSlot.captured as NavigationUM.Content + assertThat(content.backIconRes).isEqualTo(R.drawable.ic_close_24) + assertThat(content.primaryButton.textReference).isEqualTo(resourceReference(R.string.common_next)) + } + + @Test + fun `GIVEN a foreign AmountRoute in edit mode WHEN model created THEN navigation has back icon and continue button`() = + runTest { + // Arrange + val navSlot = slot() + + // Act + createModel(testScope = this, route = TestAmountRoute(isEditMode = true)) + advanceUntilIdle() + + // Assert + verify(atLeast = 1) { callback.onNavigationResult(capture(navSlot)) } + val content = navSlot.captured as NavigationUM.Content + assertThat(content.backIconRes).isEqualTo(R.drawable.ic_back_24) + assertThat(content.primaryButton.textReference).isEqualTo(resourceReference(R.string.common_continue)) + } + + private fun createModel(testScope: TestScope, route: AmountRoute): SendAmountModel { + val params = SendAmountComponentParams.AmountParams( + state = AmountState.Empty, + analyticsCategoryName = "test", + userWalletId = UserWalletId(stringValue = "0123456789"), + appCurrency = AppCurrency.Default, + predefinedValues = PredefinedValues.Empty, + cryptoCurrency = cryptoCurrency, + cryptoCurrencyStatusFlow = MutableStateFlow(mockk(relaxed = true)), + isBalanceHidingFlow = MutableStateFlow(false), + analyticsSendSource = CommonSendAnalyticEvents.CommonSendSource.Send, + accountFlow = MutableStateFlow(null), + isAccountModeFlow = MutableStateFlow(false), + callback = callback, + currentRoute = MutableStateFlow(route), + ) + return SendAmountModel( + paramsContainer = MutableParamsContainer(value = params), + dispatchers = testScope.createTestingCoroutineDispatcherProvider(), + getMinimumTransactionAmountSyncUseCase = getMinimumTransactionAmountSyncUseCase, + sendAmountReduceListener = sendAmountReduceListener, + sendAmountUpdateListener = sendAmountUpdateListener, + analyticsEventHandler = analyticsEventHandler, + getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + feeSelectorReloadTrigger = feeSelectorReloadTrigger, + getUserWalletUseCase = getUserWalletUseCase, + sendAmountAlertFactory = sendAmountAlertFactory, + getWalletsUseCase = getWalletsUseCase, + ).also { model = it } + } + + private fun TestScope.createTestingCoroutineDispatcherProvider(): TestingCoroutineDispatcherProvider { + val testDispatcher = StandardTestDispatcher(testScheduler) + return TestingCoroutineDispatcherProvider( + main = testDispatcher, + mainImmediate = testDispatcher, + io = testDispatcher, + default = testDispatcher, + single = testDispatcher, + ) + } + + private data class TestAmountRoute(override val isEditMode: Boolean) : AmountRoute +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandlerTest.kt b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandlerTest.kt index deefc4ebdf..3301f14437 100644 --- a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandlerTest.kt +++ b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandlerTest.kt @@ -22,7 +22,7 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.tokens.wallet.WalletBalanceFetcher -import com.tangem.domain.wallets.models.GetUserWalletError +import com.tangem.domain.wallets.models.errors.GetUserWalletError import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.SelectWalletUseCase diff --git a/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/deeplink/DefaultPromoDeeplinkHandlerTest.kt b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/deeplink/DefaultPromoDeeplinkHandlerTest.kt index b1d532d910..298beb8462 100644 --- a/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/deeplink/DefaultPromoDeeplinkHandlerTest.kt +++ b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/deeplink/DefaultPromoDeeplinkHandlerTest.kt @@ -24,8 +24,8 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier import com.tangem.domain.wallets.PromoCodeActivationResult -import com.tangem.domain.wallets.models.GetUserWalletError import com.tangem.domain.wallets.models.errors.ActivatePromoCodeError +import com.tangem.domain.wallets.models.errors.GetUserWalletError import com.tangem.domain.wallets.usecase.ActivateBitcoinPromocodeUseCase import com.tangem.domain.wallets.usecase.BindRefcodeWithWalletUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase