Updated on 2026-08-14
This commit is contained in:
parent
b28720df71
commit
4d74432402
18 changed files with 680 additions and 393 deletions
|
|
@ -18,6 +18,7 @@ fun AppBarWithBackButtonAndIcon(
|
|||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
text: String? = null,
|
||||
subtitle: String? = null,
|
||||
@DrawableRes backIconRes: Int? = null,
|
||||
@DrawableRes iconRes: Int? = null,
|
||||
onIconClick: (() -> Unit)? = null,
|
||||
|
|
@ -27,6 +28,7 @@ fun AppBarWithBackButtonAndIcon(
|
|||
onBackClick = onBackClick,
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
subtitle = subtitle,
|
||||
backIconRes = backIconRes,
|
||||
backgroundColor = backgroundColor,
|
||||
iconContent = {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.core.ui.components.appbar
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
|
|
@ -37,6 +37,7 @@ fun AppBarWithBackButtonAndIconContent(
|
|||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
text: String? = null,
|
||||
subtitle: String? = null,
|
||||
@DrawableRes backIconRes: Int? = null,
|
||||
backIconTint: Color = TangemTheme.colors.icon.primary1,
|
||||
backgroundColor: Color = TangemTheme.colors.background.secondary,
|
||||
|
|
@ -46,7 +47,7 @@ fun AppBarWithBackButtonAndIconContent(
|
|||
modifier = modifier
|
||||
.background(color = backgroundColor)
|
||||
.fillMaxWidth()
|
||||
.padding(all = TangemTheme.dimens.spacing16),
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
|
@ -54,6 +55,7 @@ fun AppBarWithBackButtonAndIconContent(
|
|||
painter = painterResource(backIconRes ?: R.drawable.ic_back_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing16)
|
||||
.size(size = TangemTheme.dimens.size24)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
|
|
@ -61,20 +63,37 @@ fun AppBarWithBackButtonAndIconContent(
|
|||
) { onBackClick() },
|
||||
tint = backIconTint,
|
||||
)
|
||||
AnimatedContent(
|
||||
targetState = text,
|
||||
modifier = Modifier.weight(1f),
|
||||
transitionSpec = { fadeIn().togetherWith(fadeOut()) },
|
||||
label = "Toolbar title change",
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.weight(1f)
|
||||
.animateContentSize(),
|
||||
) {
|
||||
if (!it.isNullOrBlank()) {
|
||||
AnimatedVisibility(
|
||||
visible = !text.isNullOrBlank(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
label = "Toolbar title change",
|
||||
) {
|
||||
Text(
|
||||
text = it,
|
||||
text = text.orEmpty(),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = !subtitle.isNullOrBlank(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
label = "Toolbar subtitle change",
|
||||
) {
|
||||
Text(
|
||||
text = subtitle.orEmpty(),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
iconContent()
|
||||
}
|
||||
|
|
@ -111,6 +130,7 @@ private fun PreviewAppBarWithBackButtonAndIconInDarkTheme() {
|
|||
TangemTheme(isDark = true) {
|
||||
AppBarWithBackButtonAndIconContent(
|
||||
text = "Title",
|
||||
subtitle = "Subtitle",
|
||||
onBackClick = {},
|
||||
iconContent = {
|
||||
Row {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import androidx.compose.runtime.ReadOnlyComposable
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
internal object TangemButtonsDefaults {
|
||||
object TangemButtonsDefaults {
|
||||
|
||||
val elevation: ButtonElevation
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.core.ui.components.fields
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
|
|
@ -12,7 +14,12 @@ import androidx.compose.ui.Alignment.Companion.TopCenter
|
|||
import androidx.compose.ui.Alignment.Companion.TopStart
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.ParagraphIntrinsics
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.createFontFamilyResolver
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
|
|
@ -57,51 +64,77 @@ fun AmountTextField(
|
|||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
isAutoResize: Boolean = false,
|
||||
@FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
|
||||
reduceFactor: Double = 0.9,
|
||||
) {
|
||||
val decimalFormat = rememberDecimalFormat()
|
||||
|
||||
val visualTransformation = remember { AmountVisualTransformation(decimals, symbol, decimalFormat) }
|
||||
val placeholderTextAlign = if (placeholderAlignment == TopCenter) {
|
||||
TextAlign.Center
|
||||
} else {
|
||||
TextAlign.Start
|
||||
}
|
||||
SimpleTextField(
|
||||
value = value,
|
||||
onValueChange = { newText ->
|
||||
if (decimalFormat.isValidSymbols(newText)) {
|
||||
val trimmed = decimalFormat.getValidatedNumberWithFixedDecimals(newText, decimals)
|
||||
onValueChange(trimmed)
|
||||
BoxWithConstraints(modifier = modifier) {
|
||||
var fontSize = textStyle.fontSize
|
||||
if (isAutoResize) {
|
||||
val calculateIntrinsics = @Composable {
|
||||
val transformedText = visualTransformation.filter(AnnotatedString(value)).text.text
|
||||
ParagraphIntrinsics(
|
||||
text = transformedText,
|
||||
style = textStyle.copy(fontSize = fontSize),
|
||||
density = LocalDensity.current,
|
||||
fontFamilyResolver = createFontFamilyResolver(LocalContext.current),
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.action),
|
||||
textStyle = textStyle.copy(textDirection = TextDirection.ContentOrLtr),
|
||||
color = color,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
singleLine = true,
|
||||
visualTransformation = AmountVisualTransformation(decimals, symbol, decimalFormat),
|
||||
decorationBox = { innerTextField ->
|
||||
Box {
|
||||
if (value.isBlank() && showPlaceholder) {
|
||||
val placeholder = if (symbol != null) {
|
||||
decimalFormat.defaultFormat().plus(" $symbol")
|
||||
} else {
|
||||
decimalFormat.defaultFormat()
|
||||
}
|
||||
Text(
|
||||
text = placeholder,
|
||||
style = textStyle.copy(textDirection = TextDirection.ContentOrLtr),
|
||||
color = TangemTheme.colors.text.disabled,
|
||||
textAlign = placeholderTextAlign,
|
||||
modifier = Modifier
|
||||
.align(placeholderAlignment),
|
||||
)
|
||||
var intrinsics = calculateIntrinsics()
|
||||
with(LocalDensity.current) {
|
||||
while (intrinsics.maxIntrinsicWidth > maxWidth.toPx()) {
|
||||
fontSize *= reduceFactor
|
||||
intrinsics = calculateIntrinsics()
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
SimpleTextField(
|
||||
value = value,
|
||||
onValueChange = { newText ->
|
||||
if (decimalFormat.isValidSymbols(newText)) {
|
||||
val trimmed = decimalFormat.getValidatedNumberWithFixedDecimals(newText, decimals)
|
||||
onValueChange(trimmed)
|
||||
}
|
||||
},
|
||||
textStyle = textStyle.copy(
|
||||
fontSize = fontSize,
|
||||
textDirection = TextDirection.ContentOrLtr,
|
||||
textAlign = TextAlign.Center,
|
||||
),
|
||||
color = color,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
singleLine = true,
|
||||
visualTransformation = AmountVisualTransformation(decimals, symbol, decimalFormat),
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
decorationBox = { innerTextField ->
|
||||
Box {
|
||||
if (value.isBlank() && showPlaceholder) {
|
||||
val placeholder = if (symbol != null) {
|
||||
decimalFormat.defaultFormat().plus(" $symbol")
|
||||
} else {
|
||||
decimalFormat.defaultFormat()
|
||||
}
|
||||
Text(
|
||||
text = placeholder,
|
||||
style = textStyle.copy(textDirection = TextDirection.ContentOrLtr),
|
||||
color = TangemTheme.colors.text.disabled,
|
||||
textAlign = placeholderTextAlign,
|
||||
modifier = Modifier.align(placeholderAlignment),
|
||||
)
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DecimalFormat.isValidSymbols(text: String): Boolean {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ dependencies {
|
|||
/** Compose */
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.material)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import com.tangem.blockchain.common.TransactionData
|
|||
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
|
|
@ -14,6 +13,7 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.state.amount.SendAmountSubtractConverter
|
||||
import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter
|
||||
|
|
@ -45,7 +45,12 @@ internal class SendStateFactory(
|
|||
appCurrencyProvider = appCurrencyProvider,
|
||||
)
|
||||
}
|
||||
|
||||
private val amountSubtractConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendAmountSubtractConverter(
|
||||
currentStateProvider = currentStateProvider,
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
)
|
||||
}
|
||||
private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendAmountStateConverter(
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
|
|
@ -81,6 +86,7 @@ internal class SendStateFactory(
|
|||
event = consumedEvent(),
|
||||
isEditingDisabled = false,
|
||||
isBalanceHidden = false,
|
||||
cryptoCurrencySymbol = "",
|
||||
)
|
||||
|
||||
fun getReadyState(): SendUiState {
|
||||
|
|
@ -90,6 +96,7 @@ internal class SendStateFactory(
|
|||
recipientState = state.recipientState
|
||||
?: recipientStateConverter.convert(SendRecipientStateConverter.Data("", null)),
|
||||
feeState = state.feeState ?: feeStateConverter.convert(Unit),
|
||||
cryptoCurrencySymbol = cryptoCurrencyStatusProvider().currency.symbol,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +108,7 @@ internal class SendStateFactory(
|
|||
?: recipientStateConverter.convert(SendRecipientStateConverter.Data(destinationAddress, memo)),
|
||||
feeState = state.feeState ?: feeStateConverter.convert(Unit),
|
||||
isEditingDisabled = true,
|
||||
cryptoCurrencySymbol = cryptoCurrencyStatusProvider().currency.symbol,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -218,22 +226,12 @@ internal class SendStateFactory(
|
|||
//endregion
|
||||
|
||||
//region send
|
||||
fun onSubtractSelect(isSubtract: Boolean, isAmountSubtractAvailable: Boolean): SendUiState {
|
||||
fun onSubtractSelect(isAmountSubtractAvailable: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val fee = state.feeState?.fee ?: return state
|
||||
val amountState = state.amountState ?: return state
|
||||
val amount = amountState.amountTextField.cryptoAmount
|
||||
val amountValue = amount.value ?: return state
|
||||
val amountToSend = if (isSubtract && isAmountSubtractAvailable) {
|
||||
val feeValue = fee.amount.value ?: return state
|
||||
amountValue.minus(feeValue)
|
||||
} else {
|
||||
amountValue
|
||||
}
|
||||
return state.copy(
|
||||
amountState = amountStateConverter.convert(amountToSend.parseBigDecimal(amount.decimals)),
|
||||
sendState = state.sendState.copy(isSubtract = isSubtract),
|
||||
)
|
||||
|
||||
if (!isAmountSubtractAvailable) return state
|
||||
|
||||
return amountSubtractConverter.convert(Unit)
|
||||
}
|
||||
|
||||
fun getSendingStateUpdate(isSending: Boolean): SendUiState {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import java.math.BigDecimal
|
|||
internal data class SendUiState(
|
||||
val clickIntents: SendClickIntents,
|
||||
val isEditingDisabled: Boolean,
|
||||
val cryptoCurrencySymbol: String,
|
||||
val amountState: SendStates.AmountState? = null,
|
||||
val recipientState: SendStates.RecipientState? = null,
|
||||
val feeState: SendStates.FeeState? = null,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.features.send.impl.presentation.state.amount
|
||||
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SendAmountSubtractConverter(
|
||||
private val currentStateProvider: Provider<SendUiState>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
) : Converter<Unit, SendUiState> {
|
||||
override fun convert(value: Unit): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val amountState = state.amountState ?: return state
|
||||
val feeValue = state.feeState?.fee?.amount?.value ?: return state
|
||||
val amountTextField = amountState.amountTextField
|
||||
val amountValue = amountTextField.cryptoAmount.value ?: return state
|
||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate
|
||||
val cryptoDecimals = amountTextField.cryptoAmount.decimals
|
||||
val fiatDecimals = amountTextField.fiatAmount.decimals
|
||||
|
||||
val decimalCryptoValue = amountValue.minus(feeValue)
|
||||
|
||||
if (decimalCryptoValue < BigDecimal.ZERO) return state
|
||||
|
||||
val decimalFiatValue = decimalCryptoValue.multiply(fiatRate)
|
||||
val cryptoValue = decimalCryptoValue.parseBigDecimal(cryptoDecimals)
|
||||
val fiatValue = decimalFiatValue.parseBigDecimal(fiatDecimals)
|
||||
|
||||
return state.copy(
|
||||
sendState = state.sendState.copy(isSubtract = true),
|
||||
amountState = amountState.copy(
|
||||
amountTextField = amountTextField.copy(
|
||||
value = cryptoValue,
|
||||
fiatValue = fiatValue,
|
||||
cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue),
|
||||
fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -32,11 +32,12 @@ internal class SendAmountFieldConverter(
|
|||
val cryptoDecimal = value.toBigDecimalOrDefault()
|
||||
val cryptoAmount = cryptoDecimal.convertToAmount(cryptoCurrencyStatus.currency)
|
||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate
|
||||
val fiatValue = if (value.isEmpty()) {
|
||||
""
|
||||
val (fiatValue, fiatDecimal) = if (value.isEmpty()) {
|
||||
"" to BigDecimal.ZERO
|
||||
} else {
|
||||
val fiatDecimal = fiatRate?.multiply(cryptoDecimal) ?: BigDecimal.ZERO
|
||||
fiatDecimal.parseBigDecimal(FIAT_DECIMALS)
|
||||
val fiatValue = fiatDecimal.parseBigDecimal(FIAT_DECIMALS)
|
||||
fiatValue to fiatDecimal
|
||||
}
|
||||
val isDoneActionEnabled = !cryptoDecimal.isZero()
|
||||
return SendTextField.AmountField(
|
||||
|
|
@ -50,16 +51,16 @@ internal class SendAmountFieldConverter(
|
|||
keyboardActions = KeyboardActions(onDone = { clickIntents.onNextClick() }),
|
||||
isFiatValue = false,
|
||||
cryptoAmount = cryptoAmount,
|
||||
fiatAmount = getAppCurrencyAmount(appCurrencyProvider()),
|
||||
fiatAmount = getAppCurrencyAmount(fiatDecimal, appCurrencyProvider()),
|
||||
isError = false,
|
||||
error = TextReference.Res(R.string.swapping_insufficient_funds),
|
||||
isFiatUnavailable = fiatRate == null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAppCurrencyAmount(appCurrency: AppCurrency) = Amount(
|
||||
private fun getAppCurrencyAmount(fiatValue: BigDecimal, appCurrency: AppCurrency) = Amount(
|
||||
currencySymbol = appCurrency.symbol,
|
||||
value = BigDecimal.ZERO,
|
||||
value = fiatValue,
|
||||
decimals = FIAT_DECIMALS,
|
||||
type = AmountType.FiatType(appCurrency.code),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,12 @@
|
|||
package com.tangem.features.send.impl.presentation.ui
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.animation.shrinkHorizontally
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -22,161 +16,185 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconStart
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.extensions.shareText
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiStateType
|
||||
|
||||
@Composable
|
||||
internal fun SendNavigationButtons(uiState: SendUiState, currentState: SendUiCurrentScreen) {
|
||||
Row(
|
||||
val isSuccess = uiState.sendState.isSuccess
|
||||
val isSendingState = currentState.type == SendUiStateType.Send && !isSuccess
|
||||
val isSentState = currentState.type == SendUiStateType.Send && isSuccess
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = TangemTheme.dimens.spacing12),
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
SendSecondaryNavigationButton(
|
||||
uiState = uiState,
|
||||
currentState = currentState,
|
||||
SendingText(uiState = uiState, isVisible = isSendingState)
|
||||
SendDoneButtons(
|
||||
txUrl = uiState.sendState.txUrl,
|
||||
onExploreClick = uiState.clickIntents::onExploreClick,
|
||||
onShareClick = uiState.clickIntents::onShareClick,
|
||||
isVisible = isSentState,
|
||||
)
|
||||
SendPrimaryNavigationButton(
|
||||
SendNavigationButton(
|
||||
uiState = uiState,
|
||||
currentState = currentState,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SendSecondaryNavigationButton(uiState: SendUiState, currentState: SendUiCurrentScreen) {
|
||||
val isEditingDisabled = uiState.isEditingDisabled
|
||||
val isFromConfirmation = currentState.isFromConfirmation
|
||||
val isCorrectScreen = currentState.type == SendUiStateType.Amount || currentState.type == SendUiStateType.Fee
|
||||
AnimatedVisibility(
|
||||
visible = !isEditingDisabled && isCorrectScreen && !isFromConfirmation,
|
||||
enter = expandHorizontally(expandFrom = Alignment.End),
|
||||
exit = shrinkHorizontally(shrinkTowards = Alignment.End),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing16)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.background(TangemTheme.colors.button.secondary)
|
||||
.clickable {
|
||||
uiState.clickIntents.onPrevClick()
|
||||
}
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
painter = painterResource(R.drawable.ic_back_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SendPrimaryNavigationButton(
|
||||
private fun SendNavigationButton(
|
||||
uiState: SendUiState,
|
||||
currentState: SendUiCurrentScreen,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val isEditingDisabled = uiState.isEditingDisabled
|
||||
val isSuccess = uiState.sendState.isSuccess
|
||||
val isSending = uiState.sendState.isSending
|
||||
val txUrl = uiState.sendState.txUrl
|
||||
|
||||
val isFromConfirmation = currentState.isFromConfirmation
|
||||
val isCorrectScreen = currentState.type == SendUiStateType.Amount || currentState.type == SendUiStateType.Fee
|
||||
val isSendingState = currentState.type == SendUiStateType.Send && !isSuccess
|
||||
|
||||
val (buttonTextId, buttonClick) = getButtonData(
|
||||
currentState = currentState,
|
||||
isSuccess = isSuccess,
|
||||
uiState = uiState,
|
||||
)
|
||||
|
||||
val isButtonEnabled = isButtonEnabled(
|
||||
currentState = currentState,
|
||||
uiState = uiState,
|
||||
)
|
||||
|
||||
AnimatedContent(
|
||||
targetState = buttonTextId,
|
||||
label = "Update send screen state",
|
||||
val isButtonEnabled = isButtonEnabled(currentState, uiState)
|
||||
val buttonIcon = if (isSendingState) {
|
||||
TangemButtonIconPosition.End(R.drawable.ic_tangem_24)
|
||||
} else {
|
||||
TangemButtonIconPosition.None
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
modifier = modifier,
|
||||
) { textId ->
|
||||
when {
|
||||
currentState.type == SendUiStateType.Send && !isSuccess -> {
|
||||
val hapticFeedback = rememberHapticFeedback(state = currentState, onAction = buttonClick)
|
||||
PrimaryButtonIconEnd(
|
||||
text = stringResource(textId),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
enabled = isButtonEnabled,
|
||||
onClick = hapticFeedback,
|
||||
showProgress = isSending,
|
||||
)
|
||||
}
|
||||
currentState.type == SendUiStateType.Send && isSuccess -> {
|
||||
PrimaryButtonsDone(
|
||||
textRes = textId,
|
||||
txUrl = txUrl,
|
||||
onExploreClick = uiState.clickIntents::onExploreClick,
|
||||
onShareClick = uiState.clickIntents::onShareClick,
|
||||
onDoneClick = buttonClick,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
PrimaryButton(
|
||||
text = stringResource(textId),
|
||||
enabled = isButtonEnabled,
|
||||
onClick = buttonClick,
|
||||
)
|
||||
}
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = !isEditingDisabled && isCorrectScreen && !isFromConfirmation,
|
||||
enter = expandHorizontally(expandFrom = Alignment.End),
|
||||
exit = shrinkHorizontally(shrinkTowards = Alignment.End),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_back_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.background(TangemTheme.colors.button.secondary)
|
||||
.clickable { uiState.clickIntents.onPrevClick() }
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
)
|
||||
}
|
||||
TangemButton(
|
||||
text = stringResource(buttonTextId),
|
||||
icon = buttonIcon,
|
||||
enabled = isButtonEnabled,
|
||||
onClick = {
|
||||
if (isSendingState) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
buttonClick()
|
||||
},
|
||||
showProgress = uiState.sendState.isSending,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SendingText(uiState: SendUiState, isVisible: Boolean, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
modifier = modifier,
|
||||
enter = slideInVertically().plus(fadeIn()),
|
||||
exit = slideOutVertically().plus(fadeOut()),
|
||||
label = "Animate show sending state text",
|
||||
) {
|
||||
val amountState = uiState.amountState
|
||||
val feeState = uiState.feeState
|
||||
val fiatRate = feeState?.rate
|
||||
val fiatAmount = amountState?.amountTextField?.fiatAmount
|
||||
val feeFiat = feeState?.fee?.amount?.value?.multiply(fiatRate)
|
||||
val sendingFiat = feeFiat?.let { fiatAmount?.value?.plus(it) }
|
||||
|
||||
if (feeFiat != null && sendingFiat != null) {
|
||||
val sendingValue = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = sendingFiat,
|
||||
fiatCurrencyCode = feeState.appCurrency.code,
|
||||
fiatCurrencySymbol = feeState.appCurrency.symbol,
|
||||
)
|
||||
val feeValue = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = feeFiat,
|
||||
fiatCurrencyCode = feeState.appCurrency.code,
|
||||
fiatCurrencySymbol = feeState.appCurrency.symbol,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.send_summary_transaction_description, sendingValue, feeValue),
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PrimaryButtonsDone(
|
||||
@StringRes textRes: Int,
|
||||
private fun SendDoneButtons(
|
||||
txUrl: String,
|
||||
onExploreClick: () -> Unit,
|
||||
onShareClick: () -> Unit,
|
||||
onDoneClick: () -> Unit,
|
||||
isVisible: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val context = LocalContext.current
|
||||
|
||||
Column(modifier = modifier) {
|
||||
if (txUrl.isNotBlank()) {
|
||||
Row {
|
||||
SecondaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_explore),
|
||||
iconResId = R.drawable.ic_web_24,
|
||||
onClick = onExploreClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
SpacerW12()
|
||||
SecondaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_share),
|
||||
iconResId = R.drawable.ic_share_24,
|
||||
onClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
context.shareText(txUrl)
|
||||
onShareClick()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
SpacerH12()
|
||||
AnimatedVisibility(
|
||||
visible = isVisible && txUrl.isNotBlank(),
|
||||
modifier = modifier,
|
||||
enter = slideInVertically().plus(fadeIn()),
|
||||
exit = slideOutVertically().plus(fadeOut()),
|
||||
label = "Animate show sent state buttons",
|
||||
) {
|
||||
Row(modifier = Modifier.padding(TangemTheme.dimens.spacing12)) {
|
||||
SecondaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_explore),
|
||||
iconResId = R.drawable.ic_web_24,
|
||||
onClick = onExploreClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
SpacerW12()
|
||||
SecondaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_share),
|
||||
iconResId = R.drawable.ic_share_24,
|
||||
onClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
context.shareText(txUrl)
|
||||
onShareClick()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
PrimaryButton(
|
||||
text = stringResource(id = textRes),
|
||||
enabled = true,
|
||||
onClick = onDoneClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
package com.tangem.features.send.impl.presentation.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen
|
||||
|
|
@ -40,12 +41,22 @@ internal fun SendScreen(uiState: SendUiState, currentStateFlow: StateFlow<SendUi
|
|||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
val titleRes = when (currentState.value.type) {
|
||||
SendUiStateType.Amount -> R.string.send_amount_label
|
||||
SendUiStateType.Recipient -> R.string.send_recipient_label
|
||||
SendUiStateType.Fee -> R.string.common_fee_selector_title
|
||||
SendUiStateType.Send -> if (!uiState.sendState.isSuccess) R.string.send_confirm_label else null
|
||||
SendUiStateType.Amount -> resourceReference(R.string.send_amount_label)
|
||||
SendUiStateType.Recipient -> resourceReference(R.string.send_recipient_label)
|
||||
SendUiStateType.Fee -> resourceReference(R.string.common_fee_selector_title)
|
||||
SendUiStateType.Send -> if (!uiState.sendState.isSuccess) {
|
||||
resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencySymbol))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
val isSending = currentState.value.type == SendUiStateType.Send && !uiState.sendState.isSuccess
|
||||
val subtitleRes = if (isSending) {
|
||||
uiState.amountState?.walletName
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val iconRes = if (currentState.value.type == SendUiStateType.Recipient) {
|
||||
R.drawable.ic_qrcode_scan_24
|
||||
} else {
|
||||
|
|
@ -53,12 +64,14 @@ internal fun SendScreen(uiState: SendUiState, currentStateFlow: StateFlow<SendUi
|
|||
}
|
||||
|
||||
AppBarWithBackButtonAndIcon(
|
||||
text = titleRes?.let { stringResource(it) },
|
||||
text = titleRes?.resolveReference(),
|
||||
subtitle = subtitleRes,
|
||||
onBackClick = uiState.clickIntents::popBackStack,
|
||||
onIconClick = uiState.clickIntents::onQrCodeScanClick,
|
||||
backIconRes = R.drawable.ic_close_24,
|
||||
iconRes = iconRes,
|
||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
||||
modifier = Modifier.height(TangemTheme.dimens.size56),
|
||||
)
|
||||
SendScreenContent(
|
||||
uiState = uiState,
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@ internal fun AmountFieldContainer(
|
|||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing4,
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.background(TangemTheme.colors.background.action),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.tangem.core.ui.components.ResizableText
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
|
||||
@Composable
|
||||
internal fun AmountBlock(
|
||||
amountState: SendStates.AmountState,
|
||||
isSuccess: Boolean,
|
||||
isEditingDisabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val amount = amountState.amountTextField
|
||||
|
||||
val cryptoAmount = getAmountWithSymbol(amount.value, amount.cryptoAmount.currencySymbol)
|
||||
val fiatAmount = getAmountWithSymbol(amount.fiatValue, amount.fiatAmount.currencySymbol)
|
||||
val backgroundColor = if (isEditingDisabled) {
|
||||
TangemTheme.colors.button.disabled
|
||||
} else {
|
||||
TangemTheme.colors.background.action
|
||||
}
|
||||
|
||||
val (firstAmount, secondAmount) = if (amount.isFiatValue) {
|
||||
fiatAmount to cryptoAmount
|
||||
} else {
|
||||
cryptoAmount to fiatAmount
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(backgroundColor)
|
||||
.clickable(enabled = !isSuccess && !isEditingDisabled, onClick = onClick)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing14,
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
TokenIcon(state = amountState.tokenIconState)
|
||||
ResizableText(
|
||||
text = firstAmount,
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
Text(
|
||||
text = secondAmount,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens.spacing4),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAmountWithSymbol(amount: String, symbol: String): String {
|
||||
return "$amount $symbol"
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
|
||||
@Composable
|
||||
internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) {
|
||||
val fee = feeState.fee ?: return
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
|
||||
val (title, icon) = when (feeSelectorState.selectedFee) {
|
||||
FeeType.Slow -> R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24
|
||||
FeeType.Market -> R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
|
||||
FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24
|
||||
FeeType.Custom -> R.string.common_fee_selector_option_custom to R.drawable.ic_edit_24
|
||||
}
|
||||
val feeCryptoValue = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = fee.amount.value,
|
||||
cryptoCurrency = fee.amount.currencySymbol,
|
||||
decimals = fee.amount.decimals,
|
||||
)
|
||||
val feeFiatValue = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = feeState.rate?.let { fee.amount.value?.multiply(it) },
|
||||
fiatCurrencyCode = feeState.appCurrency.code,
|
||||
fiatCurrencySymbol = feeState.appCurrency.symbol,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable(enabled = !isSuccess) { onClick() }
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing12,
|
||||
horizontal = TangemTheme.dimens.spacing14,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.common_network_fee_title),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(icon),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(title),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
EllipsisText(
|
||||
text = feeCryptoValue,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.End,
|
||||
ellipsis = TextEllipsis.OffsetEnd(fee.amount.currencySymbol.length),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = TangemTheme.dimens.spacing4),
|
||||
)
|
||||
Text(
|
||||
text = "($feeFiatValue)",
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
|
||||
@Composable
|
||||
internal fun RecipientBlock(
|
||||
recipientState: SendStates.RecipientState,
|
||||
isSuccess: Boolean,
|
||||
isEditingDisabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val backgroundColor = if (isEditingDisabled) {
|
||||
TangemTheme.colors.button.disabled
|
||||
} else {
|
||||
TangemTheme.colors.background.action
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(backgroundColor)
|
||||
.clickable(enabled = !isSuccess && !isEditingDisabled, onClick = onClick)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
AddressBlock(recipientState.addressTextField)
|
||||
MemoBlock(recipientState.memoTextField)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddressBlock(address: SendTextField.RecipientAddress) {
|
||||
Text(
|
||||
text = address.label.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
IdentIcon(
|
||||
address = address.value,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius18))
|
||||
.background(TangemTheme.colors.background.tertiary),
|
||||
)
|
||||
Text(
|
||||
text = address.value,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MemoBlock(memo: SendTextField.RecipientMemo?) {
|
||||
val showMemo = memo != null && memo.value.isNotBlank()
|
||||
if (showMemo) {
|
||||
HorizontalDivider(
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
|
||||
)
|
||||
Text(
|
||||
text = memo?.label?.resolveReference().orEmpty(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Text(
|
||||
text = memo?.value.orEmpty(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,235 +1,121 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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 com.tangem.common.Strings
|
||||
import com.tangem.core.ui.components.inputrow.InputRowDefault
|
||||
import com.tangem.core.ui.components.inputrow.InputRowImage
|
||||
import com.tangem.core.ui.components.inputrow.InputRowRecipientDefault
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter.formatCryptoAmount
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount
|
||||
import com.tangem.domain.tokens.model.AmountType
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.SendNotification
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private const val TAP_HELP_KEY = "TAP_HELP_KEY"
|
||||
private const val BLOCKS_KEY = "BLOCKS_KEY"
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun SendContent(uiState: SendUiState) {
|
||||
val amountState = uiState.amountState ?: return
|
||||
val recipientState = uiState.recipientState ?: return
|
||||
val feeState = uiState.feeState ?: return
|
||||
val sendState = uiState.sendState
|
||||
|
||||
val isSuccess = sendState.isSuccess
|
||||
val timestamp = sendState.transactionDate
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
item {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12)) {
|
||||
AnimatedVisibility(visible = isSuccess) {
|
||||
TransactionDoneTitle(
|
||||
titleRes = R.string.sent_transaction_sent_title,
|
||||
date = timestamp,
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(visible = !isSuccess) {
|
||||
FromWallet(
|
||||
walletName = amountState.walletName,
|
||||
walletBalance = amountState.walletBalance.resolveReference(),
|
||||
isBalanceHidden = uiState.isBalanceHidden,
|
||||
)
|
||||
}
|
||||
RecipientBlock(
|
||||
recipientState = recipientState,
|
||||
isSuccess = isSuccess,
|
||||
isEditingDisabled = uiState.isEditingDisabled,
|
||||
onClick = uiState.clickIntents::showRecipient,
|
||||
)
|
||||
AmountBlock(
|
||||
amountState = amountState,
|
||||
isSuccess = isSuccess,
|
||||
isEditingDisabled = uiState.isEditingDisabled,
|
||||
onClick = uiState.clickIntents::showAmount,
|
||||
)
|
||||
FeeBlock(
|
||||
feeState = feeState,
|
||||
isSuccess = isSuccess,
|
||||
onClick = uiState.clickIntents::showFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
blocks(uiState)
|
||||
tapHelp(isDisplay = sendState.notifications.isEmpty() && !uiState.sendState.isSuccess)
|
||||
notifications(sendState.notifications)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FromWallet(walletName: String, walletBalance: String, isBalanceHidden: Boolean) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.button.disabled)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append(stringResource(R.string.send_from_wallet_android))
|
||||
append(" ")
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(walletName)
|
||||
}
|
||||
},
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
val balance = if (isBalanceHidden) Strings.STARS else walletBalance
|
||||
AnimatedContent(
|
||||
targetState = balance,
|
||||
label = "Hide Balance Animation",
|
||||
) {
|
||||
Text(
|
||||
text = it,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
private fun LazyListScope.blocks(uiState: SendUiState) {
|
||||
val amountState = uiState.amountState ?: return
|
||||
val recipientState = uiState.recipientState ?: return
|
||||
val feeState = uiState.feeState ?: return
|
||||
val sendState = uiState.sendState
|
||||
val isSuccess = sendState.isSuccess
|
||||
val timestamp = sendState.transactionDate
|
||||
|
||||
item(key = BLOCKS_KEY) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12)) {
|
||||
AnimatedVisibility(
|
||||
visible = isSuccess,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
TransactionDoneTitle(
|
||||
titleRes = R.string.sent_transaction_sent_title,
|
||||
date = timestamp,
|
||||
)
|
||||
}
|
||||
RecipientBlock(
|
||||
recipientState = recipientState,
|
||||
isSuccess = isSuccess,
|
||||
isEditingDisabled = uiState.isEditingDisabled,
|
||||
onClick = uiState.clickIntents::showRecipient,
|
||||
)
|
||||
AmountBlock(
|
||||
amountState = amountState,
|
||||
isSuccess = isSuccess,
|
||||
isEditingDisabled = uiState.isEditingDisabled,
|
||||
onClick = uiState.clickIntents::showAmount,
|
||||
)
|
||||
FeeBlock(
|
||||
feeState = feeState,
|
||||
isSuccess = isSuccess,
|
||||
onClick = uiState.clickIntents::showFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountBlock(
|
||||
amountState: SendStates.AmountState,
|
||||
isSuccess: Boolean,
|
||||
isEditingDisabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val amount = amountState.amountTextField
|
||||
|
||||
val cryptoAmount = formatCryptoAmount(
|
||||
cryptoAmount = amount.cryptoAmount.value,
|
||||
cryptoCurrency = amount.cryptoAmount.currencySymbol,
|
||||
decimals = amount.cryptoAmount.decimals,
|
||||
)
|
||||
val fiatAmount = formatFiatAmount(
|
||||
fiatAmount = amount.fiatAmount.value,
|
||||
fiatCurrencyCode = (amount.fiatAmount.type as AmountType.FiatType).code,
|
||||
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
|
||||
)
|
||||
val backgroundColor = if (isEditingDisabled) {
|
||||
TangemTheme.colors.button.disabled
|
||||
} else {
|
||||
TangemTheme.colors.background.action
|
||||
}
|
||||
InputRowImage(
|
||||
title = TextReference.Res(R.string.send_amount_label),
|
||||
subtitle = TextReference.Str(cryptoAmount),
|
||||
caption = TextReference.Str(fiatAmount),
|
||||
tokenIconState = amountState.tokenIconState,
|
||||
showNetworkIcon = true,
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(backgroundColor)
|
||||
.clickable(enabled = !isSuccess && !isEditingDisabled) { onClick() },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RecipientBlock(
|
||||
recipientState: SendStates.RecipientState,
|
||||
isSuccess: Boolean,
|
||||
isEditingDisabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val address = recipientState.addressTextField
|
||||
val memo = recipientState.memoTextField
|
||||
val backgroundColor = if (isEditingDisabled) {
|
||||
TangemTheme.colors.button.disabled
|
||||
} else {
|
||||
TangemTheme.colors.background.action
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(backgroundColor)
|
||||
.clickable(enabled = !isSuccess && !isEditingDisabled) { onClick() },
|
||||
) {
|
||||
val showMemo = memo != null && memo.value.isNotBlank()
|
||||
InputRowRecipientDefault(
|
||||
title = TextReference.Res(R.string.send_recipient),
|
||||
value = address.value,
|
||||
showDivider = showMemo,
|
||||
)
|
||||
if (showMemo) {
|
||||
InputRowDefault(
|
||||
title = TextReference.Res(R.string.send_extras_hint_memo),
|
||||
text = TextReference.Str(memo?.value.orEmpty()),
|
||||
)
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun LazyListScope.tapHelp(isDisplay: Boolean, modifier: Modifier = Modifier) {
|
||||
if (isDisplay) {
|
||||
item(key = TAP_HELP_KEY) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.animateItemPlacement(),
|
||||
) {
|
||||
val background = TangemTheme.colors.button.secondary
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.send_hint_shape_12),
|
||||
tint = TangemTheme.colors.button.secondary,
|
||||
contentDescription = null,
|
||||
modifier = Modifier,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.send_summary_tap_hint),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(background)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing14,
|
||||
vertical = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) {
|
||||
val fee = feeState.fee ?: return
|
||||
val feeCryptoValue = formatCryptoAmount(
|
||||
cryptoAmount = fee.amount.value,
|
||||
cryptoCurrency = fee.amount.currencySymbol,
|
||||
decimals = fee.amount.decimals,
|
||||
)
|
||||
val feeFiatValue = formatFiatAmount(
|
||||
fiatAmount = feeState.rate?.let { fee.amount.value?.multiply(it) },
|
||||
fiatCurrencyCode = feeState.appCurrency.code,
|
||||
fiatCurrencySymbol = feeState.appCurrency.symbol,
|
||||
)
|
||||
InputRowDefault(
|
||||
title = resourceReference(R.string.common_network_fee_title),
|
||||
text = resourceReference(
|
||||
id = R.string.send_wallet_balance_format,
|
||||
wrappedList(feeCryptoValue, feeFiatValue),
|
||||
),
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable(enabled = !isSuccess) { onClick() },
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
internal fun LazyListScope.notifications(configs: ImmutableList<SendNotification>, modifier: Modifier = Modifier) {
|
||||
items(
|
||||
|
|
|
|||
|
|
@ -466,7 +466,6 @@ internal class SendViewModel @Inject constructor(
|
|||
)
|
||||
return
|
||||
} else {
|
||||
uiState = stateFactory.onSubtractSelect(false, isAmountSubtractAvailable)
|
||||
analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(false))
|
||||
}
|
||||
if (checkIfFeeTooLow(uiState)) {
|
||||
|
|
@ -606,7 +605,7 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onSubtractSelect() {
|
||||
uiState = stateFactory.onSubtractSelect(true, isAmountSubtractAvailable)
|
||||
uiState = stateFactory.onSubtractSelect(isAmountSubtractAvailable)
|
||||
stateRouter.showSend()
|
||||
analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(true))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="12dp"
|
||||
android:height="7dp"
|
||||
android:viewportWidth="12"
|
||||
android:viewportHeight="7">
|
||||
<path
|
||||
android:pathData="M4.988,0.785C3.819,3.428 1.89,7 0,7H12C10.115,7 8.269,3.447 7.157,0.806C6.784,-0.08 5.376,-0.094 4.988,0.785Z"
|
||||
android:fillColor="#EBEBEB"/>
|
||||
</vector>
|
||||
Loading…
Add table
Add a link
Reference in a new issue