From aefaf636e87dc8bd715748896af970db0095663e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 7 Oct 2025 19:37:13 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../DefaultTangemPayTxHistoryRepository.kt | 2 +- .../PreviewTangemPayTxHistoryComponent.kt | 1 + .../TangemPayEmptyTransactionHistoryState.kt | 29 ++++++++ .../model/TangemPayTxHistoryModel.kt | 17 ++++- .../tangempay/ui/TangemPayDetailsScreen.kt | 50 +++++++------- .../tangempay/ui/TangemPayTxHistoryUi.kt | 66 ++++++++++++++++++- .../utils/TangemPayTxHistoryListManager.kt | 2 + .../utils/TangemPayTxHistoryState.kt | 1 + 8 files changed, 142 insertions(+), 26 deletions(-) create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayEmptyTransactionHistoryState.kt diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt index be43b2bd72..7e197fea52 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt @@ -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()) } } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/PreviewTangemPayTxHistoryComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/PreviewTangemPayTxHistoryComponent.kt index b430960c14..ba9bbc588f 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/PreviewTangemPayTxHistoryComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/PreviewTangemPayTxHistoryComponent.kt @@ -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 }, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayEmptyTransactionHistoryState.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayEmptyTransactionHistoryState.kt new file mode 100644 index 0000000000..50816ac2a7 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayEmptyTransactionHistoryState.kt @@ -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) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryModel.kt index 1b3778feb0..ac67e1d5b4 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryModel.kt @@ -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) { + 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) } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt index c2e6cc25f7..bd9cc78a83 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt @@ -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( + collection = listOf( + PreviewTangemPayTxHistoryComponent.loadingUM, + PreviewTangemPayTxHistoryComponent.contentUM, + PreviewTangemPayTxHistoryComponent.emptyUM, + PreviewTangemPayTxHistoryComponent.errorUM, + ), ) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayTxHistoryUi.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayTxHistoryUi.kt index b7c07cbc03..466c8de420 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayTxHistoryUi.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayTxHistoryUi.kt @@ -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, + ) + } + } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryListManager.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryListManager.kt index 3ccf9cf202..40cd31c7da 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryListManager.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryListManager.kt @@ -33,6 +33,7 @@ internal class TangemPayTxHistoryListManager( private val uiManager = TangemPayTxHistoryUiManager(state = state, txHistoryUiActions = txHistoryUiActions) val uiItems: Flow> = uiManager.items + val emptyStatus: Flow = state.map { it.isEmpty }.distinctUntilChanged() val paginationStatus: Flow> = 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(), ) } } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryState.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryState.kt index 5717238dda..5df7a11936 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryState.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryState.kt @@ -7,4 +7,5 @@ import com.tangem.pagination.PaginationStatus internal data class TangemPayTxHistoryState( val status: PaginationStatus<*> = PaginationStatus.None, val uiBatches: List>> = listOf(), + val isEmpty: Boolean = false, ) \ No newline at end of file