Updated on 2026-08-14
This commit is contained in:
parent
8654ee6943
commit
02ac739980
9 changed files with 549 additions and 3 deletions
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.features.tangempay.entity.TangemPayTxHistoryUM
|
||||
import com.tangem.features.tangempay.model.TangemPayTxHistoryModel
|
||||
import com.tangem.features.tangempay.ui.tangemPayTxHistoryItems
|
||||
import com.tangem.features.tangempay.ui.tangemPayTxHistoryItemsV2
|
||||
import com.tangem.features.tangempay.utils.TangemPayTxHistoryUiActions
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
|
|
@ -20,7 +21,11 @@ internal class DefaultTangemPayTxHistoryComponent(
|
|||
override val state: StateFlow<TangemPayTxHistoryUM> = model.uiState
|
||||
|
||||
override fun LazyListScope.txHistoryContent(listState: LazyListState, state: TangemPayTxHistoryUM) {
|
||||
tangemPayTxHistoryItems(listState, state)
|
||||
if (model.isRedesignEnabled()) {
|
||||
tangemPayTxHistoryItemsV2(listState, state)
|
||||
} else {
|
||||
tangemPayTxHistoryItems(listState, state)
|
||||
}
|
||||
}
|
||||
|
||||
data class Params(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.tangempay.components.txHistory
|
|||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.extensions.ImageReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.themedColor
|
||||
|
|
@ -37,10 +38,12 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
onClick = {},
|
||||
amount = "-4.99 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "16:41",
|
||||
title = stringReference("StarbucksStarbucksStarbucksStarbucks"),
|
||||
subtitle = stringReference("Food&Drinks"),
|
||||
icon = ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction(
|
||||
|
|
@ -48,11 +51,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "-126.20 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "12:04",
|
||||
onClick = {},
|
||||
title = stringReference("Wallmart"),
|
||||
subtitle = stringReference("Supermarket"),
|
||||
icon = ImageReference.Res(R.drawable.ic_arrow_up_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction(
|
||||
|
|
@ -60,11 +65,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "+126.20 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.accent },
|
||||
amountColorV2 = { TangemTheme.colors.text.accent },
|
||||
time = "12:04",
|
||||
onClick = {},
|
||||
title = stringReference("Wallmart"),
|
||||
subtitle = stringReference("Supermarket"),
|
||||
icon = ImageReference.Res(R.drawable.ic_arrow_down_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction(
|
||||
|
|
@ -72,11 +79,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "-126.20 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "12:04",
|
||||
onClick = {},
|
||||
title = stringReference("Wallmart"),
|
||||
subtitle = stringReference("Supermarket"),
|
||||
icon = ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.GroupTitle(title = "Yesterday", itemKey = "Yesterday"),
|
||||
|
|
@ -85,11 +94,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "-4.99 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "21:41",
|
||||
onClick = {},
|
||||
title = stringReference("Starbucks"),
|
||||
subtitle = stringReference("Food&Drinks"),
|
||||
icon = ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction(
|
||||
|
|
@ -97,11 +108,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "-126.20 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "10:04",
|
||||
onClick = {},
|
||||
title = stringReference("Wallmart"),
|
||||
subtitle = stringReference("Supermarket"),
|
||||
icon = ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction(
|
||||
|
|
@ -109,11 +122,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "-4.99 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "19:41",
|
||||
onClick = {},
|
||||
title = stringReference("Starbucks"),
|
||||
subtitle = stringReference("Food&Drinks"),
|
||||
icon = ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction(
|
||||
|
|
@ -121,11 +136,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "-126.20 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "18:04",
|
||||
onClick = {},
|
||||
title = stringReference("Wallmart"),
|
||||
subtitle = stringReference("Supermarket"),
|
||||
icon = ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction(
|
||||
|
|
@ -133,11 +150,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "-4.99 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "17:41",
|
||||
onClick = {},
|
||||
title = stringReference("Starbucks"),
|
||||
subtitle = stringReference("Food&Drinks"),
|
||||
icon = ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction(
|
||||
|
|
@ -145,11 +164,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "-126.20 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "16:04",
|
||||
onClick = {},
|
||||
title = stringReference("Wallmart"),
|
||||
subtitle = stringReference("Supermarket"),
|
||||
icon = ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction(
|
||||
|
|
@ -157,11 +178,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "-4.99 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "15:41",
|
||||
onClick = {},
|
||||
title = stringReference("Starbucks"),
|
||||
subtitle = stringReference("Food&Drinks"),
|
||||
icon = ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction(
|
||||
|
|
@ -169,11 +192,13 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
id = "signiferumque",
|
||||
amount = "-126.20 USD",
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors.text.primary1 },
|
||||
time = "14:04",
|
||||
onClick = {},
|
||||
title = stringReference("Wallmart"),
|
||||
subtitle = stringReference("Supermarket"),
|
||||
icon = ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = TangemIconUM.Icon(R.drawable.ic_category_24),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
|
@ -26,4 +27,19 @@ internal sealed class TangemPayEmptyTransactionHistoryState {
|
|||
override val iconRes: Int = R.drawable.ic_empty_token_64
|
||||
override val text: TextReference = resourceReference(R.string.transaction_history_empty_transactions)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal sealed interface TangemPayEmptyTransactionHistoryStateV2 {
|
||||
|
||||
val text: TextReference
|
||||
|
||||
data class FailedToLoad(
|
||||
val onReload: () -> Unit,
|
||||
override val text: TextReference = resourceReference(R.string.transaction_history_error_failed_to_load),
|
||||
) : TangemPayEmptyTransactionHistoryStateV2
|
||||
|
||||
data object Empty : TangemPayEmptyTransactionHistoryStateV2 {
|
||||
override val text: TextReference = resourceReference(R.string.transaction_history_empty_transactions)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.extensions.ColorReference
|
||||
import com.tangem.core.ui.extensions.ColorReference2
|
||||
import com.tangem.core.ui.extensions.ImageReference
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
@Immutable
|
||||
internal sealed interface TangemPayTransactionState {
|
||||
|
||||
val id: String
|
||||
|
|
@ -15,9 +19,11 @@ internal sealed interface TangemPayTransactionState {
|
|||
val onClick: () -> Unit
|
||||
val amount: String
|
||||
val amountColor: ColorReference
|
||||
val amountColorV2: ColorReference2
|
||||
val title: TextReference
|
||||
val subtitle: TextReference
|
||||
val icon: ImageReference
|
||||
val iconV2: TangemIconUM
|
||||
val time: String
|
||||
|
||||
data class Spend(
|
||||
|
|
@ -25,9 +31,11 @@ internal sealed interface TangemPayTransactionState {
|
|||
override val onClick: () -> Unit,
|
||||
override val amount: String,
|
||||
override val amountColor: ColorReference,
|
||||
override val amountColorV2: ColorReference2,
|
||||
override val title: TextReference,
|
||||
override val subtitle: TextReference,
|
||||
override val icon: ImageReference,
|
||||
override val iconV2: TangemIconUM,
|
||||
override val time: String,
|
||||
) : Content
|
||||
|
||||
|
|
@ -36,9 +44,11 @@ internal sealed interface TangemPayTransactionState {
|
|||
override val onClick: () -> Unit,
|
||||
override val amount: String,
|
||||
override val amountColor: ColorReference,
|
||||
override val amountColorV2: ColorReference2,
|
||||
override val title: TextReference,
|
||||
override val subtitle: TextReference,
|
||||
override val icon: ImageReference,
|
||||
override val iconV2: TangemIconUM,
|
||||
override val time: String,
|
||||
) : Content
|
||||
|
||||
|
|
@ -47,9 +57,11 @@ internal sealed interface TangemPayTransactionState {
|
|||
override val onClick: () -> Unit,
|
||||
override val amount: String,
|
||||
override val amountColor: ColorReference,
|
||||
override val amountColorV2: ColorReference2,
|
||||
override val title: TextReference,
|
||||
override val subtitle: TextReference,
|
||||
override val icon: ImageReference,
|
||||
override val iconV2: TangemIconUM,
|
||||
override val time: String,
|
||||
) : Content
|
||||
|
||||
|
|
@ -58,9 +70,11 @@ internal sealed interface TangemPayTransactionState {
|
|||
override val onClick: () -> Unit,
|
||||
override val amount: String,
|
||||
override val amountColor: ColorReference,
|
||||
override val amountColorV2: ColorReference2,
|
||||
override val title: TextReference,
|
||||
override val subtitle: TextReference,
|
||||
override val icon: ImageReference,
|
||||
override val iconV2: TangemIconUM,
|
||||
override val time: String,
|
||||
) : Content
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Immutable
|
||||
internal sealed interface TangemPayTxHistoryUM {
|
||||
|
||||
val isBalanceHidden: Boolean
|
||||
|
|
@ -11,6 +13,11 @@ internal sealed interface TangemPayTxHistoryUM {
|
|||
data class Loading(override val isBalanceHidden: Boolean) : TangemPayTxHistoryUM {
|
||||
val items = persistentListOf(
|
||||
TangemPayTxHistoryItemUM.Title,
|
||||
TangemPayTxHistoryItemUM.GroupTitle(
|
||||
title = GROUP_TITLE_LOADING_PLACEHOLDER,
|
||||
itemKey = GROUP_TITLE_LOADING_ITEM_KEY,
|
||||
isLoading = true,
|
||||
),
|
||||
TangemPayTxHistoryItemUM.Transaction(TangemPayTransactionState.Loading("LOADING_TX_HASH_1")),
|
||||
TangemPayTxHistoryItemUM.Transaction(TangemPayTransactionState.Loading("LOADING_TX_HASH_2")),
|
||||
TangemPayTxHistoryItemUM.Transaction(TangemPayTransactionState.Loading("LOADING_TX_HASH_3")),
|
||||
|
|
@ -35,11 +42,22 @@ internal sealed interface TangemPayTxHistoryUM {
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface TangemPayTxHistoryItemUM {
|
||||
data object Title : TangemPayTxHistoryItemUM
|
||||
data class GroupTitle(val title: String, val itemKey: String) : TangemPayTxHistoryItemUM {
|
||||
data class GroupTitle(
|
||||
val title: String,
|
||||
val itemKey: String,
|
||||
val isLoading: Boolean = false,
|
||||
) : TangemPayTxHistoryItemUM {
|
||||
val legacyGroupTitle = TxHistoryState.TxHistoryItemState.GroupTitle(title = title, itemKey = itemKey)
|
||||
}
|
||||
data class Transaction(val transaction: TangemPayTransactionState) : TangemPayTxHistoryItemUM
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Placeholder copy for [TextShimmer] height; real date labels vary in width but not in line metrics. */
|
||||
const val GROUP_TITLE_LOADING_PLACEHOLDER: String = "Today"
|
||||
const val GROUP_TITLE_LOADING_ITEM_KEY: String = "loading-group-title"
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import com.tangem.features.tangempay.components.txHistory.DefaultTangemPayTxHistoryComponent
|
||||
import com.tangem.features.tangempay.entity.TangemPayTxHistoryUM
|
||||
import com.tangem.features.tangempay.utils.TangemPayTxHistoryListManager
|
||||
|
|
@ -25,6 +26,7 @@ internal class TangemPayTxHistoryModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val txHistoryUpdateListener: TangemPayTxHistoryUpdateListener,
|
||||
private val featureToggles: TangemPayFeatureToggles,
|
||||
) : Model() {
|
||||
|
||||
private val params: DefaultTangemPayTxHistoryComponent.Params = paramsContainer.require()
|
||||
|
|
@ -44,6 +46,8 @@ internal class TangemPayTxHistoryModel @Inject constructor(
|
|||
subscribeToUpdateListener()
|
||||
}
|
||||
|
||||
fun isRedesignEnabled(): Boolean = featureToggles.isRedesignEnabled
|
||||
|
||||
private fun launchPagination() {
|
||||
modelScope.launch { listManager.launchPagination(params.userWalletId) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.tangempay.model.transformers
|
||||
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.extensions.ImageReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -7,6 +8,9 @@ import com.tangem.core.ui.extensions.themedColor
|
|||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_arrow_down_24
|
||||
import com.tangem.core.ui.res.generated.icons.ic_arrow_up_24
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
|
|
@ -33,6 +37,7 @@ internal class TangemPayTxHistoryItemsConverter(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun convertSpend(spend: TangemPayTxHistoryItem.Spend): TangemPayTransactionState.Content.Spend {
|
||||
val localDate = spend.date.withZone(DateTimeZone.getDefault())
|
||||
val amountPrefix = when {
|
||||
|
|
@ -59,11 +64,29 @@ internal class TangemPayTxHistoryItemsConverter(
|
|||
else -> TangemTheme.colors.text.primary1
|
||||
}
|
||||
},
|
||||
amountColorV2 = {
|
||||
if (spend.status == TangemPayTxHistoryItem.Status.DECLINED) {
|
||||
TangemTheme.colors3.text.status.error
|
||||
} else {
|
||||
TangemTheme.colors3.text.primary
|
||||
}
|
||||
},
|
||||
title = stringReference(spend.enrichedMerchantName ?: spend.merchantName),
|
||||
subtitle = paySpendSubtitleConverter.convert(spend),
|
||||
time = DateTimeFormatters.formatDate(localDate, DateTimeFormatters.timeFormatter),
|
||||
icon = spend.enrichedMerchantIconUrl?.let(ImageReference::Url)
|
||||
?: ImageReference.Res(R.drawable.ic_category_24),
|
||||
iconV2 = if (spend.enrichedMerchantIconUrl != null) {
|
||||
TangemIconUM.Url(
|
||||
url = spend.enrichedMerchantIconUrl,
|
||||
fallbackRes = R.drawable.ic_category_24,
|
||||
)
|
||||
} else {
|
||||
TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_category_24,
|
||||
tintReference = { TangemTheme.colors3.icon.secondary },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -76,10 +99,15 @@ internal class TangemPayTxHistoryItemsConverter(
|
|||
onClick = { txHistoryUiActions.onTransactionClick(payment) },
|
||||
amount = amount,
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors3.text.primary },
|
||||
title = resourceReference(R.string.tangem_pay_withdrawal),
|
||||
subtitle = stringReference("Transfers"),
|
||||
time = DateTimeFormatters.formatDate(payment.date, DateTimeFormatters.timeFormatter),
|
||||
icon = ImageReference.Res(R.drawable.ic_arrow_up_24),
|
||||
iconV2 = TangemIconUM.Icon(
|
||||
imageVector = Icons.ic_arrow_up_24,
|
||||
tintReference = { TangemTheme.colors3.icon.secondary },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -93,10 +121,16 @@ internal class TangemPayTxHistoryItemsConverter(
|
|||
onClick = { txHistoryUiActions.onTransactionClick(fee) },
|
||||
amount = amount,
|
||||
amountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
amountColorV2 = { TangemTheme.colors3.text.primary },
|
||||
title = resourceReference(R.string.tangem_pay_fee_title),
|
||||
subtitle = fee.description?.let(::stringReference) ?: resourceReference(R.string.tangem_pay_fee_subtitle),
|
||||
icon = ImageReference.Res(R.drawable.ic_percent_24),
|
||||
time = DateTimeFormatters.formatDate(fee.date, DateTimeFormatters.timeFormatter),
|
||||
iconV2 = TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_percent_24,
|
||||
tintReference = { TangemTheme.colors3.icon.secondary },
|
||||
),
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +153,7 @@ internal class TangemPayTxHistoryItemsConverter(
|
|||
TangemPayTxHistoryItem.Type.Deposit -> themedColor { TangemTheme.colors.text.accent }
|
||||
TangemPayTxHistoryItem.Type.Withdrawal -> themedColor { TangemTheme.colors.text.primary1 }
|
||||
},
|
||||
amountColorV2 = { TangemTheme.colors3.text.primary },
|
||||
title = when (collateral.type) {
|
||||
TangemPayTxHistoryItem.Type.Deposit -> resourceReference(R.string.tangem_pay_deposit)
|
||||
TangemPayTxHistoryItem.Type.Withdrawal -> resourceReference(R.string.tangem_pay_withdrawal)
|
||||
|
|
@ -129,6 +164,13 @@ internal class TangemPayTxHistoryItemsConverter(
|
|||
TangemPayTxHistoryItem.Type.Withdrawal -> ImageReference.Res(R.drawable.ic_arrow_up_24)
|
||||
},
|
||||
time = DateTimeFormatters.formatDate(collateral.date, DateTimeFormatters.timeFormatter),
|
||||
iconV2 = TangemIconUM.Icon(
|
||||
imageVector = when (collateral.type) {
|
||||
TangemPayTxHistoryItem.Type.Deposit -> Icons.ic_arrow_down_24
|
||||
TangemPayTxHistoryItem.Type.Withdrawal -> Icons.ic_arrow_up_24
|
||||
},
|
||||
tintReference = { TangemTheme.colors3.icon.secondary },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -128,7 +128,19 @@ private fun TangemPayTxHistoryListItem(
|
|||
) {
|
||||
when (state) {
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.GroupTitle -> {
|
||||
TxHistoryGroupTitle(config = state.legacyGroupTitle, modifier = modifier)
|
||||
if (state.isLoading) {
|
||||
RectangleShimmer(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
)
|
||||
.height(TangemTheme.dimens.size12),
|
||||
)
|
||||
} else {
|
||||
TxHistoryGroupTitle(config = state.legacyGroupTitle, modifier = modifier)
|
||||
}
|
||||
}
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Title -> {
|
||||
TangemPayTxHistoryTitle(modifier = modifier)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,410 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.list.InfiniteListHandler
|
||||
import com.tangem.core.ui.ds.image.TangemIcon
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.row.TangemRow
|
||||
import com.tangem.core.ui.ds2.row.TangemRowText
|
||||
import com.tangem.core.ui.ds2.row.TangemRowTextRole
|
||||
import com.tangem.core.ui.ds2.row.TangemRowVerticalAlignment
|
||||
import com.tangem.core.ui.ds2.shimmers.RectangleShimmer
|
||||
import com.tangem.core.ui.ds2.shimmers.TextShimmer
|
||||
import com.tangem.core.ui.ds2.shimmers.TextShimmerStyle
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_arrow_refresh_20
|
||||
import com.tangem.core.ui.res.generated.icons.ic_binoculars_20
|
||||
import com.tangem.core.ui.test.EmptyTransactionBlockTestTags
|
||||
import com.tangem.features.tangempay.entity.TangemPayEmptyTransactionHistoryStateV2
|
||||
import com.tangem.features.tangempay.entity.TangemPayTransactionState
|
||||
import com.tangem.features.tangempay.entity.TangemPayTxHistoryUM
|
||||
|
||||
private const val LOAD_ITEMS_BUFFER = 20
|
||||
|
||||
internal fun LazyListScope.tangemPayTxHistoryItemsV2(listState: LazyListState, state: TangemPayTxHistoryUM) {
|
||||
when (state) {
|
||||
is TangemPayTxHistoryUM.Content -> {
|
||||
contentItems(listState = listState, state = state)
|
||||
}
|
||||
is TangemPayTxHistoryUM.Empty -> {
|
||||
nonContentItem(listState = listState, state = TangemPayEmptyTransactionHistoryStateV2.Empty)
|
||||
}
|
||||
is TangemPayTxHistoryUM.Error -> {
|
||||
nonContentItem(
|
||||
listState = listState,
|
||||
state = TangemPayEmptyTransactionHistoryStateV2.FailedToLoad(onReload = state.onReload),
|
||||
)
|
||||
}
|
||||
is TangemPayTxHistoryUM.Loading -> {
|
||||
loadingItems(state = state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.nonContentItem(
|
||||
listState: LazyListState,
|
||||
state: TangemPayEmptyTransactionHistoryStateV2,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val itemKey = state::class.java
|
||||
item(key = itemKey, contentType = itemKey) {
|
||||
val fillRemaining = Modifier.heightIn(min = rememberRemainingViewportHeight(listState, itemKey))
|
||||
when (state) {
|
||||
is TangemPayEmptyTransactionHistoryStateV2.Empty -> {
|
||||
TangemPayTransactionEmptyBlock(
|
||||
state = state,
|
||||
modifier = modifier
|
||||
.then(fillRemaining)
|
||||
.padding(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x3)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
is TangemPayEmptyTransactionHistoryStateV2.FailedToLoad -> {
|
||||
TangemPayFailedTransactionBlock(
|
||||
state = state,
|
||||
modifier = modifier
|
||||
.then(fillRemaining)
|
||||
.padding(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x3)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.contentItems(listState: LazyListState, state: TangemPayTxHistoryUM.Content) {
|
||||
itemsIndexed(
|
||||
items = state.items,
|
||||
key = { index, item ->
|
||||
when (item) {
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.GroupTitle -> item.itemKey
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Title -> index + item.hashCode()
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction ->
|
||||
item.transaction.id + (item.transaction as? TangemPayTransactionState.Content)?.hashCode()
|
||||
}
|
||||
},
|
||||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { _, item ->
|
||||
TangemPayTxHistoryListItem(
|
||||
state = item,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
)
|
||||
},
|
||||
)
|
||||
item {
|
||||
InfiniteListHandler(
|
||||
listState = listState,
|
||||
buffer = LOAD_ITEMS_BUFFER,
|
||||
onLoadMore = state.loadMore,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.loadingItems(state: TangemPayTxHistoryUM.Loading) {
|
||||
itemsIndexed(
|
||||
items = state.items,
|
||||
key = { _, item ->
|
||||
when (item) {
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.GroupTitle -> item.itemKey
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Title -> item.hashCode()
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction ->
|
||||
item.transaction.id + (item.transaction as? TangemPayTransactionState.Content)?.hashCode()
|
||||
}
|
||||
},
|
||||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { _, item ->
|
||||
TangemPayTxHistoryListItem(
|
||||
state = item,
|
||||
isBalanceHidden = true,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemPayTxHistoryListItem(
|
||||
state: TangemPayTxHistoryUM.TangemPayTxHistoryItemUM,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (state) {
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.GroupTitle -> GroupTitleBlock(state, modifier)
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Title -> Unit
|
||||
is TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.Transaction -> {
|
||||
TangemPayTransaction(
|
||||
transactionState = state.transaction,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GroupTitleBlock(
|
||||
state: TangemPayTxHistoryUM.TangemPayTxHistoryItemUM.GroupTitle,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (state.isLoading) {
|
||||
TextShimmer(
|
||||
modifier = modifier.width(TangemTheme.dimens2.x10),
|
||||
text = state.title,
|
||||
style = TextShimmerStyle.SUBHEADING,
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens2.x3,
|
||||
horizontal = TangemTheme.dimens2.x4,
|
||||
),
|
||||
text = state.legacyGroupTitle.title,
|
||||
style = TangemTheme.typography3.subheading.medium,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemPayTransaction(
|
||||
transactionState: TangemPayTransactionState,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TangemRow(
|
||||
verticalAlignment = TangemRowVerticalAlignment.Center,
|
||||
modifier = modifier.clickable(
|
||||
enabled = transactionState is TangemPayTransactionState.Content,
|
||||
onClick = (transactionState as? TangemPayTransactionState.Content)?.onClick ?: {},
|
||||
),
|
||||
startSlot = { Icon(state = transactionState, modifier = Modifier.size(TangemTheme.dimens2.x10)) },
|
||||
titleSlot = { Title(state = transactionState) },
|
||||
subtitleSlot = { Subtitle(state = transactionState) },
|
||||
valueSlot = { Amount(state = transactionState, isBalanceHidden = isBalanceHidden) },
|
||||
subvalueSlot = { Timestamp(state = transactionState) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Icon(state: TangemPayTransactionState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TangemPayTransactionState.Content -> TransactionListIcon(
|
||||
iconState = state.iconV2,
|
||||
modifier = modifier,
|
||||
)
|
||||
is TangemPayTransactionState.Loading -> RectangleShimmer(
|
||||
modifier = modifier.size(TangemTheme.dimens2.x10),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TransactionListIcon(iconState: TangemIconUM, modifier: Modifier = Modifier) {
|
||||
when (iconState) {
|
||||
is TangemIconUM.Url -> {
|
||||
TangemIcon(
|
||||
tangemIconUM = iconState,
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens2.x10)
|
||||
.clip(CircleShape),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens2.x10)
|
||||
.clip(CircleShape)
|
||||
.background(TangemTheme.colors3.bg.opaque.primary),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
TangemIcon(
|
||||
tangemIconUM = iconState,
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Title(state: TangemPayTransactionState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TangemPayTransactionState.Content -> {
|
||||
TangemRowText(text = state.title.resolveReference(), role = TangemRowTextRole.Title)
|
||||
}
|
||||
is TangemPayTransactionState.Loading -> {
|
||||
TextShimmer(
|
||||
modifier = modifier,
|
||||
text = "Transfer",
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
style = TextShimmerStyle.BODY,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Subtitle(state: TangemPayTransactionState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TangemPayTransactionState.Content -> {
|
||||
TangemRowText(text = state.subtitle.resolveReference(), role = TangemRowTextRole.Subtitle)
|
||||
}
|
||||
is TangemPayTransactionState.Loading -> {
|
||||
TextShimmer(
|
||||
modifier = modifier,
|
||||
text = "Transfer",
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
style = TextShimmerStyle.CAPTION,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Amount(state: TangemPayTransactionState, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TangemPayTransactionState.Content -> {
|
||||
Text(
|
||||
text = state.amount.orMaskWithStars(isBalanceHidden),
|
||||
modifier = modifier,
|
||||
textAlign = TextAlign.End,
|
||||
color = state.amountColorV2(),
|
||||
style = TangemTheme.typography3.body.medium,
|
||||
)
|
||||
}
|
||||
is TangemPayTransactionState.Loading -> {
|
||||
TextShimmer(
|
||||
modifier = modifier,
|
||||
text = "10000",
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
style = TextShimmerStyle.BODY,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Timestamp(state: TangemPayTransactionState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TangemPayTransactionState.Content -> {
|
||||
TangemRowText(text = state.time, role = TangemRowTextRole.Subvalue)
|
||||
}
|
||||
is TangemPayTransactionState.Loading -> {
|
||||
TextShimmer(
|
||||
modifier = modifier,
|
||||
text = "00:00",
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
style = TextShimmerStyle.CAPTION,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemPayTransactionEmptyBlock(
|
||||
state: TangemPayEmptyTransactionHistoryStateV2.Empty,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.testTag(EmptyTransactionBlockTestTags.BLOCK),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x3, Alignment.CenterVertically),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x10)
|
||||
.background(
|
||||
color = TangemTheme.colors3.bg.opaque.primary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.padding(10.dp)
|
||||
.testTag(EmptyTransactionBlockTestTags.ICON),
|
||||
imageVector = Icons.ic_binoculars_20,
|
||||
tint = TangemTheme.colors3.icon.secondary,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing32)
|
||||
.testTag(EmptyTransactionBlockTestTags.TEXT),
|
||||
textAlign = TextAlign.Center,
|
||||
text = state.text.resolveReference(),
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemPayFailedTransactionBlock(
|
||||
state: TangemPayEmptyTransactionHistoryStateV2.FailedToLoad,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.testTag(EmptyTransactionBlockTestTags.BLOCK),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x3, Alignment.CenterVertically),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_arrow_refresh_20),
|
||||
onClick = state.onReload,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.testTag(EmptyTransactionBlockTestTags.TEXT),
|
||||
textAlign = TextAlign.Center,
|
||||
text = state.text.resolveReference(),
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the height left between the top of the item identified by [itemKey] and the bottom of the
|
||||
* list's viewport (excluding bottom content padding). Returns `0.dp` until the item has been laid out.
|
||||
*
|
||||
* The item's own height does not affect its offset (only the items above it do), so reading the offset
|
||||
* back to size the item is stable and does not loop.
|
||||
*/
|
||||
@Composable
|
||||
private fun rememberRemainingViewportHeight(listState: LazyListState, itemKey: Any): Dp {
|
||||
val density = LocalDensity.current
|
||||
val remainingPx by remember(listState, itemKey) {
|
||||
derivedStateOf {
|
||||
val info = listState.layoutInfo
|
||||
val item = info.visibleItemsInfo.firstOrNull { it.key == itemKey }
|
||||
?: return@derivedStateOf 0
|
||||
(info.viewportEndOffset - info.afterContentPadding - item.offset).coerceAtLeast(minimumValue = 0)
|
||||
}
|
||||
}
|
||||
return with(density) { remainingPx.toDp() }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue