diff --git a/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt b/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt index ee2579d3ca..c56e3718ef 100644 --- a/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt +++ b/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt @@ -65,6 +65,10 @@ class MockCryptoCurrencyFactory(private val userWallet: UserWallet.Cold = defaul hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource, canHandleTokens = false, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = when (blockchain) { + Blockchain.Ethereum, Blockchain.EthereumTestnet -> Network.NameResolvingType.ENS + else -> Network.NameResolvingType.NONE + }, ) return factory.createCoin(network = network) @@ -95,6 +99,7 @@ class MockCryptoCurrencyFactory(private val userWallet: UserWallet.Cold = defaul hasFiatFeeRate = true, canHandleTokens = true, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ), name = "NEVER-MIND", symbol = "NEVER-MIND", diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 2ee799d6f3..8623c22b55 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -64,6 +64,10 @@ Add Wallet Select a wallet to log in Welcome back! + + %d card + %d cards + Phone Wallet You successfully backed up your wallet. These words can’t be recovered if lost. Make sure to keep it somewhere secure. @@ -866,6 +870,7 @@ %1$s, %2$s Destination Tag Enter address + Enter ENS name or address Address is the same as wallet address Minimum amount is %s Minimum change is %s @@ -947,11 +952,15 @@ Invalid amount Fee exceeds balance Total amount exceeds balance + Are you sure you want to change the token? After changing, previous data will be reset. + Changing token Swap and send Send any token, and we’ll convert it on the way. Your recipient gets exactly what they need—seamlessly. Recipient will receive Will be sent a recipient Amount to receive + Are you sure you want to cancel the conversion? After changing, previous data will be reset. + Confirm cancelation Send with swap Transaction sent Prepare to scan card or ring you want to set up. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt index b48f8fd849..418341d870 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment.Companion.CenterEnd @@ -19,6 +20,7 @@ import androidx.compose.ui.draw.clip 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.R import com.tangem.core.ui.components.buttons.small.TangemIconButton import com.tangem.core.ui.components.fields.SimpleTextField @@ -69,6 +71,7 @@ fun InputRowRecipient( showDivider: Boolean = false, isLoading: Boolean = false, isValuePasted: Boolean = false, + resolvedAddress: String? = null, ) { val (titleText, color) = if (isError && error != null) { error to TangemTheme.colors.text.warning @@ -144,11 +147,15 @@ fun InputRowRecipient( } else { TangemTheme.colors.text.primary2 }, - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing8), + modifier = Modifier.padding(start = TangemTheme.dimens.spacing8), ) } } + + ResolvedAddressRow( + isLoading = isLoading, + resolvedAddress = resolvedAddress, + ) } } } @@ -203,6 +210,38 @@ private fun RowScope.InputIcon(isLoading: Boolean, value: String) { } } +@Composable +private fun ResolvedAddressRow(isLoading: Boolean, resolvedAddress: String?) { + AnimatedContent( + targetState = if (resolvedAddress.isNullOrBlank() || isLoading) { + ResolvedState.Hide + } else { + ResolvedState.Show(resolvedAddress) + }, + label = "Resolved Address", + ) { state -> + if (state is ResolvedState.Show) { + Column { + HorizontalDivider( + thickness = 0.5.dp, + modifier = Modifier.padding(top = 12.dp, bottom = 12.dp), + color = TangemTheme.colors.stroke.primary, + ) + Text( + text = state.address, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ) + } + } + } +} + +private sealed interface ResolvedState { + data object Hide : ResolvedState + data class Show(val address: String) : ResolvedState +} + //region preview @Preview @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @@ -224,6 +263,7 @@ private fun InputRowRecipientPreview( onQrCodeClick = {}, modifier = Modifier.background(TangemTheme.colors.background.primary), isRedesignEnabled = false, + resolvedAddress = value.resolvedAddress, ) } } @@ -232,6 +272,7 @@ private data class InputRowRecipientPreviewData( val value: String, val isError: Boolean, val isLoading: Boolean = false, + val resolvedAddress: String? = null, ) private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider { @@ -250,6 +291,12 @@ private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider Network.NameResolvingType.ENS + else -> Network.NameResolvingType.NONE + } + } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) fun createNetworkStandardType(blockchain: Blockchain) = getNetworkStandardType(blockchain) diff --git a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt index 2e21a957da..28f27f5139 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt @@ -149,7 +149,9 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor( // TODO: in the future, consider optimizing this part .chunked(size = 15) // StakeKitApi limitation: no more than 15 requests at the same time .map { - async(dispatchers.io) { stakeKitApi.getMultipleYieldBalances(it).bind() } + async(dispatchers.io) { + stakeKitApi.getMultipleYieldBalances(it).bind() + } } .awaitAll() .flatten() diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/network/Network.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/network/Network.kt index 95d2ea6ad0..a955b53e54 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/network/Network.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/network/Network.kt @@ -20,6 +20,7 @@ import kotlinx.serialization.Serializable * currency (for those blockchains that have FeeResource instead of a standard type of fee) * @property canHandleTokens indicates whether the network can handle tokens * @property transactionExtrasType the type of extras supported for sending a transaction + * @property nameResolvingType the type of on-chain name resolution supported by the network (e.g., ENS, SNS etc) */ @Serializable data class Network( @@ -33,6 +34,7 @@ data class Network( val hasFiatFeeRate: Boolean, val canHandleTokens: Boolean, val transactionExtrasType: TransactionExtrasType, + val nameResolvingType: NameResolvingType, ) { /** Raw ID */ @@ -161,4 +163,16 @@ data class Network( -> true } } + + /** + * Represents the type of on-chain name resolution supported by the network. + */ + enum class NameResolvingType { + + /** No name resolution supported */ + NONE, + + /** Ethereum Name Service (ENS) */ + ENS, + } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt index a717323abe..8f3529b210 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt @@ -24,6 +24,7 @@ internal object MockNetworks { hasFiatFeeRate = true, canHandleTokens = true, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ) val network2 = Network( @@ -37,6 +38,7 @@ internal object MockNetworks { hasFiatFeeRate = true, canHandleTokens = true, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ) val network3 = Network( @@ -50,6 +52,7 @@ internal object MockNetworks { hasFiatFeeRate = true, canHandleTokens = true, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ) val verifiedNetworksStatuses: NonEmptySet diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt index 80da5da487..eca3ff5cc5 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt @@ -65,6 +65,7 @@ internal class PreviewCustomTokenSelectorComponent( hasFiatFeeRate = false, canHandleTokens = false, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ), name = "Network $index", type = "N$index", diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt index 55ac0bba8f..33f07fbef6 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt @@ -163,6 +163,7 @@ internal class PreviewManageTokensComponent( hasFiatFeeRate = false, canHandleTokens = false, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ), name = "NETWORK$networkIndex", type = "N$networkIndex", diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt index bc1276188d..28da1876e7 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt @@ -104,6 +104,7 @@ internal class PreviewOnboardingManageTokensComponent( hasFiatFeeRate = false, canHandleTokens = false, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ), name = "NETWORK$networkIndex", type = "N$networkIndex", diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt index e2cbe95c33..39cb1fd77b 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt @@ -22,6 +22,10 @@ internal class SendDestinationInitialStateTransformer( Network.TransactionExtrasType.MEMO -> R.string.send_extras_hint_memo Network.TransactionExtrasType.DESTINATION_TAG -> R.string.send_destination_tag_field } + val placeholder = when (cryptoCurrency.network.nameResolvingType) { + Network.NameResolvingType.NONE -> resourceReference(R.string.send_enter_address_field) + Network.NameResolvingType.ENS -> resourceReference(R.string.send_enter_address_field_ens) + } return DestinationUM.Content( isPrimaryButtonEnabled = false, isInitialized = isInitialized, @@ -32,7 +36,7 @@ internal class SendDestinationInitialStateTransformer( keyboardType = KeyboardType.Text, ), error = null, - placeholder = resourceReference(R.string.send_enter_address_field), + placeholder = placeholder, label = resourceReference(R.string.send_recipient), isValuePasted = false, ), diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt index bb84ed1e1e..e1c1b5da9f 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt @@ -76,11 +76,21 @@ private fun AddressBlock(address: DestinationTextFieldUM.RecipientAddress) { .clip(RoundedCornerShape(TangemTheme.dimens.radius18)) .background(TangemTheme.colors.background.tertiary), ) - Text( - text = address.value, - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.primary1, - ) + Column(modifier = Modifier.weight(1f)) { + Text( + text = address.value, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + val blockchainAddress = address.blockchainAddress + if (!blockchainAddress.isNullOrBlank()) { + Text( + text = blockchainAddress, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ) + } + } } } @@ -120,12 +130,21 @@ private fun AddressWithMemoBlock( horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24), modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), ) { - Text( - modifier = Modifier.weight(1f), - text = address.value, - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.primary1, - ) + Column(modifier = Modifier.weight(1f)) { + Text( + text = address.value, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + val blockchainAddress = address.blockchainAddress + if (!blockchainAddress.isNullOrBlank()) { + Text( + text = blockchainAddress, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ) + } + } IdentIcon( address = address.value, modifier = Modifier @@ -134,6 +153,7 @@ private fun AddressWithMemoBlock( .background(TangemTheme.colors.background.tertiary), ) } + if (memo != null && memo.value.isNotBlank()) { Text( text = stringResourceSafe(R.string.send_memo, memo.value), @@ -162,64 +182,51 @@ private fun DestinationBlockPreview( } private class DestinationBlockPreviewProvider : PreviewParameterProvider { + val previewItem = DestinationUM.Content( + isPrimaryButtonEnabled = true, + addressTextField = DestinationTextFieldUM.RecipientAddress( + value = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", + keyboardOptions = KeyboardOptions.Default, + placeholder = TextReference.Str("Enter address"), + label = TextReference.Str("Recipient Address"), + isError = false, + error = null, + isValuePasted = false, + ), + memoTextField = DestinationTextFieldUM.RecipientMemo( + value = "Test memo for transaction", + keyboardOptions = KeyboardOptions.Default, + placeholder = TextReference.Str("Enter memo (optional)"), + label = TextReference.Str("Memo"), + isError = false, + error = null, + disabledText = TextReference.Str("Memo disabled"), + isEnabled = true, + isValuePasted = false, + ), + recent = emptyList().toImmutableList(), + wallets = emptyList().toImmutableList(), + networkName = "Ethereum", + isValidating = false, + isInitialized = true, + isRedesignEnabled = false, + ) + override val values: Sequence get() = sequenceOf( - DestinationUM.Content( - isPrimaryButtonEnabled = true, - addressTextField = DestinationTextFieldUM.RecipientAddress( - value = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", - keyboardOptions = KeyboardOptions.Default, - placeholder = TextReference.Str("Enter address"), - label = TextReference.Str("Recipient Address"), - isError = false, - error = null, - isValuePasted = false, + previewItem, + previewItem.copy( + addressTextField = previewItem.addressTextField.copy( + value = "vitalik.eth", + blockchainAddress = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", ), - memoTextField = DestinationTextFieldUM.RecipientMemo( - value = "Test memo for transaction", - keyboardOptions = KeyboardOptions.Default, - placeholder = TextReference.Str("Enter memo (optional)"), - label = TextReference.Str("Memo"), - isError = false, - error = null, - disabledText = TextReference.Str("Memo disabled"), - isEnabled = true, - isValuePasted = false, - ), - recent = emptyList().toImmutableList(), - wallets = emptyList().toImmutableList(), - networkName = "Ethereum", - isValidating = false, - isInitialized = true, - isRedesignEnabled = false, ), - DestinationUM.Content( - isPrimaryButtonEnabled = true, - addressTextField = DestinationTextFieldUM.RecipientAddress( - value = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", - keyboardOptions = KeyboardOptions.Default, - placeholder = TextReference.Str("Enter address"), - label = TextReference.Str("Recipient Address"), - isError = false, - error = null, - isValuePasted = false, + previewItem.copy(isRedesignEnabled = true), + previewItem.copy( + addressTextField = previewItem.addressTextField.copy( + value = "vitalik.eth", + blockchainAddress = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", ), - memoTextField = DestinationTextFieldUM.RecipientMemo( - value = "Test memo for transaction", - keyboardOptions = KeyboardOptions.Default, - placeholder = TextReference.Str("Enter memo (optional)"), - label = TextReference.Str("Memo"), - isError = false, - error = null, - disabledText = TextReference.Str("Memo disabled"), - isEnabled = true, - isValuePasted = false, - ), - recent = emptyList().toImmutableList(), - wallets = emptyList().toImmutableList(), - networkName = "Ethereum", - isValidating = false, - isInitialized = true, isRedesignEnabled = true, ), ) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt index 31966f7310..da247fa248 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt @@ -142,6 +142,7 @@ private fun LazyListScope.addressItem( color = TangemTheme.colors.background.action, shape = TangemTheme.shapes.roundedCornersXMedium, ), + resolvedAddress = address.blockchainAddress, ) } } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt index b40ecc6c80..d7fd561378 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt @@ -39,6 +39,7 @@ internal data object SwapAmountContentPreview { hasFiatFeeRate = false, canHandleTokens = false, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ), name = "Bitcoin", diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt index 8e123b32df..fc4a214ee0 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt @@ -107,5 +107,6 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider