Updated on 2026-08-14
This commit is contained in:
parent
3069b8d32d
commit
f5e14a9ab4
13 changed files with 179 additions and 166 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue