Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-01 16:52:19 +02:00
parent 34ee61553d
commit aecd523c70
25 changed files with 1720 additions and 935 deletions

View file

@ -11,10 +11,12 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.features.tokenreceive.TokenReceiveComponent
import com.tangem.features.tokenreceive.model.TokenReceiveModel
import com.tangem.features.tokenreceive.route.TokenReceiveRoutes
import com.tangem.features.tokenreceive.ui.TokenReceiveContentSheet
import com.tangem.features.tokenreceive.ui.TokenReceiveContentSheetLegacy
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -43,13 +45,21 @@ internal class DefaultTokenReceiveComponent @AssistedInject constructor(
override fun BottomSheet() {
val content by contentStack.subscribeAsState()
val currentRoute = content.active.configuration
TokenReceiveContentSheet(
route = currentRoute,
onCloseClick = ::dismiss,
onBackClick = ::onChildBack,
contentStack = content,
)
if (LocalRedesignEnabled.current) {
TokenReceiveContentSheet(
route = currentRoute,
onCloseClick = ::dismiss,
onBackClick = ::onChildBack,
contentStack = content,
)
} else {
TokenReceiveContentSheetLegacy(
route = currentRoute,
onCloseClick = ::dismiss,
onBackClick = ::onChildBack,
contentStack = content,
)
}
}
override fun dismiss() = model.params.onDismiss()

View file

@ -9,10 +9,12 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.model.TokenReceiveAssetsModel
import com.tangem.features.tokenreceive.ui.TokenReceiveAssetsContent
import com.tangem.features.tokenreceive.ui.TokenReceiveAssetsContentLegacy
import kotlinx.collections.immutable.ImmutableList
internal class TokenReceiveAssetsComponent(
@ -25,7 +27,11 @@ internal class TokenReceiveAssetsComponent(
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
TokenReceiveAssetsContent(assetsUM = state)
if (LocalRedesignEnabled.current) {
TokenReceiveAssetsContent(assetsUM = state)
} else {
TokenReceiveAssetsContentLegacy(assetsUM = state)
}
}
internal interface TokenReceiveAssetsModelCallback {

View file

@ -7,12 +7,14 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.models.TokenReceiveType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.model.TokenReceiveQrCodeModel
import com.tangem.features.tokenreceive.ui.TokenReceiveQrCodeContent
import com.tangem.features.tokenreceive.ui.TokenReceiveQrCodeContentLegacy
internal class TokenReceiveQrCodeComponent(
appComponentContext: AppComponentContext,
@ -24,7 +26,11 @@ internal class TokenReceiveQrCodeComponent(
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
TokenReceiveQrCodeContent(qrCodeUM = state)
if (LocalRedesignEnabled.current) {
TokenReceiveQrCodeContent(qrCodeUM = state)
} else {
TokenReceiveQrCodeContentLegacy(qrCodeUM = state)
}
}
internal interface TokenReceiveQrCodeModelCallback {

View file

@ -8,9 +8,11 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.models.network.Network
import com.tangem.features.tokenreceive.model.TokenReceiveWarningModel
import com.tangem.features.tokenreceive.ui.TokenReceiveWarningContent
import com.tangem.features.tokenreceive.ui.TokenReceiveWarningContentLegacy
internal class TokenReceiveWarningComponent(
appComponentContext: AppComponentContext,
@ -22,7 +24,11 @@ internal class TokenReceiveWarningComponent(
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
TokenReceiveWarningContent(warningUM = state)
if (LocalRedesignEnabled.current) {
TokenReceiveWarningContent(warningUM = state)
} else {
TokenReceiveWarningContentLegacy(warningUM = state)
}
}
internal interface TokenReceiveWarningModelCallback {

View file

@ -12,6 +12,7 @@ internal data class ReceiveAddress(
sealed interface Type {
data object Ens : Type
@Immutable
sealed interface Primary : Type {
val displayName: TextReference

View file

@ -1,97 +1,379 @@
package com.tangem.features.tokenreceive.ui
import android.content.res.Configuration
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.runtime.rememberCoroutineScope
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.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.res.getStringSafe
import com.tangem.core.ui.R
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.components.buttons.actions.ActionBaseButton
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.buttons.actions.ActionButtonContent
import com.tangem.core.ui.components.buttons.small.TangemIconButton
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.ds.TangemPagerIndicator
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.message.TangemMessage
import com.tangem.core.ui.ds2.badge.TangemBadge
import com.tangem.core.ui.ds2.button.TangemButton
import com.tangem.core.ui.ds2.loader.TangemLoader
import com.tangem.core.ui.ds2.loader.TangemLoaderSize
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.LocalTopSnackbarHostState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_copy_24
import com.tangem.core.ui.res.generated.icons.ic_share_android_24
import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.ui.state.ReceiveAssetsUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
@Composable
internal fun TokenReceiveAssetsContent(assetsUM: ReceiveAssetsUM) {
val snackbarHostState = remember(::SnackbarHostState)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (assetsUM.isEnsResultLoading) {
LoadingBlock(
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(bottom = 8.dp),
)
}
AddressBlock(assetsUM = assetsUM)
SpacerH(16.dp)
Info(
showMemoDisclaimer = assetsUM.showMemoDisclaimer,
notificationConfigs = assetsUM.notificationConfigs,
currencyIconState = assetsUM.currencyIconState,
)
}
}
ContainerWithSnackbarHost(snackbarHostState = snackbarHostState) {
Column(
@Composable
private fun LoadingBlock(modifier: Modifier = Modifier) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(24.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors3.bg.opaque.primary),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.tertiary)
.padding(bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
.heightIn(min = 68.dp),
contentAlignment = Alignment.Center,
) {
AddressBlock(
assetsUM = assetsUM,
snackbarHostState = snackbarHostState,
)
if (assetsUM.isEnsResultLoading) {
SpacerH8()
LoadingBlock(modifier = Modifier.padding(horizontal = 16.dp))
}
SpacerH12()
Info(
showMemoDisclaimer = assetsUM.showMemoDisclaimer,
notificationConfigs = assetsUM.notificationConfigs,
currencyIconState = assetsUM.currencyIconState,
TangemLoader(
color = TangemTheme.colors3.icon.secondary,
size = TangemLoaderSize.X28,
)
}
}
}
@Composable
private fun AddressBlock(assetsUM: ReceiveAssetsUM) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val topSnackbarHostState = LocalTopSnackbarHostState.current
assetsUM.addresses
.fastFilter { it.type is ReceiveAddress.Type.Ens }
.fastForEach { address ->
key(address.value) {
EnsItem(
modifier = Modifier.padding(horizontal = 16.dp),
onCopyClick = {
assetsUM.onCopyClick(address)
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
topSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
}
},
address = address.value,
)
SpacerH8()
}
}
PrimaryAddressesItems(
addresses = assetsUM.addresses.fastFilter { it.type is ReceiveAddress.Type.Primary }.toImmutableList(),
currencyIconState = assetsUM.currencyIconState,
onShareClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
assetsUM.onShareClick(it)
},
onCopyClick = { address ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
topSnackbarHostState.showSnackbar(
SnackbarMessage(message = resourceReference(R.string.wallet_notification_address_copied)),
)
}
assetsUM.onCopyClick(address)
},
onOpenQrCodeClick = { address ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
assetsUM.onOpenQrCodeClick(address)
},
)
}
@Suppress("LongParameterList")
@Composable
private fun PrimaryAddressesItems(
addresses: ImmutableList<ReceiveAddress>,
currencyIconState: CurrencyIconState,
onShareClick: (String) -> Unit,
onCopyClick: (ReceiveAddress) -> Unit,
onOpenQrCodeClick: (String) -> Unit,
) {
if (addresses.isEmpty()) return
val pagerState = rememberPagerState(
initialPage = 0,
initialPageOffsetFraction = 0f,
pageCount = addresses::count,
)
HorizontalPager(
state = pagerState,
contentPadding = PaddingValues(horizontal = 16.dp),
pageSpacing = 16.dp,
) { page ->
val address = addresses[page]
AddressItem(
currencyIconState = currencyIconState,
onOpenQrCodeClick = { onOpenQrCodeClick(address.value) },
onCopyClick = { onCopyClick(address) },
onShareClick = { onShareClick(address.value) },
primaryType = address.type as ReceiveAddress.Type.Primary,
address = address.value,
isDynamicAddress = address.type is ReceiveAddress.Type.Primary.Dynamic,
)
}
SpacerH(8.dp)
if (addresses.size > 1) {
TangemPagerIndicator(pagerState = pagerState)
}
}
@Suppress("LongParameterList")
@Composable
private fun AddressItem(
currencyIconState: CurrencyIconState,
onOpenQrCodeClick: () -> Unit,
onCopyClick: () -> Unit,
onShareClick: () -> Unit,
primaryType: ReceiveAddress.Type.Primary,
address: String,
isDynamicAddress: Boolean,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxWidth()
.clickableSingle(onClick = onOpenQrCodeClick)
.padding(horizontal = 12.dp, vertical = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CurrencyIcon(
modifier = Modifier.size(60.dp),
state = currencyIconState,
shouldDisplayNetwork = true,
iconSize = 56.dp,
networkBadgeSize = 20.dp,
)
SpacerH(12.dp)
if (isDynamicAddress) {
DynamicAddressBadge(modifier = Modifier.padding(vertical = 4.dp))
SpacerH(12.dp)
}
Text(
text = primaryType.displayName.resolveReference(),
color = TangemTheme.colors3.text.primary,
style = TangemTheme.typography3.body.medium,
textAlign = TextAlign.Center,
)
SpacerH(4.dp)
Text(
modifier = Modifier
.heightIn(min = 40.dp)
.padding(horizontal = 16.dp),
text = address,
color = TangemTheme.colors3.text.secondary,
style = TangemTheme.typography3.caption.medium,
textAlign = TextAlign.Center,
)
SpacerH(8.dp)
Row(
modifier = Modifier
.clickableSingle(onClick = onOpenQrCodeClick)
.padding(10.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier.size(16.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_qrcode_new_24),
tint = TangemTheme.colors3.icon.primary,
contentDescription = null,
)
SpacerW(6.dp)
Text(
text = stringResourceSafe(R.string.token_receive_show_qr_code_title),
style = TangemTheme.typography3.body.medium,
color = TangemTheme.colors3.text.primary,
)
}
SpacerH(20.dp)
ButtonsBlock(
onCopyClick = onCopyClick,
onShareClick = onShareClick,
)
}
}
@Composable
private fun ButtonsBlock(onCopyClick: () -> Unit, onShareClick: () -> Unit) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val topSnackbarHostState = LocalTopSnackbarHostState.current
Row(
modifier = Modifier.width(IntrinsicSize.Min),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
TangemButton(
modifier = Modifier.weight(1f),
onClick = {
onCopyClick()
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
topSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
}
},
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_copy_24),
variant = TangemButton.Variant.Secondary,
size = TangemButton.Size.X12,
text = TextReference.Res(id = R.string.common_copy),
)
TangemButton(
modifier = Modifier.weight(1f),
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onShareClick()
},
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_share_android_24),
variant = TangemButton.Variant.Secondary,
size = TangemButton.Size.X12,
text = TextReference.Res(id = R.string.common_share),
)
}
}
@Composable
private fun EnsItem(onCopyClick: () -> Unit, address: String, modifier: Modifier = Modifier) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(24.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors3.bg.opaque.primary),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
modifier = Modifier.size(36.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_ens_36),
contentDescription = null,
)
SpacerW(12.dp)
Text(
modifier = Modifier.weight(1f),
text = address,
color = TangemTheme.colors3.text.primary,
style = TangemTheme.typography3.body.medium,
overflow = TextOverflow.MiddleEllipsis,
maxLines = 1,
)
TangemButton(
modifier = Modifier.padding(start = 12.dp),
onClick = onCopyClick,
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_share_android_24),
size = TangemButton.Size.X9,
variant = TangemButton.Variant.Secondary,
)
}
}
}
@Composable
private fun DynamicAddressBadge(modifier: Modifier = Modifier) {
TangemBadge(
modifier = modifier,
text = resourceReference(R.string.dynamic_addresses_receive_badge),
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_dynamic_addresses_badge_16),
status = TangemBadge.Status.Info,
)
}
@Composable
private fun Info(
currencyIconState: CurrencyIconState,
@ -110,227 +392,52 @@ private fun Info(
.padding(horizontal = 18.dp)
.fillMaxWidth(),
text = stringResourceSafe(R.string.receive_bottom_sheet_no_memo_required_message),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography3.caption.medium,
color = TangemTheme.colors3.text.tertiary,
textAlign = TextAlign.Center,
)
}
notificationConfigs.fastForEach { notificationConfig ->
key(notificationConfig.hashCode()) {
// TODO remove after new design system
if (notificationConfig is NotificationUM.Warning.YieldSupplyIsActive) {
YieldSupplyDepositedWarning(
currencyIconState = currencyIconState,
title = stringResourceSafe(
R.string.yield_module_balance_info_sheet_title,
notificationConfig.tokenName,
TangemMessage(
title = resourceReference(
id = R.string.yield_module_balance_info_sheet_title,
formatArgs = wrappedList(notificationConfig.tokenName),
),
subtitle = stringResourceSafe(R.string.yield_module_balance_info_sheet_subtitle),
)
} else {
Notification(config = notificationConfig.config)
}
}
}
}
}
@Composable
private fun YieldSupplyDepositedWarning(
currencyIconState: CurrencyIconState,
title: String,
subtitle: String,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size44)
.fillMaxWidth(),
shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.button.disabled,
) {
Row(
modifier = Modifier
.padding(all = TangemTheme.dimens.spacing12),
) {
Box(
modifier = Modifier
.height(22.dp)
.width(22.dp),
contentAlignment = Alignment.TopStart,
) {
CurrencyIcon(
modifier = Modifier
.align(Alignment.TopStart)
.size(13.dp),
state = currencyIconState,
shouldDisplayNetwork = false,
iconSize = 13.dp,
)
Image(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary, RoundedCornerShape(15.dp))
.padding(1.dp)
.size(15.dp)
.align(Alignment.BottomEnd),
imageVector = ImageVector.vectorResource(R.drawable.img_aave_22),
contentDescription = null,
)
}
SpacerW(width = TangemTheme.dimens.spacing8)
Column(modifier = Modifier.weight(1f)) {
Text(
text = title,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.button,
)
SpacerH(height = TangemTheme.dimens.spacing2)
Text(
text = subtitle,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
)
}
}
}
}
@Composable
private fun AddressBlock(assetsUM: ReceiveAssetsUM, snackbarHostState: SnackbarHostState) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val resources = context.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val topSnackbarHostState = LocalTopSnackbarHostState.current
assetsUM.addresses
.fastFilter { it.type is ReceiveAddress.Type.Ens }
.fastForEach { address ->
key(address.value) {
EnsItem(
modifier = Modifier.padding(horizontal = 16.dp),
onCopyClick = {
assetsUM.onCopyClick(address)
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
if (isRedesignEnabled) {
topSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
subtitle = resourceReference(R.string.yield_module_balance_info_sheet_subtitle),
leadingContent = {
Box(
modifier = Modifier
.height(22.dp)
.width(22.dp),
contentAlignment = Alignment.TopStart,
) {
CurrencyIcon(
modifier = Modifier
.align(Alignment.TopStart)
.size(13.dp),
state = currencyIconState,
shouldDisplayNetwork = false,
iconSize = 13.dp,
)
} else {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(
R.string.wallet_notification_address_copied,
),
Image(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary, RoundedCornerShape(15.dp))
.padding(1.dp)
.size(15.dp)
.align(Alignment.BottomEnd),
imageVector = ImageVector.vectorResource(R.drawable.img_aave_22),
contentDescription = null,
)
}
}
},
address = address.value,
)
SpacerH8()
}
}
PrimaryAddressesItems(
addresses = assetsUM.addresses.fastFilter { it.type is ReceiveAddress.Type.Primary }.toImmutableList(),
currencyIconState = assetsUM.currencyIconState,
snackbarHostState = snackbarHostState,
onShareClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
assetsUM.onShareClick(it)
},
onCopyClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(
R.string.wallet_notification_address_copied,
),
)
}
assetsUM.onCopyClick(it)
},
onOpenQrCodeClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
assetsUM.onOpenQrCodeClick(it)
},
)
}
@Suppress("LongParameterList")
@Composable
private fun PrimaryAddressesItems(
addresses: ImmutableList<ReceiveAddress>,
currencyIconState: CurrencyIconState,
snackbarHostState: SnackbarHostState,
onShareClick: (String) -> Unit,
onCopyClick: (ReceiveAddress) -> Unit,
onOpenQrCodeClick: (String) -> Unit,
) {
if (addresses.isEmpty()) return
var selectedAddress by remember { mutableStateOf(addresses.first()) }
val pagerState = rememberPagerState(
initialPage = 0,
initialPageOffsetFraction = 0f,
pageCount = addresses::count,
)
LaunchedEffect(key1 = pagerState.currentPage) {
selectedAddress = addresses[pagerState.currentPage]
}
HorizontalPager(
state = pagerState,
contentPadding = PaddingValues(horizontal = 16.dp),
pageSpacing = 16.dp,
) {
AddressItem(
currencyIconState = currencyIconState,
onOpenQrCodeClick = { onOpenQrCodeClick(selectedAddress.value) },
onCopyClick = { onCopyClick(selectedAddress) },
onShareClick = { onShareClick(selectedAddress.value) },
primaryType = selectedAddress.type as ReceiveAddress.Type.Primary,
address = selectedAddress.value,
isDynamicAddress = selectedAddress.type is ReceiveAddress.Type.Primary.Dynamic,
snackbarHostState = snackbarHostState,
)
}
SpacerH(4.dp)
if (pagerState.pageCount > 1) {
val indicatorState = rememberLazyListState()
val selectedColor = TangemTheme.colors.icon.primary1
val unselectedColor = TangemTheme.colors.icon.informative
LazyRow(
modifier = Modifier.height(20.dp),
state = indicatorState,
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
repeat(pagerState.pageCount) { iteration ->
item(key = iteration) {
val color by animateColorAsState(
targetValue = if (pagerState.currentPage == iteration) selectedColor else unselectedColor,
label = "",
},
)
Box(
modifier = Modifier
.padding(horizontal = 4.dp, vertical = 6.dp)
.background(color = color, shape = CircleShape)
.size(7.dp),
} else {
TangemMessage(
config = notificationConfig.config,
contentColor = Color.Transparent,
)
}
}
@ -338,308 +445,13 @@ private fun PrimaryAddressesItems(
}
}
@Suppress("LongParameterList")
@Composable
private fun AddressItem(
currencyIconState: CurrencyIconState,
onOpenQrCodeClick: () -> Unit,
onCopyClick: () -> Unit,
onShareClick: () -> Unit,
primaryType: ReceiveAddress.Type.Primary,
address: String,
isDynamicAddress: Boolean,
snackbarHostState: SnackbarHostState,
modifier: Modifier = Modifier,
) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
onClick = onOpenQrCodeClick,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CurrencyIcon(
modifier = Modifier.size(56.dp),
state = currencyIconState,
shouldDisplayNetwork = true,
iconSize = 56.dp,
)
SpacerH(12.dp)
if (isDynamicAddress) {
DynamicAddressBadge()
SpacerH(8.dp)
}
Text(
text = primaryType.displayName.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
textAlign = TextAlign.Center,
)
Text(
modifier = Modifier
.heightIn(min = 40.dp)
.padding(horizontal = 16.dp),
text = address,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
textAlign = TextAlign.Center,
)
SpacerH8()
Row(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.clickable(onClick = onOpenQrCodeClick)
.padding(vertical = 6.dp, horizontal = 12.dp),
horizontalArrangement = Arrangement.Center,
) {
Icon(
modifier = Modifier.size(16.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_qrcode_new_24),
contentDescription = null,
)
SpacerW(4.dp)
Text(
text = stringResourceSafe(R.string.token_receive_show_qr_code_title),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
)
}
SpacerH(20.dp)
ButtonsBlock(
snackbarHostState = snackbarHostState,
onCopyClick = onCopyClick,
onShareClick = onShareClick,
)
}
}
}
@Composable
private fun ButtonsBlock(snackbarHostState: SnackbarHostState, onCopyClick: () -> Unit, onShareClick: () -> Unit) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val resources = context.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val topSnackbarHostState = LocalTopSnackbarHostState.current
Row(
modifier = Modifier.width(IntrinsicSize.Min),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
ActionButtonWithResizableText(
modifier = Modifier.weight(1f),
config = ActionButtonConfig(
text = TextReference.Res(id = R.string.common_copy),
iconResId = R.drawable.ic_copy_new_24,
onClick = {
onCopyClick()
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
if (isRedesignEnabled) {
topSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
} else {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
)
}
}
},
),
)
ActionButtonWithResizableText(
modifier = Modifier.weight(1f),
config = ActionButtonConfig(
text = TextReference.Res(id = R.string.common_share),
iconResId = R.drawable.ic_share_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onShareClick()
},
),
)
}
}
@Composable
private fun EnsItem(onCopyClick: () -> Unit, address: String, modifier: Modifier = Modifier) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 14.dp, horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
modifier = Modifier.size(36.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_ens_36),
contentDescription = null,
)
SpacerW12()
EllipsisText(
modifier = Modifier.weight(1f),
text = address,
ellipsis = TextEllipsis.Middle,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
)
TangemIconButton(
modifier = Modifier.size(TangemTheme.dimens.size28),
iconRes = R.drawable.ic_share_24,
innerPadding = 6.dp,
onClick = onCopyClick,
)
}
}
}
@Composable
private fun LoadingBlock(modifier: Modifier = Modifier) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
) {
Box(
modifier = modifier
.fillMaxWidth()
.heightIn(min = 64.dp),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
color = TangemTheme.colors.icon.informative,
modifier = Modifier.padding(TangemTheme.dimens.spacing8),
)
}
}
}
@Composable
private fun ActionButtonWithResizableText(config: ActionButtonConfig, modifier: Modifier = Modifier) {
ActionBaseButton(
config = config,
shape = RoundedCornerShape(size = TangemTheme.dimens.radius24),
content = { contentModifier ->
ActionButtonContent(
config = config,
text = { color ->
Text(
text = config.text.resolveReference(),
autoSize = TextAutoSize.StepBased(
minFontSize = 10.sp,
maxFontSize = TangemTheme.typography.button.fontSize,
),
color = color,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.button,
)
},
modifier = contentModifier.padding(horizontal = 16.dp),
paddingBetweenIconAndText = 4.dp,
)
},
modifier = modifier,
color = TangemTheme.colors.button.secondary,
)
}
@Composable
private fun DynamicAddressBadge(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.background(
color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
shape = RoundedCornerShape(percent = 50),
)
.padding(horizontal = 12.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Icon(
painter = painterResource(
id = R.drawable.ic_dynamic_addresses_badge_16,
),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = TangemTheme.colors.icon.accent,
)
Text(
text = stringResourceSafe(R.string.dynamic_addresses_receive_badge),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.accent,
)
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_TokenReceiveAssetsContent(
@PreviewParameter(TokenReceiveAssetsContentProvider::class) params: ReceiveAssetsUM,
) {
TangemThemePreview {
TangemThemePreviewRedesign {
TokenReceiveAssetsContent(assetsUM = params)
}
}
private class TokenReceiveAssetsContentProvider : PreviewParameterProvider<ReceiveAssetsUM> {
val address = ReceiveAddress(
value = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d",
type = ReceiveAddress.Type.Primary.Default(
displayName = stringReference("Etherium address"),
),
)
private val config = ReceiveAssetsUM(
notificationConfigs =
persistentListOf(
NotificationUM.Warning(
title = stringReference("Send only XLM on the Ethereum network"),
subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description),
),
),
addresses = persistentListOf(
address,
address,
address.copy(type = ReceiveAddress.Type.Ens, value = "papasha.eth"),
),
showMemoDisclaimer = false,
onCopyClick = {},
onOpenQrCodeClick = {},
isEnsResultLoading = true,
network = "USDT",
currencyIconState = CurrencyIconState.Locked,
onShareClick = {},
)
override val values: Sequence<ReceiveAssetsUM>
get() = sequenceOf(config)
}

View file

@ -0,0 +1,672 @@
package com.tangem.features.tokenreceive.ui
import android.content.res.Configuration
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.res.getStringSafe
import com.tangem.core.ui.R
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.components.buttons.actions.ActionBaseButton
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.buttons.actions.ActionButtonContent
import com.tangem.core.ui.components.buttons.small.TangemIconButton
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.res.*
import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.ui.state.ReceiveAssetsUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
@Composable
internal fun TokenReceiveAssetsContentLegacy(assetsUM: ReceiveAssetsUM) {
val snackbarHostState = remember(::SnackbarHostState)
ContainerWithSnackbarHost(snackbarHostState = snackbarHostState) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.tertiary)
.padding(bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AddressBlock(
assetsUM = assetsUM,
snackbarHostState = snackbarHostState,
)
if (assetsUM.isEnsResultLoading) {
SpacerH8()
LoadingBlock(modifier = Modifier.padding(horizontal = 16.dp))
}
SpacerH12()
Info(
showMemoDisclaimer = assetsUM.showMemoDisclaimer,
notificationConfigs = assetsUM.notificationConfigs,
currencyIconState = assetsUM.currencyIconState,
)
}
}
}
@Composable
private fun Info(
currencyIconState: CurrencyIconState,
notificationConfigs: ImmutableList<NotificationUM>,
showMemoDisclaimer: Boolean,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
if (showMemoDisclaimer) {
Text(
modifier = Modifier
.padding(horizontal = 18.dp)
.fillMaxWidth(),
text = stringResourceSafe(R.string.receive_bottom_sheet_no_memo_required_message),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
}
notificationConfigs.fastForEach { notificationConfig ->
key(notificationConfig.hashCode()) {
if (notificationConfig is NotificationUM.Warning.YieldSupplyIsActive) {
YieldSupplyDepositedWarning(
currencyIconState = currencyIconState,
title = stringResourceSafe(
R.string.yield_module_balance_info_sheet_title,
notificationConfig.tokenName,
),
subtitle = stringResourceSafe(R.string.yield_module_balance_info_sheet_subtitle),
)
} else {
Notification(config = notificationConfig.config)
}
}
}
}
}
@Composable
private fun YieldSupplyDepositedWarning(
currencyIconState: CurrencyIconState,
title: String,
subtitle: String,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size44)
.fillMaxWidth(),
shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.button.disabled,
) {
Row(
modifier = Modifier
.padding(all = TangemTheme.dimens.spacing12),
) {
Box(
modifier = Modifier
.height(22.dp)
.width(22.dp),
contentAlignment = Alignment.TopStart,
) {
CurrencyIcon(
modifier = Modifier
.align(Alignment.TopStart)
.size(13.dp),
state = currencyIconState,
shouldDisplayNetwork = false,
iconSize = 13.dp,
)
Image(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary, RoundedCornerShape(15.dp))
.padding(1.dp)
.size(15.dp)
.align(Alignment.BottomEnd),
imageVector = ImageVector.vectorResource(R.drawable.img_aave_22),
contentDescription = null,
)
}
SpacerW(width = TangemTheme.dimens.spacing8)
Column(modifier = Modifier.weight(1f)) {
Text(
text = title,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.button,
)
SpacerH(height = TangemTheme.dimens.spacing2)
Text(
text = subtitle,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
)
}
}
}
}
@Composable
private fun AddressBlock(assetsUM: ReceiveAssetsUM, snackbarHostState: SnackbarHostState) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val resources = context.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val topSnackbarHostState = LocalTopSnackbarHostState.current
assetsUM.addresses
.fastFilter { it.type is ReceiveAddress.Type.Ens }
.fastForEach { address ->
key(address.value) {
EnsItem(
modifier = Modifier.padding(horizontal = 16.dp),
onCopyClick = {
assetsUM.onCopyClick(address)
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
if (isRedesignEnabled) {
topSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
} else {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(
R.string.wallet_notification_address_copied,
),
)
}
}
},
address = address.value,
)
SpacerH8()
}
}
PrimaryAddressesItems(
addresses = assetsUM.addresses.fastFilter { it.type is ReceiveAddress.Type.Primary }.toImmutableList(),
currencyIconState = assetsUM.currencyIconState,
snackbarHostState = snackbarHostState,
onShareClick = { address ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
assetsUM.onShareClick(address)
},
onCopyClick = { address ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(
R.string.wallet_notification_address_copied,
),
)
}
assetsUM.onCopyClick(address)
},
onOpenQrCodeClick = { address ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
assetsUM.onOpenQrCodeClick(address)
},
)
}
@Suppress("LongParameterList")
@Composable
private fun PrimaryAddressesItems(
addresses: ImmutableList<ReceiveAddress>,
currencyIconState: CurrencyIconState,
snackbarHostState: SnackbarHostState,
onShareClick: (String) -> Unit,
onCopyClick: (ReceiveAddress) -> Unit,
onOpenQrCodeClick: (String) -> Unit,
) {
if (addresses.isEmpty()) return
var selectedAddress by remember { mutableStateOf(addresses.first()) }
val pagerState = rememberPagerState(
initialPage = 0,
initialPageOffsetFraction = 0f,
pageCount = addresses::count,
)
LaunchedEffect(key1 = pagerState.currentPage) {
selectedAddress = addresses[pagerState.currentPage]
}
HorizontalPager(
state = pagerState,
contentPadding = PaddingValues(horizontal = 16.dp),
pageSpacing = 16.dp,
) {
AddressItem(
currencyIconState = currencyIconState,
onOpenQrCodeClick = { onOpenQrCodeClick(selectedAddress.value) },
onCopyClick = { onCopyClick(selectedAddress) },
onShareClick = { onShareClick(selectedAddress.value) },
primaryType = selectedAddress.type as ReceiveAddress.Type.Primary,
address = selectedAddress.value,
isDynamicAddress = selectedAddress.type is ReceiveAddress.Type.Primary.Dynamic,
snackbarHostState = snackbarHostState,
)
}
SpacerH(4.dp)
if (pagerState.pageCount > 1) {
val indicatorState = rememberLazyListState()
val selectedColor = TangemTheme.colors.icon.primary1
val unselectedColor = TangemTheme.colors.icon.informative
LazyRow(
modifier = Modifier.height(20.dp),
state = indicatorState,
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
repeat(pagerState.pageCount) { iteration ->
item(key = iteration) {
val color by animateColorAsState(
targetValue = if (pagerState.currentPage == iteration) selectedColor else unselectedColor,
label = "",
)
Box(
modifier = Modifier
.padding(horizontal = 4.dp, vertical = 6.dp)
.background(color = color, shape = CircleShape)
.size(7.dp),
)
}
}
}
}
}
@Suppress("LongParameterList")
@Composable
private fun AddressItem(
currencyIconState: CurrencyIconState,
onOpenQrCodeClick: () -> Unit,
onCopyClick: () -> Unit,
onShareClick: () -> Unit,
primaryType: ReceiveAddress.Type.Primary,
address: String,
isDynamicAddress: Boolean,
snackbarHostState: SnackbarHostState,
modifier: Modifier = Modifier,
) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
onClick = onOpenQrCodeClick,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CurrencyIcon(
modifier = Modifier.size(56.dp),
state = currencyIconState,
shouldDisplayNetwork = true,
iconSize = 56.dp,
)
SpacerH(12.dp)
if (isDynamicAddress) {
DynamicAddressBadge()
SpacerH(8.dp)
}
Text(
text = primaryType.displayName.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
textAlign = TextAlign.Center,
)
Text(
modifier = Modifier
.heightIn(min = 40.dp)
.padding(horizontal = 16.dp),
text = address,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
textAlign = TextAlign.Center,
)
SpacerH8()
Row(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.clickable(onClick = onOpenQrCodeClick)
.padding(vertical = 6.dp, horizontal = 12.dp),
horizontalArrangement = Arrangement.Center,
) {
Icon(
modifier = Modifier.size(16.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_qrcode_new_24),
contentDescription = null,
)
SpacerW(4.dp)
Text(
text = stringResourceSafe(R.string.token_receive_show_qr_code_title),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
)
}
SpacerH(20.dp)
ButtonsBlock(
snackbarHostState = snackbarHostState,
onCopyClick = onCopyClick,
onShareClick = onShareClick,
)
}
}
}
@Composable
private fun ButtonsBlock(snackbarHostState: SnackbarHostState, onCopyClick: () -> Unit, onShareClick: () -> Unit) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val resources = context.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val topSnackbarHostState = LocalTopSnackbarHostState.current
Row(
modifier = Modifier.width(IntrinsicSize.Min),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
ActionButtonWithResizableText(
modifier = Modifier.weight(1f),
config = ActionButtonConfig(
text = TextReference.Res(id = R.string.common_copy),
iconResId = R.drawable.ic_copy_new_24,
onClick = {
onCopyClick()
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
if (isRedesignEnabled) {
topSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
} else {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
)
}
}
},
),
)
ActionButtonWithResizableText(
modifier = Modifier.weight(1f),
config = ActionButtonConfig(
text = TextReference.Res(id = R.string.common_share),
iconResId = R.drawable.ic_share_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onShareClick()
},
),
)
}
}
@Composable
private fun EnsItem(onCopyClick: () -> Unit, address: String, modifier: Modifier = Modifier) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 14.dp, horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
modifier = Modifier.size(36.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_ens_36),
contentDescription = null,
)
SpacerW12()
EllipsisText(
modifier = Modifier.weight(1f),
text = address,
ellipsis = TextEllipsis.Middle,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
)
TangemIconButton(
modifier = Modifier.size(TangemTheme.dimens.size28),
iconRes = R.drawable.ic_share_24,
innerPadding = 6.dp,
onClick = onCopyClick,
)
}
}
}
@Composable
private fun LoadingBlock(modifier: Modifier = Modifier) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 64.dp),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
color = TangemTheme.colors.icon.informative,
modifier = Modifier.padding(TangemTheme.dimens.spacing8),
)
}
}
}
@Composable
private fun ActionButtonWithResizableText(config: ActionButtonConfig, modifier: Modifier = Modifier) {
ActionBaseButton(
config = config,
shape = RoundedCornerShape(size = TangemTheme.dimens.radius24),
content = { contentModifier ->
ActionButtonContent(
config = config,
text = { color ->
Text(
text = config.text.resolveReference(),
autoSize = TextAutoSize.StepBased(
minFontSize = 10.sp,
maxFontSize = TangemTheme.typography.button.fontSize,
),
color = color,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.button,
)
},
modifier = contentModifier.padding(horizontal = 16.dp),
paddingBetweenIconAndText = 4.dp,
)
},
modifier = modifier,
color = TangemTheme.colors.button.secondary,
)
}
@Composable
private fun DynamicAddressBadge(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.background(
color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
shape = RoundedCornerShape(percent = 50),
)
.padding(horizontal = 12.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Icon(
painter = painterResource(
id = R.drawable.ic_dynamic_addresses_badge_16,
),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = TangemTheme.colors.icon.accent,
)
Text(
text = stringResourceSafe(R.string.dynamic_addresses_receive_badge),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.accent,
)
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_TokenReceiveAssetsContent(
@PreviewParameter(TokenReceiveAssetsContentProvider::class) params: ReceiveAssetsUM,
) {
TangemThemePreview {
TokenReceiveAssetsContentLegacy(assetsUM = params)
}
}
internal class TokenReceiveAssetsContentProvider : PreviewParameterProvider<ReceiveAssetsUM> {
val address = ReceiveAddress(
value = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d",
type = ReceiveAddress.Type.Primary.Default(
displayName = stringReference("Etherium address"),
),
)
private val dynamicAddress = ReceiveAddress(
value = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
type = ReceiveAddress.Type.Primary.Dynamic(
displayName = stringReference("Ethereum address"),
),
)
private val tokenIconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = null,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
)
private val config = ReceiveAssetsUM(
notificationConfigs =
persistentListOf(
NotificationUM.Warning(
title = stringReference("Send only XLM on the Ethereum network"),
subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description),
),
),
addresses = persistentListOf(
address,
address,
address.copy(type = ReceiveAddress.Type.Ens, value = "papasha.eth"),
),
showMemoDisclaimer = false,
onCopyClick = {},
onOpenQrCodeClick = {},
isEnsResultLoading = true,
network = "USDT",
currencyIconState = CurrencyIconState.Locked,
onShareClick = {},
)
private val yieldSupplyIsActiveConfig = config.copy(
notificationConfigs = persistentListOf(
NotificationUM.Warning.YieldSupplyIsActive(tokenName = "USDT"),
),
isEnsResultLoading = false,
currencyIconState = tokenIconState,
)
private val dynamicAddressConfig = config.copy(
addresses = persistentListOf(
dynamicAddress,
address,
address.copy(type = ReceiveAddress.Type.Ens, value = "papasha.eth"),
),
isEnsResultLoading = false,
notificationConfigs = persistentListOf(),
currencyIconState = tokenIconState,
)
override val values: Sequence<ReceiveAssetsUM>
get() = sequenceOf(config, yieldSupplyIsActiveConfig, dynamicAddressConfig)
}

