Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-18 18:43:36 +05:00
parent da6ee825ab
commit 2f5d6525e2
11 changed files with 362 additions and 15 deletions

View file

@ -31,10 +31,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.containers.FooterContainer
import com.tangem.core.ui.components.inputrow.InputRowDefault
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.ImmutableList
@ -118,7 +115,7 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh
@Composable
private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest) {
FooterContainer(
footer = stringResource(id = R.string.give_permission_policy_type_footer),
footer = resourceReference(R.string.give_permission_policy_type_footer),
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
) {
AmountItem(
@ -130,7 +127,7 @@ private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest)
}
SpacerH16()
FooterContainer(
footer = data.footerText.resolveReference(),
footer = data.footerText,
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
) {
FeeItem(fee = data.fee)

View file

@ -0,0 +1,228 @@
package com.tangem.common.ui.expressStatus
import androidx.annotation.DrawableRes
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.common.ui.R
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.persistentListOf
/**
* Block with express statuses
*
* @param state ui holder
* @param modifier modifier
* @see [Figma](https://www.figma.com/design/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?node-id=18459-26521&t=4jox7bfqUiXnm2h1-4)
*/
@Composable
fun ExpressStatusBlock(state: ExpressStatusUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
.padding(
vertical = TangemTheme.dimens.spacing14,
horizontal = TangemTheme.dimens.spacing12,
),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(bottom = TangemTheme.dimens.spacing16),
) {
Text(
text = state.title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
SpacerWMax()
AnimatedVisibility(visible = state.link is ExpressLinkUM.Content) {
val link = remember(this) { state.link as ExpressLinkUM.Content }
Row(
modifier = Modifier.clickable { link.onClick() },
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = painterResource(id = link.icon),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
modifier = Modifier
.size(TangemTheme.dimens.spacing16)
.padding(end = TangemTheme.dimens.spacing2),
)
Text(
text = link.text.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
Column {
state.statuses.forEachIndexed { index, item ->
ExpressStatusStep(item, index == state.statuses.lastIndex)
}
}
}
}
@Composable
private fun ExpressStatusStep(status: ExpressStatusItemUM, isLast: Boolean) {
AnimatedContent(
targetState = status,
label = "Exchange Step Change Success",
transitionSpec = {
fadeIn(tween(durationMillis = 220)) togetherWith
fadeOut(tween(durationMillis = 220))
},
) { content ->
Row {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
when (content.state) {
ExpressStatusItemState.Active -> StepInProgress()
ExpressStatusItemState.Default -> StepDefault()
ExpressStatusItemState.Done -> Step(
iconRes = R.drawable.ic_check_24,
iconColor = TangemTheme.colors.icon.primary1,
borderColor = TangemTheme.colors.field.focused,
)
ExpressStatusItemState.Error -> Step(
iconRes = R.drawable.ic_close_24,
iconColor = TangemTheme.colors.icon.warning,
)
ExpressStatusItemState.Warning -> Step(
iconRes = R.drawable.ic_close_24,
iconColor = TangemTheme.colors.icon.attention,
)
}
if (!isLast) {
StepSeparator()
}
}
val textColor = when (status.state) {
ExpressStatusItemState.Active -> TangemTheme.colors.text.primary1
ExpressStatusItemState.Default -> TangemTheme.colors.text.disabled
ExpressStatusItemState.Done -> TangemTheme.colors.text.primary1
ExpressStatusItemState.Error -> TangemTheme.colors.text.warning
ExpressStatusItemState.Warning -> TangemTheme.colors.text.attention
}
Text(
text = content.text.resolveReference(),
style = TangemTheme.typography.body2,
color = textColor,
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing12),
)
}
}
}
@Composable
private fun StepDefault() {
Box(
modifier = Modifier
.size(TangemTheme.dimens.size20)
.border(
width = TangemTheme.dimens.size1_5,
color = TangemTheme.colors.field.focused,
shape = CircleShape,
)
.padding(TangemTheme.dimens.spacing2),
)
}
@Composable
private fun Step(iconColor: Color, @DrawableRes iconRes: Int, borderColor: Color = iconColor) {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = iconColor,
modifier = Modifier
.size(TangemTheme.dimens.size20)
.border(
width = TangemTheme.dimens.size1_5,
color = borderColor,
shape = CircleShape,
)
.padding(TangemTheme.dimens.spacing2),
)
}
@Composable
private fun StepInProgress() {
CircularProgressIndicator(
color = TangemTheme.colors.icon.primary1,
strokeWidth = TangemTheme.dimens.size2,
modifier = Modifier
.padding(TangemTheme.dimens.spacing2)
.size(TangemTheme.dimens.size14),
)
}
@Composable
private fun StepSeparator() {
Box(
modifier = Modifier
.padding(vertical = TangemTheme.dimens.spacing2)
.size(
width = TangemTheme.dimens.size1_5,
height = TangemTheme.dimens.size10,
)
.background(
color = TangemTheme.colors.field.focused,
shape = CircleShape,
),
)
}
@Preview
@Composable
private fun Preview_ExchangeStatusBlock() {
val state = ExpressStatusUM(
title = resourceReference(R.string.express_exchange_status_title),
link = ExpressLinkUM.Content(
icon = R.drawable.ic_alert_24,
text = resourceReference(R.string.common_go_to_provider),
onClick = {},
),
statuses = persistentListOf(
ExpressStatusItemUM(text = stringReference("Done"), state = ExpressStatusItemState.Done),
ExpressStatusItemUM(text = stringReference("Active"), state = ExpressStatusItemState.Active),
ExpressStatusItemUM(text = stringReference("Warning"), state = ExpressStatusItemState.Warning),
ExpressStatusItemUM(text = stringReference("Error"), state = ExpressStatusItemState.Error),
ExpressStatusItemUM(text = stringReference("Default"), state = ExpressStatusItemState.Default),
),
)
TangemThemePreview {
ExpressStatusBlock(state = state)
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.common.ui.expressStatus
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.notifications.ExpressNotificationsUM
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.res.TangemTheme
@Composable
fun ExpressStatusNotificationBlock(state: NotificationUM?) {
AnimatedContent(
targetState = state,
modifier = Modifier.padding(top = 12.dp),
label = "Express Status Notification Change",
) { notification ->
if (notification?.config != null) {
Notification(
config = notification.config,
iconTint = when (state) {
is ExpressNotificationsUM.NeedVerification -> TangemTheme.colors.icon.attention
is ExpressNotificationsUM.FailedByProvider -> TangemTheme.colors.icon.warning
else -> null
},
containerColor = TangemTheme.colors.background.action,
)
}
}
}

View file

@ -0,0 +1,54 @@
package com.tangem.common.ui.expressStatus.state
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Stable
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
/**
* UI data holder for express status block
*
* @property title block title
* @property link provider web link
* @property statuses list of possible and active statuses
*/
data class ExpressStatusUM(
val title: TextReference,
val link: ExpressLinkUM,
val statuses: ImmutableList<ExpressStatusItemUM>,
)
/**
* Provider web link for express status block.
* [Empty] if no link needed
* [Content] if link is provided and displayed
*/
@Stable
sealed class ExpressLinkUM {
data object Empty : ExpressLinkUM()
data class Content(
@DrawableRes val icon: Int,
val text: TextReference,
val onClick: () -> Unit,
) : ExpressLinkUM()
}
/**
* Single status item in express status block
*/
data class ExpressStatusItemUM(
val text: TextReference,
val state: ExpressStatusItemState,
)
/**
* Available status states for express status block
*/
enum class ExpressStatusItemState {
Active,
Default,
Done,
Warning,
Error,
;
}

View file

@ -0,0 +1,28 @@
package com.tangem.common.ui.notifications
import com.tangem.common.ui.R
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.resourceReference
object ExpressNotificationsUM {
data class NeedVerification(val onGoToProviderClick: () -> Unit) : NotificationUM.Warning(
title = resourceReference(R.string.express_exchange_notification_verification_title),
subtitle = resourceReference(R.string.express_exchange_notification_verification_text),
iconResId = R.drawable.ic_alert_triangle_20,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.common_go_to_provider),
onClick = onGoToProviderClick,
),
)
data class FailedByProvider(val onGoToProviderClick: () -> Unit) : NotificationUM.Error(
title = resourceReference(R.string.express_exchange_notification_failed_title),
subtitle = resourceReference(R.string.express_exchange_notification_failed_text),
iconResId = R.drawable.ic_alert_circle_24,
buttonState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.common_go_to_provider),
onClick = onGoToProviderClick,
),
)
}

