Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-06 13:44:56 +03:00
parent 680794d447
commit a61004a3c1
18 changed files with 459 additions and 106 deletions

View file

@ -0,0 +1,18 @@
package com.tangem.core.decompose.utils
import com.arkivanov.decompose.router.stack.StackNavigation
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
/**
* Retrieves the current stack of items from the [com.arkivanov.decompose.router.stack.ChildStack]
* @return A list representing the current stack of items
*/
suspend fun <T : Any> StackNavigation<T>.stack(): List<T> {
return suspendCancellableCoroutine { continuation ->
this.navigate(
transformer = { stack -> stack },
onComplete = { newStack, oldStack -> continuation.resume(newStack) },
)
}
}

View file

@ -21,6 +21,9 @@ dependencies {
/** Common */ /** Common */
implementation(projects.common.ui) implementation(projects.common.ui)
/** Domain */
implementation(projects.domain.transaction)
/** Domain models */ /** Domain models */
api(projects.domain.models) api(projects.domain.models)
implementation(projects.domain.appCurrency.models) implementation(projects.domain.appCurrency.models)

View file

@ -7,6 +7,7 @@ import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.features.send.v2.api.R import com.tangem.features.send.v2.api.R
import com.tangem.features.send.v2.api.entity.FeeItem.* import com.tangem.features.send.v2.api.entity.FeeItem.*
@ -61,6 +62,7 @@ data class FeeExtraInfo(
val isFeeApproximate: Boolean, val isFeeApproximate: Boolean,
val isFeeConvertibleToFiat: Boolean, val isFeeConvertibleToFiat: Boolean,
val isTronToken: Boolean, val isTronToken: Boolean,
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
) )
sealed class FeeNonce { sealed class FeeNonce {

View file

@ -6,12 +6,14 @@ import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
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.analytics.CommonSendAnalyticEvents import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
import com.tangem.features.send.v2.api.entity.FeeSelectorUM 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 onLoadFeeExtended: (suspend () -> 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
@ -22,6 +24,7 @@ sealed class FeeSelectorParams {
data class FeeSelectorBlockParams( data class FeeSelectorBlockParams(
override val state: FeeSelectorUM, override val state: FeeSelectorUM,
override val onLoadFeeExtended: (suspend () -> 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,
@ -33,6 +36,7 @@ sealed class FeeSelectorParams {
data class FeeSelectorDetailsParams( data class FeeSelectorDetailsParams(
override val state: FeeSelectorUM, override val state: FeeSelectorUM,
override val onLoadFeeExtended: (suspend () -> 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

@ -5,10 +5,8 @@ import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.essenty.instancekeeper.getOrCreateSimple
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
@ -26,7 +24,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@Suppress("PropertyUsedBeforeDeclaration")
internal class DefaultFeeSelectorComponent @AssistedInject constructor( internal class DefaultFeeSelectorComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext, @Assisted appComponentContext: AppComponentContext,
@Assisted private val params: FeeSelectorParams.FeeSelectorDetailsParams, @Assisted private val params: FeeSelectorParams.FeeSelectorDetailsParams,
@ -35,22 +32,22 @@ internal class DefaultFeeSelectorComponent @AssistedInject constructor(
private val feeSpeedSelectorComponentFactory: FeeSpeedSelectorComponent.Factory, private val feeSpeedSelectorComponentFactory: FeeSpeedSelectorComponent.Factory,
) : FeeSelectorComponent, AppComponentContext by appComponentContext { ) : FeeSelectorComponent, AppComponentContext by appComponentContext {
private val stackNavigation = instanceKeeper.getOrCreateSimple { StackNavigation<FeeSelectorRoute>() } private val model: FeeSelectorModel = getOrCreateModel(params = params)
private val innerRouter = InnerRouter<FeeSelectorRoute>(
stackNavigation = stackNavigation, val innerRouter = InnerRouter<FeeSelectorRoute>(
stackNavigation = model.stackNavigation,
popCallback = { popCallback = {
stackNavigation.pop { success -> model.stackNavigation.pop { success ->
if (!success) { if (!success) {
dismiss() dismiss()
} }
} }
}, },
) )
private val model: FeeSelectorModel = getOrCreateModel(params = params, router = innerRouter)
private val contentStack = childStack( private val contentStack = childStack(
key = "FeeSelectorDetailsStack", key = "FeeSelectorDetailsStack",
source = stackNavigation, source = model.stackNavigation,
serializer = FeeSelectorRoute.serializer(), serializer = FeeSelectorRoute.serializer(),
initialConfiguration = model.getInitialRoute(), initialConfiguration = model.getInitialRoute(),
handleBackButton = false, handleBackButton = false,
@ -69,7 +66,7 @@ internal class DefaultFeeSelectorComponent @AssistedInject constructor(
FeeSelectorModalBottomSheet( FeeSelectorModalBottomSheet(
childStack = contentStack, childStack = contentStack,
state = state, state = state,
feeSelectorIntents = model.intents, feeSelectorIntents = model,
feeDisplaySource = params.feeDisplaySource, feeDisplaySource = params.feeDisplaySource,
onDismiss = onDismiss, onDismiss = onDismiss,
onBack = innerRouter::pop, onBack = innerRouter::pop,
@ -85,7 +82,7 @@ internal class DefaultFeeSelectorComponent @AssistedInject constructor(
val componentParams = FeeSelectorComponentParams( val componentParams = FeeSelectorComponentParams(
parentParams = params, parentParams = params,
state = model.uiState, state = model.uiState,
intents = model.intents, intents = model,
) )
return when (config) { return when (config) {

View file

@ -1,23 +1,59 @@
package com.tangem.features.send.v2.feeselector.component.extended.entity package com.tangem.features.send.v2.feeselector.component.extended.entity
import com.tangem.common.ui.tokens.TokenItemStateConverter import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.core.ui.components.icons.IconTint
import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.impl.R
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
internal class SelectedTokenItemConverter( internal class SelectedTokenItemConverter(
appCurrency: AppCurrency, private val appCurrencyProvider: Provider<AppCurrency>,
onTokenClick: () -> Unit, private val onTokenClick: () -> Unit,
) : Converter<CryptoCurrencyStatus, TokenItemState> { ) : Converter<CryptoCurrencyStatus, TokenItemState> {
private val tokenItemConverter = TokenItemStateConverter(
appCurrency = appCurrency,
onItemClick = { _, _ -> onTokenClick() },
)
override fun convert(value: CryptoCurrencyStatus): TokenItemState { override fun convert(value: CryptoCurrencyStatus): TokenItemState {
// TODO if (value.value !is CryptoCurrencyStatus.Loaded) {
return tokenItemConverter.convert(value) return TokenItemState.Loading(id = value.currency.id.value)
}
val appCurrency = appCurrencyProvider()
val tokenItemConverter = TokenItemStateConverter(
appCurrency = appCurrencyProvider(),
onItemClick = { _, _ -> onTokenClick() },
)
val state = tokenItemConverter.convert(value)
return when (state) {
is TokenItemState.Content -> {
val balance = value.value.fiatAmount.format {
fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol)
}
state.copy(
fiatAmountState = TokenItemState.FiatAmountState.Icon(
iconRes = R.drawable.ic_select_18_24,
tint = IconTint.Informative,
),
subtitleState = TokenItemState.SubtitleState.TextContent(
value = resourceReference(
R.string.common_balance,
wrappedList(balance),
),
isAvailable = false,
),
subtitle2State = null,
)
}
else -> state
}
} }
} }

View file

@ -13,6 +13,7 @@ import com.tangem.features.send.v2.feeselector.component.FeeSelectorComponentPar
import com.tangem.features.send.v2.feeselector.component.extended.entity.FeeExtendedSelectorUM import com.tangem.features.send.v2.feeselector.component.extended.entity.FeeExtendedSelectorUM
import com.tangem.features.send.v2.feeselector.component.extended.entity.SelectedTokenItemConverter import com.tangem.features.send.v2.feeselector.component.extended.entity.SelectedTokenItemConverter
import com.tangem.features.send.v2.feeselector.route.FeeSelectorRoute import com.tangem.features.send.v2.feeselector.route.FeeSelectorRoute
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -29,44 +30,48 @@ 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 {
initAppCurrency()
}
val uiState: StateFlow<FeeExtendedSelectorUM> val uiState: StateFlow<FeeExtendedSelectorUM>
field = MutableStateFlow<FeeExtendedSelectorUM>(getInitialState()) field = MutableStateFlow<FeeExtendedSelectorUM>(getInitialState())
init { init {
modelScope.launch {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
uiState.value = getInitialState()
}
params.state params.state
.filterIsInstance<FeeSelectorUM.Content>()
.onEach(::onParentStateUpdated) .onEach(::onParentStateUpdated)
.launchIn(modelScope) .launchIn(modelScope)
} }
private fun getInitialState(): FeeExtendedSelectorUM { private fun getInitialState(): FeeExtendedSelectorUM {
val status = params.parentParams.feeCryptoCurrencyStatus
val parentContentState = params.state.value as FeeSelectorUM.Content val parentContentState = params.state.value as FeeSelectorUM.Content
return FeeExtendedSelectorUM( return FeeExtendedSelectorUM(
parent = parentContentState, parent = parentContentState,
token = SelectedTokenItemConverter( token = tokenConverter.convert(parentContentState.feeExtraInfo.feeCryptoCurrencyStatus),
appCurrency = appCurrency,
onTokenClick = {
router.push(FeeSelectorRoute.ChooseToken)
},
).convert(status),
fee = parentContentState.selectedFeeItem, fee = parentContentState.selectedFeeItem,
onFeeClick = { onFeeClick = { router.push(FeeSelectorRoute.ChooseSpeed) },
router.push(FeeSelectorRoute.ChooseSpeed)
},
) )
} }
private fun onParentStateUpdated(state: FeeSelectorUM) { private fun initAppCurrency() {
modelScope.launch {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
}
}
private fun onParentStateUpdated(state: FeeSelectorUM.Content) {
uiState.update { current -> uiState.update { current ->
current.copy( current.copy(
parent = state as FeeSelectorUM.Content, parent = state,
token = tokenConverter.convert(state.feeExtraInfo.feeCryptoCurrencyStatus),
fee = state.selectedFeeItem,
) )
} }
} }

View file

@ -29,6 +29,9 @@ import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
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.network.Network
import com.tangem.features.send.v2.api.entity.* import com.tangem.features.send.v2.api.entity.*
import com.tangem.features.send.v2.feeselector.component.extended.entity.FeeExtendedSelectorUM import com.tangem.features.send.v2.feeselector.component.extended.entity.FeeExtendedSelectorUM
import com.tangem.features.send.v2.feeselector.component.speed.ui.RegularFeeItemContent import com.tangem.features.send.v2.feeselector.component.speed.ui.RegularFeeItemContent
@ -95,6 +98,7 @@ fun FeeExtendedSelectorContent(state: FeeExtendedSelectorUM, modifier: Modifier
} }
} }
@Suppress("LongMethod")
@Preview @Preview
@Composable @Composable
private fun Preview() { private fun Preview() {
@ -115,6 +119,34 @@ private fun Preview() {
onItemLongClick = {}, onItemLongClick = {},
) )
val cryptoCurrencyStatus = CryptoCurrencyStatus(
currency = CryptoCurrency.Coin(
id = CryptoCurrency.ID.fromValue("coin⟨BITCOIN⟩bitcoin"),
network = Network(
id = Network.ID(
value = "bitcoin",
derivationPath = Network.DerivationPath.None,
),
backendId = "bitcoin",
name = "Bitcoin",
currencySymbol = "BTC",
derivationPath = Network.DerivationPath.None,
isTestnet = false,
standardType = Network.StandardType.Unspecified("bitcoin"),
hasFiatFeeRate = false,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
nameResolvingType = Network.NameResolvingType.NONE,
),
name = "Bitcoin",
symbol = "BTC",
decimals = 8,
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/medium/bitcoin.png",
isCustom = false,
),
value = CryptoCurrencyStatus.Loading,
)
val lowFeeItem = val lowFeeItem =
FeeItem.Market(Fee.Common(amount = Amount(value = BigDecimal("0.0002876"), blockchain = Blockchain.Ethereum))) FeeItem.Market(Fee.Common(amount = Amount(value = BigDecimal("0.0002876"), blockchain = Blockchain.Ethereum)))
@ -130,6 +162,7 @@ private fun Preview() {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = true, isFeeConvertibleToFiat = true,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeNonce = FeeNonce.None, feeNonce = FeeNonce.None,
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(

View file

@ -50,6 +50,9 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SendSelectNetworkFeeBottomSheetTestTags import com.tangem.core.ui.test.SendSelectNetworkFeeBottomSheetTestTags
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.network.Network
import com.tangem.features.send.v2.api.entity.* import com.tangem.features.send.v2.api.entity.*
import com.tangem.features.send.v2.feeselector.component.speed.FeeSpeedSelectorIntents import com.tangem.features.send.v2.feeselector.component.speed.FeeSpeedSelectorIntents
import com.tangem.features.send.v2.feeselector.component.speed.StubFeeSpeedSelectorIntents import com.tangem.features.send.v2.feeselector.component.speed.StubFeeSpeedSelectorIntents
@ -473,6 +476,7 @@ private class FeeSpeedSelectorUMContentProvider : CollectionPreviewParameterProv
isFeeApproximate = true, isFeeApproximate = true,
isFeeConvertibleToFiat = true, isFeeConvertibleToFiat = true,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(
rate = BigDecimal.TEN, rate = BigDecimal.TEN,
@ -484,44 +488,74 @@ private class FeeSpeedSelectorUMContentProvider : CollectionPreviewParameterProv
), ),
) )
private val customFeeItem = FeeItem.Custom( private val cryptoCurrencyStatus
fee = Fee.Common(Amount(value = BigDecimal("0.05"), blockchain = Blockchain.Ethereum)), get() = CryptoCurrencyStatus(
customValues = persistentListOf( currency = CryptoCurrency.Coin(
CustomFeeFieldUM( id = CryptoCurrency.ID.fromValue("coin⟨BITCOIN⟩bitcoin"),
value = "0.119806", network = Network(
onValueChange = {}, id = Network.ID(
keyboardOptions = KeyboardOptions(), value = "bitcoin",
keyboardActions = KeyboardActions(), derivationPath = Network.DerivationPath.None,
symbol = "ETH", ),
backendId = "bitcoin",
name = "Bitcoin",
currencySymbol = "BTC",
derivationPath = Network.DerivationPath.None,
isTestnet = false,
standardType = Network.StandardType.Unspecified("bitcoin"),
hasFiatFeeRate = false,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
nameResolvingType = Network.NameResolvingType.NONE,
),
name = "Bitcoin",
symbol = "BTC",
decimals = 8, decimals = 8,
title = resourceReference(R.string.send_max_fee), iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/medium/bitcoin.png",
footer = resourceReference(R.string.send_custom_amount_fee_footer), isCustom = false,
label = stringReference("~ 0,03 \$"),
isReadonly = false,
), ),
CustomFeeFieldUM( value = CryptoCurrencyStatus.Loading,
value = "40000", )
onValueChange = {},
keyboardOptions = KeyboardOptions(), private val customFeeItem
keyboardActions = KeyboardActions(), get() = FeeItem.Custom(
symbol = "GWEI", fee = Fee.Common(Amount(value = BigDecimal("0.05"), blockchain = Blockchain.Ethereum)),
decimals = 8, customValues = persistentListOf(
title = resourceReference(R.string.send_gas_price), CustomFeeFieldUM(
footer = resourceReference(R.string.send_gas_price_footer), value = "0.119806",
label = null, onValueChange = {},
isReadonly = false, keyboardOptions = KeyboardOptions(),
keyboardActions = KeyboardActions(),
symbol = "ETH",
decimals = 8,
title = resourceReference(R.string.send_max_fee),
footer = resourceReference(R.string.send_custom_amount_fee_footer),
label = stringReference("~ 0,03 \$"),
isReadonly = false,
),
CustomFeeFieldUM(
value = "40000",
onValueChange = {},
keyboardOptions = KeyboardOptions(),
keyboardActions = KeyboardActions(),
symbol = "GWEI",
decimals = 8,
title = resourceReference(R.string.send_gas_price),
footer = resourceReference(R.string.send_gas_price_footer),
label = null,
isReadonly = false,
),
CustomFeeFieldUM(
value = "31400",
onValueChange = {},
keyboardOptions = KeyboardOptions(),
keyboardActions = KeyboardActions(),
symbol = null,
decimals = 8,
title = resourceReference(R.string.send_gas_limit),
footer = resourceReference(R.string.send_gas_limit_footer),
label = null,
isReadonly = false,
),
), ),
CustomFeeFieldUM( )
value = "31400",
onValueChange = {},
keyboardOptions = KeyboardOptions(),
keyboardActions = KeyboardActions(),
symbol = null,
decimals = 8,
title = resourceReference(R.string.send_gas_limit),
footer = resourceReference(R.string.send_gas_limit_footer),
label = null,
isReadonly = false,
),
),
)

View file

@ -25,14 +25,14 @@ import javax.inject.Inject
@ModelScoped @ModelScoped
internal class FeeSelectorBlockModel @Inject constructor( internal class FeeSelectorBlockModel @Inject constructor(
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
feeSelectorLogicFactory: FeeSelectorLogic.Factory,
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val feeSelectorLogicFactory: FeeSelectorLogic.Factory,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase, private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase,
private val urlOpener: UrlOpener, private val urlOpener: UrlOpener,
) : Model(), FeeSelectorModelCallback { ) : Model(), FeeSelectorModelCallback {
private val params = paramsContainer.require<FeeSelectorParams>() private val params = paramsContainer.require<FeeSelectorParams.FeeSelectorBlockParams>()
private val feeSelectorLogic = feeSelectorLogicFactory.create( private val feeSelectorLogic = feeSelectorLogicFactory.create(
params = params, params = params,
modelScope = modelScope, modelScope = modelScope,

View file

@ -1,11 +1,18 @@
package com.tangem.features.send.v2.feeselector.model package com.tangem.features.send.v2.feeselector.model
import arrow.core.Either
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.AmountType
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.CryptoCurrencyStatus
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.models.TransactionFeeExtended
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.NonceInserted import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.NonceInserted
import com.tangem.features.send.v2.api.entity.FeeItem import com.tangem.features.send.v2.api.entity.FeeItem
import com.tangem.features.send.v2.api.entity.FeeNonce import com.tangem.features.send.v2.api.entity.FeeNonce
@ -38,6 +45,8 @@ internal class FeeSelectorLogic @AssistedInject constructor(
private val feeSelectorCheckReloadTrigger: FeeSelectorCheckReloadTrigger, private val feeSelectorCheckReloadTrigger: FeeSelectorCheckReloadTrigger,
private val feeSelectorAlertFactory: FeeSelectorAlertFactory, private val feeSelectorAlertFactory: FeeSelectorAlertFactory,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
private val sendFeatureToggles: SendFeatureToggles,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
) : FeeSelectorIntents { ) : FeeSelectorIntents {
private var appCurrency: AppCurrency = AppCurrency.Default private var appCurrency: AppCurrency = AppCurrency.Default
@ -51,10 +60,6 @@ internal class FeeSelectorLogic @AssistedInject constructor(
loadFee() loadFee()
} }
fun updateState(feeSelectorUM: FeeSelectorUM) {
uiState.value = feeSelectorUM
}
private fun initAppCurrency() { private fun initAppCurrency() {
modelScope.launch { modelScope.launch {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default } appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
@ -66,18 +71,18 @@ internal class FeeSelectorLogic @AssistedInject constructor(
uiState.update(FeeSelectorLoadingTransformer) uiState.update(FeeSelectorLoadingTransformer)
} }
modelScope.launch { modelScope.launch {
params.onLoadFee() callLoadFee()
.fold( .fold(
ifLeft = { error -> uiState.update(FeeSelectorErrorTransformer(error)) }, ifLeft = { error -> uiState.update(FeeSelectorErrorTransformer(error)) },
ifRight = { fee -> ifRight = { fee ->
uiState.update( uiState.update(
FeeSelectorLoadedTransformer( FeeSelectorLoadedTransformer(
cryptoCurrencyStatus = params.cryptoCurrencyStatus, cryptoCurrencyStatus = params.cryptoCurrencyStatus,
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus, feeCryptoCurrencyStatus = fee.selectedTokenOrNull ?: params.feeCryptoCurrencyStatus,
appCurrency = appCurrency, appCurrency = appCurrency,
fees = fee, fees = fee,
feeStateConfiguration = params.feeStateConfiguration, feeStateConfiguration = params.feeStateConfiguration,
isFeeApproximate = isFeeApproximate(fee.normal.amount.type), isFeeApproximate = isFeeApproximate(fee.transactionFee.normal.amount.type),
feeSelectorIntents = this@FeeSelectorLogic, feeSelectorIntents = this@FeeSelectorLogic,
), ),
) )
@ -98,9 +103,6 @@ internal class FeeSelectorLogic @AssistedInject constructor(
) )
} }
uiState.update(FeeItemSelectedTransformer(feeItem)) uiState.update(FeeItemSelectedTransformer(feeItem))
if (feeItem !is FeeItem.Custom) {
onDoneClick()
}
} }
override fun onCustomFeeValueChange(index: Int, value: String) { override fun onCustomFeeValueChange(index: Int, value: String) {
@ -141,8 +143,6 @@ internal class FeeSelectorLogic @AssistedInject constructor(
), ),
) )
} }
(params as? FeeSelectorParams.FeeSelectorDetailsParams)?.callback?.onFeeResult(uiState.value)
} }
private fun subscribeOnFeeReloadTriggerUpdates() { private fun subscribeOnFeeReloadTriggerUpdates() {
@ -170,10 +170,10 @@ internal class FeeSelectorLogic @AssistedInject constructor(
private fun checkLoadFee() { private fun checkLoadFee() {
modelScope.launch { modelScope.launch {
params.onLoadFee().fold( callLoadFee().fold(
ifRight = { newFee -> ifRight = { newFee ->
feeSelectorAlertFactory.getFeeUpdatedAlert( feeSelectorAlertFactory.getFeeUpdatedAlert(
newTransactionFee = newFee, newTransactionFee = newFee.transactionFee,
feeSelectorUM = uiState.value, feeSelectorUM = uiState.value,
proceedAction = { proceedAction = {
modelScope.launch { modelScope.launch {
@ -195,6 +195,61 @@ internal class FeeSelectorLogic @AssistedInject constructor(
} }
} }
@Suppress("UnreachableCode")
private suspend fun callLoadFee(): Either<GetFeeError, LoadedFeeResult> {
val extended = params.onLoadFeeExtended
return if (extended != null && sendFeatureToggles.isGaslessTransactionsEnabled) {
extended().fold(
ifLeft = { error ->
if (error is GetFeeError.GaslessError) {
params.onLoadFee().map { LoadedFeeResult.Basic(it) }
} else {
Either.Left(error)
}
},
ifRight = { fee ->
Either.Right(
LoadedFeeResult.Extended(
fee = fee,
selectedToken = if (params.feeCryptoCurrencyStatus.currency.id != fee.feeTokenId) {
getSingleCryptoCurrencyStatusUseCase
.invokeMultiWalletSync(
userWalletId = TODO(),
cryptoCurrencyId = fee.feeTokenId,
).getOrNull()
} else {
params.feeCryptoCurrencyStatus
},
),
)
},
)
} else {
params.onLoadFee().map { LoadedFeeResult.Basic(it) }
}
}
sealed class LoadedFeeResult {
data class Extended(
val fee: TransactionFeeExtended,
val selectedToken: CryptoCurrencyStatus?,
) : LoadedFeeResult()
data class Basic(val fee: TransactionFee) : LoadedFeeResult()
val selectedTokenOrNull: CryptoCurrencyStatus?
get() = when (this) {
is Extended -> selectedToken
is Basic -> null
}
val transactionFee: TransactionFee
get() = when (this) {
is Extended -> fee.transactionFee
is Basic -> fee
}
}
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create(params: FeeSelectorParams, modelScope: CoroutineScope): FeeSelectorLogic fun create(params: FeeSelectorParams, modelScope: CoroutineScope): FeeSelectorLogic

View file

@ -1,13 +1,20 @@
package com.tangem.features.send.v2.feeselector.model package com.tangem.features.send.v2.feeselector.model
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.pop
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.utils.stack
import com.tangem.features.send.v2.api.SendFeatureToggles import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.send.v2.api.entity.FeeItem
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.params.FeeSelectorParams import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.send.v2.feeselector.route.FeeSelectorRoute import com.tangem.features.send.v2.feeselector.route.FeeSelectorRoute
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.isSingleItem
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@Stable @Stable
@ -17,16 +24,61 @@ internal class FeeSelectorModel @Inject constructor(
feeSelectorLogicFactory: FeeSelectorLogic.Factory, feeSelectorLogicFactory: FeeSelectorLogic.Factory,
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val sendFeatureToggles: SendFeatureToggles, private val sendFeatureToggles: SendFeatureToggles,
) : Model() { ) : Model(), FeeSelectorIntents {
private val params = paramsContainer.require<FeeSelectorParams>() private val params = paramsContainer.require<FeeSelectorParams.FeeSelectorDetailsParams>()
private val feeSelectorLogic = feeSelectorLogicFactory.create( private val feeSelectorLogic = feeSelectorLogicFactory.create(
params = params, params = params,
modelScope = modelScope, modelScope = modelScope,
) )
val stackNavigation = StackNavigation<FeeSelectorRoute>()
val uiState = feeSelectorLogic.uiState val uiState = feeSelectorLogic.uiState
val intents: FeeSelectorIntents = feeSelectorLogic
override fun onFeeItemSelected(feeItem: FeeItem) {
feeSelectorLogic.onFeeItemSelected(feeItem)
if (contentState().selectedFeeItem is FeeItem.Custom) {
return
}
modelScope.launch {
val stack = stackNavigation.stack()
if (stack.isSingleItem() && stack[0] is FeeSelectorRoute.ChooseSpeed) {
// In case of only speed selection, we finish the flow immediately after selection
feeSelectorLogic.onDoneClick()
params.callback.onFeeResult(uiState.value)
} else {
stackNavigation.pop()
}
}
}
override fun onCustomFeeValueChange(index: Int, value: String) {
feeSelectorLogic.onCustomFeeValueChange(index, value)
}
override fun onNonceChange(value: String) {
feeSelectorLogic.onNonceChange(value)
}
override fun onDoneClick() {
modelScope.launch {
val stack = stackNavigation.stack()
if (stack.isSingleItem()) {
feeSelectorLogic.onDoneClick()
params.callback.onFeeResult(uiState.value)
} else {
stackNavigation.pop()
}
}
}
private fun contentState(): FeeSelectorUM.Content {
val currentState = uiState.value
require(currentState is FeeSelectorUM.Content) { "Current state must be FeeSelectorUM.Content" }
return currentState
}
fun getInitialRoute(): FeeSelectorRoute { fun getInitialRoute(): FeeSelectorRoute {
if (sendFeatureToggles.isGaslessTransactionsEnabled.not()) { if (sendFeatureToggles.isGaslessTransactionsEnabled.not()) {

View file

@ -1,13 +1,13 @@
package com.tangem.features.send.v2.feeselector.model.transformers package com.tangem.features.send.v2.feeselector.model.transformers
import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
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.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.* import com.tangem.features.send.v2.api.entity.*
import com.tangem.features.send.v2.api.params.FeeSelectorParams import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
import com.tangem.features.send.v2.feeselector.model.FeeSelectorLogic.LoadedFeeResult
import com.tangem.lib.crypto.BlockchainUtils.isTron import com.tangem.lib.crypto.BlockchainUtils.isTron
import com.tangem.utils.transformer.Transformer import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@ -17,7 +17,7 @@ internal class FeeSelectorLoadedTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus, private val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
private val appCurrency: AppCurrency, private val appCurrency: AppCurrency,
private val fees: TransactionFee, private val fees: LoadedFeeResult,
private val feeStateConfiguration: FeeSelectorParams.FeeStateConfiguration, private val feeStateConfiguration: FeeSelectorParams.FeeStateConfiguration,
private val isFeeApproximate: Boolean, private val isFeeApproximate: Boolean,
private val feeSelectorIntents: FeeSelectorIntents, private val feeSelectorIntents: FeeSelectorIntents,
@ -25,7 +25,7 @@ internal class FeeSelectorLoadedTransformer(
private val feeItemsConverter = FeeItemConverter( private val feeItemsConverter = FeeItemConverter(
feeStateConfiguration = feeStateConfiguration, feeStateConfiguration = feeStateConfiguration,
normalFee = fees.normal, normalFee = fees.transactionFee.normal,
feeSelectorIntents = feeSelectorIntents, feeSelectorIntents = feeSelectorIntents,
appCurrency = appCurrency, appCurrency = appCurrency,
cryptoCurrencyStatus = feeCryptoCurrencyStatus, cryptoCurrencyStatus = feeCryptoCurrencyStatus,
@ -37,7 +37,9 @@ internal class FeeSelectorLoadedTransformer(
} else { } else {
null null
} }
val feeItems: ImmutableList<FeeItem> = feeItemsConverter.convert(FeeItemConverter.Input(fees, prevCustomFee)) val feeItems: ImmutableList<FeeItem> = feeItemsConverter.convert(
FeeItemConverter.Input(fees.transactionFee, prevCustomFee),
)
val selectedFee = when (prevState) { val selectedFee = when (prevState) {
is FeeSelectorUM.Content -> feeItems.first { it.isSameClass(prevState.selectedFeeItem) } is FeeSelectorUM.Content -> feeItems.first { it.isSameClass(prevState.selectedFeeItem) }
@ -50,7 +52,7 @@ internal class FeeSelectorLoadedTransformer(
return FeeSelectorUM.Content( return FeeSelectorUM.Content(
isPrimaryButtonEnabled = true, isPrimaryButtonEnabled = true,
fees = fees, fees = fees.transactionFee,
feeItems = feeItems, feeItems = feeItems,
selectedFeeItem = selectedFee, selectedFeeItem = selectedFee,
feeExtraInfo = FeeExtraInfo( feeExtraInfo = FeeExtraInfo(
@ -58,6 +60,7 @@ internal class FeeSelectorLoadedTransformer(
isFeeConvertibleToFiat = feeCryptoCurrencyStatus.currency.network.hasFiatFeeRate, isFeeConvertibleToFiat = feeCryptoCurrencyStatus.currency.network.hasFiatFeeRate,
isTronToken = cryptoCurrencyStatus.currency is CryptoCurrency.Token && isTronToken = cryptoCurrencyStatus.currency is CryptoCurrency.Token &&
isTron(cryptoCurrencyStatus.currency.network.rawId), isTron(cryptoCurrencyStatus.currency.network.rawId),
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
), ),
feeFiatRateUM = feeCryptoCurrencyStatus.value.fiatRate?.let { rate -> feeFiatRateUM = feeCryptoCurrencyStatus.value.fiatRate?.let { rate ->
FeeFiatRateUM( FeeFiatRateUM(
@ -65,7 +68,7 @@ internal class FeeSelectorLoadedTransformer(
appCurrency = appCurrency, appCurrency = appCurrency,
) )
}, },
feeNonce = if (fees.normal is Fee.Ethereum) { feeNonce = if (fees.transactionFee.normal is Fee.Ethereum) {
FeeNonce.Nonce( FeeNonce.Nonce(
nonce = nonce, nonce = nonce,
onNonceChange = feeSelectorIntents::onNonceChange, onNonceChange = feeSelectorIntents::onNonceChange,

View file

@ -15,7 +15,7 @@ internal sealed interface FeeSelectorRoute : Route {
@Serializable @Serializable
data object NetworkFee : FeeSelectorRoute { data object NetworkFee : FeeSelectorRoute {
override val title: TextReference = resourceReference(R.string.common_fee_selector_title) override val title: TextReference = resourceReference(R.string.common_network_fee_title)
} }
@Serializable @Serializable

View file

@ -42,6 +42,9 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.FeeSelectorBlockTestTags import com.tangem.core.ui.test.FeeSelectorBlockTestTags
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.network.Network
import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.features.send.v2.api.entity.* import com.tangem.features.send.v2.api.entity.*
import com.tangem.features.send.v2.impl.R import com.tangem.features.send.v2.impl.R
@ -238,11 +241,48 @@ private fun FeeSelectorBlockContent_Preview(@PreviewParameter(FeeSelectorUMProvi
} }
private class FeeSelectorUMProvider : PreviewParameterProvider<FeeSelectorUM> { private class FeeSelectorUMProvider : PreviewParameterProvider<FeeSelectorUM> {
private val maxFeeItem = FeeItem.Market( private val maxFeeItem
fee = Fee.Common(amount = Amount(value = BigDecimal("100000000"), blockchain = Blockchain.Hedera)), get() = FeeItem.Market(
) fee = Fee.Common(amount = Amount(value = BigDecimal("100000000"), blockchain = Blockchain.Hedera)),
private val lowFeeItem = )
FeeItem.Market(Fee.Common(amount = Amount(value = BigDecimal("0.0002876"), blockchain = Blockchain.Ethereum))) private val lowFeeItem
get() = FeeItem.Market(
Fee.Common(
amount = Amount(
value = BigDecimal("0.0002876"),
blockchain = Blockchain.Ethereum,
),
),
)
val cryptoCurrencyStatus
get() = CryptoCurrencyStatus(
currency = CryptoCurrency.Coin(
id = CryptoCurrency.ID.fromValue("coin⟨BITCOIN⟩bitcoin"),
network = Network(
id = Network.ID(
value = "bitcoin",
derivationPath = Network.DerivationPath.None,
),
backendId = "bitcoin",
name = "Bitcoin",
currencySymbol = "BTC",
derivationPath = Network.DerivationPath.None,
isTestnet = false,
standardType = Network.StandardType.Unspecified("bitcoin"),
hasFiatFeeRate = false,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
nameResolvingType = Network.NameResolvingType.NONE,
),
name = "Bitcoin",
symbol = "BTC",
decimals = 8,
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/medium/bitcoin.png",
isCustom = false,
),
value = CryptoCurrencyStatus.Loading,
)
override val values: Sequence<FeeSelectorUM> = sequenceOf( override val values: Sequence<FeeSelectorUM> = sequenceOf(
FeeSelectorUM.Content( FeeSelectorUM.Content(
@ -253,6 +293,7 @@ private class FeeSelectorUMProvider : PreviewParameterProvider<FeeSelectorUM> {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = true, isFeeConvertibleToFiat = true,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeNonce = FeeNonce.None, feeNonce = FeeNonce.None,
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(
@ -269,6 +310,7 @@ private class FeeSelectorUMProvider : PreviewParameterProvider<FeeSelectorUM> {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = true, isFeeConvertibleToFiat = true,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeNonce = FeeNonce.None, feeNonce = FeeNonce.None,
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(

View file

@ -8,6 +8,8 @@ import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
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.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.features.send.v2.api.entity.* import com.tangem.features.send.v2.api.entity.*
import com.tangem.features.send.v2.common.ui.state.ConfirmUM import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.sendnft.confirm.model.transformers.NFTSendConfirmationNotificationsTransformerV2 import com.tangem.features.send.v2.sendnft.confirm.model.transformers.NFTSendConfirmationNotificationsTransformerV2
@ -28,6 +30,34 @@ class NFTSendConfirmationNotificationsTransformerV2Test {
private val appCurrency = AppCurrency(name = "US Dollar", code = "USD", symbol = "$") private val appCurrency = AppCurrency(name = "US Dollar", code = "USD", symbol = "$")
private val analyticsCategoryName = "test_category" private val analyticsCategoryName = "test_category"
val cryptoCurrencyStatus = CryptoCurrencyStatus(
currency = CryptoCurrency.Coin(
id = CryptoCurrency.ID.fromValue("coin⟨BITCOIN⟩bitcoin"),
network = Network(
id = Network.ID(
value = "bitcoin",
derivationPath = Network.DerivationPath.None,
),
backendId = "bitcoin",
name = "Bitcoin",
currencySymbol = "BTC",
derivationPath = Network.DerivationPath.None,
isTestnet = false,
standardType = Network.StandardType.Unspecified("bitcoin"),
hasFiatFeeRate = false,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
nameResolvingType = Network.NameResolvingType.NONE,
),
name = "Bitcoin",
symbol = "BTC",
decimals = 8,
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/medium/bitcoin.png",
isCustom = false,
),
value = CryptoCurrencyStatus.Loading,
)
@Test @Test
fun `GIVEN non content state WHEN transform THEN returns original state`() = runTest { fun `GIVEN non content state WHEN transform THEN returns original state`() = runTest {
// GIVEN // GIVEN
@ -191,6 +221,7 @@ class NFTSendConfirmationNotificationsTransformerV2Test {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = false, isFeeConvertibleToFiat = false,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(
rate = BigDecimal("50000"), rate = BigDecimal("50000"),
@ -274,6 +305,7 @@ class NFTSendConfirmationNotificationsTransformerV2Test {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = false, isFeeConvertibleToFiat = false,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(
rate = BigDecimal("50000"), rate = BigDecimal("50000"),
@ -345,6 +377,7 @@ class NFTSendConfirmationNotificationsTransformerV2Test {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = false, isFeeConvertibleToFiat = false,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus
), ),
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(
rate = BigDecimal("50000"), rate = BigDecimal("50000"),
@ -428,6 +461,7 @@ class NFTSendConfirmationNotificationsTransformerV2Test {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = false, isFeeConvertibleToFiat = false,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(
rate = BigDecimal("50000"), rate = BigDecimal("50000"),

View file

@ -10,6 +10,8 @@ import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
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.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.features.send.v2.api.entity.* import com.tangem.features.send.v2.api.entity.*
import com.tangem.features.send.v2.common.ui.state.ConfirmUM import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import io.mockk.mockk import io.mockk.mockk
@ -30,6 +32,34 @@ class SendConfirmationNotificationsTransformerV2Test {
private val appCurrency = AppCurrency(name = "US Dollar", code = "USD", symbol = "$") private val appCurrency = AppCurrency(name = "US Dollar", code = "USD", symbol = "$")
private val analyticsCategoryName = "test_category" private val analyticsCategoryName = "test_category"
val cryptoCurrencyStatus = CryptoCurrencyStatus(
currency = CryptoCurrency.Coin(
id = CryptoCurrency.ID.fromValue("coin⟨BITCOIN⟩bitcoin"),
network = Network(
id = Network.ID(
value = "bitcoin",
derivationPath = Network.DerivationPath.None,
),
backendId = "bitcoin",
name = "Bitcoin",
currencySymbol = "BTC",
derivationPath = Network.DerivationPath.None,
isTestnet = false,
standardType = Network.StandardType.Unspecified("bitcoin"),
hasFiatFeeRate = false,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
nameResolvingType = Network.NameResolvingType.NONE,
),
name = "Bitcoin",
symbol = "BTC",
decimals = 8,
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/medium/bitcoin.png",
isCustom = false,
),
value = CryptoCurrencyStatus.Loading,
)
@Test @Test
fun `GIVEN non content state WHEN transform THEN returns original state`() = runTest { fun `GIVEN non content state WHEN transform THEN returns original state`() = runTest {
// GIVEN // GIVEN
@ -247,6 +277,7 @@ class SendConfirmationNotificationsTransformerV2Test {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = false, isFeeConvertibleToFiat = false,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(
rate = BigDecimal("50000"), rate = BigDecimal("50000"),
@ -330,6 +361,7 @@ class SendConfirmationNotificationsTransformerV2Test {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = false, isFeeConvertibleToFiat = false,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(
rate = BigDecimal("50000"), rate = BigDecimal("50000"),
@ -401,6 +433,7 @@ class SendConfirmationNotificationsTransformerV2Test {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = false, isFeeConvertibleToFiat = false,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(
rate = BigDecimal("50000"), rate = BigDecimal("50000"),
@ -484,6 +517,7 @@ class SendConfirmationNotificationsTransformerV2Test {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = false, isFeeConvertibleToFiat = false,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
), ),
feeFiatRateUM = FeeFiatRateUM( feeFiatRateUM = FeeFiatRateUM(
rate = BigDecimal("50000"), rate = BigDecimal("50000"),

View file

@ -333,6 +333,7 @@ private fun SendWithSwapSuccessContent_Preview() {
isFeeApproximate = false, isFeeApproximate = false,
isFeeConvertibleToFiat = false, isFeeConvertibleToFiat = false,
isTronToken = false, isTronToken = false,
feeCryptoCurrencyStatus = SwapAmountContentPreview.cryptoCurrencyStatus,
), ),
feeFiatRateUM = null, feeFiatRateUM = null,
feeNonce = FeeNonce.None, feeNonce = FeeNonce.None,