Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-19 17:36:37 +01:00
parent 075db99e51
commit 4a7d9143c2
3 changed files with 121 additions and 8 deletions

View file

@ -43,10 +43,10 @@ import com.tangem.datasource.local.appsflyer.AppsFlyerStore
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.express.models.ProviderFilterType
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
@ -541,6 +541,10 @@ internal class SwapModel @Inject constructor(
)
},
),
isTransferMode = swapTransferInteractor.shouldTransferInsteadOfSwap(
fromSwapCurrency = fromSwapCurrencyStatus?.currency,
toSwapCurrency = toSwapCurrencyStatus?.currency,
),
),
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
@ -623,6 +627,10 @@ internal class SwapModel @Inject constructor(
)
},
),
isTransferMode = swapTransferInteractor.shouldTransferInsteadOfSwap(
fromSwapCurrency = newFromSwapCurrencyStatus?.currency,
toSwapCurrency = newToSwapCurrencyStatus?.currency,
),
),
fromSwapCurrencyStatus = newFromSwapCurrencyStatus,
toSwapCurrencyStatus = newToSwapCurrencyStatus,
@ -741,6 +749,10 @@ internal class SwapModel @Inject constructor(
)
},
),
isTransferMode = swapTransferInteractor.shouldTransferInsteadOfSwap(
fromSwapCurrency = fromSwapCurrencyStatus.currency,
toSwapCurrency = toSwapCurrencyStatus.currency,
),
),
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,

View file

