diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenContent.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenContent.kt index d8a18ff233..bdb765bbbf 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenContent.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenContent.kt @@ -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 get() = sequenceOf( AmountStatePreviewData.amountState, + AmountStatePreviewData.amountStateV2, ) } // endregion \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt index 94981c571b..9bef8f3470 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt @@ -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 { 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, ) } } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt index 9f714127da..b5eab4301d 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt @@ -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, 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( diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt index 63c4d0cd5d..ab6ead2229 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt @@ -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), ) diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt index 3fd2bd8ad8..e4f345ca11 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt @@ -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, ) } diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt index 70d51c2503..35d49a5387 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt @@ -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, diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt index 104f2f60ae..7b4dfe4e57 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt @@ -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), + ) + } + } + } } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldV2.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldV2.kt new file mode 100644 index 0000000000..184f7ed46e --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldV2.kt @@ -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 AnimatedContentTransitionScope.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 AnimatedContentTransitionScope.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 { + override val values: Sequence + get() = sequenceOf( + AmountStatePreviewData.emptyState, + AmountStatePreviewData.amountState, + ) +} +// endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt index 017523fbbc..be5c0a9f2c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt @@ -60,7 +60,10 @@ fun AmountTextField( modifier: Modifier = Modifier, color: Color = TangemTheme.colors.text.primary1, backgroundColor: Color = TangemTheme.colors.background.action, - visualTransformation: VisualTransformation = AmountVisualTransformation(decimals), + visualTransformation: VisualTransformation = AmountVisualTransformation( + decimals = decimals, + symbolColor = if (value.isBlank()) TangemTheme.colors.text.disabled else color, + ), keyboardOptions: KeyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number, ), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt index 17521b19b9..c16ed2e9f5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt @@ -1,9 +1,12 @@ package com.tangem.core.ui.components.fields.visualtransformations +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation +import com.tangem.core.ui.extensions.appendColored import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.CURRENCY_SPACE import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode @@ -19,6 +22,7 @@ class AmountVisualTransformation( private val symbol: String? = null, private val currencyCode: String? = null, private val decimalFormat: DecimalFormat = DecimalFormat(), + private val symbolColor: Color, ) : VisualTransformation { override fun filter(text: AnnotatedString): TransformedText { @@ -28,17 +32,22 @@ class AmountVisualTransformation( ) formattedAmount = formattedAmount.ifEmpty { decimalFormat.defaultFormat() } val formattedText = if (formattedAmount.isNotEmpty() && symbol != null) { - AnnotatedString( + buildAnnotatedString { if (currencyCode != null) { - formatFiatEditableAmount( - fiatAmount = formattedAmount, - fiatCurrencyCode = currencyCode, - fiatCurrencySymbol = symbol, + append( + formatFiatEditableAmount( + fiatAmount = formattedAmount, + fiatCurrencyCode = currencyCode, + fiatCurrencySymbol = symbol, + fiatCurrencySymbolColor = symbolColor, + ), ) } else { - formatWithSymbol(formattedAmount, symbol) - }, - ) + append(formattedAmount) + append(CURRENCY_SPACE) + appendColored(symbol, symbolColor) + } + } } else { AnnotatedString(decimalFormat.defaultFormat()) } @@ -54,9 +63,10 @@ class AmountVisualTransformation( fiatAmount: String?, fiatCurrencyCode: String, fiatCurrencySymbol: String, + fiatCurrencySymbolColor: Color, locale: Locale = Locale.getDefault(), - ): String { - if (fiatAmount == null) return BigDecimalFormatConstants.EMPTY_BALANCE_SIGN + ): AnnotatedString { + if (fiatAmount == null) return AnnotatedString(BigDecimalFormatConstants.EMPTY_BALANCE_SIGN) val formatterCurrency = getJavaCurrencyByCode(fiatCurrencyCode) val numberFormatter = NumberFormat.getCurrencyInstance(locale).apply { @@ -64,14 +74,21 @@ class AmountVisualTransformation( } val formatter = requireNotNull(numberFormatter as? DecimalFormat) { Timber.e("NumberFormat is null") - return BigDecimalFormatConstants.EMPTY_BALANCE_SIGN + return AnnotatedString(BigDecimalFormatConstants.EMPTY_BALANCE_SIGN) + } + return buildAnnotatedString { + appendColored( + formatter.positivePrefix.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol), + fiatCurrencySymbolColor, + ) + append(fiatAmount) + appendColored( + formatter.positiveSuffix.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol), + fiatCurrencySymbolColor, + ) } - return "${formatter.positivePrefix}$fiatAmount${formatter.positiveSuffix}" - .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) } - private fun formatWithSymbol(amount: String, symbol: String) = "$amount$CURRENCY_SPACE$symbol" - private class OffsetMappingImpl( private val text: String, private val formattedText: AnnotatedString, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterAmount.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterAmount.kt index 1e15b0b4dd..a3fda95160 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterAmount.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterAmount.kt @@ -82,6 +82,7 @@ fun InputRowEnterAmount( decimals = decimals, symbol = symbol, decimalFormat = rememberDecimalFormat(), + symbolColor = textColor, ), onValueChange = onValueChange, color = textColor, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt index 63903ea854..5878b03d62 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt @@ -82,6 +82,7 @@ fun InputRowEnterInfoAmount( decimals = decimals, symbol = symbol, decimalFormat = rememberDecimalFormat(), + symbolColor = textColor, ), onValueChange = onValueChange, color = textColor, @@ -120,6 +121,7 @@ fun InputRowEnterInfoAmountV2( titleColor: Color = TangemTheme.colors.text.secondary, textColor: Color = TangemTheme.colors.text.primary1, infoColor: Color = TangemTheme.colors.text.tertiary, + symbolColor: Color = TangemTheme.colors.text.primary1, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, showDivider: Boolean = false, @@ -154,6 +156,7 @@ fun InputRowEnterInfoAmountV2( decimals = decimals, symbol = symbol, decimalFormat = rememberDecimalFormat(), + symbolColor = symbolColor, ), onValueChange = onValueChange, color = textColor, diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt index fc74ccf197..ef94c6b681 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt @@ -82,7 +82,7 @@ fun Modifier.selectedBorder( color = color, shape = RoundedCornerShape(radius), ) - .padding(2.5.dp) + .padding(width) .border( width = 1.dp, color = TangemTheme.colors.text.accent, @@ -91,7 +91,7 @@ fun Modifier.selectedBorder( .clip(RoundedCornerShape(radius - 2.dp)) }, otherModifier = { - padding(2.dp) + padding(width) .clip(RoundedCornerShape(radius - 2.dp)) }, ) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt index 608587c822..07d5b17e3c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt @@ -51,6 +51,11 @@ internal fun OnrampAmountContent(state: OnrampAmountBlockUM, modifier: Modifier private fun OnrampAmountField(amountField: AmountFieldModel) { val decimalFormat = rememberDecimalFormat() val requester = remember { FocusRequester() } + val symbolColor = if (amountField.fiatValue.isBlank()) { + TangemTheme.colors.text.disabled + } else { + TangemTheme.colors.text.primary1 + } AmountTextField( value = amountField.fiatValue, decimals = amountField.fiatAmount.decimals, @@ -59,6 +64,7 @@ private fun OnrampAmountField(amountField: AmountFieldModel) { symbol = amountField.fiatAmount.currencySymbol, currencyCode = amountField.fiatAmount.currencySymbol, decimalFormat = decimalFormat, + symbolColor = symbolColor, ), onValueChange = amountField.onValueChange, keyboardOptions = amountField.keyboardOptions, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt index 691bd074ca..d60c1b6b81 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt @@ -17,6 +17,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase +import com.tangem.features.send.v2.api.SendFeatureToggles import com.tangem.features.send.v2.common.PredefinedValues import com.tangem.features.send.v2.common.ui.state.NavigationUM import com.tangem.features.send.v2.impl.R @@ -47,6 +48,7 @@ internal class SendAmountModel @Inject constructor( private val feeReloadTrigger: SendFeeReloadTrigger, private val sendAmountUpdateQRListener: SendAmountUpdateQRListener, private val analyticsEventHandler: AnalyticsEventHandler, + private val sendFeatureToggles: SendFeatureToggles, ) : Model(), AmountScreenClickIntents { private val params: SendAmountComponentParams = paramsContainer.require() @@ -95,6 +97,7 @@ internal class SendAmountModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, maxEnterAmount = maxAmountBoundary, iconStateConverter = CryptoCurrencyToIconStateConverter(), + isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled, ).convert( AmountParameters( title = stringReference(userWallet.name),