Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-13 19:15:50 +03:00
parent 536af28f80
commit 5fa7c042c7
2 changed files with 118 additions and 97 deletions

View file

@ -16,26 +16,27 @@ sealed class AddCustomTokenAction : Action {
// from user, ui
object OnCreate : AddCustomTokenAction()
object OnDestroy : AddCustomTokenAction()
data class OnTokenContractAddressChanged(val value: Field.Data<String>) : AddCustomTokenAction()
data class OnTokenNetworkChanged(val value: Field.Data<Blockchain>) : AddCustomTokenAction()
data class OnTokenNameChanged(val value: Field.Data<String>) : AddCustomTokenAction()
data class OnTokenSymbolChanged(val value: Field.Data<String>) : AddCustomTokenAction()
data class OnTokenDerivationPathChanged(val value: Field.Data<Blockchain>) : AddCustomTokenAction()
data class OnTokenDecimalsChanged(val value: Field.Data<String>) : AddCustomTokenAction()
data class OnCustomTokenSelected(val any: Any = Unit) : AddCustomTokenAction()
data class OnTokenContractAddressChanged(val contractAddress: Field.Data<String>) : AddCustomTokenAction()
data class OnTokenNetworkChanged(val blockchainNetwork: Field.Data<Blockchain>) : AddCustomTokenAction()
data class OnTokenNameChanged(val tokenName: Field.Data<String>) : AddCustomTokenAction()
data class OnTokenSymbolChanged(val tokenSymbol: Field.Data<String>) : AddCustomTokenAction()
data class OnTokenDerivationPathChanged(val blockchainDerivationPath: Field.Data<Blockchain>) : AddCustomTokenAction()
data class OnTokenDecimalsChanged(val tokenDecimals: Field.Data<String>) : AddCustomTokenAction()
// form fields
data class UpdateForm(val state: AddCustomTokenState) : AddCustomTokenAction()
object ClearTokenFields : AddCustomTokenAction()
data class FillTokenFields(
val token: Coins.CheckAddressResponse.Token,
val contract: Coins.CheckAddressResponse.Token.Contract,
) : AddCustomTokenAction()
sealed class Error : AddCustomTokenAction() {
data class Add(val id: CustomTokenFieldId, val error: AddCustomTokenError) : Error()
data class Remove(val id: CustomTokenFieldId) : Error()
sealed class FieldError : AddCustomTokenAction() {
data class Add(val id: CustomTokenFieldId, val error: AddCustomTokenError) : FieldError()
data class Remove(val id: CustomTokenFieldId) : FieldError()
}
// warnings
sealed class Warning : AddCustomTokenAction() {
data class Add(val warnings: Set<AddCustomTokenWarning>) : Warning()
data class Remove(val warnings: Set<AddCustomTokenWarning>) : Warning()

View file

@ -19,6 +19,7 @@ import com.tangem.network.api.tangemTech.Coins
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.rekotlin.Action
import timber.log.Timber
/**
[REDACTED_AUTHOR]
@ -51,57 +52,53 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
}
is OnDestroy -> hubScope.cancel()
is OnTokenContractAddressChanged -> {
val contractAddress = action.value
val contractAddress = action.contractAddress.value
val validator: TokenContractAddressValidator = getValidator(ContractAddress, hubState)
val error = validator.validate(contractAddress.value)
val error = validator.validate(contractAddress)
addOrRemoveError(ContractAddress, error)
if (error != null || contractAddress.value.isEmpty()) {
if (error != null || contractAddress.isEmpty()) {
dispatchOnMain(actionsUnlockTokenFields())
return
}
if (!action.contractAddress.isUserInput) return
val foundTokens = requestInfoAboutContractAddress(contractAddress.value, hubState)
val warningsToAdd = mutableSetOf<AddCustomTokenWarning>()
val warningsToRemove = mutableSetOf<AddCustomTokenWarning>()
when {
foundTokens.isEmpty() -> {
warningsToAdd.add(AddCustomTokenWarning.PotentialScamToken)
}
else -> {
val token = foundTokens[0]
checkToken(null, token, warningsToAdd, warningsToRemove)
}
}
if (warningsToAdd.isNotEmpty() || warningsToRemove.isNotEmpty()) {
dispatchOnMain(Warning.Replace(warningsToRemove.toSet(), warningsToAdd.toSet()))
}
val foundTokens = requestInfoAboutContractAddress(contractAddress, hubState)
manageTokenChanges(null, foundTokens)
}
is OnTokenNetworkChanged -> {
// val validator: TokenNetworkValidator = getValidator(Network, hubState)
// addOrRemoveError(Network, validator.validate(action.value.value))
if (!action.blockchainNetwork.isUserInput) return
// dispatchOnMain(OnTokenContractAddressChanged(Field.Data(
// getField<TokenField>(ContractAddress, hubState).data.value, false
// )))
val contractAddress = getField<TokenField>(ContractAddress, hubState).data.value
val foundTokens = requestInfoAboutContractAddress(contractAddress, hubState)
manageTokenChanges(null, foundTokens)
}
is OnTokenNameChanged -> {
val validator: TokenNameValidator = getValidator(Name, hubState)
addOrRemoveError(Name, validator.validate(action.value.value))
addOrRemoveError(Name, validator.validate(action.tokenName.value))
}
is OnTokenSymbolChanged -> {
val validator: TokenSymbolValidator = getValidator(Symbol, hubState)
addOrRemoveError(Symbol, validator.validate(action.value.value))
addOrRemoveError(Symbol, validator.validate(action.tokenSymbol.value))
}
is OnTokenDecimalsChanged -> {
val validator: TokenDecimalsValidator = getValidator(Decimals, hubState)
addOrRemoveError(Decimals, validator.validate(action.value.value))
addOrRemoveError(Decimals, validator.validate(action.tokenDecimals.value))
}
// is OnTokenDerivationPathChanged -> {
// val validator: TokenDerivationPathValidator = getValidator(DerivationPath, hubState)
// addOrRemoveError(DerivationPath, validator.validate(action.value.value))
// }
is OnCustomTokenSelected -> {
// dispatchOnMain()
is ClearTokenFields -> {
val nameField = getField<TokenField>(Name, hubState)
val symbolField = getField<TokenField>(Symbol, hubState)
val decimalsField = getField<TokenField>(Decimals, hubState)
nameField.data = Field.Data("", false)
symbolField.data = Field.Data("", false)
decimalsField.data = Field.Data("", false)
dispatchOnMain(UpdateForm(hubState))
}
is FillTokenFields -> {
val networkField = getField<TokenBlockchainField>(Network, hubState)
@ -147,58 +144,81 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
return result
}
private suspend fun checkToken(
private suspend fun manageTokenChanges(
card: Card?,
token: Coins.CheckAddressResponse.Token,
warningsToAdd: MutableSet<AddCustomTokenWarning>,
warningsToRemove: MutableSet<AddCustomTokenWarning>,
foundTokens: List<Coins.CheckAddressResponse.Token>,
) {
val contracts = token.contracts
when {
contracts.isEmpty() -> {
}
contracts.size == 1 -> {
val contract = contracts[0]
val isPersistIntoTheAppAddedTokenList = isPersistIntoTheAppAddedTokenList(token, contract)
val toAddWarnings = mutableSetOf<AddCustomTokenWarning>()
val toRemoveWarnings = mutableSetOf<AddCustomTokenWarning>()
if (isPersistIntoTheAppAddedTokenList) {
warningsToAdd.add(AddCustomTokenWarning.TokenAlreadyAdded)
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false)))
dispatchOnMain(actionsLockTokenFields())
} else {
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(true)))
// val isStandardDerivation = card.derivationType == DerivationType.Standard
val isStandardDerivation = true
val isStandardToken = token.active && isStandardDerivation
if (isStandardToken) {
dispatchOnMain(FillTokenFields(token, contract))
dispatchOnMain(actionsLockTokenFields())
} else {
warningsToAdd.add(AddCustomTokenWarning.PotentialScamToken)
dispatchOnMain(actionsUnlockTokenFields())
when {
foundTokens.isEmpty() -> {
toAddWarnings.add(AddCustomTokenWarning.PotentialScamToken)
toRemoveWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded)
dispatchOnMain(ClearTokenFields)
dispatchOnMain(actionsUnlockTokenFields())
}
else -> {
val token = foundTokens[0]
val contracts = token.contracts
when {
contracts.isEmpty() -> {
// TODO: refactoring:
Timber.e("Unexpected state -> throw to FB")
}
contracts.size == 1 -> {
val contract = contracts[0]
val isPersistIntoTheAppAddedTokenList = isPersistIntoTheAppAddedTokenList(token, contract)
if (isPersistIntoTheAppAddedTokenList) {
toAddWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded)
toRemoveWarnings.add(AddCustomTokenWarning.PotentialScamToken)
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false)))
dispatchOnMain(actionsLockTokenFields())
} else {
toRemoveWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded)
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(true)))
val isStandardDerivation = true
val tokenContract = token.contracts[0]
if (tokenContract.active && isStandardDerivation) {
toRemoveWarnings.add(AddCustomTokenWarning.PotentialScamToken)
dispatchOnMain(FillTokenFields(token, contract))
dispatchOnMain(actionsLockTokenFields())
} else {
toAddWarnings.add(AddCustomTokenWarning.PotentialScamToken)
dispatchOnMain(ClearTokenFields)
dispatchOnMain(actionsUnlockTokenFields())
}
}
}
else -> {
val dialog = DomainDialog.SelectTokenDialog(
items = contracts,
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(actionsLockTokenFields())
}
},
)
dispatchOnMain(DomainGlobalAction.ShowDialog(dialog))
}
}
}
else -> {
val dialog = DomainDialog.SelectTokenDialog(
items = contracts,
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(actionsLockTokenFields())
}
},
)
dispatchOnMain(DomainGlobalAction.ShowDialog(dialog))
}
}
if (toAddWarnings.isNotEmpty() || toRemoveWarnings.isNotEmpty()) {
dispatchOnMain(Warning.Replace(toRemoveWarnings.toSet(), toAddWarnings.toSet()))
}
}
@ -209,14 +229,14 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
private suspend fun addOrRemoveError(id: CustomTokenFieldId, error: AddCustomTokenError?) {
if (error == null) {
dispatchOnMain(Error.Remove(id))
dispatchOnMain(FieldError.Remove(id))
} else {
dispatchOnMain(Error.Add(id, error))
dispatchOnMain(FieldError.Add(id, error))
}
}
private fun actionsLockTokenFields(): Action {
val state = domainStore.state.addCustomTokensState
val state = hubState
return Screen.UpdateTokenFields(listOf(
Network to state.screenState.network.copy(isEnabled = false),
Name to state.screenState.name.copy(isEnabled = false),
@ -226,7 +246,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
}
private fun actionsUnlockTokenFields(): Action {
val state = domainStore.state.addCustomTokensState
val state = hubState
return Screen.UpdateTokenFields(listOf(
Network to state.screenState.network.copy(isEnabled = true),
Name to state.screenState.name.copy(isEnabled = true),
@ -250,39 +270,39 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
}
is OnTokenContractAddressChanged -> {
val field: TokenField = getField(ContractAddress, state)
field.data = action.value
field.data = action.contractAddress
updateFormState(state)
}
is OnTokenNetworkChanged -> {
val field: TokenBlockchainField = getField(Network, state)
field.data = action.value
field.data = action.blockchainNetwork
updateFormState(state)
}
is OnTokenNameChanged -> {
val field: TokenField = getField(Name, state)
field.data = action.value
field.data = action.tokenName
updateFormState(state)
}
is OnTokenSymbolChanged -> {
val field: TokenField = getField(Symbol, state)
field.data = action.value
field.data = action.tokenSymbol
updateFormState(state)
}
is OnTokenDecimalsChanged -> {
val field: TokenField = getField(Decimals, state)
field.data = action.value
field.data = action.tokenDecimals
updateFormState(state)
}
is OnTokenDerivationPathChanged -> {
val field: TokenDerivationPathField = getField(DerivationPath, state)
field.data = action.value
field.data = action.blockchainDerivationPath
updateFormState(state)
}
is Error.Add -> {
is FieldError.Add -> {
val newMap = state.formErrors.toMutableMap().apply { this[action.id] = action.error }
state.copy(formErrors = newMap)
}
is Error.Remove -> {
is FieldError.Remove -> {
val newMap = state.formErrors.toMutableMap().apply { remove(action.id) }
state.copy(formErrors = newMap)
}