View file

@ -2,22 +2,32 @@ package com.tangem.features.tokenreceive.ui
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.animation.fade
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.ds2.button.TangemButton
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.LocalHazeState
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_chevron_left_24
import com.tangem.core.ui.res.generated.icons.ic_cross_24
import com.tangem.features.tokenreceive.route.TokenReceiveRoutes
import dev.chrisbanes.haze.rememberHazeState
@Composable
internal fun TokenReceiveContentSheet(
@ -26,59 +36,89 @@ internal fun TokenReceiveContentSheet(
onBackClick: () -> Unit,
contentStack: ChildStack<TokenReceiveRoutes, ComposableContentComponent>,
) {
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onCloseClick,
content = TangemBottomSheetConfigContent.Empty,
),
onBack = onBackClick,
containerColor = TangemTheme.colors.background.tertiary,
title = {
Title(
route = route,
onBackClick = onBackClick,
onCloseClick = onCloseClick,
)
},
content = {
TokenReceiveContent(
stackState = contentStack,
modifier = Modifier,
)
},
)
CompositionLocalProvider(LocalHazeState provides rememberHazeState()) {
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
type = TangemBottomSheetType.Modal,
onBack = onBackClick,
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onCloseClick,
content = TangemBottomSheetConfigContent.Empty,
),
containerColor = TangemTheme.colors3.bg.secondary,
title = {
Title(
route = route,
onBackClick = onBackClick,
onCloseClick = onCloseClick,
)
},
content = {
TokenReceiveContent(
stackState = contentStack,
modifier = Modifier,
)
},
)
}
}
@Composable
private fun Title(route: TokenReceiveRoutes, onBackClick: () -> Unit, onCloseClick: () -> Unit) {
when (route) {
is TokenReceiveRoutes.QrCode -> {
TangemModalBottomSheetTitle(
startIconRes = R.drawable.ic_back_24,
onStartClick = onBackClick,
endIconRes = R.drawable.ic_close_24,
onEndClick = onCloseClick,
TangemTopBar(
type = TangemTopBarType.BottomSheet,
endContent = {
TangemButton(
iconStart = TangemIconUM.Icon(Icons.ic_cross_24),
onClick = onCloseClick,
size = TangemButton.Size.X11,
variant = TangemButton.Variant.Material,
)
},
startContent = {
TangemButton(
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_chevron_left_24),
onClick = onBackClick,
size = TangemButton.Size.X11,
variant = TangemButton.Variant.Material,
)
},
)
}
TokenReceiveRoutes.ReceiveAssets -> {
TangemModalBottomSheetTitle(
TangemTopBar(
title = resourceReference(R.string.domain_receive_assets_navigation_title),
endIconRes = R.drawable.ic_close_24,
onEndClick = onCloseClick,
type = TangemTopBarType.BottomSheet,
endContent = {
TangemButton(
iconStart = TangemIconUM.Icon(Icons.ic_cross_24),
onClick = onCloseClick,
size = TangemButton.Size.X11,
variant = TangemButton.Variant.Material,
)
},
)
}
TokenReceiveRoutes.Warning -> {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = onCloseClick,
TangemTopBar(
type = TangemTopBarType.BottomSheet,
endContent = {
TangemButton(
iconStart = TangemIconUM.Icon(Icons.ic_cross_24),
onClick = onCloseClick,
size = TangemButton.Size.X11,
variant = TangemButton.Variant.Material,
)
},
)
}
}
}
@Composable
internal fun TokenReceiveContent(
private fun TokenReceiveContent(
stackState: ChildStack<TokenReceiveRoutes, ComposableContentComponent>,
modifier: Modifier = Modifier,
) {
@ -86,9 +126,9 @@ internal fun TokenReceiveContent(
stack = stackState,
animation = stackAnimation(fade(animationSpec = tween(durationMillis = 100))),
modifier = modifier
.fillMaxSize()
.fillMaxWidth()
.animateContentSize(),
) {
it.instance.Content(Modifier.fillMaxSize())
it.instance.Content(Modifier.fillMaxWidth())
}
}

View file

@ -0,0 +1,94 @@
package com.tangem.features.tokenreceive.ui
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.animation.fade
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.tokenreceive.route.TokenReceiveRoutes
@Composable
internal fun TokenReceiveContentSheetLegacy(
route: TokenReceiveRoutes,
onCloseClick: () -> Unit,
onBackClick: () -> Unit,
contentStack: ChildStack<TokenReceiveRoutes, ComposableContentComponent>,
) {
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onCloseClick,
content = TangemBottomSheetConfigContent.Empty,
),
onBack = onBackClick,
containerColor = TangemTheme.colors.background.tertiary,
title = {
Title(
route = route,
onBackClick = onBackClick,
onCloseClick = onCloseClick,
)
},
content = {
TokenReceiveContent(
stackState = contentStack,
modifier = Modifier,
)
},
)
}
@Composable
private fun Title(route: TokenReceiveRoutes, onBackClick: () -> Unit, onCloseClick: () -> Unit) {
when (route) {
is TokenReceiveRoutes.QrCode -> {
TangemModalBottomSheetTitle(
startIconRes = R.drawable.ic_back_24,
onStartClick = onBackClick,
endIconRes = R.drawable.ic_close_24,
onEndClick = onCloseClick,
)
}
TokenReceiveRoutes.ReceiveAssets -> {
TangemModalBottomSheetTitle(
title = resourceReference(R.string.domain_receive_assets_navigation_title),
endIconRes = R.drawable.ic_close_24,
onEndClick = onCloseClick,
)
}
TokenReceiveRoutes.Warning -> {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = onCloseClick,
)
}
}
}
@Composable
private fun TokenReceiveContent(
stackState: ChildStack<TokenReceiveRoutes, ComposableContentComponent>,
modifier: Modifier = Modifier,
) {
Children(
stack = stackState,
animation = stackAnimation(fade(animationSpec = tween(durationMillis = 100))),
modifier = modifier
.fillMaxSize()
.animateContentSize(),
) {
it.instance.Content(Modifier.fillMaxSize())
}
}

