Updated on 2026-08-14
This commit is contained in:
parent
787d354d3d
commit
a07f371456
36 changed files with 666 additions and 469 deletions
|
|
@ -19,7 +19,7 @@ import com.tangem.wallet.R
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun Button(
|
||||
fun RectangleButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String = "",
|
||||
textId: Int? = null,
|
||||
|
|
@ -90,7 +90,7 @@ fun ButtonTest() {
|
|||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
PreviewItem("Button") {
|
||||
Button(text = "Some button") {}
|
||||
RectangleButton(text = "Some button") {}
|
||||
}
|
||||
PreviewItem("PasteButton") {
|
||||
PasteButton(onClick = {})
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ 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.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -22,7 +21,9 @@ 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.DomainErrorConverter
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.SelectTokenNetworkDialog
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Composable
|
||||
|
|
@ -51,12 +52,19 @@ fun ComposeDialogManager() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ShowTheDialog(dialogState: MutableState<DomainStateDialog?>) {
|
||||
private fun ShowTheDialog(dialogState: MutableState<DomainStateDialog?>) {
|
||||
if (dialogState.value == null) return
|
||||
|
||||
val context = LocalContext.current
|
||||
val errorConverter = remember { DomainErrorConverter(context) }
|
||||
val onDismissRequest = { domainStore.dispatch(DomainGlobalAction.ShowDialog(null)) }
|
||||
|
||||
when (val dialog = dialogState.value) {
|
||||
is DomainDialog.DialogError -> ErrorDialog(
|
||||
title = stringResource(id = R.string.common_error),
|
||||
body = errorConverter.convertError(dialog.error),
|
||||
onDismissRequest
|
||||
)
|
||||
is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest)
|
||||
}
|
||||
}
|
||||
|
|
@ -83,17 +91,7 @@ fun <T> SimpleDialog(
|
|||
Column(
|
||||
modifier = Modifier.padding(22.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = LocalTextStyle.provides(
|
||||
TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
).value
|
||||
)
|
||||
|
||||
SpacerH16()
|
||||
DialogTitle(title = title)
|
||||
LazyColumn() {
|
||||
items(items) { item ->
|
||||
Row(
|
||||
|
|
@ -111,4 +109,36 @@ fun <T> SimpleDialog(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DialogTitle(title: String) {
|
||||
Text(
|
||||
text = title,
|
||||
style = LocalTextStyle.provides(
|
||||
TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
).value
|
||||
)
|
||||
SpacerH16()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ErrorDialog(
|
||||
title: String,
|
||||
body: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
title = { DialogTitle(title) },
|
||||
text = { Text(body) },
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
Button(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(id = R.string.common_ok))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.platform.LocalView
|
||||
|
||||
sealed class Keyboard {
|
||||
data class Opened(val height: Int): Keyboard()
|
||||
data class Opened(val height: Int) : Keyboard()
|
||||
object Closed : Keyboard()
|
||||
}
|
||||
|
||||
|
|
@ -14,12 +14,21 @@ sealed class Keyboard {
|
|||
fun keyboardAsState(): State<Keyboard> {
|
||||
val keyboardState: MutableState<Keyboard> = remember { mutableStateOf(Keyboard.Closed) }
|
||||
val view = LocalView.current
|
||||
val discrepancy = remember {
|
||||
mutableStateOf(0)
|
||||
}
|
||||
DisposableEffect(view) {
|
||||
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
|
||||
|
||||
val rect = Rect()
|
||||
view.getWindowVisibleDisplayFrame(rect)
|
||||
val screenHeight = view.rootView.height
|
||||
val keypadHeight = screenHeight - rect.bottom
|
||||
val keypadHeight: Int = screenHeight - (rect.bottom + rect.top) - discrepancy.value
|
||||
if (discrepancy.value == 0) {
|
||||
discrepancy.value = keypadHeight;
|
||||
if (keypadHeight == 0) discrepancy.value = 1
|
||||
}
|
||||
|
||||
keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
|
||||
Keyboard.Opened(keypadHeight)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun copyToClipboard(value: Any, label: String = "") {
|
||||
LocalContext.current.copyToClipboard(value, label)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getFromClipboard(default: CharSequence? = null): CharSequence? {
|
||||
return LocalContext.current.getFromClipboard(default)
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
|
|||
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.shop.ui.ShopFragment
|
||||
import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment
|
||||
import com.tangem.tap.features.tokens.ui.AddTokensFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
|
|
@ -82,6 +83,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.DetailsSecurity -> DetailsSecurityFragment()
|
||||
AppScreen.Disclaimer -> DisclaimerFragment()
|
||||
AppScreen.AddTokens -> AddTokensFragment()
|
||||
AppScreen.AddCustomToken -> AddCustomTokenFragment()
|
||||
AppScreen.WalletDetails -> WalletDetailsFragment()
|
||||
AppScreen.WalletConnectSessions -> WalletConnectSessionsFragment()
|
||||
AppScreen.QrScan -> QrScanFragment()
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import com.squareup.picasso.Transformation
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.IconsUtil
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.tap.domain.extensions.getCustomIconUrl
|
||||
import com.tangem.tap.domain.tokens.getIconUrl
|
||||
import com.tangem.tap.domain.tokens.toNetworkId
|
||||
import com.tangem.wallet.R
|
||||
|
||||
fun Picasso.loadCurrenciesIcon(
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ enum class AppScreen {
|
|||
Wallet, WalletDetails,
|
||||
Send,
|
||||
Details, DetailsConfirm, DetailsSecurity,
|
||||
AddTokens,
|
||||
AddTokens, AddCustomToken,
|
||||
WalletConnectSessions,
|
||||
QrScan
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.domain.tokens
|
|||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrencyFromJson(
|
||||
|
|
@ -74,62 +75,4 @@ data class Contract(
|
|||
|
||||
fun getIconUrl(id: String): String {
|
||||
return "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/$id.png"
|
||||
}
|
||||
|
||||
|
||||
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
||||
return when (networkId) {
|
||||
"avalanche" -> Blockchain.Avalanche
|
||||
"binancecoin" -> Blockchain.Binance
|
||||
"binance-smart-chain" -> Blockchain.BSC
|
||||
"ethereum" -> Blockchain.Ethereum
|
||||
"polygon-pos" -> Blockchain.Polygon
|
||||
"solana" -> Blockchain.Solana
|
||||
"fantom" -> Blockchain.Fantom
|
||||
"bitcoin" -> Blockchain.Bitcoin
|
||||
"bitcoin-cash" -> Blockchain.BitcoinCash
|
||||
"cardano" -> Blockchain.CardanoShelley
|
||||
"dogecoin" -> Blockchain.Dogecoin
|
||||
"ducatus" -> Blockchain.Ducatus
|
||||
"litecoin" -> Blockchain.Litecoin
|
||||
"rsk" -> Blockchain.RSK
|
||||
"stellar" -> Blockchain.Stellar
|
||||
"tezos" -> Blockchain.Tezos
|
||||
"ripple" -> Blockchain.XRP
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.toNetworkId(): String {
|
||||
return when (this) {
|
||||
Blockchain.Unknown -> "unknown"
|
||||
Blockchain.Avalanche -> "avalanche"
|
||||
Blockchain.AvalancheTestnet -> "avalaunche"
|
||||
Blockchain.Binance -> "binancecoin"
|
||||
Blockchain.BinanceTestnet -> "binancecoin"
|
||||
Blockchain.BSC -> "binance-smart-chain"
|
||||
Blockchain.BSCTestnet -> "binance-smart-chain"
|
||||
Blockchain.Bitcoin -> "bitcoin"
|
||||
Blockchain.BitcoinTestnet -> "bitcoin"
|
||||
Blockchain.BitcoinCash -> "bitcoin-cash"
|
||||
Blockchain.BitcoinCashTestnet -> "bitcoin-cash"
|
||||
Blockchain.Cardano -> "cardano"
|
||||
Blockchain.CardanoShelley -> "cardano"
|
||||
Blockchain.Dogecoin -> "dogecoin"
|
||||
Blockchain.Ducatus -> "ducatus"
|
||||
Blockchain.Ethereum -> "ethereum"
|
||||
Blockchain.EthereumTestnet -> "ethereum"
|
||||
Blockchain.Fantom -> "fantom"
|
||||
Blockchain.FantomTestnet -> "fantom"
|
||||
Blockchain.Litecoin -> "litecoin"
|
||||
Blockchain.Polygon -> "matic-network"
|
||||
Blockchain.PolygonTestnet -> "matic-networks"
|
||||
Blockchain.RSK -> "rootstock"
|
||||
Blockchain.Stellar -> "stellar"
|
||||
Blockchain.StellarTestnet -> "stellar"
|
||||
Blockchain.Solana -> "solana"
|
||||
Blockchain.SolanaTestnet -> "solana"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "ripple"
|
||||
}
|
||||
}
|
||||
|
|
@ -10,40 +10,40 @@ import com.tangem.wallet.R
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CustomTokenErrorConverter(
|
||||
class DomainErrorConverter(
|
||||
private val context: Context
|
||||
) : ErrorConverter<String> {
|
||||
|
||||
override fun convertError(error: DomainError): String {
|
||||
val errorMessage = when (error) {
|
||||
is AddCustomTokenError -> AddCustomTokenConverter(context).convertError(error)
|
||||
else -> null
|
||||
}
|
||||
return errorMessage?.let { it } ?: "Unknown error: ${error::class.java.simpleName}"
|
||||
}
|
||||
}
|
||||
|
||||
private class AddCustomTokenConverter(
|
||||
private val context: Context
|
||||
) : ErrorConverter<String> {
|
||||
|
||||
override fun convertError(error: DomainError): String {
|
||||
val customTokenError = (error as? AddCustomTokenError) ?: throw UnsupportedOperationException()
|
||||
|
||||
val resId = when (customTokenError) {
|
||||
val rawMessage = when (customTokenError) {
|
||||
AddCustomTokenWarning.PotentialScamToken -> R.string.custom_token_validation_error_not_found
|
||||
AddCustomTokenWarning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added
|
||||
AddCustomTokenError.InvalidContractAddress -> R.string.custom_token_creation_error_invalid_contract_address
|
||||
AddCustomTokenError.NetworkIsNotSelected -> R.string.custom_token_creation_error_network_not_selected
|
||||
AddCustomTokenError.InvalidDerivationPath -> R.string.custom_token_creation_error_invalid_derivation_path
|
||||
AddCustomTokenError.InvalidDecimalsCount -> R.string.custom_token_creation_error_wrong_decimals
|
||||
AddCustomTokenError.FieldIsEmpty -> R.string.custom_token_creation_error_empty_fields
|
||||
else -> null
|
||||
}
|
||||
return resId?.let { context.getString(it) } ?: "Unknown error: ${customTokenError::class.java.simpleName}"
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTokenWarningConverter(
|
||||
private val context: Context
|
||||
) : ErrorConverter<String> {
|
||||
|
||||
override fun convertError(error: DomainError): String {
|
||||
val customTokenWarning = (error as? AddCustomTokenWarning) ?: throw UnsupportedOperationException()
|
||||
|
||||
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}"
|
||||
// is String -> rawMessage
|
||||
else -> "Unknown error: ${customTokenError::class.java.simpleName}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,34 +2,31 @@ package com.tangem.tap.features.tokens.addCustomToken.compose
|
|||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
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
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.domain.ErrorConverter
|
||||
import com.tangem.domain.common.form.DataField
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.common.form.FieldId
|
||||
import com.tangem.domain.features.addCustomToken.*
|
||||
import com.tangem.domain.features.addCustomToken.AddCustomTokenError
|
||||
import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.*
|
||||
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.*
|
||||
import com.tangem.tap.features.tokens.addCustomToken.CustomTokenErrorConverter
|
||||
import com.tangem.tap.features.tokens.addCustomToken.CustomTokenWarningConverter
|
||||
import com.tangem.tap.common.compose.ComposeDialogManager
|
||||
import com.tangem.tap.common.compose.keyboardAsState
|
||||
import com.tangem.tap.features.tokens.addCustomToken.DomainErrorConverter
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
|
|
@ -44,12 +41,18 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
|||
Scaffold(
|
||||
scaffoldState = scaffoldState,
|
||||
backgroundColor = colorResource(id = R.color.backgroundLightGray),
|
||||
floatingActionButton = {
|
||||
HangingOverKeyboardView(keyboardState = keyboardAsState()) {
|
||||
AddButton(state)
|
||||
}
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = 90.dp)
|
||||
) {
|
||||
item { AddCustomTokenDebugActions() }
|
||||
// item { AddCustomTokenDebugActions() }
|
||||
item {
|
||||
Surface(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
|
|
@ -67,18 +70,6 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
|||
}
|
||||
item { Warnings(state.value.warnings.toList()) }
|
||||
}
|
||||
HangingOverKeyboardView(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter),
|
||||
keyboardState = keyboardAsState(),
|
||||
defaultBottomPadding = 30.dp,
|
||||
spaceBetweenKeyboard = 20.dp,
|
||||
) {
|
||||
AddButton(
|
||||
isEnabled = state.value.screenState.addButton.isEnabled
|
||||
) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ComposeDialogManager()
|
||||
}
|
||||
|
|
@ -90,7 +81,7 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
|||
@Composable
|
||||
private fun FormFields(state: MutableState<AddCustomTokenState>) {
|
||||
val context = LocalContext.current
|
||||
val errorConverter = remember { CustomTokenErrorConverter(context) }
|
||||
val errorConverter = remember { DomainErrorConverter(context) }
|
||||
|
||||
val stateValue = state.value
|
||||
stateValue.form.fieldList.forEach { field ->
|
||||
|
|
@ -107,124 +98,11 @@ private fun FormFields(state: MutableState<AddCustomTokenState>) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenContractAddressView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_contract_address_input_title,
|
||||
placeholder = "0x0000000000000000",
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
isLoading = screenFieldData.viewState.isLoading,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) {
|
||||
domainStore.dispatch(OnTokenContractAddressChanged(Field.Data(it)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenNameView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_name_input_title,
|
||||
placeholderId = R.string.custom_token_name_input_placeholder,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) {
|
||||
domainStore.dispatch(OnTokenNameChanged(Field.Data(it)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenNetworkView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_network_input_not_selected)
|
||||
val networkField = screenFieldData.field as TokenBlockchainField
|
||||
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_network_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
|
||||
) { domainStore.dispatch(OnTokenNetworkChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenSymbolView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_token_symbol_input_title,
|
||||
placeholderId = R.string.custom_token_token_symbol_input_placeholder,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) { domainStore.dispatch(OnTokenSymbolChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenDecimalsView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_decimals_input_title,
|
||||
placeholder = "8",
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
) { domainStore.dispatch(OnTokenDecimalsChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenDerivationPathView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_derivation_path_default)
|
||||
val networkField = screenFieldData.field as TokenDerivationPathField
|
||||
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_derivation_path_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
|
||||
dropdownItemView = { blockchain ->
|
||||
val derivationPathLabel = state.convertDerivationPathLabel(blockchain, notSelected)
|
||||
val blockchainName = state.convertBlockchainName(blockchain, notSelected)
|
||||
TitleSubtitle(derivationPathLabel, blockchainName)
|
||||
}
|
||||
) { domainStore.dispatch(OnTokenDerivationPathChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Warnings(warnings: List<AddCustomTokenWarning>) {
|
||||
fun Warnings(warnings: List<AddCustomTokenWarning>) {
|
||||
if (warnings.isEmpty()) return
|
||||
|
||||
val context = LocalContext.current
|
||||
val warningConverter = remember { CustomTokenWarningConverter(context) }
|
||||
val warningConverter = remember { DomainErrorConverter(context) }
|
||||
|
||||
Column {
|
||||
warnings.forEachIndexed { index, item ->
|
||||
|
|
@ -236,8 +114,8 @@ private fun Warnings(warnings: List<AddCustomTokenWarning>) {
|
|||
Surface(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = colorResource(id = R.color.darkGray2),
|
||||
contentColor = colorResource(id = R.color.darkGray3)
|
||||
color = colorResource(id = R.color.warning_warning),
|
||||
elevation = 4.dp,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
|
|
@ -251,30 +129,48 @@ private fun Warnings(warnings: List<AddCustomTokenWarning>) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun AddButton(
|
||||
private fun AddButton(state: MutableState<AddCustomTokenState>) {
|
||||
AddCustomTokenFab(
|
||||
modifier = Modifier
|
||||
.widthIn(210.dp, 280.dp),
|
||||
isEnabled = state.value.screenState.addButton.isEnabled
|
||||
) { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddCustomTokenFab(
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean,
|
||||
textId: Int = R.string.common_add,
|
||||
onClick: () -> Unit,
|
||||
isEnabled: Boolean = true,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Button(
|
||||
textId = textId,
|
||||
isEnabled = isEnabled,
|
||||
modifier = modifier
|
||||
.height(52.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
leadingView = {
|
||||
val contentColor = if (isEnabled) {
|
||||
Color.White
|
||||
} else {
|
||||
colorResource(id = R.color.darkGray1)
|
||||
}
|
||||
val backgroundColor = Color(0xFF1ACE80)
|
||||
|
||||
ExtendedFloatingActionButton(
|
||||
modifier = modifier,
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
tint = contentColor,
|
||||
contentDescription = "Add",
|
||||
)
|
||||
},
|
||||
onClick = onClick
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_add),
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
backgroundColor = backgroundColor,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
}
|
||||
|
||||
private data class ScreenFieldData(
|
||||
data class ScreenFieldData(
|
||||
val field: DataField<*>,
|
||||
val error: AddCustomTokenError?,
|
||||
val errorConverter: ErrorConverter<String>,
|
||||
|
|
@ -284,7 +180,7 @@ private data class ScreenFieldData(
|
|||
fun fromState(
|
||||
field: DataField<*>,
|
||||
state: AddCustomTokenState,
|
||||
errorConverter: CustomTokenErrorConverter
|
||||
errorConverter: DomainErrorConverter
|
||||
): ScreenFieldData {
|
||||
return ScreenFieldData(
|
||||
field = field,
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.TangemTechServiceManager
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -145,7 +147,7 @@ private fun CustomActions() {
|
|||
CustomActionButton(
|
||||
name = "Find tokens in several networks",
|
||||
action = {
|
||||
val manager = domainStore.state.addCustomTokensState.addCustomTokenManager
|
||||
val manager = TangemTechServiceManager(TangemTechService())
|
||||
val currencies = manager.tokens()
|
||||
val asdfsd = mutableMapOf<String, MutableList<Any>>()
|
||||
val contractAddresses = currencies.mapNotNull { currency ->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.TokenBlockchainField
|
||||
import com.tangem.domain.features.addCustomToken.TokenDerivationPathField
|
||||
import com.tangem.domain.features.addCustomToken.TokenField
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.BlockchainSpinner
|
||||
import com.tangem.tap.common.compose.OutlinedTextFieldWidget
|
||||
import com.tangem.tap.common.compose.SpacerH8
|
||||
import com.tangem.tap.common.compose.TitleSubtitle
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TokenContractAddressView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_contract_address_input_title,
|
||||
placeholder = "0x0000000000000000",
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
isLoading = screenFieldData.viewState.isLoading,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) {
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data(it)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenNameView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_name_input_title,
|
||||
placeholderId = R.string.custom_token_name_input_placeholder,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) {
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data(it)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenNetworkView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_network_input_not_selected)
|
||||
val networkField = screenFieldData.field as TokenBlockchainField
|
||||
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_network_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
|
||||
) { domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenSymbolView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_token_symbol_input_title,
|
||||
placeholderId = R.string.custom_token_token_symbol_input_placeholder,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) { domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenDecimalsView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_decimals_input_title,
|
||||
placeholder = "8",
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
) { domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenDerivationPathView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_derivation_path_default)
|
||||
val networkField = screenFieldData.field as TokenDerivationPathField
|
||||
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_derivation_path_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
|
||||
dropdownItemView = { blockchain ->
|
||||
val derivationPathLabel = state.convertDerivationPathLabel(blockchain, notSelected)
|
||||
val blockchainName = state.convertBlockchainName(blockchain, notSelected)
|
||||
TitleSubtitle(derivationPathLabel, blockchainName)
|
||||
}
|
||||
) { domainStore.dispatch(AddCustomTokenAction.OnTokenDerivationPathChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -21,36 +19,19 @@ import com.tangem.tap.common.compose.Keyboard
|
|||
fun HangingOverKeyboardView(
|
||||
modifier: Modifier = Modifier,
|
||||
keyboardState: State<Keyboard>,
|
||||
defaultBottomPadding: Dp = 0.dp,
|
||||
spaceBetweenKeyboard: Dp = 10.dp,
|
||||
calculateWithActionBarHeight: Boolean = true,
|
||||
spaceBetweenKeyboard: Dp = 0.dp,
|
||||
content: @Composable() (BoxScope.() -> Unit)
|
||||
) {
|
||||
fun getActionBarHeight(context: Context): Int {
|
||||
val typedValue = TypedValue()
|
||||
return if (context.theme.resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) {
|
||||
val data = typedValue.data
|
||||
val displayMetrics = context.resources.displayMetrics
|
||||
TypedValue.complexToDimensionPixelSize(data, displayMetrics)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val calculatedPadding = when (keyboardState.value) {
|
||||
Keyboard.Closed -> defaultBottomPadding
|
||||
val padding = when (keyboardState.value) {
|
||||
Keyboard.Closed -> 0.dp
|
||||
is Keyboard.Opened -> {
|
||||
val keyboardHeight = (keyboardState.value as Keyboard.Opened).height
|
||||
val keyboardPadding = context.pxToDp(keyboardHeight.toFloat()).dp
|
||||
if (calculateWithActionBarHeight) {
|
||||
val actionBarHeight = context.pxToDp(getActionBarHeight(context).toFloat()).dp
|
||||
keyboardPadding + spaceBetweenKeyboard - actionBarHeight
|
||||
} else {
|
||||
keyboardPadding + spaceBetweenKeyboard
|
||||
}
|
||||
|
||||
keyboardPadding + spaceBetweenKeyboard
|
||||
}
|
||||
}
|
||||
Box(modifier.padding(bottom = calculatedPadding)) { content() }
|
||||
|
||||
Box(modifier.padding(bottom = padding)) { content() }
|
||||
}
|
||||
|
|
@ -18,11 +18,13 @@ sealed class TokensAction : Action {
|
|||
|
||||
data class SetAddedCurrencies(
|
||||
val wallets: List<WalletData>, val derivationStyle: DerivationStyle?
|
||||
) : TokensAction()
|
||||
) : TokensAction()
|
||||
data class SetNonRemovableCurrencies(val wallets: List<WalletData>) : TokensAction()
|
||||
|
||||
data class SaveChanges(
|
||||
val addedTokens: List<TokenWithBlockchain>,
|
||||
val addedBlockchains: List<Blockchain>
|
||||
) : TokensAction()
|
||||
|
||||
object PrepareAndNavigateToAddCustomToken : TokensAction()
|
||||
}
|
||||
|
|
@ -8,16 +8,22 @@ import com.tangem.common.card.EllipticCurve
|
|||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.domain.DomainWrapped
|
||||
import com.tangem.domain.common.KeyWalletPublicKey
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.features.addCustomToken.CompleteData
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddedCurrencies
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
|
|
@ -26,6 +32,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
class TokensMiddleware {
|
||||
|
||||
|
|
@ -35,6 +42,7 @@ class TokensMiddleware {
|
|||
when (action) {
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
is TokensAction.PrepareAndNavigateToAddCustomToken -> handleAddingCustomToken(action)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
|
|
@ -80,6 +88,22 @@ class TokensMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleAddingCustomToken(action: TokensAction.PrepareAndNavigateToAddCustomToken) {
|
||||
val tokensState = store.state.tokensState
|
||||
val addedTokensList = tokensState.addedTokens.map {
|
||||
DomainWrapped.TokenWithBlockchain(it.token.copy(), it.blockchain)
|
||||
}
|
||||
val addedBlockchains = tokensState.addedBlockchains.map { it }
|
||||
val addedCurrencies = AddedCurrencies(addedTokensList, addedBlockchains)
|
||||
domainStore.dispatch(AddCustomTokenAction.Init.SetAddedCurrencies(addedCurrencies))
|
||||
|
||||
val callback = fun(data: CompleteData) {
|
||||
Timber.e("Yoooohhhoooo")
|
||||
}
|
||||
domainStore.dispatch(AddCustomTokenAction.Init.SetOnAddTokenCallback(callback))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
|
||||
}
|
||||
|
||||
private fun deriveMissingBlockchains(
|
||||
scanResponse: ScanResponse,
|
||||
blockchains: List<Blockchain>,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.features.tokens.redux
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.StateType
|
||||
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_search -> true
|
||||
R.id.menu_navigate_add_custom_token -> {
|
||||
store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.common.extensions.getRoundIconRes
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.wallet.R
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.common.compose.Keyboard
|
||||
import com.tangem.tap.common.compose.keyboardAsState
|
||||
import com.tangem.tap.common.extensions.pixelsToDp
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.wallet.R
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue