Updated on 2026-08-14
This commit is contained in:
parent
154308b973
commit
e51453c99b
15 changed files with 173 additions and 68 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@
|
|||
<string name="auth_info_add_wallet_title">Add Wallet</string>
|
||||
<string name="auth_info_subtitle">Select a wallet to log in</string>
|
||||
<string name="auth_info_title">Welcome back!</string>
|
||||
<plurals name="auth_wallet_hardware_description">
|
||||
<item quantity="one">%d card</item>
|
||||
<item quantity="other">%d cards</item>
|
||||
</plurals>
|
||||
<string name="auth_wallet_mobile_description">Phone Wallet</string>
|
||||
<string name="backup_complete_description">You successfully backed up your wallet.</string>
|
||||
<string name="backup_complete_seed_description">These words can’t be recovered if lost. Make sure to keep it somewhere secure.</string>
|
||||
|
|
@ -866,6 +870,7 @@
|
|||
<string name="send_date_format">%1$s, %2$s</string>
|
||||
<string name="send_destination_tag_field">Destination Tag</string>
|
||||
<string name="send_enter_address_field">Enter address</string>
|
||||
<string name="send_enter_address_field_ens">Enter ENS name or address</string>
|
||||
<string name="send_error_address_same_as_wallet">Address is the same as wallet address</string>
|
||||
<string name="send_error_dust_amount_format">Minimum amount is %s</string>
|
||||
<string name="send_error_dust_change_format">Minimum change is %s</string>
|
||||
|
|
@ -947,11 +952,15 @@
|
|||
<string name="send_validation_invalid_amount">Invalid amount</string>
|
||||
<string name="send_validation_invalid_fee">Fee exceeds balance</string>
|
||||
<string name="send_validation_invalid_total">Total amount exceeds balance</string>
|
||||
<string name="send_with_swap_change_token_alert_message">Are you sure you want to change the token? After changing, previous data will be reset.</string>
|
||||
<string name="send_with_swap_change_token_alert_title">Changing token</string>
|
||||
<string name="send_with_swap_confirm_title">Swap and send</string>
|
||||
<string name="send_with_swap_notification_text">Send any token, and we’ll convert it on the way. Your recipient gets exactly what they need—seamlessly.</string>
|
||||
<string name="send_with_swap_recipient_amount_success_title">Recipient will receive</string>
|
||||
<string name="send_with_swap_recipient_amount_text">Will be sent a recipient</string>
|
||||
<string name="send_with_swap_recipient_amount_title">Amount to receive</string>
|
||||
<string name="send_with_swap_remove_convert_alert_message">Are you sure you want to cancel the conversion? After changing, previous data will be reset.</string>
|
||||
<string name="send_with_swap_remove_convert_alert_title">Confirm cancelation</string>
|
||||
<string name="send_with_swap_title">Send with swap</string>
|
||||
<string name="sent_transaction_sent_title">Transaction sent</string>
|
||||
<string name="settings_card_settings_footer">Prepare to scan card or ring you want to set up.</string>
|
||||
|
|
|
|||
|
|
@ -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<InputRowRecipientPreviewData> {
|
||||
|
|
@ -250,6 +291,12 @@ private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider<In
|
|||
isLoading = true,
|
||||
isError = true,
|
||||
),
|
||||
InputRowRecipientPreviewData(
|
||||
value = "vitalik.eth",
|
||||
isLoading = false,
|
||||
isError = true,
|
||||
resolvedAddress = "0x391316d97a07027a0702c8A002c8A0C25d8470",
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -111,6 +111,7 @@ class NetworkFactory @Inject constructor(
|
|||
hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource,
|
||||
canHandleTokens = canHandleTokens,
|
||||
transactionExtrasType = blockchain.getSupportedTransactionExtras(),
|
||||
nameResolvingType = blockchain.getNameResolvingType(),
|
||||
)
|
||||
}
|
||||
.getOrNull()
|
||||
|
|
@ -328,6 +329,13 @@ class NetworkFactory @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.getNameResolvingType(): Network.NameResolvingType {
|
||||
return when (this) {
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> Network.NameResolvingType.ENS
|
||||
else -> Network.NameResolvingType.NONE
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
fun createNetworkStandardType(blockchain: Blockchain) = getNetworkStandardType(blockchain)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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<NetworkStatus>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -76,11 +76,21 @@ private fun AddressBlock(address: DestinationTextFieldUM.RecipientAddress) {
|
|||
.clip(RoundedCornerShape(TangemTheme.dimens.radius18))
|
||||
.background(TangemTheme.colors.background.tertiary),
|
||||
)
|
||||
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),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
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,9 +182,7 @@ private fun DestinationBlockPreview(
|
|||
}
|
||||
|
||||
private class DestinationBlockPreviewProvider : PreviewParameterProvider<DestinationUM.Content> {
|
||||
override val values: Sequence<DestinationUM.Content>
|
||||
get() = sequenceOf(
|
||||
DestinationUM.Content(
|
||||
val previewItem = DestinationUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
addressTextField = DestinationTextFieldUM.RecipientAddress(
|
||||
value = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
|
||||
|
|
@ -192,34 +210,23 @@ private class DestinationBlockPreviewProvider : PreviewParameterProvider<Destina
|
|||
isValidating = false,
|
||||
isInitialized = true,
|
||||
isRedesignEnabled = false,
|
||||
)
|
||||
|
||||
override val values: Sequence<DestinationUM.Content>
|
||||
get() = sequenceOf(
|
||||
previewItem,
|
||||
previewItem.copy(
|
||||
addressTextField = previewItem.addressTextField.copy(
|
||||
value = "vitalik.eth",
|
||||
blockchainAddress = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
|
||||
),
|
||||
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,
|
||||
previewItem.copy(isRedesignEnabled = true),
|
||||
previewItem.copy(
|
||||
addressTextField = previewItem.addressTextField.copy(
|
||||
value = "vitalik.eth",
|
||||
blockchainAddress = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
|
||||
),
|
||||
recent = emptyList<DestinationRecipientListUM>().toImmutableList(),
|
||||
wallets = emptyList<DestinationRecipientListUM>().toImmutableList(),
|
||||
networkName = "Ethereum",
|
||||
isValidating = false,
|
||||
isInitialized = true,
|
||||
isRedesignEnabled = true,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ private fun LazyListScope.addressItem(
|
|||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
resolvedAddress = address.blockchainAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ internal data object SwapAmountContentPreview {
|
|||
hasFiatFeeRate = false,
|
||||
canHandleTokens = false,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
|
||||
),
|
||||
name = "Bitcoin",
|
||||
|
|
|
|||
|
|
@ -107,5 +107,6 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider<ExpressSt
|
|||
hasFiatFeeRate = true,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue