Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-23 12:38:59 +05:00
commit 929317d15c
15 changed files with 586 additions and 92 deletions

View file

@ -12,6 +12,7 @@ import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.preview.AmountScreenClickIntentsStub
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
import com.tangem.common.ui.amountScreen.ui.amountField
import com.tangem.common.ui.amountScreen.ui.amountFieldV2
import com.tangem.common.ui.amountScreen.ui.buttons
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -40,18 +41,28 @@ fun AmountScreenContent(
bottom = TangemTheme.dimens.spacing16,
),
) {
amountField(
amountState = amountState,
isBalanceHidden = isBalanceHidden,
onValueChange = clickIntents::onAmountValueChange,
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
)
buttons(
segmentedButtonConfig = amountState.segmentedButtonConfig,
clickIntents = clickIntents,
isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled,
selectedButton = amountState.selectedButton,
)
if (amountState.isRedesignEnabled) {
amountFieldV2(
amountState = amountState,
onValueChange = clickIntents::onAmountValueChange,
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
onCurrencyChange = clickIntents::onCurrencyChangeClick,
onMaxAmountClick = clickIntents::onMaxValueClick,
)
} else {
amountField(
amountState = amountState,
isBalanceHidden = isBalanceHidden,
onValueChange = clickIntents::onAmountValueChange,
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
)
buttons(
segmentedButtonConfig = amountState.segmentedButtonConfig,
clickIntents = clickIntents,
isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled,
selectedButton = amountState.selectedButton,
)
}
}
}
@ -75,6 +86,7 @@ private class SendAmountContentPreviewProvider : PreviewParameterProvider<Amount
override val values: Sequence<AmountState>
get() = sequenceOf(
AmountStatePreviewData.amountState,
AmountStatePreviewData.amountStateV2,
)
}
// endregion

View file

