diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 86d8393672..414edde2e5 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -26,6 +26,8 @@ import com.tangem.tap.features.wallet.ui.WalletDetailsFragment import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.wallet.R +private class Navigation + fun FragmentActivity.openFragment( screen: AppScreen, addToBackstack: Boolean, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt index 8de31c6e3e..1d0ee9869e 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt @@ -71,7 +71,7 @@ fun TokenNetworkView(screenFieldData: ScreenFieldData, state: AddCustomTokenStat itemList = networkField.itemList, selectedItem = networkField.data, isEnabled = screenFieldData.viewState.isEnabled, - textFieldConverter = { state.convertBlockchainName(it, notSelected) }, + textFieldConverter = { state.blockchainToName(it) ?: notSelected }, ) { domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(it))) } SpacerH8() } @@ -123,11 +123,11 @@ fun TokenDerivationPathView(screenFieldData: ScreenFieldData, state: AddCustomTo itemList = networkField.itemList, selectedItem = networkField.data, isEnabled = screenFieldData.viewState.isEnabled, - textFieldConverter = { state.convertBlockchainName(it, notSelected) }, + textFieldConverter = { state.blockchainToName(it) ?: notSelected }, dropdownItemView = { blockchain -> - val derivationPathLabel = state.convertDerivationPathLabel(blockchain, notSelected) - val blockchainName = state.convertBlockchainName(blockchain, notSelected) - TitleSubtitle(derivationPathLabel, blockchainName) + val derivationPathName = state.blockchainToName(blockchain, true) ?: notSelected + val blockchainName = state.blockchainToName(blockchain) ?: notSelected + TitleSubtitle(derivationPathName, blockchainName) } ) { domainStore.dispatch(AddCustomTokenAction.OnTokenDerivationPathChanged(Field.Data(it))) } SpacerH8() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 4ba47f905f..30aa052c99 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.tokens.redux import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationParams +import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.Token import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve @@ -13,12 +14,12 @@ 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.CustomCurrency 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.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState @@ -28,19 +29,16 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.tokens.BlockchainNetwork +import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Middleware -import timber.log.Timber class TokensMiddleware { - val tokensMiddleware: Middleware = { dispatch, state -> + val tokensMiddleware: Middleware = { _, _ -> { next -> { action -> when (action) { @@ -58,8 +56,10 @@ class TokensMiddleware { val isTestcard = scanResponse?.card?.isTestCard ?: false scope.launch { - val currencies = async { currenciesRepository.getSupportedTokens(isTestcard) - .filter(action.supportedBlockchains?.toSet()) } + val currencies = async { + currenciesRepository.getSupportedTokens(isTestcard) + .filter(action.supportedBlockchains?.toSet()) + } val delay = async { delay(600) } delay.await() store.dispatchOnMain(TokensAction.LoadCurrencies.Success(currencies.await())) @@ -69,6 +69,7 @@ class TokensMiddleware { private fun handleSaveChanges(action: TokensAction.SaveChanges) { val scanResponse = store.state.globalState.scanResponse ?: return + //TODO: bad things happens. val currentTokens = store.state.tokensState.addedWallets.toTokens() val currentBlockchains = store.state.tokensState.addedWallets.toBlockchains( store.state.tokensState.derivationStyle @@ -83,44 +84,42 @@ class TokensMiddleware { removeCurrenciesIfNeeded(blockchainsToRemove, tokensToRemove) if (tokensToAdd.isEmpty() && blockchainsToAdd.isEmpty()) { + store.dispatchDebugErrorNotification("Nothing to save") store.dispatch(NavigationAction.PopBackTo()) return } + val derivationStyle = scanResponse.card.derivationStyle + val currencyList = blockchainsToAdd.map { + Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath) + } + tokensToAdd.map { + Currency.Token(it.token, it.blockchain, it.blockchain.derivationPath(derivationStyle)?.rawPath) + } if (scanResponse.supportsHdWallet()) { - deriveMissingBlockchains(scanResponse, blockchainsToAdd, tokensToAdd) + deriveMissingBlockchains(scanResponse, currencyList) { + submitAdd(it, currencyList) + store.dispatchOnMain(NavigationAction.PopBackTo()) + } } else { - submitAdd(blockchainsToAdd, tokensToAdd, scanResponse) - store.dispatch(NavigationAction.PopBackTo()) + submitAdd(scanResponse, currencyList) + store.dispatchOnMain(NavigationAction.PopBackTo()) } } - 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, - tokens: List + currencyList: List, + onSuccess: (ScanResponse) -> Unit, ) { val derivationDataList = listOfNotNull( - getDerivations(EllipticCurve.Secp256k1, scanResponse, blockchains, tokens), - getDerivations(EllipticCurve.Ed25519, scanResponse, blockchains, tokens) + getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList), + getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList) ) - val derivations = derivationDataList.map { it.derivations }.toMap() + val derivations = derivationDataList.associate { it.derivations } + if (derivations.isEmpty()) { + onSuccess(scanResponse) + return + } scope.launch { val result = tangemSdkManager.derivePublicKeys( @@ -145,10 +144,8 @@ class TokensMiddleware { derivedKeys = updatedDerivedKeys ) store.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) - submitAdd(blockchains, tokens, updatedScanResponse) - delay(DELAY_SDK_DIALOG_CLOSE) - store.dispatchOnMain(NavigationAction.PopBackTo()) + onSuccess(updatedScanResponse) } is CompletionResult.Failure -> { store.dispatchErrorNotification(TapError.CustomError("Error adding tokens")) @@ -160,20 +157,31 @@ class TokensMiddleware { private fun getDerivations( curve: EllipticCurve, scanResponse: ScanResponse, - blockchains: List, - tokens: List + currencyList: List, ): DerivationData? { val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null - val derivationPathsCandidates = (blockchains + tokens.map { it.blockchain }).distinct() - .mapNotNull { it.derivationPath(scanResponse.card.derivationStyle) } + val manageTokensCandidates = currencyList.map { it.blockchain }.distinct().filter { + it.getSupportedCurves().contains(curve) + }.mapNotNull { + it.derivationPath(scanResponse.card.derivationStyle) + } + + val customTokensCandidates = currencyList.filter { + it.blockchain.getSupportedCurves().contains(curve) + }.mapNotNull { it.derivationPath }.map { DerivationPath(it) } + + val bothCandidates = (manageTokensCandidates + customTokensCandidates).distinct() + if (bothCandidates.isEmpty()) return null val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() val alreadyDerivedKeys: ExtendedPublicKeysMap = scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap()) val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList() - val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) } + val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) } + if (toDerive.isEmpty()) return null + return DerivationData( derivations = mapKeyOfWalletPublicKey to toDerive, alreadyDerivedKeys = alreadyDerivedKeys, @@ -188,20 +196,48 @@ class TokensMiddleware { ) private fun submitAdd( - blockchains: List, tokens: List, scanResponse: ScanResponse, + scanResponse: ScanResponse, + currencyList: List, ) { val factory = store.state.globalState.tapWalletManager.walletManagerFactory + val derivationStyle = scanResponse.card.derivationStyle - (blockchains.mapNotNull { - val walletManager = factory.makeWalletManagerForApp( - scanResponse, it, - scanResponse.card.derivationStyle?.let { DerivationParams.Default(it) } - ) ?: return@mapNotNull null - WalletAction.MultiWallet.AddBlockchain(BlockchainNetwork.fromWalletManager(walletManager), walletManager) - } + tokens.map { - val blockchainNetwork = BlockchainNetwork(it.blockchain, scanResponse.card) - WalletAction.MultiWallet.AddToken(it.token, blockchainNetwork) - }).forEach { store.dispatchOnMain(it) } + val addActions = currencyList.mapNotNull { currency -> + when (currency) { + is Currency.Blockchain -> { + val derivationPath = currency.derivationPath?.let { DerivationPath(it) } + + val derivationParams = if (derivationStyle == null) { + null + } else { + if (derivationPath == null) { + DerivationParams.Default(derivationStyle) + } else { + when (derivationStyle) { + DerivationStyle.LEGACY -> DerivationParams.Custom(derivationPath) + DerivationStyle.NEW -> DerivationParams.Default(derivationStyle) + } + } + } + + val walletManager = factory.makeWalletManagerForApp( + scanResponse = scanResponse, + blockchain = currency.blockchain, + derivationParams = derivationParams + ) ?: return@mapNotNull null + val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) + WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, walletManager) + } + is Currency.Token -> { + val rawDerivationPath = currency.derivationPath + ?: currency.blockchain.derivationPath(derivationStyle)?.rawPath + + val blockchainNetwork = BlockchainNetwork(currency.blockchain, rawDerivationPath, emptyList()) + WalletAction.MultiWallet.AddToken(currency.token, blockchainNetwork) + } + } + } + addActions.forEach { store.dispatchOnMain(it) } } private fun removeCurrenciesIfNeeded(blockchains: List, tokens: List) { @@ -221,4 +257,45 @@ class TokensMiddleware { } } + private fun isNeedToDerive(scanResponse: ScanResponse, currency: Currency): Boolean { + return currency.derivationPath?.let { + !scanResponse.hasDerivation(currency.blockchain, it) + } ?: false + } + + private fun handleAddingCustomToken(action: TokensAction.PrepareAndNavigateToAddCustomToken) { + val onAddCustomToken = fun(customCurrency: CustomCurrency) { + val scanResponse = store.state.globalState.scanResponse ?: return + + fun submitAndPopBack(scanResponse: ScanResponse, currencyList: List) { + submitAdd(scanResponse, currencyList) + // pop from the AddCustomTokenScreen + store.dispatchOnMain(NavigationAction.PopBackTo()) + store.dispatchOnMain(NavigationAction.PopBackTo()) + } + + val currency = Currency.fromCustomCurrency(customCurrency) + val isNeedToDerive = isNeedToDerive(scanResponse, currency) + val currencyList = listOf(currency) + if (isNeedToDerive) { + deriveMissingBlockchains(scanResponse, currencyList) { + submitAndPopBack(it, currencyList) + } + } else { + submitAndPopBack(scanResponse, currencyList) + } + } + + val addedCurrencies = store.state.walletState.wallets.map { walletStore -> + walletStore.walletsData.map { walletData -> walletData.currency } + }.flatten().map { + when (it) { + is Currency.Blockchain -> DomainWrapped.Currency.Blockchain(it.blockchain, it.derivationPath) + is Currency.Token -> DomainWrapped.Currency.Token(it.token, it.blockchain, it.derivationPath) + } + } + domainStore.dispatch(AddCustomTokenAction.Init.SetAddedCurrencies(addedCurrencies)) + domainStore.dispatch(AddCustomTokenAction.Init.SetOnAddTokenCallback(onAddCustomToken)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken)) + } } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/CompleteData.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/CompleteData.kt deleted file mode 100644 index 90b11472d2..0000000000 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/CompleteData.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.tangem.domain.features.addCustomToken - -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token -import com.tangem.domain.common.form.BaseFieldDataConverter -import com.tangem.domain.common.form.FieldId - -/** -[REDACTED_AUTHOR] - */ -enum class CompleteDataType { - Blockchain, Token -} - -sealed class CompleteData() { - - class CustomBlockchain( - val network: Blockchain, - val derivationPath: String? - ) : CompleteData() { - - class Converter : BaseFieldDataConverter() { - override fun getConvertedData(): CustomBlockchain { - val network = collectedData[CustomTokenFieldId.Network] as Blockchain - val derivationPath = collectedData[CustomTokenFieldId.DerivationPath] as? String - return CustomBlockchain(network, derivationPath) - } - - override fun getIdToCollect(): List = listOf(CustomTokenFieldId.Network, CustomTokenFieldId.DerivationPath) - } - } - - class CustomToken( - val token: Token, - val network: Blockchain, - val derivationPath: String?, - ) : CompleteData() { - - class Converter(val tokenId: String?) : BaseFieldDataConverter() { - - override fun getConvertedData(): CustomToken { - val token = Token( - name = collectedData[CustomTokenFieldId.Name] as String, - symbol = collectedData[CustomTokenFieldId.Symbol] as String, - contractAddress = collectedData[CustomTokenFieldId.ContractAddress] as String, - decimals = (collectedData[CustomTokenFieldId.Decimals] as String).toInt(), - id = tokenId, - ) - return CustomToken( - token, - collectedData[CustomTokenFieldId.Network] as Blockchain, - collectedData[CustomTokenFieldId.DerivationPath] as? String, - ) - } - - override fun getIdToCollect(): List = CustomTokenFieldId.values().toList() - } - } -} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/CustomCurrency.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/CustomCurrency.kt new file mode 100644 index 0000000000..67f62551eb --- /dev/null +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/CustomCurrency.kt @@ -0,0 +1,83 @@ +package com.tangem.domain.features.addCustomToken + +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.Token +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.domain.common.form.BaseFieldDataConverter +import com.tangem.domain.common.form.FieldId +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState + +/** +[REDACTED_AUTHOR] + */ +enum class CompleteDataType { + Blockchain, Token +} + +sealed class CustomCurrency( + val network: Blockchain, + val derivationPath: DerivationPath?, +) { + + class CustomBlockchain( + network: Blockchain, + derivationPath: DerivationPath? + ) : CustomCurrency(network, derivationPath) { + + class Converter( + private val derivationStyle: DerivationStyle? + ) : BaseFieldDataConverter() { + override fun getConvertedData(): CustomBlockchain { + val mainNetwork = collectedData[CustomTokenFieldId.Network] as Blockchain + val derivationPathNetwork = collectedData[CustomTokenFieldId.DerivationPath] as Blockchain + val derivationPath = AddCustomTokenState.getDerivationPath( + mainNetwork, + derivationPathNetwork, + derivationStyle + ) + return CustomBlockchain(mainNetwork, derivationPath) + } + + override fun getIdToCollect(): List = listOf(CustomTokenFieldId.Network, CustomTokenFieldId.DerivationPath) + } + } + + class CustomToken( + val token: Token, + network: Blockchain, + derivationPath: DerivationPath? + ) : CustomCurrency(network, derivationPath) { + + class Converter( + private val tokenId: String?, + private val derivationStyle: DerivationStyle? + ) : BaseFieldDataConverter() { + + override fun getConvertedData(): CustomToken { + val mainNetwork = collectedData[CustomTokenFieldId.Network] as Blockchain + val derivationPathNetwork = collectedData[CustomTokenFieldId.DerivationPath] as Blockchain + val derivationPath = AddCustomTokenState.getDerivationPath( + mainNetwork, + derivationPathNetwork, + derivationStyle + ) + + val token = Token( + name = collectedData[CustomTokenFieldId.Name] as String, + symbol = collectedData[CustomTokenFieldId.Symbol] as String, + contractAddress = collectedData[CustomTokenFieldId.ContractAddress] as String, + decimals = (collectedData[CustomTokenFieldId.Decimals] as String).toInt(), + id = tokenId, + ) + return CustomToken( + token, + collectedData[CustomTokenFieldId.Network] as Blockchain, + derivationPath, + ) + } + + override fun getIdToCollect(): List = CustomTokenFieldId.values().toList() + } + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt index 0b541fb309..dc832d367a 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt @@ -2,13 +2,13 @@ package com.tangem.domain.features.addCustomToken.redux import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle +import com.tangem.domain.DomainWrapped import com.tangem.domain.common.form.Field import com.tangem.domain.common.form.FieldId import com.tangem.domain.features.addCustomToken.AddCustomTokenError import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning -import com.tangem.domain.features.addCustomToken.CompleteData +import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.domain.features.addCustomToken.CustomTokenFieldId -import com.tangem.network.api.tangemTech.Coins import org.rekotlin.Action /** @@ -16,9 +16,8 @@ import org.rekotlin.Action */ sealed class AddCustomTokenAction : Action { sealed class Init : AddCustomTokenAction() { - data class SetAddedCurrencies(val addedCurrencies: AddedCurrencies) : AddCustomTokenAction() - - data class SetOnAddTokenCallback(val callback: (CompleteData) -> Unit) : AddCustomTokenAction() + data class SetAddedCurrencies(val addedCurrencies: List) : AddCustomTokenAction() + data class SetOnAddTokenCallback(val callback: (CustomCurrency) -> Unit) : AddCustomTokenAction() } object OnCreate : AddCustomTokenAction() { @@ -36,22 +35,16 @@ sealed class AddCustomTokenAction : Action { data class OnTokenDecimalsChanged(val tokenDecimals: Field.Data) : AddCustomTokenAction() object OnAddCustomTokenClicked : AddCustomTokenAction() + data class SetFoundTokenId(val id: 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 FieldError : AddCustomTokenAction() { data class Add(val id: CustomTokenFieldId, val error: AddCustomTokenError) : FieldError() data class Remove(val id: CustomTokenFieldId) : FieldError() } - data class SetTokenId(val id: String) : AddCustomTokenAction() - // warnings sealed class Warning : AddCustomTokenAction() { data class Add(val warnings: Set) : Warning() diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt index c1e43959f0..a127b43776 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt @@ -3,9 +3,13 @@ package com.tangem.domain.features.addCustomToken.redux import android.webkit.ValueCallback import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle +import com.tangem.common.extensions.guard +import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.DomainDialog import com.tangem.domain.DomainException +import com.tangem.domain.DomainWrapped +import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.form.* @@ -19,7 +23,7 @@ import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.global.DomainGlobalAction import com.tangem.network.api.tangemTech.Coins import com.tangem.network.api.tangemTech.TangemTechService -import kotlinx.coroutines.cancel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action @@ -39,17 +43,6 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT return storeState.copy(addCustomTokensState = newHubState) } - private val contractAddressValidator: TokenContractAddressValidator - get() = hubState.getValidator(ContractAddress) - private val nameValidator: TokenNameValidator - get() = hubState.getValidator(Name) - private val symbolValidator: TokenSymbolValidator - get() = hubState.getValidator(Symbol) - private val decimalsValidator: TokenDecimalsValidator - get() = hubState.getValidator(Decimals) - val networkValidator: TokenNetworkValidator - get() = hubState.getValidator(Network) - override suspend fun handleAction( action: Action, storeState: DomainState, @@ -58,29 +51,29 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT if (action !is AddCustomTokenAction) return when (action) { - is Init.SetAddedCurrencies -> {} - is Init.SetOnAddTokenCallback -> {} is OnCreate -> { -// hubState.addedCurrencies.guard { -// return throwUnAppropriateInitialization("addedTokens") -// } + hubState.appSavedCurrencies.guard { + return throwUnAppropriateInitialization("addedTokens") + } } - is OnDestroy -> hubScope.cancel() + is OnDestroy -> cancelAll() is OnTokenContractAddressChanged -> { + updateAddButton() + val address = action.contractAddress.value - when (val error = ContractAddress.validate(address)) { + when (val error = ContractAddress.validateValue(address)) { null -> { ContractAddress.removeError() - dispatchOnMain(unlockTokenFieldsAction()) + unlockTokenFields() } AddCustomTokenError.FieldIsEmpty -> { ContractAddress.removeError() - dispatchOnMain(lockTokenFieldsAction()) + lockTokenFields() return } AddCustomTokenError.InvalidContractAddress -> { ContractAddress.addError(error) - dispatchOnMain(unlockTokenFieldsAction()) + unlockTokenFields() return } else -> {} @@ -88,75 +81,89 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT if (!action.contractAddress.isUserInput) return - manageTokenChanges(requestInfoAboutContractAddress(address)) + manageFoundTokenChanges(requestInfoAboutToken(address)) } is OnTokenNetworkChanged -> { if (!action.blockchainNetwork.isUserInput) return + updateAddButton() + // check the only ContractAddress without any side effects val contractAddress = ContractAddress.getFieldValue() - val error = ContractAddress.validate(contractAddress) - if (error == null) { - manageTokenChanges(requestInfoAboutContractAddress(contractAddress)) - } else { - + val error = ContractAddress.validateValue(contractAddress) + if (contractAddress.isNotEmpty() && error == null) { + manageFoundTokenChanges(requestInfoAboutToken(contractAddress)) } } is OnTokenNameChanged -> { - Name.addOrRemoveError(Name.validate(action.tokenName.value)) + Name.validateField(action.tokenName.value) + updateAddButton() } is OnTokenSymbolChanged -> { - Symbol.addOrRemoveError(Symbol.validate(action.tokenSymbol.value)) + Symbol.validateField(action.tokenSymbol.value) + updateAddButton() } is OnTokenDecimalsChanged -> { - Decimals.addOrRemoveError(Decimals.validate(action.tokenDecimals.value)) + Decimals.validateField(action.tokenDecimals.value) + updateAddButton() } - is ClearTokenFields -> { - Name.setFieldValue(Field.Data("", false)) - Symbol.setFieldValue(Field.Data("", false)) - Decimals.setFieldValue(Field.Data("", false)) - dispatchOnMain(UpdateForm(hubState)) - } - is FillTokenFields -> { - val token = action.token - val contract = action.contract - val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: Blockchain.Unknown - - Network.setFieldValue(Field.Data(blockchain, false)) - Name.setFieldValue(Field.Data(token.name, false)) - Symbol.setFieldValue(Field.Data(token.symbol, false)) - Decimals.setFieldValue(Field.Data(contract.decimalCount.toString(), false)) - - dispatchOnMain(UpdateForm(hubState)) + is OnTokenDerivationPathChanged -> { + DerivationPath.validateField(action.blockchainDerivationPath.value) + val alreadyAdded = isTokenPersistIntoAppSavedTokensList( + selectedDerivationBlockchain = DerivationPath.getFieldValue() + ) + if (alreadyAdded) { + dispatchOnMain(Warning.Add(setOf(AddCustomTokenWarning.TokenAlreadyAdded))) + } else { + dispatchOnMain(Warning.Remove(setOf(AddCustomTokenWarning.TokenAlreadyAdded))) + } + updateAddButton() } is OnAddCustomTokenClicked -> { -// if (hubState.allFieldsIsEmpty()) { - dispatchOnMain( - DomainGlobalAction.ShowDialog(DomainDialog.DialogError( - AddCustomTokenError.InvalidDerivationPath - ))) - return -// } - when { - !hubState.customTokensFieldsIsEmpty() && !hubState.networkIsEmpty() -> { - hubState.getCompleteData(CompleteDataType.Token) + val state = hubState + val completeData = when { + state.tokensFieldsIsFilled() && state.networkIsSelected() -> { + state.gatherUserToken() + } + !state.tokensFieldsIsFilled() && state.networkIsSelected() -> { + state.gatherBlockchain() + } + else -> null + } + + if (completeData == null) { + // normally it can't be, because the AddButton must be blocked + } else { + hubScope.launch(Dispatchers.Main) { + state.onTokenAddCallback?.invoke(completeData) } -// !hubState.customTokensFieldsIsEmpty() && -> { -// } } -// if (true) { -// dispatchOnMain(NavigationAction.PopBackTo()) -// hubState.onTokenAddCallback?.invoke() -// } } else -> {} } } - private suspend fun requestInfoAboutContractAddress( + private suspend fun updateAddButton() { + val state = hubState + if (state.warnings.contains(AddCustomTokenWarning.TokenAlreadyAdded)) { + lockAddButton() + return + } + when { + // token + state.tokensOneFieldsIsFilled() -> lockAddButton() + // token + state.tokensFieldsIsFilled() && state.networkIsSelected() -> unlockAddButton() + // blockchain + else -> if (state.networkIsSelected()) unlockAddButton() else lockAddButton() + } + } + + private suspend fun requestInfoAboutToken( contractAddress: String, ): List { val tangemTechServiceManager = requireNotNull(hubState.tangemTechServiceManager) dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = true)))) + val field = hubState.getField(Network) val selectedNetworkId: String? = field.data.value.let { if (it == Blockchain.Unknown) null else it @@ -179,67 +186,72 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT return result } - private suspend fun manageTokenChanges(foundTokens: List) { - val toAddWarnings = mutableSetOf() - val toRemoveWarnings = mutableSetOf() + private suspend fun manageFoundTokenChanges(foundTokens: List) { + val warningsAdd = mutableSetOf() + val warningsRemove = mutableSetOf() when { foundTokens.isEmpty() -> { - toAddWarnings.add(AddCustomTokenWarning.PotentialScamToken) - toRemoveWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded) - dispatchOnMain(ClearTokenFields) - dispatchOnMain(unlockTokenFieldsAction()) + // token not found - it's completely custom + dispatchOnMain(SetFoundTokenId(null)) + warningsAdd.add(AddCustomTokenWarning.PotentialScamToken) + warningsRemove.add(AddCustomTokenWarning.TokenAlreadyAdded) + clearTokenFields() + unlockTokenFields() } else -> { - val token = foundTokens[0] - val contracts = token.contracts + // foundToken - contains all info about the token + val foundToken = foundTokens[0] + dispatchOnMain(SetFoundTokenId(foundToken.id)) + when { - contracts.isEmpty() -> { - // TODO: refactoring: + foundToken.contracts.isEmpty() -> { Timber.e("Unexpected state -> throw to FB") } - contracts.size == 1 -> { - val contract = contracts[0] - val isPersistIntoTheAppAddedTokenList = isPersistIntoTheAppAddedTokenList(token, contract) + foundToken.contracts.size == 1 -> { + // token with single contract address + val singleTokenContract = foundToken.contracts[0] + fillTokenFields(foundToken, singleTokenContract) - if (isPersistIntoTheAppAddedTokenList) { - toAddWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded) - toRemoveWarnings.add(AddCustomTokenWarning.PotentialScamToken) - - dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false))) - dispatchOnMain(lockTokenFieldsAction()) + val isInAppSavedTokens = isTokenPersistIntoAppSavedTokensList() + if (isInAppSavedTokens) { + lockTokenFields() + warningsAdd.add(AddCustomTokenWarning.TokenAlreadyAdded) + warningsRemove.add(AddCustomTokenWarning.PotentialScamToken) } 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(lockTokenFieldsAction()) + // not in the saved tokens list + if (singleTokenContract.active) { + lockTokenFields() + if (hubState.derivationPathIsSelected()) { + warningsAdd.add(AddCustomTokenWarning.PotentialScamToken) + } else { + warningsRemove.add(AddCustomTokenWarning.TokenAlreadyAdded) + warningsRemove.add(AddCustomTokenWarning.PotentialScamToken) + } } else { - toAddWarnings.add(AddCustomTokenWarning.PotentialScamToken) - dispatchOnMain(ClearTokenFields) - dispatchOnMain(unlockTokenFieldsAction()) + warningsAdd.add(AddCustomTokenWarning.PotentialScamToken) } + unlockAddButton() } } else -> { + warningsRemove.add(AddCustomTokenWarning.TokenAlreadyAdded) + warningsRemove.add(AddCustomTokenWarning.PotentialScamToken) + val dialog = DomainDialog.SelectTokenDialog( - items = contracts, + items = foundToken.contracts, networkIdConverter = { networkId -> val blockchain = Blockchain.fromNetworkId(networkId) if (blockchain == null || blockchain == Blockchain.Unknown) { throw DomainException.SelectTokeNetworkException(networkId) } - hubState.convertBlockchainName(blockchain, "") + hubState.blockchainToName(blockchain) ?: "" }, onSelect = { selectedContract -> hubScope.launch { // find how to connect to the upper coroutineContext and dispatch through them - dispatchOnMain(FillTokenFields(token, selectedContract)) - dispatchOnMain(lockTokenFieldsAction()) + fillTokenFields(foundToken, selectedContract) + lockTokenFields() } }, ) @@ -249,15 +261,86 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT } } - if (toAddWarnings.isNotEmpty() || toRemoveWarnings.isNotEmpty()) { - dispatchOnMain(Warning.Replace(toRemoveWarnings.toSet(), toAddWarnings.toSet())) + replaceWarnings(warningsAdd, warningsRemove) + } + + private suspend fun replaceWarnings( + warningsAdd: MutableSet = mutableSetOf(), + warningsRemove: MutableSet = mutableSetOf(), + ) { + if (warningsAdd.isNotEmpty() || warningsRemove.isNotEmpty()) { + dispatchOnMain(Warning.Replace(warningsRemove.toSet(), warningsAdd.toSet())) } } - private fun isPersistIntoTheAppAddedTokenList( - token: Coins.CheckAddressResponse.Token, - contract: Coins.CheckAddressResponse.Token.Contract - ): Boolean = false +// private suspend fun validateUserTokenWithSavedTokensList(userToken: CustomCurrency.CustomToken) { +// val isInAppSavedTokens = isTokenPersistIntoAppSavedTokensList( +// userToken.token.id, +// userToken.token.contractAddress, +// userToken.network.toNetworkId(), +// ) +// if (isInAppSavedTokens) { +// replaceWarnings(mutableSetOf(AddCustomTokenWarning.TokenAlreadyAdded)) +// } +// } + + /** + * These are helper functions. + */ + private fun isTokenPersistIntoAppSavedTokensList( + tokenId: String? = hubState.tokenId, + tokenContractAddress: String = ContractAddress.getFieldValue(), + tokenNetworkId: String = Network.getFieldValue().toNetworkId(), + selectedDerivationBlockchain: Blockchain = DerivationPath.getFieldValue() + ): Boolean { + val savedCurrencies = hubState.appSavedCurrencies ?: return false + + val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivationBlockchain) + savedCurrencies.forEach { wrappedCurrency -> + when (wrappedCurrency) { + is DomainWrapped.Currency.Blockchain -> {} + is DomainWrapped.Currency.Token -> { + val sameId = tokenId == wrappedCurrency.token.id + val sameAddress = tokenContractAddress == wrappedCurrency.token.contractAddress + val sameBlockchain = Blockchain.fromNetworkId(tokenNetworkId) == wrappedCurrency.blockchain + val sameDerivationPath = derivationPath?.rawPath == wrappedCurrency.derivationPath + if (sameId && sameAddress && sameBlockchain && sameDerivationPath) { + return true + } + } + } + } + return false + } + + private fun isBlockchainPersistIntoAppSavedTokensList( + selectedBlockchain: Blockchain, + selectedDerivationBlockchain: Blockchain, + ): Boolean { + val state = hubState + val savedCurrencies = state.appSavedCurrencies ?: return false + + val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivationBlockchain) + savedCurrencies.forEach { wrappedCurrency -> + when (wrappedCurrency) { + is DomainWrapped.Currency.Blockchain -> { + val isSameBlockchain = selectedBlockchain == wrappedCurrency.blockchain + val isSameDerivationPath = derivationPath?.rawPath == wrappedCurrency.derivationPath + if (isSameBlockchain && isSameDerivationPath) return true + } + is DomainWrapped.Currency.Token -> {} + } + } + return false + } + + private fun getDerivationPathFromSelectedBlockchain( + selectedDerivationBlockchain: Blockchain + ): com.tangem.common.hdWallet.DerivationPath? = AddCustomTokenState.getDerivationPath( + mainNetwork = Network.getFieldValue(), + derivationNetwork = selectedDerivationBlockchain, + derivationStyle = hubState.cardDerivationStyle + ) private suspend fun CustomTokenFieldId.addError(error: AddCustomTokenError) { dispatchOnMain(FieldError.Add(this, error)) @@ -267,13 +350,6 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT dispatchOnMain(FieldError.Remove(this)) } - private suspend fun CustomTokenFieldId.addOrRemoveError(error: AddCustomTokenError?) { - when (error) { - null -> removeError() - else -> addError(error) - } - } - private inline fun CustomTokenFieldId.getField(): T { val state = hubState val value = when (this) { @@ -310,66 +386,108 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT } } - private fun CustomTokenFieldId.validate(value: Any): AddCustomTokenError? { + private fun CustomTokenFieldId.validateValue(value: Any): AddCustomTokenError? { + val state = hubState + val contractAddressValidator: TokenContractAddressValidator = state.getValidator(ContractAddress) + val nameValidator: TokenNameValidator = state.getValidator(Name) + val symbolValidator: TokenSymbolValidator = state.getValidator(Symbol) + val decimalsValidator: TokenDecimalsValidator = state.getValidator(Decimals) + val networkValidator: TokenNetworkValidator = state.getValidator(Network) return when (this) { ContractAddress -> contractAddressValidator.validate(value as String) - Network -> networkValidator.validate(value as Blockchain) + Network, DerivationPath -> networkValidator.validate(value as Blockchain) Name -> nameValidator.validate(value as String) Symbol -> symbolValidator.validate(value as String) Decimals -> decimalsValidator.validate(value as String) - DerivationPath -> networkValidator.validate(value as Blockchain) } } - private fun lockTokenFieldsAction(): Action { + /** + * The field is being validated. + * If there is an error, then it adds it to the field. + * If not, then data is collected from other user token fields, a token is generated and checked + * for content in saved tokens and app tokens list + */ + private suspend fun CustomTokenFieldId.validateField(value: Any): AddCustomTokenError? { + val error = this.validateValue(value) + when (error) { + null -> this.removeError() + else -> this.addError(error) + } + return error + } + + private suspend fun fillTokenFields( + token: Coins.CheckAddressResponse.Token, + contract: Coins.CheckAddressResponse.Token.Contract, + ) { + val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: Blockchain.Unknown + Network.setFieldValue(Field.Data(blockchain, false)) + Name.setFieldValue(Field.Data(token.name, false)) + Symbol.setFieldValue(Field.Data(token.symbol, false)) + Decimals.setFieldValue(Field.Data(contract.decimalCount.toString(), false)) + dispatchOnMain(UpdateForm(hubState)) + } + + private suspend fun clearTokenFields() { + Name.setFieldValue(Field.Data("", false)) + Symbol.setFieldValue(Field.Data("", false)) + Decimals.setFieldValue(Field.Data("", false)) + dispatchOnMain(UpdateForm(hubState)) + } + + private suspend fun lockTokenFields() { val state = hubState - return Screen.UpdateTokenFields(listOf( + val action = Screen.UpdateTokenFields(listOf( Network to state.screenState.network.copy(isEnabled = false), Name to state.screenState.name.copy(isEnabled = false), Symbol to state.screenState.symbol.copy(isEnabled = false), Decimals to state.screenState.decimals.copy(isEnabled = false), )) + dispatchOnMain(action) } - private fun unlockTokenFieldsAction(): Action { + private suspend fun unlockTokenFields() { val state = hubState - return Screen.UpdateTokenFields(listOf( + val action = Screen.UpdateTokenFields(listOf( Network to state.screenState.network.copy(isEnabled = true), Name to state.screenState.name.copy(isEnabled = true), Symbol to state.screenState.symbol.copy(isEnabled = true), Decimals to state.screenState.decimals.copy(isEnabled = true), )) + dispatchOnMain(action) } - private suspend fun toggleAddButtonAction(enable: Boolean) = when (enable) { - true -> unlockAddButtonAction() - else -> lockAddButtonAction() - } - - private suspend fun lockAddButtonAction() { + private suspend fun lockAddButton() { dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false))) } - private suspend fun unlockAddButtonAction() { + private suspend fun unlockAddButton() { dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(true))) } override fun reduceAction(action: Action, state: AddCustomTokenState): AddCustomTokenState { return when (action) { is Init.SetAddedCurrencies -> { - state.copy(addedCurrencies = action.addedCurrencies) + state.copy(appSavedCurrencies = action.addedCurrencies) } is Init.SetOnAddTokenCallback -> { state.copy(onTokenAddCallback = action.callback) } is OnCreate -> { -// val card = requireNotNull(globalState.scanResponse?.card) + val card = requireNotNull(globalState.scanResponse?.card) val tangemTechServiceManager = TangemTechServiceManager(TangemTechService()) -// tangemTechServiceManager.attachAuthKey(card.cardPublicKey.toHexString()) + tangemTechServiceManager.attachAuthKey(card.cardPublicKey.toHexString()) + + var derivationPathState = state.screenState.derivationPath + derivationPathState = when (card.derivationStyle) { + DerivationStyle.LEGACY -> derivationPathState.copy(isVisible = true) + null, DerivationStyle.NEW -> derivationPathState.copy(isVisible = false) + } state.copy( -// derivationStyle = card.derivationStyle, - derivationStyle = DerivationStyle.LEGACY, - tangemTechServiceManager = tangemTechServiceManager + cardDerivationStyle = card.derivationStyle, + tangemTechServiceManager = tangemTechServiceManager, + screenState = state.screenState.copy(derivationPath = derivationPathState) ) } is OnDestroy -> state.reset() @@ -414,7 +532,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT val newMap = state.formErrors.toMutableMap().apply { remove(action.id) } state.copy(formErrors = newMap) } - is SetTokenId -> { + is SetFoundTokenId -> { state.copy(tokenId = action.id) } is Warning.Add -> { diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt index c919b4cc97..d8f43f4e00 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt @@ -2,15 +2,16 @@ package com.tangem.domain.features.addCustomToken.redux import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle +import com.tangem.domain.DomainWrapped import com.tangem.domain.common.form.* import com.tangem.domain.features.addCustomToken.* import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.* import org.rekotlin.StateType data class AddCustomTokenState( - val addedCurrencies: AddedCurrencies? = null, - val onTokenAddCallback: ((CompleteData) -> Unit)? = null, - val derivationStyle: DerivationStyle? = null, + val appSavedCurrencies: List? = null, + val onTokenAddCallback: ((CustomCurrency) -> Unit)? = null, + val cardDerivationStyle: DerivationStyle? = null, val form: Form = Form(createFormFields()), val formValidators: Map> = createFormValidators(), val formErrors: Map = emptyMap(), @@ -28,30 +29,74 @@ data class AddCustomTokenState( fun hasError(id: FieldId): Boolean = formErrors[id] != null - fun getCompleteData(type: CompleteDataType): CompleteData = when (type) { - CompleteDataType.Token -> getToken() - CompleteDataType.Blockchain -> getBlockchain() - } - inline fun visitDataConverter(converter: FieldDataConverter): T { form.visitDataConverter(converter) return converter.getConvertedData() } - fun convertBlockchainName(blockchain: Blockchain, unknown: String): String = when (blockchain) { - Blockchain.Unknown -> unknown - else -> blockchain.fullName + fun blockchainToName(blockchain: Blockchain, isDerivationPath: Boolean = false): String? { + return when { + isDerivationPath -> blockchain.derivationPath(cardDerivationStyle)?.rawPath + else -> { + when (blockchain) { + Blockchain.Unknown -> null + else -> blockchain.fullName + } + } + } } - fun convertDerivationPathLabel(blockchain: Blockchain, unknown: String): String { - return blockchain.derivationPath(derivationStyle)?.rawPath ?: unknown + // except network + fun tokensFieldsIsFilled(): Boolean { + val idsToCheck = listOf(ContractAddress, Name, Symbol, Decimals) + val fieldsToCheck = form.fieldList.filter { idsToCheck.contains(it.id) } + val validator = StringIsNotEmptyValidator() + fieldsToCheck.forEach { field -> + val error = validator.validate(field.data.value?.toString()) + if (error != null) return false + } + return true + } + + // except network + fun tokensOneFieldsIsFilled(): Boolean { + val idsToCheck = listOf(ContractAddress, Name, Symbol, Decimals) + val fieldsToCheck = form.fieldList.filter { idsToCheck.contains(it.id) } + val validator = StringIsEmptyValidator() + fieldsToCheck.forEach { field -> + val error = validator.validate(field.data.value?.toString()) + if (error != null) return false + } + return true + } + + fun networkIsSelected(): Boolean { + val network = getField(Network) + return network.data.value != Blockchain.Unknown + } + + fun derivationPathIsSelected(): Boolean { + val network = getField(DerivationPath) + return network.data.value != Blockchain.Unknown + } + + fun gatherUserToken(): CustomCurrency.CustomToken? = try { + getToken() + } catch (ex: Exception) { + null + } + + fun gatherBlockchain(): CustomCurrency.CustomBlockchain? = try { + getBlockchain() + } catch (ex: Exception) { + null } fun reset(): AddCustomTokenState { return this.copy( - addedCurrencies = null, + appSavedCurrencies = null, onTokenAddCallback = null, - derivationStyle = null, + cardDerivationStyle = null, form = Form(createFormFields()), formErrors = emptyMap(), tokenId = null, @@ -61,40 +106,33 @@ data class AddCustomTokenState( ) } - fun networkIsEmpty(): Boolean { - val network = getField(Network) - return network.data.value != Blockchain.Unknown - } - - fun customTokensFieldsIsEmpty(): Boolean { - val idsToCheck = listOf(ContractAddress, Name, Symbol, Decimals) - val fieldsToCheck = form.fieldList.filter { idsToCheck.contains(it.id) } - val validator = StringIsEmptyValidator() -// val errors = mutableMapOf<>() - fieldsToCheck.forEach { field -> - val error = validator.validate(field.data.value?.toString()) - if (error != null) return false - } - return true - } - - fun allFieldsIsEmpty(): Boolean { - return networkIsEmpty() && customTokensFieldsIsEmpty() - } - - private fun getToken(): CompleteData.CustomToken { - return CompleteData.CustomToken.Converter(tokenId) + private fun getToken(): CustomCurrency.CustomToken { + return CustomCurrency.CustomToken.Converter(tokenId, cardDerivationStyle) .apply { visitDataConverter(this) } .getConvertedData() } - private fun getBlockchain(): CompleteData.CustomBlockchain { - return CompleteData.CustomBlockchain.Converter() + private fun getBlockchain(): CustomCurrency.CustomBlockchain { + return CustomCurrency.CustomBlockchain.Converter(cardDerivationStyle) .apply { visitDataConverter(this) } .getConvertedData() } companion object { + + /** + * If an user select derivation path (derivationNetwork) as Blockchain.Unknown, + * then we should use a blockchain from the mainNetwork to determine a DerivationPath + */ + fun getDerivationPath( + mainNetwork: Blockchain, + derivationNetwork: Blockchain, + derivationStyle: DerivationStyle? + ): com.tangem.common.hdWallet.DerivationPath? = when (derivationNetwork) { + Blockchain.Unknown -> mainNetwork + else -> derivationNetwork + }.derivationPath(derivationStyle) + private fun createFormFields(): List> { return listOf( TokenField(ContractAddress), diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/Models.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/Models.kt index 1d9bc38a7a..3dba21a577 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/Models.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/Models.kt @@ -1,8 +1,5 @@ package com.tangem.domain.features.addCustomToken.redux -import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.DomainWrapped - /** [REDACTED_AUTHOR] */ @@ -27,9 +24,4 @@ sealed class ViewStates { data class AddButton( val isEnabled: Boolean = true ) : ViewStates() -} - -data class AddedCurrencies( - val addedTokens: List, - val addedBlockchains: List -) \ No newline at end of file +} \ No newline at end of file