Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-16 18:10:00 +04:00
parent 2e8d0bc03b
commit 298e7a5565
29 changed files with 687 additions and 43 deletions

View file

@ -25,6 +25,14 @@ internal sealed class SwapChooseTokenNetworkContentUM : TangemBottomSheetConfigC
override val messageContent: MessageBottomSheetUM,
) : SwapChooseTokenNetworkContentUM()
/**
* Token has no networks available for Send with Swap, but the pair is available in the regular Swap flow.
* Informs the user (rendered like [Error], with a different message).
*/
data class SwapAvailable(
override val messageContent: MessageBottomSheetUM,
) : SwapChooseTokenNetworkContentUM()
data class Content(
override val messageContent: MessageBottomSheetUM,
val swapNetworks: ImmutableList<SwapChooseNetworkUM>,

View file

@ -23,4 +23,24 @@ internal object SwapChooseTokenFactory {
}
}
}
fun getSwapAvailableMessage(tokenName: String, onSwapClick: () -> Unit): MessageBottomSheetUM {
return messageBottomSheetUM {
infoBlock {
icon(R.drawable.ic_alert_triangle_20) {
type = MessageBottomSheetUM.Icon.Type.Attention
backgroundType = MessageBottomSheetUM.Icon.BackgroundType.SameAsTint
}
title = resourceReference(
R.string.express_send_with_swap_not_supported_title,
wrappedList(tokenName),
)
body = resourceReference(R.string.express_send_with_swap_not_supported_text)
}
primaryButton {
text = resourceReference(R.string.express_send_with_swap_not_supported_button)
onClick { onSwapClick() }
}
}
}
}

View file

@ -99,12 +99,18 @@ internal class SwapChooseTokenNetworkModel @Inject constructor(
}
delay(MINIMUM_LOADING_TIME)
if (pairs.fromGroup.available.isEmpty()) {
analyticsEventHandler.send(
val analyticsEvent = if (pairs.fromGroup.availableForSwap.isNotEmpty()) {
SendWithSwapAnalyticEvents.NoticeSwapAvailable(
fromToken = params.initialCurrency,
toTokenSymbol = params.token.symbol,
)
} else {
SendWithSwapAnalyticEvents.NoticeCanNotSwapToken(
fromToken = params.initialCurrency,
toTokenSymbol = params.token.symbol,
),
)
)
}
analyticsEventHandler.send(analyticsEvent)
}
uiState.update(
SwapChooseContentStateTransformer(
@ -112,6 +118,7 @@ internal class SwapChooseTokenNetworkModel @Inject constructor(
onNetworkClick = ::onSwapTokenClick,
tokenName = params.token.name,
onDismiss = params.onDismiss,
onSwapClick = params.onSwapClick,
),
)
}

View file

