Updated on 2026-08-14
This commit is contained in:
parent
4dc994e8cf
commit
03da1731ea
24 changed files with 335 additions and 92 deletions
|
|
@ -25,6 +25,8 @@ dependencies {
|
|||
implementation(projects.common.ui)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.manageTokens)
|
||||
|
|
@ -35,6 +37,14 @@ dependencies {
|
|||
implementation(projects.domain.swap.models)
|
||||
implementation(projects.domain.notifications)
|
||||
|
||||
// region Project - Libs
|
||||
implementation(projects.libs.crypto)
|
||||
// endregion
|
||||
|
||||
// region Tangem SDKs
|
||||
implementation(tangemDeps.blockchain)
|
||||
// endregion
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.lifecycle.compose)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ internal sealed class ManageTokensUM {
|
|||
isSavingInProgress: Boolean = this is ManageContent && this.isSavingInProgress,
|
||||
scrollToTop: StateEvent<Unit> = this.scrollToTop,
|
||||
needToInteractWithColdWallet: Boolean = this is ManageContent && this.needToInteractWithColdWallet,
|
||||
topBar: ManageTokensTopBarUM? = this.topBar,
|
||||
): ManageTokensUM {
|
||||
return when (this) {
|
||||
is ManageContent -> copy(
|
||||
|
|
@ -65,6 +66,7 @@ internal sealed class ManageTokensUM {
|
|||
isSavingInProgress = isSavingInProgress,
|
||||
scrollToTop = scrollToTop,
|
||||
needToInteractWithColdWallet = needToInteractWithColdWallet,
|
||||
topBar = topBar,
|
||||
)
|
||||
is ReadContent -> copy(
|
||||
search = search,
|
||||
|
|
@ -72,6 +74,7 @@ internal sealed class ManageTokensUM {
|
|||
isInitialBatchLoading = isInitialBatchLoading,
|
||||
isNextBatchLoading = isNextBatchLoading,
|
||||
scrollToTop = scrollToTop,
|
||||
topBar = topBar,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,22 @@ 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.blockchain.common.Blockchain
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
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.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.managetokens.GetSupportedNetworksUseCase
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.features.managetokens.component.AddCustomTokenMode
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
|
|
@ -25,10 +34,12 @@ 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.lib.crypto.derivation.AccountNodeRecognizer
|
||||
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.first
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -38,6 +49,8 @@ internal class CustomTokenSelectorModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSupportedNetworksUseCase: GetSupportedNetworksUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -146,9 +159,8 @@ internal class CustomTokenSelectorModel @Inject constructor(
|
|||
return derivationPaths
|
||||
}
|
||||
|
||||
private suspend fun getSupportedNetworks(mode: AddCustomTokenMode): List<Network> = when (mode) {
|
||||
is AddCustomTokenMode.Account -> TODO("Account")
|
||||
is AddCustomTokenMode.Wallet -> getSupportedNetworksUseCase(mode.userWalletId).getOrElse { e ->
|
||||
private suspend fun getSupportedNetworks(mode: AddCustomTokenMode): List<Network> {
|
||||
return getSupportedNetworksUseCase(mode.userWalletId).getOrElse { e ->
|
||||
val message = SnackbarMessage(message = resourceReference(R.string.common_unknown_error))
|
||||
messageSender.send(message)
|
||||
|
||||
|
|
@ -168,7 +180,60 @@ internal class CustomTokenSelectorModel @Inject constructor(
|
|||
fun selectCustomDerivationPath(value: SelectedDerivationPath) {
|
||||
when (params) {
|
||||
is NetworkSelector -> return
|
||||
is DerivationPathSelector -> params.onDerivationPathSelected(value)
|
||||
is DerivationPathSelector -> if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
params.checkAccountDerivation(value)
|
||||
} else {
|
||||
params.onDerivationPathSelected(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DerivationPathSelector.checkAccountDerivation(derivationPath: SelectedDerivationPath) =
|
||||
modelScope.launch {
|
||||
val accountName = derivationPath.id
|
||||
?.let { Blockchain.fromId(it.rawId.value) }?.let(::AccountNodeRecognizer)
|
||||
?.let { recognizer -> derivationPath.value.value?.let { recognizer.recognize(it) } }
|
||||
?.let { accountNode ->
|
||||
fun AccountStatus.CryptoPortfolio.sameNodeAndNotMain() = !this.account.isMainAccount &&
|
||||
this.account.derivationIndex.value.toLong() == accountNode
|
||||
|
||||
val accounts = singleAccountStatusListSupplier(mode.userWalletId)
|
||||
.first().accountStatuses
|
||||
val account = accounts.find {
|
||||
when (it) {
|
||||
is AccountStatus.CryptoPortfolio -> it.sameNodeAndNotMain()
|
||||
}
|
||||
}
|
||||
val accountName = when (account) {
|
||||
is AccountStatus.CryptoPortfolio -> account.account.accountName.toUM()
|
||||
null -> null
|
||||
}
|
||||
accountName
|
||||
}
|
||||
|
||||
if (accountName == null) {
|
||||
onDerivationPathSelected(derivationPath)
|
||||
} else {
|
||||
showAccountNameExist(
|
||||
accountName = accountName.value,
|
||||
onClick = { onDerivationPathSelected(derivationPath) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showAccountNameExist(accountName: TextReference, onClick: () -> Unit) {
|
||||
val firstAction = EventMessageAction(
|
||||
title = resourceReference(R.string.common_got_it),
|
||||
onClick = onClick,
|
||||
)
|
||||
val dialogMessage = DialogMessage(
|
||||
title = resourceReference(R.string.custom_token_another_account_dialog_title),
|
||||
message = resourceReference(
|
||||
R.string.custom_token_another_account_dialog_description,
|
||||
wrappedList(accountName),
|
||||
),
|
||||
firstActionBuilder = { firstAction },
|
||||
)
|
||||
messageSender.send(dialogMessage)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import com.tangem.core.ui.event.triggeredEvent
|
|||
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.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
|
||||
import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
|
|
@ -40,13 +41,14 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
@ModelScoped
|
||||
internal class ManageTokensModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
manageTokensListManagerFactory: ManageTokensListManager.Factory,
|
||||
manageTokensUseCasesFacadeFactory: ManageTokensUseCasesFacade.Factory,
|
||||
paramsContainer: ParamsContainer,
|
||||
|
|
@ -86,6 +88,7 @@ internal class ManageTokensModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
manageTokensListManager.launchPagination(isCollapsed = true)
|
||||
}
|
||||
checkIsSupportAddCustomTokens()
|
||||
}
|
||||
|
||||
fun reloadList() {
|
||||
|
|
@ -105,16 +108,34 @@ internal class ManageTokensModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getTopBarInitialState(): ManageTokensTopBarUM = when (params.mode) {
|
||||
is ManageTokensMode.Wallet -> manageContentTopBar()
|
||||
is ManageTokensMode.Account -> ManageTokensTopBarUM.ReadContent(
|
||||
title = resourceReference(id = R.string.main_manage_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
)
|
||||
ManageTokensMode.None -> ManageTokensTopBarUM.ReadContent(
|
||||
title = resourceReference(R.string.common_search_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
)
|
||||
}
|
||||
|
||||
private fun manageContentTopBar() = ManageTokensTopBarUM.ManageContent(
|
||||
title = resourceReference(id = R.string.main_manage_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
endButton = TopAppBarButtonUM.Icon(
|
||||
iconRes = R.drawable.ic_plus_24,
|
||||
onClicked = ::navigateToAddCustomToken,
|
||||
),
|
||||
)
|
||||
|
||||
private fun createReadContentModel(): ManageTokensUM.ReadContent {
|
||||
return ManageTokensUM.ReadContent(
|
||||
popBack = router::pop,
|
||||
isInitialBatchLoading = true,
|
||||
isNextBatchLoading = false,
|
||||
items = getLoadingItems(),
|
||||
topBar = ManageTokensTopBarUM.ReadContent(
|
||||
title = resourceReference(R.string.common_search_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
),
|
||||
topBar = getTopBarInitialState(),
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.common_search),
|
||||
query = "",
|
||||
|
|
@ -132,14 +153,7 @@ internal class ManageTokensModel @Inject constructor(
|
|||
isInitialBatchLoading = true,
|
||||
isNextBatchLoading = false,
|
||||
items = getLoadingItems(),
|
||||
topBar = ManageTokensTopBarUM.ManageContent(
|
||||
title = resourceReference(id = R.string.main_manage_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
endButton = TopAppBarButtonUM.Icon(
|
||||
iconRes = R.drawable.ic_plus_24,
|
||||
onClicked = ::navigateToAddCustomToken,
|
||||
),
|
||||
),
|
||||
topBar = getTopBarInitialState(),
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.common_search),
|
||||
query = "",
|
||||
|
|
@ -174,6 +188,20 @@ internal class ManageTokensModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun checkIsSupportAddCustomTokens() {
|
||||
when (val mode = params.mode) {
|
||||
is ManageTokensMode.Account -> modelScope.launch {
|
||||
val mainAccount = singleAccountStatusListSupplier(mode.accountId.userWalletId).first().mainAccount
|
||||
if (mode.accountId == mainAccount.account.accountId) {
|
||||
state.update { it.copySealed(topBar = manageContentTopBar()) }
|
||||
}
|
||||
}
|
||||
ManageTokensMode.None,
|
||||
is ManageTokensMode.Wallet,
|
||||
-> Unit // use init state
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateItems(items: ImmutableList<CurrencyItemUM>) {
|
||||
val updatedState = state.updateAndGet { state ->
|
||||
state.copySealed(
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import com.tangem.core.ui.utils.WindowInsetsZero
|
|||
import com.tangem.core.ui.utils.rememberHideKeyboardNestedScrollConnection
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensMode
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.managetokens.component.preview.PreviewManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
|
|
@ -442,20 +443,23 @@ private class PreviewManageTokensComponentProvider : PreviewParameterProvider<Ma
|
|||
showTangemIcon = true,
|
||||
params = ManageTokensComponent.Params(
|
||||
source = ManageTokensSource.ONBOARDING,
|
||||
userWalletId = UserWalletId("0x"),
|
||||
mode = ManageTokensMode.Wallet(UserWalletId("0x")),
|
||||
),
|
||||
),
|
||||
PreviewManageTokensComponent(
|
||||
isLoading = false,
|
||||
showTangemIcon = true,
|
||||
params = ManageTokensComponent.Params(source = ManageTokensSource.ONBOARDING, userWalletId = null),
|
||||
params = ManageTokensComponent.Params(
|
||||
source = ManageTokensSource.ONBOARDING,
|
||||
mode = ManageTokensMode.None,
|
||||
),
|
||||
),
|
||||
PreviewManageTokensComponent(
|
||||
isLoading = false,
|
||||
showTangemIcon = false,
|
||||
params = ManageTokensComponent.Params(
|
||||
source = ManageTokensSource.ONBOARDING,
|
||||
userWalletId = UserWalletId("0x"),
|
||||
mode = ManageTokensMode.Wallet(UserWalletId("0x")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue