Updated on 2026-08-14
This commit is contained in:
parent
c8c2f1315b
commit
d98b2d98ed
14 changed files with 278 additions and 157 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ enum class ManageTokensSource(val analyticsName: String) {
|
|||
STORIES(analyticsName = "Stories"),
|
||||
ONBOARDING(analyticsName = "Onboarding"),
|
||||
SETTINGS(analyticsName = "Settings"),
|
||||
SEND_VIA_SWAP(analyticsName = "SendViaSwap"),
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue