Updated on 2026-08-14
This commit is contained in:
parent
35d74021ac
commit
0a25bbedbb
17 changed files with 224 additions and 173 deletions
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.form.Field
|
||||
|
||||
@Composable
|
||||
fun BlockchainSpinner(
|
||||
@StringRes title: Int,
|
||||
itemList: List<Blockchain>,
|
||||
selectedItem: Field.Data<Blockchain>,
|
||||
isEnabled: Boolean = true,
|
||||
textFieldConverter: (Blockchain) -> String,
|
||||
dropdownItemView: @Composable ((Blockchain) -> Unit)? = null,
|
||||
onItemSelected: (Blockchain) -> Unit,
|
||||
) {
|
||||
OutlinedSpinner(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = stringResource(id = title),
|
||||
itemList = itemList,
|
||||
selectedItem = selectedItem,
|
||||
textFieldConverter = textFieldConverter,
|
||||
dropdownItemView = dropdownItemView,
|
||||
isEnabled = isEnabled,
|
||||
onItemSelected = onItemSelected
|
||||
)
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
|
|
@ -22,6 +22,7 @@ import com.tangem.domain.DomainStateDialog
|
|||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.domain.redux.global.DomainGlobalAction
|
||||
import com.tangem.domain.redux.global.DomainGlobalState
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.SelectTokenNetworkDialog
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Composable
|
||||
|
|
@ -56,15 +57,7 @@ fun ShowTheDialog(dialogState: MutableState<DomainStateDialog?>) {
|
|||
val onDismissRequest = { domainStore.dispatch(DomainGlobalAction.ShowDialog(null)) }
|
||||
|
||||
when (val dialog = dialogState.value) {
|
||||
is DomainDialog.SelectTokenDialog -> {
|
||||
SimpleDialog(
|
||||
title = "Select a token",
|
||||
items = dialog.items,
|
||||
itemNameConverter = dialog.itemNameConverter,
|
||||
onSelect = dialog.onSelect,
|
||||
onDismissRequest = onDismissRequest
|
||||
)
|
||||
}
|
||||
is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,9 +68,9 @@ fun ShowTheDialog(dialogState: MutableState<DomainStateDialog?>) {
|
|||
fun <T> SimpleDialog(
|
||||
title: String,
|
||||
items: List<T>,
|
||||
itemNameConverter: (T) -> String,
|
||||
onSelect: (T) -> Unit,
|
||||
onDismissRequest: () -> Unit
|
||||
onDismissRequest: () -> Unit,
|
||||
itemContent: @Composable (T) -> Unit,
|
||||
) {
|
||||
Dialog(
|
||||
properties = DialogProperties(false, false),
|
||||
|
|
@ -85,7 +78,7 @@ fun <T> SimpleDialog(
|
|||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
shape = MaterialTheme.shapes.medium
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(22.dp)
|
||||
|
|
@ -112,11 +105,7 @@ fun <T> SimpleDialog(
|
|||
onDismissRequest()
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = itemNameConverter(item),
|
||||
)
|
||||
}
|
||||
) { itemContent(item) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
|
|
@ -14,17 +16,16 @@ import com.tangem.tap.common.extensions.ValueCallback
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
private class OutlinedSpinner
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun <T> OutlinedSpinner(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
label: String,
|
||||
itemList: List<T>,
|
||||
selectedItem: Field.Data<T>,
|
||||
onItemSelected: ValueCallback<T>,
|
||||
itemNameConverter: (T) -> String = { it.toString() },
|
||||
textFieldConverter: (T) -> String = { it.toString() },
|
||||
dropdownItemView: @Composable ((T) -> Unit)? = null,
|
||||
isEnabled: Boolean = true,
|
||||
onClose: VoidCallback = {}
|
||||
) {
|
||||
|
|
@ -48,24 +49,27 @@ fun <T> OutlinedSpinner(
|
|||
expanded = rIsExpanded.value,
|
||||
onExpandedChange = { rIsExpanded.value = !rIsExpanded.value },
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = modifier,
|
||||
readOnly = true,
|
||||
enabled = isEnabled,
|
||||
value = itemNameConverter(rSelectedItem.value),
|
||||
onValueChange = {},
|
||||
label = { Text(title) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = rIsExpanded.value) },
|
||||
)
|
||||
ProvideTextStyle(value = TextStyle(color = Color.Blue)) {
|
||||
OutlinedTextField(
|
||||
modifier = modifier,
|
||||
readOnly = true,
|
||||
enabled = isEnabled,
|
||||
value = textFieldConverter(rSelectedItem.value),
|
||||
onValueChange = {},
|
||||
label = { Text(label) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = rIsExpanded.value) },
|
||||
)
|
||||
}
|
||||
ExposedDropdownMenu(
|
||||
expanded = rIsExpanded.value,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
itemList.forEach { item ->
|
||||
DropdownMenuItem(
|
||||
onClick = { onItemSelectedInternal(item) }
|
||||
) {
|
||||
Text(itemNameConverter(item))
|
||||
DropdownMenuItem(onClick = { onItemSelectedInternal(item) }) {
|
||||
when (dropdownItemView) {
|
||||
null -> Text(textFieldConverter(item))
|
||||
else -> dropdownItemView(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +81,7 @@ fun <T> OutlinedSpinner(
|
|||
fun TestSpinnerPreview() {
|
||||
Scaffold() {
|
||||
OutlinedSpinner(
|
||||
title = "Blockchain name",
|
||||
label = "Blockchain name",
|
||||
itemList = listOf(Blockchain.values()),
|
||||
selectedItem = Field.Data(Blockchain.Avalanche),
|
||||
onItemSelected = {},
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ import com.tangem.tap.common.compose.extensions.stringResourceDefault
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
private class OutlinedTextFieldWidget
|
||||
|
||||
@Composable
|
||||
fun OutlinedTextFieldWidget(
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -90,7 +88,7 @@ private fun OutlinedProgressTextField(
|
|||
val rTextDebouncer = valueDebouncerAsState(debounce, onTextChanged)
|
||||
val rText = remember { mutableStateOf(textFieldData.value) }
|
||||
|
||||
fun updateFieldValueAndEmmit(value: String){
|
||||
fun updateFieldValueAndEmmit(value: String) {
|
||||
rText.value = value
|
||||
rTextDebouncer.emmit(value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,28 +40,28 @@ fun SpacerH24(modifier: Modifier = Modifier) {
|
|||
|
||||
// ***************************** Vertical
|
||||
@Composable
|
||||
fun SpacerV(width: Dp, modifier: Modifier = Modifier) {
|
||||
fun SpacerW(width: Dp, modifier: Modifier = Modifier) {
|
||||
Spacer(modifier = modifier.width(width))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SpacerV4(modifier: Modifier = Modifier) {
|
||||
SpacerV(4.dp, modifier)
|
||||
fun SpacerW4(modifier: Modifier = Modifier) {
|
||||
SpacerW(4.dp, modifier)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SpacerV8(modifier: Modifier = Modifier) {
|
||||
SpacerV(8.dp, modifier)
|
||||
fun SpacerW8(modifier: Modifier = Modifier) {
|
||||
SpacerW(8.dp, modifier)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SpacerV16(modifier: Modifier = Modifier) {
|
||||
SpacerV(16.dp, modifier)
|
||||
fun SpacerW16(modifier: Modifier = Modifier) {
|
||||
SpacerW(16.dp, modifier)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SpacerV24(modifier: Modifier = Modifier) {
|
||||
SpacerV(24.dp, modifier)
|
||||
fun SpacerW24(modifier: Modifier = Modifier) {
|
||||
SpacerW(24.dp, modifier)
|
||||
}
|
||||
|
||||
// ***************************** Size
|
||||
|
|
|
|||
27
app/src/main/java/com/tangem/tap/common/compose/Undefined.kt
Normal file
27
app/src/main/java/com/tangem/tap/common/compose/Undefined.kt
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
* Compose views are not typically used as a main or base view.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun TitleSubtitle(
|
||||
title: String,
|
||||
subtitle: String
|
||||
) {
|
||||
Column() {
|
||||
Text(text = title)
|
||||
Text(
|
||||
text = subtitle,
|
||||
fontSize = 12.sp,
|
||||
color = Color.Gray
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.tap.common.compose.SpacerV4
|
||||
import com.tangem.tap.common.compose.SpacerW4
|
||||
|
||||
@Composable
|
||||
fun StoriesProgressBar(
|
||||
|
|
@ -68,7 +68,7 @@ fun StoriesProgressBar(
|
|||
) {}
|
||||
}
|
||||
if (index != steps) {
|
||||
SpacerV4()
|
||||
SpacerW4()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,10 +35,15 @@ class CustomTokenWarningConverter(
|
|||
override fun convertError(error: DomainError): String {
|
||||
val customTokenWarning = (error as? AddCustomTokenWarning) ?: throw UnsupportedOperationException()
|
||||
|
||||
val resId = when (customTokenWarning) {
|
||||
val rawMessage = when (customTokenWarning) {
|
||||
AddCustomTokenWarning.PotentialScamToken -> R.string.custom_token_validation_error_not_found
|
||||
AddCustomTokenWarning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added
|
||||
AddCustomTokenWarning.Network.CheckAddressRequestError -> "CheckAddressRequestError"
|
||||
}
|
||||
return when (rawMessage) {
|
||||
is Int -> context.getString(rawMessage)
|
||||
is String -> rawMessage
|
||||
else -> "Unknown error: ${customTokenWarning::class.java.simpleName}"
|
||||
}
|
||||
return context.getString(resId)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,10 @@ package com.tangem.tap.features.tokens.addCustomToken.compose
|
|||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -29,10 +27,7 @@ import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
|||
import com.tangem.domain.features.addCustomToken.redux.ScreenState
|
||||
import com.tangem.domain.features.addCustomToken.redux.ViewStates
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.ComposeDialogManager
|
||||
import com.tangem.tap.common.compose.OutlinedTextFieldWidget
|
||||
import com.tangem.tap.common.compose.SpacerH8
|
||||
import com.tangem.tap.common.compose.keyboardObserverAsState
|
||||
import com.tangem.tap.common.compose.*
|
||||
import com.tangem.tap.features.tokens.addCustomToken.CustomTokenErrorConverter
|
||||
import com.tangem.tap.features.tokens.addCustomToken.CustomTokenWarningConverter
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -52,12 +47,12 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
|||
) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = 80.dp)
|
||||
contentPadding = PaddingValues(bottom = 90.dp)
|
||||
) {
|
||||
item {
|
||||
Surface(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
elevation = 4.dp,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -143,7 +138,7 @@ private fun TokenNameView(screenFieldData: ScreenFieldData) {
|
|||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) {
|
||||
domainStore.dispatch(OnTokenFieldChanged(screenFieldData.field.id, Field.Data(it)))
|
||||
domainStore.dispatch(OnTokenNameChanged(Field.Data(it)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -153,14 +148,14 @@ private fun TokenNetworkView(screenFieldData: ScreenFieldData) {
|
|||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_network_input_not_selected)
|
||||
val networkField = screenFieldData.field as TokenNetworkField
|
||||
val networkField = screenFieldData.field as TokenBlockchainField
|
||||
|
||||
TokenNetworkSpinner(
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_network_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
itemNameConverter = { AddCustomTokenState.convertBlockchainName(it, notSelected) },
|
||||
textFieldConverter = { AddCustomTokenState.convertBlockchainName(it, notSelected) },
|
||||
) { domainStore.dispatch(OnTokenNetworkChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -178,7 +173,7 @@ private fun TokenSymbolView(screenFieldData: ScreenFieldData) {
|
|||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) { domainStore.dispatch(OnTokenFieldChanged(screenFieldData.field.id, Field.Data(it))) }
|
||||
) { domainStore.dispatch(OnTokenSymbolChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
|
|
@ -207,12 +202,17 @@ private fun TokenDerivationPathView(screenFieldData: ScreenFieldData) {
|
|||
val notSelected = stringResource(id = R.string.custom_token_derivation_path_default)
|
||||
val networkField = screenFieldData.field as TokenDerivationPathField
|
||||
|
||||
TokenNetworkSpinner(
|
||||
title = R.string.custom_token_network_input_title,
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_derivation_path_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
itemNameConverter = { AddCustomTokenState.convertDerivationPathName(it, notSelected) },
|
||||
textFieldConverter = { AddCustomTokenState.convertBlockchainName(it, notSelected) },
|
||||
dropdownItemView = { blockchain ->
|
||||
val derivationPathLabel = AddCustomTokenState.convertDerivationPathLabel(blockchain, notSelected)
|
||||
val blockchainName = AddCustomTokenState.convertBlockchainName(blockchain, notSelected)
|
||||
TitleSubtitle(derivationPathLabel, blockchainName)
|
||||
}
|
||||
) { domainStore.dispatch(OnTokenDerivationPathChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -227,13 +227,13 @@ private fun Warnings(warnings: List<AddCustomTokenWarning>) {
|
|||
Column {
|
||||
warnings.forEachIndexed { index, item ->
|
||||
val modifier = when (index) {
|
||||
0 -> Modifier.padding(16.dp, 0.dp, 16.dp, 16.dp)
|
||||
warnings.lastIndex -> Modifier.padding(16.dp, 16.dp, 16.dp, 16.dp)
|
||||
else -> Modifier.padding(16.dp, 16.dp, 16.dp, 0.dp)
|
||||
0 -> Modifier.padding(16.dp, 16.dp, 16.dp, 0.dp)
|
||||
warnings.lastIndex -> Modifier.padding(16.dp, 8.dp, 16.dp, 16.dp)
|
||||
else -> Modifier.padding(16.dp, 8.dp, 16.dp, 0.dp)
|
||||
}
|
||||
Surface(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = colorResource(id = R.color.darkGray2),
|
||||
contentColor = colorResource(id = R.color.darkGray3)
|
||||
) {
|
||||
|
|
@ -248,6 +248,30 @@ private fun Warnings(warnings: List<AddCustomTokenWarning>) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddButton(
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean,
|
||||
textId: Int = R.string.common_add,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Button(
|
||||
textId = textId,
|
||||
isEnabled = isEnabled,
|
||||
modifier = modifier
|
||||
.height(52.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
leadingView = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = "Add",
|
||||
)
|
||||
},
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
|
||||
private data class ScreenFieldData(
|
||||
val field: DataField<*>,
|
||||
val error: AddCustomTokenError?,
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.tap.common.compose.Button
|
||||
import com.tangem.tap.common.compose.OutlinedSpinner
|
||||
import com.tangem.tap.common.extensions.ValueCallback
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TokenNetworkSpinner(
|
||||
title: Int,
|
||||
itemList: List<Blockchain>,
|
||||
selectedItem: Field.Data<Blockchain>,
|
||||
isEnabled: Boolean = true,
|
||||
itemNameConverter: (Blockchain) -> String,
|
||||
onItemSelected: ValueCallback<Blockchain>,
|
||||
) {
|
||||
|
||||
OutlinedSpinner(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
title = stringResource(id = title),
|
||||
itemList = itemList,
|
||||
selectedItem = selectedItem,
|
||||
itemNameConverter = itemNameConverter,
|
||||
isEnabled = isEnabled,
|
||||
onItemSelected = onItemSelected
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddButton(
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean,
|
||||
textId: Int = R.string.common_add,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Button(
|
||||
textId = textId,
|
||||
isEnabled = isEnabled,
|
||||
modifier = modifier
|
||||
.height(52.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
leadingView = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = "Add",
|
||||
)
|
||||
},
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.domain.DomainDialog
|
||||
import com.tangem.tap.common.compose.SimpleDialog
|
||||
import com.tangem.tap.common.compose.TitleSubtitle
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun SelectTokenNetworkDialog(dialog: DomainDialog.SelectTokenDialog, onDismissRequest: () -> Unit) {
|
||||
SimpleDialog(
|
||||
title = stringResource(id = R.string.custom_token_type_network),
|
||||
items = dialog.items,
|
||||
onSelect = dialog.onSelect,
|
||||
onDismissRequest = onDismissRequest
|
||||
) { contract -> TitleSubtitle(dialog.networkIdConverter(contract.networkId), contract.address) }
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ sealed class DomainDialog : DomainStateDialog {
|
|||
|
||||
data class SelectTokenDialog(
|
||||
val items: List<Coins.CheckAddressResponse.Token.Contract>,
|
||||
val itemNameConverter: (Coins.CheckAddressResponse.Token.Contract) -> String,
|
||||
val networkIdConverter: (String) -> String,
|
||||
val onSelect: (Coins.CheckAddressResponse.Token.Contract) -> Unit,
|
||||
val onClose: VoidCallback = {}
|
||||
) : DomainDialog()
|
||||
|
|
|
|||
|
|
@ -15,35 +15,36 @@ class AddCustomTokenManager(
|
|||
suspend fun checkAddress(
|
||||
contractAddress: String,
|
||||
networkId: String? = null
|
||||
): List<Coins.CheckAddressResponse.Token> {
|
||||
): Result<List<Coins.CheckAddressResponse.Token>> {
|
||||
val result = tangemTechService.coins.checkAddress(contractAddress, networkId)
|
||||
return when (result) {
|
||||
is Result.Success -> {
|
||||
val resultTokens = result.data.tokens
|
||||
val newTokensList = mutableListOf<Coins.CheckAddressResponse.Token>()
|
||||
resultTokens.forEach {
|
||||
val contractsWithTheSameAddress = it.contracts.filter { it.address == contractAddress }
|
||||
var tokensList = mutableListOf<Coins.CheckAddressResponse.Token>()
|
||||
resultTokens.forEach { token ->
|
||||
val contractsWithTheSameAddress = token.contracts
|
||||
.filter { it.address == contractAddress }
|
||||
.filter { it.decimalCount != null }
|
||||
if (contractsWithTheSameAddress.isNotEmpty()) {
|
||||
val newToken = it.copy(contracts = contractsWithTheSameAddress)
|
||||
newTokensList.add(newToken)
|
||||
val newToken = token.copy(contracts = contractsWithTheSameAddress)
|
||||
tokensList.add(newToken)
|
||||
}
|
||||
}
|
||||
when {
|
||||
if (tokensList.size > 1) {
|
||||
// https://tangem.slack.com/archives/GMXC6PP71/p1649672562078679
|
||||
newTokensList.size > 1 -> listOf(newTokensList[0])
|
||||
else -> newTokensList
|
||||
tokensList = mutableListOf(tokensList[0])
|
||||
}
|
||||
Result.Success(tokensList)
|
||||
}
|
||||
is Result.Failure -> emptyList()
|
||||
is Result.Failure -> result
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun tokens(): List<Coins.TokensResponse.Token> {
|
||||
val result = tangemTechService.coins.tokens()
|
||||
return when (result) {
|
||||
return when (val result = tangemTechService.coins.tokens()) {
|
||||
is Result.Success -> {
|
||||
val currencies = result.data.tokens
|
||||
currencies.filter {
|
||||
val tokens = result.data.tokens
|
||||
tokens.filter {
|
||||
it.contracts.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,4 +18,8 @@ sealed class AddCustomTokenError : AnError(ERROR_CODE_ADD_CUSTOM_TOKEN, "Add cus
|
|||
sealed class AddCustomTokenWarning : AnError(ERROR_CODE_ADD_CUSTOM_TOKEN, "Add custom token - warning") {
|
||||
object PotentialScamToken : AddCustomTokenWarning()
|
||||
object TokenAlreadyAdded : AddCustomTokenWarning()
|
||||
|
||||
sealed class Network : AddCustomTokenWarning() {
|
||||
object CheckAddressRequestError : Network()
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ data class TokenField(
|
|||
override val id: FieldId,
|
||||
) : BaseDataField<String>(id, Field.Data(""))
|
||||
|
||||
data class TokenNetworkField(
|
||||
data class TokenBlockchainField(
|
||||
override val id: FieldId,
|
||||
val itemList: List<Blockchain>,
|
||||
) : BaseDataField<Blockchain>(id, Field.Data(Blockchain.Unknown))
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ import org.rekotlin.Action
|
|||
*/
|
||||
internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomTokenHub") {
|
||||
|
||||
private val hubState: AddCustomTokenState
|
||||
get() = domainStore.state.addCustomTokensState
|
||||
|
||||
override fun getHubState(storeState: DomainState): AddCustomTokenState {
|
||||
return storeState.addCustomTokensState
|
||||
}
|
||||
|
|
@ -41,7 +44,6 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
if (action !is AddCustomTokenAction) return
|
||||
// val card = storeState.globalState.scanResponse?.card
|
||||
// ?: throw IllegalStateException("ScanResponse must be set before showing the AddCustomToken screen")
|
||||
val hubState = storeState.addCustomTokensState
|
||||
|
||||
when (action) {
|
||||
is OnCreate -> {
|
||||
|
|
@ -102,7 +104,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
// dispatchOnMain()
|
||||
}
|
||||
is FillTokenFields -> {
|
||||
val networkField = getField<TokenNetworkField>(Network, hubState)
|
||||
val networkField = getField<TokenBlockchainField>(Network, hubState)
|
||||
val nameField = getField<TokenField>(Name, hubState)
|
||||
val symbolField = getField<TokenField>(Symbol, hubState)
|
||||
val decimalsField = getField<TokenField>(Decimals, hubState)
|
||||
|
|
@ -127,13 +129,22 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
): List<Coins.CheckAddressResponse.Token> {
|
||||
dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = true))))
|
||||
val tokenManager = hubState.addCustomTokenManager
|
||||
val field = getField<TokenNetworkField>(Network, hubState)
|
||||
val field = getField<TokenBlockchainField>(Network, hubState)
|
||||
val selectedNetworkId: String? = field.data.value.let {
|
||||
if (it == Blockchain.Unknown) null else it
|
||||
}?.toNetworkId()
|
||||
val foundTokens = tokenManager.checkAddress(contractAddress, selectedNetworkId)
|
||||
|
||||
// delay(1000)
|
||||
val result = when (val foundTokensResult = tokenManager.checkAddress(contractAddress, selectedNetworkId)) {
|
||||
is Result.Success -> foundTokensResult.data
|
||||
is Result.Failure -> {
|
||||
// val warning = AddCustomTokenWarning.Network.CheckAddressRequestError
|
||||
// dispatchOnMain(Warning.Add(setOf(warning)))
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = false))))
|
||||
return foundTokens
|
||||
return result
|
||||
}
|
||||
|
||||
private suspend fun checkToken(
|
||||
|
|
@ -171,12 +182,18 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
else -> {
|
||||
val dialog = DomainDialog.SelectTokenDialog(
|
||||
items = contracts,
|
||||
itemNameConverter = { it.address },
|
||||
networkIdConverter = { networkId ->
|
||||
val blockchain = Blockchain.fromNetworkId(networkId)
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
throw DomainException.SelectTokeNetworkException(networkId)
|
||||
}
|
||||
AddCustomTokenState.convertBlockchainName(blockchain, "")
|
||||
},
|
||||
onSelect = { selectedContract ->
|
||||
hubScope.launch {
|
||||
// find how to connect to the upper coroutineContext and dispatch through them
|
||||
dispatchOnMain(FillTokenFields(token, selectedContract))
|
||||
dispatchOnMain(FillTokenFields(token, selectedContract))
|
||||
dispatchOnMain(actionsLockTokenFields())
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -237,7 +254,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
updateFormState(state)
|
||||
}
|
||||
is OnTokenNetworkChanged -> {
|
||||
val field: TokenNetworkField = getField(Network, state)
|
||||
val field: TokenBlockchainField = getField(Network, state)
|
||||
field.data = action.value
|
||||
updateFormState(state)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,22 +49,17 @@ data class AddCustomTokenState(
|
|||
companion object {
|
||||
fun convertBlockchainName(blockchain: Blockchain, unknown: String): String = when (blockchain) {
|
||||
Blockchain.Unknown -> unknown
|
||||
Blockchain.Cardano -> "Cardano"
|
||||
Blockchain.CardanoShelley -> "Cardano Shelley"
|
||||
else -> blockchain.fullName
|
||||
}
|
||||
|
||||
fun convertDerivationPathName(blockchain: Blockchain, unknown: String): String = when (blockchain) {
|
||||
Blockchain.Unknown -> unknown
|
||||
Blockchain.BSC -> "BNB Smart Chain"
|
||||
Blockchain.Fantom -> "Fantom Opera"
|
||||
else -> blockchain.fullName
|
||||
fun convertDerivationPathLabel(blockchain: Blockchain, unknown: String): String {
|
||||
return blockchain.derivationPath()?.rawPath ?: unknown
|
||||
}
|
||||
|
||||
private fun createFormFields(): List<DataField<*>> {
|
||||
return listOf(
|
||||
TokenField(ContractAddress),
|
||||
TokenNetworkField(Network, getSupportedNetworks()),
|
||||
TokenBlockchainField(Network, getSupportedNetworks()),
|
||||
TokenField(Name),
|
||||
TokenField(Symbol),
|
||||
TokenField(Decimals),
|
||||
|
|
@ -84,6 +79,7 @@ data class AddCustomTokenState(
|
|||
|
||||
private fun getSupportedNetworks(): List<Blockchain> {
|
||||
return listOf(
|
||||
Blockchain.Unknown,
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.BSC,
|
||||
Blockchain.Binance,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue