diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TxHistoryState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TxHistoryState.kt index 85c907187a..a35545faea 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TxHistoryState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TxHistoryState.kt @@ -1,76 +1,67 @@ package com.tangem.core.ui.components.transactions.state import androidx.paging.PagingData +import androidx.paging.TerminalSeparatorType +import androidx.paging.insertHeaderItem import com.tangem.core.ui.components.wallet.WalletLockedContentState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map -/** - * Wallet transaction history state - */ +/** Wallet transaction history state */ sealed interface TxHistoryState { /** - * Wallet transaction history state with content + * Wallet transaction history state with content. Items contains a required [TxHistoryItemState.Title]. * - * @property items content items + * @property contentItems content items */ - sealed class ContentState(open val items: Flow>) : TxHistoryState + sealed class ContentState(private val contentItems: Flow>) : TxHistoryState { + + /** Lambda be invoke when explore button was clicked */ + abstract val onExploreClick: () -> Unit + + /** Content items with [TxHistoryItemState.Title] */ + val items: Flow> + get() { + return contentItems.map { + it.insertHeaderItem( + terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE, + item = TxHistoryItemState.Title(onExploreClick = onExploreClick), + ) + } + } + } /** * Loading state * * @property onExploreClick lambda be invoke when explore button was clicked + * @property transactions loading transactions */ - data class Loading(val onExploreClick: () -> Unit) : ContentState( - items = flowOf( - PagingData.from( - listOf( - TxHistoryItemState.Title(onExploreClick = onExploreClick), - TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)), - ), - ), - ), - ) - - /** - * Wallet transaction history state with loading transactions - * - * @property itemsCount count of loading transactions - */ - data class ContentWithLoadingItems(val itemsCount: Int) : ContentState( - items = flowOf( - value = PagingData.from( - data = buildList(capacity = itemsCount) { - add(TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH))) - }, - ), - ), - ) + data class Loading( + override val onExploreClick: () -> Unit, + val transactions: Flow> = getDefaultLoadingTransactions(), + ) : ContentState(transactions) /** * Wallet transaction history state with content * - * @property items content items + * @property onExploreClick lambda be invoke when explore button was clicked + * @property contentItems content items */ - data class Content(override val items: Flow>) : ContentState(items) + data class Content( + override val onExploreClick: () -> Unit, + val contentItems: Flow>, + ) : ContentState(contentItems) /** * Locked state * * @property onExploreClick lambda be invoke when explore button was clicked */ - data class Locked(val onExploreClick: () -> Unit) : - ContentState( - items = flowOf( - PagingData.from( - listOf( - TxHistoryItemState.Title(onExploreClick = onExploreClick), - TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)), - ), - ), - ), - ), + data class Locked(override val onExploreClick: () -> Unit) : + ContentState(contentItems = getDefaultLoadingTransactions()), WalletLockedContentState /** @@ -121,5 +112,17 @@ sealed interface TxHistoryState { private companion object { const val LOADING_TX_HASH = "LOADING_TX_HASH" + + private fun getDefaultLoadingTransactions(): Flow> { + return flowOf( + value = PagingData.from( + data = listOf( + element = TxHistoryItemState.Transaction( + state = TransactionState.Loading(txHash = LOADING_TX_HASH), + ), + ), + ), + ) + } } } \ No newline at end of file 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 d0bb5cc8e7..62c0b8ed22 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 @@ -351,10 +351,10 @@ internal object WalletPreviewData { ), ), txHistoryState = TxHistoryState.Content( - flowOf( + onExploreClick = {}, + contentItems = flowOf( PagingData.from( listOf( - TxHistoryState.TxHistoryItemState.Title(onExploreClick = {}), TxHistoryState.TxHistoryItemState.GroupTitle("Today"), TxHistoryState.TxHistoryItemState.Transaction( TransactionState.Sending( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt index bb2883ed5f..214c651d7f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt @@ -75,12 +75,13 @@ internal sealed interface TokenItemState { ) : TokenItemState /** Token options state */ + @Immutable sealed interface TokenOptionsState { /** * Visible token options state * - * @property fiatAmount fiat amount of token + * @property fiatAmount fiat amount of token * @property priceChange value of price changing */ data class Visible(val fiatAmount: String, val priceChange: PriceChangeConfig) : TokenOptionsState 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 8433b8d9f7..ab23400cca 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 @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.state +import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.feature.wallet.presentation.wallet.state.components.* @@ -11,14 +12,12 @@ import kotlinx.collections.immutable.persistentListOf * [REDACTED_AUTHOR] */ +@Immutable internal sealed class WalletSingleCurrencyState : WalletState.ContentState() { /** Manage buttons */ abstract val buttons: ImmutableList - /** Market price block state */ - abstract val marketPriceBlockState: MarketPriceBlockState? - /** Transactions history state */ abstract val txHistoryState: TxHistoryState @@ -30,8 +29,8 @@ internal sealed class WalletSingleCurrencyState : WalletState.ContentState() { override val notifications: ImmutableList, override val bottomSheetConfig: WalletBottomSheetConfig?, override val buttons: ImmutableList, - override val marketPriceBlockState: MarketPriceBlockState, override val txHistoryState: TxHistoryState, + val marketPriceBlockState: MarketPriceBlockState, ) : WalletSingleCurrencyState() data class Locked( @@ -61,8 +60,6 @@ internal sealed class WalletSingleCurrencyState : WalletState.ContentState() { ), ) - override val marketPriceBlockState = null - override val txHistoryState: TxHistoryState = TxHistoryState.Locked(onExploreClick) } } \ No newline at end of file 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 9cc1f3fa27..c9c327f540 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 @@ -34,14 +34,26 @@ internal sealed class WalletState { /** * Util function that allow to make a copy * - * @param walletsListConfig wallets list config + * @param walletsListConfig wallets list config + * @param pullToRefreshConfig pull to refresh config */ - fun copySealed(walletsListConfig: WalletsListConfig = this.walletsListConfig): ContentState { + fun copySealed( + walletsListConfig: WalletsListConfig = this.walletsListConfig, + pullToRefreshConfig: WalletPullToRefreshConfig = this.pullToRefreshConfig, + ): ContentState { return when (this) { - is WalletMultiCurrencyState.Content -> copy(walletsListConfig = walletsListConfig) - is WalletMultiCurrencyState.Locked -> copy(walletsListConfig = walletsListConfig) - is WalletSingleCurrencyState.Content -> copy(walletsListConfig = walletsListConfig) - is WalletSingleCurrencyState.Locked -> copy(walletsListConfig = walletsListConfig) + is WalletMultiCurrencyState.Content -> { + copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig) + } + is WalletMultiCurrencyState.Locked -> { + copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig) + } + is WalletSingleCurrencyState.Content -> { + copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig) + } + is WalletSingleCurrencyState.Locked -> { + copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig) + } } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt index 6d6055c045..e8745338ed 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.components +import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.feature.wallet.impl.R @@ -11,6 +12,7 @@ import com.tangem.feature.wallet.impl.R * [REDACTED_AUTHOR] */ +@Immutable sealed class WalletManageButton(val config: ActionButtonConfig) { /** Lambda be invoked when manage button is clicked */ diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt index 99a59df14d..60abf781ae 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.components +import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.notifications.NotificationState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.WrappedList @@ -14,6 +15,7 @@ import com.tangem.feature.wallet.impl.R [REDACTED_AUTHOR] */ // TODO: Finalize notification strings [REDACTED_JIRA] +@Immutable sealed class WalletNotification(open val state: NotificationState) { /** Clickable notification */ diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt index 1209a2da37..4df7b1d04d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt @@ -28,14 +28,17 @@ internal sealed class WalletTokensListState { open val onOrganizeTokensClick: (() -> Unit)?, ) : WalletTokensListState() - /** Loading content state */ - object Loading : ContentState( - items = persistentListOf( + /** + * Loading content state + * + * @property items content items + */ + data class Loading( + override val items: ImmutableList = persistentListOf( TokensListItemState.Token(state = TokenItemState.Loading(id = FIRST_LOADING_TOKEN_ID)), TokensListItemState.Token(state = TokenItemState.Loading(id = SECOND_LOADING_TOKEN_ID)), ), - onOrganizeTokensClick = null, - ) + ) : ContentState(items = items, onOrganizeTokensClick = null) /** * Content state diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRefreshStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRefreshStateConverter.kt new file mode 100644 index 0000000000..0fe9e475c9 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRefreshStateConverter.kt @@ -0,0 +1,128 @@ +package com.tangem.feature.wallet.presentation.wallet.state.factory + +import androidx.paging.PagingData +import androidx.paging.map +import com.tangem.common.Provider +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.components.transactions.state.TxHistoryState.TxHistoryItemState +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +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.components.WalletCardState +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.TokensListItemState +import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig +import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.map + +internal class WalletRefreshStateConverter( + private val currentStateProvider: Provider, + private val clickIntents: WalletClickIntents, +) : Converter { + + override fun convert(value: Unit): WalletState { + return when (val state = currentStateProvider()) { + is WalletMultiCurrencyState.Content -> state.getRefreshState() + is WalletSingleCurrencyState.Content -> state.getRefreshState() + else -> state + } + } + + private fun WalletMultiCurrencyState.Content.getRefreshState(): WalletMultiCurrencyState.Content { + return copy( + walletsListConfig = getWalletsListConfig(), + pullToRefreshConfig = getPullToRefreshConfig(), + tokensListState = getTokenListState(), + ) + } + + private fun WalletSingleCurrencyState.Content.getRefreshState(): WalletSingleCurrencyState.Content { + return copy( + // TODO: [REDACTED_JIRA] + walletsListConfig = getWalletsListConfig(additionalInfo = ""), + pullToRefreshConfig = getPullToRefreshConfig(), + txHistoryState = getTxHistoryState(), + marketPriceBlockState = MarketPriceBlockState.Loading(currencyName = marketPriceBlockState.currencyName), + ) + } + + private fun WalletState.ContentState.getWalletsListConfig(additionalInfo: String? = null): WalletsListConfig { + val selectedWallet = walletsListConfig.wallets[walletsListConfig.selectedWalletIndex] + + return walletsListConfig.copy( + wallets = walletsListConfig.wallets + .toPersistentList() + .set( + index = walletsListConfig.selectedWalletIndex, + element = WalletCardState.Loading( + id = selectedWallet.id, + title = selectedWallet.title, + additionalInfo = additionalInfo ?: selectedWallet.additionalInfo, + imageResId = selectedWallet.imageResId, + ), + ), + ) + } + + private fun WalletState.ContentState.getPullToRefreshConfig(): WalletPullToRefreshConfig { + return pullToRefreshConfig.copy(isRefreshing = true) + } + + private fun WalletMultiCurrencyState.Content.getTokenListState(): WalletTokensListState { + return when (tokensListState) { + is WalletTokensListState.Content -> { + WalletTokensListState.Loading( + items = tokensListState.items + .filterIsInstance() + .map { + TokensListItemState.Token(state = TokenItemState.Loading(id = it.state.id)) + } + .toImmutableList(), + ) + } + is WalletTokensListState.Empty -> WalletTokensListState.Loading() + is WalletTokensListState.Loading, + is WalletTokensListState.Locked, + -> tokensListState + } + } + + private fun WalletSingleCurrencyState.Content.getTxHistoryState(): TxHistoryState { + return when (txHistoryState) { + is TxHistoryState.Content -> { + TxHistoryState.Loading( + onExploreClick = clickIntents::onExploreClick, + transactions = txHistoryState.contentItems + .filterIsInstance>() + .mapPagingData { transaction -> + transaction.copy( + state = TransactionState.Loading(txHash = transaction.state.txHash), + ) + }, + ) + } + is TxHistoryState.Empty, + is TxHistoryState.Error, + is TxHistoryState.NotSupported, + -> TxHistoryState.Loading(onExploreClick = clickIntents::onExploreClick) + is TxHistoryState.Locked, + is TxHistoryState.Loading, + -> txHistoryState + } + } + + private fun Flow>.mapPagingData( + transform: (TxHistoryItemState.Transaction) -> TxHistoryItemState, + ): Flow> { + return map { it.map(transform) } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt index c6d3e7fae9..7e84f3f652 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt @@ -14,6 +14,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyS import com.tangem.feature.wallet.presentation.wallet.state.WalletState import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig +import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletSingleCurrencyLoadedBalanceConverter.SingleCurrencyLoadedBalanceModel import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toPersistentList import java.math.BigDecimal @@ -22,21 +23,32 @@ internal class WalletSingleCurrencyLoadedBalanceConverter( private val currentStateProvider: Provider, private val cardTypeResolverProvider: Provider, private val appCurrencyProvider: Provider, -) : Converter, WalletSingleCurrencyState.Content> { +) : Converter { - override fun convert(value: Either): WalletSingleCurrencyState.Content { - return value.fold(ifLeft = { convertError() }, ifRight = ::convert) + override fun convert(value: SingleCurrencyLoadedBalanceModel): WalletSingleCurrencyState.Content { + return value.cryptoCurrencyEither.fold( + ifLeft = { convertError() }, + ifRight = { convertContent(it, value.isRefreshing) }, + ) } private fun convertError(): WalletSingleCurrencyState.Content { return requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content) } - private fun convert(status: CryptoCurrencyStatus): WalletSingleCurrencyState.Content { + private fun convertContent( + status: CryptoCurrencyStatus, + isRefreshing: Boolean, + ): WalletSingleCurrencyState.Content { val state = requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content) val currencyName = state.marketPriceBlockState.currencyName return state.copy( walletsListConfig = getUpdatedSelectedWallet(status = status.value, state = state), + pullToRefreshConfig = if (isRefreshing) { + state.pullToRefreshConfig.copy(isRefreshing = status.value is CryptoCurrencyStatus.Loading) + } else { + state.pullToRefreshConfig + }, marketPriceBlockState = getMarketPriceState(status = status.value, currencyName = currencyName), ) } @@ -153,4 +165,9 @@ internal class WalletSingleCurrencyLoadedBalanceConverter( fiatCurrencySymbol = appCurrency.symbol, ) } + + data class SingleCurrencyLoadedBalanceModel( + val cryptoCurrencyEither: Either, + val isRefreshing: Boolean, + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt index f40a3b577a..f452ed705f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt @@ -47,7 +47,7 @@ internal class WalletSkeletonStateConverter( topBarConfig = createTopBarConfig(), walletsListConfig = createWalletsListConfig(value), pullToRefreshConfig = createPullToRefreshConfig(), - tokensListState = WalletTokensListState.Loading, + tokensListState = WalletTokensListState.Loading(), notifications = persistentListOf(), bottomSheetConfig = null, ) 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 9e274774b5..5b53bed190 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 @@ -77,6 +77,10 @@ internal class WalletStateFactory( ) } + private val refreshStateConverter by lazy { + WalletRefreshStateConverter(currentStateProvider = currentStateProvider, clickIntents = clickIntents) + } + fun getInitialState(): WalletState = WalletState.Initial(onBackClick = clickIntents::onBackClick) fun getSkeletonState(wallets: List, selectedWalletIndex: Int): WalletState { @@ -105,9 +109,7 @@ internal class WalletStateFactory( } } - fun getStateAfterContentRefreshing(): WalletState { - return currentStateProvider() - } + fun getStateAfterContentRefreshing(): WalletState = refreshStateConverter.convert(Unit) fun getStateWithOpenBottomSheet(content: WalletBottomSheetConfig.BottomSheetContentConfig): WalletState { return when (val state = currentStateProvider() as WalletState.ContentState) { @@ -205,7 +207,13 @@ internal class WalletStateFactory( fun getSingleCurrencyLoadedBalanceState( cryptoCurrencyEither: Either, + isRefreshing: Boolean, ): WalletState { - return singleCurrencyLoadedBalanceConverter.convert(cryptoCurrencyEither) + return singleCurrencyLoadedBalanceConverter.convert( + value = WalletSingleCurrencyLoadedBalanceConverter.SingleCurrencyLoadedBalanceModel( + cryptoCurrencyEither = cryptoCurrencyEither, + isRefreshing = isRefreshing, + ), + ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt index ef8131d180..b9600ca87e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt @@ -1,13 +1,16 @@ package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory +import androidx.paging.PagingData import arrow.core.Either import com.tangem.common.Provider +import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.txhistory.models.TxHistoryStateError 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.viewmodels.WalletClickIntents import com.tangem.utils.converter.Converter +import kotlinx.coroutines.flow.flow /** * Converter from loading tx history state to [WalletSingleCurrencyState.Content] @@ -44,7 +47,17 @@ internal class WalletLoadingTxHistoryConverter( private fun convert(value: Int): WalletSingleCurrencyState.Content { return requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content).copy( - txHistoryState = TxHistoryState.ContentWithLoadingItems(itemsCount = value), + txHistoryState = TxHistoryState.Loading( + onExploreClick = clickIntents::onExploreClick, + transactions = flow { + PagingData.from( + data = MutableList( + size = value, + init = { TransactionState.Loading(it.toString()) }, + ), + ) + }, + ), ) } } \ No newline at end of file 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 1c4af33fac..b3fbee3db3 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 @@ -1,7 +1,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory import android.text.format.DateUtils -import androidx.paging.* +import androidx.paging.PagingData +import androidx.paging.TerminalSeparatorType +import androidx.paging.insertSeparators +import androidx.paging.map import com.tangem.blockchain.common.Blockchain import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState @@ -58,17 +61,14 @@ internal class WalletTxHistoryItemFlowConverter( override fun convert(value: Flow>): TxHistoryState { return TxHistoryState.Content( - items = value + onExploreClick = clickIntents::onExploreClick, + contentItems = value .map { pagingData -> pagingData .map { item -> // [createTransactionState] returns timestamp without formatting TxHistoryItemState.Transaction(state = createTransactionState(item)) } - .insertHeaderItem( - terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE, - item = TxHistoryItemState.Title(onExploreClick = clickIntents::onExploreClick), - ) .insertGroupTitle() // method uses the raw timestamp .formatTransactionsTimestamp() // method formats the timestamp }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt index fb21502fe6..7d29a08e9e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt @@ -4,10 +4,8 @@ import com.tangem.common.Provider import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.tokens.model.TokenList -import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter.TokensListModel import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents @@ -35,7 +33,7 @@ internal class TokenListToWalletStateConverter( return state.copy( walletsListConfig = state.updateSelectedWallet(fiatBalance = value.tokenList.totalFiatBalance), pullToRefreshConfig = if (value.isRefreshing) { - state.pullToRefreshConfig.copy(isRefreshing = state.getRefreshingStatus()) + state.pullToRefreshConfig.copy(isRefreshing = getRefreshingStatus(tokenList = value.tokenList)) } else { state.pullToRefreshConfig }, @@ -60,17 +58,8 @@ internal class TokenListToWalletStateConverter( ) } - private fun WalletState.getRefreshingStatus(): Boolean { - return if (this is WalletMultiCurrencyState.Content && - this.tokensListState is WalletTokensListState.ContentState - ) { - tokensListState.items.any { tokensListItemState -> - tokensListItemState is WalletTokensListState.TokensListItemState.Token && - tokensListItemState.state is TokenItemState.Loading - } - } else { - false - } + private fun getRefreshingStatus(tokenList: TokenList): Boolean { + return tokenList.totalFiatBalance is TokenList.FiatBalance.Loading } data class TokensListModel(val tokenList: TokenList, val isRefreshing: Boolean) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt index 11beecade5..fb70c489e4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt @@ -1,15 +1,10 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels -import com.tangem.common.Provider import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList -import com.tangem.feature.wallet.presentation.common.state.TokenItemState -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow @@ -26,7 +21,6 @@ import kotlinx.coroutines.flow.flow [REDACTED_AUTHOR] */ internal class WalletNotificationsListFactory( - private val currentStateProvider: Provider, private val wasCardScannedCallback: suspend (String) -> Boolean, private val isUserAlreadyRateAppCallback: suspend () -> Boolean, private val isDemoCardCallback: (String) -> Boolean, @@ -56,7 +50,7 @@ internal class WalletNotificationsListFactory( add(element = WalletNotification.DemoCard) } - if (hasUnreachableNetworks()) { + if (hasUnreachableNetworks(tokenList)) { add(element = WalletNotification.UnreachableNetworks) } @@ -112,15 +106,22 @@ internal class WalletNotificationsListFactory( } } - private fun hasUnreachableNetworks(): Boolean { - val isUnreachableState = { item: WalletTokensListState.TokensListItemState -> - (item as? WalletTokensListState.TokensListItemState.Token)?.state is TokenItemState.Unreachable - } - - return currentStateProvider().let { state -> - state is WalletMultiCurrencyState.Content && - state.tokensListState is WalletTokensListState.ContentState && - state.tokensListState.items.any(isUnreachableState) + private fun hasUnreachableNetworks(tokenList: TokenList?): Boolean { + return when (tokenList) { + is TokenList.GroupedByNetwork -> { + tokenList.groups + .flatMap(NetworkGroup::currencies) + .map(CryptoCurrencyStatus::value) + .any { it is CryptoCurrencyStatus.Unreachable } + } + is TokenList.Ungrouped -> { + tokenList.currencies + .map(CryptoCurrencyStatus::value) + .any { it is CryptoCurrencyStatus.Unreachable } + } + is TokenList.NotInitialized, + null, + -> false } } 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 f81cd1f969..b6bca4b04a 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 @@ -43,6 +43,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject @@ -83,7 +84,6 @@ internal class WalletViewModel @Inject constructor( private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() private val notificationsListFactory = WalletNotificationsListFactory( - currentStateProvider = Provider { uiState }, wasCardScannedCallback = getCardWasScannedUseCase::invoke, isUserAlreadyRateAppCallback = isUserAlreadyRateAppUseCase::invoke, isDemoCardCallback = isDemoCardUseCase::invoke, @@ -148,7 +148,7 @@ internal class WalletViewModel @Inject constructor( when { getWallet(index).isLocked -> uiState = stateFactory.getLockedState() cardTypeResolver.isMultiwalletAllowed() -> updateMultiCurrencyContent(index, isRefreshing) - !cardTypeResolver.isMultiwalletAllowed() -> updateSingleCurrencyContent(index) + !cardTypeResolver.isMultiwalletAllowed() -> updateSingleCurrencyContent(index, isRefreshing) } } @@ -157,7 +157,7 @@ internal class WalletViewModel @Inject constructor( "Impossible to update tokens list if state isn't WalletMultiCurrencyState" } - getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[index].id) + getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[index].id, refresh = isRefreshing) .distinctUntilChanged() .onEach { tokenListEither -> uiState = stateFactory.getStateByTokensList( @@ -175,13 +175,13 @@ internal class WalletViewModel @Inject constructor( .saveIn(tokensJobHolder) } - private fun updateSingleCurrencyContent(index: Int) { + private fun updateSingleCurrencyContent(index: Int, isRefreshing: Boolean) { val wallet = getWallet(index) updateTxHistory( blockchain = getCardTypeResolver(index).getBlockchain(), derivationStyle = wallet.scanResponse.derivationStyleProvider.getDerivationStyle(), ) - updateMarketPrice(userWalletId = wallet.walletId) + updateMarketPrice(userWalletId = wallet.walletId, isRefreshing = isRefreshing) updateNotifications(index) } @@ -209,10 +209,16 @@ internal class WalletViewModel @Inject constructor( } } - private fun updateMarketPrice(userWalletId: UserWalletId) { + // It also update wallet balance + private fun updateMarketPrice(userWalletId: UserWalletId, isRefreshing: Boolean) { getPrimaryCurrencyUseCase(userWalletId = userWalletId) .distinctUntilChanged() - .onEach { uiState = stateFactory.getSingleCurrencyLoadedBalanceState(cryptoCurrencyEither = it) } + .onEach { + uiState = stateFactory.getSingleCurrencyLoadedBalanceState( + cryptoCurrencyEither = it, + isRefreshing = isRefreshing, + ) + } .flowOn(dispatchers.io) .launchIn(viewModelScope) .saveIn(marketPriceJobHolder) @@ -336,7 +342,10 @@ internal class WalletViewModel @Inject constructor( val cacheState = WalletStateCache.getState(userWalletId = state.walletsListConfig.wallets[index].id) if (cacheState != null) { uiState = if (cacheState is WalletState.ContentState) { - cacheState.copySealed(walletsListConfig = state.walletsListConfig.copy(selectedWalletIndex = index)) + cacheState.copySealed( + walletsListConfig = state.walletsListConfig.copy(selectedWalletIndex = index), + pullToRefreshConfig = state.pullToRefreshConfig.copy(isRefreshing = false), + ) } else { cacheState } @@ -366,19 +375,27 @@ internal class WalletViewModel @Inject constructor( tokensListState is WalletTokensListState.Loading || hasLoadingTokens } is WalletSingleCurrencyState -> { - txHistoryState is TxHistoryState.Loading || marketPriceBlockState is MarketPriceBlockState.Loading + this is WalletSingleCurrencyState.Content && marketPriceBlockState is MarketPriceBlockState.Loading || + txHistoryState is TxHistoryState.Loading } is WalletState.Initial -> false } } override fun onRefreshSwipe() { - uiState = stateFactory.getStateAfterContentRefreshing() + if (uiState is WalletState.Initial || uiState is WalletLockedState) return - updateContentItems( - index = requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex, - isRefreshing = true, - ) + viewModelScope.launch(dispatchers.io) { + uiState = stateFactory.getStateAfterContentRefreshing() + + // TODO: [REDACTED_JIRA] + delay(timeMillis = 500) + + updateContentItems( + index = requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex, + isRefreshing = true, + ) + } } override fun onOrganizeTokensClick() { @@ -397,6 +414,7 @@ internal class WalletViewModel @Inject constructor( uiState = stateFactory.getStateAfterContentRefreshing() updateSingleCurrencyContent( index = requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex, + isRefreshing = true, ) }