diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 92f4f91db5..c23995df13 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -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, diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index ca1d72018b..a7fe90f04a 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -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 { 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 -> diff --git a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderInitialStateTest.kt b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderInitialStateTest.kt index d0ef725adf..daee7b3ac7 100644 --- a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderInitialStateTest.kt +++ b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderInitialStateTest.kt @@ -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(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, + ) } \ No newline at end of file