Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-22 12:48:12 +07:00
parent afb3ce1794
commit f120b184ac
13 changed files with 111 additions and 147 deletions

View file

@ -7,17 +7,14 @@ import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.FabPosition
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@ -32,9 +29,7 @@ import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items
import androidx.paging.compose.*
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
@ -134,8 +129,11 @@ private fun TokensListContent(
item { DifferentAddressesWarning() }
}
items(items = tokens, key = TokenItemState::composedId) {
it?.let { TokenItem(model = it) }
tokens.itemKey(TokenItemState::composedId)
tokens.itemContentType(TokenItemState::composedId)
items(items = tokens.itemSnapshotList.items, key = TokenItemState::composedId) {
TokenItem(model = it)
}
}
}

View file

@ -4,9 +4,11 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.ui.Modifier
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.itemsIndexed
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionBlock
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionsBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
@ -26,7 +28,7 @@ fun LazyListScope.txHistoryItems(
modifier: Modifier = Modifier,
) {
when (state) {
is TxHistoryState.ContentState -> {
is TxHistoryState.Content -> {
contentItems(
txHistoryItems = requireNotNull(txHistoryItems),
modifier = modifier,
@ -58,8 +60,18 @@ private fun LazyListScope.contentItems(
txHistoryItems: LazyPagingItems<TxHistoryState.TxHistoryItemState>,
modifier: Modifier = Modifier,
) {
txHistoryItems.itemKey { item ->
when (item) {
is TxHistoryState.TxHistoryItemState.GroupTitle -> item.title
is TxHistoryState.TxHistoryItemState.Title -> item.onExploreClick.hashCode()
is TxHistoryState.TxHistoryItemState.Transaction -> item.state.txHash
}
}
txHistoryItems.itemContentType { it::class.java }
itemsIndexed(
items = txHistoryItems,
items = txHistoryItems.itemSnapshotList.items,
key = { _, item ->
when (item) {
is TxHistoryState.TxHistoryItemState.GroupTitle -> item.title
@ -68,8 +80,6 @@ private fun LazyListScope.contentItems(
}
},
) { index, item ->
if (item == null) return@itemsIndexed
TxHistoryListItem(
state = item,
modifier = modifier

View file

@ -1,68 +1,17 @@
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
import kotlinx.coroutines.flow.MutableStateFlow
/** Wallet transaction history state */
sealed interface TxHistoryState {
/**
* Wallet transaction history state with content. Items contains a required [TxHistoryItemState.Title].
*
* @property contentItems content items
*/
sealed class ContentState(private val contentItems: Flow<PagingData<TxHistoryItemState>>) : TxHistoryState {
/** Lambda be invoke when explore button was clicked */
abstract val onExploreClick: () -> Unit
/** Content items with [TxHistoryItemState.Title] */
val items: Flow<PagingData<TxHistoryItemState>>
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(
override val onExploreClick: () -> Unit,
val transactions: Flow<PagingData<TxHistoryItemState>> = getDefaultLoadingTransactions(),
) : ContentState(transactions)
/**
* Wallet transaction history state with content
*
* @property onExploreClick lambda be invoke when explore button was clicked
* @property contentItems content items
* @property contentItems content items
*/
data class Content(
override val onExploreClick: () -> Unit,
val contentItems: Flow<PagingData<TxHistoryItemState>>,
) : ContentState(contentItems)
/**
* Locked state
*
* @property onExploreClick lambda be invoke when explore button was clicked
*/
data class Locked(override val onExploreClick: () -> Unit) :
ContentState(contentItems = getDefaultLoadingTransactions()),
WalletLockedContentState
data class Content(val contentItems: MutableStateFlow<PagingData<TxHistoryItemState>>) : TxHistoryState
/**
* Empty state
@ -110,16 +59,15 @@ sealed interface TxHistoryState {
data class Transaction(val state: TransactionState) : TxHistoryItemState
}
private companion object {
const val LOADING_TX_HASH = "LOADING_TX_HASH"
companion object {
private const val LOADING_TX_HASH = "LOADING_TX_HASH"
private fun getDefaultLoadingTransactions(): Flow<PagingData<TxHistoryItemState>> {
return flowOf(
value = PagingData.from(
data = listOf(
element = TxHistoryItemState.Transaction(
state = TransactionState.Loading(txHash = LOADING_TX_HASH),
),
fun getDefaultLoadingTransactions(onExploreClick: () -> Unit): PagingData<TxHistoryItemState> {
return PagingData.from(
data = listOf(
TxHistoryItemState.Title(onExploreClick = onExploreClick),
TxHistoryItemState.Transaction(
state = TransactionState.Loading(txHash = LOADING_TX_HASH),
),
),
)

View file

@ -19,7 +19,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.components.*
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.TokensListItemState
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow
import java.util.UUID
@Suppress("LargeClass")
@ -350,8 +350,7 @@ internal object WalletPreviewData {
),
),
txHistoryState = TxHistoryState.Content(
onExploreClick = {},
contentItems = flowOf(
contentItems = MutableStateFlow(
PagingData.from(
listOf(
TxHistoryState.TxHistoryItemState.GroupTitle("Today"),

View file

@ -1,11 +1,13 @@
package com.tangem.feature.wallet.presentation.wallet.state
import androidx.compose.runtime.Immutable
import androidx.paging.PagingData
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.*
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
/**
* Single currency wallet content state
@ -60,6 +62,8 @@ internal sealed class WalletSingleCurrencyState : WalletState.ContentState() {
),
)
override val txHistoryState: TxHistoryState = TxHistoryState.Locked(onExploreClick)
override val txHistoryState: TxHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(PagingData.empty()),
)
}
}

View file

@ -1,12 +1,8 @@
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
@ -20,9 +16,7 @@ import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickInten
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
import kotlinx.coroutines.flow.update
internal class WalletRefreshStateConverter(
private val currentStateProvider: Provider<WalletState>,
@ -98,30 +92,15 @@ internal class WalletRefreshStateConverter(
private fun WalletSingleCurrencyState.Content.getTxHistoryState(): TxHistoryState {
return when (txHistoryState) {
is TxHistoryState.Content -> {
TxHistoryState.Loading(
onExploreClick = clickIntents::onExploreClick,
transactions = txHistoryState.contentItems
.filterIsInstance<PagingData<TxHistoryItemState.Transaction>>()
.mapPagingData { transaction ->
transaction.copy(
state = TransactionState.Loading(txHash = transaction.state.txHash),
)
},
)
txHistoryState.contentItems.update {
TxHistoryState.getDefaultLoadingTransactions(onExploreClick = clickIntents::onExploreClick)
}
txHistoryState
}
is TxHistoryState.Empty,
is TxHistoryState.Error,
is TxHistoryState.NotSupported,
-> TxHistoryState.Loading(onExploreClick = clickIntents::onExploreClick)
is TxHistoryState.Locked,
is TxHistoryState.Loading,
-> txHistoryState
}
}
private fun Flow<PagingData<TxHistoryItemState.Transaction>>.mapPagingData(
transform: (TxHistoryItemState.Transaction) -> TxHistoryItemState,
): Flow<PagingData<TxHistoryItemState>> {
return map { it.map(transform) }
}
}

View file

@ -16,6 +16,7 @@ import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
/**
* Converter from loaded list of [UserWallet] to skeleton state of screen [WalletState.ContentState]
@ -62,7 +63,11 @@ internal class WalletSkeletonStateConverter(
bottomSheetConfig = null,
buttons = getButtons(),
marketPriceBlockState = MarketPriceBlockState.Loading(currencyName = currencyName),
txHistoryState = TxHistoryState.Loading(onExploreClick = clickIntents::onExploreClick),
txHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),
),
),
)
}

View file

@ -30,6 +30,7 @@ internal class WalletLoadedTxHistoryConverter(
private val walletTxHistoryItemFlowConverter by lazy {
WalletTxHistoryItemFlowConverter(
currentStateProvider = currentStateProvider,
blockchain = currentCardTypeResolverProvider().getBlockchain(),
clickIntents = clickIntents,
)

View file

@ -4,13 +4,13 @@ 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.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
import kotlinx.coroutines.flow.update
/**
* Converter from loading tx history state to [WalletSingleCurrencyState.Content]
@ -33,31 +33,36 @@ internal class WalletLoadingTxHistoryConverter(
return requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content).copy(
txHistoryState = when (error) {
is TxHistoryStateError.EmptyTxHistories -> {
TxHistoryState.Empty(onBuyClick = clickIntents::onBuyClick)
Empty(onBuyClick = clickIntents::onBuyClick)
}
is TxHistoryStateError.DataError -> {
TxHistoryState.Error(onReloadClick = clickIntents::onReloadClick)
Error(onReloadClick = clickIntents::onReloadClick)
}
is TxHistoryStateError.TxHistoryNotImplemented -> {
TxHistoryState.NotSupported(onExploreClick = clickIntents::onExploreClick)
NotSupported(onExploreClick = clickIntents::onExploreClick)
}
},
)
}
private fun convert(value: Int): WalletSingleCurrencyState.Content {
return requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content).copy(
txHistoryState = TxHistoryState.Loading(
onExploreClick = clickIntents::onExploreClick,
transactions = flow {
PagingData.from(
data = MutableList(
size = value,
init = { TransactionState.Loading(it.toString()) },
),
)
},
),
)
val state = requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content)
val txHistoryContent = requireNotNull(state.txHistoryState as? Content)
txHistoryContent.contentItems.update {
PagingData.from(
data = listOf(TxHistoryItemState.Title(onExploreClick = clickIntents::onExploreClick)) +
MutableList(
size = value,
init = {
TxHistoryItemState.Transaction(
state = TransactionState.Loading(it.toString()),
)
},
),
)
}
return state
}
}

View file

@ -1,23 +1,27 @@
package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory
import android.text.format.DateUtils
import androidx.paging.PagingData
import androidx.paging.TerminalSeparatorType
import androidx.paging.insertSeparators
import androidx.paging.map
import androidx.paging.*
import com.tangem.blockchain.common.Blockchain
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.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.txhistory.models.TxHistoryItem
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 com.tangem.utils.extensions.isToday
import com.tangem.utils.extensions.isYesterday
import com.tangem.utils.toBriefAddressFormat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.format.DateTimeFormatterBuilder
@ -27,12 +31,14 @@ import java.util.Locale
/**
* Convert from [Flow] of [TxHistoryItem] to [TxHistoryState]
*
* @property blockchain blockchain of transactions history
* @property clickIntents screen click intents
* @property currentStateProvider current state provider
* @property blockchain blockchain of transactions history
* @property clickIntents screen click intents
*
[REDACTED_AUTHOR]
*/
internal class WalletTxHistoryItemFlowConverter(
private val currentStateProvider: Provider<WalletState>,
private val blockchain: Blockchain,
private val clickIntents: WalletClickIntents,
) : Converter<Flow<PagingData<TxHistoryItem>>, TxHistoryState> {
@ -60,19 +66,30 @@ internal class WalletTxHistoryItemFlowConverter(
}
override fun convert(value: Flow<PagingData<TxHistoryItem>>): TxHistoryState {
return TxHistoryState.Content(
onExploreClick = clickIntents::onExploreClick,
contentItems = value
.map { pagingData ->
pagingData
val state = currentStateProvider() as WalletSingleCurrencyState
val txHistoryContent = state.txHistoryState as TxHistoryState.Content
// FIXME: TxHistoryRepository should send loading transactions
// [REDACTED_JIRA]
value
.onEach { txHistoryStatePagingData ->
txHistoryContent.contentItems.update {
txHistoryStatePagingData
.map<TxHistoryItem, TxHistoryItemState> { item ->
// [createTransactionState] returns timestamp without formatting
TxHistoryItemState.Transaction(state = createTransactionState(item))
}
.insertHeaderItem(
terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE,
item = TxHistoryItemState.Title(clickIntents::onExploreClick),
)
.insertGroupTitle() // method uses the raw timestamp
.formatTransactionsTimestamp() // method formats the timestamp
},
)
}
}
.launchIn(CoroutineScope(Dispatchers.IO))
return txHistoryContent
}
private fun createTransactionState(item: TxHistoryItem): TransactionState {

View file

@ -69,9 +69,9 @@ private fun WalletContent(state: WalletState.ContentState) {
.pullRefresh(pullRefreshState),
) {
val txHistoryItems = if (state is WalletSingleCurrencyState &&
state.txHistoryState is TxHistoryState.ContentState
state.txHistoryState is TxHistoryState.Content
) {
(state.txHistoryState as? TxHistoryState.ContentState)?.items?.collectAsLazyPagingItems()
(state.txHistoryState as? TxHistoryState.Content)?.contentItems?.collectAsLazyPagingItems()
} else {
null
}

View file

@ -9,7 +9,6 @@ import com.tangem.common.Provider
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.card.*
@ -375,8 +374,7 @@ internal class WalletViewModel @Inject constructor(
tokensListState is WalletTokensListState.Loading || hasLoadingTokens
}
is WalletSingleCurrencyState -> {
this is WalletSingleCurrencyState.Content && marketPriceBlockState is MarketPriceBlockState.Loading ||
txHistoryState is TxHistoryState.Loading
this is WalletSingleCurrencyState.Content && marketPriceBlockState is MarketPriceBlockState.Loading
}
is WalletState.Initial -> false
}

View file

@ -30,7 +30,7 @@ compose-material3 = "1.1.0"
compose-constraint = "1.0.1"
compose-navigation = "2.5.3"
compose-accompanist = "0.30.1"
compose-paging = "1.0.0-alpha18"
compose-paging = "3.2.0"
compose-reorderable = "0.9.6"
# endregion Compose