Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-07 19:37:13 +05:00
parent f1f9dff5ce
commit aefaf636e8
8 changed files with 142 additions and 26 deletions

View file

@ -86,6 +86,6 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
}.result
val items = TangemPayTxHistoryItemConverter.convertList(result.transactions).filterNotNull()
txHistoryItemsStore.store(key = customerWalletAddress, cursor = cursor ?: INITIAL_CURSOR, value = items)
}
}.onLeft { error(it.toString()) }
}
}

View file

@ -21,6 +21,7 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
companion object {
val loadingUM = TangemPayTxHistoryUM.Loading(isBalanceHidden = true)
val emptyUM = TangemPayTxHistoryUM.Empty(isBalanceHidden = true)
val errorUM = TangemPayTxHistoryUM.Error(isBalanceHidden = true, onReload = {})
val contentUM = TangemPayTxHistoryUM.Content(
isBalanceHidden = false,
loadMore = { false },

View file

@ -0,0 +1,29 @@
package com.tangem.features.tangempay.entity
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.tangempay.details.impl.R
internal sealed class TangemPayEmptyTransactionHistoryState {
abstract val iconRes: Int
abstract val text: TextReference
data class FailedToLoad(
private val onReload: () -> Unit,
) : TangemPayEmptyTransactionHistoryState() {
override val iconRes: Int = R.drawable.ic_alert_history_64
override val text: TextReference = resourceReference(R.string.transaction_history_error_failed_to_load)
val actionButtonConfig = ActionButtonConfig(
text = resourceReference(R.string.common_reload),
iconResId = R.drawable.ic_refresh_24,
onClick = onReload,
)
}
data object Empty : TangemPayEmptyTransactionHistoryState() {
override val iconRes: Int = R.drawable.ic_empty_token_64
override val text: TextReference = resourceReference(R.string.transaction_history_empty_transactions)
}
}

View file

@ -54,11 +54,16 @@ internal class TangemPayTxHistoryModel @Inject constructor(
.onEach(::updateState)
.launchIn(modelScope)
listManager.paginationStatus
.onEach { paginationStatus -> handlePaginationStatus(paginationStatus) }
.onEach(::handlePaginationStatus)
.launchIn(modelScope)
listManager.emptyStatus
.onEach(::handleEmptyState)
.launchIn(modelScope)
}
private fun updateState(items: ImmutableList<TangemPayTxHistoryUM.TangemPayTxHistoryItemUM>) {
if (items.isEmpty()) return // fast exit. If items is empty, no need to update ui items
uiState.update { state ->
if (state is TangemPayTxHistoryUM.Content) {
state.copy(items = items)
@ -72,6 +77,12 @@ internal class TangemPayTxHistoryModel @Inject constructor(
}
}
private fun handleEmptyState(isEmpty: Boolean) {
if (isEmpty) {
uiState.update { getEmptyState(it.isBalanceHidden) }
}
}
private fun handlePaginationStatus(status: PaginationStatus<*>) {
uiState.update { state ->
when (status) {
@ -108,6 +119,10 @@ internal class TangemPayTxHistoryModel @Inject constructor(
Timber.d("onTransactionClick: $item")
}
private fun getEmptyState(isBalanceHidden: Boolean): TangemPayTxHistoryUM.Empty {
return TangemPayTxHistoryUM.Empty(isBalanceHidden = isBalanceHidden)
}
private fun getErrorState(isBalanceHidden: Boolean): TangemPayTxHistoryUM.Error {
return TangemPayTxHistoryUM.Error(isBalanceHidden = isBalanceHidden, onReload = ::reload)
}

View file

@ -2,12 +2,7 @@ package com.tangem.features.tangempay.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@ -15,12 +10,8 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -44,21 +35,14 @@ import com.tangem.core.ui.components.dropdownmenu.TangemDropdownItem
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenu
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenDetailsTopBarTestTags
import com.tangem.features.tangempay.components.txHistory.PreviewTangemPayTxHistoryComponent
import com.tangem.features.tangempay.components.txHistory.TangemPayTxHistoryComponent
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarConfig
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
import com.tangem.features.tangempay.entity.*
import com.tangem.utils.StringsSigns.DASH_SIGN
import kotlinx.collections.immutable.persistentListOf
@ -415,8 +399,8 @@ private fun TangemPayDetailsTopAppBar(config: TangemPayDetailsTopBarConfig, modi
)
}
@Preview(device = Devices.PIXEL_7_PRO, group = "day")
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_7_PRO, group = "night")
@Preview(device = Devices.PIXEL_7_PRO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_7_PRO)
@Composable
private fun TangemPayDetailsScreenPreview(
@PreviewParameter(TangemPayDetailsUMProvider::class) state: TangemPayDetailsUM,
@ -473,4 +457,26 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
isBalanceHidden = false,
),
),
)
@Preview(device = Devices.PIXEL_7_PRO)
@Composable
private fun TangemPayDetailsTxHistoryScreenPreview(
@PreviewParameter(TangemPayDetailsTxHistoryProvider::class) state: TangemPayTxHistoryUM,
) {
TangemThemePreview {
TangemPayDetailsScreen(
state = TangemPayDetailsUMProvider().values.first(),
txHistoryComponent = PreviewTangemPayTxHistoryComponent(txHistoryUM = state),
)
}
}
private class TangemPayDetailsTxHistoryProvider : CollectionPreviewParameterProvider<TangemPayTxHistoryUM>(
collection = listOf(
PreviewTangemPayTxHistoryComponent.loadingUM,
PreviewTangemPayTxHistoryComponent.contentUM,
PreviewTangemPayTxHistoryComponent.emptyUM,
PreviewTangemPayTxHistoryComponent.errorUM,
),
)

View file

@ -20,6 +20,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ChainStyle
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
@ -27,6 +28,7 @@ import coil.compose.rememberAsyncImagePainter
import com.tangem.core.ui.R
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.buttons.actions.ActionButton
import com.tangem.core.ui.components.list.InfiniteListHandler
import com.tangem.core.ui.components.transactions.TxHistoryGroupTitle
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
@ -34,7 +36,9 @@ import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.EmptyTransactionBlockTestTags
import com.tangem.core.ui.test.TransactionHistoryBlockTestTags
import com.tangem.features.tangempay.entity.TangemPayEmptyTransactionHistoryState
import com.tangem.features.tangempay.entity.TangemPayTransactionState
import com.tangem.features.tangempay.entity.TangemPayTxHistoryUM
@ -43,12 +47,25 @@ private const val LOAD_ITEMS_BUFFER = 20
internal fun LazyListScope.tangemPayTxHistoryItems(listState: LazyListState, state: TangemPayTxHistoryUM) {
when (state) {
is TangemPayTxHistoryUM.Content -> contentItems(listState = listState, state = state)
is TangemPayTxHistoryUM.Empty -> TODO("[REDACTED_JIRA]")
is TangemPayTxHistoryUM.Error -> TODO("[REDACTED_JIRA]")
is TangemPayTxHistoryUM.Empty -> nonContentItem(state = TangemPayEmptyTransactionHistoryState.Empty)
is TangemPayTxHistoryUM.Error -> nonContentItem(
state = TangemPayEmptyTransactionHistoryState.FailedToLoad(onReload = state.onReload),
)
is TangemPayTxHistoryUM.Loading -> loadingItems(state = state)
}
}
private fun LazyListScope.nonContentItem(state: TangemPayEmptyTransactionHistoryState, modifier: Modifier = Modifier) {
item(key = state::class.java, contentType = state::class.java) {
TangemPayEmptyTransactionBlock(
state = state,
modifier = modifier
.padding(horizontal = TangemTheme.dimens.spacing16, vertical = TangemTheme.dimens.spacing12)
.fillMaxWidth(),
)
}
}
private fun LazyListScope.contentItems(listState: LazyListState, state: TangemPayTxHistoryUM.Content) {
itemsIndexed(
items = state.items,
@ -370,4 +387,49 @@ private fun Timestamp(state: TangemPayTransactionState, modifier: Modifier = Mod
)
}
}
}
@Composable
private fun TangemPayEmptyTransactionBlock(
state: TangemPayEmptyTransactionHistoryState,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(color = TangemTheme.colors.background.primary)
.padding(vertical = TangemTheme.dimens.spacing24)
.testTag(EmptyTransactionBlockTestTags.BLOCK),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
modifier = Modifier
.size(TangemTheme.dimens.size64)
.testTag(EmptyTransactionBlockTestTags.ICON),
painter = painterResource(id = state.iconRes),
tint = TangemTheme.colors.icon.inactive,
contentDescription = null,
)
Text(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing32)
.testTag(EmptyTransactionBlockTestTags.TEXT),
textAlign = TextAlign.Center,
text = state.text.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
when (state) {
is TangemPayEmptyTransactionHistoryState.Empty -> Unit
is TangemPayEmptyTransactionHistoryState.FailedToLoad -> ActionButton(
modifier = Modifier
.padding(horizontal = 24.dp)
.fillMaxWidth(),
config = state.actionButtonConfig,
)
}
}
}

