Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-07 15:45:42 +03:00
parent 8564f21d01
commit 7740d6f453
18 changed files with 167 additions and 50 deletions

View file

@ -291,7 +291,6 @@ internal object TransactionDomainModule {
@Singleton @Singleton
fun provideGetFeeForGaslessUseCase( fun provideGetFeeForGaslessUseCase(
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
demoConfig: DemoConfig,
gaslessTransactionRepository: GaslessTransactionRepository, gaslessTransactionRepository: GaslessTransactionRepository,
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
getFeeUseCase: GetFeeUseCase, getFeeUseCase: GetFeeUseCase,
@ -299,7 +298,7 @@ internal object TransactionDomainModule {
): GetFeeForGaslessUseCase { ): GetFeeForGaslessUseCase {
return GetFeeForGaslessUseCase( return GetFeeForGaslessUseCase(
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
demoConfig = demoConfig, demoConfig = DemoConfig,
gaslessTransactionRepository = gaslessTransactionRepository, gaslessTransactionRepository = gaslessTransactionRepository,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase, getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase,
@ -311,7 +310,6 @@ internal object TransactionDomainModule {
@Singleton @Singleton
fun provideGetFeeForTokenUseCase( fun provideGetFeeForTokenUseCase(
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
demoConfig: DemoConfig,
gaslessTransactionRepository: GaslessTransactionRepository, gaslessTransactionRepository: GaslessTransactionRepository,
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase, getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase,
@ -319,7 +317,7 @@ internal object TransactionDomainModule {
return GetFeeForTokenUseCase( return GetFeeForTokenUseCase(
gaslessTransactionRepository = gaslessTransactionRepository, gaslessTransactionRepository = gaslessTransactionRepository,
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
demoConfig = demoConfig, demoConfig = DemoConfig,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase, getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase,
) )

View file

@ -25,9 +25,9 @@ class MockedGaslessTransactionRepository(
val usdcPolygon = responseCryptoCurrenciesFactory.createToken( val usdcPolygon = responseCryptoCurrenciesFactory.createToken(
blockchain = Blockchain.Polygon, blockchain = Blockchain.Polygon,
sdkToken = Token( sdkToken = Token(
contractAddress = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
name = "USD Coin", name = "Tether",
symbol = "USDC", symbol = "USDT",
decimals = 6, decimals = 6,
), ),
network = network, network = network,

View file

@ -114,7 +114,9 @@ internal class TokenFeeCalculator(
.toBigInteger() .toBigInteger()
val feeInTokenCurrency = coinPriceInTokenInBigDecimal.multiply( val feeInTokenCurrency = coinPriceInTokenInBigDecimal.multiply(
feeInNativeCurrency.toBigDecimal(), feeInNativeCurrency
.toBigDecimal()
.movePointLeft(nativeCurrencyStatus.currency.decimals),
) )
val tokenBalance = tokenForPayFeeStatus.value.amount ?: BigDecimal.ZERO val tokenBalance = tokenForPayFeeStatus.value.amount ?: BigDecimal.ZERO

View file

@ -1,6 +1,8 @@
package com.tangem.features.send.v2.api.entity package com.tangem.features.send.v2.api.entity
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
@ -44,7 +46,7 @@ sealed class FeeSelectorUM {
is Custom, is Custom,
-> AnalyticsParam.FeeType.Custom -> AnalyticsParam.FeeType.Custom
is Fast -> AnalyticsParam.FeeType.Max is Fast -> AnalyticsParam.FeeType.Max
is Market -> AnalyticsParam.FeeType.Normal is Market, is FeeItem.Loading -> AnalyticsParam.FeeType.Normal
is Slow -> AnalyticsParam.FeeType.Min is Slow -> AnalyticsParam.FeeType.Min
} }
} }
@ -84,6 +86,16 @@ sealed class FeeItem {
return this::class == other::class return this::class == other::class
} }
fun isLoading(): Boolean {
return this is Loading
}
object Loading : FeeItem() {
override val title: TextReference = resourceReference(R.string.common_fee_selector_option_market)
override val iconRes: Int = R.drawable.ic_bird_24
override val fee: Fee = Fee.Common(Amount(BigDecimal.ZERO, Blockchain.Unknown))
}
data class Suggested( data class Suggested(
override val title: TextReference, override val title: TextReference,
override val fee: Fee, override val fee: Fee,

View file

@ -15,7 +15,7 @@ import com.tangem.features.send.v2.api.entity.FeeSelectorUM
sealed class FeeSelectorParams { sealed class FeeSelectorParams {
abstract val state: FeeSelectorUM abstract val state: FeeSelectorUM
abstract val userWalletId: UserWalletId abstract val userWalletId: UserWalletId
abstract val onLoadFeeExtended: (suspend () -> Either<GetFeeError, TransactionFeeExtended>)? abstract val onLoadFeeExtended: (suspend (CryptoCurrencyStatus?) -> Either<GetFeeError, TransactionFeeExtended>)?
abstract val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee> abstract val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>
abstract val cryptoCurrencyStatus: CryptoCurrencyStatus abstract val cryptoCurrencyStatus: CryptoCurrencyStatus
abstract val feeCryptoCurrencyStatus: CryptoCurrencyStatus abstract val feeCryptoCurrencyStatus: CryptoCurrencyStatus
@ -27,7 +27,9 @@ sealed class FeeSelectorParams {
data class FeeSelectorBlockParams( data class FeeSelectorBlockParams(
override val state: FeeSelectorUM, override val state: FeeSelectorUM,
override val userWalletId: UserWalletId, override val userWalletId: UserWalletId,
override val onLoadFeeExtended: (suspend () -> Either<GetFeeError, TransactionFeeExtended>)? = null, override val onLoadFeeExtended: (
suspend (CryptoCurrencyStatus?) -> Either<GetFeeError, TransactionFeeExtended>
)? = null,
override val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>, override val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>,
override val cryptoCurrencyStatus: CryptoCurrencyStatus, override val cryptoCurrencyStatus: CryptoCurrencyStatus,
override val feeCryptoCurrencyStatus: CryptoCurrencyStatus, override val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
@ -40,7 +42,9 @@ sealed class FeeSelectorParams {
data class FeeSelectorDetailsParams( data class FeeSelectorDetailsParams(
override val state: FeeSelectorUM, override val state: FeeSelectorUM,
override val userWalletId: UserWalletId, override val userWalletId: UserWalletId,
override val onLoadFeeExtended: (suspend () -> Either<GetFeeError, TransactionFeeExtended>)? = null, override val onLoadFeeExtended: (
suspend (CryptoCurrencyStatus?) -> Either<GetFeeError, TransactionFeeExtended>
)? = null,
override val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>, override val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>,
override val cryptoCurrencyStatus: CryptoCurrencyStatus, override val cryptoCurrencyStatus: CryptoCurrencyStatus,
override val feeCryptoCurrencyStatus: CryptoCurrencyStatus, override val feeCryptoCurrencyStatus: CryptoCurrencyStatus,

View file

@ -47,6 +47,7 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
params = FeeSelectorParams.FeeSelectorDetailsParams( params = FeeSelectorParams.FeeSelectorDetailsParams(
state = model.uiState.value, state = model.uiState.value,
onLoadFee = params.onLoadFee, onLoadFee = params.onLoadFee,
onLoadFeeExtended = params.onLoadFeeExtended,
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus, feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
cryptoCurrencyStatus = params.cryptoCurrencyStatus, cryptoCurrencyStatus = params.cryptoCurrencyStatus,
callback = model, callback = model,

View file

@ -29,10 +29,6 @@ class FeeExtendedSelectorModel @Inject constructor(
private val params = paramsContainer.require<FeeSelectorComponentParams>() private val params = paramsContainer.require<FeeSelectorComponentParams>()
private var appCurrency: AppCurrency = AppCurrency.Default private var appCurrency: AppCurrency = AppCurrency.Default
private val tokenConverter = SelectedTokenItemConverter(
appCurrencyProvider = Provider { appCurrency },
onTokenClick = { router.push(FeeSelectorRoute.ChooseToken) },
)
init { init {
initAppCurrency() initAppCurrency()
@ -44,19 +40,13 @@ class FeeExtendedSelectorModel @Inject constructor(
init { init {
params.state params.state
.filterIsInstance<FeeSelectorUM.Content>() .filterIsInstance<FeeSelectorUM.Content>()
.onEach(::onParentStateUpdated) .onEach { state -> uiState.value = convertState(state) }
.launchIn(modelScope) .launchIn(modelScope)
} }
private fun getInitialState(): FeeExtendedSelectorUM { private fun getInitialState(): FeeExtendedSelectorUM {
val parentContentState = params.state.value as FeeSelectorUM.Content val parentContentState = params.state.value as FeeSelectorUM.Content
return convertState(parentContentState)
return FeeExtendedSelectorUM(
parent = parentContentState,
token = tokenConverter.convert(parentContentState.feeExtraInfo.feeCryptoCurrencyStatus),
fee = parentContentState.selectedFeeItem,
onFeeClick = { router.push(FeeSelectorRoute.ChooseSpeed) },
)
} }
private fun initAppCurrency() { private fun initAppCurrency() {
@ -65,13 +55,18 @@ class FeeExtendedSelectorModel @Inject constructor(
} }
} }
private fun onParentStateUpdated(state: FeeSelectorUM.Content) { private fun convertState(state: FeeSelectorUM.Content): FeeExtendedSelectorUM {
uiState.update { current -> val tokenConverter = SelectedTokenItemConverter(
current.copy( appCurrencyProvider = Provider { appCurrency },
parent = state, onTokenClick = { router.push(FeeSelectorRoute.ChooseToken) },
token = tokenConverter.convert(state.feeExtraInfo.feeCryptoCurrencyStatus), isTokenSelectionAvailable = state.feeExtraInfo.availableFeeCurrencies.orEmpty().size > 1,
fee = state.selectedFeeItem, )
)
} return FeeExtendedSelectorUM(
parent = state,
token = tokenConverter.convert(state.feeExtraInfo.feeCryptoCurrencyStatus),
fee = state.selectedFeeItem,
onFeeClick = { router.push(FeeSelectorRoute.ChooseSpeed) },
)
} }
} }

View file

@ -16,6 +16,7 @@ import com.tangem.utils.converter.Converter
internal class SelectedTokenItemConverter( internal class SelectedTokenItemConverter(
private val appCurrencyProvider: Provider<AppCurrency>, private val appCurrencyProvider: Provider<AppCurrency>,
private val onTokenClick: () -> Unit, private val onTokenClick: () -> Unit,
private val isTokenSelectionAvailable: Boolean,
) : Converter<CryptoCurrencyStatus, TokenItemState> { ) : Converter<CryptoCurrencyStatus, TokenItemState> {
override fun convert(value: CryptoCurrencyStatus): TokenItemState { override fun convert(value: CryptoCurrencyStatus): TokenItemState {
@ -24,9 +25,14 @@ internal class SelectedTokenItemConverter(
} }
val appCurrency = appCurrencyProvider() val appCurrency = appCurrencyProvider()
val tokenItemConverter = TokenItemStateConverter( val tokenItemConverter = TokenItemStateConverter(
appCurrency = appCurrencyProvider(), appCurrency = appCurrencyProvider(),
onItemClick = { _, _ -> onTokenClick() }, onItemClick = if (isTokenSelectionAvailable) {
{ _, _ -> onTokenClick() }
} else {
null
},
) )
val state = tokenItemConverter.convert(value) val state = tokenItemConverter.convert(value)
@ -38,10 +44,14 @@ internal class SelectedTokenItemConverter(
} }
state.copy( state.copy(
fiatAmountState = TokenItemState.FiatAmountState.Icon( fiatAmountState = if (isTokenSelectionAvailable) {
iconRes = R.drawable.ic_select_18_24, TokenItemState.FiatAmountState.Icon(
tint = IconTint.Informative, iconRes = R.drawable.ic_select_18_24,
), tint = IconTint.Informative,
)
} else {
TokenItemState.FiatAmountState.Empty
},
subtitleState = TokenItemState.SubtitleState.TextContent( subtitleState = TokenItemState.SubtitleState.TextContent(
value = resourceReference( value = resourceReference(
R.string.common_balance, R.string.common_balance,
@ -50,7 +60,6 @@ internal class SelectedTokenItemConverter(
isAvailable = false, isAvailable = false,
), ),
subtitle2State = null, subtitle2State = null,
) )
} }
else -> state else -> state

View file

@ -22,6 +22,7 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.icons.IconTint import com.tangem.core.ui.components.icons.IconTint
import com.tangem.core.ui.components.token.TokenItem import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fee import com.tangem.core.ui.format.bigdecimal.fee
@ -61,12 +62,15 @@ fun FeeExtendedSelectorContent(state: FeeExtendedSelectorUM, modifier: Modifier
val feeFiatRateUM = state.parent.feeFiatRateUM val feeFiatRateUM = state.parent.feeFiatRateUM
val fee = state.fee.fee val fee = state.fee.fee
val isChooseSpeedAvailable = state.parent.feeItems.size > 1
RegularFeeItemContent( RegularFeeItemContent(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(TangemTheme.dimens.radius14)) .clip(RoundedCornerShape(TangemTheme.dimens.radius14))
.clickable(onClick = state.onFeeClick) .conditional(isChooseSpeedAvailable) {
clickable(onClick = state.onFeeClick)
}
.background( .background(
TangemTheme.colors.background.action, TangemTheme.colors.background.action,
RoundedCornerShape(TangemTheme.dimens.radius14), RoundedCornerShape(TangemTheme.dimens.radius14),
@ -93,7 +97,8 @@ fun FeeExtendedSelectorContent(state: FeeExtendedSelectorUM, modifier: Modifier
null null
}, },
ellipsizeOffset = fee.amount.currencySymbol.length, ellipsizeOffset = fee.amount.currencySymbol.length,
showSelectorIcon = true, showSelectorIcon = isChooseSpeedAvailable,
isLoading = state.fee.isLoading(),
) )
} }
} }

View file

@ -38,6 +38,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.ui.amountScreen.utils.getFiatReference import com.tangem.common.ui.amountScreen.utils.getFiatReference
import com.tangem.core.ui.components.SpacerH8 import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.components.inputrow.InputRowEnter import com.tangem.core.ui.components.inputrow.InputRowEnter
@ -325,6 +326,7 @@ internal fun RegularFeeItemContent(
postDot: TextReference? = null, postDot: TextReference? = null,
ellipsizeOffset: Int? = null, ellipsizeOffset: Int? = null,
showSelectorIcon: Boolean = false, showSelectorIcon: Boolean = false,
isLoading: Boolean = false,
) { ) {
Column(modifier = modifier) { Column(modifier = modifier) {
Row( Row(
@ -349,6 +351,7 @@ internal fun RegularFeeItemContent(
preDot = preDot, preDot = preDot,
postDot = postDot, postDot = postDot,
ellipsizeOffset = ellipsizeOffset, ellipsizeOffset = ellipsizeOffset,
isLoading = isLoading,
) )
if (showSelectorIcon) { if (showSelectorIcon) {
@ -371,6 +374,7 @@ private fun FeeDescription(
preDot: TextReference? = null, preDot: TextReference? = null,
postDot: TextReference? = null, postDot: TextReference? = null,
ellipsizeOffset: Int? = null, ellipsizeOffset: Int? = null,
isLoading: Boolean = false,
) { ) {
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(2.dp)) { Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text( Text(
@ -379,8 +383,12 @@ private fun FeeDescription(
style = TangemTheme.typography.subtitle2, style = TangemTheme.typography.subtitle2,
modifier = Modifier.testTag(SendSelectNetworkFeeBottomSheetTestTags.REGULAR_ITEM_TITLE), modifier = Modifier.testTag(SendSelectNetworkFeeBottomSheetTestTags.REGULAR_ITEM_TITLE),
) )
if (preDot != null) { when {
FeeValueContent(preDot = preDot, postDot = postDot, ellipsizeOffset = ellipsizeOffset) isLoading -> TextShimmer(
modifier = Modifier.width(122.dp),
style = TangemTheme.typography.caption1,
)
preDot != null -> FeeValueContent(preDot = preDot, postDot = postDot, ellipsizeOffset = ellipsizeOffset)
} }
} }
} }

View file

@ -22,6 +22,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -47,7 +48,8 @@ internal class FeeTokenSelectorModel @Inject constructor(
initAppCurrency() initAppCurrency()
params.state params.state
.onEach { uiState.value = stateFromParent(it as FeeSelectorUM.Content) } .filterIsInstance<FeeSelectorUM.Content>()
.onEach { state -> uiState.value = stateFromParent(state) }
.launchIn(modelScope) .launchIn(modelScope)
} }

View file

@ -92,6 +92,7 @@ internal class FeeSelectorAlertFactory @Inject constructor(
is FeeItem.Slow -> newTransactionFee.minimum is FeeItem.Slow -> newTransactionFee.minimum
is FeeItem.Market -> newTransactionFee.normal is FeeItem.Market -> newTransactionFee.normal
is FeeItem.Fast -> newTransactionFee.priority is FeeItem.Fast -> newTransactionFee.priority
FeeItem.Loading -> newTransactionFee.normal
is FeeItem.Custom -> return is FeeItem.Custom -> return
} }
} }

View file

@ -8,6 +8,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.error.GetFeeError
@ -31,6 +32,7 @@ import com.tangem.utils.transformer.update
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -56,6 +58,7 @@ internal class FeeSelectorLogic @AssistedInject constructor(
) : FeeSelectorIntents { ) : FeeSelectorIntents {
private var appCurrency: AppCurrency = AppCurrency.Default private var appCurrency: AppCurrency = AppCurrency.Default
private var selectedToken: CryptoCurrencyStatus? = null
val uiState = MutableStateFlow<FeeSelectorUM>(params.state) val uiState = MutableStateFlow<FeeSelectorUM>(params.state)
init { init {
@ -112,9 +115,19 @@ internal class FeeSelectorLogic @AssistedInject constructor(
} }
override fun onTokenSelected(status: CryptoCurrencyStatus) { override fun onTokenSelected(status: CryptoCurrencyStatus) {
if (status.currency !is CryptoCurrency.Token ||
status.currency.id == params.feeCryptoCurrencyStatus.currency.id) {
selectedToken = null
loadFee()
return
}
uiState.update(FeeSelectorTokenSelectedTransformer(status))
uiState.update { state -> uiState.update { state ->
if (state is FeeSelectorUM.Content) { if (state is FeeSelectorUM.Content) {
state.copy( state.copy(
selectedFeeItem = FeeItem.Loading,
feeItems = persistentListOf(FeeItem.Loading),
feeExtraInfo = state.feeExtraInfo.copy( feeExtraInfo = state.feeExtraInfo.copy(
feeCryptoCurrencyStatus = status, feeCryptoCurrencyStatus = status,
), ),
@ -123,6 +136,14 @@ internal class FeeSelectorLogic @AssistedInject constructor(
state state
} }
} }
if (status.currency is CryptoCurrency.Token) {
selectedToken = status
} else {
selectedToken = null
}
loadFee()
} }
override fun onCustomFeeValueChange(index: Int, value: String) { override fun onCustomFeeValueChange(index: Int, value: String) {
@ -219,7 +240,7 @@ internal class FeeSelectorLogic @AssistedInject constructor(
private suspend fun callLoadFee(): Either<GetFeeError, LoadedFeeResult> { private suspend fun callLoadFee(): Either<GetFeeError, LoadedFeeResult> {
val extended = params.onLoadFeeExtended val extended = params.onLoadFeeExtended
return if (extended != null && sendFeatureToggles.isGaslessTransactionsEnabled) { return if (extended != null && sendFeatureToggles.isGaslessTransactionsEnabled) {
extended().fold( extended(selectedToken).fold(
ifLeft = { error -> ifLeft = { error ->
if (error is GetFeeError.GaslessError) { if (error is GetFeeError.GaslessError) {
params.onLoadFee().map { LoadedFeeResult.Basic(it) } params.onLoadFee().map { LoadedFeeResult.Basic(it) }

View file

@ -43,7 +43,8 @@ internal class FeeSelectorLoadedTransformer(
) )
val selectedFee = when (prevState) { val selectedFee = when (prevState) {
is FeeSelectorUM.Content -> feeItems.first { it.isSameClass(prevState.selectedFeeItem) } is FeeSelectorUM.Content ->
feeItems.firstOrNull { it.isSameClass(prevState.selectedFeeItem) } ?: FeeItem.Loading
is FeeSelectorUM.Error, is FeeSelectorUM.Error,
FeeSelectorUM.Loading, FeeSelectorUM.Loading,
-> feeItems.find { it is FeeItem.Suggested } ?: feeItems.first { it is FeeItem.Market } -> feeItems.find { it is FeeItem.Suggested } ?: feeItems.first { it is FeeItem.Market }
@ -52,7 +53,7 @@ internal class FeeSelectorLoadedTransformer(
val nonce = ((prevState as? FeeSelectorUM.Content)?.feeNonce as? FeeNonce.Nonce)?.nonce val nonce = ((prevState as? FeeSelectorUM.Content)?.feeNonce as? FeeNonce.Nonce)?.nonce
return FeeSelectorUM.Content( return FeeSelectorUM.Content(
isPrimaryButtonEnabled = true, isPrimaryButtonEnabled = selectedFee !is FeeItem.Loading,
fees = fees.transactionFee, fees = fees.transactionFee,
feeItems = feeItems, feeItems = feeItems,
selectedFeeItem = selectedFee, selectedFeeItem = selectedFee,

View file

@ -0,0 +1,26 @@
package com.tangem.features.send.v2.feeselector.model.transformers
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.FeeItem
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
class FeeSelectorTokenSelectedTransformer(
private val selectedToken: CryptoCurrencyStatus,
) : Transformer<FeeSelectorUM> {
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
return if (prevState is FeeSelectorUM.Content) {
prevState.copy(
selectedFeeItem = FeeItem.Loading,
feeItems = persistentListOf(FeeItem.Loading),
feeExtraInfo = prevState.feeExtraInfo.copy(
feeCryptoCurrencyStatus = selectedToken,
),
)
} else {
prevState
}
}
}

View file

@ -216,6 +216,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
callback = model, callback = model,
predefinedValues = model.predefinedValues, predefinedValues = model.predefinedValues,
onLoadFee = model::loadFee, onLoadFee = model::loadFee,
onLoadFeeExtended = model::loadFeeExtended,
onSendTransaction = { onSendTransaction = {
innerRouter.replaceAll(CommonSendRoute.ConfirmSuccess) innerRouter.replaceAll(CommonSendRoute.ConfirmSuccess)
}, },

View file

@ -15,6 +15,7 @@ import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.models.TransactionFeeExtended
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent import com.tangem.features.send.v2.api.SendNotificationsComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
@ -86,6 +87,7 @@ internal class SendConfirmComponent(
params = FeeSelectorParams.FeeSelectorBlockParams( params = FeeSelectorParams.FeeSelectorBlockParams(
state = model.uiState.value.feeSelectorUM, state = model.uiState.value.feeSelectorUM,
onLoadFee = params.onLoadFee, onLoadFee = params.onLoadFee,
onLoadFeeExtended = params.onLoadFeeExtended,
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus, feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
cryptoCurrencyStatus = params.cryptoCurrencyStatus, cryptoCurrencyStatus = params.cryptoCurrencyStatus,
feeStateConfiguration = model.feeStateConfiguration, feeStateConfiguration = model.feeStateConfiguration,
@ -164,6 +166,7 @@ internal class SendConfirmComponent(
val isBalanceHidingFlow: StateFlow<Boolean>, val isBalanceHidingFlow: StateFlow<Boolean>,
val predefinedValues: PredefinedValues, val predefinedValues: PredefinedValues,
val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>, val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>,
val onLoadFeeExtended: suspend (CryptoCurrencyStatus?) -> Either<GetFeeError, TransactionFeeExtended>,
val onSendTransaction: () -> Unit, val onSendTransaction: () -> Unit,
) )

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
import arrow.core.Either import arrow.core.Either
import arrow.core.getOrElse import arrow.core.getOrElse
import arrow.core.left import arrow.core.left
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.navigationButtons.NavigationUM import com.tangem.common.ui.navigationButtons.NavigationUM
@ -37,8 +38,11 @@ import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.models.TransactionFeeExtended
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.transaction.usecase.gasless.GetFeeForGaslessUseCase
import com.tangem.domain.transaction.usecase.gasless.GetFeeForTokenUseCase
import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.models.GetUserWalletError import com.tangem.domain.wallets.models.GetUserWalletError
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
@ -95,6 +99,8 @@ internal class SendModel @Inject constructor(
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase, private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
private val getFeeUseCase: GetFeeUseCase, private val getFeeUseCase: GetFeeUseCase,
private val getFeeForGaslessUseCase: GetFeeForGaslessUseCase,
private val getFeeForTokenUseCase: GetFeeForTokenUseCase,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase, private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val sendAmountUpdateTrigger: SendAmountUpdateTrigger, private val sendAmountUpdateTrigger: SendAmountUpdateTrigger,
@ -245,10 +251,10 @@ internal class SendModel @Inject constructor(
showAlertError() showAlertError()
} }
suspend fun loadFee(): Either<GetFeeError, TransactionFee> { suspend fun prepareTransferTransaction(): Either<Throwable, TransactionData> {
val predefinedValues = predefinedValues val predefinedValues = predefinedValues
val cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value val cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value
val transferTransaction = if (predefinedValues is PredefinedValues.Content.Deeplink) { return if (predefinedValues is PredefinedValues.Content.Deeplink) {
val predefinedAmount = predefinedValues.amount.parseBigDecimalOrNull() val predefinedAmount = predefinedValues.amount.parseBigDecimalOrNull()
createTransferTransactionUseCase( createTransferTransactionUseCase(
amount = predefinedAmount?.convertToSdkAmount(cryptoCurrencyStatus) ?: error("Invalid amount"), amount = predefinedAmount?.convertToSdkAmount(cryptoCurrencyStatus) ?: error("Invalid amount"),
@ -271,9 +277,12 @@ internal class SendModel @Inject constructor(
userWalletId = userWallet.walletId, userWalletId = userWallet.walletId,
network = cryptoCurrency.network, network = cryptoCurrency.network,
) )
}.getOrElse {
return GetFeeError.DataError(it).left()
} }
}
suspend fun loadFee(): Either<GetFeeError, TransactionFee> {
val transferTransaction = prepareTransferTransaction()
.getOrElse { return GetFeeError.DataError(it).left() }
return getFeeUseCase( return getFeeUseCase(
transactionData = transferTransaction, transactionData = transferTransaction,
@ -282,6 +291,25 @@ internal class SendModel @Inject constructor(
) )
} }
suspend fun loadFeeExtended(maybeToken: CryptoCurrencyStatus?): Either<GetFeeError, TransactionFeeExtended> {
val transferTransaction = prepareTransferTransaction()
.getOrElse { return GetFeeError.DataError(it).left() }
return if (maybeToken == null) {
getFeeForGaslessUseCase(
transactionData = transferTransaction,
userWallet = userWallet,
network = params.currency.network,
)
} else {
getFeeForTokenUseCase(
transactionData = transferTransaction,
userWallet = userWallet,
token = maybeToken.currency,
)
}
}
fun showAlertError() { fun showAlertError() {
sendConfirmAlertFactory.getGenericErrorState( sendConfirmAlertFactory.getGenericErrorState(
onFailedTxEmailClick = ::onFailedTxEmailClick, onFailedTxEmailClick = ::onFailedTxEmailClick,