From cd28b908733ec699bcb9fb9cff2c651eabd3be65 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 24 Oct 2023 15:08:44 +0300 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 1 + core/res/src/main/res/values/strings.xml | 4 +- .../com/tangem/core/ui/components/Buttons.kt | 9 +- .../components/buttons/common/TangemButton.kt | 4 +- .../segmentedbutton/SegmentedButton.kt | 161 +++++++++++++++++ features/send/impl/build.gradle.kts | 14 ++ .../state/SendAmountSegmentedButtonsConfig.kt | 21 +++ .../presentation/send/state/SendUiState.kt | 57 ++++++ .../send/state/fields/SendTextField.kt | 35 ++++ .../send/ui/SendBottomSheetConfig.kt | 7 - .../send/ui/SendNavigationButtons.kt | 52 +++--- .../impl/presentation/send/ui/SendScreen.kt | 7 +- .../impl/presentation/send/ui/SendStates.kt | 34 ---- .../send/ui/amount/AmountField.kt | 168 ++++++++++++++++++ .../send/ui/amount/AmountFieldContainer.kt | 61 +++++++ 15 files changed, 557 insertions(+), 78 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/SendAmountSegmentedButtonsConfig.kt create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/SendUiState.kt create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/fields/SendTextField.kt delete mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendBottomSheetConfig.kt delete mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendStates.kt create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/amount/AmountField.kt create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/amount/AmountFieldContainer.kt diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 3345fb4f82..76f6aa404f 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -90,6 +90,7 @@ Нравится Заблокирован Основная сеть + Далее Нет Нет адреса Нет данных diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 456401a4a4..3e043496b0 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -89,6 +89,7 @@ Like Locked Main network + Next No No address No data @@ -114,7 +115,6 @@ Submit Success Swap - Next terms and conditions Transaction failed Transactions @@ -392,6 +392,7 @@ Normal Priority Maximum amount + Max Network fee Sending %s Total @@ -400,6 +401,7 @@ %s will be sent Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while Invalid address + Insufficient funds for transfer Buy now I have a promo code… Tangem Wallet diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index 385824799e..cac2add621 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -7,11 +7,10 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R -import com.tangem.core.ui.components.buttons.common.TangemButton -import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition -import com.tangem.core.ui.components.buttons.common.TangemButtonSize +import com.tangem.core.ui.components.buttons.common.* import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults import com.tangem.core.ui.res.TangemTheme @@ -146,6 +145,8 @@ fun SecondaryButton( modifier: Modifier = Modifier, showProgress: Boolean = false, enabled: Boolean = true, + size: TangemButtonSize = TangemButtonSize.Default, + shape: Shape = size.toShape(), ) { TangemButton( modifier = modifier, @@ -155,6 +156,8 @@ fun SecondaryButton( colors = TangemButtonsDefaults.secondaryButtonColors, enabled = enabled, showProgress = showProgress, + size = size, + shape = shape, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt index fc114059fe..f245558598 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle @@ -29,13 +30,14 @@ fun TangemButton( size: TangemButtonSize = TangemButtonSize.Default, elevation: ButtonElevation = TangemButtonsDefaults.elevation, textStyle: TextStyle = TangemTheme.typography.button, + shape: Shape = size.toShape(), ) { Button( modifier = modifier.heightIn(min = size.toHeightDp()), onClick = { if (!showProgress) onClick() }, enabled = enabled, elevation = elevation, - shape = size.toShape(), + shape = shape, colors = colors, contentPadding = size.toContentPadding(icon = icon), ) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt new file mode 100644 index 0000000000..df18f78ab0 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt @@ -0,0 +1,161 @@ +package com.tangem.core.ui.components.buttons.segmentedbutton + +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.res.TangemTheme +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf + +/** + * Segmented buttons + * + * [Figma component](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1961-2004&mode=design&t=OFPQ18YhLHVAANab-4) + * + * @param config list of buttons in SegmentedButtons + * @param onClick button click + * @param modifier component modifier + * @param color default button color + * @param selectedColor selected button color + * @param dividerColor border and divider color + * @param showIndication show ripple indication + * @param buttonContent content as separate button + */ +@Composable +inline fun SegmentedButtons( + config: PersistentList, + crossinline onClick: (T) -> Unit, + modifier: Modifier = Modifier, + color: Color = TangemTheme.colors.background.tertiary, + selectedColor: Color = TangemTheme.colors.background.action, + dividerColor: Color = TangemTheme.colors.stroke.primary, + showIndication: Boolean = true, + crossinline buttonContent: @Composable (T) -> Unit, +) { + if (config.isEmpty() || config.size == 1) return + + var selected by remember { mutableIntStateOf(0) } + + Row( + modifier = modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius26)) + .background(dividerColor) + .padding(TangemTheme.dimens.spacing1), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing1), + ) { + repeat(config.size) { index -> + val leftRadius = if (index == 0) TangemTheme.dimens.radius26 else TangemTheme.dimens.radius0 + val rightRadius = if (index == config.lastIndex) TangemTheme.dimens.radius26 else TangemTheme.dimens.radius0 + + Box( + modifier = Modifier + .weight(1f) + .background( + color = if (index == selected) selectedColor else color, + shape = RoundedCornerShape( + topStart = leftRadius, + topEnd = rightRadius, + bottomEnd = rightRadius, + bottomStart = leftRadius, + ), + ) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = if (showIndication) LocalIndication.current else null, + ) { + onClick(config[index]) + selected = index + }, + ) { + buttonContent.invoke(config[index]) + } + } + } +} + +@Preview +@Composable +private fun SegmentedButtonsPreview_Light( + @PreviewParameter(SegmentedButtonsPreviewProvider::class) config: PersistentList, +) { + TangemTheme { + SegmentedButtons( + config = config, + onClick = {}, + ) { + Text( + text = it.text, + modifier = Modifier.padding(TangemTheme.dimens.spacing16), + ) + } + } +} + +@Preview +@Composable +private fun SegmentedButtonsPreview_Dark( + @PreviewParameter(SegmentedButtonsPreviewProvider::class) config: PersistentList, +) { + TangemTheme(isDark = true) { + SegmentedButtons( + config = config, + onClick = {}, + ) { + Text( + text = it.text, + modifier = Modifier.padding(TangemTheme.dimens.spacing16), + ) + } + } +} + +//region Preview config +/** + * Segmented buttons preview model + * + * @param text button title + */ +internal data class SegmentedButtonsConfigPreview( + val text: String, +) + +/** + * Segmented button preview provider + */ +internal class SegmentedButtonsPreviewProvider : + CollectionPreviewParameterProvider>( + collection = listOf( + persistentListOf( + SegmentedButtonsConfigPreview( + text = "Title 1", + ), + SegmentedButtonsConfigPreview( + text = "Title 2", + ), + SegmentedButtonsConfigPreview( + text = "Title 3", + ), + ), + persistentListOf( + SegmentedButtonsConfigPreview( + text = "Title 1", + ), + SegmentedButtonsConfigPreview( + text = "Title 2", + ), + ), + ), + ) + +//endregion \ No newline at end of file diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts index 3c6f6bd7f7..c0cfcb3894 100644 --- a/features/send/impl/build.gradle.kts +++ b/features/send/impl/build.gradle.kts @@ -14,7 +14,11 @@ dependencies { /** AndroidX */ implementation(deps.androidx.fragment.ktx) implementation(deps.androidx.appCompat) + + /** Other dependencies */ + implementation(deps.kotlin.immutable.collections) implementation(deps.material) + implementation(deps.arrow.core) /** Compose */ implementation(deps.compose.accompanist.systemUiController) @@ -23,13 +27,23 @@ dependencies { implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) implementation(deps.compose.navigation) + implementation(deps.compose.navigation.hilt) + + /** Common */ + implementation(projects.common) /** Core modules */ implementation(projects.core.featuretoggles) implementation(projects.core.ui) + implementation(projects.core.utils) /** Domain modules */ + implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets) + implementation(projects.domain.wallets.models) + implementation(projects.domain.appCurrency) + implementation(projects.domain.appCurrency.models) /** Feature modules */ implementation(projects.features.send.api) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/SendAmountSegmentedButtonsConfig.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/SendAmountSegmentedButtonsConfig.kt new file mode 100644 index 0000000000..ea3220b6e6 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/SendAmountSegmentedButtonsConfig.kt @@ -0,0 +1,21 @@ +package com.tangem.features.send.impl.presentation.send.state + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.currency.tokenicon.TokenIconState +import com.tangem.core.ui.extensions.TextReference + +/** + * Segmented buttons config + * + * @param title button title + * @param iconState currency icon state + * @param iconUrl currency icon url + * @param isFiat is fiat currency + */ +@Immutable +internal data class SendAmountSegmentedButtonsConfig( + val title: TextReference, + val iconState: TokenIconState? = null, + val iconUrl: String? = null, + val isFiat: Boolean, +) \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/SendUiState.kt new file mode 100644 index 0000000000..10df3f1966 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/SendUiState.kt @@ -0,0 +1,57 @@ +package com.tangem.features.send.impl.presentation.send.state + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.currency.tokenicon.TokenIconState +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.send.state.fields.SendTextField +import kotlinx.collections.immutable.PersistentList + +/** + * Ui states of the send screen + */ +@Immutable +internal sealed class SendUiState { + + /** States with content */ + sealed class Content : SendUiState() { + + abstract val nextButtonEnabled: Boolean + + /** Initial state */ + data class Initial( + override val nextButtonEnabled: Boolean = false, + ) : Content() + + /** Amount state */ + data class AmountState( + override val nextButtonEnabled: Boolean = false, + val walletName: String, + val walletBalance: String, + val tokenIconState: TokenIconState, + val cryptoCurrencyStatus: CryptoCurrencyStatus, + val appCurrency: AppCurrency, + val isFiatValue: Boolean, + val segmentedButtonConfig: PersistentList, + val amountTextField: SendTextField.Amount, + ) : Content() + + // todo [REDACTED_JIRA] + /** Recipient state */ + data class RecipientState( + override val nextButtonEnabled: Boolean = false, + ) : Content() + + // todo [REDACTED_JIRA] + /** Fee and speed state */ + data class FeeState( + override val nextButtonEnabled: Boolean = false, + ) : Content() + + // todo [REDACTED_JIRA] + /** Send state */ + data class SendState( + override val nextButtonEnabled: Boolean = true, + ) : Content() + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/fields/SendTextField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/fields/SendTextField.kt new file mode 100644 index 0000000000..e7677e747d --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/state/fields/SendTextField.kt @@ -0,0 +1,35 @@ +package com.tangem.features.send.impl.presentation.send.state.fields + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.extensions.TextReference + +@Immutable +internal sealed class SendTextField { + + /** Current value */ + abstract val value: String + + /** Lambda be invoked when value is been changed */ + abstract val onValueChange: (String) -> Unit + + /** Keyboard options */ + abstract val keyboardOptions: KeyboardOptions + + /** Label */ + abstract val label: TextReference + + /** Placeholder (hint) */ + abstract val placeholder: TextReference + + data class Amount( + override val value: String, + override val onValueChange: (String) -> Unit, + override val keyboardOptions: KeyboardOptions, + override val label: TextReference, + override val placeholder: TextReference, + val fiatValue: String, + val isError: Boolean, + val error: TextReference, + ) : SendTextField() +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendBottomSheetConfig.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendBottomSheetConfig.kt deleted file mode 100644 index 73f5797f7b..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendBottomSheetConfig.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.tangem.features.send.impl.presentation.send.ui - -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent - -data class SendBottomSheetConfig( - val currentState: SendStates, -) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendNavigationButtons.kt index 7630c89501..9604a47dfd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendNavigationButtons.kt @@ -18,17 +18,18 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.send.impl.presentation.send.state.SendUiState @Composable -internal fun SendNavigationButtons(currentState: SendStates) { +internal fun SendNavigationButtons(uiState: SendUiState.Content) { Row( modifier = Modifier .fillMaxWidth() .padding(bottom = TangemTheme.dimens.spacing12), ) { - SendSecondaryNavigationButton(currentState) + SendSecondaryNavigationButton(uiState) SendPrimaryNavigationButton( - currentState, + uiState = uiState, modifier = Modifier .weight(1f) .padding(horizontal = TangemTheme.dimens.spacing16), @@ -37,9 +38,9 @@ internal fun SendNavigationButtons(currentState: SendStates) { } @Composable -private fun SendSecondaryNavigationButton(currentState: SendStates) { +private fun SendSecondaryNavigationButton(uiState: SendUiState.Content) { AnimatedVisibility( - visible = currentState == SendStates.Recipient || currentState == SendStates.Fee, + visible = uiState is SendUiState.Content.RecipientState || uiState is SendUiState.Content.FeeState, ) { Icon( modifier = Modifier @@ -57,40 +58,37 @@ private fun SendSecondaryNavigationButton(currentState: SendStates) { } @Composable -private fun SendPrimaryNavigationButton(currentState: SendStates, modifier: Modifier = Modifier) { - val buttonTextId = when (currentState) { - SendStates.Amount, - SendStates.Recipient, - SendStates.Fee, +private fun SendPrimaryNavigationButton(uiState: SendUiState.Content, modifier: Modifier = Modifier) { + val buttonTextId = when (uiState) { + is SendUiState.Content.AmountState, + is SendUiState.Content.RecipientState, + is SendUiState.Content.FeeState, -> R.string.common_next - SendStates.Filled -> R.string.common_send - SendStates.Done -> R.string.common_close + is SendUiState.Content.SendState -> R.string.common_send + else -> R.string.common_close } AnimatedContent( targetState = buttonTextId, label = "Update send screen state", modifier = modifier, ) { textId -> - when (currentState) { - SendStates.Amount, - SendStates.Recipient, - SendStates.Fee, - SendStates.Done, - -> PrimaryButton( + if (uiState is SendUiState.Content.SendState) { + PrimaryButtonIconEnd( text = stringResource(textId), + iconResId = R.drawable.ic_tangem_24, + enabled = uiState.nextButtonEnabled, + onClick = { + // todo add next click + }, + ) + } else { + PrimaryButton( + text = stringResource(textId), + enabled = uiState.nextButtonEnabled, onClick = { // todo add next click }, ) - SendStates.Filled -> { - PrimaryButtonIconEnd( - text = stringResource(textId), - iconResId = R.drawable.ic_tangem_24, - onClick = { - // todo add next click - }, - ) - } } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendScreen.kt index 3ec40b3169..61351bd875 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendScreen.kt @@ -9,17 +9,14 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetDraggableHeader import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.send.impl.presentation.send.state.SendUiState @Composable fun SendScreen() { - // todo will be removed - val config = remember { SendBottomSheetConfig(SendStates.Amount) } - Column( modifier = Modifier .imePadding() @@ -42,7 +39,7 @@ fun SendScreen() { ) { SendScreenContent() } - SendNavigationButtons(config.currentState) + SendNavigationButtons(uiState = SendUiState.Content.Initial()) } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendStates.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendStates.kt deleted file mode 100644 index f0a3e9840b..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/SendStates.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.features.send.impl.presentation.send.ui - -/** - * Screen states of Send - */ -enum class SendStates { - Amount, - Recipient, - Fee, - Filled, - Done, - ; - - companion object { - - /** Get next [SendStates] */ - fun SendStates.next(): SendStates { - return if (this.ordinal < SendStates.values().last().ordinal) { - SendStates.values()[this.ordinal + 1] - } else { - this - } - } - - /** Get previous [SendStates] */ - fun SendStates.previous(): SendStates { - return if (this.ordinal > 0) { - SendStates.values()[this.ordinal - 1] - } else { - this - } - } - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/amount/AmountField.kt new file mode 100644 index 0000000000..4eda379a95 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/amount/AmountField.kt @@ -0,0 +1,168 @@ +package com.tangem.features.send.impl.presentation.send.ui.amount + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.BottomCenter +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +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 androidx.compose.ui.text.style.TextAlign +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.send.impl.presentation.send.state.fields.SendTextField + +@Composable +internal fun ColumnScope.AmountField( + sendField: SendTextField.Amount, + cryptoSymbol: String, + fiatSymbol: String, + isFiat: Boolean, +) { + val value = if (isFiat) sendField.fiatValue else sendField.value + val secondaryValue = if (!isFiat) sendField.fiatValue else sendField.value + val symbol = if (isFiat) fiatSymbol else cryptoSymbol + val secondarySymbol = if (!isFiat) fiatSymbol else cryptoSymbol + + AmountFieldInner( + value = value, + placeholder = sendField.placeholder, + symbol = symbol, + onValueChange = sendField.onValueChange, + keyboardOptions = sendField.keyboardOptions, + modifier = Modifier + .align(CenterHorizontally) + .padding( + top = TangemTheme.dimens.spacing24, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + ), + ) + + Box( + modifier = Modifier + .align(CenterHorizontally) + .padding( + top = TangemTheme.dimens.spacing8, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + ), + ) { + Text( + text = "$secondaryValue $secondarySymbol", + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + modifier = Modifier + .align(BottomCenter) + .padding(bottom = TangemTheme.dimens.spacing32), + ) + AmountFieldError( + isError = sendField.isError, + error = sendField.error, + modifier = Modifier + .align(BottomCenter) + .padding(bottom = TangemTheme.dimens.spacing12), + ) + } +} + +@Composable +private fun AmountFieldInner( + value: String, + placeholder: TextReference, + symbol: String, + onValueChange: (String) -> Unit, + keyboardOptions: KeyboardOptions, + modifier: Modifier = Modifier, +) { + val focusRequester = remember { FocusRequester() } + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier + .focusRequester(focusRequester) + .background(TangemTheme.colors.background.action), + textStyle = TangemTheme.typography.h2.copy( + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ), + keyboardOptions = keyboardOptions, + singleLine = true, + visualTransformation = AmountVisualTransformation(symbol), + decorationBox = { innerTextField -> + Box { + if (value.isBlank()) { + Text( + text = "${placeholder.resolveReference()} $symbol", + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.disabled, + textAlign = TextAlign.Center, + modifier = Modifier + .align(Alignment.TopCenter), + ) + } + innerTextField() + } + }, + ) +} + +@Composable +private fun AmountFieldError(isError: Boolean, error: TextReference, modifier: Modifier = Modifier) { + AnimatedVisibility( + visible = isError, + enter = fadeIn(), + exit = fadeOut(), + modifier = modifier, + ) { + Text( + text = error.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.warning, + textAlign = TextAlign.Center, + ) + } +} + +private class AmountVisualTransformation( + private val symbol: String, +) : VisualTransformation { + override fun filter(text: AnnotatedString): TransformedText { + return TransformedText( + buildAnnotatedString { + append(text) + if (text.isNotBlank()) { + append(" ") + append(symbol) + } + }, + object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + return text.length + } + + override fun transformedToOriginal(offset: Int): Int { + return text.length + } + }, + ) + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/amount/AmountFieldContainer.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/amount/AmountFieldContainer.kt new file mode 100644 index 0000000000..b7810d5acb --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/send/ui/amount/AmountFieldContainer.kt @@ -0,0 +1,61 @@ +package com.tangem.features.send.impl.presentation.send.ui.amount + +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.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign +import com.tangem.core.ui.components.currency.tokenicon.TokenIcon +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.send.impl.presentation.send.state.SendUiState + +@Composable +internal fun AmountFieldContainer(amountState: SendUiState.Content.AmountState, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxWidth() + .padding( + top = TangemTheme.dimens.spacing4, + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + ) + .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) + .background(TangemTheme.colors.background.action), + ) { + Text( + text = amountState.walletName, + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing14) + .align(Alignment.CenterHorizontally), + ) + Text( + text = amountState.walletBalance, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing2) + .align(Alignment.CenterHorizontally), + ) + TokenIcon( + state = amountState.tokenIconState, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing32) + .align(Alignment.CenterHorizontally), + ) + AmountField( + sendField = amountState.amountTextField, + cryptoSymbol = amountState.cryptoCurrencyStatus.currency.symbol, + fiatSymbol = amountState.appCurrency.symbol, + isFiat = amountState.isFiatValue, + ) + } +} \ No newline at end of file