Updated on 2026-08-14
This commit is contained in:
parent
8564f21d01
commit
7740d6f453
18 changed files with 167 additions and 50 deletions
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue