Updated on 2026-08-14
This commit is contained in:
commit
47e284436a
21 changed files with 523 additions and 78 deletions
|
|
@ -3,12 +3,13 @@ package com.tangem.features.managetokens.component
|
|||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableDialogComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
|
||||
|
||||
internal interface CustomTokenDerivationInputComponent : ComposableDialogComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val onConfirm: (String) -> Unit,
|
||||
val onConfirm: (SelectedDerivationPath) -> Unit,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ internal interface CustomTokenFormComponent : ComposableContentComponent {
|
|||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val network: SelectedNetwork,
|
||||
val derivationPath: SelectedDerivationPath,
|
||||
val derivationPath: SelectedDerivationPath?,
|
||||
val formValues: CustomTokenFormValues,
|
||||
val onSelectNetworkClick: (CustomTokenFormValues) -> Unit,
|
||||
val onSelectDerivationPathClick: (CustomTokenFormValues) -> Unit,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ internal interface CustomTokenSelectorComponent : ComposableContentComponent {
|
|||
|
||||
data class DerivationPathSelector(
|
||||
val userWalletId: UserWalletId,
|
||||
val selectedNetwork: SelectedNetwork,
|
||||
val selectedDerivationPath: SelectedDerivationPath?,
|
||||
val onDerivationPathSelected: (SelectedDerivationPath) -> Unit,
|
||||
) : Params()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ 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.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.managetokens.component.AddCustomTokenComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenFormComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
|
|
@ -20,7 +19,6 @@ import com.tangem.features.managetokens.entity.customtoken.AddCustomTokenConfig
|
|||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormValues
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.ui.AddCustomTokenBottomSheet
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -110,6 +108,9 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
|
|||
context = childByContext(componentContext),
|
||||
params = CustomTokenSelectorComponent.Params.DerivationPathSelector(
|
||||
userWalletId = config.userWalletId,
|
||||
selectedNetwork = requireNotNull(config.selectedNetwork) {
|
||||
"Network is not selected"
|
||||
},
|
||||
selectedDerivationPath = config.selectedDerivationPath,
|
||||
onDerivationPathSelected = { derivationPath ->
|
||||
showForm(derivationPath = derivationPath)
|
||||
|
|
@ -122,12 +123,10 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
|
|||
context = childByContext(componentContext),
|
||||
params = CustomTokenFormComponent.Params(
|
||||
userWalletId = config.userWalletId,
|
||||
network = config.selectedNetwork ?: error("Network is not selected"),
|
||||
derivationPath = config.selectedDerivationPath ?: SelectedDerivationPath(
|
||||
id = config.selectedNetwork.id,
|
||||
value = config.selectedNetwork.derivationPath,
|
||||
networkName = resourceReference(R.string.custom_token_derivation_path_default),
|
||||
),
|
||||
network = requireNotNull(config.selectedNetwork) {
|
||||
"Network is not selected"
|
||||
},
|
||||
derivationPath = config.selectedDerivationPath,
|
||||
formValues = config.formValues,
|
||||
onSelectNetworkClick = ::showNetworkSelector,
|
||||
onSelectDerivationPathClick = ::showDerivationPathSelector,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
package com.tangem.features.managetokens.component.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
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
|
||||
import com.tangem.features.managetokens.component.CustomTokenDerivationInputComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomDerivationInputUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.ui.dialog.CustomDerivationInputDialog
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
internal class DefaultCustomTokenDerivationInputComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: CustomTokenDerivationInputComponent.Params,
|
||||
private val validateDerivationPathUseCase: ValidateDerivationPathUseCase,
|
||||
) : CustomTokenDerivationInputComponent, AppComponentContext by context {
|
||||
|
||||
private val state: MutableStateFlow<CustomDerivationInputUM> = MutableStateFlow(
|
||||
value = getInitialState(),
|
||||
)
|
||||
|
||||
init {
|
||||
observeValueUpdates()
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Dialog() {
|
||||
val state by state.collectAsStateWithLifecycle()
|
||||
|
||||
CustomDerivationInputDialog(
|
||||
model = state,
|
||||
onDismiss = ::dismiss,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun observeValueUpdates() {
|
||||
state
|
||||
.map { it.value.text }
|
||||
.distinctUntilChanged()
|
||||
.sample(periodMillis = 1_000)
|
||||
.onEach(::validateValue)
|
||||
.launchIn(componentScope)
|
||||
}
|
||||
|
||||
private fun getInitialState(): CustomDerivationInputUM = CustomDerivationInputUM(
|
||||
value = TextFieldValue(),
|
||||
error = null,
|
||||
updateValue = ::updateValue,
|
||||
isConfirmEnabled = false,
|
||||
onConfirm = ::confirm,
|
||||
)
|
||||
|
||||
private fun validateValue(value: String) {
|
||||
validateDerivationPathUseCase(value).getOrElse { e ->
|
||||
updateWithValidationError(e)
|
||||
return
|
||||
}
|
||||
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
error = null,
|
||||
isConfirmEnabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWithValidationError(e: DerivationPathValidationException) {
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
error = when (e) {
|
||||
DerivationPathValidationException.Empty -> null
|
||||
DerivationPathValidationException.Invalid -> {
|
||||
resourceReference(R.string.custom_token_invalid_derivation_path)
|
||||
}
|
||||
},
|
||||
isConfirmEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateValue(value: TextFieldValue) {
|
||||
state.update { state ->
|
||||
state.copy(value = value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun confirm() {
|
||||
if (state.value.error != null && !state.value.isConfirmEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
val value = state.value.value.text
|
||||
val model = SelectedDerivationPath(
|
||||
id = null,
|
||||
value = Network.DerivationPath.Custom(value),
|
||||
networkName = stringReference(value = value),
|
||||
)
|
||||
|
||||
params.onConfirm(model)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : CustomTokenDerivationInputComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: CustomTokenDerivationInputComponent.Params,
|
||||
): DefaultCustomTokenDerivationInputComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -4,33 +4,61 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableDialogComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenDerivationInputComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.component.preview.PreviewCustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenSelectorUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenSelectorDialogConfig
|
||||
import com.tangem.features.managetokens.model.CustomTokenSelectorModel
|
||||
import com.tangem.features.managetokens.ui.CustomTokenSelectorContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
internal class DefaultCustomTokenSelectorComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted params: CustomTokenSelectorComponent.Params,
|
||||
private val customTokenDerivationInputComponentFactory: CustomTokenDerivationInputComponent.Factory,
|
||||
) : CustomTokenSelectorComponent, AppComponentContext by context {
|
||||
|
||||
private val state: MutableStateFlow<CustomTokenSelectorUM> = MutableStateFlow(
|
||||
value = PreviewCustomTokenSelectorComponent(params = params).previewState,
|
||||
private val model: CustomTokenSelectorModel = getOrCreateModel(params)
|
||||
private val dialogSlot = childSlot(
|
||||
source = model.dialogNavigation,
|
||||
serializer = CustomTokenSelectorDialogConfig.serializer(),
|
||||
childFactory = ::createDialog,
|
||||
)
|
||||
|
||||
private fun createDialog(
|
||||
config: CustomTokenSelectorDialogConfig,
|
||||
context: ComponentContext,
|
||||
): ComposableDialogComponent = when (config) {
|
||||
is CustomTokenSelectorDialogConfig.CustomDerivationInput -> customTokenDerivationInputComponentFactory.create(
|
||||
context = childByContext(context),
|
||||
params = CustomTokenDerivationInputComponent.Params(
|
||||
userWalletId = config.userWalletId,
|
||||
onConfirm = model::selectCustomDerivationPath,
|
||||
onDismiss = model.dialogNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by state.collectAsStateWithLifecycle()
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val dialog by dialogSlot.subscribeAsState()
|
||||
|
||||
CustomTokenSelectorContent(
|
||||
modifier = modifier,
|
||||
model = state,
|
||||
)
|
||||
|
||||
dialog.child?.instance?.Dialog()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.features.managetokens.component.AddCustomTokenComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.managetokens.BottomSheetConfig
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensBottomSheetConfig
|
||||
import com.tangem.features.managetokens.model.ManageTokensModel
|
||||
import com.tangem.features.managetokens.ui.ManageTokensScreen
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -32,7 +32,7 @@ internal class DefaultManageTokensComponent @AssistedInject constructor(
|
|||
|
||||
private val bottomSheetSlot = childSlot(
|
||||
source = model.bottomSheetNavigation,
|
||||
serializer = BottomSheetConfig.serializer(),
|
||||
serializer = ManageTokensBottomSheetConfig.serializer(),
|
||||
handleBackButton = false,
|
||||
childFactory = ::bottomSheetChild,
|
||||
)
|
||||
|
|
@ -53,10 +53,10 @@ internal class DefaultManageTokensComponent @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun bottomSheetChild(
|
||||
config: BottomSheetConfig,
|
||||
config: ManageTokensBottomSheetConfig,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent = when (config) {
|
||||
is BottomSheetConfig.AddCustomToken -> {
|
||||
is ManageTokensBottomSheetConfig.AddCustomToken -> {
|
||||
addCustomTokenComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = AddCustomTokenComponent.Params(
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ internal class PreviewAddCustomTokenComponent(
|
|||
PreviewCustomTokenSelectorComponent(
|
||||
params = CustomTokenSelectorComponent.Params.DerivationPathSelector(
|
||||
userWalletId = state.userWalletId,
|
||||
selectedDerivationPath = state.selectedDerivationPath,
|
||||
selectedNetwork = state.selectedNetwork!!,
|
||||
selectedDerivationPath = state.selectedDerivationPath!!,
|
||||
onDerivationPathSelected = {},
|
||||
),
|
||||
).Content(modifier)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ internal class PreviewCustomTokenSelectorComponent(
|
|||
)
|
||||
|
||||
DerivationPathUM(
|
||||
id = d.id.value,
|
||||
id = d.id?.value ?: "",
|
||||
value = d.value.value.orEmpty(),
|
||||
networkName = d.networkName,
|
||||
isSelected = d.value == params.selectedDerivationPath?.value,
|
||||
|
|
@ -75,7 +75,10 @@ internal class PreviewCustomTokenSelectorComponent(
|
|||
|
||||
val previewState = CustomTokenSelectorUM(
|
||||
header = when (params) {
|
||||
is Params.DerivationPathSelector -> CustomTokenSelectorUM.HeaderUM.CustomDerivationButton({})
|
||||
is Params.DerivationPathSelector -> CustomTokenSelectorUM.HeaderUM.CustomDerivationButton(
|
||||
value = null,
|
||||
onClick = {},
|
||||
)
|
||||
is Params.NetworkSelector -> if (params.selectedNetwork == null) {
|
||||
CustomTokenSelectorUM.HeaderUM.Description
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
package com.tangem.features.managetokens.di
|
||||
|
||||
import com.tangem.features.managetokens.component.AddCustomTokenComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenFormComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.impl.DefaultAddCustomTokenComponent
|
||||
import com.tangem.features.managetokens.component.impl.DefaultCustomTokenFormComponent
|
||||
import com.tangem.features.managetokens.component.impl.DefaultCustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.component.impl.DefaultManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.*
|
||||
import com.tangem.features.managetokens.component.impl.*
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -39,4 +33,10 @@ internal interface ComponentModule {
|
|||
fun bindCustomTokenFormComponentFactory(
|
||||
factory: DefaultCustomTokenFormComponent.Factory,
|
||||
): CustomTokenFormComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindCustomTokenDerivationInputComponentFactory(
|
||||
factory: DefaultCustomTokenDerivationInputComponent.Factory,
|
||||
): CustomTokenDerivationInputComponent.Factory
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.features.managetokens.di
|
|||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.managetokens.model.CustomTokenFormModel
|
||||
import com.tangem.features.managetokens.model.CustomTokenSelectorModel
|
||||
import com.tangem.features.managetokens.model.ManageTokensModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -17,4 +19,14 @@ internal interface ModelModule {
|
|||
@IntoMap
|
||||
@ClassKey(ManageTokensModel::class)
|
||||
fun provideManageTokensModel(model: ManageTokensModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(CustomTokenFormModel::class)
|
||||
fun provideCustomTokenFormModel(model: CustomTokenFormModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(CustomTokenSelectorModel::class)
|
||||
fun provideCustomTokenSelectorModel(model: CustomTokenSelectorModel): Model
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ internal data class SelectedNetwork(
|
|||
|
||||
@Serializable
|
||||
internal data class SelectedDerivationPath(
|
||||
val id: Network.ID,
|
||||
val id: Network.ID?,
|
||||
val value: Network.DerivationPath,
|
||||
val networkName: TextReference,
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.managetokens.entity.customtoken
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed class CustomTokenSelectorDialogConfig {
|
||||
|
||||
@Serializable
|
||||
data class CustomDerivationInput(
|
||||
val userWalletId: UserWalletId,
|
||||
) : CustomTokenSelectorDialogConfig()
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ internal data class CustomTokenSelectorUM(
|
|||
data object Description : HeaderUM()
|
||||
|
||||
data class CustomDerivationButton(
|
||||
val value: String?,
|
||||
val onClick: () -> Unit,
|
||||
) : HeaderUM()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed class BottomSheetConfig {
|
||||
internal sealed class ManageTokensBottomSheetConfig {
|
||||
|
||||
@Serializable
|
||||
data class AddCustomToken(
|
||||
val userWalletId: UserWalletId,
|
||||
) : BottomSheetConfig()
|
||||
) : ManageTokensBottomSheetConfig()
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import com.tangem.domain.card.DerivePublicKeysUseCase
|
|||
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
|
||||
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.component.CustomTokenFormComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.ClickableFieldUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
|
||||
|
|
@ -57,7 +58,7 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
customCurrencyValidator.createCoin(
|
||||
userWalletId = params.userWalletId,
|
||||
networkId = params.network.id,
|
||||
derivationPath = params.derivationPath.value,
|
||||
derivationPath = getDerivationPath(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +78,7 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
},
|
||||
derivationPath = ClickableFieldUM(
|
||||
label = resourceReference(R.string.custom_token_derivation_path),
|
||||
value = if (params.derivationPath.id == params.network.id) {
|
||||
value = if (params.derivationPath == null || params.derivationPath.id == params.network.id) {
|
||||
resourceReference(R.string.custom_token_derivation_path_default)
|
||||
} else {
|
||||
params.derivationPath.networkName
|
||||
|
|
@ -104,7 +105,7 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
customCurrencyValidator.validateForm(
|
||||
userWalletId = params.userWalletId,
|
||||
networkId = params.network.id,
|
||||
derivationPath = params.derivationPath.value,
|
||||
derivationPath = getDerivationPath(),
|
||||
formValues = formValues,
|
||||
)
|
||||
}
|
||||
|
|
@ -266,6 +267,10 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
return formValues.fillValues(form)
|
||||
}
|
||||
|
||||
private fun getDerivationPath(): Network.DerivationPath {
|
||||
return params.derivationPath?.value ?: params.network.derivationPath
|
||||
}
|
||||
|
||||
private fun updateContractAddress(value: String) {
|
||||
state.update { state ->
|
||||
state.updateTokenForm {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
package com.tangem.features.managetokens.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
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
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent.Params.DerivationPathSelector
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent.Params.NetworkSelector
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenSelectorDialogConfig
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenSelectorUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenSelectorUM.HeaderUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
|
||||
import com.tangem.features.managetokens.entity.item.DerivationPathUM
|
||||
import com.tangem.features.managetokens.entity.item.SelectableItemUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.utils.mapper.toCurrencyNetworkModel
|
||||
import com.tangem.features.managetokens.utils.mapper.toDerivationPathModel
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
internal class CustomTokenSelectorModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSupportedNetworksUseCase: GetSupportedNetworksUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: CustomTokenSelectorComponent.Params = paramsContainer.require()
|
||||
|
||||
val dialogNavigation: SlotNavigation<CustomTokenSelectorDialogConfig> = SlotNavigation()
|
||||
|
||||
val state: MutableStateFlow<CustomTokenSelectorUM> = MutableStateFlow(
|
||||
value = getInitialState(),
|
||||
)
|
||||
|
||||
init {
|
||||
loadItems()
|
||||
}
|
||||
|
||||
private fun getInitialState(): CustomTokenSelectorUM = when (params) {
|
||||
is NetworkSelector -> CustomTokenSelectorUM(
|
||||
header = if (params.selectedNetwork == null) {
|
||||
HeaderUM.Description
|
||||
} else {
|
||||
HeaderUM.None
|
||||
},
|
||||
items = persistentListOf(),
|
||||
)
|
||||
is DerivationPathSelector -> CustomTokenSelectorUM(
|
||||
header = HeaderUM.CustomDerivationButton(
|
||||
value = (params.selectedDerivationPath?.value as? Network.DerivationPath.Custom)?.value,
|
||||
onClick = ::showCustomDerivationInput,
|
||||
),
|
||||
items = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadItems() = modelScope.launch {
|
||||
val items = when (params) {
|
||||
is NetworkSelector -> loadNetworks(params).toImmutableList()
|
||||
is DerivationPathSelector -> loadDerivationPaths(params).toImmutableList()
|
||||
}
|
||||
|
||||
state.update { state ->
|
||||
state.copy(items = items)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadNetworks(selector: NetworkSelector): List<SelectableItemUM> {
|
||||
return getSupportedNetworks(selector.userWalletId).map { network ->
|
||||
network.toCurrencyNetworkModel(
|
||||
isSelected = network.id == selector.selectedNetwork?.id,
|
||||
onSelectedStateChange = {
|
||||
val model = SelectedNetwork(
|
||||
id = network.id,
|
||||
name = stringReference(network.name),
|
||||
derivationPath = network.derivationPath,
|
||||
canHandleTokens = network.canHandleTokens,
|
||||
)
|
||||
|
||||
selector.onNetworkSelected(model)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadDerivationPaths(selector: DerivationPathSelector): List<SelectableItemUM> {
|
||||
val derivationPaths = mutableListOf<DerivationPathUM>()
|
||||
val defaultPath = selector.selectedNetwork.let { network ->
|
||||
network.toDerivationPathModel(
|
||||
isSelected = network.id == selector.selectedDerivationPath?.id,
|
||||
onSelectedStateChange = {
|
||||
val model = SelectedDerivationPath(
|
||||
id = network.id,
|
||||
networkName = resourceReference(R.string.custom_token_derivation_path_default),
|
||||
value = network.derivationPath,
|
||||
)
|
||||
|
||||
selector.onDerivationPathSelected(model)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (defaultPath != null) {
|
||||
derivationPaths.add(defaultPath)
|
||||
}
|
||||
|
||||
getSupportedNetworks(selector.userWalletId)
|
||||
.mapNotNullTo(derivationPaths) { network ->
|
||||
if (network.id == selector.selectedNetwork.id) {
|
||||
return@mapNotNullTo null // Skip default path
|
||||
}
|
||||
|
||||
network.toDerivationPathModel(
|
||||
isSelected = network.id == selector.selectedDerivationPath?.id,
|
||||
onSelectedStateChange = {
|
||||
val model = SelectedDerivationPath(
|
||||
id = network.id,
|
||||
networkName = stringReference(network.name),
|
||||
value = network.derivationPath,
|
||||
)
|
||||
|
||||
selector.onDerivationPathSelected(model)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return derivationPaths
|
||||
}
|
||||
|
||||
private suspend fun getSupportedNetworks(userWalletId: UserWalletId): List<Network> {
|
||||
return getSupportedNetworksUseCase(userWalletId).getOrElse { e ->
|
||||
val message = SnackbarMessage(message = resourceReference(R.string.common_unknown_error))
|
||||
messageSender.send(message)
|
||||
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCustomDerivationInput() {
|
||||
val config = when (params) {
|
||||
is NetworkSelector -> return
|
||||
is DerivationPathSelector -> CustomTokenSelectorDialogConfig.CustomDerivationInput(params.userWalletId)
|
||||
}
|
||||
|
||||
dialogNavigation.activate(config)
|
||||
}
|
||||
|
||||
fun selectCustomDerivationPath(value: SelectedDerivationPath) {
|
||||
when (params) {
|
||||
is NetworkSelector -> return
|
||||
is DerivationPathSelector -> params.onDerivationPathSelected(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ import com.tangem.domain.managetokens.SaveManagedTokensUseCase
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.managetokens.BottomSheetConfig
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensBottomSheetConfig
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
|
|
@ -47,7 +47,7 @@ internal class ManageTokensModel @Inject constructor(
|
|||
private val params: ManageTokensComponent.Params = paramsContainer.require()
|
||||
|
||||
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(getInitialState(params.userWalletId))
|
||||
val bottomSheetNavigation: SlotNavigation<BottomSheetConfig> = SlotNavigation()
|
||||
val bottomSheetNavigation: SlotNavigation<ManageTokensBottomSheetConfig> = SlotNavigation()
|
||||
|
||||
init {
|
||||
manageTokensListManager.uiItems
|
||||
|
|
@ -228,7 +228,7 @@ internal class ManageTokensModel @Inject constructor(
|
|||
|
||||
private fun navigateToAddCustomToken() {
|
||||
params.userWalletId?.let {
|
||||
bottomSheetNavigation.activate(BottomSheetConfig.AddCustomToken(it))
|
||||
bottomSheetNavigation.activate(ManageTokensBottomSheetConfig.AddCustomToken(it))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
|||
import com.tangem.features.managetokens.component.preview.PreviewCustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenSelectorUM
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM
|
||||
import com.tangem.features.managetokens.entity.item.DerivationPathUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
|
|
@ -110,6 +111,8 @@ private fun Header(header: CustomTokenSelectorUM.HeaderUM, modifier: Modifier =
|
|||
is CustomTokenSelectorUM.HeaderUM.CustomDerivationButton -> {
|
||||
CustomDerivationButton(
|
||||
modifier = modifier.padding(vertical = TangemTheme.dimens.spacing16),
|
||||
enteredDerivationPath = header.value,
|
||||
isSelected = header.value != null,
|
||||
onClick = header.onClick,
|
||||
)
|
||||
}
|
||||
|
|
@ -164,38 +167,41 @@ private fun NetworkItem(model: CurrencyNetworkUM, modifier: Modifier = Modifier)
|
|||
)
|
||||
},
|
||||
action = {
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
visible = model.isSelected,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_check_24),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
SelectedIcon(isVisible = model.isSelected)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CustomDerivationButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
private fun CustomDerivationButton(
|
||||
enteredDerivationPath: String?,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
InformationBlock(
|
||||
modifier = modifier.clickable(onClick = onClick),
|
||||
modifier = modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.clickable(onClick = onClick),
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.custom_token_custom_derivation),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.custom_token_custom_derivation),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
||||
text = enteredDerivationPath ?: stringResource(id = R.string.custom_token_custom_derivation_title),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
},
|
||||
content = {
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
||||
text = stringResource(id = R.string.custom_token_custom_derivation_title),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
action = {
|
||||
SelectedIcon(isVisible = isSelected)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -206,23 +212,43 @@ private fun DerivationPathItem(model: DerivationPathUM, modifier: Modifier = Mod
|
|||
modifier = modifier,
|
||||
shape = RectangleShape,
|
||||
title = {
|
||||
Text(
|
||||
text = model.networkName.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
Text(
|
||||
text = model.networkName.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
||||
text = model.value,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
},
|
||||
content = {
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
||||
text = model.value,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
action = {
|
||||
SelectedIcon(isVisible = model.isSelected)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SelectedIcon(isVisible: Boolean, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier.size(TangemTheme.dimens.size24),
|
||||
visible = isVisible,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_check_24),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
|
|
@ -243,6 +269,12 @@ private class CustomTokenNetworkSelectorComponentPreviewProvider :
|
|||
PreviewCustomTokenSelectorComponent(
|
||||
params = CustomTokenSelectorComponent.Params.DerivationPathSelector(
|
||||
userWalletId = UserWalletId(stringValue = "321"),
|
||||
selectedNetwork = SelectedNetwork(
|
||||
id = Network.ID(value = "0"),
|
||||
name = stringReference("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"),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.managetokens.utils.mapper
|
|||
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM
|
||||
import com.tangem.features.managetokens.utils.ui.getIconRes
|
||||
|
|
@ -15,7 +16,7 @@ internal fun ManagedCryptoCurrency.Token.toUiNetworksModel(
|
|||
return if (isExpanded) {
|
||||
NetworksUM.Expanded(
|
||||
networks = availableNetworks.map {
|
||||
it.toUiModel(
|
||||
it.toCurrencyNetworkModel(
|
||||
isSelected = it.network in addedIn,
|
||||
isEditable = isItemsEditable,
|
||||
onSelectedStateChange = onSelectedStateChange,
|
||||
|
|
@ -27,7 +28,22 @@ internal fun ManagedCryptoCurrency.Token.toUiNetworksModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun SourceNetwork.toUiModel(
|
||||
internal fun Network.toCurrencyNetworkModel(
|
||||
isSelected: Boolean,
|
||||
onSelectedStateChange: (Boolean) -> Unit,
|
||||
): CurrencyNetworkUM {
|
||||
return CurrencyNetworkUM(
|
||||
network = this,
|
||||
name = name,
|
||||
iconResId = id.getIconRes(isColored = true),
|
||||
isSelected = isSelected,
|
||||
type = standardType.name,
|
||||
isMainNetwork = false,
|
||||
onSelectedStateChange = onSelectedStateChange,
|
||||
)
|
||||
}
|
||||
|
||||
private fun SourceNetwork.toCurrencyNetworkModel(
|
||||
isSelected: Boolean,
|
||||
isEditable: Boolean,
|
||||
onSelectedStateChange: (SourceNetwork, Boolean) -> Unit,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.managetokens.utils.mapper
|
||||
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
|
||||
import com.tangem.features.managetokens.entity.item.DerivationPathUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
|
||||
internal fun Network.toDerivationPathModel(
|
||||
isSelected: Boolean,
|
||||
onSelectedStateChange: (Boolean) -> Unit,
|
||||
): DerivationPathUM? {
|
||||
return DerivationPathUM(
|
||||
id = id.value,
|
||||
value = derivationPath.value ?: return null,
|
||||
networkName = stringReference(name),
|
||||
isSelected = isSelected,
|
||||
onSelectedStateChange = onSelectedStateChange,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun SelectedNetwork.toDerivationPathModel(
|
||||
isSelected: Boolean,
|
||||
onSelectedStateChange: (Boolean) -> Unit,
|
||||
): DerivationPathUM? {
|
||||
return DerivationPathUM(
|
||||
id = id.value,
|
||||
value = derivationPath.value ?: return null,
|
||||
networkName = resourceReference(R.string.custom_token_derivation_path_default),
|
||||
isSelected = isSelected,
|
||||
onSelectedStateChange = onSelectedStateChange,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue