From 50af6db2612d8dafb78df586918877cd787c6234 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Sep 2023 17:32:29 +0800 Subject: [PATCH] Updated on 2026-08-14 --- .../components/notifications/Notification.kt | 2 +- .../core/ui/extensions/TextReference.kt | 17 +++ .../domain/wallets/models/SaveWalletError.kt | 4 +- .../wallets/usecase/SaveWalletUseCase.kt | 13 +- .../presentation/common/WalletPreviewData.kt | 2 + .../presentation/wallet/state/WalletEvent.kt | 12 ++ .../wallet/state/WalletMultiCurrencyState.kt | 4 + .../wallet/state/WalletSingleCurrencyState.kt | 4 + .../presentation/wallet/state/WalletState.kt | 30 ++++- .../state/factory/WalletStateFactory.kt | 30 ++++- .../WalletLoadedTxHistoryConverter.kt | 4 +- .../WalletTxHistoryItemFlowConverter.kt | 8 +- .../wallet/ui/WalletEventEffect.kt | 36 ++++++ .../presentation/wallet/ui/WalletScreen.kt | 99 ++++++++++---- .../wallet/ui/WalletsListEffects.kt | 35 +++++ .../wallet/ui/components/WalletsList.kt | 44 ++++++- .../ui/components/common/WalletSideEffects.kt | 38 ------ .../wallet/ui/utils/ScrollOffsetCollector.kt | 48 ++++--- .../utils/WalletsListInteractionsCollector.kt | 14 ++ .../wallet/viewmodels/WalletViewModel.kt | 121 ++++++++++++------ 20 files changed, 418 insertions(+), 147 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffects.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletSideEffects.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/WalletsListInteractionsCollector.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index b465ae8d17..213f8f90e8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -85,7 +85,7 @@ private fun BaseContainer( Surface( modifier = modifier .defaultMinSize(minHeight = TangemTheme.dimens.size62) - .wrapContentWidth(), + .fillMaxWidth(), shape = TangemTheme.shapes.roundedCornersXMedium, color = containerColor, ) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt index cd7845822f..d90718dcb8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.extensions +import android.content.res.Resources import androidx.annotation.PluralsRes import androidx.annotation.StringRes import androidx.compose.runtime.Composable @@ -121,6 +122,22 @@ fun TextReference.resolveReference(): String { } } +/** Resolve [TextReference] as [String] using [resources] (non-composable context) */ +fun TextReference.resolveReference(resources: Resources): String { + return when (this) { + is TextReference.Res -> resources.getString(id, *formatArgs.toTypedArray()) + is TextReference.PluralRes -> resources.getQuantityString(id, count, *formatArgs.toTypedArray()) + is TextReference.Str -> value + is TextReference.Combined -> { + buildString { + refs.forEach { + append(it.resolveReference(resources)) + } + } + } + } +} + /** Concatenate [this] reference with [ref] */ operator fun TextReference.plus(ref: TextReference): TextReference { return when (this) { diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SaveWalletError.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SaveWalletError.kt index f292875ed2..7b320009c7 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SaveWalletError.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SaveWalletError.kt @@ -5,5 +5,7 @@ package com.tangem.domain.wallets.models */ sealed interface SaveWalletError { - object CommonError : SaveWalletError + object DataError : SaveWalletError + + data class WalletAlreadySaved(val messageId: Int) : SaveWalletError } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt index 3afdb23663..9998fa02aa 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt @@ -5,6 +5,7 @@ import arrow.core.left import arrow.core.right import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.domain.wallets.legacy.UserWalletsListError import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.models.SaveWalletError import com.tangem.domain.wallets.models.UserWallet @@ -19,9 +20,17 @@ import com.tangem.domain.wallets.models.UserWallet class SaveWalletUseCase(private val walletsStateHolder: WalletsStateHolder) { suspend operator fun invoke(userWallet: UserWallet, canOverride: Boolean = false): Either { - requireNotNull(walletsStateHolder.userWalletsListManager).save(userWallet, canOverride) + val userWalletListManager = walletsStateHolder.userWalletsListManager + ?: return SaveWalletError.DataError.left() + + userWalletListManager.save(userWallet, canOverride) .doOnSuccess { return Unit.right() } - .doOnFailure { return SaveWalletError.CommonError.left() } + .doOnFailure { + return when (it) { + is UserWalletsListError.WalletAlreadySaved -> SaveWalletError.WalletAlreadySaved(it.messageResId) + else -> SaveWalletError.DataError + }.left() + } return Unit.right() } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index a909a17c19..3c8749d178 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -383,6 +383,7 @@ internal object WalletPreviewData { bottomSheetConfig = bottomSheet, tokenActionsBottomSheet = actionsBottomSheet, onManageTokensClick = {}, + event = consumedEvent(), ) } @@ -432,6 +433,7 @@ internal object WalletPreviewData { ), ), ), + event = consumedEvent(), ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt new file mode 100644 index 0000000000..61c176f094 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt @@ -0,0 +1,12 @@ +package com.tangem.feature.wallet.presentation.wallet.state + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.extensions.TextReference + +@Immutable +internal sealed class WalletEvent { + + data class ChangeWallet(val index: Int) : WalletEvent() + + data class ShowError(val text: TextReference) : WalletEvent() +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMultiCurrencyState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMultiCurrencyState.kt index d0734666c9..d91449abb4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMultiCurrencyState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMultiCurrencyState.kt @@ -1,6 +1,8 @@ package com.tangem.feature.wallet.presentation.wallet.state import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.event.StateEvent +import com.tangem.core.ui.event.consumedEvent import com.tangem.feature.wallet.presentation.wallet.state.components.* import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -23,6 +25,7 @@ internal sealed class WalletMultiCurrencyState : WalletState.ContentState() { override val notifications: ImmutableList, override val bottomSheetConfig: TangemBottomSheetConfig?, override val tokensListState: WalletTokensListState, + override val event: StateEvent = consumedEvent(), val tokenActionsBottomSheet: ActionsBottomSheetConfig?, val onManageTokensClick: () -> Unit, ) : WalletMultiCurrencyState() @@ -37,6 +40,7 @@ internal sealed class WalletMultiCurrencyState : WalletState.ContentState() { override val onScanClick: () -> Unit, override val isBottomSheetShow: Boolean = false, override val onBottomSheetDismiss: () -> Unit = {}, + override val event: StateEvent = consumedEvent(), ) : WalletMultiCurrencyState(), WalletLockedState { override val notifications = persistentListOf( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletSingleCurrencyState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletSingleCurrencyState.kt index 8d9c0e265e..6a5467d025 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletSingleCurrencyState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletSingleCurrencyState.kt @@ -6,6 +6,8 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState +import com.tangem.core.ui.event.StateEvent +import com.tangem.core.ui.event.consumedEvent import com.tangem.feature.wallet.presentation.wallet.state.components.* import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.PersistentList @@ -35,6 +37,7 @@ internal sealed class WalletSingleCurrencyState : WalletState.ContentState() { override val bottomSheetConfig: TangemBottomSheetConfig?, override val buttons: PersistentList, override val txHistoryState: TxHistoryState, + override val event: StateEvent = consumedEvent(), val marketPriceBlockState: MarketPriceBlockState, ) : WalletSingleCurrencyState() @@ -49,6 +52,7 @@ internal sealed class WalletSingleCurrencyState : WalletState.ContentState() { override val onScanClick: () -> Unit, override val isBottomSheetShow: Boolean = false, override val onBottomSheetDismiss: () -> Unit = {}, + override val event: StateEvent = consumedEvent(), val onExploreClick: () -> Unit, ) : WalletSingleCurrencyState(), WalletLockedState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletState.kt index 6279d481aa..b938c5aa73 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletState.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.state import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.event.StateEvent import com.tangem.feature.wallet.presentation.wallet.state.components.* import kotlinx.collections.immutable.ImmutableList @@ -32,28 +33,49 @@ internal sealed class WalletState { /** Bottom sheet config */ abstract val bottomSheetConfig: TangemBottomSheetConfig? + /** State event */ + abstract val event: StateEvent + /** * Util function that allow to make a copy * * @param walletsListConfig wallets list config * @param pullToRefreshConfig pull to refresh config + * @param event state event */ fun copySealed( walletsListConfig: WalletsListConfig = this.walletsListConfig, pullToRefreshConfig: WalletPullToRefreshConfig = this.pullToRefreshConfig, + event: StateEvent = this.event, ): ContentState { return when (this) { is WalletMultiCurrencyState.Content -> { - copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig) + copy( + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + event = event, + ) } is WalletMultiCurrencyState.Locked -> { - copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig) + copy( + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + event = event, + ) } is WalletSingleCurrencyState.Content -> { - copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig) + copy( + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + event = event, + ) } is WalletSingleCurrencyState.Locked -> { - copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig) + copy( + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + event = event, + ) } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt index 6865f416ad..60f57fd065 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt @@ -5,6 +5,8 @@ import arrow.core.Either import com.tangem.common.Provider import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.event.consumedEvent +import com.tangem.core.ui.event.triggeredEvent import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.tokens.error.CurrencyStatusError @@ -16,10 +18,7 @@ import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryListError import com.tangem.domain.txhistory.models.TxHistoryStateError import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state.ActionsBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.* import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadedTxHistoryConverter import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadingTxHistoryConverter @@ -36,8 +35,10 @@ import kotlinx.coroutines.flow.Flow * @property currentStateProvider current ui state provider * @property currentCardTypeResolverProvider current card type resolver * @property currentWalletProvider current wallet + * @property appCurrencyProvider app currency provider * @property clickIntents screen click intents */ +@Suppress("TooManyFunctions") internal class WalletStateFactory( private val currentStateProvider: Provider, private val currentCardTypeResolverProvider: Provider, @@ -246,4 +247,25 @@ internal class WalletStateFactory( fun getStateByCurrencyStatusError(error: CurrencyStatusError): WalletState { return currencyStatusErrorConverter.convert(error) } + + fun getStateAndTriggerEvent( + state: WalletState, + event: WalletEvent, + setUiState: (WalletState) -> Unit, + ): WalletState { + return when (state) { + is WalletState.ContentState -> state.copySealed( + event = triggeredEvent( + data = event, + onConsume = { + val currentState = currentStateProvider() + if (currentState is WalletState.ContentState) { + setUiState(currentState.copySealed(event = consumedEvent())) + } + }, + ), + ) + is WalletState.Initial -> state + } + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt index 9c56350d24..202fcc3ae5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt @@ -63,7 +63,9 @@ internal class WalletLoadedTxHistoryConverter( private fun convert(items: Flow>): WalletState { return when (val state = currentStateProvider()) { is WalletSingleCurrencyState.Content -> { - state.copy(txHistoryState = walletTxHistoryItemFlowConverter.convert(value = items)) + return state.copy( + txHistoryState = walletTxHistoryItemFlowConverter.convert(value = items) ?: state.txHistoryState, + ) } is WalletMultiCurrencyState.Content, is WalletMultiCurrencyState.Locked, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt index 5e4a05132f..d1d713271b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt @@ -43,7 +43,7 @@ internal class WalletTxHistoryItemFlowConverter( private val currentStateProvider: Provider, private val blockchain: Blockchain, private val clickIntents: WalletClickIntents, -) : Converter>, TxHistoryState> { +) : Converter>, TxHistoryState?> { /** Example, 2 Aug, 2023 */ private val dateFormatter by lazy { @@ -67,9 +67,9 @@ internal class WalletTxHistoryItemFlowConverter( .withLocale(Locale.getDefault()) } - override fun convert(value: Flow>): TxHistoryState { - val state = currentStateProvider() as WalletSingleCurrencyState - val txHistoryContent = state.txHistoryState as TxHistoryState.Content + override fun convert(value: Flow>): TxHistoryState? { + val state = currentStateProvider() as? WalletSingleCurrencyState ?: return null + val txHistoryContent = state.txHistoryState as? TxHistoryState.Content ?: return state.txHistoryState // FIXME: TxHistoryRepository should send loading transactions // [REDACTED_JIRA] diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt new file mode 100644 index 0000000000..cbf3224300 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt @@ -0,0 +1,36 @@ +package com.tangem.feature.wallet.presentation.wallet.ui + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import com.tangem.core.ui.event.EventEffect +import com.tangem.core.ui.event.StateEvent +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent +import kotlinx.coroutines.delay + +@Composable +internal fun WalletEventEffect( + walletsListState: LazyListState, + snackbarHostState: SnackbarHostState, + event: StateEvent, + onAutoScrollSet: () -> Unit, +) { + val resources = LocalContext.current.resources + EventEffect( + event = event, + onTrigger = { value -> + when (value) { + is WalletEvent.ChangeWallet -> { + onAutoScrollSet() + delay(timeMillis = 800) + walletsListState.animateScrollToItem(index = value.index) + } + is WalletEvent.ShowError -> { + snackbarHostState.showSnackbar(message = value.text.resolveReference(resources)) + } + } + }, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index b09123933c..f403ba6e73 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -3,13 +3,13 @@ package com.tangem.feature.wallet.presentation.wallet.ui import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material3.FabPosition -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -28,6 +28,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencySt import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState import com.tangem.feature.wallet.presentation.wallet.state.WalletState import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.OrganizeTokensButtonState import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet @@ -50,27 +51,44 @@ internal fun WalletScreen(state: WalletState) { BackHandler(onBack = state.onBackClick) when (state) { - is WalletState.ContentState -> WalletContent(state = state) + is WalletState.ContentState -> { + val walletsListState = rememberLazyListState() + val snackbarHostState = remember { SnackbarHostState() } + val isAutoScroll = remember { mutableStateOf(value = false) } + + WalletContent( + state = state, + walletsListState = walletsListState, + snackbarHostState = snackbarHostState, + isAutoScroll = isAutoScroll, + onAutoScrollReset = { isAutoScroll.value = false }, + ) + + WalletEventEffect( + walletsListState = walletsListState, + snackbarHostState = snackbarHostState, + event = state.event, + onAutoScrollSet = { isAutoScroll.value = true }, + ) + } is WalletState.Initial -> Unit } } -@OptIn(ExperimentalMaterialApi::class) @Composable -private fun WalletContent(state: WalletState.ContentState) { - val walletsListState = rememberLazyListState() - - BaseScaffold(state = state) { scaffoldPaddings -> +private fun WalletContent( + state: WalletState.ContentState, + walletsListState: LazyListState, + snackbarHostState: SnackbarHostState, + isAutoScroll: State, + onAutoScrollReset: () -> Unit, +) { + BaseScaffold(state = state, snackbarHostState) { scaffoldPaddings -> val movableItemModifier = Modifier.changeWalletAnimator(walletsListState) - val pullRefreshState = rememberPullRefreshState( - refreshing = state.pullToRefreshConfig.isRefreshing, - onRefresh = state.pullToRefreshConfig.onRefresh, - ) - Box( - modifier = Modifier - .padding(paddingValues = scaffoldPaddings) - .pullRefresh(pullRefreshState), + UpdatableContainer( + pullToRefreshConfig = state.pullToRefreshConfig, + modifier = Modifier.padding(paddingValues = scaffoldPaddings), ) { val txHistoryItems = if (state is WalletSingleCurrencyState && state.txHistoryState is TxHistoryState.Content @@ -126,24 +144,51 @@ private fun WalletContent(state: WalletState.ContentState) { } } } - - WalletPullToRefreshIndicator( - isRefreshing = state.pullToRefreshConfig.isRefreshing, - state = pullRefreshState, - modifier = Modifier.align(Alignment.TopCenter), - ) } } WalletBottomSheets(state = state) - WalletSideEffects(lazyListState = walletsListState, walletsListConfig = state.walletsListConfig) + WalletsListEffects( + lazyListState = walletsListState, + walletsListConfig = state.walletsListConfig, + isAutoScroll = isAutoScroll, + onAutoScrollReset = onAutoScrollReset, + ) +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun UpdatableContainer( + pullToRefreshConfig: WalletPullToRefreshConfig, + modifier: Modifier = Modifier, + content: @Composable BoxScope.() -> Unit, +) { + val pullRefreshState = rememberPullRefreshState( + refreshing = pullToRefreshConfig.isRefreshing, + onRefresh = pullToRefreshConfig.onRefresh, + ) + + Box(modifier = modifier.pullRefresh(pullRefreshState)) { + content() + + WalletPullToRefreshIndicator( + isRefreshing = pullToRefreshConfig.isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + ) + } } @Composable -private fun BaseScaffold(state: WalletState.ContentState, content: @Composable (PaddingValues) -> Unit) { +private fun BaseScaffold( + state: WalletState.ContentState, + snackbarHostState: SnackbarHostState, + content: @Composable (PaddingValues) -> Unit, +) { Scaffold( topBar = { WalletTopBar(config = state.topBarConfig) }, + snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, floatingActionButton = { if (state is WalletMultiCurrencyState.Content) { ManageTokensButton(onManageTokensClick = state.onManageTokensClick) @@ -193,7 +238,7 @@ private fun WalletBottomSheets(state: WalletState) { @Composable private fun WalletScreenPreview_Light(@PreviewParameter(WalletScreenParameterProvider::class) state: WalletState) { TangemTheme { - WalletScreen(state) + WalletScreen(state = state) } } @@ -201,7 +246,7 @@ private fun WalletScreenPreview_Light(@PreviewParameter(WalletScreenParameterPro @Composable private fun WalletScreenPreview_Dark(@PreviewParameter(WalletScreenParameterProvider::class) state: WalletState) { TangemTheme(isDark = true) { - WalletScreen(state) + WalletScreen(state = state) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffects.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffects.kt new file mode 100644 index 0000000000..482d206fbf --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffects.kt @@ -0,0 +1,35 @@ +package com.tangem.feature.wallet.presentation.wallet.ui + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.snapshotFlow +import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig +import com.tangem.feature.wallet.presentation.wallet.ui.utils.ScrollOffsetCollector +import com.tangem.feature.wallet.presentation.wallet.ui.utils.WalletsListInteractionsCollector + +@Composable +internal fun WalletsListEffects( + lazyListState: LazyListState, + walletsListConfig: WalletsListConfig, + isAutoScroll: State, + onAutoScrollReset: () -> Unit, +) { + LaunchedEffect(key1 = lazyListState, key2 = walletsListConfig.onWalletChange) { + snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo } + .collect( + collector = ScrollOffsetCollector( + lazyListState = lazyListState, + walletsListConfig = walletsListConfig, + isAutoScroll = isAutoScroll, + ), + ) + } + + LaunchedEffect(Unit) { + lazyListState.interactionSource.interactions.collect( + collector = WalletsListInteractionsCollector(onDragStart = onAutoScrollReset), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt index 09acf941f4..9da275d0cb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt @@ -1,7 +1,11 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components +import androidx.compose.animation.core.* +import androidx.compose.animation.rememberSplineBasedDecay import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.snapping.SnapFlingBehavior +import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues @@ -10,19 +14,20 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig import com.tangem.feature.wallet.presentation.wallet.ui.components.common.WalletCard +private const val SHORT_SNAP_ELEMENT_COUNT = 50 + /** * Wallets list component * @@ -43,7 +48,7 @@ internal fun WalletsList(config: WalletsListConfig, lazyListState: LazyListState state = lazyListState, contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), - flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState), + flingBehavior = rememberWalletsFlingBehaviour(lazyListState = lazyListState, itemWidth = itemWidth), ) { items( items = config.wallets, @@ -60,6 +65,35 @@ internal fun WalletsList(config: WalletsListConfig, lazyListState: LazyListState } } +/** + * Custom implementation of fling behaviour that overrides 'shortSnapVelocityThreshold'. + * Every user's drag action will similar to a short snap + * if drag offset is less than [SHORT_SNAP_ELEMENT_COUNT] * item width. + * + * @param lazyListState lazy list state + * @param itemWidth list item width + * + * @see rememberSnapFlingBehavior + */ +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun rememberWalletsFlingBehaviour(lazyListState: LazyListState, itemWidth: Dp): SnapFlingBehavior { + val snappingLayout = remember(lazyListState) { SnapLayoutInfoProvider(lazyListState) } + val density = LocalDensity.current + val highVelocityApproachSpec: DecayAnimationSpec = rememberSplineBasedDecay() + + return remember(key1 = snappingLayout, key2 = highVelocityApproachSpec, key3 = density) { + SnapFlingBehavior( + snapLayoutInfoProvider = snappingLayout, + lowVelocityAnimationSpec = tween(durationMillis = 1000, easing = LinearEasing), + highVelocityAnimationSpec = highVelocityApproachSpec, + snapAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow), + density = density, + shortSnapVelocityThreshold = itemWidth * SHORT_SNAP_ELEMENT_COUNT, + ) + } +} + @Preview @Composable private fun Preview_WalletsList_LightTheme() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletSideEffects.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletSideEffects.kt deleted file mode 100644 index db3972e010..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletSideEffects.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.ui.components.common - -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.snapshotFlow -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig -import com.tangem.feature.wallet.presentation.wallet.ui.utils.ScrollOffsetCollector - -/** - * Wallet screen side effects - * - * @param lazyListState lazy list state - * @param walletsListConfig wallets list config - * -[REDACTED_AUTHOR] - */ -@Composable -internal fun WalletSideEffects(lazyListState: LazyListState, walletsListConfig: WalletsListConfig) { - LaunchedEffect(key1 = walletsListConfig.selectedWalletIndex) { - if (!lazyListState.isScrollInProgress) { - lazyListState.animateScrollToItem(walletsListConfig.selectedWalletIndex) - } - } - - val dragInteraction = lazyListState.interactionSource.interactions.collectAsState(initial = null) - LaunchedEffect(key1 = lazyListState, key2 = walletsListConfig.onWalletChange) { - snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo } - .collect( - collector = ScrollOffsetCollector( - lazyListState = lazyListState, - dragInteraction = dragInteraction, - callback = walletsListConfig.onWalletChange, - ), - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollector.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollector.kt index ba5548fe3e..60f81583b2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollector.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollector.kt @@ -1,50 +1,58 @@ package com.tangem.feature.wallet.presentation.wallet.ui.utils -import androidx.compose.foundation.interaction.DragInteraction -import androidx.compose.foundation.interaction.Interaction import androidx.compose.foundation.lazy.LazyListItemInfo import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.State +import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig import kotlinx.coroutines.flow.FlowCollector import kotlin.math.abs /** * Flow collector for scroll items tracking. - * If first visible item offset is greater than half item size, then [callback] be invoked. - * If last visible item offset is greater than half item size, then [callback] be invoked. + * If first visible item offset is greater than half item size, then change selected wallet index. + * If last visible item offset is greater than half item size, then change selected wallet index. * - * @property lazyListState lazy list state - * @property dragInteraction current drag interaction - * @property callback lambda be invoked when current scroll items is changed + * @property lazyListState lazy list state + * @property walletsListConfig wallets list config + * @property isAutoScroll check if last scrolling is auto scroll * [REDACTED_AUTHOR] */ internal class ScrollOffsetCollector( private val lazyListState: LazyListState, - private val dragInteraction: State, - private val callback: (Int) -> Unit, + private val walletsListConfig: WalletsListConfig, + private val isAutoScroll: State, ) : FlowCollector> { private val LazyListItemInfo.halfItemSize get() = size.div(other = 2) + private var currentIndex = walletsListConfig.selectedWalletIndex + set(value) { + if (field != value) { + field = value + } + } + override suspend fun emit(value: List) { - if (isNotUserInteraction() || value.size <= 1) return + // Auto scroll must not change wallet + if (isAutoScroll.value) { + currentIndex = walletsListConfig.selectedWalletIndex + return + } + + if (!lazyListState.isScrollInProgress || value.size <= 1) return val firstItem = value.firstOrNull() ?: return val lastItem = value.lastOrNull() ?: return if (abs(firstItem.offset) > firstItem.halfItemSize) { - callback(firstItem.index + 1) + val newIndex = firstItem.index + 1 + currentIndex = newIndex + walletsListConfig.onWalletChange(newIndex) } else if (abs(lastItem.offset) > lastItem.halfItemSize) { - callback(lastItem.index - 1) + val newIndex = lastItem.index - 1 + currentIndex = newIndex + walletsListConfig.onWalletChange(newIndex) } } - - /** - * Sometimes the list is scrolled programmatically. Example: selecting a specific wallet when a user opens the - * screen for the first time or scans a new wallet. Therefore [ScrollOffsetCollector] should not respond to changes. - */ - private fun isNotUserInteraction(): Boolean { - return !lazyListState.isScrollInProgress || dragInteraction.value !is DragInteraction.Start - } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/WalletsListInteractionsCollector.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/WalletsListInteractionsCollector.kt new file mode 100644 index 0000000000..49ade64a90 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/WalletsListInteractionsCollector.kt @@ -0,0 +1,14 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.utils + +import androidx.compose.foundation.interaction.DragInteraction +import androidx.compose.foundation.interaction.Interaction +import kotlinx.coroutines.flow.FlowCollector + +internal class WalletsListInteractionsCollector( + private val onDragStart: () -> Unit, +) : FlowCollector { + + override suspend fun emit(value: Interaction?) { + if (value is DragInteraction.Start) onDragStart() + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index a0deea99b7..17ee898f80 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -10,6 +10,7 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig import com.tangem.core.ui.components.marketprice.MarketPriceBlockState +import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.card.* @@ -30,15 +31,13 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.SaveWalletError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.* import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.router.InnerWalletRouter -import com.tangem.feature.wallet.presentation.wallet.state.WalletLockedState -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.* import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState @@ -51,6 +50,7 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.properties.Delegates @@ -131,6 +131,7 @@ internal class WalletViewModel @Inject constructor( private val buttonsJobHolder = JobHolder() private val notificationsJobHolder = JobHolder() private val refreshContentJobHolder = JobHolder() + private val onWalletChangeJobHolder = JobHolder() private val walletsUpdateActionResolver = WalletsUpdateActionResolver( currentStateProvider = Provider { uiState }, @@ -181,18 +182,17 @@ internal class WalletViewModel @Inject constructor( } } - private fun loadAndUpdateState(index: Int) { - uiState = stateFactory.getSkeletonState(wallets = wallets, selectedWalletIndex = index) - - getContentItemsUpdates(index = index) - } - private fun deleteWalletAndUpdateState(action: WalletsUpdateActionResolver.Action.DeleteWallet) { val cacheState = WalletStateCache.getState(userWalletId = action.selectedWalletId) if (cacheState != null) { uiState = stateFactory.getStateWithoutDeletedWallet(cacheState, action) if (cacheState.isLoadingState()) { + uiState = stateFactory.getStateAndTriggerEvent( + state = uiState, + event = WalletEvent.ChangeWallet(action.selectedWalletIndex), + setUiState = { uiState = it }, + ) getContentItemsUpdates(action.selectedWalletIndex) } } else { @@ -200,6 +200,18 @@ internal class WalletViewModel @Inject constructor( } } + private fun loadAndUpdateState(index: Int) { + uiState = stateFactory.getSkeletonState(wallets = wallets, selectedWalletIndex = index) + + uiState = stateFactory.getStateAndTriggerEvent( + state = uiState, + event = WalletEvent.ChangeWallet(index = index), + setUiState = { uiState = it }, + ) + + getContentItemsUpdates(index = index) + } + override fun onBackClick() { viewModelScope.launch(dispatchers.main) { router.popBackStack(screen = if (shouldSaveUserWalletsUseCase()) AppScreen.Welcome else AppScreen.Home) @@ -215,8 +227,31 @@ internal class WalletViewModel @Inject constructor( if (userWallet != null) { saveWalletUseCase(userWallet = userWallet, canOverride = false) + .onLeft { saveWalletError -> + when (saveWalletError) { + is SaveWalletError.DataError -> Unit + is SaveWalletError.WalletAlreadySaved -> { + uiState = stateFactory.getStateAndTriggerEvent( + state = uiState, + event = WalletEvent.ShowError( + text = TextReference.Res(saveWalletError.messageId), + ), + setUiState = { uiState = it }, + ) + } + } + } } } + .doOnFailure { tangemError -> + uiState = stateFactory.getStateAndTriggerEvent( + state = uiState, + event = WalletEvent.ShowError( + text = TextReference.Str(tangemError.customMessage), + ), + setUiState = { uiState = it }, + ) + } } } @@ -307,39 +342,45 @@ internal class WalletViewModel @Inject constructor( val state = uiState as? WalletState.ContentState ?: return if (state.walletsListConfig.selectedWalletIndex == index) return - viewModelScope.launch(dispatchers.io) { - selectWalletUseCase(getWallet(index = index).walletId) - } + // Reset the job to avoid a redundant state updating + onWalletChangeJobHolder.update(null) - val cacheState = WalletStateCache.getState(userWalletId = state.walletsListConfig.wallets[index].id) - if (cacheState != null && cacheState !is WalletLockedState) { - uiState = cacheState.copySealed( - walletsListConfig = state.walletsListConfig.copy( - selectedWalletIndex = index, - wallets = state.walletsListConfig.wallets - .mapIndexed { mapIndex, currentWallet -> - val cacheWallet = cacheState.walletsListConfig.wallets.getOrNull(mapIndex) - - if (currentWallet is WalletCardState.Loading && cacheWallet != null && - cacheWallet.isLoaded() - ) { - cacheWallet - } else { - currentWallet - } - } - .toImmutableList(), - ), - pullToRefreshConfig = cacheState.pullToRefreshConfig.copy(isRefreshing = false), - ) - - if (cacheState.isLoadingState()) { - getContentItemsUpdates(index) + viewModelScope.launch(dispatchers.main) { + withContext(dispatchers.io) { + selectWalletUseCase(userWalletId = state.walletsListConfig.wallets[index].id) + } + + val cacheState = WalletStateCache.getState(userWalletId = state.walletsListConfig.wallets[index].id) + if (cacheState != null && cacheState !is WalletLockedState) { + uiState = cacheState.copySealed( + walletsListConfig = state.walletsListConfig.copy( + selectedWalletIndex = index, + wallets = state.walletsListConfig.wallets + .mapIndexed { mapIndex, currentWallet -> + val cacheWallet = cacheState.walletsListConfig.wallets.getOrNull(mapIndex) + + if (currentWallet is WalletCardState.Loading && cacheWallet != null && + cacheWallet.isLoaded() + ) { + cacheWallet + } else { + currentWallet + } + } + .toImmutableList(), + ), + pullToRefreshConfig = cacheState.pullToRefreshConfig.copy(isRefreshing = false), + ) + + if (cacheState.isLoadingState()) { + getContentItemsUpdates(index) + } + } else { + uiState = stateFactory.getSkeletonState(wallets = wallets, selectedWalletIndex = index) + getContentItemsUpdates(index = index) } - } else { - uiState = stateFactory.getSkeletonState(wallets = wallets, selectedWalletIndex = index) - getContentItemsUpdates(index = index) } + .saveIn(onWalletChangeJobHolder) } private fun WalletCardState.isLoaded(): Boolean {