diff --git a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationModelCallback.kt b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationModelCallback.kt index 2e84e796a8..e21434050b 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationModelCallback.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationModelCallback.kt @@ -1,7 +1,8 @@ package com.tangem.common.ui.navigationButtons +import com.tangem.core.decompose.navigation.Route + interface NavigationModelCallback { - fun onNavigationResult(navigationUM: NavigationUM) - fun onBackClick() - fun onNextClick() + fun onBackClick(currentRoute: Route) + fun onNextClick(currentRoute: Route) } \ No newline at end of file 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 index afcdebbae2..a4a4ce0301 100644 --- 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 @@ -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 } \ 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 index 79e3d44a57..759d7edb35 100644 --- 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 @@ -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) diff --git a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponentParams.kt b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponentParams.kt index bda588bfee..e976dbb5ca 100644 --- a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponentParams.kt +++ b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/amount/SendAmountComponentParams.kt @@ -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, override val isAccountModeFlow: StateFlow, val callback: SendAmountComponent.ModelCallback, - val currentRoute: Flow, + val route: AmountRoute, ) : SendAmountComponentParams() data class AmountBlockParams( diff --git a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/DestinationRoute.kt b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/DestinationRoute.kt index c83ce3ebf0..79ac0df1d3 100644 --- a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/DestinationRoute.kt +++ b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/DestinationRoute.kt @@ -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 } \ No newline at end of file diff --git a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/SendDestinationComponent.kt b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/SendDestinationComponent.kt index 6a323c5bf3..4eb849925a 100644 --- a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/SendDestinationComponent.kt +++ b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/SendDestinationComponent.kt @@ -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) diff --git a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/SendDestinationComponentParams.kt b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/SendDestinationComponentParams.kt index ead4f35164..7399b00475 100644 --- a/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/SendDestinationComponentParams.kt +++ b/features/send/api/src/main/java/com/tangem/features/send/api/subcomponents/destination/SendDestinationComponentParams.kt @@ -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, - val currentRoute: Flow, + val route: DestinationRoute, val callback: SendDestinationComponent.ModelCallback, override val isAllowSelfSend: Boolean = false, ) : SendDestinationComponentParams() 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 c098d9b414..9483285003 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 @@ -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 diff --git a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountComponent.kt b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountComponent.kt index 0732ec6492..2546c2eb3a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountComponent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/amount/DefaultSendAmountComponent.kt @@ -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( 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 0b2b8231da..2d505312ed 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 @@ -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(), - 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 diff --git a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/DefaultSendDestinationComponent.kt b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/DefaultSendDestinationComponent.kt index e2f98cccea..648d4e5a93 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/DefaultSendDestinationComponent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/DefaultSendDestinationComponent.kt @@ -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( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/model/SendDestinationModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/model/SendDestinationModel.kt index d493255dca..055470d142 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/model/SendDestinationModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/model/SendDestinationModel.kt @@ -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?>(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 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 index 837e9d2ee3..57a55fc5b8 100644 --- 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 @@ -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()` 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() - - // 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() - - // 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(null), isAccountModeFlow = MutableStateFlow(false), callback = callback, - currentRoute = MutableStateFlow(route), + route = route, ) return SendAmountModel( paramsContainer = MutableParamsContainer(value = params),