Updated on 2026-08-14
This commit is contained in:
parent
4690bf2e6c
commit
3718b9e42f
12 changed files with 876 additions and 42 deletions
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.features.customtoken.impl.presentation.models
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
|
|
||||||
|
|
@ -243,20 +244,29 @@ internal sealed interface AddCustomTokenSelectorField {
|
||||||
/** Title */
|
/** Title */
|
||||||
val title: TextReference
|
val title: TextReference
|
||||||
|
|
||||||
|
/** Blockchain */
|
||||||
|
val blockchain: Blockchain
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Menu item with title
|
* Menu item with title
|
||||||
*
|
*
|
||||||
* @property title title text
|
* @property title title text
|
||||||
|
* @property blockchain blockchain
|
||||||
*/
|
*/
|
||||||
data class Title(override val title: TextReference) : SelectorItem
|
data class Title(override val title: TextReference, override val blockchain: Blockchain) : SelectorItem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Menu item with title ans subtitle
|
* Menu item with title ans subtitle
|
||||||
*
|
*
|
||||||
* @property title title text
|
* @property title title text
|
||||||
* @property subtitle subtitle text
|
* @property subtitle subtitle text
|
||||||
|
* @property blockchain blockchain
|
||||||
*/
|
*/
|
||||||
data class TitleWithSubtitle(override val title: TextReference, val subtitle: TextReference) : SelectorItem
|
data class TitleWithSubtitle(
|
||||||
|
override val title: TextReference,
|
||||||
|
val subtitle: TextReference,
|
||||||
|
override val blockchain: Blockchain,
|
||||||
|
) : SelectorItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.FabPosition
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.key
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenToolbar
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarning
|
||||||
|
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom token content
|
||||||
|
*
|
||||||
|
* @param state screen state
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content) {
|
||||||
|
BackHandler(onBack = state.onBackButtonClick)
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
AddCustomTokenToolbar(
|
||||||
|
title = state.toolbar.title,
|
||||||
|
onBackButtonClick = state.toolbar.onBackButtonClick,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
floatingActionButton = { AddCustomTokenFloatingButton(model = state.floatingButton) },
|
||||||
|
floatingActionButtonPosition = FabPosition.Center,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues = it)
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
AddCustomTokenForm(model = state.form)
|
||||||
|
|
||||||
|
state.warnings.forEach { description ->
|
||||||
|
key(description) {
|
||||||
|
AddCustomTokenWarning(description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showSystemUi = true)
|
||||||
|
@Composable
|
||||||
|
private fun Preview_AddCustomTokenContent() {
|
||||||
|
TangemTheme {
|
||||||
|
AddCustomTokenContent(
|
||||||
|
state = AddCustomTokenStateHolder.Content(
|
||||||
|
onBackButtonClick = {},
|
||||||
|
toolbar = AddCustomTokensToolbar(
|
||||||
|
title = TextReference.Res(R.string.add_custom_token_title),
|
||||||
|
onBackButtonClick = {},
|
||||||
|
),
|
||||||
|
form = com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm(
|
||||||
|
contractAddressInputField = AddCustomTokenInputField.ContactAddress(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isError = false,
|
||||||
|
isLoading = false,
|
||||||
|
),
|
||||||
|
networkSelectorField = AddCustomTokenSelectorField.Network(
|
||||||
|
selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
|
||||||
|
title = TextReference.Str("Avalanche"),
|
||||||
|
blockchain = Blockchain.Avalanche,
|
||||||
|
),
|
||||||
|
items = listOf(),
|
||||||
|
onMenuItemClick = {},
|
||||||
|
),
|
||||||
|
tokenNameInputField = AddCustomTokenInputField.TokenName(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isEnabled = false,
|
||||||
|
isError = false,
|
||||||
|
),
|
||||||
|
tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isEnabled = false,
|
||||||
|
isError = false,
|
||||||
|
),
|
||||||
|
decimalsInputField = AddCustomTokenInputField.Decimals(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isEnabled = false,
|
||||||
|
isError = false,
|
||||||
|
),
|
||||||
|
derivationPathSelectorField = null,
|
||||||
|
),
|
||||||
|
warnings = listOf(),
|
||||||
|
floatingButton = AddCustomTokenFloatingButton(
|
||||||
|
isEnabled = false,
|
||||||
|
onClick = {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom token screen
|
||||||
|
*
|
||||||
|
* @param stateHolder state holder
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@Suppress("UnusedPrivateMember")
|
||||||
|
@Composable
|
||||||
|
internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder) {
|
||||||
|
when (stateHolder) {
|
||||||
|
is AddCustomTokenStateHolder.Content -> AddCustomTokenContent(state = stateHolder)
|
||||||
|
is AddCustomTokenStateHolder.TestContent -> AddCustomTokenTestContent(state = stateHolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,311 @@
|
||||||
|
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.BottomSheetScaffold
|
||||||
|
import androidx.compose.material.BottomSheetScaffoldState
|
||||||
|
import androidx.compose.material.BottomSheetState
|
||||||
|
import androidx.compose.material.BottomSheetValue
|
||||||
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.FabPosition
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.rememberBottomSheetScaffoldState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.key
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.core.ui.components.PrimaryButton
|
||||||
|
import com.tangem.core.ui.components.SpacerH8
|
||||||
|
import com.tangem.core.ui.components.atoms.Hand
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TestTokenItem
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenToolbar
|
||||||
|
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom token content for testing
|
||||||
|
*
|
||||||
|
* @param state screen state
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestContent) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
|
||||||
|
bottomSheetState = BottomSheetState(initialValue = BottomSheetValue.Collapsed),
|
||||||
|
)
|
||||||
|
|
||||||
|
BackHandler(
|
||||||
|
onBack = {
|
||||||
|
onBackButtonClicked(
|
||||||
|
coroutineScope = coroutineScope,
|
||||||
|
bottomSheetScaffoldState = bottomSheetScaffoldState,
|
||||||
|
defaultAction = state.onBackButtonClick,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
BottomSheetScaffold(
|
||||||
|
sheetContent = {
|
||||||
|
SheetContent(
|
||||||
|
coroutineScope = coroutineScope,
|
||||||
|
bottomSheetScaffoldState = bottomSheetScaffoldState,
|
||||||
|
model = state.bottomSheet,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
scaffoldState = bottomSheetScaffoldState,
|
||||||
|
topBar = {
|
||||||
|
AddCustomTokenToolbar(
|
||||||
|
title = state.toolbar.title,
|
||||||
|
onBackButtonClick = {
|
||||||
|
onBackButtonClicked(
|
||||||
|
coroutineScope,
|
||||||
|
bottomSheetScaffoldState,
|
||||||
|
defaultAction = state.toolbar.onBackButtonClick,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
floatingActionButton = { AddCustomTokenFloatingButton(model = state.floatingButton) },
|
||||||
|
floatingActionButtonPosition = FabPosition.Center,
|
||||||
|
sheetBackgroundColor = TangemTheme.colors.background.secondary,
|
||||||
|
sheetPeekHeight = TangemTheme.dimens.size0,
|
||||||
|
backgroundColor = TangemTheme.colors.background.secondary,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(it),
|
||||||
|
) {
|
||||||
|
TestBlock(
|
||||||
|
model = state.testBlock,
|
||||||
|
coroutineScope,
|
||||||
|
bottomSheetScaffoldState,
|
||||||
|
)
|
||||||
|
|
||||||
|
AddCustomTokenForm(model = state.form)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
private fun onBackButtonClicked(
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
bottomSheetScaffoldState: BottomSheetScaffoldState,
|
||||||
|
defaultAction: () -> Unit,
|
||||||
|
) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
if (bottomSheetScaffoldState.bottomSheetState.isExpanded) {
|
||||||
|
bottomSheetScaffoldState.bottomSheetState.collapse()
|
||||||
|
} else {
|
||||||
|
defaultAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun SheetContent(
|
||||||
|
model: AddCustomTokenChooseTokenBottomSheet,
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
bottomSheetScaffoldState: BottomSheetScaffoldState,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(LocalConfiguration.current.screenHeightDp.dp - TangemTheme.dimens.spacing16),
|
||||||
|
) {
|
||||||
|
Hand()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
model.categoriesBlocks.forEachIndexed { index, categoryBlock ->
|
||||||
|
key(categoryBlock) {
|
||||||
|
Column {
|
||||||
|
TokensList(
|
||||||
|
title = categoryBlock.name,
|
||||||
|
tokens = categoryBlock.items,
|
||||||
|
onTestTokenClick = { address ->
|
||||||
|
model.onTestTokenClick(address)
|
||||||
|
coroutineScope.launch { bottomSheetScaffoldState.bottomSheetState.collapse() }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (model.categoriesBlocks.lastIndex != index) {
|
||||||
|
Divider()
|
||||||
|
SpacerH8()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TokensList(title: String, tokens: List<TestTokenItem>, onTestTokenClick: (String) -> Unit) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = TangemTheme.dimens.spacing24,
|
||||||
|
vertical = TangemTheme.dimens.spacing8,
|
||||||
|
),
|
||||||
|
maxLines = 1,
|
||||||
|
style = TangemTheme.typography.h3,
|
||||||
|
)
|
||||||
|
|
||||||
|
tokens.forEach { token ->
|
||||||
|
key(token) {
|
||||||
|
PrimaryButton(
|
||||||
|
text = token.name,
|
||||||
|
onClick = { onTestTokenClick(token.address) },
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||||
|
.padding(bottom = TangemTheme.dimens.spacing8)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun TestBlock(
|
||||||
|
model: AddCustomTokenTestBlock,
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
bottomSheetScaffoldState: BottomSheetScaffoldState,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||||
|
.padding(top = TangemTheme.dimens.spacing16),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
|
||||||
|
) {
|
||||||
|
val softwareKeyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
PrimaryButton(
|
||||||
|
text = model.chooseTokenButtonText,
|
||||||
|
onClick = {
|
||||||
|
softwareKeyboardController?.hide()
|
||||||
|
coroutineScope.launch { bottomSheetScaffoldState.bottomSheetState.expand() }
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
PrimaryButton(
|
||||||
|
text = model.clearButtonText,
|
||||||
|
onClick = model.onClearAddressButtonClick,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
PrimaryButton(
|
||||||
|
text = model.resetButtonText,
|
||||||
|
onClick = model.onResetButtonClick,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showSystemUi = true)
|
||||||
|
@Composable
|
||||||
|
private fun Preview_AddCustomTokenTestContent() {
|
||||||
|
TangemTheme {
|
||||||
|
AddCustomTokenTestContent(
|
||||||
|
state = AddCustomTokenStateHolder.TestContent(
|
||||||
|
onBackButtonClick = {},
|
||||||
|
toolbar = AddCustomTokensToolbar(
|
||||||
|
title = TextReference.Res(R.string.add_custom_token_title),
|
||||||
|
onBackButtonClick = {},
|
||||||
|
),
|
||||||
|
form = com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm(
|
||||||
|
contractAddressInputField = AddCustomTokenInputField.ContactAddress(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isError = false,
|
||||||
|
isLoading = false,
|
||||||
|
),
|
||||||
|
networkSelectorField = AddCustomTokenSelectorField.Network(
|
||||||
|
selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
|
||||||
|
title = TextReference.Str(value = "Avalanche"),
|
||||||
|
blockchain = Blockchain.Avalanche,
|
||||||
|
),
|
||||||
|
items = listOf(),
|
||||||
|
onMenuItemClick = {},
|
||||||
|
),
|
||||||
|
tokenNameInputField = AddCustomTokenInputField.TokenName(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isEnabled = false,
|
||||||
|
isError = false,
|
||||||
|
),
|
||||||
|
tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isEnabled = false,
|
||||||
|
isError = false,
|
||||||
|
),
|
||||||
|
decimalsInputField = AddCustomTokenInputField.Decimals(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isEnabled = false,
|
||||||
|
isError = false,
|
||||||
|
),
|
||||||
|
derivationPathSelectorField = null,
|
||||||
|
),
|
||||||
|
warnings = listOf(),
|
||||||
|
floatingButton = AddCustomTokenFloatingButton(
|
||||||
|
isEnabled = false,
|
||||||
|
onClick = {},
|
||||||
|
),
|
||||||
|
testBlock = AddCustomTokenTestBlock(
|
||||||
|
chooseTokenButtonText = "Choose token",
|
||||||
|
clearButtonText = "Clear address",
|
||||||
|
resetButtonText = "Reset",
|
||||||
|
onClearAddressButtonClick = {},
|
||||||
|
onResetButtonClick = {},
|
||||||
|
),
|
||||||
|
bottomSheet = AddCustomTokenChooseTokenBottomSheet(
|
||||||
|
categoriesBlocks = listOf(),
|
||||||
|
onTestTokenClick = {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.components.PrimaryButtonIconLeft
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom token floating button. Attached above the keyboard.
|
||||||
|
*
|
||||||
|
* @param model button model
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton) {
|
||||||
|
PrimaryButtonIconLeft(
|
||||||
|
modifier = Modifier
|
||||||
|
.imePadding()
|
||||||
|
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
text = stringResource(id = R.string.common_add),
|
||||||
|
icon = painterResource(id = R.drawable.ic_plus_24),
|
||||||
|
enabled = model.isEnabled,
|
||||||
|
onClick = model.onClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun Preview_AddCustomTokenFloatingButton_Enabled() {
|
||||||
|
TangemTheme {
|
||||||
|
AddCustomTokenFloatingButton(model = AddCustomTokenFloatingButton(isEnabled = true, onClick = {}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun Preview_AddCustomTokenFloatingButton_Disabled() {
|
||||||
|
TangemTheme {
|
||||||
|
AddCustomTokenFloatingButton(model = AddCustomTokenFloatingButton(isEnabled = false, onClick = {}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material.DropdownMenuItem
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.ExposedDropdownMenuBox
|
||||||
|
import androidx.compose.material.ExposedDropdownMenuDefaults
|
||||||
|
import androidx.compose.material.LinearProgressIndicator
|
||||||
|
import androidx.compose.material.OutlinedTextField
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.tap.common.compose.TangemTextFieldsDefault
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
|
||||||
|
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
|
||||||
|
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||||
|
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom token form
|
||||||
|
*
|
||||||
|
* @param model component model
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun AddCustomTokenForm(model: AddCustomTokenForm) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(TangemTheme.dimens.spacing16),
|
||||||
|
shape = RoundedCornerShape(TangemTheme.dimens.radius8),
|
||||||
|
backgroundColor = TangemTheme.colors.background.primary,
|
||||||
|
elevation = TangemTheme.dimens.elevation4,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(TangemTheme.dimens.spacing16),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||||
|
) {
|
||||||
|
InputField(model = model.contractAddressInputField)
|
||||||
|
SelectorField(model = model.networkSelectorField)
|
||||||
|
InputField(model = model.tokenNameInputField)
|
||||||
|
InputField(model = model.tokenSymbolInputField)
|
||||||
|
InputField(model = model.decimalsInputField)
|
||||||
|
model.derivationPathSelectorField?.let { SelectorField(model = it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InputField(model: AddCustomTokenInputField) {
|
||||||
|
Box {
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
value = model.value,
|
||||||
|
onValueChange = model.onValueChange,
|
||||||
|
keyboardOptions = model.keyboardOptions,
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = model.label.resolveReference(),
|
||||||
|
style = TangemTheme.typography.caption,
|
||||||
|
color = TangemTextFieldsDefault.defaultTextFieldColors.labelColor(
|
||||||
|
enabled = model.isEnabled,
|
||||||
|
error = model.isError,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
).value,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = model.placeholder.resolveReference(),
|
||||||
|
style = TangemTheme.typography.body1,
|
||||||
|
color = TangemTextFieldsDefault.defaultTextFieldColors
|
||||||
|
.placeholderColor(enabled = model.isEnabled)
|
||||||
|
.value,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
enabled = model.isEnabled,
|
||||||
|
isError = model.isError,
|
||||||
|
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = model.isLoading,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(horizontal = TangemTheme.dimens.spacing6)
|
||||||
|
.padding(bottom = TangemTheme.dimens.spacing6),
|
||||||
|
) {
|
||||||
|
LinearProgressIndicator(color = TangemTheme.colors.icon.primary1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun SelectorField(model: AddCustomTokenSelectorField) {
|
||||||
|
var isExpanded by remember { mutableStateOf(value = false) }
|
||||||
|
|
||||||
|
ExposedDropdownMenuBox(
|
||||||
|
expanded = isExpanded,
|
||||||
|
onExpandedChange = { isExpanded = !isExpanded },
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = when (val item = model.selectedItem) {
|
||||||
|
is AddCustomTokenSelectorField.SelectorItem.Title -> item.title
|
||||||
|
is AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle -> item.subtitle
|
||||||
|
}.resolveReference(),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onValueChange = {},
|
||||||
|
readOnly = true,
|
||||||
|
enabled = model.isEnabled,
|
||||||
|
label = { Text(text = model.label.resolveReference()) },
|
||||||
|
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = isExpanded) },
|
||||||
|
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||||
|
)
|
||||||
|
|
||||||
|
ExposedDropdownMenu(
|
||||||
|
expanded = isExpanded && model.isEnabled,
|
||||||
|
onDismissRequest = { isExpanded = false },
|
||||||
|
) {
|
||||||
|
FocusRequester
|
||||||
|
model.items.forEachIndexed { index, item ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
model.onMenuItemClick(index)
|
||||||
|
isExpanded = false
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Text(text = item.title.resolveReference())
|
||||||
|
|
||||||
|
val subtitle = (item as? AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle)
|
||||||
|
?.subtitle?.resolveReference()
|
||||||
|
|
||||||
|
if (!subtitle.isNullOrBlank()) {
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
maxLines = 1,
|
||||||
|
style = TangemTheme.typography.caption,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun Preview_AddCustomTokenForm() {
|
||||||
|
TangemTheme {
|
||||||
|
AddCustomTokenForm(
|
||||||
|
AddCustomTokenForm(
|
||||||
|
contractAddressInputField = AddCustomTokenInputField.ContactAddress(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isError = false,
|
||||||
|
isLoading = false,
|
||||||
|
),
|
||||||
|
networkSelectorField = AddCustomTokenSelectorField.Network(
|
||||||
|
selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
|
||||||
|
title = TextReference.Str(value = "Avalanche"),
|
||||||
|
blockchain = Blockchain.Avalanche,
|
||||||
|
),
|
||||||
|
items = listOf(),
|
||||||
|
onMenuItemClick = {},
|
||||||
|
),
|
||||||
|
tokenNameInputField = AddCustomTokenInputField.TokenName(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isEnabled = false,
|
||||||
|
isError = false,
|
||||||
|
),
|
||||||
|
tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isEnabled = false,
|
||||||
|
isError = false,
|
||||||
|
),
|
||||||
|
decimalsInputField = AddCustomTokenInputField.Decimals(
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
isEnabled = false,
|
||||||
|
isError = false,
|
||||||
|
),
|
||||||
|
derivationPathSelectorField = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||||
|
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom token toolbar
|
||||||
|
*
|
||||||
|
* @param title title
|
||||||
|
* @param onBackButtonClick lambda be invoked when BackButton is been pressed
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun AddCustomTokenToolbar(title: TextReference, onBackButtonClick: () -> Unit) {
|
||||||
|
TopAppBar(backgroundColor = TangemTheme.colors.background.secondary) {
|
||||||
|
IconButton(onClick = onBackButtonClick) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_back_24),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = TangemTheme.colors.icon.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing26))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = title.resolveReference(),
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
maxLines = 1,
|
||||||
|
style = TangemTheme.typography.h3,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun Preview_AddCustomTokenToolbar() {
|
||||||
|
TangemTheme {
|
||||||
|
AddCustomTokenToolbar(title = TextReference.Res(R.string.add_custom_token_title), onBackButtonClick = {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.tangem.core.ui.res.TangemColorPalette
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||||
|
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom token warning component
|
||||||
|
* FIXME("Incorrect typography. Replace with typography from design system")
|
||||||
|
*
|
||||||
|
* @param description warning description
|
||||||
|
* @param modifier modifier
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun AddCustomTokenWarning(description: TextReference, modifier: Modifier = Modifier) {
|
||||||
|
Card(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = RoundedCornerShape(TangemTheme.dimens.radius4),
|
||||||
|
backgroundColor = TangemColorPalette.Tangerine,
|
||||||
|
contentColor = TangemColorPalette.White,
|
||||||
|
elevation = TangemTheme.dimens.elevation4,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.common_warning),
|
||||||
|
maxLines = 1,
|
||||||
|
style = TangemTheme.typography.body2.copy(fontWeight = FontWeight.Bold),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = description.resolveReference(),
|
||||||
|
fontSize = 13.sp,
|
||||||
|
lineHeight = 18.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ interface DataConverterVisitor<Data, Result> {
|
||||||
interface FieldDataConverter<Result> : DataConverterVisitor<FieldData, Result>
|
interface FieldDataConverter<Result> : DataConverterVisitor<FieldData, Result>
|
||||||
|
|
||||||
abstract class BaseFieldDataConverter<Result> : FieldDataConverter<Result> {
|
abstract class BaseFieldDataConverter<Result> : FieldDataConverter<Result> {
|
||||||
protected val collectIds: List<FieldId>
|
private val collectIds: List<FieldId>
|
||||||
get() = getIdToCollect()
|
get() = getIdToCollect()
|
||||||
|
|
||||||
protected val collectedData: MutableMap<FieldId, Any?> = mutableMapOf()
|
protected val collectedData: MutableMap<FieldId, Any?> = mutableMapOf()
|
||||||
|
|
@ -31,7 +31,7 @@ abstract class BaseFieldDataConverter<Result> : FieldDataConverter<Result> {
|
||||||
|
|
||||||
class FieldToJsonConverter(
|
class FieldToJsonConverter(
|
||||||
private val fieldsToConvert: List<FieldId> = listOf(),
|
private val fieldsToConvert: List<FieldId> = listOf(),
|
||||||
protected val jsonConverter: MoshiJsonConverter,
|
private val jsonConverter: MoshiJsonConverter,
|
||||||
) : BaseFieldDataConverter<String>() {
|
) : BaseFieldDataConverter<String>() {
|
||||||
|
|
||||||
override fun getConvertedData(): String = jsonConverter.toJson(collectedData, " ")
|
override fun getConvertedData(): String = jsonConverter.toJson(collectedData, " ")
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,14 @@ import timber.log.Timber
|
||||||
interface CustomTokenValidator<T> : Validator<T, AddCustomTokenError>
|
interface CustomTokenValidator<T> : Validator<T, AddCustomTokenError>
|
||||||
|
|
||||||
class StringIsEmptyValidator : CustomTokenValidator<String> {
|
class StringIsEmptyValidator : CustomTokenValidator<String> {
|
||||||
override fun validate(data: String?): AddCustomTokenError? = when {
|
override fun validate(data: String?): AddCustomTokenError? {
|
||||||
data == null || data.isEmpty() -> null
|
return if (data.isNullOrEmpty()) null else AddCustomTokenError.FieldIsNotEmpty
|
||||||
else -> AddCustomTokenError.FieldIsNotEmpty
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StringIsNotEmptyValidator : CustomTokenValidator<String> {
|
class StringIsNotEmptyValidator : CustomTokenValidator<String> {
|
||||||
override fun validate(data: String?): AddCustomTokenError? = when {
|
override fun validate(data: String?): AddCustomTokenError? {
|
||||||
data == null || data.isEmpty() -> AddCustomTokenError.FieldIsEmpty
|
return if (data.isNullOrEmpty()) AddCustomTokenError.FieldIsEmpty else null
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,12 +44,10 @@ class TokenContractAddressValidator : CustomTokenValidator<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun validate(data: String?): AddCustomTokenError? {
|
override fun validate(data: String?): AddCustomTokenError? {
|
||||||
if (data == null || data.isEmpty()) return AddCustomTokenError.FieldIsEmpty
|
return when {
|
||||||
|
data.isNullOrEmpty() -> AddCustomTokenError.FieldIsEmpty
|
||||||
return if (getAddressService().validate(data)) {
|
getAddressService().validate(data) -> null
|
||||||
null
|
else -> AddCustomTokenError.InvalidContractAddress
|
||||||
} else {
|
|
||||||
AddCustomTokenError.InvalidContractAddress
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,9 +70,11 @@ class TokenContractAddressValidator : CustomTokenValidator<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TokenNetworkValidator : CustomTokenValidator<Blockchain> {
|
class TokenNetworkValidator : CustomTokenValidator<Blockchain> {
|
||||||
override fun validate(data: Blockchain?): AddCustomTokenError? = when (data) {
|
override fun validate(data: Blockchain?): AddCustomTokenError? {
|
||||||
null, Blockchain.Unknown -> AddCustomTokenError.NetworkIsNotSelected
|
return when (data) {
|
||||||
else -> null
|
null, Blockchain.Unknown -> AddCustomTokenError.NetworkIsNotSelected
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,14 +86,14 @@ class TokenSymbolValidator : CustomTokenValidator<String> {
|
||||||
override fun validate(data: String?): AddCustomTokenError? = StringIsNotEmptyValidator().validate(data)
|
override fun validate(data: String?): AddCustomTokenError? = StringIsNotEmptyValidator().validate(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
|
||||||
class TokenDecimalsValidator : CustomTokenValidator<String> {
|
class TokenDecimalsValidator : CustomTokenValidator<String> {
|
||||||
override fun validate(data: String?): AddCustomTokenError? {
|
override fun validate(data: String?): AddCustomTokenError? {
|
||||||
val decimal = data?.toIntOrNull() ?: return AddCustomTokenError.FieldIsEmpty
|
val decimal = data?.toIntOrNull() ?: return AddCustomTokenError.FieldIsEmpty
|
||||||
|
|
||||||
return when {
|
return if (decimal > INVALID_DECIMALS_COUNT) AddCustomTokenError.InvalidDecimalsCount else null
|
||||||
decimal > 30 -> AddCustomTokenError.InvalidDecimalsCount
|
}
|
||||||
else -> null
|
|
||||||
}
|
private companion object {
|
||||||
|
const val INVALID_DECIMALS_COUNT = 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +125,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -532,21 +532,24 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CustomTokenFieldId.validateValue(value: Any): AddCustomTokenError? {
|
private fun CustomTokenFieldId.validateValue(value: Any): AddCustomTokenError? {
|
||||||
val state = hubState
|
|
||||||
val contractAddressValidator: TokenContractAddressValidator = state.getValidator(ContractAddress)
|
|
||||||
val nameValidator: TokenNameValidator = state.getValidator(Name)
|
|
||||||
val symbolValidator: TokenSymbolValidator = state.getValidator(Symbol)
|
|
||||||
val decimalsValidator: TokenDecimalsValidator = state.getValidator(Decimals)
|
|
||||||
val networkValidator: TokenNetworkValidator = state.getValidator(Network)
|
|
||||||
return when (this) {
|
return when (this) {
|
||||||
ContractAddress -> {
|
ContractAddress -> {
|
||||||
|
val contractAddressValidator: TokenContractAddressValidator = hubState.getValidator(ContractAddress)
|
||||||
contractAddressValidator.nextValidationFor(Network.getFieldValue())
|
contractAddressValidator.nextValidationFor(Network.getFieldValue())
|
||||||
contractAddressValidator.validate(value as String)
|
contractAddressValidator.validate(value as String)
|
||||||
}
|
}
|
||||||
Network, DerivationPath -> networkValidator.validate(value as Blockchain)
|
Network, DerivationPath -> {
|
||||||
Name -> nameValidator.validate(value as String)
|
hubState.getValidator<TokenNetworkValidator>(Network).validate(value as Blockchain)
|
||||||
Symbol -> symbolValidator.validate(value as String)
|
}
|
||||||
Decimals -> decimalsValidator.validate(value as String)
|
Name -> {
|
||||||
|
hubState.getValidator<TokenNameValidator>(Name).validate(value as String)
|
||||||
|
}
|
||||||
|
Symbol -> {
|
||||||
|
hubState.getValidator<TokenSymbolValidator>(Symbol).validate(value as String)
|
||||||
|
}
|
||||||
|
Decimals -> {
|
||||||
|
hubState.getValidator<TokenDecimalsValidator>(Decimals).validate(value as String)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,10 +159,6 @@ data class AddCustomTokenState(
|
||||||
.getConvertedData()
|
.getConvertedData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNetworks(card: CardDTO, type: CustomTokenType): List<Blockchain> {
|
|
||||||
return getNetworksList(card, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue