Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-25 11:59:59 +05:00
parent 3069b8d32d
commit f5e14a9ab4
13 changed files with 179 additions and 166 deletions

View file

@ -1,8 +1,10 @@
package com.tangem.features.send.api.subcomponents.amount
import com.tangem.core.decompose.navigation.Route
/**
* Common route for amount
*/
interface AmountRoute {
interface AmountRoute : Route {
val isEditMode: Boolean
}

View file

@ -3,10 +3,10 @@ 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.core.ui.decompose.ComposableModularContentComponent
import com.tangem.domain.wallets.models.errors.GetUserWalletError
interface SendAmountComponent : ComposableContentComponent {
interface SendAmountComponent : ComposableModularContentComponent {
fun updateState(amountUM: AmountState)

View file

@ -9,7 +9,6 @@ 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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
sealed class SendAmountComponentParams {
@ -39,7 +38,7 @@ sealed class SendAmountComponentParams {
override val accountFlow: StateFlow<Account?>,
override val isAccountModeFlow: StateFlow<Boolean>,
val callback: SendAmountComponent.ModelCallback,
val currentRoute: Flow<AmountRoute>,
val route: AmountRoute,
) : SendAmountComponentParams()
data class AmountBlockParams(

View file

@ -1,8 +1,10 @@
package com.tangem.features.send.api.subcomponents.destination
import com.tangem.core.decompose.navigation.Route
/**
* Common route for destination
*/
interface DestinationRoute {
interface DestinationRoute : Route {
val isEditMode: Boolean
}

View file

@ -2,10 +2,10 @@ package com.tangem.features.send.api.subcomponents.destination
import com.tangem.common.ui.navigationButtons.NavigationModelCallback
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.decompose.ComposableModularContentComponent
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
interface SendDestinationComponent : ComposableContentComponent {
interface SendDestinationComponent : ComposableModularContentComponent {
fun updateState(destinationUM: DestinationUM)

View file

@ -6,7 +6,6 @@ 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.api.subcomponents.destination.entity.DestinationUM
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
sealed class SendDestinationComponentParams {
@ -26,7 +25,7 @@ sealed class SendDestinationComponentParams {
override val userWalletId: UserWalletId,
val title: TextReference,
val isBalanceHidingFlow: StateFlow<Boolean>,
val currentRoute: Flow<DestinationRoute>,
val route: DestinationRoute,
val callback: SendDestinationComponent.ModelCallback,
override val isAllowSelfSend: Boolean = false,
) : SendDestinationComponentParams()

View file

@ -16,7 +16,7 @@ internal sealed class CommonSendRoute : Route {
@Serializable
data object Confirm : CommonSendRoute() {
override val isEditMode: Boolean = true
override val isEditMode: Boolean = false
}
@Serializable

View file

@ -1,14 +1,23 @@
package com.tangem.features.send.subcomponents.amount
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.api.subcomponents.amount.SendAmountComponent
import com.tangem.features.send.api.subcomponents.amount.SendAmountComponentParams
import com.tangem.features.send.impl.R
import com.tangem.features.send.subcomponents.amount.model.SendAmountModel
import com.tangem.features.send.subcomponents.amount.ui.SendAmountContent
import dagger.assisted.Assisted
@ -24,6 +33,23 @@ internal class DefaultSendAmountComponent @AssistedInject constructor(
override fun updateState(amountUM: AmountState) = model.updateState(amountUM)
@Composable
override fun Title() {
AppBarWithBackButtonAndIcon(
text = stringResourceSafe(R.string.send_amount_label),
onBackClick = {
params.callback.onBackClick(params.route)
},
backIconRes = if (params.route.isEditMode) {
R.drawable.ic_back_24
} else {
R.drawable.ic_close_24
},
backgroundColor = TangemTheme.colors.background.tertiary,
modifier = Modifier.height(TangemTheme.dimens.size56),
)
}
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
@ -37,6 +63,30 @@ internal class DefaultSendAmountComponent @AssistedInject constructor(
)
}
@Composable
override fun Footer() {
val state by model.uiState.collectAsStateWithLifecycle()
PrimaryButton(
text = if (params.route.isEditMode) {
stringResourceSafe(R.string.common_continue)
} else {
stringResourceSafe(R.string.common_next)
},
enabled = state.isPrimaryButtonEnabled,
onClick = {
model.onAmountNext()
params.callback.onNextClick(params.route)
},
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
)
}
@AssistedFactory
interface Factory : SendAmountComponent.Factory {
override fun create(

View file

@ -10,8 +10,6 @@ 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
@ -29,14 +27,12 @@ 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.impl.R
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
@ -84,7 +80,6 @@ internal class SendAmountModel @Inject constructor(
private var maxAmountBoundary: EnterAmountBoundary by Delegates.notNull()
init {
configAmountNavigation()
initAppCurrency()
subscribeOnCryptoCurrencyStatusFlow()
subscribeOnAmountReduceByTriggerUpdates()
@ -104,6 +99,7 @@ internal class SendAmountModel @Inject constructor(
},
ifRight = { wallet ->
userWallet = wallet
setSendWithSwapAvailability()
},
)
}.launchIn(modelScope)
@ -274,9 +270,7 @@ internal class SendAmountModel @Inject constructor(
override fun onConvertToAnotherToken() {
val amountParams = params as? SendAmountComponentParams.AmountParams ?: return
modelScope.launch {
var isEditMode = false
amountParams.currentRoute.collect { route -> isEditMode = route.isEditMode }
if (isEditMode) {
if (amountParams.route.isEditMode) {
sendAmountAlertFactory.showResetSendingAlert {
params.callback.resetSendNavigation()
confirmConvertToToken()
@ -363,44 +357,6 @@ internal class SendAmountModel @Inject constructor(
)
}
private fun configAmountNavigation() {
val params = params as? SendAmountComponentParams.AmountParams ?: return
combine(
flow = uiState,
// 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<AmountRoute>(),
transform = { state, route -> state to route },
).onEach { (state, route) ->
setSendWithSwapAvailability()
params.callback.onNavigationResult(
NavigationUM.Content(
source = CommonSendRoute.Amount::class.java.simpleName,
title = resourceReference(R.string.send_amount_label),
subtitle = null,
backIconRes = if (route.isEditMode) {
R.drawable.ic_back_24
} else {
R.drawable.ic_close_24
},
backIconClick = params.callback::onBackClick,
primaryButton = NavigationButton(
textReference = if (route.isEditMode) {
resourceReference(R.string.common_continue)
} else {
resourceReference(R.string.common_next)
},
isEnabled = state.isPrimaryButtonEnabled,
onClick = {
onAmountNext()
params.callback.onNextClick()
},
),
),
)
}.launchIn(modelScope)
}
private fun setSendWithSwapAvailability() {
// Allowed only in multicurrency wallets
val isMultiCurrency = userWallet?.isMultiCurrency == true

View file

@ -1,8 +1,13 @@
package com.tangem.features.send.subcomponents.destination
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
@ -14,9 +19,15 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.addressbook.AddressBookContactsBlockComponent
import com.tangem.features.addressbook.AddressBookFeatureToggles
import com.tangem.features.addressbook.AddressSelectorComponent
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
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
import com.tangem.features.send.impl.R
import com.tangem.features.send.subcomponents.destination.model.SendDestinationModel
import com.tangem.features.send.subcomponents.destination.ui.SendDestinationContent
import dagger.assisted.Assisted
@ -68,6 +79,22 @@ internal class DefaultSendDestinationComponent @AssistedInject constructor(
override fun updateState(destinationUM: DestinationUM) = model.updateState(destinationUM)
@Composable
override fun Title() {
BackHandler(onBack = model::onBackClick)
AppBarWithBackButtonAndIcon(
text = params.title.resolveReference(),
onBackClick = model::onBackClick,
backIconRes = if (params.route.isEditMode) {
R.drawable.ic_back_24
} else {
R.drawable.ic_close_24
},
backgroundColor = TangemTheme.colors.background.tertiary,
modifier = Modifier.height(TangemTheme.dimens.size56),
)
}
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
@ -83,6 +110,30 @@ internal class DefaultSendDestinationComponent @AssistedInject constructor(
selector.child?.instance?.BottomSheet()
}
@Composable
override fun Footer() {
val state by model.uiState.collectAsStateWithLifecycle()
PrimaryButton(
text = if (params.route.isEditMode) {
stringResourceSafe(R.string.common_continue)
} else {
stringResourceSafe(R.string.common_next)
},
enabled = state.isPrimaryButtonEnabled,
onClick = {
model.saveResult()
params.callback.onNextClick(params.route)
},
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
)
}
@AssistedFactory
interface Factory : SendDestinationComponent.Factory {
override fun create(

View file

@ -8,14 +8,11 @@ import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.entity.AddressBookOpenMode
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.core.decompose.navigation.Router
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.GetBackupProblematicWalletForAddressUseCase
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
@ -49,8 +46,6 @@ import com.tangem.features.send.api.entity.PredefinedValues
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.common.CommonSendRoute
import com.tangem.features.send.impl.R
import com.tangem.features.send.subcomponents.destination.SendDestinationAlertFactory
import com.tangem.features.send.subcomponents.destination.analytics.EnterAddressSource
import com.tangem.features.send.subcomponents.destination.analytics.SendDestinationAnalyticEvents
@ -142,7 +137,6 @@ internal class SendDestinationModel @Inject constructor(
private val backupProblematicWalletCache = AtomicReference<Pair<String, UserWalletId?>?>(null)
init {
configDestinationNavigation()
subscribeOnQRScannerResult()
initialState()
resetContactOnEdit()
@ -153,15 +147,12 @@ internal class SendDestinationModel @Inject constructor(
private fun resetContactOnEdit() {
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
params.currentRoute
.filter { it.isEditMode }
.onEach {
val content = uiState.value as? DestinationUM.Content ?: return@onEach
if (content.addressTextField.contactName != null) {
_uiState.update(SendDestinationContactTransformer(contact = null))
}
if (params.route.isEditMode) {
val content = uiState.value as? DestinationUM.Content ?: return
if (content.addressTextField.contactName != null) {
_uiState.update(SendDestinationContactTransformer(contact = null))
}
.launchIn(modelScope)
}
}
fun onContactClick(contact: MatchedContact) {
@ -259,7 +250,23 @@ internal class SendDestinationModel @Inject constructor(
)
}
private fun saveResult() {
fun onBackClick() {
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
if (!params.route.isEditMode) {
analyticsEventHandler.send(
CommonSendAnalyticEvents.CloseButtonClicked(
categoryName = params.analyticsCategoryName,
source = SendScreenSource.Address,
isFromSummary = false,
isValid = uiState.value.isPrimaryButtonEnabled,
),
)
saveResult()
}
params.callback.onBackClick(params.route)
}
fun saveResult() {
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
params.callback.onDestinationResult(uiState.value)
}
@ -490,60 +497,10 @@ internal class SendDestinationModel @Inject constructor(
private fun autoNextFromRecipient(type: EnterAddressSource, isValidAddress: Boolean, isValidMemo: Boolean) {
if (type.isAutoNext && isValidAddress && isValidMemo) {
saveResult()
(params as? SendDestinationComponentParams.DestinationParams)?.callback?.onNextClick()
(params as? SendDestinationComponentParams.DestinationParams)?.callback?.onNextClick(params.route)
}
}
@Suppress("LongMethod")
private fun configDestinationNavigation() {
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
combine(
flow = uiState,
flow2 = params.currentRoute,
transform = { state, route -> state to route },
).onEach { (state, route) ->
params.callback.onNavigationResult(
NavigationUM.Content(
source = CommonSendRoute.Destination::class.java.simpleName,
title = params.title,
subtitle = null,
backIconRes = if (route.isEditMode) {
R.drawable.ic_back_24
} else {
R.drawable.ic_close_24
},
backIconClick = {
if (!route.isEditMode) {
analyticsEventHandler.send(
CommonSendAnalyticEvents.CloseButtonClicked(
categoryName = params.analyticsCategoryName,
source = SendScreenSource.Address,
isFromSummary = false,
isValid = state.isPrimaryButtonEnabled,
),
)
saveResult()
}
params.callback.onBackClick()
},
primaryButton = NavigationButton(
textReference = if (route.isEditMode) {
resourceReference(R.string.common_continue)
} else {
resourceReference(R.string.common_next)
},
isEnabled = state.isPrimaryButtonEnabled,
onClick = {
saveResult()
params.callback.onNextClick()
},
),
secondaryPairButtonsUM = null,
),
)
}.launchIn(modelScope)
}
private companion object {
const val RECENT_TX_SIZE = 100
const val RECENT_LOAD_DELAY = 500L

View file

@ -1,14 +1,11 @@
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
@ -25,13 +22,11 @@ import com.tangem.features.send.api.subcomponents.amount.SendAmountComponentPara
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
@ -46,12 +41,14 @@ 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<CommonSendRoute.Amount>()` 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.
* Guards the route-decoupling fix at the model level. The amount step is now hostable by foreign flows
* (e.g. staking / send-with-swap) that supply their own [AmountRoute] implementation rather than the
* `internal CommonSendRoute.Amount`. The navigation chrome (back icon / Next vs Continue) moved out of
* the model into `DefaultSendAmountComponent`, but the model still makes a route-driven decision in
* [SendAmountModel.onConvertToAnotherToken]: it reads `params.route.isEditMode` *directly* (previously
* it collected the route flow). This test feeds a foreign [AmountRoute] and verifies that edit-mode
* branches to the "reset sending" alert while a fresh (non-edit) route converts immediately both via
* the public interface, not `CommonSendRoute`.
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal class SendAmountNavigationTest {
@ -79,10 +76,11 @@ internal class SendAmountNavigationTest {
sendAmountUpdateListener,
getSelectedAppCurrencyUseCase,
getUserWalletUseCase,
sendAmountAlertFactory,
callback,
)
// No wallet → the model stays on AmountState.Empty (the heavy AmountStateConverter path is skipped),
// which is all the navigation block needs to emit.
// which is all these route-driven assertions need.
every { getUserWalletUseCase.invokeFlow(any()) } returns emptyFlow()
every { sendAmountReduceListener.reduceToTriggerFlow } returns emptyFlow()
every { sendAmountReduceListener.reduceByTriggerFlow } returns emptyFlow()
@ -94,43 +92,41 @@ internal class SendAmountNavigationTest {
@AfterEach
fun tearDown() {
// Cancel modelScope so the long-lived navigation/status collectors stop between tests.
// Cancel modelScope so the long-lived reduce/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<NavigationUM>()
// 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`() =
fun `GIVEN foreign AmountRoute in edit mode WHEN onConvertToAnotherToken THEN reset sending alert shown`() =
runTest {
// Arrange
val navSlot = slot<NavigationUM>()
// 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))
// Act
model?.onConvertToAnotherToken()
advanceUntilIdle()
// Assert — edit mode must guard the conversion behind the reset-sending confirmation.
verify(exactly = 1) { sendAmountAlertFactory.showResetSendingAlert(any()) }
verify(exactly = 0) { callback.onConvertToAnotherToken(any(), any()) }
}
@Test
fun `GIVEN foreign AmountRoute not in edit mode WHEN onConvertToAnotherToken THEN converts without alert`() =
runTest {
// Arrange
createModel(testScope = this, route = TestAmountRoute(isEditMode = false))
advanceUntilIdle()
// Act
model?.onConvertToAnotherToken()
advanceUntilIdle()
// Assert — a fresh route converts immediately, with no reset-sending alert.
verify(exactly = 0) { sendAmountAlertFactory.showResetSendingAlert(any()) }
verify(exactly = 1) { callback.onConvertToAnotherToken(any(), any()) }
}
private fun createModel(testScope: TestScope, route: AmountRoute): SendAmountModel {
@ -147,7 +143,7 @@ internal class SendAmountNavigationTest {
accountFlow = MutableStateFlow<Account?>(null),
isAccountModeFlow = MutableStateFlow(false),
callback = callback,
currentRoute = MutableStateFlow(route),
route = route,
)
return SendAmountModel(
paramsContainer = MutableParamsContainer(value = params),