View file

@ -5,8 +5,11 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
@ -20,15 +23,16 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun FooterContainer(
modifier: Modifier = Modifier,
footer: String? = null,
footer: TextReference? = null,
footerTopPadding: Dp = TangemTheme.dimens.spacing8,
content: @Composable () -> Unit,
) {
Column(modifier = modifier) {
content()
AnimatedVisibility(visible = footer != null) {
val footerWrapped = remember(this) { requireNotNull(footer) }
Text(
text = footer.orEmpty(),
text = footerWrapped.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier

View file

@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.inputrow.InputRowEnterAmount
import com.tangem.core.ui.components.inputrow.InputRowEnterInfoAmount
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.impl.presentation.state.fee.FeeType
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
@ -41,7 +40,7 @@ internal fun SendCustomFee(
repeat(customValues.size) { index ->
val value = customValues[index]
FooterContainer(
footer = value.footer.resolveReference(),
footer = value.footer,
) {
if (value.label != null) {
InputRowEnterInfoAmount(

View file

@ -21,8 +21,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.containers.FooterContainer
import com.tangem.core.ui.components.inputrow.InputRowRecipient
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.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.send.impl.R
@ -32,8 +36,6 @@ import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData
import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub
import com.tangem.core.ui.components.containers.FooterContainer
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import kotlinx.collections.immutable.ImmutableList
@ -107,7 +109,7 @@ private fun LazyListScope.addressItem(
) {
item(key = ADDRESS_FIELD_KEY) {
FooterContainer(
footer = stringResource(R.string.send_recipient_address_footer, network),
footer = resourceReference(R.string.send_recipient_address_footer, wrappedList(network)),
) {
InputRowRecipient(
value = address.value,
@ -137,7 +139,7 @@ private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onM
value = memoField.value,
label = memoField.label,
placeholder = placeholder,
footer = stringResource(R.string.send_recipient_memo_footer),
footer = resourceReference(R.string.send_recipient_memo_footer),
onValueChange = memoField.onValueChange,
onPasteClick = onMemoChange,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing20),

View file

@ -27,7 +27,7 @@ internal fun TextFieldWithPaste(
onValueChange: (String) -> Unit,
onPasteClick: (String) -> Unit,
modifier: Modifier = Modifier,
footer: String? = null,
footer: TextReference? = null,
labelStyle: TextStyle = TangemTheme.typography.body2,
error: TextReference? = null,
isError: Boolean = false,

View file

@ -19,6 +19,7 @@ internal sealed interface ExchangeStatusNotifications {
sealed class CommonNotification(val config: NotificationConfig) : ExchangeStatusNotifications
@Deprecated("Use one in ExpressNotificationsUM")
data class NeedVerification(val onGoToProviderClick: () -> Unit) : CommonNotification(
config = NotificationConfig(
title = resourceReference(R.string.express_exchange_notification_verification_title),
@ -31,6 +32,7 @@ internal sealed interface ExchangeStatusNotifications {
),
)
@Deprecated("Use one in ExpressNotificationsUM")
data class Failed(val onGoToProviderClick: () -> Unit) : CommonNotification(
config = NotificationConfig(
title = resourceReference(R.string.express_exchange_notification_failed_title),

View file

@ -30,6 +30,7 @@ import com.tangem.features.tokendetails.impl.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@Deprecated("Use ExpressStatusBlock from common")
@Composable
internal fun ExchangeStatusBlock(
statuses: ImmutableList<ExchangeStatusState>,