diff --git a/domain/manage-tokens/models/build.gradle.kts b/domain/manage-tokens/models/build.gradle.kts index 95fb1657d6..d30b3f0186 100644 --- a/domain/manage-tokens/models/build.gradle.kts +++ b/domain/manage-tokens/models/build.gradle.kts @@ -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) } \ No newline at end of file diff --git a/domain/manage-tokens/models/src/main/kotlin/com/tangem/domain/managetokens/model/ManagedCryptoCurrency.kt b/domain/manage-tokens/models/src/main/kotlin/com/tangem/domain/managetokens/model/ManagedCryptoCurrency.kt index 483a320ae9..5cb6e2e818 100644 --- a/domain/manage-tokens/models/src/main/kotlin/com/tangem/domain/managetokens/model/ManagedCryptoCurrency.kt +++ b/domain/manage-tokens/models/src/main/kotlin/com/tangem/domain/managetokens/model/ManagedCryptoCurrency.kt @@ -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, diff --git a/features/manage-tokens/api/build.gradle.kts b/features/manage-tokens/api/build.gradle.kts index 8bbbd4e02b..c3a7b45a50 100644 --- a/features/manage-tokens/api/build.gradle.kts +++ b/features/manage-tokens/api/build.gradle.kts @@ -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) diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt index e281e28864..c1d317c3d8 100644 --- a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt @@ -4,4 +4,5 @@ enum class ManageTokensSource(val analyticsName: String) { STORIES(analyticsName = "Stories"), ONBOARDING(analyticsName = "Onboarding"), SETTINGS(analyticsName = "Settings"), + SEND_VIA_SWAP(analyticsName = "SendViaSwap"), } \ No newline at end of file diff --git a/features/manage-tokens/impl/build.gradle.kts b/features/manage-tokens/impl/build.gradle.kts index 1d92f0d67a..87ecb9bb7b 100644 --- a/features/manage-tokens/impl/build.gradle.kts +++ b/features/manage-tokens/impl/build.gradle.kts @@ -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) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt index 65be95e384..732be9f2b7 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt @@ -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 = MutableStateFlow(getInitialState(params.userWalletId)) val bottomSheetNavigation: SlotNavigation = SlotNavigation() diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt index 485566a94a..d6b6ca0899 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt @@ -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 = MutableStateFlow(getInitialState()) val returnToParentComponentFlow = MutableSharedFlow() diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt index e35c8871c7..606d9ae826 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt @@ -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 = { diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensTopBar.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensTopBar.kt new file mode 100644 index 0000000000..2b7d67794c --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensTopBar.kt @@ -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), + ) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt index bf76e4e809..7476e29b4c 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt @@ -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 = 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>.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 + } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiActions.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiActions.kt index ec165926b1..1be1bb6e11 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiActions.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiActions.kt @@ -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) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt index 3e4585d5bf..8c038934c5 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt @@ -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, private val sourceProvider: Provider, 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>.currencyIndexById(id: ManagedCryptoCurrency.ID): Int { - return data - .indexOfFirst { it.id == id } - .takeIf { it != -1 } - ?: error("Currency with currency '$id' not found in batch #$key") - } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUtils.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUtils.kt new file mode 100644 index 0000000000..8ea446461a --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUtils.kt @@ -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 { + return List(size = 10) { index -> + CurrencyItemUM.Loading(index) + }.toPersistentList() +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyItemMapper.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyItemMapper.kt index d37da299a6..c16e25cd84 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyItemMapper.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyItemMapper.kt @@ -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) }, ) } \ No newline at end of file