@ -12,6 +12,7 @@ import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapCho
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkContentUM
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkUM
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.SwapChooseTokenFactory.getErrorMessage
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.SwapChooseTokenFactory.getSwapAvailableMessage
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toPersistentList
@ -21,6 +22,7 @@ internal class SwapChooseContentStateTransformer(
private val tokenName: String,
private val onNetworkClick: (SwapCurrencies, CryptoCurrency) -> Unit,
private val onDismiss: () -> Unit,
private val onSwapClick: (CryptoCurrency) -> Unit,
) : Transformer<SwapChooseTokenNetworkUM> {
override fun transform(prevState: SwapChooseTokenNetworkUM): SwapChooseTokenNetworkUM {
val swapNetworks = pairs.fromGroup.available.map { availableCurrency ->
@ -51,16 +53,26 @@ internal class SwapChooseContentStateTransformer(
return prevState.copy(
bottomSheetConfig = prevState.bottomSheetConfig.copy(
content = if (swapNetworks.isNotEmpty()) {
SwapChooseTokenNetworkContentUM.Content(
content = when {
swapNetworks.isNotEmpty() -> SwapChooseTokenNetworkContentUM.Content(
swapNetworks = swapNetworks,
messageContent = getErrorMessage(
tokenName = tokenName,
onDismiss = onDismiss,
),
)
} else {
SwapChooseTokenNetworkContentUM.Error(
// No networks for Send with Swap, but the pair is available in the regular Swap flow
pairs.fromGroup.availableForSwap.isNotEmpty() -> {
val firstAvailableForSwap = pairs.fromGroup.availableForSwap.first()
val swapTargetCurrency = firstAvailableForSwap.currencyStatus.currency
SwapChooseTokenNetworkContentUM.SwapAvailable(
messageContent = getSwapAvailableMessage(
tokenName = tokenName,
onSwapClick = { onSwapClick(swapTargetCurrency) },
),
)
}
else -> SwapChooseTokenNetworkContentUM.Error(
messageContent = getErrorMessage(
tokenName = tokenName,
onDismiss = onDismiss,

View file

@ -112,6 +112,19 @@ internal sealed class SendWithSwapAnalyticEvents(
),
)
/** Token can't be used in Send with Swap, but is available in the regular Swap flow */
data class NoticeSwapAvailable(
val fromToken: CryptoCurrency,
val toTokenSymbol: String,
) : SendWithSwapAnalyticEvents(
event = "Notice - Swap Available",
params = mapOf(
SEND_TOKEN to fromToken.symbol,
RECEIVE_TOKEN to toTokenSymbol,
SEND_BLOCKCHAIN to fromToken.network.name,
),
)
data object NoticeFixedRate : SendWithSwapAnalyticEvents(
event = "Notice - Fixed Rate",
params = emptyMap(),

View file

@ -0,0 +1,80 @@
package com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.transformers
import com.google.common.truth.Truth.assertThat
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.swap.models.SwapCryptoCurrency
import com.tangem.domain.swap.models.SwapCurrencies
import com.tangem.domain.swap.models.SwapCurrenciesGroup
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkContentUM
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkUM
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.SwapChooseTokenFactory
import io.mockk.mockk
import org.junit.jupiter.api.Test
internal class SwapChooseContentStateTransformerTest {
private val tokenName = "Shiba Inu"
@Test
fun `GIVEN no available but availableForSwap present WHEN transform THEN content is SwapAvailable`() {
// Arrange
val pairs = swapCurrencies(available = emptyList(), availableForSwap = listOf(swapCryptoCurrency()))
// Act
val result = transformer(pairs).transform(prevState())
// Assert
assertThat(result.bottomSheetConfig.content)
.isInstanceOf(SwapChooseTokenNetworkContentUM.SwapAvailable::class.java)
}
@Test
fun `GIVEN no available and no availableForSwap WHEN transform THEN content is Error`() {
// Arrange
val pairs = swapCurrencies(available = emptyList(), availableForSwap = emptyList())
// Act
val result = transformer(pairs).transform(prevState())
// Assert
assertThat(result.bottomSheetConfig.content)
.isInstanceOf(SwapChooseTokenNetworkContentUM.Error::class.java)
}
private fun transformer(pairs: SwapCurrencies) = SwapChooseContentStateTransformer(
pairs = pairs,
tokenName = tokenName,
onNetworkClick = { _, _ -> },
onDismiss = {},
onSwapClick = {},
)
private fun swapCurrencies(
available: List<SwapCryptoCurrency>,
availableForSwap: List<SwapCryptoCurrency>,
): SwapCurrencies = SwapCurrencies.EMPTY.copy(
fromGroup = SwapCurrenciesGroup(
available = available,
unavailable = emptyList(),
isAfterSearch = false,
availableForSwap = availableForSwap,
),
)
private fun swapCryptoCurrency(): SwapCryptoCurrency = SwapCryptoCurrency(
currencyStatus = CryptoCurrencyStatus(currency = mockk<CryptoCurrency>(relaxed = true), value = mockk(relaxed = true)),
providers = emptyList(),
)
private fun prevState(): SwapChooseTokenNetworkUM = SwapChooseTokenNetworkUM(
bottomSheetConfig = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = SwapChooseTokenNetworkContentUM.Loading(
messageContent = SwapChooseTokenFactory.getErrorMessage(tokenName = tokenName, onDismiss = {}),
),
),
)
}