diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 636d609568..8730764f58 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -47,6 +47,7 @@ Принять Добавить Применить + Одобрение Внимание Баланс: %s биометрическую аутентификацию @@ -74,7 +75,9 @@ Начать Отправить Успешно + Обмен условия участия + Перевод Я понял Да Адрес контракта скопирован! @@ -386,7 +389,6 @@ Безлимитно Открыть в обозревателе В процессе - Обмен Обменять Обмен %s на Котировки включают дополнительную комиссию Tangem в размере %s. Это помогает нам предоставлять первоклассный продукт. diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index 08f4e9e68b..f9ce322037 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -69,6 +69,7 @@ 開始 提交 成功 + 交換 條款和條件 我了解 @@ -367,7 +368,6 @@ 要繼續,您需要允許 1inch 智能合約使用您的 %s 在瀏覽器中查看 進行中 - 交換 交易 交易 %s 至 此外,報價包括%s的 Tangem 費用。這有助於我們提供一流的產品 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index af04c2fd30..0c933df75f 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -45,6 +45,7 @@ Accept Add Apply + Approval Attention Balance: %s biometric authentication @@ -72,7 +73,9 @@ Start Submit Success + Swap terms and conditions + Transfer I understand Yes Contract address copied! @@ -382,7 +385,6 @@ Unlimited View in Explorer In progress - Swap Swap Swap of %s to Quotes include an additional Tangem commission of %s. This helps us deliver a top-of-the-line product. diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 1dc31c8335..94bb016bdc 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -9,8 +9,10 @@ dependencies { implementation(deps.androidx.fragment.ktx) /** Compose */ + implementation(deps.compose.constraintLayout) implementation(deps.compose.foundation) implementation(deps.compose.material) + implementation(deps.compose.material3) implementation(deps.compose.ui.tooling) /** Other libraries */ diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt new file mode 100644 index 0000000000..0f265ecce9 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt @@ -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 Figma Component + * +[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( + 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, + ), +) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt new file mode 100644 index 0000000000..f0502a4a21 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt @@ -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 +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index c2fbac9891..421c82c9e1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -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, diff --git a/core/ui/src/main/res/drawable/ic_doc_24.xml b/core/ui/src/main/res/drawable/ic_doc_24.xml new file mode 100644 index 0000000000..8855153e59 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_doc_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index f5a39df29e..acad315b28 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -54,7 +54,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, onPermissionWarningClick: Column { AppBarWithBackButton( - text = stringResource(R.string.swapping_swap), + text = stringResource(R.string.common_swap), onBackClick = state.onBackClicked, iconRes = R.drawable.ic_close_24, ) @@ -348,7 +348,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U else -> { PrimaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.swapping_swap), + text = stringResource(id = R.string.common_swap), iconResId = R.drawable.ic_tangem_24, enabled = state.swapButton.enabled, showProgress = state.swapButton.loading, @@ -384,7 +384,7 @@ private val receiveCard = SwapCardData( coinId = "", ) -val stateSelectable = SelectableItemsState( +val stateSelectable = SelectableItemsState( selectedItem = Item( 0, TextReference.Str("Balance"), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt index d8ad3e646a..2d6f3e52b4 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt @@ -39,7 +39,7 @@ fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) { }, topBar = { AppBarWithBackButton( - text = stringResource(R.string.swapping_swap), + text = stringResource(R.string.common_swap), onBackClick = onBack, iconRes = R.drawable.ic_close_24, )