Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-19 13:09:15 +04:00
parent 5b4f0214d2
commit fe09323d64
7 changed files with 103 additions and 69 deletions

View file

@ -55,7 +55,7 @@ internal class ManageTokensModel @Inject constructor(
).launchIn(modelScope)
modelScope.launch {
manageTokensListManager.launch(params.userWalletId)
manageTokensListManager.launchPagination(params.userWalletId)
}
}
@ -110,7 +110,7 @@ internal class ManageTokensModel @Inject constructor(
onActiveChange = ::toggleSearchBar,
),
hasChanges = false,
saveChanges = ::onSaveClick,
saveChanges = ::saveChanges,
loadMore = ::loadMoreItems,
)
}
@ -126,7 +126,9 @@ internal class ManageTokensModel @Inject constructor(
private fun updatePaginationStatus(status: PaginationStatus<*>) {
state.update { state ->
when (status) {
is PaginationStatus.InitialLoading -> {
is PaginationStatus.None,
is PaginationStatus.InitialLoading,
-> {
if (state.search.isActive) {
state
} else {
@ -151,7 +153,6 @@ internal class ManageTokensModel @Inject constructor(
isNextBatchLoading = false,
)
}
is PaginationStatus.None,
is PaginationStatus.Paginating,
is PaginationStatus.EndOfPagination,
-> state.copySealed(
@ -172,7 +173,7 @@ internal class ManageTokensModel @Inject constructor(
private fun loadMoreItems(): Boolean {
val state = state.value
if (state.isInitialBatchLoading) return false
if (state.isInitialBatchLoading || state.isNextBatchLoading) return false
modelScope.launch {
manageTokensListManager.loadMore(
@ -192,7 +193,7 @@ internal class ManageTokensModel @Inject constructor(
// TODO: [REDACTED_JIRA]
}
private fun onSaveClick() {
private fun saveChanges() {
// TODO: [REDACTED_JIRA]
}

View file

@ -3,7 +3,6 @@ package com.tangem.features.managetokens.ui
import android.content.res.Configuration
import androidx.compose.animation.*
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@ -173,7 +172,6 @@ private fun Content(state: ManageTokensUM, modifier: Modifier = Modifier) {
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun Currencies(
items: ImmutableList<CurrencyItemUM>,
@ -215,15 +213,13 @@ private fun Currencies(
}
}
item(key = "loading_indicator") {
AnimatedVisibility(
modifier = Modifier
.padding(vertical = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
visible = showLoadingItem,
label = "loading_indicator_visibility",
) {
ProgressIndicator()
if (showLoadingItem) {
item(key = "loading_item") {
ProgressIndicator(
modifier = Modifier
.padding(vertical = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
)
}
}
}

View file

@ -7,12 +7,25 @@ import kotlinx.coroutines.flow.update
internal typealias ChangedCurrencies = Map<ManagedCryptoCurrency.ID, Set<Network.ID>>
internal interface ChangedCurrenciesManager {
internal class ChangedCurrenciesManager {
val currenciesToAdd: MutableStateFlow<ChangedCurrencies>
val currenciesToRemove: MutableStateFlow<ChangedCurrencies>
val currenciesToAdd: MutableStateFlow<ChangedCurrencies> = MutableStateFlow(emptyMap())
val currenciesToRemove: MutableStateFlow<ChangedCurrencies> = MutableStateFlow(emptyMap())
fun updateChangedItems(
fun addCurrency(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) {
updateChangedItems(currencyId, networkId, currenciesToRemove, currenciesToAdd)
}
fun removeCurrency(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) {
updateChangedItems(currencyId, networkId, currenciesToAdd, currenciesToRemove)
}
fun containsCurrency(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID): Boolean {
return networkId in currenciesToAdd.value[currencyId].orEmpty() ||
networkId in currenciesToRemove.value[currencyId].orEmpty()
}
private fun updateChangedItems(
currencyId: ManagedCryptoCurrency.ID,
networkId: Network.ID,
removeFromIfPresent: MutableStateFlow<ChangedCurrencies>,

View file

@ -19,11 +19,11 @@ import com.tangem.features.managetokens.impl.R
import com.tangem.pagination.BatchAction
import com.tangem.pagination.BatchListState
import com.tangem.pagination.PaginationStatus
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
@ -38,37 +38,37 @@ internal class ManageTokensListManager @Inject constructor(
private val checkHasLinkedTokensUseCase: CheckHasLinkedTokensUseCase,
private val messageSender: UiMessageSender,
private val dispatchers: CoroutineDispatcherProvider,
) : ChangedCurrenciesManager,
ManageTokensUiManager(
messageSender = messageSender,
dispatchers = dispatchers,
) {
) : ManageTokensUiActions {
override lateinit var scope: CoroutineScope
private lateinit var scope: CoroutineScope
private val jobHolder = JobHolder()
private val actionsFlow = MutableSharedFlow<ManageTokensBatchAction>(
private val actionsFlow: MutableSharedFlow<ManageTokensBatchAction> = MutableSharedFlow(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
override val state: MutableStateFlow<ManageTokensListState> = MutableStateFlow(ManageTokensListState())
private val state: MutableStateFlow<ManageTokensListState> = MutableStateFlow(ManageTokensListState())
val paginationStatus: MutableStateFlow<PaginationStatus<*>> = MutableStateFlow(PaginationStatus.None)
private val changedCurrenciesManager = ChangedCurrenciesManager()
private val uiManager = ManageTokensUiManager(
state = state,
messageSender = messageSender,
dispatchers = dispatchers,
actions = this,
scopeProvider = Provider { scope },
)
override val currenciesToAdd: MutableStateFlow<ChangedCurrencies> = MutableStateFlow(emptyMap())
override val currenciesToRemove: MutableStateFlow<ChangedCurrencies> = MutableStateFlow(emptyMap())
val currenciesToAdd: StateFlow<ChangedCurrencies> = changedCurrenciesManager.currenciesToAdd
val currenciesToRemove: StateFlow<ChangedCurrencies> = changedCurrenciesManager.currenciesToRemove
@OptIn(ExperimentalCoroutinesApi::class)
val uiItems: Flow<ImmutableList<CurrencyItemUM>> = state
.mapLatest { state ->
state.uiBatches.asSequence()
.flatMap { it.data }
.toImmutableList()
}
val paginationStatus: Flow<PaginationStatus<*>> = state
.mapLatest { it.status }
.distinctUntilChanged()
val uiItems: Flow<ImmutableList<CurrencyItemUM>> = uiManager.items
suspend fun launch(userWalletId: UserWalletId?) = coroutineScope {
suspend fun launchPagination(userWalletId: UserWalletId?) = coroutineScope {
scope = this
val batchFlow = getManagedTokensUseCase(
@ -120,7 +120,11 @@ internal class ManageTokensListManager @Inject constructor(
batchListState: BatchListState<Int, List<ManagedCryptoCurrency>>,
userWalletId: UserWalletId?,
) {
paginationStatus.value = batchListState.status
state.update { state ->
state.copy(
status = batchListState.status,
)
}
state.update { state ->
val newBatches = batchListState.data
@ -138,20 +142,20 @@ internal class ManageTokensListManager @Inject constructor(
state.copy(
userWalletId = userWalletId,
currencyBatches = newBatches,
uiBatches = getUiBatches(newBatches, canEditItems),
uiBatches = uiManager.createOrUpdateUiBatches(newBatches, canEditItems),
canEditItems = canEditItems,
)
}
}
override fun addCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) {
updateChangedItems(currencyId, networkId, currenciesToRemove, currenciesToAdd)
changedCurrenciesManager.addCurrency(currencyId, networkId)
sendSelectCurrencyAction(batchKey, currencyId, networkId, isSelected = true)
}
override fun removeCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) {
updateChangedItems(currencyId, networkId, currenciesToAdd, currenciesToRemove)
changedCurrenciesManager.removeCurrency(currencyId, networkId)
sendSelectCurrencyAction(batchKey, currencyId, networkId, isSelected = false)
}
@ -159,8 +163,7 @@ internal class ManageTokensListManager @Inject constructor(
override fun checkNeedToShowRemoveNetworkWarning(
currencyId: ManagedCryptoCurrency.ID,
networkId: Network.ID,
): Boolean = networkId !in currenciesToRemove.value[currencyId].orEmpty() &&
networkId !in currenciesToAdd.value[currencyId].orEmpty()
): Boolean = !changedCurrenciesManager.containsCurrency(currencyId, networkId)
private fun sendSelectCurrencyAction(
batchKey: Int,

View file

@ -7,10 +7,12 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.managetokens.entity.CurrencyItemUM
import com.tangem.pagination.Batch
import com.tangem.pagination.BatchAction
import com.tangem.pagination.PaginationStatus
internal typealias ManageTokensBatchAction = BatchAction<Int, ManageTokensListConfig, ManageTokensUpdateAction>
internal data class ManageTokensListState(
val status: PaginationStatus<*> = PaginationStatus.None,
val userWalletId: UserWalletId? = null,
val uiBatches: List<Batch<Int, List<CurrencyItemUM>>> = mutableListOf(),
val currencyBatches: List<Batch<Int, List<ManagedCryptoCurrency>>> = mutableListOf(),

View file

@ -0,0 +1,16 @@
package com.tangem.features.managetokens.utils.list
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
internal interface ManageTokensUiActions {
fun addCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID)
fun removeCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID)
fun checkNeedToShowRemoveNetworkWarning(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID): Boolean
suspend fun checkHasLinkedTokens(userWalletId: UserWalletId, network: Network): Boolean
}

View file

@ -4,7 +4,6 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.message.ContentMessage
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.managetokens.entity.CurrencyItemUM
import com.tangem.features.managetokens.ui.dialog.HasLinkedTokensWarning
import com.tangem.features.managetokens.ui.dialog.HideTokenWarning
@ -12,21 +11,36 @@ 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
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
internal abstract class ManageTokensUiManager(
internal class ManageTokensUiManager(
private val state: MutableStateFlow<ManageTokensListState>,
private val messageSender: UiMessageSender,
private val dispatchers: CoroutineDispatcherProvider,
private val scopeProvider: Provider<CoroutineScope>,
private val actions: ManageTokensUiActions,
) {
abstract val scope: CoroutineScope
abstract val state: MutableStateFlow<ManageTokensListState>
private val scope: CoroutineScope
get() = scopeProvider()
protected fun getUiBatches(
@OptIn(ExperimentalCoroutinesApi::class)
val items: Flow<ImmutableList<CurrencyItemUM>> = state
.mapLatest { state ->
state.uiBatches.asSequence()
.flatMap { it.data }
.toImmutableList()
}
.distinctUntilChanged()
fun createOrUpdateUiBatches(
newCurrencyBatches: List<Batch<Int, List<ManagedCryptoCurrency>>>,
canEditItems: Boolean,
): List<Batch<Int, List<CurrencyItemUM>>> {
@ -128,32 +142,23 @@ internal abstract class ManageTokensUiManager(
if (currency !is ManagedCryptoCurrency.Token) return@launch
if (isSelected) {
addCurrency(batchKey, currency.id, source.id)
actions.addCurrency(batchKey, currency.id, source.id)
} else {
if (checkNeedToShowRemoveNetworkWarning(currency.id, source.id)) {
if (actions.checkNeedToShowRemoveNetworkWarning(currency.id, source.id)) {
showRemoveNetworkWarning(
currency = currency,
network = source.network,
isCoin = source is ManagedCryptoCurrency.SourceNetwork.Main,
onConfirm = {
removeCurrency(batchKey, currency.id, source.id)
actions.removeCurrency(batchKey, currency.id, source.id)
},
)
} else {
removeCurrency(batchKey, currency.id, source.id)
actions.removeCurrency(batchKey, currency.id, source.id)
}
}
}
protected abstract fun addCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID)
protected abstract fun removeCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID)
protected abstract fun checkNeedToShowRemoveNetworkWarning(
currencyId: ManagedCryptoCurrency.ID,
networkId: Network.ID,
): Boolean
private suspend fun showRemoveNetworkWarning(
currency: ManagedCryptoCurrency,
network: Network,
@ -164,7 +169,7 @@ internal abstract class ManageTokensUiManager(
val hasLinkedTokens = if (userWalletId == null || !isCoin) {
false
} else {
checkHasLinkedTokens(userWalletId, network)
actions.checkHasLinkedTokens(userWalletId, network)
}
val message = ContentMessage { onDismiss ->
@ -189,8 +194,6 @@ internal abstract class ManageTokensUiManager(
messageSender.send(message)
}
protected abstract suspend fun checkHasLinkedTokens(userWalletId: UserWalletId, network: Network): Boolean
private fun Batch<Int, List<ManagedCryptoCurrency>>.currencyIndexById(id: ManagedCryptoCurrency.ID): Int {
return data
.indexOfFirst { it.id == id }