Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-04 11:56:32 +05:00
parent c8c2f1315b
commit d98b2d98ed
14 changed files with 278 additions and 157 deletions

View file

@ -1,5 +1,6 @@
plugins {
alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
@ -8,4 +9,7 @@ dependencies {
/* Domain */
implementation(projects.domain.models)
implementation(projects.domain.tokens.models)
/** Other */
implementation(deps.kotlin.serialization)
}

View file

@ -2,7 +2,9 @@ package com.tangem.domain.managetokens.model
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import kotlinx.serialization.Serializable
@Serializable
sealed class ManagedCryptoCurrency {
abstract val id: ID
@ -10,6 +12,7 @@ sealed class ManagedCryptoCurrency {
abstract val symbol: String
abstract val iconUrl: String?
@Serializable
sealed class Custom : ManagedCryptoCurrency() {
abstract val currencyId: CryptoCurrency.ID
@ -18,6 +21,7 @@ sealed class ManagedCryptoCurrency {
override val id: ID
get() = ID(currencyId.value)
@Serializable
data class Token(
override val currencyId: CryptoCurrency.ID,
override val name: String,
@ -28,6 +32,7 @@ sealed class ManagedCryptoCurrency {
val decimals: Int,
) : Custom()
@Serializable
data class Coin(
override val currencyId: CryptoCurrency.ID,
override val name: String,
@ -37,6 +42,7 @@ sealed class ManagedCryptoCurrency {
) : Custom()
}
@Serializable
data class Token(
override val id: ID,
override val name: String,
@ -50,8 +56,10 @@ sealed class ManagedCryptoCurrency {
}
@JvmInline
@Serializable
value class ID(val value: String)
@Serializable
sealed class SourceNetwork {
abstract val network: Network
@ -77,12 +85,14 @@ sealed class ManagedCryptoCurrency {
}
}
@Serializable
data class Main(
override val network: Network,
override val decimals: Int,
val isL2Network: Boolean,
) : SourceNetwork()
@Serializable
data class Default(
override val network: Network,
override val decimals: Int,

View file

@ -10,7 +10,9 @@ android {
dependencies {
/* Project - Domain */
implementation(projects.domain.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.manageTokens.models)
/* Project - Core */
implementation(projects.core.ui)

View file

@ -4,4 +4,5 @@ enum class ManageTokensSource(val analyticsName: String) {
STORIES(analyticsName = "Stories"),
ONBOARDING(analyticsName = "Onboarding"),
SETTINGS(analyticsName = "Settings"),
SEND_VIA_SWAP(analyticsName = "SendViaSwap"),
}

View file

@ -18,9 +18,10 @@ dependencies {
/* Project - Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
implementation(projects.common.routing)
implementation(projects.core.configToggles)
implementation(projects.core.analytics)
implementation(projects.common.routing)
implementation(projects.common.ui)
/* Project - Domain */
implementation(projects.domain.card)

View file

@ -46,16 +46,18 @@ import javax.inject.Inject
internal class ManageTokensModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val manageTokensListManager: ManageTokensListManager,
private val messageSender: UiMessageSender,
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
private val saveManagedTokensUseCase: SaveManagedTokensUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
manageTokensListManagerFactory: ManageTokensListManager.Factory,
paramsContainer: ParamsContainer,
) : Model() {
private val params: ManageTokensComponent.Params = paramsContainer.require()
private val manageTokensListManager = manageTokensListManagerFactory.create()
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(getInitialState(params.userWalletId))
val bottomSheetNavigation: SlotNavigation<ManageTokensBottomSheetConfig> = SlotNavigation()

View file

@ -40,16 +40,18 @@ import javax.inject.Inject
@ModelScoped
internal class OnboardingManageTokensModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val manageTokensListManager: ManageTokensListManager,
private val messageSender: UiMessageSender,
private val reduxStateHolder: ReduxStateHolder,
private val saveManagedTokensUseCase: SaveManagedTokensUseCase,
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
manageTokensListManagerFactory: ManageTokensListManager.Factory,
paramsContainer: ParamsContainer,
) : Model() {
private val params: OnboardingManageTokensComponent.Params = paramsContainer.require()
private val manageTokensListManager = manageTokensListManagerFactory.create()
val state: MutableStateFlow<OnboardingManageTokensUM> = MutableStateFlow(getInitialState())
val returnToParentComponentFlow = MutableSharedFlow<Unit>()

View file

@ -32,8 +32,6 @@ import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.TangemSwitch
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.buttons.common.TangemButton
@ -41,8 +39,6 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.fields.SearchBar
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.list.InfiniteListHandler
import com.tangem.core.ui.components.rows.ArrowRow
import com.tangem.core.ui.components.rows.BlockchainRow
@ -51,7 +47,6 @@ import com.tangem.core.ui.components.rows.ChainRowContainer
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
import com.tangem.core.ui.components.rows.model.ChainRowUM
import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.haptic.TangemHapticEffect
@ -66,14 +61,13 @@ import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.managetokens.component.preview.PreviewManageTokensComponent
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
import com.tangem.features.managetokens.impl.R
import kotlinx.collections.immutable.ImmutableList
private const val CHEVRON_ROTATION_EXPANDED = 180f
private const val CHEVRON_ROTATION_COLLAPSED = 0f
private const val LOAD_ITEMS_BUFFER = 10
internal const val LOAD_ITEMS_BUFFER = 10
@Composable
internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modifier) {
@ -85,7 +79,9 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
contentWindowInsets = WindowInsetsZero,
topBar = {
ManageTokensTopBar(
modifier = Modifier.statusBarsPadding(),
modifier = Modifier
.statusBarsPadding()
.background(TangemTheme.colors.background.primary),
topBar = state.topBar,
search = state.search,
)
@ -116,31 +112,6 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
)
}
@Composable
private fun ManageTokensTopBar(topBar: ManageTokensTopBarUM?, search: SearchBarUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier.background(TangemTheme.colors.background.primary),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
) {
if (topBar != null) {
TangemTopAppBar(
title = topBar.title.resolveReference(),
startButton = TopAppBarButtonUM.Back(topBar.onBackButtonClick),
endButton = when (topBar) {
is ManageTokensTopBarUM.ManageContent -> topBar.endButton
is ManageTokensTopBarUM.ReadContent -> null
},
)
}
SearchBar(
modifier = Modifier
.padding(bottom = TangemTheme.dimens.spacing12)
.padding(horizontal = TangemTheme.dimens.spacing16),
state = search,
)
}
}
@Composable
private fun SaveChangesButton(
isVisible: Boolean,
@ -263,7 +234,7 @@ internal fun Currencies(
}
@Composable
private fun SearchNothingFoundText(modifier: Modifier = Modifier) {
fun SearchNothingFoundText(modifier: Modifier = Modifier) {
Box(
modifier = modifier,
contentAlignment = Alignment.Center,
@ -277,7 +248,7 @@ private fun SearchNothingFoundText(modifier: Modifier = Modifier) {
}
@Composable
private fun ProgressIndicator(modifier: Modifier = Modifier) {
fun ProgressIndicator(modifier: Modifier = Modifier) {
Box(
modifier = modifier.background(color = TangemTheme.colors.background.primary),
contentAlignment = Alignment.Center,
@ -287,7 +258,7 @@ private fun ProgressIndicator(modifier: Modifier = Modifier) {
}
@Composable
private fun LoadingItem(modifier: Modifier = Modifier) {
fun LoadingItem(modifier: Modifier = Modifier) {
ChainRowContainer(
modifier = modifier,
icon = {

View file

@ -0,0 +1,39 @@
package com.tangem.features.managetokens.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.fields.SearchBar
import com.tangem.core.ui.components.fields.TangemSearchBarDefaults
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
@Composable
internal fun ManageTokensTopBar(topBar: ManageTokensTopBarUM?, search: SearchBarUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
) {
if (topBar != null) {
TangemTopAppBar(
title = topBar.title.resolveReference(),
startButton = TopAppBarButtonUM.Back(topBar.onBackButtonClick),
endButton = when (topBar) {
is ManageTokensTopBarUM.ManageContent -> topBar.endButton
is ManageTokensTopBarUM.ReadContent -> null
},
)
}
SearchBar(
colors = TangemSearchBarDefaults.secondaryTextFieldColors,
state = search,
modifier = Modifier
.padding(bottom = TangemTheme.dimens.spacing12)
.padding(horizontal = TangemTheme.dimens.spacing16),
)
}
}

View file

@ -2,11 +2,14 @@ package com.tangem.features.managetokens.utils.list
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
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.managetokens.*
import com.tangem.domain.managetokens.model.*
@ -16,6 +19,7 @@ import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.impl.R
import com.tangem.features.managetokens.utils.ui.toggleExpanded
import com.tangem.pagination.Batch
import com.tangem.pagination.BatchAction
import com.tangem.pagination.BatchListState
@ -24,6 +28,9 @@ import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -32,11 +39,9 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped
internal class ManageTokensListManager @Inject constructor(
@Suppress("LongParameterList", "LargeClass")
internal class ManageTokensListManager @AssistedInject constructor(
private val getManagedTokensUseCase: GetManagedTokensUseCase,
private val getDistinctManagedTokensUseCase: GetDistinctManagedCurrenciesUseCase,
private val checkHasLinkedTokensUseCase: CheckHasLinkedTokensUseCase,
@ -45,7 +50,8 @@ internal class ManageTokensListManager @Inject constructor(
private val messageSender: UiMessageSender,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
clipboardManager: ClipboardManager,
private val clipboardManager: ClipboardManager,
@Assisted private val onCurrencySelect: (ManagedCryptoCurrency.Token) -> Unit = {},
) : ManageTokensUiActions {
private lateinit var scope: CoroutineScope
@ -67,7 +73,6 @@ internal class ManageTokensListManager @Inject constructor(
actions = this,
scopeProvider = Provider { scope },
sourceProvider = Provider { source },
clipboardManager = clipboardManager,
)
val currenciesToAdd: StateFlow<ChangedCurrencies> = changedCurrenciesManager.currenciesToAdd.asStateFlow()
@ -186,6 +191,14 @@ internal class ManageTokensListManager @Inject constructor(
}
}
override fun onTokenClick(currency: ManagedCryptoCurrency.Token) {
if (source == ManageTokensSource.SEND_VIA_SWAP) {
onCurrencySelect(currency)
} else {
toggleCurrencyNetworksVisibility(currency)
}
}
override fun addCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network) {
changedCurrenciesManager.addCurrency(currency, network)
@ -298,4 +311,168 @@ internal class ManageTokensListManager @Inject constructor(
null
}
}
private fun toggleCurrencyNetworksVisibility(currency: ManagedCryptoCurrency.Token) = scope.launch(
dispatchers.default,
) {
state.update { batches ->
val batchIndex = batches.batchIndexByCurrencyId(currency.id)
val currencyBatch = batches.currencyBatches[batchIndex]
val currencyIndex = currencyBatch.currencyIndexById(currency.id)
val uiBatch = batches.uiBatches[batchIndex]
val updatedUiItem = uiBatch.data[currencyIndex].toggleExpanded(
currency = currencyBatch.data[currencyIndex],
isEditable = batches.canEditItems,
onSelectCurrencyNetwork = { networkId, isSelected ->
selectNetwork(currencyBatch.key, currency, networkId, isSelected)
},
onLongTap = ::copyContractAddress,
)
batches.updateUiBatchesItem(
indexToBatch = batchIndex to uiBatch,
indexToItem = currencyIndex to updatedUiItem,
)
}
}
private fun copyContractAddress(source: ManagedCryptoCurrency.SourceNetwork) {
if (source is ManagedCryptoCurrency.SourceNetwork.Default) {
clipboardManager.setText(text = source.contractAddress, isSensitive = false)
showSnackbarMessage(resourceReference(R.string.contract_address_copied_message))
}
}
private fun showSnackbarMessage(messageText: TextReference) {
val message = SnackbarMessage(message = messageText)
messageSender.send(message)
}
private fun selectNetwork(
batchKey: Int,
currency: ManagedCryptoCurrency,
source: ManagedCryptoCurrency.SourceNetwork,
isSelected: Boolean,
) = scope.launch(dispatchers.default) {
if (currency !is ManagedCryptoCurrency.Token) return@launch
if (isSelected) {
val userWalletId = state.value.userWalletId
val unsupportedState = userWalletId?.let { checkCurrencyUnsupportedState(it, source) }
if (unsupportedState != null) {
showUnsupportedWarning(unsupportedState)
} else {
addCurrency(batchKey, currency, source.network)
}
} else {
if (checkNeedToShowRemoveNetworkWarning(currency, source.network)) {
showRemoveNetworkWarning(
currency = currency,
network = source.network,
isCoin = source is ManagedCryptoCurrency.SourceNetwork.Main,
onConfirm = {
removeCurrency(batchKey, currency, source.network)
},
)
} else {
removeCurrency(batchKey, currency, source.network)
}
}
}
private fun showUnsupportedWarning(unsupportedState: CurrencyUnsupportedState) {
val message = DialogMessage(
title = resourceReference(R.string.common_warning),
message = when (unsupportedState) {
is CurrencyUnsupportedState.Token.NetworkTokensUnsupported -> resourceReference(
id = R.string.alert_manage_tokens_unsupported_message,
formatArgs = wrappedList(unsupportedState.networkName),
)
is CurrencyUnsupportedState.Token.UnsupportedCurve -> resourceReference(
id = R.string.alert_manage_tokens_unsupported_curve_message,
formatArgs = wrappedList(unsupportedState.networkName),
)
is CurrencyUnsupportedState.UnsupportedNetwork -> resourceReference(
id = R.string.alert_manage_tokens_unsupported_curve_message,
formatArgs = wrappedList(unsupportedState.networkName),
)
},
)
messageSender.send(message)
}
private suspend fun showRemoveNetworkWarning(
currency: ManagedCryptoCurrency,
network: Network,
isCoin: Boolean,
onConfirm: () -> Unit,
) {
val userWalletId = state.value.userWalletId
val hasLinkedTokens = if (userWalletId == null || !isCoin) {
false
} else {
checkHasLinkedTokens(userWalletId, network)
}
val canHideWithoutConfirming = source == ManageTokensSource.ONBOARDING
if (hasLinkedTokens) {
showLinkedTokensWarning(currency, network)
} else if (canHideWithoutConfirming) {
onConfirm()
} else {
showHideTokenWarning(currency, onConfirm)
}
}
private fun showLinkedTokensWarning(currency: ManagedCryptoCurrency, network: Network) {
val message = DialogMessage(
title = resourceReference(
id = R.string.token_details_unable_hide_alert_title,
formatArgs = wrappedList(currency.name),
),
message = resourceReference(
id = R.string.token_details_unable_hide_alert_message,
formatArgs = wrappedList(
currency.name,
currency.symbol,
network.name,
),
),
)
messageSender.send(message)
}
private fun showHideTokenWarning(currency: ManagedCryptoCurrency, onConfirm: () -> Unit) {
val message = DialogMessage(
title = resourceReference(
id = R.string.token_details_hide_alert_title,
formatArgs = wrappedList(currency.name),
),
message = resourceReference(R.string.token_details_hide_alert_message),
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.token_details_hide_alert_hide),
warning = true,
onClick = onConfirm,
)
},
secondActionBuilder = { cancelAction() },
)
messageSender.send(message)
}
private fun Batch<Int, List<ManagedCryptoCurrency>>.currencyIndexById(id: ManagedCryptoCurrency.ID): Int {
return data
.indexOfFirst { it.id == id }
.takeIf { it != -1 }
?: error("Currency with currency '$id' not found in batch #$key")
}
@AssistedFactory
interface Factory {
fun create(onCurrencySelect: (ManagedCryptoCurrency.Token) -> Unit = {}): ManageTokensListManager
}
}

View file

@ -7,6 +7,8 @@ import com.tangem.domain.wallets.models.UserWalletId
internal interface ManageTokensUiActions {
fun onTokenClick(currency: ManagedCryptoCurrency.Token)
fun addCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network)
fun removeCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network)

View file

@ -1,21 +1,16 @@
package com.tangem.features.managetokens.utils.list
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.clipboard.ClipboardManager
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.managetokens.model.CurrencyUnsupportedState
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.impl.R
import com.tangem.features.managetokens.utils.mapper.toUiModel
import com.tangem.features.managetokens.utils.ui.toggleExpanded
import com.tangem.features.managetokens.utils.ui.update
import com.tangem.pagination.Batch
import com.tangem.utils.Provider
@ -25,7 +20,10 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
@Suppress("LongParameterList")
@ -36,7 +34,6 @@ internal class ManageTokensUiManager(
private val scopeProvider: Provider<CoroutineScope>,
private val sourceProvider: Provider<ManageTokensSource>,
private val actions: ManageTokensUiActions,
private val clipboardManager: ClipboardManager,
) {
private val scope: CoroutineScope
@ -72,7 +69,7 @@ internal class ManageTokensUiManager(
item.toUiModel(
isEditable = canEditItems,
onRemoveCustomCurrencyClick = ::removeCustomCurrency,
onExpandNetworksClick = ::toggleCurrencyNetworksVisibility,
onTokenClick = actions::onTokenClick,
)
},
)
@ -96,7 +93,7 @@ internal class ManageTokensUiManager(
item.toUiModel(
isEditable = canEditItems,
onRemoveCustomCurrencyClick = ::removeCustomCurrency,
onExpandNetworksClick = ::toggleCurrencyNetworksVisibility,
onTokenClick = actions::onTokenClick,
)
} else {
previousUiItem.update(item)
@ -123,97 +120,6 @@ internal class ManageTokensUiManager(
)
}
private fun toggleCurrencyNetworksVisibility(currency: ManagedCryptoCurrency.Token) = scope.launch(
dispatchers.default,
) {
state.update { batches ->
val batchIndex = batches.batchIndexByCurrencyId(currency.id)
val currencyBatch = batches.currencyBatches[batchIndex]
val currencyIndex = currencyBatch.currencyIndexById(currency.id)
val uiBatch = batches.uiBatches[batchIndex]
val updatedUiItem = uiBatch.data[currencyIndex].toggleExpanded(
currency = currencyBatch.data[currencyIndex],
isEditable = batches.canEditItems,
onSelectCurrencyNetwork = { networkId, isSelected ->
selectNetwork(currencyBatch.key, currency, networkId, isSelected)
},
onLongTap = ::copyContractAddress,
)
batches.updateUiBatchesItem(
indexToBatch = batchIndex to uiBatch,
indexToItem = currencyIndex to updatedUiItem,
)
}
}
private fun copyContractAddress(source: ManagedCryptoCurrency.SourceNetwork) {
if (source is ManagedCryptoCurrency.SourceNetwork.Default) {
clipboardManager.setText(text = source.contractAddress, isSensitive = false)
showSnackbarMessage(resourceReference(R.string.contract_address_copied_message))
}
}
private fun showSnackbarMessage(messageText: TextReference) {
val message = SnackbarMessage(message = messageText)
messageSender.send(message)
}
private fun selectNetwork(
batchKey: Int,
currency: ManagedCryptoCurrency,
source: ManagedCryptoCurrency.SourceNetwork,
isSelected: Boolean,
) = scope.launch(dispatchers.default) {
if (currency !is ManagedCryptoCurrency.Token) return@launch
if (isSelected) {
val userWalletId = state.value.userWalletId
val unsupportedState = userWalletId?.let { actions.checkCurrencyUnsupportedState(it, source) }
if (unsupportedState != null) {
showUnsupportedWarning(unsupportedState)
} else {
actions.addCurrency(batchKey, currency, source.network)
}
} else {
if (actions.checkNeedToShowRemoveNetworkWarning(currency, source.network)) {
showRemoveNetworkWarning(
currency = currency,
network = source.network,
isCoin = source is ManagedCryptoCurrency.SourceNetwork.Main,
onConfirm = {
actions.removeCurrency(batchKey, currency, source.network)
},
)
} else {
actions.removeCurrency(batchKey, currency, source.network)
}
}
}
private fun showUnsupportedWarning(unsupportedState: CurrencyUnsupportedState) {
val message = DialogMessage(
title = resourceReference(R.string.common_warning),
message = when (unsupportedState) {
is CurrencyUnsupportedState.Token.NetworkTokensUnsupported -> resourceReference(
id = R.string.alert_manage_tokens_unsupported_message,
formatArgs = wrappedList(unsupportedState.networkName),
)
is CurrencyUnsupportedState.Token.UnsupportedCurve -> resourceReference(
id = R.string.alert_manage_tokens_unsupported_curve_message,
formatArgs = wrappedList(unsupportedState.networkName),
)
is CurrencyUnsupportedState.UnsupportedNetwork -> resourceReference(
id = R.string.alert_manage_tokens_unsupported_curve_message,
formatArgs = wrappedList(unsupportedState.networkName),
)
},
)
messageSender.send(message)
}
private suspend fun showRemoveNetworkWarning(
currency: ManagedCryptoCurrency,
network: Network,
@ -274,11 +180,4 @@ internal class ManageTokensUiManager(
messageSender.send(message)
}
private fun Batch<Int, List<ManagedCryptoCurrency>>.currencyIndexById(id: ManagedCryptoCurrency.ID): Int {
return data
.indexOfFirst { it.id == id }
.takeIf { it != -1 }
?: error("Currency with currency '$id' not found in batch #$key")
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.features.managetokens.utils.list
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
internal fun getLoadingItems(): ImmutableList<CurrencyItemUM> {
return List(size = 10) { index ->
CurrencyItemUM.Loading(index)
}.toPersistentList()
}

View file

@ -11,11 +11,11 @@ import com.tangem.features.managetokens.utils.ui.getIconRes
internal fun ManagedCryptoCurrency.toUiModel(
isEditable: Boolean,
onExpandNetworksClick: (ManagedCryptoCurrency.Token) -> Unit,
onTokenClick: (ManagedCryptoCurrency.Token) -> Unit,
onRemoveCustomCurrencyClick: (ManagedCryptoCurrency.Custom) -> Unit,
): CurrencyItemUM = when (this) {
is ManagedCryptoCurrency.Custom -> toUiModel(onRemoveCustomCurrencyClick)
is ManagedCryptoCurrency.Token -> toUiModel(isEditable, onExpandNetworksClick)
is ManagedCryptoCurrency.Token -> toUiModel(isEditable, onTokenClick)
}
private fun ManagedCryptoCurrency.Custom.toUiModel(
@ -53,7 +53,7 @@ private fun ManagedCryptoCurrency.Custom.toUiModel(
private fun ManagedCryptoCurrency.Token.toUiModel(
isEditable: Boolean,
onExpandNetworksClick: (ManagedCryptoCurrency.Token) -> Unit,
onTokenClick: (ManagedCryptoCurrency.Token) -> Unit,
): CurrencyItemUM {
val background = TangemColorPalette.Black
@ -71,7 +71,7 @@ private fun ManagedCryptoCurrency.Token.toUiModel(
),
networks = NetworksUM.Collapsed,
onExpandClick = {
onExpandNetworksClick(this)
onTokenClick(this)
},
)
}