Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-27 14:34:43 +03:00
parent 229dc89bcb
commit 3398db66e2
13 changed files with 609 additions and 156 deletions

View file

@ -14,7 +14,8 @@ import androidx.compose.ui.util.fastForEach
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionBlock
import com.tangem.core.ui.R
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionBlockLegacy
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionsBlockState
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
@ -45,13 +46,21 @@ fun LazyListScope.txHistoryItems(
)
}
is TxHistoryState.Empty -> {
nonContentItem(state = EmptyTransactionsBlockState.Empty(state.onExploreClick), modifier = modifier)
nonContentItem(
state = EmptyTransactionsBlockState.Empty(
onExplore = state.onExploreClick,
exploreIconResId = R.drawable.ic_arrow_top_right_24,
),
modifier = modifier,
)
}
is TxHistoryState.Error -> {
nonContentItem(
state = EmptyTransactionsBlockState.FailedToLoad(
onReload = state.onReloadClick,
onExplore = state.onExploreClick,
reloadIconResId = R.drawable.ic_refresh_24,
exploreIconResId = R.drawable.ic_arrow_top_right_24,
),
modifier = modifier,
)
@ -64,7 +73,10 @@ fun LazyListScope.txHistoryItems(
}
nonContentItem(
state = EmptyTransactionsBlockState.NotImplemented(onExplore = state.onExploreClick),
state = EmptyTransactionsBlockState.NotImplemented(
onExplore = state.onExploreClick,
exploreIconResId = R.drawable.ic_arrow_top_right_24,
),
modifier = modifier,
)
}
@ -121,7 +133,7 @@ fun PendingTxsBlock(pendingTxs: ImmutableList<TransactionState>, isBalanceHidden
private fun LazyListScope.nonContentItem(state: EmptyTransactionsBlockState, modifier: Modifier = Modifier) {
item(key = state::class.java, contentType = state::class.java) {
EmptyTransactionBlock(
EmptyTransactionBlockLegacy(
state = state,
modifier = modifier
.animateItem(fadeInSpec = null, fadeOutSpec = null)

View file

@ -1,65 +1,74 @@
package com.tangem.core.ui.components.transactions.empty
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.buttons.actions.ActionButton
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.ds.button.TangemButton
import com.tangem.core.ui.ds.button.TangemButtonIconPosition
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.ds.button.TangemButtonType
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.EmptyTransactionBlockTestTags
/**
* Placeholder for transaction's block without content
*
* @param state component state
* @param modifier modifier
*/
@Composable
fun EmptyTransactionBlock(state: EmptyTransactionsBlockState, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(color = TangemTheme.colors.background.primary)
.padding(vertical = TangemTheme.dimens.spacing24)
.fillMaxWidth()
.padding(vertical = TangemTheme.dimens2.x6)
.testTag(EmptyTransactionBlockTestTags.BLOCK),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
TangemIcon(
tangemIconUM = TangemIconUM.Icon(
iconRes = state.iconRes,
tintReference = { TangemTheme.colors2.graphic.neutral.tertiary },
),
modifier = Modifier
.size(TangemTheme.dimens.size64)
.size(TangemTheme.dimens2.x16)
.testTag(EmptyTransactionBlockTestTags.ICON),
painter = painterResource(id = state.iconRes),
tint = TangemTheme.colors.icon.inactive,
contentDescription = null,
)
Spacer(modifier = Modifier.height(TangemTheme.dimens2.x4))
Text(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing32)
.padding(horizontal = TangemTheme.dimens2.x8)
.testTag(EmptyTransactionBlockTestTags.TEXT),
textAlign = TextAlign.Center,
text = state.text.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography2.calloutRegular15,
color = TangemTheme.colors2.text.neutral.tertiary,
)
Spacer(modifier = Modifier.height(TangemTheme.dimens2.x8))
Buttons(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing18)
.padding(horizontal = TangemTheme.dimens2.x4)
.testTag(EmptyTransactionBlockTestTags.EXPLORE_BUTTON),
state = state.buttonsState,
)
@ -76,41 +85,70 @@ private fun Buttons(state: EmptyTransactionsBlockState.ButtonsState, modifier: M
@Composable
private fun SingleButton(state: EmptyTransactionsBlockState.ButtonsState.SingleButton, modifier: Modifier = Modifier) {
ActionButton(modifier = modifier, config = state.actionButtonConfig)
Row(
modifier = modifier,
horizontalArrangement = Arrangement.Center,
) {
TangemButton(buttonUM = state.actionButtonConfig.toButtonUM())
}
}
@Composable
private fun PairButtons(state: EmptyTransactionsBlockState.ButtonsState.PairButtons, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
ActionButton(
TangemButton(
modifier = Modifier.weight(1F),
config = state.firstButtonConfig,
buttonUM = state.firstButtonConfig.toButtonUM(),
)
ActionButton(
TangemButton(
modifier = Modifier.weight(1F),
config = state.secondButtonConfig,
buttonUM = state.secondButtonConfig.toButtonUM(),
)
}
}
private fun ActionButtonConfig.toButtonUM(): TangemButtonUM = TangemButtonUM(
text = text,
tangemIconUM = TangemIconUM.Icon(iconRes = iconResId),
type = TangemButtonType.Secondary,
size = TangemButtonSize.X12,
isEnabled = isEnabled,
isLoading = isInProgress,
onClick = onClick,
shape = TangemButtonShape.Rounded,
iconPosition = TangemButtonIconPosition.End,
)
@Composable
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun EmptyTransactionBlockPreview(
@PreviewParameter(EmptyTransactionBlockStateProvider::class) state: EmptyTransactionsBlockState,
) {
TangemThemePreview {
TangemThemePreviewRedesign {
EmptyTransactionBlock(state = state)
}
}
private class EmptyTransactionBlockStateProvider : CollectionPreviewParameterProvider<EmptyTransactionsBlockState>(
collection = listOf(
EmptyTransactionsBlockState.Empty {},
EmptyTransactionsBlockState.FailedToLoad(onReload = {}, onExplore = {}),
EmptyTransactionsBlockState.NotImplemented(onExplore = {}),
),
)
private class EmptyTransactionBlockStateProvider :
CollectionPreviewParameterProvider<EmptyTransactionsBlockState>(
collection = listOf(
EmptyTransactionsBlockState.Empty(
onExplore = {},
exploreIconResId = R.drawable.ic_compass_24,
),
EmptyTransactionsBlockState.FailedToLoad(
onReload = {},
onExplore = {},
reloadIconResId = R.drawable.ic_refresh_24,
exploreIconResId = R.drawable.ic_compass_24,
),
EmptyTransactionsBlockState.NotImplemented(
onExplore = {},
exploreIconResId = R.drawable.ic_compass_24,
),
),
)

View file

@ -0,0 +1,129 @@
package com.tangem.core.ui.components.transactions.empty
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.actions.ActionButton
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.EmptyTransactionBlockTestTags
/**
* Placeholder for transaction's block without content
*
* @param state component state
* @param modifier modifier
*/
@Composable
fun EmptyTransactionBlockLegacy(state: EmptyTransactionsBlockState, 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,
)
Buttons(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing18)
.testTag(EmptyTransactionBlockTestTags.EXPLORE_BUTTON),
state = state.buttonsState,
)
}
}
@Composable
private fun Buttons(state: EmptyTransactionsBlockState.ButtonsState, modifier: Modifier = Modifier) {
when (state) {
is EmptyTransactionsBlockState.ButtonsState.SingleButton -> SingleButton(state = state, modifier = modifier)
is EmptyTransactionsBlockState.ButtonsState.PairButtons -> PairButtons(state = state, modifier = modifier)
}
}
@Composable
private fun SingleButton(state: EmptyTransactionsBlockState.ButtonsState.SingleButton, modifier: Modifier = Modifier) {
ActionButton(modifier = modifier, config = state.actionButtonConfig)
}
@Composable
private fun PairButtons(state: EmptyTransactionsBlockState.ButtonsState.PairButtons, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
ActionButton(
modifier = Modifier.weight(1F),
config = state.firstButtonConfig,
)
ActionButton(
modifier = Modifier.weight(1F),
config = state.secondButtonConfig,
)
}
}
@Composable
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun EmptyTransactionBlockLegacyPreview(
@PreviewParameter(EmptyTransactionBlockLegacyStateProvider::class) state: EmptyTransactionsBlockState,
) {
TangemThemePreview {
EmptyTransactionBlockLegacy(state = state)
}
}
private class EmptyTransactionBlockLegacyStateProvider :
CollectionPreviewParameterProvider<EmptyTransactionsBlockState>(
collection = listOf(
EmptyTransactionsBlockState.Empty(
onExplore = {},
exploreIconResId = R.drawable.ic_arrow_top_right_24,
),
EmptyTransactionsBlockState.FailedToLoad(
onReload = {},
onExplore = {},
reloadIconResId = R.drawable.ic_refresh_24,
exploreIconResId = R.drawable.ic_arrow_top_right_24,
),
EmptyTransactionsBlockState.NotImplemented(
onExplore = {},
exploreIconResId = R.drawable.ic_arrow_top_right_24,
),
),
)

View file

@ -1,5 +1,6 @@
package com.tangem.core.ui.components.transactions.empty
import androidx.annotation.DrawableRes
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.extensions.TextReference
@ -21,17 +22,19 @@ sealed class EmptyTransactionsBlockState(
data class FailedToLoad(
val onReload: () -> Unit,
val onExplore: () -> Unit,
@DrawableRes val reloadIconResId: Int,
@DrawableRes val exploreIconResId: Int,
) : EmptyTransactionsBlockState(
buttonsState = ButtonsState.PairButtons(
firstButtonConfig = ActionButtonConfig(
text = TextReference.Res(R.string.common_reload),
iconResId = R.drawable.ic_refresh_24,
iconResId = reloadIconResId,
onClick = onReload,
isEnabled = true,
),
secondButtonConfig = ActionButtonConfig(
text = TextReference.Res(R.string.common_explore),
iconResId = R.drawable.ic_arrow_top_right_24,
iconResId = exploreIconResId,
onClick = onExplore,
isEnabled = true,
),
@ -40,11 +43,14 @@ sealed class EmptyTransactionsBlockState(
text = TextReference.Res(R.string.transaction_history_error_failed_to_load),
)
data class Empty(val onExplore: (() -> Unit)) : EmptyTransactionsBlockState(
data class Empty(
val onExplore: () -> Unit,
@DrawableRes val exploreIconResId: Int,
) : EmptyTransactionsBlockState(
buttonsState = ButtonsState.SingleButton(
actionButtonConfig = ActionButtonConfig(
text = TextReference.Res(R.string.common_explore),
iconResId = R.drawable.ic_arrow_top_right_24,
iconResId = exploreIconResId,
onClick = onExplore,
isEnabled = true,
),
@ -53,11 +59,14 @@ sealed class EmptyTransactionsBlockState(
text = TextReference.Res(R.string.transaction_history_empty_transactions),
)
data class NotImplemented(val onExplore: () -> Unit) : EmptyTransactionsBlockState(
data class NotImplemented(
val onExplore: () -> Unit,
@DrawableRes val exploreIconResId: Int,
) : EmptyTransactionsBlockState(
buttonsState = ButtonsState.SingleButton(
actionButtonConfig = ActionButtonConfig(
text = TextReference.Res(R.string.common_explore_transaction_history),
iconResId = R.drawable.ic_arrow_top_right_24,
iconResId = exploreIconResId,
onClick = onExplore,
isEnabled = true,
),