Updated on 2026-08-14

This commit is contained in:
Tangem 2024-03-07 20:30:58 +01:00
parent 0e515c142e
commit 24c6425299
7 changed files with 170 additions and 54 deletions

View file

@ -2,6 +2,7 @@ package com.tangem.managetokens.presentation.addcustomtoken.state.factory
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.tokens.error.AddCustomTokenError
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWallet
@ -15,6 +16,7 @@ import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.collections.immutable.toPersistentSet
@Suppress("LargeClass")
internal class AddCustomTokenStateFactory(
private val currentStateProvider: Provider<AddCustomTokenState>,
private val clickIntents: AddCustomTokenClickIntents,
@ -72,8 +74,11 @@ internal class AddCustomTokenStateFactory(
)
}
private fun getListOfDerivations(supportedNetworks: List<Network>): List<Derivation> {
return supportedNetworks.mapNotNull { network ->
private fun getListOfDerivations(
networksListToGenerateDerivations: List<Network>,
filterOnlyHardenedDerivations: Boolean = false,
): List<Derivation> {
return networksListToGenerateDerivations.mapNotNull { network ->
network.derivationPath.value?.let { rawPath ->
Derivation(
networkName = network.name,
@ -82,6 +87,12 @@ internal class AddCustomTokenStateFactory(
networkId = network.backendId,
onDerivationSelected = clickIntents::onDerivationSelected,
)
}.takeIf { derivation ->
if (filterOnlyHardenedDerivations) {
derivation?.let { allNodesHardened(createDerivationPathOrNull(it.path)) } ?: false
} else {
true
}
}
}
}
@ -218,41 +229,52 @@ internal class AddCustomTokenStateFactory(
)
}
fun updateStateOnNetworkSelected(networkItemState: NetworkItemState, supportsTokens: Boolean): AddCustomTokenState {
fun updateStateOnNetworkSelected(
networkItemState: NetworkItemState,
supportsTokens: Boolean,
networks: List<Network>,
requiresHardenedDerivationOnly: Boolean,
): AddCustomTokenState {
val uiState = currentStateProvider()
val tokenData = if (supportsTokens) {
uiState.tokenData
?: CustomTokenData(
contractAddressTextField = TextFieldState.Editable(
value = "",
isEnabled = true,
onValueChange = clickIntents::onContractAddressChange,
onFocusExit = clickIntents::onContractAddressFocusExit,
),
nameTextField = TextFieldState.Editable(
value = "",
isEnabled = false,
onValueChange = clickIntents::onTokenNameChange,
onFocusExit = clickIntents::onTokenNameFocusExit,
),
symbolTextField = TextFieldState.Editable(
value = "",
isEnabled = false,
onValueChange = clickIntents::onSymbolChange,
onFocusExit = clickIntents::onSymbolFocusExit,
),
decimalsTextField = TextFieldState.Editable(
value = "",
isEnabled = false,
onValueChange = clickIntents::onDecimalsChange,
onFocusExit = clickIntents::onDecimalsFocusExit,
),
)
uiState.tokenData ?: CustomTokenData(
contractAddressTextField = TextFieldState.Editable(
value = "",
isEnabled = true,
onValueChange = clickIntents::onContractAddressChange,
onFocusExit = clickIntents::onContractAddressFocusExit,
),
nameTextField = TextFieldState.Editable(
value = "",
isEnabled = false,
onValueChange = clickIntents::onTokenNameChange,
onFocusExit = clickIntents::onTokenNameFocusExit,
),
symbolTextField = TextFieldState.Editable(
value = "",
isEnabled = false,
onValueChange = clickIntents::onSymbolChange,
onFocusExit = clickIntents::onSymbolFocusExit,
),
decimalsTextField = TextFieldState.Editable(
value = "",
isEnabled = false,
onValueChange = clickIntents::onDecimalsChange,
onFocusExit = clickIntents::onDecimalsFocusExit,
),
)
} else {
null
}
val derivations = getListOfDerivations(networks, requiresHardenedDerivationOnly)
val chooseDerivationState = createChooseDerivationState(derivations)
return uiState.copy(
chooseNetworkState = uiState.chooseNetworkState.copy(selectedNetwork = networkItemState),
chooseNetworkState = uiState.chooseNetworkState.copy(
selectedNetwork = networkItemState,
),
chooseDerivationState = chooseDerivationState,
tokenData = tokenData,
addTokenButton = uiState.addTokenButton.copy(isEnabled = true),
)
@ -291,6 +313,47 @@ internal class AddCustomTokenStateFactory(
)
}
fun updateOnCustomDerivationEntered(input: String, requiresHardenedDerivationOnly: Boolean): AddCustomTokenState {
val uiState = currentStateProvider()
val path = createDerivationPathOrNull(input)
val isWrongDerivationForWallet2 = isWrongDerivationForWallet2(
requiresHardenedDerivationOnly = requiresHardenedDerivationOnly,
derivationPath = path,
)
val enterDerivationState = uiState.chooseDerivationState?.enterCustomDerivationState?.copy(
confirmButtonEnabled = path != null && !isWrongDerivationForWallet2,
derivationIncorrect = input.isNotBlank() && path == null || isWrongDerivationForWallet2,
)
return uiState.copy(
chooseDerivationState = uiState.chooseDerivationState?.copy(
enterCustomDerivationState = enterDerivationState,
),
)
}
private fun isWrongDerivationForWallet2(
requiresHardenedDerivationOnly: Boolean,
derivationPath: DerivationPath?,
): Boolean {
return if (requiresHardenedDerivationOnly) {
!allNodesHardened(derivationPath)
} else {
false
}
}
private fun allNodesHardened(derivationPath: DerivationPath?): Boolean {
return derivationPath?.nodes?.all { it.isHardened } ?: false
}
private fun createDerivationPathOrNull(rawPath: String): DerivationPath? {
return try {
DerivationPath(rawPath)
} catch (error: Throwable) {
null
}
}
fun updateStateOnLoadingTokenInfo(contractAddress: String): AddCustomTokenState {
return currentStateProvider().copy(
tokenData = CustomTokenData(

View file

@ -9,8 +9,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.HDWalletError
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.model.CryptoCurrency
@ -56,6 +54,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
private val validateContractAddressUseCase: ValidateContractAddressUseCase,
private val getNetworksSupportedByWallet: GetNetworksSupportedByWallet,
private val areTokensSupportedByNetworkUseCase: AreTokensSupportedByNetworkUseCase,
private val requiresHardenedDerivationOnlyUseCase: RequiresHardenedDerivationOnlyUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel(), AddCustomTokenClickIntents, DefaultLifecycleObserver {
@ -135,13 +134,25 @@ internal class AddCustomTokenViewModel @Inject constructor(
private fun selectNetwork(networkItemState: NetworkItemState) {
viewModelScope.launch(dispatchers.io) {
val selectedWalletId = getSelectedWalletSyncUseCase().getOrNull()?.walletId
// TODO [REDACTED_TASK_KEY]
val selectedWalletId = getSelectedWalletSyncUseCase().getOrNull()?.walletId ?: return@launch
val supportsTokens = areTokensSupportedByNetworkUseCase(
networkId = networkItemState.id,
userWalletId = selectedWalletId,
).getOrNull() ?: false
val networksForDerivations = getSupportedNetworks(selectedWalletId)
withContext(dispatchers.main) {
uiState = stateFactory.updateStateOnNetworkSelected(networkItemState, supportsTokens)
uiState = stateFactory.updateStateOnNetworkSelected(
networkItemState = networkItemState,
supportsTokens = supportsTokens,
networks = networksForDerivations,
requiresHardenedDerivationOnly = requiresHardenedDerivationOnly(
networkId = networkItemState.id,
userWalletId = selectedWalletId,
),
)
}
}
}
@ -347,6 +358,8 @@ internal class AddCustomTokenViewModel @Inject constructor(
}
override fun onCustomDerivationChange(input: String) {
val selectedWallet = getSelectedWalletSyncUseCase().getOrNull() ?: return
analyticsEventHandler.send(ManageTokens.CustomTokenDerivationSelected(ManageTokens.Derivation.CUSTOM.value))
uiState = uiState.copy(
chooseDerivationState = uiState.chooseDerivationState?.copy(
@ -356,25 +369,19 @@ internal class AddCustomTokenViewModel @Inject constructor(
),
)
debouncer.debounce(waitMs = DEFAULT_WAIT_TIME_MS, coroutineScope = viewModelScope + dispatchers.io) {
val path = createDerivationPathOrNull(input)
val enterDerivationState = uiState.chooseDerivationState?.enterCustomDerivationState?.copy(
confirmButtonEnabled = path != null,
derivationIncorrect = input.isNotBlank() && path == null,
)
uiState = uiState.copy(
chooseDerivationState = uiState.chooseDerivationState?.copy(
enterCustomDerivationState = enterDerivationState,
),
val networkId = uiState.chooseNetworkState.selectedNetwork?.id ?: return@debounce
uiState = stateFactory.updateOnCustomDerivationEntered(
input = input,
requiresHardenedDerivationOnly = requiresHardenedDerivationOnly(networkId, selectedWallet.walletId),
)
}
}
private fun createDerivationPathOrNull(rawPath: String): DerivationPath? {
return try {
DerivationPath(rawPath)
} catch (error: HDWalletError) {
null
}
private suspend fun requiresHardenedDerivationOnly(networkId: String, userWalletId: UserWalletId): Boolean {
return requiresHardenedDerivationOnlyUseCase.invoke(
networkId = networkId,
userWalletId = userWalletId,
).getOrElse { false }
}
override fun onCustomDerivationSelected() {