View file

@ -2,11 +2,9 @@ package com.tangem.features.tokenreceive.ui
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@ -18,24 +16,25 @@ import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
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.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.res.getStringSafe
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds2.button.TangemButton
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.LocalTopSnackbarHostState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_copy_24
import com.tangem.core.ui.res.generated.icons.ic_share_android_24
import com.tangem.core.ui.test.TokenReceiveQrCodeBottomSheetTestTags
import com.tangem.features.tokenreceive.impl.R
import com.tangem.features.tokenreceive.ui.state.QrCodeUM
@ -43,34 +42,28 @@ import kotlinx.coroutines.launch
@Composable
internal fun TokenReceiveQrCodeContent(qrCodeUM: QrCodeUM) {
val snackbarHostState = remember(::SnackbarHostState)
val qrCodePainter = rememberQrPainter(content = qrCodeUM.addressValue)
ContainerWithSnackbarHost(snackbarHostState = snackbarHostState) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.tertiary)
.padding(bottom = 16.dp)
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
SpacerH8()
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp, start = 16.dp, end = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
QrCodePage(
addressFullName = qrCodeUM.addressName,
addressValue = qrCodeUM.addressValue,
network = qrCodeUM.network,
qrCodePainter = qrCodePainter,
)
QrCodePage(
addressFullName = qrCodeUM.addressName,
addressValue = qrCodeUM.addressValue,
network = qrCodeUM.network,
qrCodePainter = qrCodePainter,
)
SpacerH24()
SpacerH(24.dp)
Buttons(
onShareClick = { qrCodeUM.onShareClick(qrCodeUM.addressValue) },
onCopyClick = qrCodeUM.onCopyClick,
snackbarHostState = snackbarHostState,
)
}
Buttons(
modifier = Modifier.padding(top = 8.dp),
onShareClick = { qrCodeUM.onShareClick(qrCodeUM.addressValue) },
onCopyClick = qrCodeUM.onCopyClick,
)
}
}
@ -81,7 +74,12 @@ private fun QrCodePage(addressFullName: TextReference, addressValue: String, net
horizontalAlignment = Alignment.CenterHorizontally,
) {
Column(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.size36),
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
top = 24.dp,
bottom = 16.dp,
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
@ -90,22 +88,22 @@ private fun QrCodePage(addressFullName: TextReference, addressValue: String, net
addressFullName.resolveReference(),
network,
),
color = TangemTheme.colors.text.primary1,
color = TangemTheme.colors3.text.primary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.h3,
style = TangemTheme.typography3.heading.small,
modifier = Modifier.testTag(TokenReceiveQrCodeBottomSheetTestTags.TITLE),
)
SpacerH(20.dp)
SpacerH(32.dp)
Box(
modifier = Modifier
.border(
width = 8.dp,
color = TangemTheme.colors.icon.constant,
shape = RoundedCornerShape(8.dp),
width = 16.dp,
color = TangemTheme.colors3.bg.secondary,
shape = RoundedCornerShape(12.dp),
)
.padding(8.dp)
.padding(16.dp)
.testTag(TokenReceiveQrCodeBottomSheetTestTags.QR_CODE),
) {
@ -116,79 +114,66 @@ private fun QrCodePage(addressFullName: TextReference, addressValue: String, net
modifier = Modifier.sizeIn(minWidth = 148.dp),
)
}
SpacerH24()
}
Text(
text = stringResourceSafe(R.string.wc_common_address),
color = TangemTheme.colors.text.tertiary,
color = TangemTheme.colors3.text.secondary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.subtitle2,
style = TangemTheme.typography3.caption.medium,
)
SpacerH2()
SpacerH(4.dp)
Text(
text = addressValue,
color = TangemTheme.colors.text.primary1,
color = TangemTheme.colors3.text.primary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.subtitle1,
style = TangemTheme.typography3.body.medium,
modifier = Modifier.testTag(TokenReceiveQrCodeBottomSheetTestTags.ADDRESS),
)
}
}
@Composable
private fun Buttons(
snackbarHostState: SnackbarHostState,
onShareClick: () -> Unit,
onCopyClick: () -> Unit,
modifier: Modifier = Modifier,
) {
private fun Buttons(onShareClick: () -> Unit, onCopyClick: () -> Unit, modifier: Modifier = Modifier) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val resources = context.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
SecondaryButtonIconStart(
TangemButton(
modifier = Modifier.weight(1f),
text = stringResourceSafe(id = R.string.common_copy),
iconResId = R.drawable.ic_copy_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onCopyClick()
coroutineScope.launch {
if (isRedesignEnabled) {
tangemTopSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
} else {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
)
}
tangemTopSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
}
},
text = resourceReference(R.string.common_copy),
iconStart = TangemIconUM.Icon(Icons.ic_copy_24),
size = TangemButton.Size.X12,
variant = TangemButton.Variant.Secondary,
)
SecondaryButtonIconStart(
TangemButton(
modifier = Modifier.weight(1f),
text = stringResourceSafe(id = R.string.common_share),
iconResId = R.drawable.ic_share_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onShareClick()
},
text = resourceReference(R.string.common_share),
iconStart = TangemIconUM.Icon(Icons.ic_share_android_24),
size = TangemButton.Size.X12,
variant = TangemButton.Variant.Secondary,
)
}
}
@ -212,20 +197,7 @@ private fun rememberQrPainter(content: String, size: Dp = 248.dp, padding: Dp =
private fun Preview_TokenReceiveQrCodeContent(
@PreviewParameter(TokenReceiveQrCodeContentPreviewProvider::class) qrCodeUM: QrCodeUM,
) {
TangemThemePreview {
TangemThemePreviewRedesign {
TokenReceiveQrCodeContent(qrCodeUM = qrCodeUM)
}
}
private class TokenReceiveQrCodeContentPreviewProvider : PreviewParameterProvider<QrCodeUM> {
private val config = QrCodeUM(
network = "Ethereum",
addressName = stringReference("Etherium"),
addressValue = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d",
onCopyClick = {},
onShareClick = {},
)
override val values: Sequence<QrCodeUM>
get() = sequenceOf(config)
}

View file

@ -0,0 +1,231 @@
package com.tangem.features.tokenreceive.ui
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
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.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.res.getStringSafe
import com.tangem.core.ui.components.*
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.LocalTopSnackbarHostState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenReceiveQrCodeBottomSheetTestTags
import com.tangem.features.tokenreceive.impl.R
import com.tangem.features.tokenreceive.ui.state.QrCodeUM
import kotlinx.coroutines.launch
@Composable
internal fun TokenReceiveQrCodeContentLegacy(qrCodeUM: QrCodeUM) {
val snackbarHostState = remember(::SnackbarHostState)
val qrCodePainter = rememberQrPainter(content = qrCodeUM.addressValue)
ContainerWithSnackbarHost(snackbarHostState = snackbarHostState) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.tertiary)
.padding(bottom = 16.dp)
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
SpacerH8()
QrCodePage(
addressFullName = qrCodeUM.addressName,
addressValue = qrCodeUM.addressValue,
network = qrCodeUM.network,
qrCodePainter = qrCodePainter,
)
SpacerH24()
Buttons(
onShareClick = { qrCodeUM.onShareClick(qrCodeUM.addressValue) },
onCopyClick = qrCodeUM.onCopyClick,
snackbarHostState = snackbarHostState,
)
}
}
}
@Composable
private fun QrCodePage(addressFullName: TextReference, addressValue: String, network: String, qrCodePainter: Painter) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Column(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.size36),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(
R.string.receive_bottom_sheet_warning_message_compact,
addressFullName.resolveReference(),
network,
),
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
style = TangemTheme.typography.h3,
modifier = Modifier.testTag(TokenReceiveQrCodeBottomSheetTestTags.TITLE),
)
SpacerH(20.dp)
Box(
modifier = Modifier
.border(
width = 8.dp,
color = TangemTheme.colors.icon.constant,
shape = RoundedCornerShape(8.dp),
)
.padding(8.dp)
.testTag(TokenReceiveQrCodeBottomSheetTestTags.QR_CODE),
) {
Image(
painter = qrCodePainter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier.sizeIn(minWidth = 148.dp),
)
}
SpacerH24()
}
Text(
text = stringResourceSafe(R.string.wc_common_address),
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.subtitle2,
)
SpacerH2()
Text(
text = addressValue,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
style = TangemTheme.typography.subtitle1,
modifier = Modifier.testTag(TokenReceiveQrCodeBottomSheetTestTags.ADDRESS),
)
}
}
@Composable
private fun Buttons(
snackbarHostState: SnackbarHostState,
onShareClick: () -> Unit,
onCopyClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val resources = context.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
SecondaryButtonIconStart(
modifier = Modifier.weight(1f),
text = stringResourceSafe(id = R.string.common_copy),
iconResId = R.drawable.ic_copy_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onCopyClick()
coroutineScope.launch {
if (isRedesignEnabled) {
tangemTopSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
} else {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
)
}
}
},
)
SecondaryButtonIconStart(
modifier = Modifier.weight(1f),
text = stringResourceSafe(id = R.string.common_share),
iconResId = R.drawable.ic_share_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onShareClick()
},
)
}
}
@Composable
private fun rememberQrPainter(content: String, size: Dp = 248.dp, padding: Dp = 0.dp): BitmapPainter {
val density = LocalDensity.current
return remember(content) {
BitmapPainter(
content.toQrCode(
sizePx = with(density) { size.roundToPx() },
paddingPx = with(density) { padding.roundToPx() },
).asImageBitmap(),
)
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_TokenReceiveQrCodeContent(
@PreviewParameter(TokenReceiveQrCodeContentPreviewProvider::class) qrCodeUM: QrCodeUM,
) {
TangemThemePreview {
TokenReceiveQrCodeContentLegacy(qrCodeUM = qrCodeUM)
}
}
internal class TokenReceiveQrCodeContentPreviewProvider : PreviewParameterProvider<QrCodeUM> {
private val config = QrCodeUM(
network = "Ethereum",
addressName = stringReference("Etherium"),
addressValue = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d",
onCopyClick = {},
onShareClick = {},
)
override val values: Sequence<QrCodeUM>
get() = sequenceOf(config)
}

View file

@ -1,8 +1,10 @@
package com.tangem.features.tokenreceive.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
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
@ -13,19 +15,14 @@ import androidx.compose.ui.platform.testTag
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.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.SpacerW6
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds2.button.TangemButton
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TokenReceiveWarningBottomSheetTestTags
import com.tangem.features.tokenreceive.impl.R
import com.tangem.features.tokenreceive.ui.state.WarningUM
@ -37,51 +34,51 @@ internal fun TokenReceiveWarningContent(warningUM: WarningUM) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.tertiary)
.padding(
top = 28.dp,
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
).testTag(TokenReceiveWarningBottomSheetTestTags.BOTTOM_SHEET),
)
.testTag(TokenReceiveWarningBottomSheetTestTags.BOTTOM_SHEET),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CurrencyIcon(
modifier = Modifier
.padding(8.dp)
.size(size = 64.dp),
.size(size = 76.dp),
state = warningUM.iconState,
shouldDisplayNetwork = true,
iconSize = 56.dp,
networkBadgeSize = 24.dp,
iconSize = 72.dp,
)
SpacerH24()
SpacerH(32.dp)
WarningBlock(networkName = warningUM.network)
SpacerH12()
SpacerH(8.dp)
Text(
modifier = Modifier.padding(horizontal = 16.dp),
textAlign = TextAlign.Center,
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_description),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
style = TangemTheme.typography3.subheading.medium,
color = TangemTheme.colors3.text.secondary,
)
SpacerH(48.dp)
SecondaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResourceSafe(R.string.common_got_it),
TangemButton(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
variant = TangemButton.Variant.Secondary,
text = resourceReference(R.string.common_got_it),
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
warningUM.onWarningAcknowledged()
},
size = TangemButton.Size.X12,
)
}
}
@Composable
fun WarningBlock(networkName: String, modifier: Modifier = Modifier) {
private fun WarningBlock(networkName: String, modifier: Modifier = Modifier) {
Column(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
@ -89,17 +86,15 @@ fun WarningBlock(networkName: String, modifier: Modifier = Modifier) {
Text(
textAlign = TextAlign.Center,
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_title),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography3.heading.small,
color = TangemTheme.colors3.text.primary,
)
SpacerW6()
Text(
textAlign = TextAlign.Center,
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_network_name, networkName),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography3.heading.small,
color = TangemTheme.colors3.text.primary,
)
}
}
@ -110,27 +105,7 @@ fun WarningBlock(networkName: String, modifier: Modifier = Modifier) {
private fun Preview_TokenReceiveWarningContent(
@PreviewParameter(TokenReceiveWarningContentProvider::class) warningUM: WarningUM,
) {
TangemThemePreview {
TangemThemePreviewRedesign {
TokenReceiveWarningContent(warningUM = warningUM)
}
}
private class TokenReceiveWarningContentProvider : PreviewParameterProvider<WarningUM> {
val iconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = null,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
)
override val values: Sequence<WarningUM>
get() = sequenceOf(
WarningUM(
iconState = iconState,
onWarningAcknowledged = {},
network = "Etherium",
),
)
}

View file

@ -0,0 +1,139 @@
package com.tangem.features.tokenreceive.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
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.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
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.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenReceiveWarningBottomSheetTestTags
import com.tangem.features.tokenreceive.impl.R
import com.tangem.features.tokenreceive.ui.state.WarningUM
@Composable
internal fun TokenReceiveWarningContentLegacy(warningUM: WarningUM) {
val hapticFeedback = LocalHapticFeedback.current
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.tertiary)
.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
)
.testTag(TokenReceiveWarningBottomSheetTestTags.BOTTOM_SHEET),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CurrencyIcon(
modifier = Modifier
.padding(8.dp)
.size(size = 64.dp),
state = warningUM.iconState,
shouldDisplayNetwork = true,
iconSize = 56.dp,
)
SpacerH24()
WarningBlock(networkName = warningUM.network)
SpacerH12()
Text(
textAlign = TextAlign.Center,
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_description),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
SpacerH(48.dp)
SecondaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResourceSafe(R.string.common_got_it),
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
warningUM.onWarningAcknowledged()
},
)
}
}
@Composable
private fun WarningBlock(networkName: String, modifier: Modifier = Modifier) {
Column(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
textAlign = TextAlign.Center,
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_title),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
SpacerH(6.dp)
Text(
textAlign = TextAlign.Center,
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_network_name, networkName),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_TokenReceiveWarningContent(
@PreviewParameter(TokenReceiveWarningContentProvider::class) warningUM: WarningUM,
) {
TangemThemePreview {
TokenReceiveWarningContentLegacy(warningUM = warningUM)
}
}
internal class TokenReceiveWarningContentProvider : PreviewParameterProvider<WarningUM> {
val iconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = null,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
)
override val values: Sequence<WarningUM>
get() = sequenceOf(
WarningUM(
iconState = iconState,
onWarningAcknowledged = {},
network = "Etherium",
),
)
}