Updated on 2026-08-14
This commit is contained in:
commit
cf021480f9
20 changed files with 457 additions and 259 deletions
|
|
@ -8,6 +8,7 @@ interface AddCustomTokenComponent : ComposableBottomSheetComponent {
|
|||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val source: ManageTokensSource,
|
||||
val onDismiss: () -> Unit,
|
||||
val onCurrencyAdded: () -> Unit,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
package com.tangem.features.managetokens.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
|
||||
internal sealed class CustomTokenAnalyticsEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(
|
||||
category = "Manage Tokens / Custom",
|
||||
event = event,
|
||||
params = params,
|
||||
) {
|
||||
|
||||
class ScreenOpened(source: ManageTokensSource) : CustomTokenAnalyticsEvent(
|
||||
event = "Custom Token Screen Opened",
|
||||
params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
|
||||
)
|
||||
|
||||
class CustomTokenWasAdded(
|
||||
currencySymbol: String,
|
||||
derivationPath: String,
|
||||
source: ManageTokensSource,
|
||||
) : CustomTokenAnalyticsEvent(
|
||||
event = "Custom Token Was Added",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Key.TOKEN_PARAM to currencySymbol,
|
||||
AnalyticsParam.Key.DERIVATION to derivationPath,
|
||||
AnalyticsParam.Key.SOURCE to source.name,
|
||||
),
|
||||
)
|
||||
|
||||
class NetworkSelected(networkName: String, source: ManageTokensSource) : CustomTokenAnalyticsEvent(
|
||||
event = "Custom Token Network Selected",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Key.BLOCKCHAIN to networkName,
|
||||
AnalyticsParam.Key.SOURCE to source.name,
|
||||
),
|
||||
)
|
||||
|
||||
class DerivationSelected(derivationName: String, source: ManageTokensSource) : CustomTokenAnalyticsEvent(
|
||||
event = "Custom Token Derivation Selected",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Key.DERIVATION to derivationName,
|
||||
AnalyticsParam.Key.SOURCE to source.name,
|
||||
),
|
||||
)
|
||||
|
||||
class Address(isValid: Boolean, source: ManageTokensSource) : CustomTokenAnalyticsEvent(
|
||||
event = "Custom Token Address",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Key.VALIDATION to AnalyticsParam.Validation.from(isValid),
|
||||
AnalyticsParam.Key.SOURCE to source.name,
|
||||
),
|
||||
)
|
||||
|
||||
class Name(source: ManageTokensSource) : CustomTokenAnalyticsEvent(
|
||||
event = "Custom Token Name",
|
||||
params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
|
||||
)
|
||||
|
||||
class Symbol(source: ManageTokensSource) : CustomTokenAnalyticsEvent(
|
||||
event = "Custom Token Symbol",
|
||||
params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
|
||||
)
|
||||
|
||||
class Decimals(source: ManageTokensSource) : CustomTokenAnalyticsEvent(
|
||||
event = "Custom Token Decimals",
|
||||
params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
|
||||
)
|
||||
|
||||
class ButtonCustomToken(source: ManageTokensSource) : CustomTokenAnalyticsEvent(
|
||||
event = "Button - Custom Token",
|
||||
params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
|
||||
)
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ internal interface CustomTokenFormComponent : ComposableContentComponent {
|
|||
val network: SelectedNetwork,
|
||||
val derivationPath: SelectedDerivationPath?,
|
||||
val formValues: CustomTokenFormValues,
|
||||
val source: ManageTokensSource,
|
||||
val onSelectNetworkClick: (CustomTokenFormValues) -> Unit,
|
||||
val onSelectDerivationPathClick: (CustomTokenFormValues) -> Unit,
|
||||
val onCurrencyAdded: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
|
|||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
||||
import com.arkivanov.decompose.router.stack.*
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
|
||||
import com.tangem.features.managetokens.component.AddCustomTokenComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenFormComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
|
|
@ -29,6 +31,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
|
|||
@Assisted private val params: AddCustomTokenComponent.Params,
|
||||
private val selectorComponentFactory: CustomTokenSelectorComponent.Factory,
|
||||
private val formComponentFactory: CustomTokenFormComponent.Factory,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : AddCustomTokenComponent, AppComponentContext by context {
|
||||
|
||||
private val navigation = StackNavigation<AddCustomTokenConfig>()
|
||||
|
|
@ -45,6 +48,10 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
|
|||
childFactory = ::contentChild,
|
||||
)
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(CustomTokenAnalyticsEvent.ScreenOpened(params.source))
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
|
@ -85,9 +92,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
|
|||
params = CustomTokenSelectorComponent.Params.NetworkSelector(
|
||||
userWalletId = config.userWalletId,
|
||||
selectedNetwork = null,
|
||||
onNetworkSelected = { network ->
|
||||
showForm(network = network)
|
||||
},
|
||||
onNetworkSelected = ::changeSelectedNetwork,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -97,9 +102,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
|
|||
params = CustomTokenSelectorComponent.Params.NetworkSelector(
|
||||
userWalletId = config.userWalletId,
|
||||
selectedNetwork = config.selectedNetwork,
|
||||
onNetworkSelected = { network ->
|
||||
showForm(network = network)
|
||||
},
|
||||
onNetworkSelected = ::changeSelectedNetwork,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -112,9 +115,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
|
|||
"Network is not selected"
|
||||
},
|
||||
selectedDerivationPath = config.selectedDerivationPath,
|
||||
onDerivationPathSelected = { derivationPath ->
|
||||
showForm(derivationPath = derivationPath)
|
||||
},
|
||||
onDerivationPathSelected = ::changeDerivationPath,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -128,6 +129,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
|
|||
},
|
||||
derivationPath = config.selectedDerivationPath,
|
||||
formValues = config.formValues,
|
||||
source = params.source,
|
||||
onSelectNetworkClick = ::showNetworkSelector,
|
||||
onSelectDerivationPathClick = ::showDerivationPathSelector,
|
||||
onCurrencyAdded = ::dismissAndNotify,
|
||||
|
|
@ -136,6 +138,26 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun changeSelectedNetwork(network: SelectedNetwork) {
|
||||
val event = CustomTokenAnalyticsEvent.NetworkSelected(
|
||||
networkName = network.name,
|
||||
source = params.source,
|
||||
)
|
||||
analyticsEventHandler.send(event)
|
||||
|
||||
showForm(network = network)
|
||||
}
|
||||
|
||||
private fun changeDerivationPath(derivationPath: SelectedDerivationPath) {
|
||||
val event = CustomTokenAnalyticsEvent.DerivationSelected(
|
||||
derivationName = derivationPath.name,
|
||||
source = params.source,
|
||||
)
|
||||
analyticsEventHandler.send(event)
|
||||
|
||||
showForm(derivationPath = derivationPath)
|
||||
}
|
||||
|
||||
private fun showDerivationPathSelector(formValues: CustomTokenFormValues) {
|
||||
val currentConfig = contentStack.value.active.configuration
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.managetokens.ValidateDerivationPathUseCase
|
||||
import com.tangem.domain.managetokens.model.exceptoin.DerivationPathValidationException
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -111,7 +110,8 @@ internal class DefaultCustomTokenDerivationInputComponent @AssistedInject constr
|
|||
val model = SelectedDerivationPath(
|
||||
id = null,
|
||||
value = Network.DerivationPath.Custom(value),
|
||||
networkName = stringReference(value = value),
|
||||
name = value,
|
||||
isDefault = false,
|
||||
)
|
||||
|
||||
params.onConfirm(model)
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ internal class DefaultManageTokensComponent @AssistedInject constructor(
|
|||
context = childByContext(componentContext),
|
||||
params = AddCustomTokenComponent.Params(
|
||||
userWalletId = config.userWalletId,
|
||||
source = params.source,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
onCurrencyAdded = model::reloadList,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.managetokens.component.preview
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
|
|
@ -13,6 +14,7 @@ import com.tangem.features.managetokens.impl.R
|
|||
import com.tangem.features.managetokens.ui.CustomTokenFormContent
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
|
||||
internal class PreviewCustomTokenFormComponent(
|
||||
networkName: ClickableFieldUM = PreviewCustomTokenFormComponent.networkName,
|
||||
|
|
@ -48,30 +50,40 @@ internal class PreviewCustomTokenFormComponent(
|
|||
onClick = {},
|
||||
)
|
||||
val tokenForm: CustomTokenFormUM.TokenFormUM = CustomTokenFormUM.TokenFormUM(
|
||||
contractAddress = TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_contract_address_input_title),
|
||||
placeholder = stringReference(value = "0x000000000000000000000000000"),
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
),
|
||||
name = TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_name_input_title),
|
||||
placeholder = stringReference(value = "E.g. USD Coin"),
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
),
|
||||
symbol = TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_token_symbol_input_title),
|
||||
placeholder = stringReference(value = "E.g. USDC"),
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
),
|
||||
decimals = TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_decimals_input_title),
|
||||
placeholder = stringReference(value = "8"),
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
),
|
||||
fields = mapOf(
|
||||
CustomTokenFormUM.TokenFormUM.Field.CONTRACT_ADDRESS to TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_contract_address_input_title),
|
||||
placeholder = stringReference(value = "0x000000000000000000000000000"),
|
||||
value = "",
|
||||
keyboardOptions = KeyboardOptions(),
|
||||
onValueChange = {},
|
||||
onFocusChange = {},
|
||||
),
|
||||
CustomTokenFormUM.TokenFormUM.Field.NAME to TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_name_input_title),
|
||||
placeholder = stringReference(value = "E.g. USD Coin"),
|
||||
value = "",
|
||||
keyboardOptions = KeyboardOptions(),
|
||||
onValueChange = {},
|
||||
onFocusChange = {},
|
||||
),
|
||||
CustomTokenFormUM.TokenFormUM.Field.SYMBOL to TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_token_symbol_input_title),
|
||||
placeholder = stringReference(value = "E.g. USDC"),
|
||||
value = "",
|
||||
keyboardOptions = KeyboardOptions(),
|
||||
onValueChange = {},
|
||||
onFocusChange = {},
|
||||
),
|
||||
CustomTokenFormUM.TokenFormUM.Field.DECIMALS to TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_decimals_input_title),
|
||||
placeholder = stringReference(value = "8"),
|
||||
value = "",
|
||||
keyboardOptions = KeyboardOptions(),
|
||||
onValueChange = {},
|
||||
onFocusChange = {},
|
||||
),
|
||||
).toPersistentMap(),
|
||||
)
|
||||
val notifications: PersistentList<CustomTokenFormUM.NotificationUM> = persistentListOf(
|
||||
CustomTokenFormUM.NotificationUM(
|
||||
|
|
|
|||
|
|
@ -31,13 +31,14 @@ internal class PreviewCustomTokenSelectorComponent(
|
|||
val d = SelectedDerivationPath(
|
||||
id = Network.ID(index.toString()),
|
||||
value = Network.DerivationPath.Card("m/44'/0'/0'/0/$index"),
|
||||
networkName = stringReference(value = "Network $index"),
|
||||
name = "Network $index",
|
||||
isDefault = false,
|
||||
)
|
||||
|
||||
DerivationPathUM(
|
||||
id = d.id?.value ?: "",
|
||||
value = d.value.value.orEmpty(),
|
||||
networkName = d.networkName,
|
||||
networkName = stringReference(d.name),
|
||||
isSelected = d.value == params.selectedDerivationPath?.value,
|
||||
onSelectedStateChange = { params.onDerivationPathSelected(d) },
|
||||
)
|
||||
|
|
@ -45,7 +46,7 @@ internal class PreviewCustomTokenSelectorComponent(
|
|||
is Params.NetworkSelector -> {
|
||||
val n = SelectedNetwork(
|
||||
id = Network.ID(index.toString()),
|
||||
name = stringReference(value = "Network $index"),
|
||||
name = "Network $index",
|
||||
derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/$index"),
|
||||
canHandleTokens = false,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.features.managetokens.entity.customtoken
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -27,7 +26,7 @@ internal data class AddCustomTokenConfig(
|
|||
@Serializable
|
||||
internal data class SelectedNetwork(
|
||||
val id: Network.ID,
|
||||
val name: TextReference,
|
||||
val name: String,
|
||||
val derivationPath: Network.DerivationPath,
|
||||
val canHandleTokens: Boolean,
|
||||
)
|
||||
|
|
@ -36,5 +35,6 @@ internal data class SelectedNetwork(
|
|||
internal data class SelectedDerivationPath(
|
||||
val id: Network.ID?,
|
||||
val value: Network.DerivationPath,
|
||||
val networkName: TextReference,
|
||||
val name: String,
|
||||
val isDefault: Boolean,
|
||||
)
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.features.managetokens.entity.customtoken
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal data class CustomTokenFormUM(
|
||||
|
|
@ -16,12 +18,17 @@ internal data class CustomTokenFormUM(
|
|||
) {
|
||||
|
||||
data class TokenFormUM(
|
||||
val contractAddress: TextInputFieldUM,
|
||||
val name: TextInputFieldUM,
|
||||
val symbol: TextInputFieldUM,
|
||||
val decimals: TextInputFieldUM,
|
||||
val fields: PersistentMap<Field, TextInputFieldUM>,
|
||||
val wasFilled: Boolean = false,
|
||||
)
|
||||
) {
|
||||
|
||||
enum class Field {
|
||||
CONTRACT_ADDRESS,
|
||||
NAME,
|
||||
SYMBOL,
|
||||
DECIMALS,
|
||||
}
|
||||
}
|
||||
|
||||
data class NotificationUM(
|
||||
val id: String,
|
||||
|
|
@ -32,10 +39,13 @@ internal data class CustomTokenFormUM(
|
|||
internal data class TextInputFieldUM(
|
||||
val label: TextReference,
|
||||
val placeholder: TextReference,
|
||||
val keyboardOptions: KeyboardOptions,
|
||||
val value: String = "",
|
||||
val isFocused: Boolean = false,
|
||||
val error: TextReference? = null,
|
||||
val isEnabled: Boolean = true,
|
||||
val onValueChange: (String) -> Unit,
|
||||
val onFocusChange: (Boolean) -> Unit,
|
||||
)
|
||||
|
||||
internal data class ClickableFieldUM(
|
||||
|
|
|
|||
|
|
@ -1,45 +1,35 @@
|
|||
package com.tangem.features.managetokens.entity.customtoken
|
||||
|
||||
import com.tangem.domain.managetokens.model.AddCustomTokenForm
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@JvmInline
|
||||
@Serializable
|
||||
internal value class CustomTokenFormValues private constructor(private val values: List<String>) {
|
||||
|
||||
constructor() : this(values = emptyList())
|
||||
internal class CustomTokenFormValues(
|
||||
private val contractAddress: String = "",
|
||||
private val name: String = "",
|
||||
private val symbol: String = "",
|
||||
private val decimals: String = "",
|
||||
) {
|
||||
|
||||
constructor(form: TokenFormUM?) : this(
|
||||
values = if (form == null) {
|
||||
emptyList()
|
||||
} else {
|
||||
listOf(
|
||||
form.contractAddress.value,
|
||||
form.name.value,
|
||||
form.symbol.value,
|
||||
form.decimals.value,
|
||||
)
|
||||
},
|
||||
contractAddress = form?.fields?.get(Field.CONTRACT_ADDRESS)?.value.orEmpty(),
|
||||
name = form?.fields?.get(Field.NAME)?.value.orEmpty(),
|
||||
symbol = form?.fields?.get(Field.SYMBOL)?.value.orEmpty(),
|
||||
decimals = form?.fields?.get(Field.DECIMALS)?.value.orEmpty(),
|
||||
)
|
||||
|
||||
fun fillValues(to: TokenFormUM): TokenFormUM = to.copy(
|
||||
contractAddress = to.contractAddress.copy(value = values.getOrElse(index = 0) { "" }),
|
||||
name = to.name.copy(value = values.getOrElse(index = 1) { "" }),
|
||||
symbol = to.symbol.copy(value = values.getOrElse(index = 2) { "" }),
|
||||
decimals = to.decimals.copy(value = values.getOrElse(index = 3) { "" }),
|
||||
)
|
||||
|
||||
fun toDomainModel(): AddCustomTokenForm.Raw? {
|
||||
return if (values.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
AddCustomTokenForm.Raw(
|
||||
contractAddress = values.getOrElse(index = 0) { "" },
|
||||
name = values.getOrElse(index = 1) { "" },
|
||||
symbol = values.getOrElse(index = 2) { "" },
|
||||
decimals = values.getOrElse(index = 3) { "" },
|
||||
fields = to.fields.mapValues { (key, field) ->
|
||||
field.copy(
|
||||
value = when (key) {
|
||||
Field.CONTRACT_ADDRESS -> contractAddress
|
||||
Field.NAME -> name
|
||||
Field.SYMBOL -> symbol
|
||||
Field.DECIMALS -> decimals
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}.toPersistentMap(),
|
||||
)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.managetokens.model
|
|||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -15,22 +16,27 @@ import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationE
|
|||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
|
||||
import com.tangem.features.managetokens.component.CustomTokenFormComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.ClickableFieldUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormValues
|
||||
import com.tangem.features.managetokens.entity.customtoken.TextInputFieldUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.utils.CustomCurrencyFormBuilder
|
||||
import com.tangem.features.managetokens.utils.CustomCurrencyValidator
|
||||
import com.tangem.features.managetokens.utils.mapper.mapToDomainModel
|
||||
import com.tangem.features.managetokens.utils.ui.*
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ComponentScoped
|
||||
internal class CustomTokenFormModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -38,6 +44,8 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
|
||||
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val customTokenFormManager: CustomCurrencyFormBuilder,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -68,20 +76,23 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
return CustomTokenFormUM(
|
||||
networkName = ClickableFieldUM(
|
||||
label = resourceReference(R.string.custom_token_network_input_title),
|
||||
value = params.network.name,
|
||||
value = stringReference(params.network.name),
|
||||
onClick = ::selectNetwork,
|
||||
),
|
||||
tokenForm = if (params.network.canHandleTokens) {
|
||||
getInitialTokenForm()
|
||||
customTokenFormManager.buildForm(
|
||||
updateFormFieldValue = ::updateFormFieldValue,
|
||||
updateFormFieldFocus = ::updateFormFieldFocus,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
derivationPath = ClickableFieldUM(
|
||||
label = resourceReference(R.string.custom_token_derivation_path),
|
||||
value = if (params.derivationPath == null || params.derivationPath.id == params.network.id) {
|
||||
value = if (params.derivationPath == null || params.derivationPath.isDefault) {
|
||||
resourceReference(R.string.custom_token_derivation_path_default)
|
||||
} else {
|
||||
params.derivationPath.networkName
|
||||
stringReference(params.derivationPath.name)
|
||||
},
|
||||
onClick = ::selectDerivationPath,
|
||||
),
|
||||
|
|
@ -241,79 +252,52 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getInitialTokenForm(): CustomTokenFormUM.TokenFormUM {
|
||||
val formValues = params.formValues
|
||||
|
||||
val form = CustomTokenFormUM.TokenFormUM(
|
||||
contractAddress = TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_contract_address_input_title),
|
||||
placeholder = stringReference(CONTRACT_ADDRESS_PLACEHOLDER),
|
||||
onValueChange = ::updateContractAddress,
|
||||
),
|
||||
name = TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_name_input_title),
|
||||
placeholder = resourceReference(R.string.custom_token_name_input_placeholder),
|
||||
onValueChange = ::updateTokenName,
|
||||
),
|
||||
symbol = TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_token_symbol_input_title),
|
||||
placeholder = resourceReference(R.string.custom_token_token_symbol_input_placeholder),
|
||||
onValueChange = ::updateTokenSymbol,
|
||||
),
|
||||
decimals = TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_decimals_input_title),
|
||||
placeholder = stringReference(DECIMALS_PLACEHOLDER),
|
||||
onValueChange = ::updateDecimals,
|
||||
),
|
||||
)
|
||||
|
||||
return formValues.fillValues(form)
|
||||
}
|
||||
|
||||
private fun getDerivationPath(): Network.DerivationPath {
|
||||
return params.derivationPath?.value ?: params.network.derivationPath
|
||||
}
|
||||
|
||||
private fun updateContractAddress(value: String) {
|
||||
private fun updateFormFieldValue(field: Field, value: String) {
|
||||
state.update { state ->
|
||||
state.updateTokenForm {
|
||||
val fieldValue = fields.getValue(field)
|
||||
|
||||
if (!fieldValue.isEnabled) return@updateTokenForm this
|
||||
|
||||
val updatedFieldValue = fieldValue.copy(
|
||||
value = value,
|
||||
)
|
||||
val updatedFields = fields.mutate {
|
||||
it[field] = updatedFieldValue
|
||||
}
|
||||
|
||||
copy(
|
||||
contractAddress = contractAddress.updateValue(value),
|
||||
fields = updatedFields,
|
||||
wasFilled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTokenName(value: String) {
|
||||
private fun updateFormFieldFocus(field: Field, isFocused: Boolean) {
|
||||
state.update { state ->
|
||||
state.updateTokenForm {
|
||||
copy(
|
||||
name = name.updateValue(value),
|
||||
wasFilled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val fieldValue = fields.getValue(field)
|
||||
|
||||
private fun updateTokenSymbol(value: String) {
|
||||
state.update { state ->
|
||||
state.updateTokenForm {
|
||||
copy(
|
||||
symbol = symbol.updateValue(value),
|
||||
wasFilled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fieldValue.isEnabled) return@updateTokenForm this
|
||||
|
||||
private fun updateDecimals(value: String) {
|
||||
state.update { state ->
|
||||
state.updateTokenForm {
|
||||
copy(
|
||||
decimals = decimals.updateValue(value),
|
||||
wasFilled = false,
|
||||
val updatedFieldValue = fieldValue.copy(
|
||||
isFocused = isFocused,
|
||||
)
|
||||
val updatedFields = fields.mutate {
|
||||
it[field] = updatedFieldValue
|
||||
}
|
||||
|
||||
// Checking if a field is out of focus
|
||||
if (fieldValue.isFocused && !isFocused && fieldValue.value.isNotEmpty()) {
|
||||
sendFieldAnalyticsEvent(field, fieldValue)
|
||||
}
|
||||
|
||||
copy(fields = updatedFields)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -337,6 +321,13 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
return@resource
|
||||
}
|
||||
|
||||
val event = CustomTokenAnalyticsEvent.CustomTokenWasAdded(
|
||||
currencySymbol = currency.symbol,
|
||||
derivationPath = currency.network.derivationPath.value.orEmpty(),
|
||||
source = params.source,
|
||||
)
|
||||
analyticsEventHandler.send(event)
|
||||
|
||||
derivePublicKeysUseCase(params.userWalletId, listOf(currency)).getOrElse {
|
||||
Timber.e(it, "Failed to derive public keys")
|
||||
showErrorDialog()
|
||||
|
|
@ -360,8 +351,17 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
params.onSelectDerivationPathClick(CustomTokenFormValues(state.value.tokenForm))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CONTRACT_ADDRESS_PLACEHOLDER = "0x000000000000000000000000000..."
|
||||
const val DECIMALS_PLACEHOLDER = "0"
|
||||
private fun sendFieldAnalyticsEvent(field: Field, fieldValue: TextInputFieldUM) {
|
||||
val event = when (field) {
|
||||
Field.CONTRACT_ADDRESS -> CustomTokenAnalyticsEvent.Address(
|
||||
isValid = fieldValue.error == null,
|
||||
source = params.source,
|
||||
)
|
||||
Field.NAME -> CustomTokenAnalyticsEvent.Name(params.source)
|
||||
Field.SYMBOL -> CustomTokenAnalyticsEvent.Symbol(params.source)
|
||||
Field.DECIMALS -> CustomTokenAnalyticsEvent.Decimals(params.source)
|
||||
}
|
||||
|
||||
analyticsEventHandler.send(event)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.managetokens.GetSupportedNetworksUseCase
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -90,7 +89,7 @@ internal class CustomTokenSelectorModel @Inject constructor(
|
|||
onSelectedStateChange = {
|
||||
val model = SelectedNetwork(
|
||||
id = network.id,
|
||||
name = stringReference(network.name),
|
||||
name = network.name,
|
||||
derivationPath = network.derivationPath,
|
||||
canHandleTokens = network.canHandleTokens,
|
||||
)
|
||||
|
|
@ -109,8 +108,9 @@ internal class CustomTokenSelectorModel @Inject constructor(
|
|||
onSelectedStateChange = {
|
||||
val model = SelectedDerivationPath(
|
||||
id = network.id,
|
||||
networkName = resourceReference(R.string.custom_token_derivation_path_default),
|
||||
name = network.name,
|
||||
value = network.derivationPath,
|
||||
isDefault = true,
|
||||
)
|
||||
|
||||
selector.onDerivationPathSelected(model)
|
||||
|
|
@ -133,8 +133,9 @@ internal class CustomTokenSelectorModel @Inject constructor(
|
|||
onSelectedStateChange = {
|
||||
val model = SelectedDerivationPath(
|
||||
id = network.id,
|
||||
networkName = stringReference(network.name),
|
||||
name = network.name,
|
||||
value = network.derivationPath,
|
||||
isDefault = false,
|
||||
)
|
||||
|
||||
selector.onDerivationPathSelected(model)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.managetokens.SaveManagedTokensUseCase
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
|
||||
import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
|
|
@ -278,6 +279,8 @@ internal class ManageTokensModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun navigateToAddCustomToken() {
|
||||
analyticsEventHandler.send(CustomTokenAnalyticsEvent.ButtonCustomToken(params.source))
|
||||
|
||||
params.userWalletId?.let {
|
||||
bottomSheetNavigation.activate(ManageTokensBottomSheetConfig.AddCustomToken(it))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetTitle
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -102,7 +101,7 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
|
|||
popBack = {},
|
||||
selectedNetwork = SelectedNetwork(
|
||||
id = Network.ID(value = "1"),
|
||||
name = stringReference("Ethereum"),
|
||||
name = "Ethereum",
|
||||
derivationPath = Network.DerivationPath.None,
|
||||
canHandleTokens = false,
|
||||
),
|
||||
|
|
@ -115,7 +114,7 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
|
|||
popBack = {},
|
||||
selectedNetwork = SelectedNetwork(
|
||||
id = Network.ID(value = "0"),
|
||||
name = stringReference("Ethereum"),
|
||||
name = "Ethereum",
|
||||
derivationPath = Network.DerivationPath.None,
|
||||
canHandleTokens = false,
|
||||
),
|
||||
|
|
@ -129,7 +128,8 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
|
|||
selectedDerivationPath = SelectedDerivationPath(
|
||||
id = Network.ID(value = "0"),
|
||||
value = Network.DerivationPath.None,
|
||||
networkName = stringReference("Ethereum"),
|
||||
name = "Ethereum",
|
||||
isDefault = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -7,19 +7,16 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
|
@ -37,9 +34,11 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
import com.tangem.features.managetokens.component.preview.PreviewCustomTokenFormComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.ClickableFieldUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
|
||||
import com.tangem.features.managetokens.entity.customtoken.TextInputFieldUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.ui.component.AddCustomTokenDescription
|
||||
import kotlinx.collections.immutable.mutate
|
||||
|
||||
@Composable
|
||||
internal fun CustomTokenFormContent(model: CustomTokenFormUM, modifier: Modifier = Modifier) {
|
||||
|
|
@ -125,43 +124,14 @@ private fun TokenForm(tokenForm: CustomTokenFormUM.TokenFormUM, modifier: Modifi
|
|||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
) {
|
||||
TextField(
|
||||
model = tokenForm.contractAddress,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
imeAction = ImeAction.Next,
|
||||
),
|
||||
)
|
||||
TextField(
|
||||
model = tokenForm.name,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
imeAction = ImeAction.Next,
|
||||
),
|
||||
)
|
||||
TextField(
|
||||
model = tokenForm.symbol,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
imeAction = ImeAction.Next,
|
||||
),
|
||||
)
|
||||
TextField(
|
||||
model = tokenForm.decimals,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Decimal,
|
||||
imeAction = ImeAction.Next,
|
||||
),
|
||||
)
|
||||
tokenForm.fields.values.forEach { field ->
|
||||
TextField(model = field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextField(
|
||||
model: TextInputFieldUM,
|
||||
modifier: Modifier = Modifier,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
) {
|
||||
var isFocused by remember { mutableStateOf(value = false) }
|
||||
|
||||
private fun TextField(model: TextInputFieldUM, modifier: Modifier = Modifier) {
|
||||
InformationBlock(
|
||||
modifier = modifier,
|
||||
title = {
|
||||
|
|
@ -173,7 +143,7 @@ private fun TextField(
|
|||
model.error != null -> {
|
||||
TangemTheme.colors.text.warning
|
||||
}
|
||||
model.value.isNotBlank() || isFocused -> {
|
||||
model.value.isNotBlank() || model.isFocused -> {
|
||||
TangemTheme.colors.text.tertiary
|
||||
}
|
||||
else -> {
|
||||
|
|
@ -204,16 +174,15 @@ private fun TextField(
|
|||
.padding(bottom = TangemTheme.dimens.spacing12)
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
isFocused = it.isFocused
|
||||
model.onFocusChange(it.isFocused)
|
||||
},
|
||||
value = model.value,
|
||||
color = color,
|
||||
onValueChange = model.onValueChange,
|
||||
placeholder = model.placeholder,
|
||||
readOnly = !model.isEnabled && !isFocused,
|
||||
readOnly = !model.isEnabled && !model.isFocused,
|
||||
singleLine = true,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
keyboardOptions = model.keyboardOptions,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -262,25 +231,31 @@ private class PreviewCustomTokenFormComponentProvider :
|
|||
override val values: Sequence<PreviewCustomTokenFormComponent>
|
||||
get() = sequenceOf(
|
||||
PreviewCustomTokenFormComponent(
|
||||
tokenForm = PreviewCustomTokenFormComponent.tokenForm.copy(
|
||||
contractAddress = TextInputFieldUM(
|
||||
label = stringReference("Contract address"),
|
||||
value = "0x1234567890",
|
||||
placeholder = stringReference("0x1234567890"),
|
||||
onValueChange = {},
|
||||
),
|
||||
),
|
||||
tokenForm = PreviewCustomTokenFormComponent.tokenForm.let { form ->
|
||||
form.copy(
|
||||
fields = form.fields.mutate {
|
||||
it[Field.CONTRACT_ADDRESS] = it[Field.CONTRACT_ADDRESS]!!.copy(
|
||||
label = stringReference("Contract address"),
|
||||
value = "0x1234567890",
|
||||
placeholder = stringReference("0x1234567890"),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
PreviewCustomTokenFormComponent(
|
||||
tokenForm = PreviewCustomTokenFormComponent.tokenForm.copy(
|
||||
contractAddress = TextInputFieldUM(
|
||||
label = stringReference("Contract address"),
|
||||
value = "0x1234567890",
|
||||
error = stringReference("Contract address is invalid"),
|
||||
placeholder = stringReference("0x1234567890"),
|
||||
onValueChange = {},
|
||||
),
|
||||
),
|
||||
tokenForm = PreviewCustomTokenFormComponent.tokenForm.let { form ->
|
||||
form.copy(
|
||||
fields = form.fields.mutate {
|
||||
it[Field.CONTRACT_ADDRESS] = it[Field.CONTRACT_ADDRESS]!!.copy(
|
||||
label = stringReference("Contract address"),
|
||||
value = "0x1234567890",
|
||||
error = stringReference("Contract address is invalid"),
|
||||
placeholder = stringReference("0x1234567890"),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
PreviewCustomTokenFormComponent(
|
||||
tokenForm = null,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
|||
import com.tangem.core.ui.components.rows.ChainRow
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -271,14 +270,15 @@ private class CustomTokenNetworkSelectorComponentPreviewProvider :
|
|||
userWalletId = UserWalletId(stringValue = "321"),
|
||||
selectedNetwork = SelectedNetwork(
|
||||
id = Network.ID(value = "0"),
|
||||
name = stringReference("Ethereum"),
|
||||
name = "Ethereum",
|
||||
derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/0"),
|
||||
canHandleTokens = true,
|
||||
),
|
||||
selectedDerivationPath = SelectedDerivationPath(
|
||||
id = Network.ID(value = "0"),
|
||||
value = Network.DerivationPath.Card("m/44'/0'/0'/0/0"),
|
||||
networkName = stringReference(""),
|
||||
name = "",
|
||||
isDefault = false,
|
||||
),
|
||||
onDerivationPathSelected = {},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
package com.tangem.features.managetokens.utils
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.managetokens.component.CustomTokenFormComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
|
||||
import com.tangem.features.managetokens.entity.customtoken.TextInputFieldUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
internal class CustomCurrencyFormBuilder @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
) {
|
||||
|
||||
private val params: CustomTokenFormComponent.Params = paramsContainer.require()
|
||||
|
||||
fun buildForm(
|
||||
updateFormFieldValue: (Field, String) -> Unit,
|
||||
updateFormFieldFocus: (Field, Boolean) -> Unit,
|
||||
): CustomTokenFormUM.TokenFormUM {
|
||||
val formValues = params.formValues
|
||||
val fields = persistentMapOf(
|
||||
Field.CONTRACT_ADDRESS to TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_contract_address_input_title),
|
||||
placeholder = stringReference(CONTRACT_ADDRESS_PLACEHOLDER),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
keyboardType = KeyboardType.Text,
|
||||
),
|
||||
onValueChange = { value ->
|
||||
updateFormFieldValue(Field.CONTRACT_ADDRESS, value)
|
||||
},
|
||||
onFocusChange = { isFocused ->
|
||||
updateFormFieldFocus(Field.CONTRACT_ADDRESS, isFocused)
|
||||
},
|
||||
),
|
||||
Field.NAME to TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_name_input_title),
|
||||
placeholder = resourceReference(R.string.custom_token_name_input_placeholder),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Words,
|
||||
keyboardType = KeyboardType.Text,
|
||||
),
|
||||
onValueChange = { value ->
|
||||
updateFormFieldValue(Field.NAME, value)
|
||||
},
|
||||
onFocusChange = { isFocused ->
|
||||
updateFormFieldFocus(Field.NAME, isFocused)
|
||||
},
|
||||
),
|
||||
Field.SYMBOL to TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_token_symbol_input_title),
|
||||
placeholder = resourceReference(R.string.custom_token_token_symbol_input_placeholder),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Characters,
|
||||
keyboardType = KeyboardType.Text,
|
||||
),
|
||||
onValueChange = { value ->
|
||||
updateFormFieldValue(Field.SYMBOL, value)
|
||||
},
|
||||
onFocusChange = { isFocused ->
|
||||
updateFormFieldFocus(Field.SYMBOL, isFocused)
|
||||
},
|
||||
),
|
||||
Field.DECIMALS to TextInputFieldUM(
|
||||
label = resourceReference(R.string.custom_token_decimals_input_title),
|
||||
placeholder = stringReference(DECIMALS_PLACEHOLDER),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||
onValueChange = { value ->
|
||||
updateFormFieldValue(Field.DECIMALS, value)
|
||||
},
|
||||
onFocusChange = { isFocused ->
|
||||
updateFormFieldFocus(Field.DECIMALS, isFocused)
|
||||
},
|
||||
),
|
||||
)
|
||||
val form = CustomTokenFormUM.TokenFormUM(fields)
|
||||
|
||||
return formValues.fillValues(form)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CONTRACT_ADDRESS_PLACEHOLDER = "0x000000000000000000000000000..."
|
||||
const val DECIMALS_PLACEHOLDER = "0"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,9 @@ import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
|
|||
|
||||
internal fun CustomTokenFormUM.TokenFormUM.mapToDomainModel(): AddCustomTokenForm.Raw {
|
||||
return AddCustomTokenForm.Raw(
|
||||
contractAddress = contractAddress.value,
|
||||
symbol = symbol.value,
|
||||
name = name.value,
|
||||
decimals = decimals.value,
|
||||
contractAddress = fields.getValue(CustomTokenFormUM.TokenFormUM.Field.CONTRACT_ADDRESS).value,
|
||||
symbol = fields.getValue(CustomTokenFormUM.TokenFormUM.Field.SYMBOL).value,
|
||||
name = fields.getValue(CustomTokenFormUM.TokenFormUM.Field.NAME).value,
|
||||
decimals = fields.getValue(CustomTokenFormUM.TokenFormUM.Field.DECIMALS).value,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
package com.tangem.features.managetokens.utils.ui
|
||||
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.managetokens.ValidateTokenFormUseCase
|
||||
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.TextInputFieldUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
|
||||
internal fun CustomTokenFormUM.updateTokenForm(
|
||||
block: CustomTokenFormUM.TokenFormUM.() -> CustomTokenFormUM.TokenFormUM,
|
||||
|
|
@ -23,19 +23,6 @@ internal fun CustomTokenFormUM.updateTokenForm(
|
|||
return copy(tokenForm = updatedForm)
|
||||
}
|
||||
|
||||
internal fun TextInputFieldUM.updateValue(
|
||||
value: String = this.value,
|
||||
error: TextReference? = this.error,
|
||||
isEnabled: Boolean = this.isEnabled,
|
||||
clearError: Boolean = false,
|
||||
): TextInputFieldUM {
|
||||
return copy(
|
||||
value = value,
|
||||
error = if (clearError) null else error,
|
||||
isEnabled = isEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun CustomTokenFormUM.updateWithProgress(
|
||||
showProgress: Boolean,
|
||||
isWasFilled: Boolean = this.tokenForm?.wasFilled ?: false,
|
||||
|
|
@ -49,22 +36,21 @@ internal fun CustomTokenFormUM.updateWithProgress(
|
|||
canAddToken = canAddToken,
|
||||
notifications = if (clearNotifications) persistentListOf() else notifications,
|
||||
).updateTokenForm {
|
||||
val updatedFields = fields.mapValues { (key, field) ->
|
||||
field.copy(
|
||||
isEnabled = when (key) {
|
||||
Field.CONTRACT_ADDRESS -> field.isEnabled
|
||||
Field.NAME,
|
||||
Field.SYMBOL,
|
||||
Field.DECIMALS,
|
||||
-> !(showProgress || disableSecondaryFields)
|
||||
},
|
||||
error = if (clearFieldErrors) null else field.error,
|
||||
)
|
||||
}
|
||||
|
||||
copy(
|
||||
contractAddress = contractAddress.updateValue(
|
||||
clearError = clearFieldErrors,
|
||||
),
|
||||
name = name.updateValue(
|
||||
isEnabled = !showProgress && !disableSecondaryFields,
|
||||
clearError = clearFieldErrors,
|
||||
),
|
||||
symbol = symbol.updateValue(
|
||||
isEnabled = !showProgress && !disableSecondaryFields,
|
||||
clearError = clearFieldErrors,
|
||||
),
|
||||
decimals = decimals.updateValue(
|
||||
isEnabled = !showProgress && !disableSecondaryFields,
|
||||
clearError = clearFieldErrors,
|
||||
),
|
||||
fields = updatedFields.toPersistentMap(),
|
||||
wasFilled = isWasFilled,
|
||||
)
|
||||
}
|
||||
|
|
@ -72,11 +58,31 @@ internal fun CustomTokenFormUM.updateWithProgress(
|
|||
|
||||
internal fun CustomTokenFormUM.updateWithCurrency(currency: CryptoCurrency): CustomTokenFormUM {
|
||||
return updateTokenForm {
|
||||
val updatedFields = fields.mapValues { (key, field) ->
|
||||
when (key) {
|
||||
Field.CONTRACT_ADDRESS -> field.copy(
|
||||
error = null,
|
||||
)
|
||||
Field.NAME -> field.copy(
|
||||
value = currency.name,
|
||||
error = null,
|
||||
isEnabled = false,
|
||||
)
|
||||
Field.SYMBOL -> field.copy(
|
||||
value = currency.symbol,
|
||||
error = null,
|
||||
isEnabled = false,
|
||||
)
|
||||
Field.DECIMALS -> field.copy(
|
||||
value = currency.decimals.toString(),
|
||||
error = null,
|
||||
isEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
copy(
|
||||
contractAddress = contractAddress.updateValue(error = null),
|
||||
name = name.updateValue(currency.name),
|
||||
symbol = symbol.updateValue(currency.symbol),
|
||||
decimals = decimals.updateValue(currency.decimals.toString()),
|
||||
fields = updatedFields.toPersistentMap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -85,18 +91,20 @@ internal fun CustomTokenFormUM.updateWithContractAddressException(
|
|||
exception: CustomTokenFormValidationException.ContractAddress,
|
||||
): CustomTokenFormUM {
|
||||
return updateTokenForm {
|
||||
copy(
|
||||
contractAddress = contractAddress.updateValue(
|
||||
val updatedFields = fields.mutate {
|
||||
it[Field.CONTRACT_ADDRESS] = it.getValue(Field.CONTRACT_ADDRESS).copy(
|
||||
error = when (exception) {
|
||||
CustomTokenFormValidationException.ContractAddress.Empty -> {
|
||||
null
|
||||
null // Should not display this error
|
||||
}
|
||||
CustomTokenFormValidationException.ContractAddress.Invalid -> {
|
||||
resourceReference(R.string.custom_token_creation_error_invalid_contract_address)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
copy(fields = updatedFields)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,11 +112,11 @@ internal fun CustomTokenFormUM.updateWithDecimalsException(
|
|||
exception: CustomTokenFormValidationException.Decimals,
|
||||
): CustomTokenFormUM {
|
||||
return updateTokenForm {
|
||||
copy(
|
||||
decimals = decimals.updateValue(
|
||||
val updatedFields = fields.mutate {
|
||||
it[Field.DECIMALS] = it.getValue(Field.DECIMALS).copy(
|
||||
error = when (exception) {
|
||||
is CustomTokenFormValidationException.Decimals.Empty -> {
|
||||
null
|
||||
null // Should not display this error
|
||||
}
|
||||
is CustomTokenFormValidationException.Decimals.Invalid -> {
|
||||
resourceReference(
|
||||
|
|
@ -117,8 +125,10 @@ internal fun CustomTokenFormUM.updateWithDecimalsException(
|
|||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
copy(fields = updatedFields)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue