diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6e9c2b9786..de202bea18 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { implementation(project(":domain:wallets:models")) implementation(projects.domain.settings) implementation(projects.domain.tokens) + implementation(projects.domain.txhistory) implementation(project(":common")) implementation(project(":core:analytics")) @@ -43,11 +44,13 @@ dependencies { implementation(project(":core:utils")) implementation(project(":libs:crypto")) implementation(project(":libs:auth")) + implementation(project(":data:source:preferences")) implementation(projects.data.card) + implementation(projects.data.common) implementation(projects.data.settings) implementation(projects.data.tokens) - implementation(projects.data.common) + implementation(projects.data.txhistory) /** Features */ implementation(project(":features:onboarding")) diff --git a/app/src/main/java/com/tangem/tap/di/domain/TxHistoryDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TxHistoryDomainModule.kt new file mode 100644 index 0000000000..3ab36cd231 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/domain/TxHistoryDomainModule.kt @@ -0,0 +1,28 @@ +package com.tangem.tap.di.domain + +import com.tangem.domain.tokens.* +import com.tangem.domain.txhistory.repository.TxHistoryRepository +import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase +import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped + +@Module +@InstallIn(ViewModelComponent::class) +internal object TxHistoryDomainModule { + + @Provides + @ViewModelScoped + fun provideGetTxHistoryItemsCountUseCase(txHistoryRepository: TxHistoryRepository): GetTxHistoryItemsCountUseCase { + return GetTxHistoryItemsCountUseCase(repository = txHistoryRepository) + } + + @Provides + @ViewModelScoped + fun provideGetTxHistoryItemsUseCase(txHistoryRepository: TxHistoryRepository): GetTxHistoryItemsUseCase { + return GetTxHistoryItemsUseCase(repository = txHistoryRepository) + } +} \ No newline at end of file diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsCountUseCase.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsCountUseCase.kt index c6c0b347b3..1ecdc72dd6 100644 --- a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsCountUseCase.kt +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsCountUseCase.kt @@ -12,12 +12,12 @@ class GetTxHistoryItemsCountUseCase(private val repository: TxHistoryRepository) return either { catch( block = { repository.getTxHistoryItemsCount(networkId, derivationPath) }, - catch = { e -> + catch = { throwable -> raise( - when (e) { - is TxHistoryStateError.TxHistoryNotImplemented -> e - is TxHistoryStateError.EmptyTxHistories -> e - else -> TxHistoryStateError.DataError(e) + when (throwable) { + is TxHistoryStateError.TxHistoryNotImplemented -> throwable + is TxHistoryStateError.EmptyTxHistories -> throwable + else -> TxHistoryStateError.DataError(throwable) }, ) }, diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsUseCase.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsUseCase.kt index 48d2759704..9f3fc2fbe0 100644 --- a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsUseCase.kt +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsUseCase.kt @@ -2,17 +2,12 @@ package com.tangem.domain.txhistory.usecase import androidx.paging.PagingData import arrow.core.Either -import arrow.core.left -import arrow.core.raise.Raise -import arrow.core.raise.recover -import arrow.core.right +import arrow.core.raise.either import com.tangem.domain.txhistory.error.TxHistoryListError import com.tangem.domain.txhistory.model.TxHistoryItem import com.tangem.domain.txhistory.repository.TxHistoryRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.collect private const val DEFAULT_PAGE_SIZE = 20 @@ -21,29 +16,11 @@ class GetTxHistoryItemsUseCase(private val repository: TxHistoryRepository) { operator fun invoke( networkId: String, pageSize: Int = DEFAULT_PAGE_SIZE, - ): Flow,>,> { - return channelFlow { - recover( - block = { - getTxHistoryItems( - networkId = networkId, - pageSize = pageSize, - ).collect { items -> - send(items.right()) - } - }, - recover = { error -> send(error.left()) }, - ) + ): Either>> { + return either { + repository + .getTxHistoryItems(networkId = networkId, pageSize = pageSize) + .catch { raise(TxHistoryListError.DataError(it)) } } } - - private fun Raise.getTxHistoryItems( - networkId: String, - pageSize: Int, - ): Flow> { - return repository - .getTxHistoryItems(networkId = networkId, pageSize = pageSize) - .catch { raise(TxHistoryListError.DataError(it)) } - } } \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 60ca50d490..25b400217f 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -12,18 +12,19 @@ dependencies { implementation(deps.material) /** Compose */ + implementation(deps.compose.accompanist.systemUiController) implementation(deps.compose.coil) implementation(deps.compose.constraintLayout) - implementation(deps.compose.material) implementation(deps.compose.foundation) + implementation(deps.compose.material) implementation(deps.compose.material3) implementation(deps.compose.navigation) implementation(deps.compose.navigation.hilt) + implementation(deps.compose.paging) + implementation(deps.compose.reorderable) + implementation(deps.compose.shimmer) implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) - implementation(deps.compose.shimmer) - implementation(deps.compose.accompanist.systemUiController) - implementation(deps.compose.reorderable) /** Other libraries */ implementation(deps.arrow.core) @@ -50,6 +51,7 @@ dependencies { implementation(projects.domain.models) implementation(projects.domain.settings) implementation(projects.domain.tokens) + implementation(projects.domain.txhistory) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) 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 5dfb53248c..722d10e9cb 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 @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.common +import androidx.paging.PagingData import com.tangem.core.ui.R import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.marketprice.PriceChangeConfig @@ -11,8 +12,11 @@ import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder import com.tangem.feature.wallet.presentation.wallet.state.* +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.flow.flowOf import java.util.UUID internal object WalletPreviewData { @@ -221,54 +225,57 @@ internal object WalletPreviewData { onBackClick = {}, topBarConfig = walletTopBarConfig, walletsListConfig = walletListConfig, - contentItems = persistentListOf( - WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle("Bitcoin"), - WalletContentItemState.MultiCurrencyItem.Token( - tokenItemVisibleState.copy( - id = "token_1", - name = "Ethereum", - tokenIconResId = R.drawable.img_eth_22, - networkIconResId = null, - amount = "1,89340821 ETH", - ), - ), - WalletContentItemState.MultiCurrencyItem.Token( - tokenItemVisibleState.copy( - id = "token_2", - name = "Ethereum", - tokenIconResId = R.drawable.img_eth_22, - networkIconResId = null, - amount = "1,89340821 ETH", - ), - ), - WalletContentItemState.MultiCurrencyItem.Token( - tokenItemVisibleState.copy( - id = "token_3", - name = "Ethereum", - tokenIconResId = R.drawable.img_eth_22, - networkIconResId = null, - amount = "1,89340821 ETH", - ), - ), - WalletContentItemState.MultiCurrencyItem.Token( - tokenItemVisibleState.copy( - id = "token_4", - name = "Ethereum", - tokenIconResId = R.drawable.img_eth_22, - networkIconResId = null, - amount = "1,89340821 ETH", - ), - ), - WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle("Ethereum"), - WalletContentItemState.MultiCurrencyItem.Token( - tokenItemVisibleState.copy( - id = "token_5", - name = "Ethereum", - tokenIconResId = R.drawable.img_eth_22, - networkIconResId = null, - amount = "1,89340821 ETH", + tokensListState = WalletTokensListState.Content( + persistentListOf( + WalletTokensListState.TokensListItemState.NetworkGroupTitle("Bitcoin"), + WalletTokensListState.TokensListItemState.Token( + tokenItemVisibleState.copy( + id = "token_1", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + ), + WalletTokensListState.TokensListItemState.Token( + tokenItemVisibleState.copy( + id = "token_2", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + ), + WalletTokensListState.TokensListItemState.Token( + tokenItemVisibleState.copy( + id = "token_3", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + ), + WalletTokensListState.TokensListItemState.Token( + tokenItemVisibleState.copy( + id = "token_4", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + ), + WalletTokensListState.TokensListItemState.NetworkGroupTitle("Ethereum"), + WalletTokensListState.TokensListItemState.Token( + tokenItemVisibleState.copy( + id = "token_5", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), ), ), + onOrganizeTokensClick = {}, ), pullToRefreshConfig = WalletPullToRefreshConfig( isRefreshing = false, @@ -281,32 +288,12 @@ internal object WalletPreviewData { WalletNotification.ScanCard(onClick = {}), ), bottomSheet = bottomSheet, - onOrganizeTokensClick = {}, ) val singleWalletScreenState = WalletStateHolder.SingleCurrencyContent( onBackClick = {}, topBarConfig = walletTopBarConfig, walletsListConfig = walletListConfig, - contentItems = persistentListOf( - WalletContentItemState.SingleCurrencyItem.Title(onExploreClick = {}), - WalletContentItemState.SingleCurrencyItem.GroupTitle("Today"), - WalletContentItemState.SingleCurrencyItem.Transaction( - TransactionState.Sending( - address = "33BddS...ga2B", - amount = "-0.500913 BTC", - timestamp = "8:41", - ), - ), - WalletContentItemState.SingleCurrencyItem.GroupTitle("Yesterday"), - WalletContentItemState.SingleCurrencyItem.Transaction( - TransactionState.Sending( - address = "33BddS...ga2B", - amount = "-0.500913 BTC", - timestamp = "8:41", - ), - ), - ), pullToRefreshConfig = WalletPullToRefreshConfig( isRefreshing = false, onRefresh = {}, @@ -322,5 +309,30 @@ internal object WalletPreviewData { type = PriceChangeConfig.Type.UP, ), ), + txHistoryState = WalletTxHistoryState.Content( + flowOf( + PagingData.from( + listOf( + WalletTxHistoryState.TxHistoryItemState.Title(onExploreClick = {}), + WalletTxHistoryState.TxHistoryItemState.GroupTitle("Today"), + WalletTxHistoryState.TxHistoryItemState.Transaction( + TransactionState.Sending( + address = "33BddS...ga2B", + amount = "-0.500913 BTC", + timestamp = "8:41", + ), + ), + WalletTxHistoryState.TxHistoryItemState.GroupTitle("Yesterday"), + WalletTxHistoryState.TxHistoryItemState.Transaction( + TransactionState.Sending( + address = "33BddS...ga2B", + amount = "-0.500913 BTC", + timestamp = "8:41", + ), + ), + ), + ), + ), + ), ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletContentItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletContentItemState.kt deleted file mode 100644 index 19142be1cd..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletContentItemState.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state - -import com.tangem.core.ui.components.transactions.TransactionState -import com.tangem.feature.wallet.presentation.common.state.TokenItemState - -/** - * Wallet screen content item state - * -[REDACTED_AUTHOR] - */ -internal sealed interface WalletContentItemState { - - /** Multi currency wallet content state */ - sealed interface MultiCurrencyItem : WalletContentItemState { - - /** - * Network group title item - * - * @property networkName network name - */ - data class NetworkGroupTitle(val networkName: String) : MultiCurrencyItem - - /** - * Token item - * - * @property state token item state - */ - data class Token(val state: TokenItemState) : MultiCurrencyItem - } - - /** Single currency wallet content state */ - sealed interface SingleCurrencyItem : WalletContentItemState { - - /** - * Title item - * - * @property onExploreClick lambda be invoke when explore button was clicked - */ - data class Title(val onExploreClick: () -> Unit) : SingleCurrencyItem - - /** - * Group title item - * - * @property title title - */ - data class GroupTitle(val title: String) : SingleCurrencyItem - - /** - * Transaction item - * - * @property state transaction state - */ - data class Transaction(val state: TransactionState) : SingleCurrencyItem - } - - object Loading : WalletContentItemState -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt index 169139dacd..216dc94148 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt @@ -3,6 +3,9 @@ package com.tangem.feature.wallet.presentation.wallet.state import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletLockedContentState +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -13,7 +16,6 @@ import kotlinx.collections.immutable.persistentListOf * @property topBarConfig top bar config * @property walletsListConfig wallets list config * @property pullToRefreshConfig pull to refresh config - * @property contentItems content items * @property notifications notifications * [REDACTED_AUTHOR] @@ -23,7 +25,6 @@ internal sealed class WalletStateHolder( open val topBarConfig: WalletTopBarConfig, open val walletsListConfig: WalletsListConfig, open val pullToRefreshConfig: WalletPullToRefreshConfig, - open val contentItems: ImmutableList, open val notifications: ImmutableList, open val bottomSheet: WalletBottomSheetConfig? = null, ) { @@ -33,7 +34,6 @@ internal sealed class WalletStateHolder( topBarConfig: WalletTopBarConfig = this.topBarConfig, walletsListConfig: WalletsListConfig = this.walletsListConfig, pullToRefreshConfig: WalletPullToRefreshConfig = this.pullToRefreshConfig, - contentItems: ImmutableList = this.contentItems, notifications: ImmutableList = this.notifications, bottomSheet: WalletBottomSheetConfig? = this.bottomSheet, ): WalletStateHolder { @@ -43,7 +43,6 @@ internal sealed class WalletStateHolder( topBarConfig = topBarConfig, walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig, - contentItems = contentItems as ImmutableList, notifications = notifications, bottomSheet = bottomSheet, ) @@ -52,7 +51,6 @@ internal sealed class WalletStateHolder( topBarConfig = topBarConfig, walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig, - contentItems = contentItems as ImmutableList, notifications = notifications, bottomSheet = bottomSheet, ) @@ -73,27 +71,24 @@ internal sealed class WalletStateHolder( * @property topBarConfig top bar config * @property walletsListConfig wallets list config * @property pullToRefreshConfig pull to refresh config - * @property contentItems content items + * @property tokensListState token list state * @property notifications notifications - * @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked */ data class MultiCurrencyContent( override val onBackClick: () -> Unit, override val topBarConfig: WalletTopBarConfig, override val walletsListConfig: WalletsListConfig, override val pullToRefreshConfig: WalletPullToRefreshConfig, - override val contentItems: ImmutableList, override val notifications: ImmutableList, override val bottomSheet: WalletBottomSheetConfig? = null, - val onOrganizeTokensClick: () -> Unit, + val tokensListState: WalletTokensListState, ) : WalletStateHolder( onBackClick = onBackClick, topBarConfig = topBarConfig, walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig, - contentItems = contentItems, - bottomSheet = bottomSheet, notifications = notifications, + bottomSheet = bottomSheet, ) /** @@ -103,27 +98,26 @@ internal sealed class WalletStateHolder( * @property topBarConfig top bar config * @property walletsListConfig wallets list config * @property pullToRefreshConfig pull to refresh config - * @property contentItems content items * @property notifications notifications * @property buttons manage buttons * @property marketPriceBlockState market price block state + * @property txHistoryState transactions history state */ data class SingleCurrencyContent( override val onBackClick: () -> Unit, override val topBarConfig: WalletTopBarConfig, override val walletsListConfig: WalletsListConfig, override val pullToRefreshConfig: WalletPullToRefreshConfig, - override val contentItems: ImmutableList, override val notifications: ImmutableList, override val bottomSheet: WalletBottomSheetConfig? = null, val buttons: ImmutableList, val marketPriceBlockState: MarketPriceBlockState, + val txHistoryState: WalletTxHistoryState, ) : WalletStateHolder( onBackClick = onBackClick, topBarConfig = topBarConfig, walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig, - contentItems = contentItems, notifications = notifications, bottomSheet = bottomSheet, ) @@ -134,6 +128,8 @@ internal sealed class WalletStateHolder( * @property onBackClick lambda be invoked when back button is clicked * @property topBarConfig top bar config * @property walletsListConfig wallets list config + * @property pullToRefreshConfig pull to refresh config + * @property lockedContentState locked content state * @property onUnlockWalletsNotificationClick lambda be invoked when unlock wallets notification is clicked * @property onBottomSheetDismissRequest lambda be invoked when bottom sheet is dismissed * @property onUnlockClick lambda be invoked when unlock button is clicked @@ -144,6 +140,7 @@ internal sealed class WalletStateHolder( override val topBarConfig: WalletTopBarConfig, override val walletsListConfig: WalletsListConfig, override val pullToRefreshConfig: WalletPullToRefreshConfig, + val lockedContentState: WalletLockedContentState, val onUnlockWalletsNotificationClick: () -> Unit, val onBottomSheetDismissRequest: () -> Unit, val onUnlockClick: () -> Unit, @@ -153,7 +150,6 @@ internal sealed class WalletStateHolder( topBarConfig = topBarConfig, walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig, - contentItems = persistentListOf(WalletContentItemState.Loading), notifications = persistentListOf(WalletNotification.UnlockWallets(onUnlockWalletsNotificationClick)), bottomSheet = WalletBottomSheetConfig( isShow = false, @@ -186,7 +182,6 @@ internal sealed class WalletStateHolder( onWalletChange = {}, ), pullToRefreshConfig = WalletPullToRefreshConfig(isRefreshing = false, onRefresh = {}), - contentItems = persistentListOf(WalletContentItemState.Loading), notifications = persistentListOf(), bottomSheet = null, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/content/WalletLockedContentState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/content/WalletLockedContentState.kt new file mode 100644 index 0000000000..9867aed606 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/content/WalletLockedContentState.kt @@ -0,0 +1,9 @@ +package com.tangem.feature.wallet.presentation.wallet.state.content + +/** + * Wallet locked content state. + * It allows to divide the locked content of multi-currency and single-currency wallets. + * +[REDACTED_AUTHOR] + */ +internal sealed interface WalletLockedContentState \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/content/WalletTokensListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/content/WalletTokensListState.kt new file mode 100644 index 0000000000..4f7e51d4d3 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/content/WalletTokensListState.kt @@ -0,0 +1,60 @@ +package com.tangem.feature.wallet.presentation.wallet.state.content + +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +/** + * Wallet tokens list state + * + * @property items content items + * @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked + * +[REDACTED_AUTHOR] + */ +// TODO: Finalize strings [REDACTED_JIRA] +internal sealed class WalletTokensListState( + open val items: ImmutableList, + open val onOrganizeTokensClick: (() -> Unit)?, +) { + + /** + * Content state + * + * @property items content items + * @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked + */ + data class Content( + override val items: ImmutableList, + override val onOrganizeTokensClick: (() -> Unit)?, + ) : WalletTokensListState(items, onOrganizeTokensClick) + + /** Locked content state */ + object Locked : + WalletTokensListState( + items = persistentListOf( + TokensListItemState.NetworkGroupTitle(networkName = "Tokens"), + TokensListItemState.Token(state = TokenItemState.Loading), + ), + onOrganizeTokensClick = null, + ), + WalletLockedContentState + + /** Tokens list item state */ + sealed interface TokensListItemState { + + /** + * Network group title item + * + * @property networkName network name + */ + data class NetworkGroupTitle(val networkName: String) : TokensListItemState + + /** + * Token item + * + * @property state token item state + */ + data class Token(val state: TokenItemState) : TokensListItemState + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/content/WalletTxHistoryState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/content/WalletTxHistoryState.kt new file mode 100644 index 0000000000..f9b3c822ee --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/content/WalletTxHistoryState.kt @@ -0,0 +1,92 @@ +package com.tangem.feature.wallet.presentation.wallet.state.content + +import androidx.paging.PagingData +import com.tangem.core.ui.components.transactions.TransactionState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** + * Wallet transaction history state + * +[REDACTED_AUTHOR] + */ +internal sealed interface WalletTxHistoryState { + + /** + * Wallet transaction history state with content + * + * @property items content items + */ + sealed class ContentItemsState(open val items: Flow>) : WalletTxHistoryState + + /** + * Content state + * + * @property items content items + */ + data class Content(override val items: Flow>) : ContentItemsState(items) + + /** + * Empty state + * + * @property onBuyClick lambda be invoke when buy button was clicked + */ + data class Empty(val onBuyClick: () -> Unit) : WalletTxHistoryState + + /** + * Not supported tx history state + * + * @property onExploreClick lambda be invoke when explore button was clicked + */ + data class NotSupported(val onExploreClick: () -> Unit) : WalletTxHistoryState + + /** + * Error state + * + * @property onReloadClick lambda be invoke when reload button was clicked + */ + data class Error(val onReloadClick: () -> Unit) : WalletTxHistoryState + + /** + * Locked state + * + * @property onExploreClick lambda be invoke when explore button was clicked + */ + data class Locked(val onExploreClick: () -> Unit) : + ContentItemsState( + items = flowOf( + PagingData.from( + listOf( + TxHistoryItemState.Title(onExploreClick = onExploreClick), + TxHistoryItemState.Transaction(state = TransactionState.Loading), + ), + ), + ), + ), + WalletLockedContentState + + /** Transactions history item state */ + sealed interface TxHistoryItemState { + + /** + * Title item + * + * @property onExploreClick lambda be invoke when explore button was clicked + */ + data class Title(val onExploreClick: () -> Unit) : TxHistoryItemState + + /** + * Group title item + * + * @property title title + */ + data class GroupTitle(val title: String) : TxHistoryItemState + + /** + * Transaction item + * + * @property state transaction state + */ + data class Transaction(val state: TransactionState) : TxHistoryItemState + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt index 138f36b6fd..edcbbdd1e5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt @@ -8,6 +8,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletLoadedTokensListConverter.LoadedTokensListModel import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorToWalletStateConverter import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter +import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickCallbacks import com.tangem.utils.converter.Converter /** @@ -19,17 +20,19 @@ import com.tangem.utils.converter.Converter */ internal class WalletLoadedTokensListConverter( private val currentStateProvider: Provider, + clickCallbacks: WalletClickCallbacks, ) : Converter { private val tokenListStateConverter = TokenListToWalletStateConverter( - currentState = currentStateProvider(), + currentStateProvider = currentStateProvider, isWalletContentHidden = false, // TODO: [REDACTED_JIRA] fiatCurrencyCode = "USD", // TODO: [REDACTED_JIRA] fiatCurrencySymbol = "$", // TODO: [REDACTED_JIRA] + clickCallbacks = clickCallbacks, ) private val tokenListErrorStateConverter = TokenListErrorToWalletStateConverter( - currentState = currentStateProvider(), + currentStateProvider = currentStateProvider, ) override fun convert(value: LoadedTokensListModel): WalletStateHolder { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTxHistoryConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTxHistoryConverter.kt new file mode 100644 index 0000000000..1d8fa010d8 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTxHistoryConverter.kt @@ -0,0 +1,130 @@ +package com.tangem.feature.wallet.presentation.wallet.state.factory + +import androidx.paging.PagingData +import androidx.paging.map +import arrow.core.Either +import com.tangem.common.Provider +import com.tangem.core.ui.components.marketprice.MarketPriceBlockState +import com.tangem.core.ui.components.transactions.TransactionState +import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.txhistory.error.TxHistoryListError +import com.tangem.domain.txhistory.model.TxHistoryItem +import com.tangem.feature.wallet.presentation.wallet.state.WalletManageButton +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState.TxHistoryItemState +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** + * Converter from loaded tx history to [WalletTxHistoryState] + * + * @property currentStateProvider current state provider + * @property currentCardTypeResolverProvider current card type resolver provider + * +[REDACTED_AUTHOR] + */ +internal class WalletLoadedTxHistoryConverter( + private val currentStateProvider: Provider, + private val currentCardTypeResolverProvider: Provider, +) : Converter>>, WalletStateHolder> { + + override fun convert(value: Either>>): WalletStateHolder { + return value.fold(ifLeft = { convertError() }, ifRight = ::convert) + } + + private fun convertError(): WalletStateHolder { + val state = currentStateProvider() + return WalletStateHolder.SingleCurrencyContent( + onBackClick = state.onBackClick, + topBarConfig = state.topBarConfig, + walletsListConfig = state.walletsListConfig, + pullToRefreshConfig = state.pullToRefreshConfig, + notifications = state.notifications, + bottomSheet = state.bottomSheet, + buttons = persistentListOf( + WalletManageButton.Buy(onClick = {}), + WalletManageButton.Send(onClick = {}), + WalletManageButton.Receive(onClick = {}), + WalletManageButton.Exchange(onClick = {}), + WalletManageButton.CopyAddress(onClick = {}), + ) + .map(WalletManageButton::config) + .toImmutableList(), + marketPriceBlockState = MarketPriceBlockState.Loading( + currencyName = currentCardTypeResolverProvider().getBlockchain().currency, + ), + // TODO: [REDACTED_JIRA] + txHistoryState = WalletTxHistoryState.Error(onReloadClick = {}), + ) + } + + private fun convert(items: Flow>): WalletStateHolder { + val state = currentStateProvider() + return WalletStateHolder.SingleCurrencyContent( + onBackClick = state.onBackClick, + topBarConfig = state.topBarConfig, + walletsListConfig = state.walletsListConfig, + pullToRefreshConfig = state.pullToRefreshConfig, + notifications = state.notifications, + bottomSheet = state.bottomSheet, + buttons = persistentListOf( + WalletManageButton.Buy(onClick = {}), + WalletManageButton.Send(onClick = {}), + WalletManageButton.Receive(onClick = {}), + WalletManageButton.Exchange(onClick = {}), + WalletManageButton.CopyAddress(onClick = {}), + ) + .map(WalletManageButton::config) + .toImmutableList(), + marketPriceBlockState = MarketPriceBlockState.Loading( + currencyName = currentCardTypeResolverProvider().getBlockchain().currency, + ), + txHistoryState = createContentTxHistory(items), + ) + } + + private fun createContentTxHistory(items: Flow>): WalletTxHistoryState { + return WalletTxHistoryState.Content( + items = items.map { pagingData -> + pagingData.map { item -> + TxHistoryItemState.Transaction( + state = when (val direction = item.direction) { + is TxHistoryItem.TransactionDirection.Incoming -> { + when (item.status) { + TxHistoryItem.TxStatus.Confirmed -> TransactionState.Receive( + address = direction.from, + amount = item.amount.toPlainString(), + timestamp = item.timestamp.toString(), + ) + TxHistoryItem.TxStatus.Unconfirmed -> TransactionState.Receiving( + address = direction.from, + amount = item.amount.toPlainString(), + timestamp = item.timestamp.toString(), + ) + } + } + is TxHistoryItem.TransactionDirection.Outgoing -> { + when (item.status) { + TxHistoryItem.TxStatus.Confirmed -> TransactionState.Send( + address = direction.to, + amount = item.amount.toPlainString(), + timestamp = item.timestamp.toString(), + ) + TxHistoryItem.TxStatus.Unconfirmed -> TransactionState.Sending( + address = direction.to, + amount = item.amount.toPlainString(), + timestamp = item.timestamp.toString(), + ) + } + } + }, + ) + } + }, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadingTxHistoryConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadingTxHistoryConverter.kt new file mode 100644 index 0000000000..b24b26dd5a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadingTxHistoryConverter.kt @@ -0,0 +1,103 @@ +package com.tangem.feature.wallet.presentation.wallet.state.factory + +import androidx.paging.PagingData +import arrow.core.Either +import com.tangem.common.Provider +import com.tangem.core.ui.components.marketprice.MarketPriceBlockState +import com.tangem.core.ui.components.transactions.TransactionState +import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.txhistory.error.TxHistoryStateError +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.wallet.state.WalletManageButton +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.flowOf + +/** + * Converter from loading tx history to [WalletTxHistoryState] + * + * @property currentStateProvider current state provider + * @property currentCardTypeResolverProvider current card type resolver provider + * +[REDACTED_AUTHOR] + */ +internal class WalletLoadingTxHistoryConverter( + private val currentStateProvider: Provider, + private val currentCardTypeResolverProvider: Provider, +) : Converter, WalletStateHolder> { + + override fun convert(value: Either): WalletStateHolder { + return value.fold(ifLeft = ::convertError, ifRight = ::convert) + } + + private fun convert(value: Int): WalletStateHolder { + val state = currentStateProvider() + + return WalletStateHolder.SingleCurrencyContent( + onBackClick = state.onBackClick, + topBarConfig = state.topBarConfig, + walletsListConfig = state.walletsListConfig, + pullToRefreshConfig = state.pullToRefreshConfig, + notifications = state.notifications, + bottomSheet = state.bottomSheet, + buttons = persistentListOf( + WalletManageButton.Buy(onClick = {}), + WalletManageButton.Send(onClick = {}), + WalletManageButton.Receive(onClick = {}), + WalletManageButton.Exchange(onClick = {}), + WalletManageButton.CopyAddress(onClick = {}), + ) + .map(WalletManageButton::config) + .toImmutableList(), + marketPriceBlockState = MarketPriceBlockState.Loading( + currencyName = currentCardTypeResolverProvider().getBlockchain().currency, + ), + txHistoryState = createLoadingTxHistory(itemCount = value), + ) + } + + private fun createLoadingTxHistory(itemCount: Int): WalletTxHistoryState { + return WalletTxHistoryState.Content( + items = flowOf( + value = PagingData.from( + data = buildList( + capacity = itemCount, + builderAction = { + add( + element = WalletTxHistoryState.TxHistoryItemState.Transaction( + state = TransactionState.Loading, + ), + ) + }, + ), + ), + ), + ) + } + + private fun convertError(error: TxHistoryStateError): WalletStateHolder { + val state = currentStateProvider() + + return WalletStateHolder.SingleCurrencyContent( + onBackClick = state.onBackClick, + topBarConfig = state.topBarConfig, + walletsListConfig = state.walletsListConfig, + pullToRefreshConfig = state.pullToRefreshConfig, + notifications = state.notifications, + bottomSheet = state.bottomSheet, + buttons = WalletPreviewData.singleWalletScreenState.buttons, + marketPriceBlockState = MarketPriceBlockState.Loading( + currencyName = currentCardTypeResolverProvider().getBlockchain().currency, + ), + // TODO: [REDACTED_JIRA] + txHistoryState = when (error) { + is TxHistoryStateError.EmptyTxHistories -> WalletTxHistoryState.Empty(onBuyClick = {}) + is TxHistoryStateError.DataError -> WalletTxHistoryState.Error(onReloadClick = {}) + is TxHistoryStateError.TxHistoryNotImplemented -> WalletTxHistoryState.NotSupported(onExploreClick = {}) + }, + ) + } +} \ 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 e372cb1500..dfdf7abdb3 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 @@ -1,17 +1,20 @@ package com.tangem.feature.wallet.presentation.wallet.state.factory +import androidx.paging.PagingData import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.state.* +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickCallbacks import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.flow /** * Converter from loaded list of [UserWallet] to skeleton state of screen [WalletStateHolder] @@ -40,10 +43,12 @@ internal class WalletSkeletonStateConverter( topBarConfig = createTopBarConfig(), walletsListConfig = createWalletsListConfig(wallets), pullToRefreshConfig = createPullToRefreshConfig(), - contentItems = persistentListOf(), + tokensListState = WalletTokensListState.Content( + items = persistentListOf(), + onOrganizeTokensClick = clickCallbacks::onOrganizeTokensClick, + ), notifications = persistentListOf(), bottomSheet = null, - onOrganizeTokensClick = clickCallbacks::onOrganizeTokensClick, ) } @@ -56,13 +61,15 @@ internal class WalletSkeletonStateConverter( topBarConfig = createTopBarConfig(), walletsListConfig = createWalletsListConfig(wallets), pullToRefreshConfig = createPullToRefreshConfig(), - contentItems = persistentListOf(), notifications = persistentListOf(), bottomSheet = null, buttons = WalletPreviewData.singleWalletScreenState.buttons, // TODO: create buttons marketPriceBlockState = MarketPriceBlockState.Loading( currencyName = cardTypeResolver.getBlockchain().currency, ), + txHistoryState = WalletTxHistoryState.Content( + items = flow { PagingData.empty() }, + ), ) } @@ -78,12 +85,7 @@ internal class WalletSkeletonStateConverter( selectedWalletIndex = 0, wallets = wallets.map { wallet -> WalletCardState.Loading( - // TODO remove after adding release logic for GetTokenListUseCase - id = if (wallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) { - UserWalletId("123") - } else { - UserWalletId("321") - }, + id = wallet.walletId, title = wallet.name, additionalInfo = "", // TODO add additional info resolver imageResId = WalletImageResolver.resolve(cardTypesResolver = wallet.scanResponse.cardTypesResolver), 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 3aec8d0359..e4e0952491 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 @@ -1,9 +1,14 @@ package com.tangem.feature.wallet.presentation.wallet.state.factory +import androidx.paging.PagingData import arrow.core.Either import com.tangem.common.Provider +import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.txhistory.error.TxHistoryListError +import com.tangem.domain.txhistory.error.TxHistoryStateError +import com.tangem.domain.txhistory.model.TxHistoryItem import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.state.WalletBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.WalletNotification @@ -11,6 +16,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletLoadedTokensListConverter.LoadedTokensListModel import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickCallbacks import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.flow.Flow /** * Main factory for creating [WalletStateHolder] @@ -20,11 +26,32 @@ import kotlinx.collections.immutable.ImmutableList */ internal class WalletStateFactory( private val currentStateProvider: Provider, + currentCardTypeResolverProvider: Provider, private val clickCallbacks: WalletClickCallbacks, ) { - private val skeletonConverter = WalletSkeletonStateConverter(clickCallbacks = clickCallbacks) - private val loadedTokensListConverter = WalletLoadedTokensListConverter(currentStateProvider = currentStateProvider) + private val skeletonConverter by lazy { WalletSkeletonStateConverter(clickCallbacks = clickCallbacks) } + + private val loadedTokensListConverter by lazy { + WalletLoadedTokensListConverter( + currentStateProvider = currentStateProvider, + clickCallbacks = clickCallbacks, + ) + } + + private val loadingTransactionsStateConverter by lazy { + WalletLoadingTxHistoryConverter( + currentStateProvider = currentStateProvider, + currentCardTypeResolverProvider = currentCardTypeResolverProvider, + ) + } + + private val loadedTxHistoryConverter by lazy { + WalletLoadedTxHistoryConverter( + currentStateProvider = currentStateProvider, + currentCardTypeResolverProvider = currentCardTypeResolverProvider, + ) + } fun getInitialState(): WalletStateHolder = WalletStateHolder.Loading(onBackClick = clickCallbacks::onBackClick) @@ -66,4 +93,14 @@ internal class WalletStateFactory( ) } } + + fun getLoadingTxHistoryState(itemsCountEither: Either): WalletStateHolder { + return loadingTransactionsStateConverter.convert(value = itemsCountEither) + } + + fun getLoadedTxHistoryState( + txHistoryEither: Either>>, + ): WalletStateHolder { + return loadedTxHistoryConverter.convert(txHistoryEither) + } } \ 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 c777e06a49..1ba7a134d6 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 @@ -4,10 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.compose.animation.* import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.* import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.PullRefreshState @@ -17,29 +14,26 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemsIndexed import com.tangem.core.ui.components.buttons.HorizontalActionChips -import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig -import com.tangem.core.ui.components.buttons.actions.RoundedActionButton import com.tangem.core.ui.components.marketprice.MarketPriceBlock import com.tangem.core.ui.components.notifications.Notification -import com.tangem.core.ui.components.transactions.Transaction import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.common.component.NetworkGroupItem -import com.tangem.feature.wallet.presentation.common.component.TokenItem -import com.tangem.feature.wallet.presentation.common.state.TokenItemState -import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletBottomSheet import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletTopBar import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList -import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TransactionsBlockGroupTitle -import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TransactionsBlockTitle +import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.MultiCurrencyContentItem +import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.OrganizeTokensButton +import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.SingleCurrencyContentItem import com.tangem.feature.wallet.presentation.wallet.ui.decorations.walletContentItemDecoration import com.tangem.feature.wallet.presentation.wallet.ui.utils.ScrollOffsetCollector import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator @@ -74,6 +68,16 @@ internal fun WalletScreen(state: WalletStateHolder) { .padding(paddingValues = scaffoldPaddings) .pullRefresh(pullRefreshState), ) { + val txHistoryItems = if (state is WalletStateHolder.SingleCurrencyContent) { + if (state.txHistoryState is WalletTxHistoryState.ContentItemsState) { + state.txHistoryState.items.collectAsLazyPagingItems() + } else { + null + } + } else { + null + } + LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(vertical = TangemTheme.dimens.spacing8), @@ -90,8 +94,7 @@ internal fun WalletScreen(state: WalletStateHolder) { item { HorizontalActionChips( buttons = state.buttons, - modifier = changeableItemModifier - .padding(top = TangemTheme.dimens.spacing14), + modifier = changeableItemModifier.padding(top = TangemTheme.dimens.spacing14), contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), ) } @@ -120,33 +123,12 @@ internal fun WalletScreen(state: WalletStateHolder) { } } - itemsIndexed( - items = state.contentItems, - key = { index, item -> - when (item) { - is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> item.networkName - is WalletContentItemState.MultiCurrencyItem.Token -> index - is WalletContentItemState.SingleCurrencyItem.Title -> index - is WalletContentItemState.SingleCurrencyItem.GroupTitle -> item.title - is WalletContentItemState.SingleCurrencyItem.Transaction -> index - is WalletContentItemState.Loading -> index - } - }, - itemContent = { index, item -> - ContentItem( - item = item, - modifier = changeableItemModifier.walletContentItemDecoration( - currentIndex = index, - lastIndex = state.contentItems.lastIndex, - ), - ) - }, - ) + contentItems(state = state, txHistoryItems = txHistoryItems, modifier = changeableItemModifier) if (state is WalletStateHolder.MultiCurrencyContent) { item { OrganizeTokensButton( - onClick = state.onOrganizeTokensClick, + onClick = state.tokensListState.onOrganizeTokensClick, modifier = changeableItemModifier .padding(top = TangemTheme.dimens.spacing14) .padding(horizontal = TangemTheme.dimens.spacing16), @@ -173,6 +155,74 @@ internal fun WalletScreen(state: WalletStateHolder) { } } +private fun LazyListScope.contentItems( + state: WalletStateHolder, + txHistoryItems: LazyPagingItems?, + modifier: Modifier = Modifier, +) { + when (state) { + is WalletStateHolder.MultiCurrencyContent -> { + tokensListItems(state = state.tokensListState, modifier = modifier) + } + is WalletStateHolder.SingleCurrencyContent -> { + txHistoryItems( + state = state.txHistoryState, + txHistoryItems = txHistoryItems, + modifier = modifier, + ) + } + is WalletStateHolder.Loading, + is WalletStateHolder.UnlockWalletContent, + -> Unit + } +} + +private fun LazyListScope.tokensListItems(state: WalletTokensListState, modifier: Modifier = Modifier) { + itemsIndexed( + items = state.items, + key = { index, _ -> index }, + itemContent = { index, item -> + MultiCurrencyContentItem( + state = item, + modifier = modifier.walletContentItemDecoration( + currentIndex = index, + lastIndex = state.items.lastIndex, + ), + ) + }, + ) +} + +private fun LazyListScope.txHistoryItems( + state: WalletTxHistoryState, + txHistoryItems: LazyPagingItems?, + modifier: Modifier = Modifier, +) { + when (state) { + is WalletTxHistoryState.ContentItemsState -> { + checkNotNull(txHistoryItems) + itemsIndexed( + items = txHistoryItems, + key = { index, _ -> index }, + itemContent = { index, item -> + if (item == null) return@itemsIndexed + + SingleCurrencyContentItem( + state = item, + modifier = modifier.walletContentItemDecoration( + currentIndex = index, + lastIndex = txHistoryItems.itemCount, + ), + ) + }, + ) + } + is WalletTxHistoryState.Empty -> TODO("[REDACTED_JIRA]") + is WalletTxHistoryState.Error -> TODO("[REDACTED_JIRA]") + is WalletTxHistoryState.NotSupported -> TODO("[REDACTED_JIRA]") + } +} + @OptIn(ExperimentalMaterialApi::class) @Composable private fun PullToRefreshIndicator(isRefreshing: Boolean, state: PullRefreshState, modifier: Modifier = Modifier) { @@ -183,42 +233,6 @@ private fun PullToRefreshIndicator(isRefreshing: Boolean, state: PullRefreshStat ) } -@Composable -private fun ContentItem(item: WalletContentItemState, modifier: Modifier = Modifier) { - when (item) { - is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> { - NetworkGroupItem(networkName = item.networkName, modifier = modifier) - } - is WalletContentItemState.MultiCurrencyItem.Token -> { - TokenItem(state = item.state, modifier = modifier) - } - is WalletContentItemState.SingleCurrencyItem.Title -> { - TransactionsBlockTitle(config = item, modifier = modifier) - } - is WalletContentItemState.SingleCurrencyItem.GroupTitle -> { - TransactionsBlockGroupTitle(config = item, modifier = modifier) - } - is WalletContentItemState.SingleCurrencyItem.Transaction -> { - Transaction(state = item.state, modifier = modifier) - } - WalletContentItemState.Loading -> { - TokenItem(state = TokenItemState.Loading, modifier = modifier) - } - } -} - -@Composable -private fun OrganizeTokensButton(onClick: () -> Unit, modifier: Modifier = Modifier) { - RoundedActionButton( - config = ActionButtonConfig( - text = stringResource(id = R.string.organize_tokens_title), - iconResId = R.drawable.ic_filter_24, - onClick = onClick, - ), - modifier = modifier, - ) -} - // region Preview @Preview @Composable diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt new file mode 100644 index 0000000000..a387c5f956 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt @@ -0,0 +1,27 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.feature.wallet.presentation.common.component.NetworkGroupItem +import com.tangem.feature.wallet.presentation.common.component.TokenItem +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState + +/** + * Multi-currency content item + * + * @param state item state + * @param modifier modifier + * +[REDACTED_AUTHOR] + */ +@Composable +internal fun MultiCurrencyContentItem(state: WalletTokensListState.TokensListItemState, modifier: Modifier = Modifier) { + when (state) { + is WalletTokensListState.TokensListItemState.NetworkGroupTitle -> { + NetworkGroupItem(networkName = state.networkName, modifier = modifier) + } + is WalletTokensListState.TokensListItemState.Token -> { + TokenItem(state = state.state, modifier = modifier) + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/OrganizeTokensButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/OrganizeTokensButton.kt new file mode 100644 index 0000000000..f7e77273fa --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/OrganizeTokensButton.kt @@ -0,0 +1,29 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig +import com.tangem.core.ui.components.buttons.actions.RoundedActionButton +import com.tangem.feature.wallet.impl.R + +/** + * Organize tokens button + * + * @param onClick callback, if null button is disabled + * @param modifier modifier + * +[REDACTED_AUTHOR] + */ +@Composable +internal fun OrganizeTokensButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier) { + RoundedActionButton( + config = ActionButtonConfig( + text = stringResource(id = R.string.organize_tokens_title), + iconResId = R.drawable.ic_filter_24, + onClick = onClick ?: {}, + enabled = onClick != null, + ), + modifier = modifier, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyContentItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyContentItem.kt new file mode 100644 index 0000000000..58489e3a3d --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyContentItem.kt @@ -0,0 +1,29 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.ui.components.transactions.Transaction +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState + +/** + * Single currency content item + * + * @param state state + * @param modifier modifier + * +[REDACTED_AUTHOR] + */ +@Composable +internal fun SingleCurrencyContentItem(state: WalletTxHistoryState.TxHistoryItemState, modifier: Modifier = Modifier) { + when (state) { + is WalletTxHistoryState.TxHistoryItemState.GroupTitle -> { + TxHistoryGroupTitle(config = state, modifier = modifier) + } + is WalletTxHistoryState.TxHistoryItemState.Title -> { + TxHistoryTitle(config = state, modifier = modifier) + } + is WalletTxHistoryState.TxHistoryItemState.Transaction -> { + Transaction(state = state.state, modifier = modifier) + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TransactionsBlockGroupTitle.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TxHistoryGroupTitle.kt similarity index 73% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TransactionsBlockGroupTitle.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TxHistoryGroupTitle.kt index eed98b7ebf..275f2fce6d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TransactionsBlockGroupTitle.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TxHistoryGroupTitle.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState.TxHistoryItemState /** * Transactions block group title @@ -18,10 +18,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemStat * @param modifier modifier */ @Composable -internal fun TransactionsBlockGroupTitle( - config: WalletContentItemState.SingleCurrencyItem.GroupTitle, - modifier: Modifier = Modifier, -) { +internal fun TxHistoryGroupTitle(config: TxHistoryItemState.GroupTitle, modifier: Modifier = Modifier) { Text( text = config.title, modifier = modifier @@ -41,7 +38,7 @@ internal fun TransactionsBlockGroupTitle( @Composable private fun Preview_TransactionsBlockGroupTitle_Light() { TangemTheme(isDark = false) { - TransactionsBlockGroupTitle(config = WalletContentItemState.SingleCurrencyItem.GroupTitle(title = "Today")) + TxHistoryGroupTitle(config = TxHistoryItemState.GroupTitle(title = "Today")) } } @@ -49,6 +46,6 @@ private fun Preview_TransactionsBlockGroupTitle_Light() { @Composable private fun Preview_TransactionsBlockGroupTitle_Dark() { TangemTheme(isDark = true) { - TransactionsBlockGroupTitle(config = WalletContentItemState.SingleCurrencyItem.GroupTitle(title = "Today")) + TxHistoryGroupTitle(config = TxHistoryItemState.GroupTitle(title = "Today")) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TransactionsBlockTitle.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TxHistoryTitle.kt similarity index 81% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TransactionsBlockTitle.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TxHistoryTitle.kt index 58e675f1fb..a71ec939db 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TransactionsBlockTitle.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TxHistoryTitle.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState.TxHistoryItemState /** * Transactions block title @@ -21,10 +21,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemStat * @param modifier modifier */ @Composable -internal fun TransactionsBlockTitle( - config: WalletContentItemState.SingleCurrencyItem.Title, - modifier: Modifier = Modifier, -) { +internal fun TxHistoryTitle(config: TxHistoryItemState.Title, modifier: Modifier = Modifier) { Row( modifier = modifier .background(TangemTheme.colors.background.primary) @@ -62,9 +59,7 @@ internal fun TransactionsBlockTitle( @Composable private fun Preview_TransactionsBlockTitle_Light() { TangemTheme(isDark = false) { - TransactionsBlockTitle( - config = WalletContentItemState.SingleCurrencyItem.Title(onExploreClick = {}), - ) + TxHistoryTitle(config = TxHistoryItemState.Title(onExploreClick = {})) } } @@ -72,8 +67,6 @@ private fun Preview_TransactionsBlockTitle_Light() { @Composable private fun Preview_TransactionsBlockTitle_Dark() { TangemTheme(isDark = true) { - TransactionsBlockTitle( - config = WalletContentItemState.SingleCurrencyItem.Title(onExploreClick = {}), - ) + TxHistoryTitle(config = TxHistoryItemState.Title(onExploreClick = {})) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt index c00f864c32..067db48037 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt @@ -1,15 +1,15 @@ package com.tangem.feature.wallet.presentation.wallet.utils import com.tangem.feature.wallet.presentation.common.state.TokenItemState -import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState.MultiCurrencyItem -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.toPersistentList +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList internal object LoadingItemsProvider { - fun getLoadingMultiCurrencyTokens(): PersistentList { - return List(size = 5) { TokenItemState.Loading } - .map { MultiCurrencyItem.Token(it) } - .toPersistentList() + fun getLoadingMultiCurrencyTokens(): ImmutableList { + return buildList(capacity = 5) { + add(WalletTokensListState.TokensListItemState.Token(state = TokenItemState.Loading)) + }.toImmutableList() } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorToWalletStateConverter.kt index d6f3112be9..b93109b6f6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorToWalletStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorToWalletStateConverter.kt @@ -1,15 +1,27 @@ package com.tangem.feature.wallet.presentation.wallet.utils +import com.tangem.common.Provider import com.tangem.domain.tokens.error.TokenListError import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf internal class TokenListErrorToWalletStateConverter( - private val currentState: WalletStateHolder, + private val currentStateProvider: Provider, ) : Converter { // TODO: [REDACTED_JIRA] override fun convert(value: TokenListError): WalletStateHolder { - return currentState + val state = currentStateProvider() + return WalletStateHolder.MultiCurrencyContent( + onBackClick = state.onBackClick, + topBarConfig = state.topBarConfig, + walletsListConfig = state.walletsListConfig, + pullToRefreshConfig = state.pullToRefreshConfig, + notifications = state.notifications, + bottomSheet = state.bottomSheet, + tokensListState = WalletTokensListState.Content(items = persistentListOf(), onOrganizeTokensClick = null), + ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt index e488aa7b7f..f4f1d3803f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt @@ -3,10 +3,11 @@ package com.tangem.feature.wallet.presentation.wallet.utils import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TokenStatus -import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState.MultiCurrencyItem +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState.TokensListItemState import com.tangem.feature.wallet.presentation.wallet.utils.LoadingItemsProvider.getLoadingMultiCurrencyTokens +import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickCallbacks import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.mutate import kotlinx.collections.immutable.persistentListOf @@ -15,36 +16,44 @@ internal class TokenListToContentItemsConverter( isWalletContentHidden: Boolean, fiatCurrencyCode: String, fiatCurrencySymbol: String, -) : Converter> { + private val clickCallbacks: WalletClickCallbacks, +) : Converter { private val tokenStatusConverter = TokenStatusToTokenItemConverter( - isWalletContentHidden, - fiatCurrencyCode, - fiatCurrencySymbol, + isWalletContentHidden = isWalletContentHidden, + fiatCurrencyCode = fiatCurrencyCode, + fiatCurrencySymbol = fiatCurrencySymbol, ) - override fun convert(value: TokenList): ImmutableList { - return when (value) { - is TokenList.GroupedByNetwork -> value.mapToMultiCurrencyItems() - is TokenList.Ungrouped -> value.mapToMultiCurrencyItems() - is TokenList.NotInitialized -> getLoadingMultiCurrencyTokens() - } + override fun convert(value: TokenList): WalletTokensListState { + return WalletTokensListState.Content( + items = when (value) { + is TokenList.GroupedByNetwork -> value.mapToMultiCurrencyItems() + is TokenList.Ungrouped -> value.mapToMultiCurrencyItems() + is TokenList.NotInitialized -> getLoadingMultiCurrencyTokens() + }, + onOrganizeTokensClick = if (value.totalFiatBalance is TokenList.FiatBalance.Loaded) { + clickCallbacks::onOrganizeTokensClick + } else { + null + }, + ) } - private fun TokenList.GroupedByNetwork.mapToMultiCurrencyItems(): PersistentList { + private fun TokenList.GroupedByNetwork.mapToMultiCurrencyItems(): PersistentList { return groups.fold(initial = persistentListOf()) { acc, group -> acc.mutate { it.addGroup(group) } } } - private fun TokenList.Ungrouped.mapToMultiCurrencyItems(): PersistentList { + private fun TokenList.Ungrouped.mapToMultiCurrencyItems(): PersistentList { return tokens.fold(initial = persistentListOf()) { acc, token -> acc.mutate { it.addToken(token) } } } - private fun MutableList.addGroup(group: NetworkGroup): List { - this.add(MultiCurrencyItem.NetworkGroupTitle(group.network.name)) + private fun MutableList.addGroup(group: NetworkGroup): List { + this.add(TokensListItemState.NetworkGroupTitle(group.network.name)) group.tokens.forEach { token -> this.addToken(token) @@ -53,10 +62,10 @@ internal class TokenListToContentItemsConverter( return this } - private fun MutableList.addToken(token: TokenStatus): List { + private fun MutableList.addToken(token: TokenStatus): List { val tokenItemState = tokenStatusConverter.convert(token) - this.add(MultiCurrencyItem.Token(tokenItemState)) + this.add(TokensListItemState.Token(tokenItemState)) return this } 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 b1374bbead..056bbb3d1d 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 @@ -1,48 +1,56 @@ package com.tangem.feature.wallet.presentation.wallet.utils -import com.tangem.core.ui.components.transactions.TransactionState +import com.tangem.common.Provider import com.tangem.domain.tokens.model.TokenList import com.tangem.feature.wallet.presentation.common.state.TokenItemState -import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder.MultiCurrencyContent import com.tangem.feature.wallet.presentation.wallet.state.WalletsListConfig +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter.TokensListModel +import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickCallbacks import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toPersistentList internal class TokenListToWalletStateConverter( - private val currentState: WalletStateHolder, + private val currentStateProvider: Provider, private val isWalletContentHidden: Boolean, private val fiatCurrencyCode: String, private val fiatCurrencySymbol: String, + clickCallbacks: WalletClickCallbacks, ) : Converter { private val tokenListToContentConverter = TokenListToContentItemsConverter( isWalletContentHidden = isWalletContentHidden, fiatCurrencyCode = fiatCurrencyCode, fiatCurrencySymbol = fiatCurrencySymbol, + clickCallbacks = clickCallbacks, ) override fun convert(value: TokensListModel): WalletStateHolder { - val state = if (currentState is MultiCurrencyContent) { - currentState.updateWithTokenList(tokenList = value.tokenList) - } else { - currentState - } - - return state.copySealed( - walletsListConfig = state.updateSelectedWallet(value.tokenList.totalFiatBalance), - pullToRefreshConfig = if (value.isRefreshing) { - state.pullToRefreshConfig.copy(isRefreshing = state.getRefreshingStatus()) - } else { - state.pullToRefreshConfig - }, - ) + val state = currentStateProvider() + return state + .updateWithTokenList(tokenList = value.tokenList) + .copySealed( + walletsListConfig = state.updateSelectedWallet(value.tokenList.totalFiatBalance), + pullToRefreshConfig = if (value.isRefreshing) { + state.pullToRefreshConfig.copy(isRefreshing = state.getRefreshingStatus()) + } else { + state.pullToRefreshConfig + }, + ) } - private fun MultiCurrencyContent.updateWithTokenList(tokenList: TokenList): MultiCurrencyContent { - return this.copy(contentItems = tokenListToContentConverter.convert(value = tokenList)) + private fun WalletStateHolder.updateWithTokenList(tokenList: TokenList): MultiCurrencyContent { + return MultiCurrencyContent( + onBackClick = onBackClick, + topBarConfig = topBarConfig, + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + notifications = notifications, + bottomSheet = bottomSheet, + tokensListState = tokenListToContentConverter.convert(value = tokenList), + ) } private fun WalletStateHolder.updateSelectedWallet(fiatBalance: TokenList.FiatBalance): WalletsListConfig { @@ -63,13 +71,13 @@ internal class TokenListToWalletStateConverter( } private fun WalletStateHolder.getRefreshingStatus(): Boolean { - return contentItems.any { state -> - val isMultiCurrencyItem = state as? WalletContentItemState.MultiCurrencyItem.Token - val isSingleCurrencyItem = state as? WalletContentItemState.SingleCurrencyItem.Transaction - - isMultiCurrencyItem?.state is TokenItemState.Loading || - isSingleCurrencyItem?.state is TransactionState.Loading || - state is WalletContentItemState.Loading + return if (this is MultiCurrencyContent) { + tokensListState.items.any { tokensListItemState -> + tokensListItemState is WalletTokensListState.TokensListItemState.Token && + tokensListItemState.state is TokenItemState.Loading + } + } else { + false } } 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 d0df6d7f3a..a5bbac817e 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 @@ -6,9 +6,9 @@ import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TokenStatus import com.tangem.feature.wallet.presentation.common.state.TokenItemState -import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState import com.tangem.feature.wallet.presentation.wallet.state.WalletNotification import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow @@ -32,7 +32,7 @@ internal class WalletNotificationsListFactory( private val clickCallbacks: WalletClickCallbacks, ) { - fun create(cardTypesResolver: CardTypesResolver, tokenList: TokenList): Flow> { + fun create(cardTypesResolver: CardTypesResolver, tokenList: TokenList?): Flow> { // TODO: [REDACTED_JIRA] order return flow { emit( @@ -63,7 +63,7 @@ internal class WalletNotificationsListFactory( add(element = WalletNotification.BackupCard(onClick = clickCallbacks::onBackupCardClick)) } - if (tokenList.hasMissedDerivations()) { + if (tokenList != null && tokenList.hasMissedDerivations()) { add(element = WalletNotification.ScanCard(onClick = clickCallbacks::onScanCardClick)) } @@ -112,12 +112,12 @@ internal class WalletNotificationsListFactory( } private fun hasUnreachableNetworks(): Boolean { - val isUnreachableState = { item: WalletContentItemState.MultiCurrencyItem -> - (item as? WalletContentItemState.MultiCurrencyItem.Token)?.state is TokenItemState.Unreachable + val isUnreachableState = { item: WalletTokensListState.TokensListItemState -> + (item as? WalletTokensListState.TokensListItemState.Token)?.state is TokenItemState.Unreachable } return currentStateProvider().let { state -> - state is WalletStateHolder.MultiCurrencyContent && state.contentItems.any(isUnreachableState) + state is WalletStateHolder.MultiCurrencyContent && state.tokensListState.items.any(isUnreachableState) } } 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 13b9716f84..3b008c9023 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 @@ -4,6 +4,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.* +import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.Provider import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess @@ -14,8 +15,10 @@ import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase +import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.userwallets.UserWalletBuilder -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.domain.wallets.usecase.SaveWalletUseCase import com.tangem.feature.wallet.presentation.router.InnerWalletRouter @@ -46,6 +49,8 @@ internal class WalletViewModel @Inject constructor( private val isUserAlreadyRateAppUseCase: IsUserAlreadyRateAppUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, private val scanCardProcessor: ScanCardProcessor, + private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, + private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, private val dispatchers: CoroutineDispatcherProvider, ) : ViewModel(), DefaultLifecycleObserver, WalletClickCallbacks { @@ -62,6 +67,9 @@ internal class WalletViewModel @Inject constructor( private val stateFactory = WalletStateFactory( currentStateProvider = Provider { uiState }, + currentCardTypeResolverProvider = Provider { + getCardTypeResolver(index = uiState.walletsListConfig.selectedWalletIndex) + }, clickCallbacks = this, ) @@ -69,10 +77,10 @@ internal class WalletViewModel @Inject constructor( var uiState: WalletStateHolder by mutableStateOf(stateFactory.getInitialState()) private set - private var cardTypeResolverMap: Map by Delegates.notNull() + private var wallets: List by Delegates.notNull() - private val getTokensJobHolder = JobHolder() - private val getNotificationJobHolder = JobHolder() + private val tokensJobHolder = JobHolder() + private val notificationsJobHolder = JobHolder() override fun onCreate(owner: LifecycleOwner) { getWalletsUseCase() @@ -80,15 +88,7 @@ internal class WalletViewModel @Inject constructor( .distinctUntilChanged() .onEach { wallets -> if (wallets.isEmpty()) return@onEach - - cardTypeResolverMap = wallets.associate { wallet -> - // TODO remove after adding release logic for GetTokenListUseCase - if (wallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) { - UserWalletId("123") - } else { - UserWalletId("321") - } to wallet.scanResponse.cardTypesResolver - } + this.wallets = wallets uiState = stateFactory.getSkeletonState(wallets = wallets) @@ -99,8 +99,15 @@ internal class WalletViewModel @Inject constructor( } private fun updateContentItems(index: Int, isRefreshing: Boolean = false) { - // TODO: use GetTransactionsUseCase + val cardTypeResolver = getCardTypeResolver(index) + if (cardTypeResolver.isMultiwalletAllowed()) { + updateByTokensList(index, isRefreshing) + } else { + updateByTxHistory(index) + } + } + private fun updateByTokensList(index: Int, isRefreshing: Boolean) { getTokenListUseCase(userWalletId = uiState.walletsListConfig.wallets[index].id) .distinctUntilChanged() .onEach { tokenListEither -> @@ -113,26 +120,50 @@ internal class WalletViewModel @Inject constructor( } .flowOn(dispatchers.io) .launchIn(viewModelScope) - .saveIn(jobHolder = getTokensJobHolder) + .saveIn(tokensJobHolder) } - private fun updateNotifications(index: Int, tokenList: TokenList) { + private fun updateByTxHistory(index: Int) { + viewModelScope.launch(dispatchers.io) { + val wallet = getWallet(index) + val blockchain = wallet.scanResponse.cardTypesResolver.getBlockchain() + uiState = stateFactory.getLoadingTxHistoryState( + itemsCountEither = txHistoryItemsCountUseCase( + networkId = blockchain.id, + derivationPath = requireNotNull(blockchain.derivationPath(style = DerivationStyle.LEGACY)).rawPath, + ), + ) + + updateTxHistory(networkId = blockchain.id) + updateNotifications(index) + } + } + + private fun updateTxHistory(networkId: String) { + uiState = stateFactory.getLoadedTxHistoryState(txHistoryEither = txHistoryItemsUseCase(networkId)) + } + + private fun updateNotifications(index: Int, tokenList: TokenList? = null) { notificationsListFactory.create( - cardTypesResolver = requireNotNull( - value = cardTypeResolverMap[uiState.walletsListConfig.wallets[index].id], - lazyMessage = { - "CardTypeResolverMap doesn't contain this id = ${uiState.walletsListConfig.wallets[index].id}" - }, - ), + cardTypesResolver = getCardTypeResolver(index = index), tokenList = tokenList, ) .distinctUntilChanged() .onEach { uiState = stateFactory.getStateByNotifications(notifications = it) } .flowOn(dispatchers.io) .launchIn(viewModelScope) - .saveIn(jobHolder = getNotificationJobHolder) + .saveIn(jobHolder = notificationsJobHolder) } + private fun getWallet(index: Int): UserWallet { + return requireNotNull( + value = wallets.getOrNull(index), + lazyMessage = { "WalletsList doesn't contain element with index = $index" }, + ) + } + + private fun getCardTypeResolver(index: Int): CardTypesResolver = getWallet(index).scanResponse.cardTypesResolver + override fun onBackClick() = router.popBackStack() override fun onScanCardClick() {