From a223c140cf2d58f13852b2be698ffcc56a1d44cd Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 22 Dec 2023 17:06:53 +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 | 1 - .../com/tangem/core/ui/components/Buttons.kt | 15 +- .../com/tangem/core/ui/components/Dialogs.kt | 53 +++- .../buttons/common/TangemButtonsDefaults.kt | 10 + .../fields/SimpleDialogTextField.kt | 97 +++++++ .../common/ui/ChooseWalletScreen.kt | 4 +- .../previewdata/AddCustomTokenPreviewData.kt | 49 ++++ .../ChooseDerivationPreviewData.kt | 42 +++ .../ChooseNetworkCustomPreviewData.kt | 32 +++ .../customtokens/ui/ChooseDerivationScreen.kt | 114 ++++++++ .../ui/ChooseNetworkCustomScreen.kt | 94 +++++++ .../customtokens/ui/CustomDerivationDialog.kt | 43 +++ .../ui/CustomTokensChooseWalletScreen.kt | 20 ++ .../customtokens/ui/CustomTokensScreen.kt | 256 ++++++++++++++++++ .../customtokens/ui/TokenTextField.kt | 54 ++++ .../router/DefaultManageTokensRouter.kt | 29 +- 17 files changed, 890 insertions(+), 24 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleDialogTextField.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/AddCustomTokenPreviewData.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseDerivationPreviewData.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseNetworkCustomPreviewData.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseDerivationScreen.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseNetworkCustomScreen.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomDerivationDialog.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensChooseWalletScreen.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensScreen.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/TokenTextField.kt diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 190b4f48ad..530ffd016c 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -265,7 +265,6 @@ Рыночная капитализация Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление. Голосовать - Кошелек не поддерживает выбранную монету Выберите кошелек Кошелёк не поддерживает более одной сети Вам необходимо установить единый код доступа для защиты всех ваших карт diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 4aea85ef4c..414edfc00e 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -286,7 +286,6 @@ Coin market cap The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it. Upvote - Wallet Incompatible with selected Coin Choose wallet The wallet doesn\'t support more than one network You have to set up a single access code to protect all your wallets 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 a9eddb52f7..1e2301844b 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 @@ -5,13 +5,13 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.ButtonColors 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.* -import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults import com.tangem.core.ui.res.TangemTheme // region TextButton @@ -19,7 +19,13 @@ import com.tangem.core.ui.res.TangemTheme * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=97%3A103&t=TmfD6UBHPg9uYfev-4) * */ @Composable -fun TextButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true) { +fun TextButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + colors: ButtonColors = TangemButtonsDefaults.defaultTextButtonColors, + enabled: Boolean = true, +) { TangemButton( modifier = modifier, text = text, @@ -27,7 +33,7 @@ fun TextButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier, onClick = onClick, enabled = enabled, showProgress = false, - colors = TangemButtonsDefaults.defaultTextButtonColors, + colors = colors, size = TangemButtonSize.Text, ) } @@ -41,6 +47,7 @@ fun TextButtonIconStart( @DrawableRes iconResId: Int, onClick: () -> Unit, modifier: Modifier = Modifier, + colors: ButtonColors = TangemButtonsDefaults.defaultTextButtonColors, enabled: Boolean = true, ) { TangemButton( @@ -50,7 +57,7 @@ fun TextButtonIconStart( onClick = onClick, enabled = enabled, showProgress = false, - colors = TangemButtonsDefaults.defaultTextButtonColors, + colors = colors, size = TangemButtonSize.Text, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt index 41e2d008b6..f332e61cbf 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt @@ -7,9 +7,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material.RadioButton -import androidx.compose.material.RadioButtonDefaults -import androidx.compose.material.Text +import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember @@ -24,6 +22,8 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.tangem.core.ui.R import com.tangem.core.ui.components.SelctorDialogParamsProvider.SelectorDialogParams +import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults +import com.tangem.core.ui.components.fields.SimpleDialogTextField import com.tangem.core.ui.res.TangemTheme import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -118,6 +118,34 @@ fun TextInputDialog( ) } +@Composable +fun TextInputDialog( + fieldValue: String, + confirmButton: DialogButton, + onDismissDialog: () -> Unit, + onValueChange: (String) -> Unit, + textFieldParams: AdditionalTextInputDialogParams, + title: String? = null, + dismissButton: DialogButton? = null, + isDismissable: Boolean = true, +) { + TangemDialog( + type = DialogType.SimpleTextInput( + value = fieldValue, + onValueChange = onValueChange, + params = textFieldParams, + ), + confirmButton = confirmButton, + onDismissDialog = onDismissDialog, + title = title, + dismissButton = dismissButton, + properties = DialogProperties( + dismissOnBackPress = isDismissable, + dismissOnClickOutside = isDismissable, + ), + ) +} + @Composable fun SelectorDialog( selectedItemIndex: Int, @@ -164,6 +192,7 @@ data class AdditionalTextInputDialogParams( val caption: String? = null, val enabled: Boolean = true, val isError: Boolean = false, + val errorText: String? = null, ) // region Defaults @@ -194,6 +223,7 @@ private fun TangemDialog( style = when (type) { is DialogType.Message -> TangemTheme.typography.h2 is DialogType.TextInput -> TangemTheme.typography.h3 + is DialogType.SimpleTextInput -> TangemTheme.typography.h3 is DialogType.Selector -> TangemTheme.typography.h2 }, color = TangemTheme.colors.text.primary1, @@ -231,6 +261,16 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) { color = TangemTheme.colors.text.secondary, ) } + is DialogType.SimpleTextInput -> { + SimpleDialogTextField( + value = type.value, + onValueChange = type.onValueChange, + isError = type.params.isError, + errorText = type.params.errorText, + isEnabled = type.params.enabled, + placeholder = type.params.placeholder, + ) + } is DialogType.TextInput -> { OutlineTextField( modifier = Modifier @@ -305,6 +345,7 @@ private fun DialogButton( text = text, enabled = enabled, onClick = onClick, + colors = TangemButtonsDefaults.positiveButtonColors, ) } } @@ -371,6 +412,12 @@ private sealed class DialogType { val params: AdditionalTextInputDialogParams = AdditionalTextInputDialogParams(), ) : DialogType() + data class SimpleTextInput( + val value: String, + val onValueChange: (String) -> Unit, + val params: AdditionalTextInputDialogParams = AdditionalTextInputDialogParams(), + ) : DialogType() + data class Selector( val selectedItemIndex: Int, val items: ImmutableList, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonsDefaults.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonsDefaults.kt index 1511a25c2f..db255b710f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonsDefaults.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonsDefaults.kt @@ -66,4 +66,14 @@ internal object TangemButtonsDefaults { disabledBackgroundColor = Color.Transparent, disabledContentColor = TangemTheme.colors.text.disabled, ) + + val positiveButtonColors: ButtonColors + @Composable + @ReadOnlyComposable + get() = TangemButtonColors( + backgroundColor = Color.Transparent, + contentColor = TangemTheme.colors.text.accent, + disabledBackgroundColor = Color.Transparent, + disabledContentColor = TangemTheme.colors.text.disabled, + ) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleDialogTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleDialogTextField.kt new file mode 100644 index 0000000000..098f576a94 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleDialogTextField.kt @@ -0,0 +1,97 @@ +package com.tangem.core.ui.components.fields + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.SolidColor +import com.tangem.core.ui.res.TangemTheme + +/** + * A simple input field for a basic dialog with input + * + * [Show in Figma] (https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=48%3A30&mode=design + * &t=OsU3Nqsm9d8WDNuv-1) + */ +@Composable +fun SimpleDialogTextField( + value: String, + onValueChange: (String) -> Unit, + isError: Boolean = false, + errorText: String? = null, + isEnabled: Boolean = true, + placeholder: String? = null, +) { + val strokeColor = if (isError) { + TangemTheme.colors.icon.warning + } else { + TangemTheme.colors.stroke.primary + } + Column { + BasicTextField( + value = value, + onValueChange = onValueChange, + textStyle = TangemTheme.typography.body1, + cursorBrush = SolidColor(TangemTheme.colors.text.primary1), + singleLine = true, + readOnly = !isEnabled, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing24) + .drawBehind { + val strokeWidth = 2f + val y = size.height - strokeWidth / 2 + drawLine( + strokeColor, + Offset(0f, y), + Offset(size.width, y), + strokeWidth, + ) + }, + decorationBox = { textValue -> + Box { + this@Column.AnimatedVisibility( + visible = value.isBlank() && placeholder != null, + enter = fadeIn(), + exit = fadeOut(), + ) { + if (value.isBlank() && placeholder != null) { + Text( + text = placeholder, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.disabled, + ) + } + } + textValue() + } + }, + ) + AnimatedVisibility( + visible = errorText != null, + enter = fadeIn(), + exit = fadeOut(), + ) { + if (errorText != null) { + Text( + text = errorText, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.warning, + modifier = Modifier + .padding( + horizontal = TangemTheme.dimens.spacing24, + ), + ) + } + } + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt index 617fed74bd..878a5f28e1 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt @@ -45,7 +45,9 @@ internal fun ChooseWalletScreen(state: ChooseWalletState.Choose, modifier: Modif ) { IconButton( onClick = state.onCloseChoosingWalletClick, - modifier = Modifier.align(Alignment.CenterStart), + modifier = Modifier + .align(Alignment.CenterStart) + .clickable { state.onCloseChoosingWalletClick() }, ) { Icon( painterResource(id = R.drawable.ic_back_24), diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/AddCustomTokenPreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/AddCustomTokenPreviewData.kt new file mode 100644 index 0000000000..998c00a3e7 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/AddCustomTokenPreviewData.kt @@ -0,0 +1,49 @@ +package com.tangem.managetokens.presentation.customtokens.state.previewdata + +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.common.state.WalletState +import com.tangem.managetokens.presentation.customtokens.state.* +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentSetOf + +internal object AddCustomTokenPreviewData { + val state = AddCustomTokenState( + chooseWalletState = ChooseWalletState.Choose( + wallets = persistentListOf(), + selectedWallet = WalletState( + "", + "", + "My Wallet", + {}, + ), + onChooseWalletClick = { }, + onCloseChoosingWalletClick = { }, + ), + chooseNetworkState = ChooseNetworkState( + networks = persistentListOf(), + selectedNetwork = null, + onChooseNetworkClick = { }, + onCloseChoosingNetworkClick = {}, + ), + chooseDerivationState = ChooseDerivationState( + derivations = persistentListOf(), + selectedDerivation = null, + enterCustomDerivationState = null, + onChooseDerivationClick = { }, + onCloseChoosingDerivationClick = {}, + onEnterCustomDerivation = {}, + ), + tokenData = CustomTokenData( + contractAddressTextField = TextFieldState.Editable( + value = "0x4ace7262705b68bcba5b91de96889349394", + isEnabled = false, + onValueChange = {}, + ), + nameTextField = TextFieldState.Loading, + symbolTextField = TextFieldState.Loading, + decimalsTextField = TextFieldState.Loading, + ), + warnings = persistentSetOf(), + addTokenButton = ButtonState(isEnabled = true, onClick = {}), + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseDerivationPreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseDerivationPreviewData.kt new file mode 100644 index 0000000000..2bb35400d2 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseDerivationPreviewData.kt @@ -0,0 +1,42 @@ +package com.tangem.managetokens.presentation.customtokens.state.previewdata + +import com.tangem.managetokens.presentation.customtokens.state.ChooseDerivationState +import com.tangem.managetokens.presentation.customtokens.state.Derivation +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +internal object ChooseDerivationPreviewData { + private val derivations: ImmutableList = persistentListOf( + Derivation( + networkName = "Ethereum", + path = "m/44’/9001’/0’/0/0", + networkId = "ethereum", + standardType = "ERC", + onDerivationSelected = {}, + ), + Derivation( + networkName = "Polygon", + path = "m/44’/9001’/0’/0/0", + networkId = "polygon", + standardType = "ERC", + onDerivationSelected = {}, + ), + Derivation( + networkName = "Avalanche", + path = "m/44’/9001’/0’/0/0", + networkId = "avalanche", + standardType = "ERC", + onDerivationSelected = {}, + ), + ) + + val state = ChooseDerivationState( + derivations = derivations, + selectedDerivation = derivations.first(), + enterCustomDerivationState = null, + onEnterCustomDerivation = {}, + onCloseChoosingDerivationClick = {}, + onChooseDerivationClick = {}, + show = true, + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseNetworkCustomPreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseNetworkCustomPreviewData.kt new file mode 100644 index 0000000000..5618ce7fb4 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseNetworkCustomPreviewData.kt @@ -0,0 +1,32 @@ +package com.tangem.managetokens.presentation.customtokens.state.previewdata + +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.common.state.NetworkItemState +import com.tangem.managetokens.presentation.customtokens.state.ChooseNetworkState +import kotlinx.collections.immutable.persistentListOf + +internal object ChooseNetworkCustomPreviewData { + val networks = persistentListOf( + NetworkItemState.Selectable( + name = "Ethereum", + protocolName = "ETH", + iconResId = R.drawable.img_kusama_22, + id = "ethereum", + onNetworkClick = { }, + ), + NetworkItemState.Selectable( + name = "BNB SMART CHAIN", + protocolName = "BEP20", + iconResId = R.drawable.ic_bsc_16, + id = "binance smart chain", + onNetworkClick = { }, + ), + ) + val state = ChooseNetworkState( + networks, + selectedNetwork = networks.first(), + onCloseChoosingNetworkClick = {}, + onChooseNetworkClick = {}, + show = true, + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseDerivationScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseDerivationScreen.kt new file mode 100644 index 0000000000..625ff06edd --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseDerivationScreen.kt @@ -0,0 +1,114 @@ +package com.tangem.managetokens.presentation.customtokens.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.common.ui.components.SimpleSelectionBlock +import com.tangem.managetokens.presentation.customtokens.state.ChooseDerivationState +import com.tangem.managetokens.presentation.customtokens.state.previewdata.ChooseDerivationPreviewData + +@Composable +internal fun ChooseDerivationScreen(state: ChooseDerivationState, modifier: Modifier = Modifier) { + if (state.enterCustomDerivationState != null) { + CustomDerivationDialog(state.enterCustomDerivationState) + } + + Column( + modifier = modifier + .fillMaxSize() + .background(color = TangemTheme.colors.background.tertiary) + .statusBarsPadding() + .navigationBarsPadding() + .padding( + top = TangemTheme.dimens.spacing10, + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing18, + ), + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .defaultMinSize(minHeight = TangemTheme.dimens.size44) + .padding(bottom = TangemTheme.dimens.spacing12), + contentAlignment = Alignment.CenterStart, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_back_24), + contentDescription = null, + tint = TangemTheme.colors.icon.primary1, + modifier = Modifier + .clickable { state.onCloseChoosingDerivationClick() }, + ) + Text( + text = stringResource(id = R.string.custom_token_derivation_path), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing24) + .align(Alignment.Center), + ) + } + DerivationsList(state) + } +} + +@Composable +private fun DerivationsList(state: ChooseDerivationState) { + LazyColumn( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16), + contentPadding = PaddingValues(vertical = TangemTheme.dimens.spacing12), + ) { + item { + SimpleSelectionBlock( + title = stringResource(id = R.string.custom_token_custom_derivation), + subtitle = stringResource(id = R.string.custom_token_custom_derivation), + onClick = state.onEnterCustomDerivation, + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + ) + } + items(state.derivations.count()) { index -> + val derivationItem = state.derivations[index] + SimpleSelectionBlock( + title = derivationItem.networkName, + subtitle = derivationItem.path, + onClick = { derivationItem.onDerivationSelected(derivationItem) }, + modifier = Modifier.roundedShapeItemDecoration( + currentIndex = index, + lastIndex = state.derivations.lastIndex, + addDefaultPadding = false, + ), + roundedCorners = false, + ) + } + } +} + +@Preview +@Composable +private fun Preview_ChooseDerivationScreen_Light() { + TangemTheme(isDark = false) { + ChooseDerivationScreen(state = ChooseDerivationPreviewData.state) + } +} + +@Preview +@Composable +private fun Preview_ChooseDerivationScreen_Dark() { + TangemTheme(isDark = true) { + ChooseDerivationScreen(state = ChooseDerivationPreviewData.state) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseNetworkCustomScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseNetworkCustomScreen.kt new file mode 100644 index 0000000000..c18fac8a6b --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseNetworkCustomScreen.kt @@ -0,0 +1,94 @@ +package com.tangem.managetokens.presentation.customtokens.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.common.ui.components.NetworkItem +import com.tangem.managetokens.presentation.customtokens.state.ChooseNetworkState +import com.tangem.managetokens.presentation.customtokens.state.previewdata.ChooseNetworkCustomPreviewData + +@Composable +internal fun ChooseNetworkCustomScreen(state: ChooseNetworkState, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxSize() + .background(color = TangemTheme.colors.background.tertiary) + .statusBarsPadding() + .navigationBarsPadding() + .padding( + top = TangemTheme.dimens.spacing10, + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing18, + ), + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .defaultMinSize(minHeight = TangemTheme.dimens.size44) + .padding(bottom = TangemTheme.dimens.spacing12), + contentAlignment = Alignment.CenterStart, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_back_24), + contentDescription = null, + tint = TangemTheme.colors.icon.primary1, + modifier = Modifier + .clickable { state.onCloseChoosingNetworkClick() }, + ) + Text( + text = stringResource(id = R.string.custom_token_network_selector_title), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing24) + .align(Alignment.Center), + ) + } + LazyColumn( + contentPadding = PaddingValues(vertical = TangemTheme.dimens.spacing12), + ) { + items(state.networks.count()) { index -> + NetworkItem( + state = state.networks[index], + tokenState = null, + isSelected = state.selectedNetwork == state.networks[index], + modifier = Modifier + .roundedShapeItemDecoration( + currentIndex = index, + lastIndex = state.networks.lastIndex, + addDefaultPadding = false, + ), + ) + } + } + } +} + +@Preview +@Composable +private fun Preview_ChooseNetworkScreen_Light() { + TangemTheme(isDark = false) { + ChooseNetworkCustomScreen(ChooseNetworkCustomPreviewData.state) + } +} + +@Preview +@Composable +private fun Preview_ChooseNetworkScreen_Dark() { + TangemTheme(isDark = true) { + ChooseNetworkCustomScreen(ChooseNetworkCustomPreviewData.state) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomDerivationDialog.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomDerivationDialog.kt new file mode 100644 index 0000000000..db9a76365f --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomDerivationDialog.kt @@ -0,0 +1,43 @@ +package com.tangem.managetokens.presentation.customtokens.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.components.AdditionalTextInputDialogParams +import com.tangem.core.ui.components.DialogButton +import com.tangem.core.ui.components.TextInputDialog +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.customtokens.state.EnterCustomDerivationState + +@Composable +internal fun CustomDerivationDialog(state: EnterCustomDerivationState) { + val confirmButton = DialogButton( + title = stringResource(id = R.string.common_ok), + enabled = state.confirmButtonEnabled, + onClick = state.onConfirmButtonClick, + ) + + val dismissButton = DialogButton( + title = stringResource(id = R.string.common_cancel), + onClick = state.onDismiss, + ) + + val params = AdditionalTextInputDialogParams( + placeholder = stringResource(id = R.string.custom_token_custom_derivation_placeholder), + isError = state.derivationIncorrect, + errorText = if (state.derivationIncorrect) { + stringResource(R.string.custom_token_invalid_derivation_path) + } else { + null + }, + ) + + TextInputDialog( + fieldValue = state.value, + confirmButton = confirmButton, + onDismissDialog = state.onDismiss, + onValueChange = state.onValueChange, + textFieldParams = params, + title = stringResource(id = R.string.custom_token_custom_derivation_title), + dismissButton = dismissButton, + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensChooseWalletScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensChooseWalletScreen.kt new file mode 100644 index 0000000000..358d2d6841 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensChooseWalletScreen.kt @@ -0,0 +1,20 @@ +package com.tangem.managetokens.presentation.customtokens.ui + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.common.ui.ChooseWalletScreen + +@Composable +internal fun CustomTokensChooseWalletScreen(state: ChooseWalletState.Choose) { + ChooseWalletScreen( + state = state, + modifier = Modifier + .fillMaxSize() + .statusBarsPadding() + .navigationBarsPadding(), + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensScreen.kt new file mode 100644 index 0000000000..98f97905a6 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensScreen.kt @@ -0,0 +1,256 @@ +package com.tangem.managetokens.presentation.customtokens.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +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.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.common.state.AlertState +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.common.ui.ChooseWalletBottomSheet +import com.tangem.managetokens.presentation.common.ui.ChooseWalletBottomSheetConfig +import com.tangem.managetokens.presentation.common.ui.EventEffect +import com.tangem.managetokens.presentation.common.ui.components.Alert +import com.tangem.managetokens.presentation.common.ui.components.SimpleSelectionBlock +import com.tangem.managetokens.presentation.customtokens.state.AddCustomTokenState +import com.tangem.managetokens.presentation.customtokens.state.CustomTokenData +import com.tangem.managetokens.presentation.customtokens.state.TextFieldState +import com.tangem.managetokens.presentation.customtokens.state.previewdata.AddCustomTokenPreviewData + +@Composable +internal fun CustomTokensScreen(state: AddCustomTokenState, modifier: Modifier = Modifier) { + var alertState by remember { mutableStateOf(value = null) } + + EventEffect( + event = state.event, + onAlertStateSet = { alertState = it }, + ) + alertState?.let { + Alert(state = it, onDismiss = { alertState = null }) + } + Content(state = state, modifier = modifier) +} + +@Composable +private fun Content(state: AddCustomTokenState, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .background(color = TangemTheme.colors.background.tertiary) + .statusBarsPadding() + .navigationBarsPadding() + .padding( + top = TangemTheme.dimens.spacing10, + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing18, + ) + .fillMaxWidth(), + ) { + Text( + text = stringResource(id = R.string.manage_tokens_network_selector_title), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = TangemTheme.dimens.spacing10), + ) + Text( + text = stringResource(id = R.string.custom_token_subtitle), + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.caption2, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = TangemTheme.dimens.spacing16), + ) + CustomTokenItemsList(state = state) + PrimaryButton( + text = stringResource(id = R.string.custom_token_add_token), + onClick = state.addTokenButton.onClick, + enabled = state.addTokenButton.isEnabled, + modifier = Modifier.fillMaxWidth(), + ) + } + + if (state.chooseWalletState is ChooseWalletState.Choose && state.chooseWalletState.show) { + val config = TangemBottomSheetConfig( + isShow = true, + content = ChooseWalletBottomSheetConfig(state.chooseWalletState), + onDismissRequest = state.chooseWalletState.onCloseChoosingWalletClick, + ) + ChooseWalletBottomSheet(config) + } +} + +@Composable +private fun ColumnScope.CustomTokenItemsList(state: AddCustomTokenState, modifier: Modifier = Modifier) { + LazyColumn( + modifier + .fillMaxWidth() + .weight(1f), + ) { + if (state.chooseWalletState is ChooseWalletState.Choose) { + item { + SimpleSelectionBlock( + title = stringResource(id = R.string.manage_tokens_network_selector_wallet), + subtitle = state.chooseWalletState.selectedWallet?.walletName ?: "", + onClick = state.chooseWalletState.onChooseWalletClick, + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing12), + ) + } + } + item { + SimpleSelectionBlock( + title = stringResource(id = R.string.custom_token_network_input_title), + subtitle = state.chooseNetworkState.selectedNetwork?.name + ?: stringResource(id = R.string.manage_tokens_network_selector_title), + onClick = state.chooseNetworkState.onChooseNetworkClick, + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing12), + ) + } + if (state.tokenData != null) { + item { + TokenFields( + state = state.tokenData, + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing12), + ) + } + } + if (state.chooseDerivationState != null) { + item { + SimpleSelectionBlock( + title = stringResource(id = R.string.custom_token_derivation_path), + subtitle = state.chooseDerivationState.selectedDerivation?.path + ?: stringResource(id = R.string.custom_token_derivation_path_default), + onClick = state.chooseDerivationState.onChooseDerivationClick, + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + ) + } + } + } +} + +@Composable +private fun TokenFields(state: CustomTokenData, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxWidth() + .clip(shape = RoundedCornerShape(TangemTheme.dimens.radius16)) + .background(color = TangemTheme.colors.background.action), + ) { + TokenField( + textFieldState = state.contractAddressTextField, + placeholder = "0x0000000000000000000000000000000", + title = R.string.custom_token_contract_address_input_title, + ) + TokenField( + textFieldState = state.nameTextField, + placeholder = stringResource(id = R.string.custom_token_name_input_placeholder), + title = R.string.custom_token_name_input_title, + ) + TokenField( + textFieldState = state.symbolTextField, + placeholder = stringResource(id = R.string.custom_token_token_symbol_input_placeholder), + title = R.string.custom_token_token_symbol_input_title, + ) + TokenField( + textFieldState = state.decimalsTextField, + placeholder = "0", + title = R.string.custom_token_decimals_input_title, + ) + } +} + +@Composable +private fun TokenField( + textFieldState: TextFieldState, + placeholder: String, + title: Int, + keyboardType: KeyboardType = KeyboardType.Text, +) { + Column( + modifier = Modifier + .background(color = TangemTheme.colors.background.action) + .padding(TangemTheme.dimens.spacing16), + ) { + TokenTextFieldTitle( + state = textFieldState, + title = stringResource(id = title), + ) + when (textFieldState) { + is TextFieldState.Editable -> TokenTextField( + state = textFieldState, + placeholder = placeholder, + keyboardType = keyboardType, + ) + is TextFieldState.Loading -> TokenShimmer() + } + } +} + +@Composable +private fun TokenShimmer() { + RectangleShimmer( + radius = TangemTheme.dimens.radius3, + modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing4) + .size( + height = TangemTheme.dimens.size12, + width = TangemTheme.dimens.size90, + ), + ) +} + +@Composable +private fun TokenTextFieldTitle(state: TextFieldState?, title: String) { + val modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing4) + val error = (state as? TextFieldState.Editable)?.error + if (error == null) { + Text( + text = title, + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.subtitle1, + modifier = modifier, + ) + } else { + Text( + text = error.title.resolveReference(), + color = TangemTheme.colors.text.warning, + style = TangemTheme.typography.subtitle1, + modifier = modifier, + ) + } +} + +@Preview +@Composable +private fun Preview_ChooseDerivationScreen_Light() { + TangemTheme(isDark = false) { + CustomTokensScreen(state = AddCustomTokenPreviewData.state) + } +} + +@Preview +@Composable +private fun Preview_ChooseDerivationScreen_Dark() { + TangemTheme(isDark = true) { + CustomTokensScreen(state = AddCustomTokenPreviewData.state) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/TokenTextField.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/TokenTextField.kt new file mode 100644 index 0000000000..a615d22738 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/TokenTextField.kt @@ -0,0 +1,54 @@ +package com.tangem.managetokens.presentation.customtokens.ui + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +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.ui.Modifier +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.core.ui.res.TangemTheme +import com.tangem.managetokens.presentation.customtokens.state.TextFieldState + +@Composable +internal fun TokenTextField( + state: TextFieldState.Editable, + placeholder: String, + keyboardType: KeyboardType = KeyboardType.Text, +) { + BasicTextField( + value = state.value, + onValueChange = state.onValueChange, + keyboardOptions = KeyboardOptions(keyboardType = keyboardType, imeAction = ImeAction.Default), + singleLine = true, + maxLines = 1, + textStyle = TangemTheme.typography.subtitle1.copy( + fontWeight = FontWeight.Normal, + color = if (state.isEnabled) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.disabled, + ), + cursorBrush = SolidColor(TangemTheme.colors.icon.primary1), + modifier = Modifier + .fillMaxWidth(), + decorationBox = { innerTextField -> + Row(modifier = Modifier.fillMaxWidth()) { + if (state.value.isEmpty()) { + Text( + text = placeholder, + color = if (state.isEnabled) { + TangemTheme.colors.text.tertiary + } else { + TangemTheme.colors.text.disabled + }, + style = TangemTheme.typography.body2, + ) + } + } + innerTextField() + }, + enabled = state.isEnabled, + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/DefaultManageTokensRouter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/DefaultManageTokensRouter.kt index feeab80123..dca7527d4b 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/DefaultManageTokensRouter.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/DefaultManageTokensRouter.kt @@ -14,6 +14,11 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.core.navigation.ReduxNavController import com.tangem.managetokens.ManageTokensFragment +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.customtokens.ui.ChooseDerivationScreen +import com.tangem.managetokens.presentation.customtokens.ui.ChooseNetworkCustomScreen +import com.tangem.managetokens.presentation.customtokens.ui.CustomTokensChooseWalletScreen +import com.tangem.managetokens.presentation.customtokens.ui.CustomTokensScreen import com.tangem.managetokens.presentation.customtokens.viewmodels.CustomTokensViewModel import com.tangem.managetokens.presentation.managetokens.ui.ManageTokensScreen import com.tangem.managetokens.presentation.managetokens.viewmodels.ManageTokensViewModel @@ -50,8 +55,7 @@ internal class DefaultManageTokensRouter( val viewModel = hiltViewModel(viewModelStoreOwner).apply { router = this@DefaultManageTokensRouter } - // CustomTokensScreen(state = viewModel.uiState) - // TODO: enable in [REDACTED_JIRA] + CustomTokensScreen(state = viewModel.uiState) } composable( ManageTokensRoute.CustomTokens.ChooseNetwork.route, @@ -59,10 +63,9 @@ internal class DefaultManageTokensRouter( val viewModel = hiltViewModel(viewModelStoreOwner).apply { router = this@DefaultManageTokensRouter } - // ChooseNetworkCustomScreen( - // state = viewModel.uiState.chooseNetworkState, - // ) - // TODO: enable in [REDACTED_JIRA] + ChooseNetworkCustomScreen( + state = viewModel.uiState.chooseNetworkState, + ) } composable( ManageTokensRoute.CustomTokens.ChooseDerivation.route, @@ -70,10 +73,9 @@ internal class DefaultManageTokensRouter( val viewModel = hiltViewModel(viewModelStoreOwner).apply { router = this@DefaultManageTokensRouter } - // ChooseDerivationScreen( - // state = requireNotNull(viewModel.uiState.chooseDerivationState), - // ) - // TODO: enable in [REDACTED_JIRA] + ChooseDerivationScreen( + state = requireNotNull(viewModel.uiState.chooseDerivationState), + ) } composable( ManageTokensRoute.CustomTokens.ChooseWallet.route, @@ -81,10 +83,9 @@ internal class DefaultManageTokensRouter( val viewModel = hiltViewModel(viewModelStoreOwner).apply { router = this@DefaultManageTokensRouter } - // CustomTokensChooseWalletScreen( - // state = viewModel.uiState.chooseWalletState as ChooseWalletState.Choose, - // ) - // TODO: enable in [REDACTED_JIRA] + CustomTokensChooseWalletScreen( + state = viewModel.uiState.chooseWalletState as ChooseWalletState.Choose, + ) } } }