@ -58,10 +58,11 @@ class AmountStateConverter(
return AmountState.Data(
title = value.title,
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
tokenName = stringReference(status.currency.name),
tokenIconState = iconStateConverter.convert(status),
amountTextField = amountFieldConverter.convert(value.value),
isPrimaryButtonEnabled = false,
appCurrencyCode = appCurrency.code,
appCurrency = appCurrency,
segmentedButtonConfig = persistentListOf(
AmountSegmentedButtonsConfig(
title = stringReference(status.currency.symbol),
@ -80,6 +81,7 @@ class AmountStateConverter(
),
isSegmentedButtonsEnabled = !noFeeRate,
selectedButton = 0,
isRedesignEnabled = false,
)
}
}
@ -99,6 +101,7 @@ class AmountStateConverterV2(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val maxEnterAmount: EnterAmountBoundary,
private val iconStateConverter: CryptoCurrencyToIconStateConverter,
private val isRedesignEnabled: Boolean,
) : Converter<AmountParameters, AmountState> {
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
@ -116,11 +119,16 @@ class AmountStateConverterV2(
return AmountState.Data(
title = value.title,
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
availableBalance = if (isRedesignEnabled) {
resourceReference(R.string.common_balance, wrappedList(crypto))
} else {
resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat))
},
tokenName = stringReference(cryptoCurrencyStatus.currency.name),
tokenIconState = iconStateConverter.convert(cryptoCurrencyStatus),
amountTextField = amountFieldConverter.convert(value.value),
isPrimaryButtonEnabled = false,
appCurrencyCode = appCurrency.code,
appCurrency = appCurrency,
segmentedButtonConfig = persistentListOf(
AmountSegmentedButtonsConfig(
title = stringReference(cryptoCurrencyStatus.currency.symbol),
@ -139,6 +147,7 @@ class AmountStateConverterV2(
),
isSegmentedButtonsEnabled = !noFeeRate,
selectedButton = 0,
isRedesignEnabled = isRedesignEnabled,
)
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.common.ui.amountScreen.models
import androidx.compose.runtime.Stable
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.appcurrency.model.AppCurrency
import kotlinx.collections.immutable.PersistentList
import java.math.BigDecimal
@ -21,7 +22,7 @@ sealed class AmountState {
* @param selectedButton selected currency index
* @param isSegmentedButtonsEnabled indicates if currency switches is enabled
* @param amountTextField amount field state
* @param appCurrencyCode app currency code
* @param appCurrency app currency
* @param isEditingDisabled indicated whether amount is editable
* @param reduceAmountBy reduces amount to be sent by specified value
* @param isIgnoreReduce ignores reduce amount value
@ -30,15 +31,17 @@ sealed class AmountState {
override val isPrimaryButtonEnabled: Boolean,
val title: TextReference,
val availableBalance: TextReference,
val tokenName: TextReference,
val tokenIconState: CurrencyIconState,
val segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
val selectedButton: Int,
val isSegmentedButtonsEnabled: Boolean,
val amountTextField: AmountFieldModel,
val appCurrencyCode: String,
val appCurrency: AppCurrency,
val isEditingDisabled: Boolean = false,
val reduceAmountBy: BigDecimal = BigDecimal.ZERO,
val isIgnoreReduce: Boolean = false,
val isRedesignEnabled: Boolean,
) : AmountState()
data class Empty(

View file

@ -2,12 +2,16 @@ package com.tangem.common.ui.amountScreen.preview
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.tokens.model.AmountType
import kotlinx.collections.immutable.persistentListOf
@ -15,6 +19,8 @@ import java.math.BigDecimal
object AmountStatePreviewData {
val emptyState = AmountState.Empty()
val amountState = AmountState.Data(
isPrimaryButtonEnabled = false,
title = stringReference("Family Wallet"),
@ -31,7 +37,8 @@ object AmountStatePreviewData {
isFiat = true,
),
),
appCurrencyCode = "usd",
appCurrency = AppCurrency.Default,
tokenName = stringReference("Tether"),
amountTextField = AmountFieldModel(
value = "",
onValueChange = {},
@ -60,6 +67,7 @@ object AmountStatePreviewData {
),
isSegmentedButtonsEnabled = true,
selectedButton = 0,
isRedesignEnabled = false,
)
val amountWithValueState = amountState.copy(
@ -75,6 +83,11 @@ object AmountStatePreviewData {
),
)
val amountStateV2 = amountState.copy(
isRedesignEnabled = true,
availableBalance = resourceReference(R.string.common_balance, wrappedList("2 130,88 USDT")),
)
val amountWithValueFiatState = amountWithValueState.copy(
amountTextField = amountWithValueState.amountTextField.copy(isFiatValue = false),
)

View file

@ -36,7 +36,7 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
val fiatAmount = amount.fiatAmount.value.format {
fiat(
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
fiatCurrencyCode = amountState.appCurrencyCode,
fiatCurrencyCode = amountState.appCurrency.code,
)
}

View file

@ -31,56 +31,6 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.rememberDecimalFormat
import kotlinx.coroutines.delay
@Deprecated("Use AmountField with clicks")
@Composable
internal fun AmountField(amountField: AmountFieldModel, appCurrencyCode: String) {
val decimalFormat = rememberDecimalFormat()
val isFiatValue = amountField.isFiatValue
val currencyCode = if (isFiatValue) appCurrencyCode else null
val (primaryAmount, primaryValue) = if (isFiatValue) {
amountField.fiatAmount to amountField.fiatValue
} else {
amountField.cryptoAmount to amountField.value
}
val requester = remember { FocusRequester() }
AmountTextField(
value = primaryValue,
decimals = primaryAmount.decimals,
visualTransformation = AmountVisualTransformation(
decimals = primaryAmount.decimals,
symbol = primaryAmount.currencySymbol,
currencyCode = currencyCode,
decimalFormat = decimalFormat,
),
onValueChange = amountField.onValueChange,
keyboardOptions = amountField.keyboardOptions,
keyboardActions = amountField.keyboardActions,
textStyle = TangemTheme.typography.h2.copy(
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
),
isAutoResize = true,
isValuePasted = amountField.isValuePasted,
onValuePastedTriggerDismiss = amountField.onValuePastedTriggerDismiss,
modifier = Modifier
.focusRequester(requester)
.padding(
top = TangemTheme.dimens.spacing24,
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
)
.requiredHeightIn(min = TangemTheme.dimens.size32),
)
LaunchedEffect(key1 = Unit) {
delay(timeMillis = 200)
requester.requestFocus()
}
AmountSecondary(amountField, appCurrencyCode)
}
@Composable
internal fun AmountField(
amountField: AmountFieldModel,
@ -97,7 +47,7 @@ internal fun AmountField(
amountField.cryptoAmount to amountField.value
}
val requester = remember { FocusRequester() }
val symbolColor = if (primaryValue.isBlank()) TangemTheme.colors.text.disabled else TangemTheme.colors.text.primary1
AmountTextField(
value = primaryValue,
decimals = primaryAmount.decimals,
@ -106,6 +56,7 @@ internal fun AmountField(
symbol = primaryAmount.currencySymbol,
currencyCode = currencyCode,
decimalFormat = decimalFormat,
symbolColor = symbolColor,
),
onValueChange = onValueChange,
keyboardOptions = amountField.keyboardOptions,

View file

@ -2,20 +2,32 @@ package com.tangem.common.ui.amountScreen.ui
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountState
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.TextEllipsis
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
private const val AMOUNT_FIELD_KEY = "amountFieldKey"
@ -64,10 +76,142 @@ internal fun LazyListScope.amountField(
)
AmountField(
amountField = amountState.amountTextField,
appCurrencyCode = amountState.appCurrencyCode,
appCurrencyCode = amountState.appCurrency.code,
onValueChange = onValueChange,
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
)
}
}
}
internal fun LazyListScope.amountFieldV2(
amountState: AmountState,
modifier: Modifier = Modifier,
onValueChange: (String) -> Unit,
onValuePastedTriggerDismiss: () -> Unit,
onCurrencyChange: (Boolean) -> Unit,
onMaxAmountClick: () -> Unit,
) {
item(key = AMOUNT_FIELD_KEY) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.action),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier
.padding(top = 48.dp, bottom = 28.dp),
) {
if (amountState !is AmountState.Data) {
TextShimmer(
style = TangemTheme.typography.caption2,
modifier = Modifier.width(60.dp),
)
} else {
Text(
text = amountState.title.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
AmountFieldV2(
amountUM = amountState,
onValueChange = onValueChange,
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
onCurrencyChange = onCurrencyChange,
modifier = Modifier,
)
}
HorizontalDivider(
modifier = Modifier.padding(horizontal = 12.dp),
thickness = 0.5.dp,
color = TangemTheme.colors.stroke.primary,
)
AmountInfo(
amountUM = amountState,
onMaxAmountClick = onMaxAmountClick,
)
}
}
}
@Composable
private fun AmountInfo(amountUM: AmountState, onMaxAmountClick: () -> Unit, modifier: Modifier = Modifier) {
val tokenIconState = (amountUM as? AmountState.Data)?.tokenIconState ?: CurrencyIconState.Loading
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = modifier,
) {
CurrencyIcon(
state = tokenIconState,
shouldDisplayNetwork = true,
modifier = Modifier.padding(
start = 16.dp,
top = 16.dp,
bottom = 16.dp,
),
)
AmountInfoMain(amountUM = amountUM)
SpacerWMax()
Text(
text = stringResourceSafe(R.string.send_max_amount),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.primary1,
modifier = Modifier
.padding(end = 16.dp)
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.secondary)
.padding(horizontal = 12.dp, vertical = 4.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(),
onClick = onMaxAmountClick,
),
)
}
}
@Composable
private fun AmountInfoMain(amountUM: AmountState, modifier: Modifier = Modifier) {
AnimatedContent(
targetState = amountUM !is AmountState.Data,
modifier = modifier,
) { isContent ->
if (isContent) {
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
TextShimmer(
style = TangemTheme.typography.subtitle2,
modifier = Modifier.width(56.dp),
)
TextShimmer(
style = TangemTheme.typography.caption2,
modifier = Modifier.width(72.dp),
)
}
} else {
val amountUM = amountUM as AmountState.Data
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = amountUM.tokenName.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
)
EllipsisText(
text = amountUM.availableBalance.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
ellipsis = TextEllipsis.OffsetEnd(amountUM.amountTextField.cryptoAmount.currencySymbol.length),
)
}
}
}
}

