Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-22 17:06:53 +03:00
parent 81777ad79e
commit a223c140cf
17 changed files with 890 additions and 24 deletions

View file

@ -265,7 +265,6 @@
<string name="manage_tokens_title">Рыночная капитализация</string>
<string name="manage_tokens_unavailable_description">Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление.</string>
<string name="manage_tokens_unavailable_vote">Голосовать</string>
<string name="manage_tokens_wallet_does_not_supported_blockchain">Кошелек не поддерживает выбранную монету</string>
<string name="manage_tokens_wallet_selector_title">Выберите кошелек</string>
<string name="manage_tokens_wallet_support_only_one_network_title">Кошелёк не поддерживает более одной сети</string>
<string name="onboarding_access_code_feature_1_description">Вам необходимо установить единый код доступа для защиты всех ваших карт</string>

View file

@ -286,7 +286,6 @@
<string name="manage_tokens_title">Coin market cap</string>
<string name="manage_tokens_unavailable_description">The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it.</string>
<string name="manage_tokens_unavailable_vote">Upvote</string>
<string name="manage_tokens_wallet_does_not_supported_blockchain">Wallet Incompatible with selected Coin</string>
<string name="manage_tokens_wallet_selector_title">Choose wallet</string>
<string name="manage_tokens_wallet_support_only_one_network_title">The wallet doesn\'t support more than one network</string>
<string name="onboarding_access_code_feature_1_description">You have to set up a single access code to protect all your wallets</string>

View file

@ -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,
)
}

View file

@ -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<String>,

View file

@ -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,
)
}

View file

@ -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,
),
)
}
}
}
}

View file

@ -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),

View file

@ -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 = {}),
)
}

View file

@ -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<Derivation> = 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,
)
}

View file

@ -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,
)
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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,
)
}

View file

@ -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(),
)
}

View file

@ -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<AlertState?>(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)
}
}

View file

@ -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,
)
}

View file

@ -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<CustomTokensViewModel>(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<CustomTokensViewModel>(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<CustomTokensViewModel>(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<CustomTokensViewModel>(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,
)
}
}
}