Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-19 16:01:49 +08:00
parent 53100fe46a
commit e22137c544
10 changed files with 521 additions and 7 deletions

View file

@ -0,0 +1,342 @@
package com.tangem.core.ui.components.transactions
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
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 androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
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.res.TangemTheme
/**
* Transaction component
*
* @param state state
*
* @see <a href = "https://www.figma.com/file/RU7AIgwHtGdMfy83T5UOoR/iOS?type=design&node-id=446-438&t=71jPDxMMk4e0
* a025-4">Figma Component</a>
*
[REDACTED_AUTHOR]
*/
@Composable
fun Transaction(state: TransactionState) {
Surface(
modifier = Modifier
.background(TangemTheme.colors.background.primary)
.defaultMinSize(minHeight = TangemTheme.dimens.size56)
.padding(horizontal = TangemTheme.dimens.spacing12, vertical = TangemTheme.dimens.spacing10),
color = TangemTheme.colors.background.primary,
) {
@Suppress("DestructuringDeclarationWithTooManyEntries")
ConstraintLayout(modifier = Modifier.fillMaxWidth()) {
val (iconItem, titleItem, subtitleItem, amountItem, timestampItem) = createRefs()
Icon(
state = state,
modifier = Modifier
.size(TangemTheme.dimens.size40)
.constrainAs(iconItem) {
start.linkTo(parent.start)
centerVerticallyTo(parent)
},
)
Title(
state = state,
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing12)
.constrainAs(titleItem) {
start.linkTo(iconItem.end)
top.linkTo(iconItem.top)
},
)
Subtitle(
state = state,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing6)
.padding(horizontal = TangemTheme.dimens.spacing12)
.constrainAs(subtitleItem) {
start.linkTo(iconItem.end)
top.linkTo(amountItem.bottom)
end.linkTo(timestampItem.start)
width = Dimension.fillToConstraints
},
)
Amount(
state = state,
modifier = Modifier.constrainAs(amountItem) {
start.linkTo(titleItem.end)
top.linkTo(titleItem.top)
end.linkTo(parent.end)
width = Dimension.fillToConstraints
},
)
Timestamp(
state = state,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing6)
.constrainAs(timestampItem) {
top.linkTo(amountItem.bottom)
end.linkTo(parent.end)
},
)
}
}
}
@Composable
private fun Icon(state: TransactionState, modifier: Modifier = Modifier) {
when (state) {
is TransactionState.Content -> {
Box(
modifier = modifier
.size(TangemTheme.dimens.size40)
.background(
color = when (state) {
is TransactionState.ProcessedTransactionContent -> {
TangemTheme.colors.icon.attention.copy(alpha = 0.1f)
}
is TransactionState.CompletedTransactionContent -> {
TangemTheme.colors.background.secondary
}
},
shape = CircleShape,
),
) {
Icon(
painter = painterResource(
id = when (state) {
is TransactionState.Sending,
is TransactionState.Send,
-> R.drawable.ic_arrow_up_24
is TransactionState.Receiving,
is TransactionState.Receive,
-> R.drawable.ic_arrow_down_24
is TransactionState.Approving,
is TransactionState.Approved,
-> R.drawable.ic_doc_24
is TransactionState.Swapping,
is TransactionState.Swapped,
-> R.drawable.ic_exchange_vertical_24
},
),
contentDescription = null,
modifier = Modifier
.size(TangemTheme.dimens.size20)
.align(Alignment.Center),
tint = when (state) {
is TransactionState.ProcessedTransactionContent -> TangemTheme.colors.icon.attention
is TransactionState.CompletedTransactionContent -> TangemTheme.colors.icon.informative
},
)
}
}
is TransactionState.Loading -> {
CircleShimmer(modifier = modifier.size(TangemTheme.dimens.size40))
}
}
}
@Composable
private fun Title(state: TransactionState, modifier: Modifier = Modifier) {
when (state) {
is TransactionState.ProcessedTransactionContent -> {
Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) {
Text(
text = stringResource(
id = when (state) {
is TransactionState.Sending -> R.string.common_transfer
is TransactionState.Receiving -> R.string.common_transfer
is TransactionState.Approving -> R.string.common_approval
is TransactionState.Swapping -> R.string.common_swap
},
),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
)
Image(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = R.drawable.img_loader_15),
contentDescription = null,
)
}
}
is TransactionState.CompletedTransactionContent -> {
Text(
text = stringResource(
id = when (state) {
is TransactionState.Send -> R.string.common_transfer
is TransactionState.Receive -> R.string.common_transfer
is TransactionState.Approved -> R.string.common_approval
is TransactionState.Swapped -> R.string.common_swap
},
),
modifier = modifier,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
)
}
is TransactionState.Loading -> {
RectangleShimmer(
modifier = modifier.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12),
)
}
}
}
@Composable
private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
when (state) {
is TransactionState.Content -> {
Text(
text = when (state) {
is TransactionState.Sending -> "to: ${state.address}"
is TransactionState.Send -> "to: ${state.address}"
is TransactionState.Receiving -> "from: ${state.address}"
is TransactionState.Receive -> "from: ${state.address}"
is TransactionState.Approving -> "from: ${state.address}"
is TransactionState.Approved -> "from: ${state.address}"
is TransactionState.Swapping -> "contract: ${state.address}"
is TransactionState.Swapped -> "contract ${state.address}"
},
modifier = modifier,
textAlign = TextAlign.Start,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption,
)
}
is TransactionState.Loading -> {
RectangleShimmer(
modifier = modifier.size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12),
)
}
}
}
@Composable
private fun Amount(state: TransactionState, modifier: Modifier = Modifier) {
when (state) {
is TransactionState.Content -> {
Text(
text = state.amount,
modifier = modifier,
textAlign = TextAlign.End,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.body2,
)
}
is TransactionState.Loading -> {
RectangleShimmer(
modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
)
}
}
}
@Composable
private fun Timestamp(state: TransactionState, modifier: Modifier = Modifier) {
when (state) {
is TransactionState.Content -> {
Text(
text = state.timestamp,
modifier = modifier,
textAlign = TextAlign.End,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption,
)
}
is TransactionState.Loading -> {
RectangleShimmer(
modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
)
}
}
}
@Preview
@Composable
private fun Preview_TransactionItem_LightTheme(
@PreviewParameter(TransactionItemStateProvider::class) state: TransactionState,
) {
TangemTheme(isDark = false) {
Transaction(state)
}
}
@Preview
@Composable
private fun Preview_TransactionItem_DarkTheme(
@PreviewParameter(TransactionItemStateProvider::class) state: TransactionState,
) {
TangemTheme(isDark = true) {
Transaction(state)
}
}
private class TransactionItemStateProvider : CollectionPreviewParameterProvider<TransactionState>(
collection = listOf(
TransactionState.Sending(
address = "33BddS...ga2B",
amount = "-0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Receiving(
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Approving(
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Swapping(
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Send(
address = "33BddS...ga2B",
amount = "-0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Receive(
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Approved(
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Swapped(
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Loading,
),
)

View file

@ -0,0 +1,155 @@
package com.tangem.core.ui.components.transactions
/**
* Transaction component state
*
[REDACTED_AUTHOR]
*/
sealed interface TransactionState {
/**
* Content state
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
sealed class Content(
open val address: String,
open val amount: String,
open val timestamp: String,
) : TransactionState
/**
* Content state for processed transaction
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
sealed class ProcessedTransactionContent(
override val address: String,
override val amount: String,
override val timestamp: String,
) : Content(address, amount, timestamp)
/**
* Content state for completed transaction
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
sealed class CompletedTransactionContent(
override val address: String,
override val amount: String,
override val timestamp: String,
) : Content(address, amount, timestamp)
/**
* Processed sending transaction state
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
data class Sending(
override val address: String,
override val amount: String,
override val timestamp: String,
) : ProcessedTransactionContent(address, amount, timestamp)
/**
* Processed receiving transaction state
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
data class Receiving(
override val address: String,
override val amount: String,
override val timestamp: String,
) : ProcessedTransactionContent(address, amount, timestamp)
/**
* Processed approving transaction state
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
data class Approving(
override val address: String,
override val amount: String,
override val timestamp: String,
) : ProcessedTransactionContent(address, amount, timestamp)
/**
* Processed swapping transaction state
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
data class Swapping(
override val address: String,
override val amount: String,
override val timestamp: String,
) : ProcessedTransactionContent(address, amount, timestamp)
/**
* Completed sending transaction state
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
data class Send(
override val address: String,
override val amount: String,
override val timestamp: String,
) : CompletedTransactionContent(address, amount, timestamp)
/**
* Completed receiving transaction state
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
data class Receive(
override val address: String,
override val amount: String,
override val timestamp: String,
) : CompletedTransactionContent(address, amount, timestamp)
/**
* Completed approving transaction state
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
data class Approved(
override val address: String,
override val amount: String,
override val timestamp: String,
) : CompletedTransactionContent(address, amount, timestamp)
/**
* Completed swapping transaction state
*
* @property address address
* @property amount amount
* @property timestamp timestamp
*/
data class Swapped(
override val address: String,
override val amount: String,
override val timestamp: String,
) : CompletedTransactionContent(address, amount, timestamp)
/** Loading state */
object Loading : TransactionState
}

View file

@ -56,9 +56,11 @@ data class TangemDimens internal constructor(
val size46: Dp = 46.dp,
val size48: Dp = 48.dp,
val size50: Dp = 50.dp,
val size52: Dp = 52.dp,
val size56: Dp = 56.dp,
val size62: Dp = 62.dp,
val size68: Dp = 68.dp,
val size70: Dp = 70.dp,
val size72: Dp = 72.dp,
val size80: Dp = 80.dp,
val size84: Dp = 84.dp,

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M6,2C5.47,2 4.961,2.211 4.586,2.586C4.211,2.961 4,3.47 4,4V20C4,20.53 4.211,21.039 4.586,21.414C4.961,21.789 5.47,22 6,22H18C18.53,22 19.039,21.789 19.414,21.414C19.789,21.039 20,20.53 20,20V8L14,2H6ZM6,4H13V9H18V20H6V4ZM8,12V14H16V12H8ZM8,16V18H13V16H8Z"
android:fillColor="#000000" />
</vector>