View file

@ -33,6 +33,7 @@ internal class TangemPayTxHistoryListManager(
private val uiManager = TangemPayTxHistoryUiManager(state = state, txHistoryUiActions = txHistoryUiActions)
val uiItems: Flow<ImmutableList<TangemPayTxHistoryUM.TangemPayTxHistoryItemUM>> = uiManager.items
val emptyStatus: Flow<Boolean> = state.map { it.isEmpty }.distinctUntilChanged()
val paginationStatus: Flow<PaginationStatus<*>> = state.map { it.status }.distinctUntilChanged()
suspend fun launchPagination() = coroutineScope {
@ -80,6 +81,7 @@ internal class TangemPayTxHistoryListManager(
newCurrencyBatches = batchListState.data,
clearUiBatches = clearUiBatches,
),
isEmpty = batchListState.status is PaginationStatus.EndOfPagination && batchListState.data.isEmpty(),
)
}
}

View file

@ -7,4 +7,5 @@ import com.tangem.pagination.PaginationStatus
internal data class TangemPayTxHistoryState(
val status: PaginationStatus<*> = PaginationStatus.None,
val uiBatches: List<Batch<Int, List<TangemPayTxHistoryUM.TangemPayTxHistoryItemUM>>> = listOf(),
val isEmpty: Boolean = false,
)