Updated on 2026-08-14

This commit is contained in:
Tangem 2023-05-02 08:00:37 +08:00
parent de449519a3
commit 64ac275eb3
14 changed files with 518 additions and 66 deletions

View file

@ -35,12 +35,40 @@ sealed class ManageTokens(
params: Map<String, String> = mapOf(),
) : ManageTokens(event, params) {
class ScreenOpened : ManageTokens("Custom Token Screen Opened")
object ScreenOpened : ManageTokens(event = "Custom Token Screen Opened")
class TokenWasAdded(customCurrency: CustomCurrency) : ManageTokens(
// TODO("Get rid of strong binding (CustomCurrency)
open class TokenWasAdded(customCurrency: CustomCurrency) : ManageTokens(
event = "Custom Token Was Added",
params = convertToParam(customCurrency),
) {
data class Token(
val symbol: String,
val derivationPath: String?,
val blockchain: com.tangem.blockchain.common.Blockchain,
val contractAddress: String,
) : ManageTokens(
event = "Custom Token Was Added",
params = mapOf(
"Token" to symbol,
"Derivation Path" to derivationPath,
"Network Id" to blockchain.currency,
"Contract Address" to contractAddress,
).filterNotNull(),
)
data class Blockchain(
val derivationPath: String?,
val blockchain: com.tangem.blockchain.common.Blockchain,
) : ManageTokens(
event = "Custom Token Was Added",
params = mapOf(
"Token" to blockchain.currency,
"Derivation Path" to derivationPath,
).filterNotNull(),
)
companion object {
private fun convertToParam(customCurrency: CustomCurrency): Map<String, String> = with(customCurrency) {
return when (this) {
@ -48,6 +76,7 @@ sealed class ManageTokens(
"Token" to network.currency,
"Derivation Path" to derivationPath?.rawPath,
).filterNotNull()
is CustomCurrency.CustomToken -> mapOf(
"Token" to token.symbol,
"Derivation Path" to derivationPath?.rawPath,

View file

@ -0,0 +1,57 @@
package com.tangem.tap.features.customtoken.impl.data
import com.tangem.blockchain.common.Blockchain
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.tap.features.customtoken.impl.data.converters.FoundTokenConverter
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenRepository
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
/**
* Default implementation of custom token repository
*
* @property tangemTechApi TangemTech API
* @property dispatchers coroutine dispatchers provider
* @property reduxStateHolder redux state holder
*
[REDACTED_AUTHOR]
*/
class DefaultCustomTokenRepository(
private val tangemTechApi: TangemTechApi,
private val dispatchers: CoroutineDispatcherProvider,
private val reduxStateHolder: AppStateHolder,
) : CustomTokenRepository {
override suspend fun findToken(address: String, networkId: String?): FoundToken {
val supportedTokenNetworkIds = requireNotNull(reduxStateHolder.scanResponse?.card)
.supportedBlockchains()
.filter(Blockchain::canHandleTokens)
.map(Blockchain::toNetworkId)
return withContext(dispatchers.io) {
val foundCoin = tangemTechApi.getCoins(
contractAddress = address,
networkIds = networkId ?: supportedTokenNetworkIds.joinToString(separator = ","),
)
.coins.firstNotNullOfOrNull { coin ->
val networksWithTheSameAddress = coin.networks.filter { network ->
(network.contractAddress != null || network.decimalCount != null) &&
network.contractAddress?.equals(address, ignoreCase = true) == true &&
supportedTokenNetworkIds.contains(network.networkId)
}
if (networksWithTheSameAddress.isNotEmpty()) {
coin.copy(networks = networksWithTheSameAddress)
} else {
null
}
}
foundCoin?.let(FoundTokenConverter::convert) ?: error("Token not found")
}
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.tap.features.customtoken.impl.data.converters
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
import com.tangem.utils.converter.Converter
/**
* Converter between data model [CoinsResponse.Coin] and domain model [FoundToken]
*
[REDACTED_AUTHOR]
*/
object FoundTokenConverter : Converter<CoinsResponse.Coin, FoundToken> {
override fun convert(value: CoinsResponse.Coin): FoundToken {
return FoundToken(
id = value.id,
name = value.name,
symbol = value.symbol,
network = value.networks.firstOrNull()?.let { network ->
FoundToken.Network(
id = network.networkId,
address = requireNotNull(network.contractAddress),
decimalCount = requireNotNull(network.decimalCount).toString(),
)
} ?: error("Found token networks is empty"),
)
}
}

View file

@ -0,0 +1,41 @@
package com.tangem.tap.features.customtoken.impl.di
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.lib.crypto.DerivationManager
import com.tangem.tap.features.customtoken.impl.data.DefaultCustomTokenRepository
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
import com.tangem.tap.features.customtoken.impl.domain.DefaultCustomTokenInteractor
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
/**
[REDACTED_AUTHOR]
*/
@Module
@InstallIn(ViewModelComponent::class)
internal object CustomTokenInteractorModule {
@Provides
@ViewModelScoped
fun provideCustomTokenInteractor(
tangemTechApi: TangemTechApi,
appCoroutineDispatcherProvider: AppCoroutineDispatcherProvider,
reduxStateHolder: AppStateHolder,
derivationManager: DerivationManager,
): CustomTokenInteractor {
return DefaultCustomTokenInteractor(
featureRepository = DefaultCustomTokenRepository(
tangemTechApi = tangemTechApi,
dispatchers = appCoroutineDispatcherProvider,
reduxStateHolder = reduxStateHolder,
),
derivationManager = derivationManager,
reduxStateHolder = reduxStateHolder,
)
}
}

View file

@ -0,0 +1,19 @@
package com.tangem.tap.features.customtoken.impl.domain
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
import com.tangem.tap.features.wallet.models.Currency
/**
* Custom token interactor
*
[REDACTED_AUTHOR]
*/
interface CustomTokenInteractor {
/** Find token by [address] and [blockchain] */
suspend fun findToken(address: String, blockchain: Blockchain): FoundToken
/** Save token [currency] with contact address [address] */
suspend fun saveToken(currency: Currency, address: String)
}

View file

@ -0,0 +1,14 @@
package com.tangem.tap.features.customtoken.impl.domain
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
/**
* Custom token repository
*
[REDACTED_AUTHOR]
*/
interface CustomTokenRepository {
/** Find token by [address] and [networkId] */
suspend fun findToken(address: String, networkId: String?): FoundToken
}

View file

@ -0,0 +1,96 @@
package com.tangem.tap.features.customtoken.impl.domain
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.guard
import com.tangem.common.flatMap
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.lib.crypto.DerivationManager
import com.tangem.lib.crypto.models.Currency.NativeToken
import com.tangem.lib.crypto.models.Currency.NonNativeToken
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.scope
import com.tangem.tap.userWalletsListManager
import com.tangem.tap.walletCurrenciesManager
import kotlinx.coroutines.launch
import timber.log.Timber
/**
* Default implementation of custom token interactor
*
* @property featureRepository feature repository
* @property derivationManager derivation manager
* @property reduxStateHolder redux state holder
*
[REDACTED_AUTHOR]
*/
class DefaultCustomTokenInteractor(
private val featureRepository: CustomTokenRepository,
private val derivationManager: DerivationManager,
private val reduxStateHolder: AppStateHolder,
) : CustomTokenInteractor {
override suspend fun findToken(address: String, blockchain: Blockchain): FoundToken {
return featureRepository.findToken(
address = address,
networkId = if (blockchain != Blockchain.Unknown) blockchain.toNetworkId() else null,
)
}
override suspend fun saveToken(currency: Currency, address: String) {
val hasDerivation = derivationManager.hasDerivation(
networkId = currency.blockchain.toNetworkId(),
derivationPath = requireNotNull(currency.derivationPath),
)
if (!hasDerivation) {
derivationManager.deriveMissingBlockchains(
when (currency) {
is Currency.Blockchain -> NativeToken(
id = requireNotNull(currency.coinId),
name = currency.currencyName,
symbol = currency.currencySymbol,
networkId = currency.blockchain.toNetworkId(),
)
is Currency.Token -> NonNativeToken(
id = requireNotNull(currency.coinId),
name = currency.currencyName,
symbol = currency.currencySymbol,
networkId = currency.blockchain.toNetworkId(),
contractAddress = address,
decimalCount = currency.decimals,
)
},
)
submitAdd(
scanResponse = requireNotNull(reduxStateHolder.scanResponse),
currency = currency,
)
}
}
private fun submitAdd(scanResponse: ScanResponse, currency: Currency) {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to add currencies, no user wallet selected")
return
}
scope.launch {
userWalletsListManager.update(
userWalletId = selectedUserWallet.walletId,
update = { userWallet ->
userWallet.copy(scanResponse = scanResponse)
},
)
.flatMap { updatedUserWallet ->
walletCurrenciesManager.addCurrencies(
userWallet = updatedUserWallet,
currenciesToAdd = listOf(currency),
)
}
}
}
}

View file

@ -0,0 +1,23 @@
package com.tangem.tap.features.customtoken.impl.domain.models
/**
* Found token model
*
* @property id id
* @property name name
* @property symbol symbol
* @property network network
*
[REDACTED_AUTHOR]
*/
data class FoundToken(val id: String, val name: String, val symbol: String, val network: Network) {
/**
* Found token network
*
* @property id id
* @property address address
* @property decimalCount decimal count
*/
data class Network(val id: String, val address: String, val decimalCount: String)
}

View file

@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.fragment.app.Fragment
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.transition.TransitionInflater
@ -32,7 +33,9 @@ internal class AddCustomTokenFragment : Fragment() {
setContent {
isTransitionGroup = true
val viewModel = hiltViewModel<AddCustomTokenViewModel>()
val viewModel = hiltViewModel<AddCustomTokenViewModel>().apply {
LocalLifecycleOwner.current.lifecycle.addObserver(this)
}
TangemTheme {
AddCustomTokenScreen(stateHolder = viewModel.uiState)

View file

@ -3,13 +3,23 @@ package com.tangem.tap.features.customtoken.impl.presentation.viewmodels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.isSupportedInApp
import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.tap.common.analytics.events.ManageTokens
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TestTokenItem
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TokensCategoryBlock
@ -24,26 +34,37 @@ import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTok
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContactAddressValidator
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContractAddressValidatorResult
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
/**
* ViewModel for add custom token screen
*
* @param featureRouter feature router
* @property reduxStateHolder redux state holder
* @param featureRouter feature router
* @property featureInteractor feature interactor
* @property dispatchers coroutine dispatchers provider
* @property reduxStateHolder redux state holder
* @property analyticsEventHandler analytics event handler
*
[REDACTED_AUTHOR]
*/
@HiltViewModel
internal class AddCustomTokenViewModel @Inject constructor(
featureRouter: CustomTokenRouter,
private val featureInteractor: CustomTokenInteractor,
private val dispatchers: AppCoroutineDispatcherProvider,
private val reduxStateHolder: AppStateHolder,
) : ViewModel() {
private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel(), DefaultLifecycleObserver {
private val actionsHandler = ActionsHandler(featureRouter)
private val testActionsHandler = TestActionsHandler()
@ -52,6 +73,12 @@ internal class AddCustomTokenViewModel @Inject constructor(
var uiState by mutableStateOf(getInitialUiState())
private set
private var foundTokenId: String? = null
override fun onCreate(owner: LifecycleOwner) {
analyticsEventHandler.send(ManageTokens.CustomToken.ScreenOpened)
}
private fun getInitialUiState(): AddCustomTokenStateHolder {
return if (BuildConfig.TEST_ACTION_ENABLED) {
AddCustomTokenStateHolder.TestContent(
@ -72,7 +99,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
TokensCategoryBlock(name = "Common", items = COMMON_TOKENS),
TokensCategoryBlock(name = "Solana", items = SOLANA_TOKENS),
),
onTestTokenClick = testActionsHandler::onTestTokenClick,
onTestTokenClick = actionsHandler::onContactAddressValueChange,
),
)
} else {
@ -262,24 +289,66 @@ internal class AddCustomTokenViewModel @Inject constructor(
}
fun onAddCustomTokenClick() {
// TODO("[REDACTED_TASK_KEY] Add processing")
if (uiState.form.networkSelectorField.selectedItem.blockchain != Blockchain.Unknown) {
val selectedNetwork = uiState.form.networkSelectorField.selectedItem.blockchain
val currency = if (isAnyTokenFieldsFilled() || isAllTokenFieldsFilled()) {
Currency.Token(
token = Token(
name = uiState.form.tokenNameInputField.value,
symbol = uiState.form.tokenSymbolInputField.value,
contractAddress = uiState.form.contractAddressInputField.value,
decimals = uiState.form.decimalsInputField.value.toInt(),
id = foundTokenId,
),
blockchain = selectedNetwork,
derivationPath = getDerivationPath(
mainNetwork = selectedNetwork,
derivationNetwork = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain,
derivationStyle = reduxStateHolder.scanResponse?.card?.derivationStyle,
)?.rawPath,
)
} else {
Currency.Blockchain(
blockchain = selectedNetwork,
derivationPath = getDerivationPath(
mainNetwork = selectedNetwork,
derivationNetwork = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain,
derivationStyle = reduxStateHolder.scanResponse?.card?.derivationStyle,
)?.rawPath,
)
}
sendOnAddTokenButtonClick(currency = currency, address = uiState.form.contractAddressInputField.value)
viewModelScope.launch(dispatchers.io) {
featureInteractor.saveToken(
currency = currency,
address = uiState.form.contractAddressInputField.value,
)
}
}
}
fun onContactAddressValueChange(enteredValue: String) {
with(uiState.form) {
val selectedNetwork = networkSelectorField.selectedItem.blockchain
val isValid = ContactAddressValidator.validate(
address = enteredValue,
blockchain = networkSelectorField.selectedItem.blockchain,
blockchain = selectedNetwork,
)
when (isValid) {
is ContractAddressValidatorResult.Success -> {
uiState = uiState.copySealed(
form = uiState.form.copy(
contractAddressInputField = contractAddressInputField.copy(isError = false),
contractAddressInputField = contractAddressInputField.copy(
isError = false,
isLoading = true,
),
),
)
// TODO("[REDACTED_TASK_KEY] Add loading custom token information")
updateForm(address = enteredValue, selectedNetwork = selectedNetwork)
}
is ContractAddressValidatorResult.Error -> {
@ -330,17 +399,99 @@ internal class AddCustomTokenViewModel @Inject constructor(
// TODO("[REDACTED_TASK_KEY] Update floating button")
}
private fun isAnotherTokenFieldsFilled(): Boolean {
private fun getDerivationPath(
mainNetwork: Blockchain,
derivationNetwork: Blockchain?,
derivationStyle: DerivationStyle?,
): DerivationPath? {
val network = if (derivationNetwork == Blockchain.Unknown) mainNetwork else derivationNetwork
return network?.derivationPath(
style = if (derivationNetwork == Blockchain.Unknown) derivationStyle else DerivationStyle.LEGACY,
)
}
private fun sendOnAddTokenButtonClick(currency: Currency, address: String) {
when (currency) {
is Currency.Blockchain -> {
analyticsEventHandler.send(
ManageTokens.CustomToken.TokenWasAdded.Blockchain(
derivationPath = currency.derivationPath,
blockchain = currency.blockchain,
),
)
}
is Currency.Token -> {
analyticsEventHandler.send(
ManageTokens.CustomToken.TokenWasAdded.Token(
symbol = currency.currencySymbol,
derivationPath = currency.derivationPath,
blockchain = currency.blockchain,
contractAddress = address,
),
)
}
}
}
private fun isAnyTokenFieldsFilled(): Boolean {
return with(uiState.form) {
contractAddressInputField.value.isNotEmpty() || tokenNameInputField.value.isNotEmpty() ||
tokenSymbolInputField.value.isNotEmpty() || decimalsInputField.value.isNotEmpty()
}
}
private fun isAllTokenFieldsFilled(): Boolean {
return with(uiState.form) {
contractAddressInputField.value.isNotEmpty() && tokenNameInputField.value.isNotEmpty() &&
tokenSymbolInputField.value.isNotEmpty() && decimalsInputField.value.isNotEmpty()
}
}
private fun updateForm(address: String, selectedNetwork: Blockchain) {
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
featureInteractor.findToken(address = address, blockchain = selectedNetwork)
}
.onSuccess { token ->
with(uiState.form) {
uiState = uiState.copySealed(
form = copy(
contractAddressInputField = contractAddressInputField.copy(isLoading = false),
networkSelectorField = networkSelectorField.copy(
selectedItem = createNetworkSelectorItem(
blockchain = Blockchain.fromNetworkId(token.network.id)
?: Blockchain.Unknown,
),
),
tokenNameInputField = tokenNameInputField.copy(
value = token.name,
isEnabled = false,
),
tokenSymbolInputField = tokenSymbolInputField.copy(
value = token.symbol,
isEnabled = false,
),
decimalsInputField = decimalsInputField.copy(
value = token.network.decimalCount,
isEnabled = false,
),
),
)
}
}
.onFailure {
foundTokenId = null
Timber.e(it)
}
}
}
private fun handleContractAddressErrorValidation(type: AddCustomTokenError) {
with(uiState.form) {
val isNetworkSelectorFilled = networkSelectorField.selectedItem.blockchain != Blockchain.Unknown
val isAnotherTokenFieldsFilled = isAnotherTokenFieldsFilled()
val isAnotherTokenFieldsFilled = isAnyTokenFieldsFilled()
when {
isNetworkSelectorFilled && type == AddCustomTokenError.InvalidContractAddress -> {
@ -432,11 +583,6 @@ internal class AddCustomTokenViewModel @Inject constructor(
)
}
}
@Suppress("UnusedPrivateMember")
fun onTestTokenClick(address: String) {
// TODO("[REDACTED_TASK_KEY] Add processing")
}
}
private companion object {

View file

@ -32,7 +32,7 @@ class AddCustomTokenFragment : BaseStoreFragment(R.layout.view_compose_fragment)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(ManageTokens.CustomToken.ScreenOpened())
Analytics.send(ManageTokens.CustomToken.ScreenOpened)
}
override fun subscribeToStore() {

View file

@ -14,41 +14,36 @@ class AddCustomTokenService(
private val supportedTokenNetworkIds: List<String>,
) {
suspend fun findToken(
contractAddress: String,
networkId: String? = null,
active: Boolean? = null,
): List<CoinsResponse.Coin> = withContext(dispatchers.io) {
runCatching {
tangemTechApi.getCoins(
contractAddress = contractAddress,
networkIds = selectNetworksForSearch(networkId),
active = active,
)
suspend fun findToken(contractAddress: String, networkId: String?): List<CoinsResponse.Coin> {
return withContext(dispatchers.io) {
runCatching {
tangemTechApi.getCoins(
contractAddress = contractAddress,
networkIds = selectNetworksForSearch(networkId),
)
}
.fold(
onSuccess = { response ->
var coinsList = mutableListOf<CoinsResponse.Coin>()
response.coins.forEach { coin ->
val networksWithTheSameAddress = coin.networks
.filter { it.contractAddress != null || it.decimalCount != null }
.filter { it.contractAddress?.equals(contractAddress, ignoreCase = true) == true }
.filter { supportedTokenNetworkIds.contains(it.networkId) }
if (networksWithTheSameAddress.isNotEmpty()) {
val newToken = coin.copy(networks = networksWithTheSameAddress)
coinsList.add(newToken)
}
}
if (coinsList.size > 1) {
// https://tangem.slack.com/archives/GMXC6PP71/p1649672562078679
coinsList = mutableListOf(coinsList[0])
}
coinsList
},
onFailure = { emptyList() },
)
}
.onSuccess { response ->
var coinsList = mutableListOf<CoinsResponse.Coin>()
response.coins.forEach { coin ->
val networksWithTheSameAddress = coin.networks
.filter { it.contractAddress != null || it.decimalCount != null }
.filter { it.contractAddress?.equals(contractAddress, ignoreCase = true) == true }
.filter { supportedTokenNetworkIds.contains(it.networkId) }
if (networksWithTheSameAddress.isNotEmpty()) {
val newToken = coin.copy(networks = networksWithTheSameAddress)
coinsList.add(newToken)
}
}
if (coinsList.size > 1) {
// https://tangem.slack.com/archives/GMXC6PP71/p1649672562078679
coinsList = mutableListOf(coinsList[0])
}
return@withContext coinsList
}
.onFailure {
return@withContext emptyList()
}
error("Unreachable code because runCatching must return result")
}
private fun selectNetworksForSearch(networkId: String?): String {

View file

@ -342,7 +342,6 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
}
}
@Suppress("MagicNumber")
private suspend fun requestInfoAboutToken(contractAddress: String): List<CoinsResponse.Coin> {
val tangemTechServiceManager = requireNotNull(hubState.tangemTechServiceManager)
dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = true))))
@ -354,7 +353,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
// simulate loading effect. It would be better if the delay would only run if tokenManager.checkAddress()
// got the result faster than 500ms and the delay would only be the difference between them.
delay(500)
delay(timeMillis = 500)
val result = tangemTechServiceManager.findToken(contractAddress, selectedNetworkId)
@ -450,7 +449,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
enableDisableTokenDetailFields(false)
}
private suspend fun enableDisableTokenDetailFields(isEnabled: Boolean = true) {
private suspend fun enableDisableTokenDetailFields(isEnabled: Boolean) {
val state = hubState
val action = Screen.UpdateTokenFields(
listOf(
@ -590,17 +589,18 @@ private class AddCustomTokenReducer(
is OnCreate -> {
val card = requireNotNull(globalState.scanResponse?.card)
val supportedTokenNetworkIds = card.supportedBlockchains()
.filter { it.canHandleTokens() }
.map { it.toNetworkId() }
.filter(Blockchain::canHandleTokens)
.map(Blockchain::toNetworkId)
val tangemTechServiceManager = AddCustomTokenService(
tangemTechApi = globalState.networkServices.tangemTechService.api,
dispatchers = AppCoroutineDispatcherProvider(),
supportedTokenNetworkIds = supportedTokenNetworkIds,
)
val form = Form(AddCustomTokenState.createFormFields(card, CustomTokenType.Blockchain))
state.copy(
cardDerivationStyle = card.derivationStyle,
form = form,
form = Form(AddCustomTokenState.createFormFields(card, CustomTokenType.Blockchain)),
tangemTechServiceManager = tangemTechServiceManager,
screenState = createInitialScreenState(card.settings.isHDWalletAllowed),
)

View file

@ -64,8 +64,6 @@ data class AddCustomTokenState(
fun getError(id: FieldId): AddCustomTokenError? = formErrors[id]
fun hasError(id: FieldId): Boolean = formErrors[id] != null
inline fun <reified T> visitDataConverter(converter: FieldDataConverter<T>): T {
form.visitDataConverter(converter)
return converter.getConvertedData()
@ -116,9 +114,12 @@ data class AddCustomTokenState(
return network.data.value != Blockchain.Unknown
}
fun getCustomTokenType(): CustomTokenType = when {
tokensAnyFieldsIsFilled() || tokensFieldsIsFilled() -> CustomTokenType.Token
else -> CustomTokenType.Blockchain
fun getCustomTokenType(): CustomTokenType {
return if (tokensAnyFieldsIsFilled() || tokensFieldsIsFilled()) {
CustomTokenType.Token
} else {
CustomTokenType.Blockchain
}
}
fun gatherUserToken(): CustomCurrency.CustomToken? = try {
@ -264,7 +265,7 @@ data class AddCustomTokenState(
private var builder: StringBuilder = StringBuilder()
override fun convert(action: Action, stateHolder: DomainState): String? {
val action = action as? AddCustomTokenAction ?: return null
if (action !is AddCustomTokenAction) return null
val state = stateHolder.addCustomTokensState
val fieldConverter =