Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-21 07:44:33 +03:00
parent f8cccf4482
commit b8d5c8f4f2
5 changed files with 138 additions and 41 deletions

View file

@ -10,6 +10,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.form.Field
import com.tangem.tap.common.extensions.ValueCallback
import timber.log.Timber
/**
[REDACTED_AUTHOR]
@ -27,14 +28,24 @@ fun <T> OutlinedSpinner(
isEnabled: Boolean = true,
onClose: VoidCallback = {}
) {
val compositionCounter = remember { CompositionCounter(label) }
val counter = compositionCounter.increase(label)
val rIsExpanded = remember { mutableStateOf(false) }
val rSelectedItem = remember { mutableStateOf(selectedItem.value) }
val stateSelectedItem = remember { mutableStateOf(selectedItem.value) }
osLog(label, "recompose ------------------------------------", counter)
osLog(label, "selectedItem: $selectedItem", counter)
osLog(label, "stateSelectedItem: ${stateSelectedItem.value}", counter)
if (!selectedItem.isUserInput) {
rSelectedItem.value = selectedItem.value
osLog(label, "stateSelectedItem.value: update = selectedItem.value", counter)
stateSelectedItem.value = selectedItem.value
}
osLog(label, "stateSelectedItem: ${stateSelectedItem.value}", counter)
val onDropDownItemSelectedInternal: (T) -> Unit = {
rSelectedItem.value = it
osLog(label, "onDropDownItemSelectedInternal: value: [${it.toString()}]", counter)
stateSelectedItem.value = it
osLog(label, "onDropDownItemSelectedInternal: stateSelectedItem: ${stateSelectedItem.value}", counter)
rIsExpanded.value = false
onItemSelected(it)
}
@ -51,7 +62,7 @@ fun <T> OutlinedSpinner(
modifier = modifier,
readOnly = true,
enabled = isEnabled,
value = textFieldConverter(rSelectedItem.value),
value = textFieldConverter(stateSelectedItem.value),
onValueChange = {},
label = { Text(label) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = rIsExpanded.value) },
@ -74,6 +85,27 @@ fun <T> OutlinedSpinner(
}
}
private fun osLog(id: String, log: String, counter: CompositionCounter) {
if (id == "Сеть") Timber.d(
"OutlinedSpinner[$id]:[${counter.count}] - $log"
)
}
class CompositionCounter(
val id: String,
count: Int = 0
) {
var count: Int = count
private set
fun increase(id: String): CompositionCounter {
if (this.id != id) return this
count += 1
return CompositionCounter(id, count)
}
}
@Preview
@Composable
fun TestSpinnerPreview() {

View file

@ -1,10 +1,7 @@
package com.tangem.tap.features.wallet.ui.test
import com.tangem.blockchain.blockchains.solana.SolanaWalletManager
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.blockchain.common.WalletManager
import com.tangem.tap.common.TestAction
import com.tangem.tap.common.TestActions
import com.tangem.tap.domain.tokens.BlockchainNetwork
@ -68,7 +65,7 @@ private class SolanaRentWarningActionEmitter {
}
private fun setBalance(value: BigDecimal) {
val amount = Amount(getBlockchain()).copy(value = value)
val amount = Amount(getBlockchainNetwork().blockchain).copy(value = value)
getWalletManager().apply {
TestActions.testAmountInjectionForWalletManagerEnabled = true
wallet.setAmount(amount)
@ -76,19 +73,12 @@ private class SolanaRentWarningActionEmitter {
store.dispatch(WalletAction.LoadData)
}
private fun getWalletManager(): SolanaWalletManager {
return store.state.walletState.getWalletManager(BlockchainNetwork(
blockchain = getBlockchain(),
card = scanResponse().card
)) as SolanaWalletManager
private fun getWalletManager(): WalletManager {
return store.state.walletState.getWalletManager(getBlockchainNetwork())!!
}
private fun getBlockchain(): Blockchain = when {
scanResponse().card.isTestCard -> Blockchain.SolanaTestnet
else -> Blockchain.Solana
}
private fun scanResponse(): ScanResponse {
return store.state.globalState.scanResponse ?: throw UnsupportedOperationException()
private fun getBlockchainNetwork(): BlockchainNetwork {
val currency = store.state.walletState.getSelectedWalletData()!!.currency
return BlockchainNetwork(currency.blockchain, currency.derivationPath, listOf())
}
}

View file

@ -4,12 +4,26 @@ package com.tangem.domain.common.form
[REDACTED_AUTHOR]
*/
class Form(
val fieldList: List<DataField<*>>,
fieldList: List<DataField<*>>,
) {
private val _fieldList: MutableList<DataField<*>> = fieldList.toMutableList()
val fieldList: List<DataField<*>>
get() = _fieldList.toList()
fun getField(id: FieldId): DataField<*>? = fieldList.firstOrNull { it.id == id }
fun getData(id: FieldId): Pair<FieldId, *>? = getField(id)?.getData()
fun setField(field: DataField<*>) {
val oldField = getField(field.id) ?: return
val oldIndexOfField = _fieldList.indexOf(oldField)
if (oldIndexOfField == -1) return
_fieldList.removeAt(oldIndexOfField)
_fieldList.add(oldIndexOfField, field)
}
// convert this form data whatever you want
fun visitDataConverter(converter: FieldDataConverter<*>) {
fieldList.forEach { it.visitDataConverter(converter) }

View file

@ -65,31 +65,25 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
null -> {
ContractAddress.removeError()
unlockTokenFields()
changeBlockchainNetworkList()
}
AddCustomTokenError.FieldIsEmpty -> {
ContractAddress.removeError()
changeBlockchainNetworkList()
return
}
AddCustomTokenError.InvalidContractAddress -> {
ContractAddress.addError(error)
unlockTokenFields()
changeBlockchainNetworkList()
return
}
else -> {}
}
if (!action.contractAddress.isUserInput) return
manageFoundTokenChanges(requestInfoAboutToken(address))
}
is OnTokenNameChanged -> {
updateAddButton()
}
is OnTokenSymbolChanged -> {
updateAddButton()
}
is OnTokenDecimalsChanged -> {
updateAddButton()
}
is OnTokenNetworkChanged -> {
if (!action.blockchainNetwork.isUserInput) return
@ -122,13 +116,25 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
updateWarningAlreadyAdded(isAlreadyAdded)
updateAddButton()
}
is OnTokenNameChanged -> {
changeBlockchainNetworkList()
updateAddButton()
}
is OnTokenSymbolChanged -> {
changeBlockchainNetworkList()
updateAddButton()
}
is OnTokenDecimalsChanged -> {
changeBlockchainNetworkList()
updateAddButton()
}
is OnAddCustomTokenClicked -> {
val state = hubState
val completeData = when {
state.tokensFieldsIsFilled() && state.networkIsSelected() -> {
state.getCustomTokenType() == CustomTokenType.Token && state.networkIsSelected() -> {
state.gatherUserToken()
}
!state.tokensFieldsIsFilled() && state.networkIsSelected() -> {
state.getCustomTokenType() == CustomTokenType.Blockchain && state.networkIsSelected() -> {
state.gatherBlockchain()
}
else -> null
@ -146,6 +152,41 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
}
}
/**
* This feature is only needed until Solana tokens are added.
* While they are not there - this function excludes the Solana blockchain if the user has
* filled in at least one field of the token.
*/
private suspend fun changeBlockchainNetworkList() {
val state = hubState
val networkBlockchainList = Network.getField<TokenBlockchainField>().itemList
val newNetworkBlockchainList: List<Blockchain> = state.getNetworks(state.getCustomTokenType())
val listsIdentical = newNetworkBlockchainList.toSet() == networkBlockchainList.toSet()
if (listsIdentical) return
val networkFieldIsFilled = Network.isFilled()
val newNetworkField = Network.getField<TokenBlockchainField>().copy(itemList = newNetworkBlockchainList)
if (networkFieldIsFilled) {
val listSameSize = networkBlockchainList.size == newNetworkBlockchainList.size
val newListLessThanOld = networkBlockchainList.size > newNetworkBlockchainList.size
if (listSameSize || newListLessThanOld) {
// check selected blockchain
val selectedBlockchain = Network.getFieldValue<Blockchain>()
val defaultSelection = Blockchain.Unknown
if (!newNetworkBlockchainList.contains(selectedBlockchain)
&& newNetworkBlockchainList.contains(defaultSelection)
) {
// selectedBlockchain not present in the new list. Change selection to default
newNetworkField.data = Field.Data(defaultSelection, false)
}
}
}
hubState.setField(newNetworkField)
dispatchOnMain(UpdateForm(hubState))
}
private suspend fun updateWarningAlreadyAdded(isInAppSavedList: Boolean) {
if (isInAppSavedList) {
TokenAlreadyAdded.add()

View file

@ -13,7 +13,7 @@ data class AddCustomTokenState(
val appSavedCurrencies: List<DomainWrapped.Currency>? = null,
val onTokenAddCallback: ((CustomCurrency) -> Unit)? = null,
val cardDerivationStyle: DerivationStyle? = null,
val form: Form = Form(createFormFields()),
val form: Form = Form(createFormFields(CustomTokenType.Blockchain)),
val formValidators: Map<CustomTokenFieldId, CustomTokenValidator<out Any>> = createFormValidators(),
val formErrors: Map<CustomTokenFieldId, AddCustomTokenError> = emptyMap(),
val tokenId: String? = null,
@ -24,6 +24,10 @@ data class AddCustomTokenState(
inline fun <reified T> getField(id: FieldId): T = form.getField(id) as T
fun setField(field: DataField<*>) {
form.setField(field)
}
inline fun <reified T> getValidator(id: FieldId): T = formValidators[id] as T
fun getError(id: FieldId): AddCustomTokenError? = formErrors[id]
@ -80,6 +84,11 @@ data class AddCustomTokenState(
return network.data.value != Blockchain.Unknown
}
fun getCustomTokenType(): CustomTokenType = when {
tokensOneFieldsIsFilled() || tokensFieldsIsFilled() -> CustomTokenType.Token
else -> CustomTokenType.Blockchain
}
fun gatherUserToken(): CustomCurrency.CustomToken? = try {
getToken()
} catch (ex: Exception) {
@ -97,7 +106,7 @@ data class AddCustomTokenState(
appSavedCurrencies = null,
onTokenAddCallback = null,
cardDerivationStyle = null,
form = Form(createFormFields()),
form = Form(createFormFields(CustomTokenType.Blockchain)),
formErrors = emptyMap(),
tokenId = null,
warnings = emptySet(),
@ -118,6 +127,10 @@ data class AddCustomTokenState(
.getConvertedData()
}
fun getNetworks(type: CustomTokenType): List<Blockchain> {
return getSupportedNetworks(type)
}
companion object {
/**
@ -133,10 +146,10 @@ data class AddCustomTokenState(
else -> derivationNetwork
}.derivationPath(derivationStyle)
private fun createFormFields(): List<DataField<*>> {
private fun createFormFields(type: CustomTokenType): List<DataField<*>> {
return listOf(
TokenField(ContractAddress),
TokenBlockchainField(Network, getSupportedNetworks()),
TokenBlockchainField(Network, getSupportedNetworks(type)),
TokenField(Name),
TokenField(Symbol),
TokenField(Decimals),
@ -154,17 +167,20 @@ data class AddCustomTokenState(
)
}
private fun getSupportedNetworks(): List<Blockchain> {
return listOf(
private fun getSupportedNetworks(type: CustomTokenType): List<Blockchain> {
val networks = mutableListOf(
Blockchain.Unknown,
Blockchain.Ethereum,
Blockchain.BSC,
Blockchain.Binance,
Blockchain.Polygon,
Blockchain.Avalanche,
// Blockchain.Solana, // not supported until tokens added to the Blockchain SDK
Blockchain.Fantom,
Blockchain.Solana, // should be unsupported for tokens until they are added to the Blockchain SDK
)
if (type == CustomTokenType.Token) networks.remove(Blockchain.Solana)
return networks
}
private fun getSupportedDerivations(): List<Blockchain> {
@ -186,4 +202,8 @@ data class AddCustomTokenState(
)
}
}
}
enum class CustomTokenType {
Token, Blockchain
}