@ -153,10 +153,15 @@ internal class StateBuilder(
isHoldToConfirm = fromSwapCurrencyStatus?.userWallet?.isHotWallet == true,
onClick = { },
),
shouldShowMaxAmount = shouldShowMaxAmount(fromSwapCurrencyStatus?.currency, toSwapCurrencyStatus?.currency),
predefinedButtons = createPredefinedButtons(
shouldShowMaxAmount = shouldShowMaxAmount(
fromSwapCurrencyStatus?.currency,
toSwapCurrencyStatus?.currency,
emptyAmountState.isTransferMode,
),
predefinedButtons = createPredefinedButtons(
fromToken = fromSwapCurrencyStatus?.currency,
toCurrency = toSwapCurrencyStatus?.currency,
isTransferMode = emptyAmountState.isTransferMode,
),
changeCardsButtonState = ChangeCardsButtonState.ENABLED,
providerState = ProviderState.Empty(),
@ -244,6 +249,11 @@ internal class StateBuilder(
toSwapCurrencyStatus: SwapCurrencyStatus?,
shouldResetAmount: Boolean,
): SwapStateHolder {
val shouldShowMaxAmount = shouldShowMaxAmount(
fromSwapCurrencyStatus?.currency,
toSwapCurrencyStatus?.currency,
emptyAmountState.isTransferMode,
)
return uiStateHolder.copy(
sendCardData = uiStateHolder.sendCardData.updateCurrencyStatus(
swapCurrencyStatus = fromSwapCurrencyStatus,
@ -267,10 +277,11 @@ internal class StateBuilder(
isHoldToConfirm = fromSwapCurrencyStatus?.userWallet?.isHotWallet == true,
onClick = { },
),
shouldShowMaxAmount = shouldShowMaxAmount(fromSwapCurrencyStatus?.currency, toSwapCurrencyStatus?.currency),
shouldShowMaxAmount = shouldShowMaxAmount,
predefinedButtons = createPredefinedButtons(
fromSwapCurrencyStatus?.currency,
toSwapCurrencyStatus?.currency,
fromToken = fromSwapCurrencyStatus?.currency,
toCurrency = toSwapCurrencyStatus?.currency,
isTransferMode = emptyAmountState.isTransferMode,
),
changeCardsButtonState = ChangeCardsButtonState.ENABLED,
providerState = ProviderState.Empty(),
@ -683,7 +694,12 @@ internal class StateBuilder(
)
}
private fun shouldShowMaxAmount(fromToken: CryptoCurrency?, toCurrency: CryptoCurrency?): Boolean {
private fun shouldShowMaxAmount(
fromToken: CryptoCurrency?,
toCurrency: CryptoCurrency?,
isTransferMode: Boolean = false,
): Boolean {
if (isTransferMode) return true
return !(fromToken is CryptoCurrency.Coin && fromToken.network.id == toCurrency?.network?.id)
}
@ -696,9 +712,10 @@ internal class StateBuilder(
private fun createPredefinedButtons(
fromToken: CryptoCurrency?,
toCurrency: CryptoCurrency?,
isTransferMode: Boolean = false,
): ImmutableList<PredefinedPercentButtonUM> {
if (!swapFeatureToggles.isSwapPredefinedButtonsEnabled) return persistentListOf()
val shouldShowMaxAmount = shouldShowMaxAmount(fromToken, toCurrency)
val shouldShowMaxAmount = shouldShowMaxAmount(fromToken, toCurrency, isTransferMode)
return PredefinedPercentAmount.entries
.filter { it != PredefinedPercentAmount.MAX || shouldShowMaxAmount }
.map { percent ->

View file

@ -7,6 +7,7 @@ import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.swap.models.SwapCurrencyStatus
@ -270,6 +271,50 @@ internal class StateBuilderInitialStateTest {
assertThat(result.notifications).isEmpty()
}
@Test
fun `GIVEN same coin on different wallets in transfer mode WHEN called THEN shouldShowMaxAmount is true`() {
// Arrange — same coin (shared network id) moved between two wallets => transfer mode
val sharedNetwork = buildSharedNetwork()
val walletA: UserWallet.Cold = mockk(relaxed = true) { every { walletId } returns UserWalletId("aabb") }
val walletB: UserWallet.Cold = mockk(relaxed = true) { every { walletId } returns UserWalletId("ccdd") }
val fromStatus = buildCoinSwapCurrencyStatus(walletA, sharedNetwork)
val toStatus = buildCoinSwapCurrencyStatus(walletB, sharedNetwork)
val transferState = SwapState.EmptyAmountState(
zeroAmountEquivalent = com.tangem.core.ui.extensions.stringReference("$0.00"),
isTransferMode = true,
)
// Act
val result = sut.createInitialReadyState(
uiStateHolder = baseState,
emptyAmountState = transferState,
fromSwapCurrencyStatus = fromStatus,
toSwapCurrencyStatus = toStatus,
)
// Assert
assertThat(result.shouldShowMaxAmount).isTrue()
}
@Test
fun `GIVEN same-network coin swap not in transfer mode WHEN called THEN shouldShowMaxAmount is false`() {
// Arrange — same network coin pair, regular swap => MAX hidden to keep balance for the fee
val sharedNetwork = buildSharedNetwork()
val fromStatus = buildCoinSwapCurrencyStatus(userWallet, sharedNetwork)
val toStatus = buildCoinSwapCurrencyStatus(userWallet, sharedNetwork)
// Act
val result = sut.createInitialReadyState(
uiStateHolder = baseState,
emptyAmountState = emptyAmountState,
fromSwapCurrencyStatus = fromStatus,
toSwapCurrencyStatus = toStatus,
)
// Assert
assertThat(result.shouldShowMaxAmount).isFalse()
}
}
// endregion
@ -574,4 +619,43 @@ internal fun buildSwapCurrencyStatus(
status = cryptoCurrencyStatus,
account = account,
)
}
/**
* A single [Network] mock whose [Network.id] resolves to one shared instance, so two currencies built from
* it compare equal on `network.id` the condition that gates [StateBuilder.shouldShowMaxAmount].
*/
internal fun buildSharedNetwork(): Network = mockk(relaxed = true) {
every { id } returns mockk(relaxed = true)
every { name } returns "Ethereum"
every { currencySymbol } returns "ETH"
every { rawId } returns "ethereum"
}
/**
* Builds a [SwapCurrencyStatus] backed by a [CryptoCurrency.Coin] on the given [network]. Pass the same
* [network] instance to two calls to model the "same coin on different wallets" (transfer) case.
*/
internal fun buildCoinSwapCurrencyStatus(
userWallet: UserWallet,
network: Network,
): SwapCurrencyStatus {
val account = Account.CryptoPortfolio.createMainAccount(userWallet.walletId)
val currency = mockk<CryptoCurrency.Coin>(relaxed = true) {
every { symbol } returns "ETH"
every { decimals } returns 18
every { name } returns "Ethereum"
every { this@mockk.network } returns network
}
val statusValue: CryptoCurrencyStatus.Value = mockk(relaxed = true) {
every { amount } returns java.math.BigDecimal("1.0")
every { fiatRate } returns java.math.BigDecimal("2000.00")
every { fiatAmount } returns java.math.BigDecimal("2000.00")
}
val cryptoCurrencyStatus = CryptoCurrencyStatus(currency = currency, value = statusValue)
return SwapCurrencyStatus(
userWallet = userWallet,
status = cryptoCurrencyStatus,
account = account,
)
}