View file

@ -0,0 +1,329 @@
package com.tangem.common.ui.amountScreen.ui
import android.content.res.Configuration
import androidx.compose.animation.*
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.BottomCenter
import androidx.compose.ui.Alignment.Companion.TopCenter
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.currency.fiaticon.FiatIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.fields.AmountTextField
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.rememberDecimalFormat
import kotlinx.coroutines.delay
private const val ROTATED_DEGREE = 180f
private const val INITIAL_DEGREE = 0f
@Composable
fun AmountFieldV2(
amountUM: AmountState,
onValueChange: (String) -> Unit,
onValuePastedTriggerDismiss: () -> Unit,
onCurrencyChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
val decimalFormat = rememberDecimalFormat()
val requester = remember { FocusRequester() }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier,
) {
if (amountUM is AmountState.Empty) {
TextShimmer(
style = TangemTheme.typography.head,
modifier = Modifier.width(150.dp),
)
} else if (amountUM is AmountState.Data) {
AnimatedContent(
targetState = amountUM.amountTextField.isFiatValue,
transitionSpec = { primaryFieldCurrencyAnimation() },
label = "Primary field change animation",
) { isFiatValue ->
val currencyCode = if (isFiatValue) amountUM.appCurrency.code else null
val (primaryAmount, primaryValue) = if (isFiatValue) {
amountUM.amountTextField.fiatAmount to amountUM.amountTextField.fiatValue
} else {
amountUM.amountTextField.cryptoAmount to amountUM.amountTextField.value
}
AmountTextField(
value = primaryValue,
decimals = primaryAmount.decimals,
visualTransformation = AmountVisualTransformation(
decimals = primaryAmount.decimals,
symbol = primaryAmount.currencySymbol,
currencyCode = currencyCode,
decimalFormat = decimalFormat,
symbolColor = TangemTheme.colors.text.disabled,
),
onValueChange = onValueChange,
keyboardOptions = amountUM.amountTextField.keyboardOptions,
keyboardActions = amountUM.amountTextField.keyboardActions,
textStyle = TangemTheme.typography.head.copy(
textAlign = TextAlign.Center,
),
isAutoResize = true,
isValuePasted = amountUM.amountTextField.isValuePasted,
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
modifier = Modifier
.focusRequester(requester)
.requiredHeightIn(min = 44.dp),
)
LaunchedEffect(key1 = Unit) {
delay(timeMillis = 200)
requester.requestFocus()
}
}
}
AmountSecondary(
amountUM = amountUM,
onCurrencyChange = onCurrencyChange,
)
}
}
@Composable
private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -> Unit) {
Box(
modifier = Modifier
.fillMaxWidth()
.animateContentSize()
.padding(top = 8.dp),
) {
if (amountUM is AmountState.Empty) {
TextShimmer(
style = TangemTheme.typography.caption2,
modifier = Modifier
.width(90.dp)
.padding(bottom = 20.dp)
.align(TopCenter),
)
} else if (amountUM is AmountState.Data) {
AmountFieldCurrencyInfo(
amountUM = amountUM,
onCurrencyChange = onCurrencyChange,
)
AmountFieldError(
isError = amountUM.amountTextField.isError,
isWarning = amountUM.amountTextField.isWarning,
error = amountUM.amountTextField.error,
modifier = Modifier
.align(BottomCenter)
.padding(top = 20.dp),
)
}
}
}
@Composable
private fun BoxScope.AmountFieldCurrencyInfo(amountUM: AmountState.Data, onCurrencyChange: (Boolean) -> Unit) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
.align(TopCenter)
.padding(bottom = 20.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = { onCurrencyChange(!amountUM.amountTextField.isFiatValue) },
),
) {
val iconRotateState by animateFloatAsState(
targetValue = if (amountUM.amountTextField.isFiatValue) ROTATED_DEGREE else INITIAL_DEGREE,
animationSpec = tween(
durationMillis = 400,
easing = FastOutSlowInEasing,
),
label = "Currency change icon animation",
)
Icon(
painter = rememberVectorPainter(
ImageVector.vectorResource(R.drawable.ic_exchange_vertical_24),
),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
modifier = Modifier
.size(16.dp)
.graphicsLayer {
rotationY = iconRotateState
},
)
AmountFieldCurrencyIcon(
amountUM = amountUM,
)
}
}
@Composable
private fun AmountFieldCurrencyIcon(amountUM: AmountState.Data) {
AnimatedContent(
targetState = amountUM.amountTextField.isFiatValue,
transitionSpec = { secondaryFieldCurrencyAnimation() },
label = "Secondary field change animation",
) { isFiatValue ->
val secondaryAmount =
if (isFiatValue) amountUM.amountTextField.cryptoAmount else amountUM.amountTextField.fiatAmount
val text = secondaryAmount.value.format {
if (isFiatValue) {
crypto(secondaryAmount.currencySymbol, secondaryAmount.decimals)
} else {
fiat(
fiatCurrencySymbol = amountUM.appCurrency.symbol,
fiatCurrencyCode = amountUM.appCurrency.code,
)
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = text,
style = TangemTheme.typography.caption2.copy(textDirection = TextDirection.ContentOrLtr),
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
if (isFiatValue) {
CurrencyIcon(
state = amountUM.tokenIconState,
shouldDisplayNetwork = false,
modifier = Modifier.size(12.dp),
)
} else {
FiatIcon(
url = amountUM.appCurrency.iconSmallUrl,
size = 12.dp,
isGrayscale = false,
modifier = Modifier.size(12.dp),
)
}
}
}
}
@Composable
private fun AmountFieldError(
isError: Boolean,
isWarning: Boolean,
error: TextReference,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = isError || isWarning,
enter = fadeIn(),
exit = fadeOut(),
modifier = modifier,
label = "Error field appearance animation",
) {
val errorText = remember(this, error) { error }
val color = if (isError) TangemTheme.colors.text.warning else TangemTheme.colors.text.attention
Text(
text = errorText.resolveReference(),
style = TangemTheme.typography.caption2,
color = color,
textAlign = TextAlign.Center,
)
}
}
private fun <S> AnimatedContentTransitionScope<S>.secondaryFieldCurrencyAnimation(): ContentTransform {
return (
fadeIn(
animationSpec = tween(durationMillis = 300),
) + scaleIn(
animationSpec = tween(durationMillis = 300),
initialScale = 0.9f,
)
).togetherWith(
fadeOut(
animationSpec = tween(durationMillis = 300),
) + scaleOut(
animationSpec = tween(durationMillis = 300),
targetScale = 0.9f,
),
)
}
private fun <S> AnimatedContentTransitionScope<S>.primaryFieldCurrencyAnimation(): ContentTransform {
return (
fadeIn(
animationSpec = tween(durationMillis = 300),
) + scaleIn(
animationSpec = tween(durationMillis = 300),
initialScale = 0.9f,
) + slideInVertically(initialOffsetY = { it / 2 })
)
.togetherWith(
fadeOut(
animationSpec = tween(durationMillis = 300),
) + scaleOut(
animationSpec = tween(durationMillis = 300),
targetScale = 0.9f,
) + slideOutVertically(targetOffsetY = { it / 2 }),
)
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun AmountFieldV2_Preview(@PreviewParameter(AmountFieldV2PreviewProvider::class) params: AmountState) {
TangemThemePreview {
AmountFieldV2(
amountUM = params,
onValueChange = {},
onValuePastedTriggerDismiss = { },
onCurrencyChange = {},
modifier = Modifier.background(TangemTheme.colors.background.action),
)
}
}
private class AmountFieldV2PreviewProvider : PreviewParameterProvider<AmountState> {
override val values: Sequence<AmountState>
get() = sequenceOf(
AmountStatePreviewData.emptyState,
AmountStatePreviewData.amountState,
)
}
// endregion