Updated on 2026-08-14
This commit is contained in:
parent
f9feacf074
commit
07f63b47e4
15 changed files with 245 additions and 32 deletions
|
|
@ -70,5 +70,9 @@
|
||||||
{
|
{
|
||||||
"name": "SWAP_REDESIGN_ENABLED",
|
"name": "SWAP_REDESIGN_ENABLED",
|
||||||
"version": "undefined"
|
"version": "undefined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SEND_REDESIGN_ENABLED",
|
||||||
|
"version": "undefined"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -856,8 +856,12 @@
|
||||||
<string name="send_recipient">Recipient</string>
|
<string name="send_recipient">Recipient</string>
|
||||||
<string name="send_recipient_address_error">Not a valid address</string>
|
<string name="send_recipient_address_error">Not a valid address</string>
|
||||||
<string name="send_recipient_address_footer">Ensure the receiving wallet address is on the %s network to avoid losing your tokens</string>
|
<string name="send_recipient_address_footer">Ensure the receiving wallet address is on the %s network to avoid losing your tokens</string>
|
||||||
|
<string name="send_recipient_address_footer_highlighted_part">send to %s</string>
|
||||||
|
<string name="send_recipient_address_footer_v2">Ensure you %s network address, as errors may result in lost transfers</string>
|
||||||
<string name="send_recipient_label">Send to</string>
|
<string name="send_recipient_label">Send to</string>
|
||||||
<string name="send_recipient_memo_footer">A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds</string>
|
<string name="send_recipient_memo_footer">A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds</string>
|
||||||
|
<string name="send_recipient_memo_footer_v2">Memo / Destination Tag is a code separating transactions to a shared recipient in a crypto network.</string>
|
||||||
|
<string name="send_recipient_memo_footer_v2_highlighted">Caution: Missing a memo may lead to fund loss.</string>
|
||||||
<string name="send_recipient_wallets_title">My wallets</string>
|
<string name="send_recipient_wallets_title">My wallets</string>
|
||||||
<string name="send_satoshi_per_byte_text">A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners.</string>
|
<string name="send_satoshi_per_byte_text">A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners.</string>
|
||||||
<string name="send_satoshi_per_byte_title">Satoshi / vByte</string>
|
<string name="send_satoshi_per_byte_title">Satoshi / vByte</string>
|
||||||
|
|
@ -885,7 +889,7 @@
|
||||||
<string name="settings_forget_wallet">Forget wallet</string>
|
<string name="settings_forget_wallet">Forget wallet</string>
|
||||||
<string name="settings_forget_wallet_footer">This will remove the wallet from the application. The wallet itself can be added again.</string>
|
<string name="settings_forget_wallet_footer">This will remove the wallet from the application. The wallet itself can be added again.</string>
|
||||||
<string name="settings_wallet_name_title">Name</string>
|
<string name="settings_wallet_name_title">Name</string>
|
||||||
<string name="stake_token_description">Join staking with this token</string>
|
<string name="stake_token_description">Put your token to work</string>
|
||||||
<string name="staking_amount_requirement_error">The amount to stake must be at least %s</string>
|
<string name="staking_amount_requirement_error">The amount to stake must be at least %s</string>
|
||||||
<string name="staking_amount_tron_integer_error">Staking amount will be rounded to %1$s TRX due to network rules.</string>
|
<string name="staking_amount_tron_integer_error">Staking amount will be rounded to %1$s TRX due to network rules.</string>
|
||||||
<string name="staking_amount_tron_integer_error_unstaking">Unstaking amount will be rounded to %1$s TRX due to network rules.</string>
|
<string name="staking_amount_tron_integer_error_unstaking">Unstaking amount will be rounded to %1$s TRX due to network rules.</string>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,7 +32,7 @@ fun FooterContainer(
|
||||||
AnimatedVisibility(visible = footer != null) {
|
AnimatedVisibility(visible = footer != null) {
|
||||||
val footerWrapped = remember(this) { requireNotNull(footer) }
|
val footerWrapped = remember(this) { requireNotNull(footer) }
|
||||||
Text(
|
Text(
|
||||||
text = footerWrapped.resolveReference(),
|
text = footerWrapped.resolveAnnotatedReference(),
|
||||||
style = TangemTheme.typography.caption2,
|
style = TangemTheme.typography.caption2,
|
||||||
color = TangemTheme.colors.text.tertiary,
|
color = TangemTheme.colors.text.tertiary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@ package com.tangem.core.ui.components.inputrow
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
|
@ -16,6 +20,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.components.buttons.small.TangemIconButton
|
||||||
import com.tangem.core.ui.components.fields.SimpleTextField
|
import com.tangem.core.ui.components.fields.SimpleTextField
|
||||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||||
import com.tangem.core.ui.components.inputrow.inner.CrossIcon
|
import com.tangem.core.ui.components.inputrow.inner.CrossIcon
|
||||||
|
|
@ -25,24 +30,29 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.core.ui.utils.DEFAULT_ANIMATION_DURATION
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Input Row Recipient](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-826&mode=design&t=IQ5lBJEkFGU4WSvi-4)
|
* * [Design Reference](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-826&mode=design&t=IQ5lBJEkFGU4WSvi-4)
|
||||||
*
|
*
|
||||||
* @param title title reference
|
* @param title The title text reference to display above the input field
|
||||||
* @param value recipient address
|
* @param value The current recipient address value to display in the input field
|
||||||
* @param placeholder placeholder
|
* @param placeholder The placeholder text reference to show when the input is empty
|
||||||
* @param onValueChange callback for value change
|
* @param onValueChange Callback invoked when the input value changes, receives the new string value
|
||||||
* @param onPasteClick callback for paste
|
* @param onPasteClick Callback invoked when the paste button is clicked, receives the pasted string
|
||||||
* @param modifier composable modifier
|
* @param onQrCodeClick Callback invoked when the QR code scan button is clicked
|
||||||
* @param singleLine is single line text
|
* @param modifier Optional modifier to apply to the composable
|
||||||
* @param error error text
|
* @param singleLine Whether the text field should be constrained to a single line (default: false)
|
||||||
* @param isError is error flag
|
* @param error Optional error text reference to display when validation fails
|
||||||
* @param showDivider show divider
|
* @param isError Whether the input is in an error state, affects styling and title color
|
||||||
|
* @param showDivider Whether to show a divider below the component (default: false)
|
||||||
|
* @param isLoading Whether to show a loading indicator instead of the identicon (default: false)
|
||||||
|
* @param isValuePasted Whether the current value was pasted, affects visual feedback (default: false)
|
||||||
*
|
*
|
||||||
* @see InputRowRecipientDefault for readonly version
|
* @see InputRowRecipientDefault for a read-only version of this component
|
||||||
*/
|
*/
|
||||||
|
@Suppress("LongParameterList", "LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
fun InputRowRecipient(
|
fun InputRowRecipient(
|
||||||
title: TextReference,
|
title: TextReference,
|
||||||
|
|
@ -50,6 +60,8 @@ fun InputRowRecipient(
|
||||||
placeholder: TextReference,
|
placeholder: TextReference,
|
||||||
onValueChange: (String) -> Unit,
|
onValueChange: (String) -> Unit,
|
||||||
onPasteClick: (String) -> Unit,
|
onPasteClick: (String) -> Unit,
|
||||||
|
onQrCodeClick: () -> Unit,
|
||||||
|
isRedesignEnabled: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
singleLine: Boolean = false,
|
singleLine: Boolean = false,
|
||||||
error: TextReference? = null,
|
error: TextReference? = null,
|
||||||
|
|
@ -109,18 +121,55 @@ fun InputRowRecipient(
|
||||||
.padding(start = TangemTheme.dimens.spacing8),
|
.padding(start = TangemTheme.dimens.spacing8),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PasteButton(
|
Row(
|
||||||
isPasteButtonVisible = value.isBlank(),
|
|
||||||
onClick = onPasteClick,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(CenterEnd)
|
.align(CenterEnd),
|
||||||
.padding(start = TangemTheme.dimens.spacing8),
|
) {
|
||||||
)
|
if (isRedesignEnabled) {
|
||||||
|
QrButton(
|
||||||
|
visible = !singleLine,
|
||||||
|
onQrCodeClick = onQrCodeClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PasteButton(
|
||||||
|
isPasteButtonVisible = value.isBlank(),
|
||||||
|
onClick = onPasteClick,
|
||||||
|
backgroundColorEnabled = if (isRedesignEnabled) {
|
||||||
|
TangemTheme.colors.button.secondary
|
||||||
|
} else {
|
||||||
|
TangemTheme.colors.button.primary
|
||||||
|
},
|
||||||
|
textColor = if (isRedesignEnabled) {
|
||||||
|
TangemTheme.colors.text.primary1
|
||||||
|
} else {
|
||||||
|
TangemTheme.colors.text.primary2
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = TangemTheme.dimens.spacing8),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun QrButton(visible: Boolean, onQrCodeClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = visible,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(animationSpec = tween(DEFAULT_ANIMATION_DURATION)),
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
TangemIconButton(
|
||||||
|
iconRes = R.drawable.ic_scan_16,
|
||||||
|
onClick = onQrCodeClick,
|
||||||
|
background = TangemTheme.colors.button.secondary,
|
||||||
|
iconTint = TangemTheme.colors.text.primary1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RowScope.InputIcon(isLoading: Boolean, value: String) {
|
private fun RowScope.InputIcon(isLoading: Boolean, value: String) {
|
||||||
var isLoadingProxy by remember { mutableStateOf(isLoading) }
|
var isLoadingProxy by remember { mutableStateOf(isLoading) }
|
||||||
|
|
@ -172,7 +221,9 @@ private fun InputRowRecipientPreview(
|
||||||
showDivider = true,
|
showDivider = true,
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
onPasteClick = {},
|
onPasteClick = {},
|
||||||
|
onQrCodeClick = {},
|
||||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||||
|
isRedesignEnabled = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.ripple
|
import androidx.compose.material3.ripple
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
|
@ -34,14 +35,21 @@ import com.tangem.core.ui.utils.DEFAULT_ANIMATION_DURATION
|
||||||
* @param modifier composable modifier
|
* @param modifier composable modifier
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun PasteButton(isPasteButtonVisible: Boolean, onClick: (String) -> Unit, modifier: Modifier = Modifier) {
|
fun PasteButton(
|
||||||
|
isPasteButtonVisible: Boolean,
|
||||||
|
onClick: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
backgroundColorEnabled: Color = TangemTheme.colors.button.primary,
|
||||||
|
backgroundColorDisabled: Color = TangemTheme.colors.button.secondary,
|
||||||
|
textColor: Color = TangemTheme.colors.text.primary2,
|
||||||
|
) {
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
val hapticFeedback = LocalHapticFeedback.current
|
val hapticFeedback = LocalHapticFeedback.current
|
||||||
val isPasteEnabled = !clipboardManager.getText()?.text.isNullOrEmpty()
|
val isPasteEnabled = !clipboardManager.getText()?.text.isNullOrEmpty()
|
||||||
val color = if (isPasteEnabled) {
|
val color = if (isPasteEnabled) {
|
||||||
TangemTheme.colors.button.primary
|
backgroundColorEnabled
|
||||||
} else {
|
} else {
|
||||||
TangemTheme.colors.button.secondary
|
backgroundColorDisabled
|
||||||
}
|
}
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = isPasteButtonVisible,
|
visible = isPasteButtonVisible,
|
||||||
|
|
@ -53,7 +61,7 @@ fun PasteButton(isPasteButtonVisible: Boolean, onClick: (String) -> Unit, modifi
|
||||||
Text(
|
Text(
|
||||||
text = stringResourceSafe(R.string.common_paste),
|
text = stringResourceSafe(R.string.common_paste),
|
||||||
style = TangemTheme.typography.caption1,
|
style = TangemTheme.typography.caption1,
|
||||||
color = TangemTheme.colors.text.primary2,
|
color = textColor,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(
|
.background(
|
||||||
color = color,
|
color = color,
|
||||||
|
|
|
||||||
|
|
@ -60,4 +60,40 @@ fun AnnotatedString.Builder.appendSpace() = append(" ")
|
||||||
|
|
||||||
fun AnnotatedString.Builder.appendColored(text: String, color: Color) = withStyle(SpanStyle(color = color)) {
|
fun AnnotatedString.Builder.appendColored(text: String, color: Color) = withStyle(SpanStyle(color = color)) {
|
||||||
append(text)
|
append(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends text from a template string to the AnnotatedString.Builder, replacing a placeholder (default "%s")
|
||||||
|
* with custom styled content provided by a lambda. The lambda allows you to insert styled or complex content
|
||||||
|
* (e.g., colored, bold, or annotated text) at the placeholder position.
|
||||||
|
*
|
||||||
|
* If the placeholder is not found in the template, the function does nothing.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* builder.appendWithStyledPlaceholder(
|
||||||
|
* template = "Ensure you %s network address, as errors may result in lost transfers"
|
||||||
|
* ) {
|
||||||
|
* withStyle(SpanStyle(color = Color.Red, fontWeight = FontWeight.Bold)) {
|
||||||
|
* append("Ethereum")
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param template The template string containing the placeholder to be replaced.
|
||||||
|
* @param placeholder The placeholder string to be replaced by styled content. Default is "%s".
|
||||||
|
* @param styledContent Lambda to build the styled content to insert at the placeholder position.
|
||||||
|
*/
|
||||||
|
fun AnnotatedString.Builder.appendWithStyledPlaceholder(
|
||||||
|
template: String,
|
||||||
|
placeholder: String = "%s",
|
||||||
|
styledContent: AnnotatedString.Builder.() -> Unit,
|
||||||
|
) {
|
||||||
|
val index = template.indexOf(placeholder)
|
||||||
|
if (index < 0) return
|
||||||
|
// Append before placeholder
|
||||||
|
if (index > 0) append(template.substring(0, index))
|
||||||
|
// Append styled content
|
||||||
|
styledContent()
|
||||||
|
// Append after placeholder
|
||||||
|
val afterIndex = index + placeholder.length
|
||||||
|
if (afterIndex < template.length) append(template.substring(afterIndex))
|
||||||
}
|
}
|
||||||
10
core/ui/src/main/res/drawable/ic_scan_16.xml
Normal file
10
core/ui/src/main/res/drawable/ic_scan_16.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="17dp"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:viewportHeight="17">
|
||||||
|
<path
|
||||||
|
android:pathData="M6.666,2.391H4C2.895,2.391 2,3.287 2,4.391V7.391L3.333,7.391V4.391C3.333,4.023 3.631,3.725 4,3.725H6.666V2.391ZM2,10.391V12.391C2,13.496 2.895,14.391 4,14.391H6.666V13.058H4C3.631,13.058 3.333,12.759 3.333,12.391V10.391H12.666V12.391C12.666,12.759 12.368,13.058 12,13.058H9.333V14.391H12C13.104,14.391 14,13.496 14,12.391V10.391H14.666V9.058H1.333V10.391H2ZM14,4.391V7.391H12.666V4.391C12.666,4.023 12.368,3.725 12,3.725H9.333V2.391H12C13.104,2.391 14,3.287 14,4.391Z"
|
||||||
|
android:fillColor="#1E1E1E"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
package com.tangem.features.send.v2.api
|
package com.tangem.features.send.v2.api
|
||||||
|
|
||||||
interface SendFeatureToggles
|
interface SendFeatureToggles {
|
||||||
|
|
||||||
|
val isSendRedesignEnabled: Boolean
|
||||||
|
}
|
||||||
|
|
@ -4,5 +4,8 @@ import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||||
|
|
||||||
internal class DefaultSendFeatureToggles(
|
internal class DefaultSendFeatureToggles(
|
||||||
@Suppress("UnusedPrivateMember") private val featureToggles: FeatureTogglesManager,
|
private val featureToggles: FeatureTogglesManager,
|
||||||
) : SendFeatureToggles
|
) : SendFeatureToggles {
|
||||||
|
override val isSendRedesignEnabled: Boolean
|
||||||
|
get() = featureToggles.isFeatureEnabled("SEND_REDESIGN_ENABLED")
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,7 @@ import com.tangem.domain.wallets.models.isMultiCurrency
|
||||||
import com.tangem.domain.wallets.models.requireColdWallet
|
import com.tangem.domain.wallets.models.requireColdWallet
|
||||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.features.send.v2.api.SendComponent
|
import com.tangem.features.send.v2.api.SendComponent
|
||||||
|
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||||
import com.tangem.features.send.v2.common.PredefinedValues
|
import com.tangem.features.send.v2.common.PredefinedValues
|
||||||
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
|
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
|
||||||
|
|
@ -87,6 +88,7 @@ internal class SendModel @Inject constructor(
|
||||||
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
|
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
|
||||||
private val getFeeUseCase: GetFeeUseCase,
|
private val getFeeUseCase: GetFeeUseCase,
|
||||||
private val sendAmountUpdateQRTrigger: SendAmountUpdateQRTrigger,
|
private val sendAmountUpdateQRTrigger: SendAmountUpdateQRTrigger,
|
||||||
|
private val sendFeatureToggles: SendFeatureToggles,
|
||||||
) : Model(), SendComponentCallback {
|
) : Model(), SendComponentCallback {
|
||||||
|
|
||||||
private val params: SendComponent.Params = paramsContainer.require()
|
private val params: SendComponent.Params = paramsContainer.require()
|
||||||
|
|
@ -357,6 +359,7 @@ internal class SendModel @Inject constructor(
|
||||||
amountUM = AmountState.Empty(),
|
amountUM = AmountState.Empty(),
|
||||||
destinationUM = SendDestinationInitialStateTransformer(
|
destinationUM = SendDestinationInitialStateTransformer(
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
|
isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled,
|
||||||
).transform(DestinationUM.Empty()),
|
).transform(DestinationUM.Empty()),
|
||||||
feeUM = FeeUM.Empty(),
|
feeUM = FeeUM.Empty(),
|
||||||
confirmUM = ConfirmUM.Empty,
|
confirmUM = ConfirmUM.Empty,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import com.tangem.domain.wallets.models.UserWallet
|
||||||
import com.tangem.domain.wallets.models.isLocked
|
import com.tangem.domain.wallets.models.isLocked
|
||||||
import com.tangem.domain.wallets.models.isMultiCurrency
|
import com.tangem.domain.wallets.models.isMultiCurrency
|
||||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||||
|
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||||
import com.tangem.features.send.v2.common.PredefinedValues
|
import com.tangem.features.send.v2.common.PredefinedValues
|
||||||
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
|
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
|
||||||
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents.SendScreenSource
|
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents.SendScreenSource
|
||||||
|
|
@ -65,6 +66,7 @@ internal class SendDestinationModel @Inject constructor(
|
||||||
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
||||||
private val parseQrCodeUseCase: ParseQrCodeUseCase,
|
private val parseQrCodeUseCase: ParseQrCodeUseCase,
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
private val sendFeatureToggles: SendFeatureToggles,
|
||||||
) : Model(), SendDestinationClickIntents {
|
) : Model(), SendDestinationClickIntents {
|
||||||
private val params: SendDestinationComponentParams = paramsContainer.require()
|
private val params: SendDestinationComponentParams = paramsContainer.require()
|
||||||
|
|
||||||
|
|
@ -90,6 +92,7 @@ internal class SendDestinationModel @Inject constructor(
|
||||||
_uiState.update(
|
_uiState.update(
|
||||||
SendDestinationInitialStateTransformer(
|
SendDestinationInitialStateTransformer(
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
|
isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled,
|
||||||
isInitialized = true,
|
isInitialized = true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -297,6 +300,7 @@ internal class SendDestinationModel @Inject constructor(
|
||||||
flow2 = params.currentRoute,
|
flow2 = params.currentRoute,
|
||||||
transform = { state, route -> state to route },
|
transform = { state, route -> state to route },
|
||||||
).onEach { (state, route) ->
|
).onEach { (state, route) ->
|
||||||
|
val isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled
|
||||||
params.callback.onNavigationResult(
|
params.callback.onNavigationResult(
|
||||||
NavigationUM.Content(
|
NavigationUM.Content(
|
||||||
title = params.title,
|
title = params.title,
|
||||||
|
|
@ -319,8 +323,16 @@ internal class SendDestinationModel @Inject constructor(
|
||||||
}
|
}
|
||||||
params.onBackClick()
|
params.onBackClick()
|
||||||
},
|
},
|
||||||
additionalIconRes = R.drawable.ic_qrcode_scan_24,
|
additionalIconRes = if (isRedesignEnabled) {
|
||||||
additionalIconClick = ::onQrCodeScanClick,
|
null
|
||||||
|
} else {
|
||||||
|
R.drawable.ic_qrcode_scan_24
|
||||||
|
},
|
||||||
|
additionalIconClick = if (isRedesignEnabled) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
::onQrCodeScanClick
|
||||||
|
},
|
||||||
primaryButton = ButtonsUM.PrimaryButtonUM(
|
primaryButton = ButtonsUM.PrimaryButtonUM(
|
||||||
text = if (route.isEditMode) {
|
text = if (route.isEditMode) {
|
||||||
resourceReference(R.string.common_continue)
|
resourceReference(R.string.common_continue)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.tangem.utils.transformer.Transformer
|
||||||
|
|
||||||
internal class SendDestinationInitialStateTransformer(
|
internal class SendDestinationInitialStateTransformer(
|
||||||
val cryptoCurrency: CryptoCurrency,
|
val cryptoCurrency: CryptoCurrency,
|
||||||
|
val isRedesignEnabled: Boolean,
|
||||||
val isInitialized: Boolean = false,
|
val isInitialized: Boolean = false,
|
||||||
) : Transformer<DestinationUM> {
|
) : Transformer<DestinationUM> {
|
||||||
override fun transform(prevState: DestinationUM): DestinationUM {
|
override fun transform(prevState: DestinationUM): DestinationUM {
|
||||||
|
|
@ -54,6 +55,7 @@ internal class SendDestinationInitialStateTransformer(
|
||||||
recent = loadingListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT),
|
recent = loadingListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT),
|
||||||
networkName = cryptoCurrency.network.name,
|
networkName = cryptoCurrency.network.name,
|
||||||
isValidating = false,
|
isValidating = false,
|
||||||
|
isRedesignEnabled = isRedesignEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,6 +16,10 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.core.ui.components.containers.FooterContainer
|
import com.tangem.core.ui.components.containers.FooterContainer
|
||||||
import com.tangem.core.ui.components.inputrow.InputRowRecipient
|
import com.tangem.core.ui.components.inputrow.InputRowRecipient
|
||||||
|
|
@ -60,10 +64,13 @@ internal fun SendDestinationContent(
|
||||||
isError = isError,
|
isError = isError,
|
||||||
isValidating = isValidating,
|
isValidating = isValidating,
|
||||||
onAddressChange = clickIntents::onRecipientAddressValueChange,
|
onAddressChange = clickIntents::onRecipientAddressValueChange,
|
||||||
|
onQrCodeClick = clickIntents::onQrCodeScanClick,
|
||||||
|
isRedesignEnabled = state.isRedesignEnabled,
|
||||||
)
|
)
|
||||||
memoField(
|
memoField(
|
||||||
memoField = memoField,
|
memoField = memoField,
|
||||||
onMemoChange = clickIntents::onRecipientMemoValueChange,
|
onMemoChange = clickIntents::onRecipientMemoValueChange,
|
||||||
|
isRedesignEnabled = state.isRedesignEnabled,
|
||||||
)
|
)
|
||||||
listHeaderItem(
|
listHeaderItem(
|
||||||
titleRes = R.string.send_recipient_wallets_title,
|
titleRes = R.string.send_recipient_wallets_title,
|
||||||
|
|
@ -100,16 +107,23 @@ internal fun SendDestinationContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
private fun LazyListScope.addressItem(
|
private fun LazyListScope.addressItem(
|
||||||
address: DestinationTextFieldUM.RecipientAddress,
|
address: DestinationTextFieldUM.RecipientAddress,
|
||||||
networkName: String,
|
networkName: String,
|
||||||
isError: Boolean,
|
isError: Boolean,
|
||||||
isValidating: Boolean,
|
isValidating: Boolean,
|
||||||
onAddressChange: (String, EnterAddressSource) -> Unit,
|
onAddressChange: (String, EnterAddressSource) -> Unit,
|
||||||
|
onQrCodeClick: () -> Unit,
|
||||||
|
isRedesignEnabled: Boolean,
|
||||||
) {
|
) {
|
||||||
item(key = ADDRESS_FIELD_KEY) {
|
item(key = ADDRESS_FIELD_KEY) {
|
||||||
FooterContainer(
|
FooterContainer(
|
||||||
footer = resourceReference(R.string.send_recipient_address_footer, wrappedList(networkName)),
|
footer = if (isRedesignEnabled) {
|
||||||
|
buildHighlightedFooterText(networkName)
|
||||||
|
} else {
|
||||||
|
resourceReference(R.string.send_recipient_address_footer, wrappedList(networkName))
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
InputRowRecipient(
|
InputRowRecipient(
|
||||||
value = address.value,
|
value = address.value,
|
||||||
|
|
@ -117,6 +131,8 @@ private fun LazyListScope.addressItem(
|
||||||
placeholder = address.placeholder,
|
placeholder = address.placeholder,
|
||||||
onValueChange = { onAddressChange(it, EnterAddressSource.InputField) },
|
onValueChange = { onAddressChange(it, EnterAddressSource.InputField) },
|
||||||
onPasteClick = { onAddressChange(it, EnterAddressSource.PasteButton) },
|
onPasteClick = { onAddressChange(it, EnterAddressSource.PasteButton) },
|
||||||
|
onQrCodeClick = onQrCodeClick,
|
||||||
|
isRedesignEnabled = isRedesignEnabled,
|
||||||
isError = isError,
|
isError = isError,
|
||||||
isLoading = isValidating,
|
isLoading = isValidating,
|
||||||
error = address.error,
|
error = address.error,
|
||||||
|
|
@ -131,9 +147,37 @@ private fun LazyListScope.addressItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun buildHighlightedFooterText(networkName: String): TextReference {
|
||||||
|
return annotatedReference(
|
||||||
|
buildAnnotatedString {
|
||||||
|
val styledText = stringResourceSafe(
|
||||||
|
R.string.send_recipient_address_footer_highlighted_part,
|
||||||
|
networkName,
|
||||||
|
)
|
||||||
|
val styledColor = TangemTheme.colors.text.secondary
|
||||||
|
appendWithStyledPlaceholder(
|
||||||
|
template = stringResourceSafe(
|
||||||
|
R.string.send_recipient_address_footer_v2,
|
||||||
|
networkName,
|
||||||
|
),
|
||||||
|
placeholder = networkName,
|
||||||
|
) {
|
||||||
|
withStyle(SpanStyle(fontWeight = FontWeight.Medium)) {
|
||||||
|
appendColored(
|
||||||
|
text = styledText,
|
||||||
|
color = styledColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun LazyListScope.memoField(
|
private fun LazyListScope.memoField(
|
||||||
memoField: DestinationTextFieldUM.RecipientMemo?,
|
memoField: DestinationTextFieldUM.RecipientMemo?,
|
||||||
onMemoChange: (String, Boolean) -> Unit,
|
onMemoChange: (String, Boolean) -> Unit,
|
||||||
|
isRedesignEnabled: Boolean,
|
||||||
) {
|
) {
|
||||||
if (memoField != null) {
|
if (memoField != null) {
|
||||||
item(key = MEMO_FIELD_KEY) {
|
item(key = MEMO_FIELD_KEY) {
|
||||||
|
|
@ -142,7 +186,24 @@ private fun LazyListScope.memoField(
|
||||||
value = memoField.value,
|
value = memoField.value,
|
||||||
label = memoField.label,
|
label = memoField.label,
|
||||||
placeholder = placeholder,
|
placeholder = placeholder,
|
||||||
footer = resourceReference(R.string.send_recipient_memo_footer),
|
footer = if (isRedesignEnabled) {
|
||||||
|
annotatedReference(
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(stringResourceSafe(R.string.send_recipient_memo_footer_v2))
|
||||||
|
append("\n")
|
||||||
|
withStyle(SpanStyle(fontWeight = FontWeight.Medium)) {
|
||||||
|
appendColored(
|
||||||
|
text = stringResourceSafe(
|
||||||
|
R.string.send_recipient_memo_footer_v2_highlighted,
|
||||||
|
),
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
resourceReference(R.string.send_recipient_memo_footer)
|
||||||
|
},
|
||||||
onValueChange = { onMemoChange(it, false) },
|
onValueChange = { onMemoChange(it, false) },
|
||||||
onPasteClick = { onMemoChange(it, true) },
|
onPasteClick = { onMemoChange(it, true) },
|
||||||
modifier = Modifier.padding(top = 20.dp),
|
modifier = Modifier.padding(top = 20.dp),
|
||||||
|
|
@ -151,6 +212,7 @@ private fun LazyListScope.memoField(
|
||||||
error = memoField.error,
|
error = memoField.error,
|
||||||
isReadOnly = !memoField.isEnabled,
|
isReadOnly = !memoField.isEnabled,
|
||||||
isValuePasted = memoField.isValuePasted,
|
isValuePasted = memoField.isValuePasted,
|
||||||
|
isRedesignEnabled = isRedesignEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.components.containers.FooterContainer
|
import com.tangem.core.ui.components.containers.FooterContainer
|
||||||
|
|
||||||
|
@Suppress("LongMethod", "LongParameterList")
|
||||||
@Composable
|
@Composable
|
||||||
internal fun TextFieldWithPaste(
|
internal fun TextFieldWithPaste(
|
||||||
value: String,
|
value: String,
|
||||||
|
|
@ -27,6 +28,7 @@ internal fun TextFieldWithPaste(
|
||||||
label: TextReference,
|
label: TextReference,
|
||||||
onValueChange: (String) -> Unit,
|
onValueChange: (String) -> Unit,
|
||||||
onPasteClick: (String) -> Unit,
|
onPasteClick: (String) -> Unit,
|
||||||
|
isRedesignEnabled: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
footer: TextReference? = null,
|
footer: TextReference? = null,
|
||||||
labelStyle: TextStyle = TangemTheme.typography.body2,
|
labelStyle: TextStyle = TangemTheme.typography.body2,
|
||||||
|
|
@ -96,6 +98,18 @@ internal fun TextFieldWithPaste(
|
||||||
PasteButton(
|
PasteButton(
|
||||||
isPasteButtonVisible = value.isBlank(),
|
isPasteButtonVisible = value.isBlank(),
|
||||||
onClick = onPasteClick,
|
onClick = onPasteClick,
|
||||||
|
backgroundColorEnabled = if (isRedesignEnabled) {
|
||||||
|
TangemTheme.colors.button.secondary
|
||||||
|
} else {
|
||||||
|
TangemTheme.colors.button.primary
|
||||||
|
},
|
||||||
|
textColor = if (isRedesignEnabled) {
|
||||||
|
TangemTheme.colors.text.primary1
|
||||||
|
} else {
|
||||||
|
TangemTheme.colors.text.primary2
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = TangemTheme.dimens.spacing8),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ internal sealed class DestinationUM {
|
||||||
val networkName: String,
|
val networkName: String,
|
||||||
val isValidating: Boolean = false,
|
val isValidating: Boolean = false,
|
||||||
val isInitialized: Boolean = false,
|
val isInitialized: Boolean = false,
|
||||||
|
val isRedesignEnabled: Boolean = false,
|
||||||
) : DestinationUM()
|
) : DestinationUM()
|
||||||
|
|
||||||
data class Empty(
|
data class Empty(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue