Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-08 12:10:42 +03:00
commit f80f0f32ee
19 changed files with 264 additions and 259 deletions

View file

@ -21,10 +21,10 @@ import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.format.bigdecimal.anyDecimals
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.BigDecimalFormatter
import java.math.BigDecimal
@Composable
@ -33,15 +33,11 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
val amount = amountState.amountTextField
val cryptoAmount = formatWithSymbol(amount.value, amount.cryptoAmount.currencySymbol)
val fiatAmount = BigDecimalFormatter.formatFiatAmount(
fiatAmount = amount.fiatAmount.value,
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
fiatCurrencyCode = amountState.appCurrencyCode,
)
val backgroundColor = if (isEditingDisabled) {
TangemTheme.colors.button.disabled
} else {
TangemTheme.colors.background.action
val fiatAmount = amount.fiatAmount.value.format {
fiat(
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
fiatCurrencyCode = amountState.appCurrencyCode,
)
}
val (firstAmount, secondAmount) = if (amount.isFiatValue) {
@ -54,7 +50,7 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(backgroundColor)
.background(TangemTheme.colors.background.action)
.clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick)
.padding(TangemTheme.dimens.spacing16),
) {

View file

@ -12,6 +12,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -34,20 +36,20 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod
url2 = null,
url3 = null,
modifier = Modifier
.padding(horizontal = 34.dp)
.padding(top = 56.dp)
.padding(top = 64.dp)
.padding(horizontal = 24.dp)
.weight(ALL_STEPS_TOP_CONTAINER_WEIGHT)
.fillMaxWidth(),
state = WalletArtworksState.Folded,
)
Column(
Modifier
modifier = Modifier.weight(1 - ALL_STEPS_TOP_CONTAINER_WEIGHT)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(32.dp)
.weight(1.0f),
.padding(horizontal = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
SpacerHMax()
Text(
text = stringResource(R.string.onboarding_create_wallet_header),
style = TangemTheme.typography.h2,
@ -55,7 +57,7 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 16.dp),
)
SpacerH16()
Text(
text = stringResource(R.string.onboarding_create_wallet_body),
style = TangemTheme.typography.body1,
@ -63,6 +65,7 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 12.dp),
)
SpacerHMax()
}
PrimaryButtonIconEnd(

View file

@ -32,54 +32,58 @@ fun OnboardingNoteTopUpHeader(
) {
Box(
modifier = modifier
.heightIn(min = 228.dp)
.heightIn(min = 180.dp)
.widthIn(max = 450.dp),
contentAlignment = Alignment.Center,
) {
Box(
modifier = Modifier
.padding(vertical = 24.dp)
.padding(vertical = 24.dp, horizontal = 16.dp)
.fillMaxSize()
.background(
TangemTheme.colors.button.secondary,
shape = TangemTheme.shapes.roundedCornersMedium,
),
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 32.dp),
contentAlignment = Alignment.Center,
) {
WalletCard(
modifier = Modifier.width(120.dp),
url = cardArtworkUrl,
)
SpacerHMax()
Text(
text = stringResourceSafe(R.string.common_balance_title),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
SpacerH8()
Text(
modifier = if (balance.isEmpty()) {
Modifier
.width(120.dp)
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius3))
.shimmer(LocalTangemShimmer.current)
} else {
Modifier
},
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
text = balance,
)
SpacerHMax()
RefreshButton(
isRefreshing = isRefreshing,
onRefreshBalanceClick = onRefreshBalanceClick,
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 32.dp),
) {
SpacerHMax()
Text(
text = stringResourceSafe(R.string.common_balance_title),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
SpacerH8()
Text(
modifier = if (balance.isEmpty()) {
Modifier
.width(120.dp)
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius3))
.shimmer(LocalTangemShimmer.current)
} else {
Modifier
},
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
text = balance,
)
SpacerHMax()
}
}
WalletCard(
modifier = Modifier.width(120.dp).align(Alignment.TopCenter),
url = cardArtworkUrl,
)
RefreshButton(
modifier = Modifier.align(Alignment.BottomCenter),
isRefreshing = isRefreshing,
onRefreshBalanceClick = onRefreshBalanceClick,
)
}
}

View file

@ -14,10 +14,13 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheet
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.note.impl.ALL_STEPS_TOP_CONTAINER_WEIGHT
import com.tangem.features.onboarding.v2.note.impl.child.topup.ui.state.OnboardingNoteTopUpUM
@Composable
@ -35,20 +38,19 @@ fun OnboardingNoteTopUp(state: OnboardingNoteTopUpUM, modifier: Modifier = Modif
onRefreshBalanceClick = state.onRefreshBalanceClick,
isRefreshing = state.isRefreshing,
modifier = Modifier
.padding(horizontal = 34.dp)
.padding(top = 16.dp)
.height(228.dp)
.padding(top = 64.dp)
.padding(horizontal = 24.dp)
.weight(ALL_STEPS_TOP_CONTAINER_WEIGHT)
.fillMaxWidth(),
)
Column(
modifier = Modifier
modifier = Modifier.weight(1 - ALL_STEPS_TOP_CONTAINER_WEIGHT)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(horizontal = 32.dp)
.padding(top = 16.dp)
.weight(1.0f),
.padding(horizontal = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
SpacerHMax()
Text(
text = stringResourceSafe(R.string.onboarding_topup_title),
style = TangemTheme.typography.h2,
@ -65,13 +67,14 @@ fun OnboardingNoteTopUp(state: OnboardingNoteTopUpUM, modifier: Modifier = Modif
} else {
stringResourceSafe(R.string.onboarding_top_up_body)
}
SpacerH16()
Text(
text = text,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 12.dp),
)
SpacerHMax()
}
BottomButtons(state)

View file

@ -15,7 +15,6 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.features.send.v2.send.ui.state.ButtonsUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.feedback.GetCardInfoUseCase
@ -49,6 +48,7 @@ import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmSe
import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmSentStateTransformer
import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmationNotificationsTransformer
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
import com.tangem.features.send.v2.send.ui.state.ButtonsUM
import com.tangem.features.send.v2.send.ui.state.SendUM
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM
import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadTrigger
@ -274,20 +274,14 @@ internal class SendConfirmModel @Inject constructor(
private fun initialState() {
val confirmUM = uiState.value.confirmUM
val amountUM = uiState.value.amountUM
val feeUM = uiState.value.feeUM
modelScope.launch {
val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false }
if (confirmUM is ConfirmUM.Empty || feeUM is FeeUM.Empty) {
if (confirmUM is ConfirmUM.Empty) {
_uiState.update {
it.copy(
confirmUM = SendConfirmInitialStateTransformer(
appCurrency = appCurrency,
feeUM = feeUM,
amountUM = amountUM,
isShowTapHelp = isShowTapHelp,
isSubtracted = false,
).transform(uiState.value.confirmUM),
)
}
@ -472,15 +466,17 @@ internal class SendConfirmModel @Inject constructor(
feeError = feeError,
),
)
}
_uiState.update {
it.copy(
confirmUM = SendConfirmationNotificationsTransformer(
feeUM = uiState.value.feeUM,
analyticsEventHandler = analyticsEventHandler,
cryptoCurrency = cryptoCurrencyStatus.currency,
).transform(uiState.value.confirmUM),
)
_uiState.update {
it.copy(
confirmUM = SendConfirmationNotificationsTransformer(
feeUM = uiState.value.feeUM,
amountUM = uiState.value.amountUM,
analyticsEventHandler = analyticsEventHandler,
cryptoCurrency = cryptoCurrencyStatus.currency,
appCurrency = appCurrency,
).transform(uiState.value.confirmUM),
)
}
}
}
@ -489,7 +485,7 @@ internal class SendConfirmModel @Inject constructor(
flow = uiState,
flow2 = params.currentRoute,
transform = { state, route -> state to route },
).distinctUntilChanged().onEach { (state, _) ->
).onEach { (state, _) ->
val amountUM = state.amountUM as? AmountState.Data
val confirmUM = state.confirmUM
params.callback.onResult(

View file

@ -1,137 +1,19 @@
package com.tangem.features.send.v2.send.confirm.model.transformers
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fee
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.features.send.v2.impl.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
internal class SendConfirmInitialStateTransformer(
private val appCurrency: AppCurrency,
private val feeUM: FeeUM,
private val amountUM: AmountState,
private val isShowTapHelp: Boolean,
private val isSubtracted: Boolean,
) : Transformer<ConfirmUM> {
override fun transform(prevState: ConfirmUM): ConfirmUM {
return ConfirmUM.Content(
isSending = false,
showTapHelp = isShowTapHelp,
sendingFooter = getSendingFooterText(),
sendingFooter = TextReference.EMPTY,
notifications = persistentListOf(),
)
}
private fun getSendingFooterText(): TextReference {
val feeUM = feeUM as? FeeUM.Content
val amountUM = amountUM as? AmountState.Data
if (feeUM == null || amountUM == null) return TextReference.EMPTY
val fee = (feeUM.feeSelectorUM as? FeeSelectorUM.Content)?.selectedFee
val fiatAmountValue = amountUM.amountTextField.fiatAmount.value
val fiatFeeValue = feeUM.rate?.let { fee?.amount?.value?.multiply(it) }
val fiatSendingValue = if (isSubtracted) {
fiatAmountValue
} else {
if (feeUM.isFeeConvertibleToFiat) {
fiatFeeValue?.let { fiatAmountValue?.plus(it) }
} else {
fiatAmountValue
}
}
val fiatSending = fiatSendingValue.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
val fiatFee = formatFiatFee(
amount = fee?.amount,
isFeeConvertibleToFiat = feeUM.isFeeConvertibleToFiat,
isFeeApproximate = feeUM.isFeeApproximate,
)
return if (feeUM.isTronToken && fee is Fee.Tron) {
getTokenFeeSendingText(
fee = fee,
fiatFee = fiatFee,
fiatSending = fiatSending,
)
} else {
resourceReference(
id = if (feeUM.isFeeConvertibleToFiat) {
R.string.send_summary_transaction_description
} else {
R.string.send_summary_transaction_description_no_fiat_fee
},
formatArgs = wrappedList(fiatSending, fiatFee),
)
}
}
private fun formatFiatFee(amount: Amount?, isFeeConvertibleToFiat: Boolean, isFeeApproximate: Boolean): String {
return if (isFeeConvertibleToFiat) {
amount?.value.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
} else {
amount?.value.format {
crypto(
decimals = amount?.decimals ?: 0,
symbol = amount?.currencySymbol.orEmpty(),
).fee(
canBeLower = isFeeApproximate,
)
}
}
}
private fun getTokenFeeSendingText(fee: Fee.Tron, fiatFee: String, fiatSending: String): TextReference {
val suffix = when {
fee.remainingEnergy == 0L -> {
resourceReference(
R.string.send_summary_transaction_description_suffix_including,
wrappedList(fiatFee),
)
}
fee.feeEnergy <= fee.remainingEnergy -> {
resourceReference(
R.string.send_summary_transaction_description_suffix_fee_covered,
wrappedList(fee.feeEnergy),
)
}
else -> {
resourceReference(
R.string.send_summary_transaction_description_suffix_fee_reduced,
wrappedList(fee.remainingEnergy),
)
}
}
val prefix = resourceReference(
R.string.send_summary_transaction_description_prefix,
wrappedList(fiatSending),
)
return combinedReference(prefix, COMMA_SEPARATOR, suffix)
}
companion object {
private val COMMA_SEPARATOR = stringReference(", ")
}
}

View file

@ -1,10 +1,20 @@
package com.tangem.features.send.v2.send.confirm.model.transformers
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fee
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
import com.tangem.features.send.v2.subcomponents.fee.model.checkIfFeeTooHigh
@ -16,13 +26,16 @@ import kotlinx.collections.immutable.toPersistentList
internal class SendConfirmationNotificationsTransformer(
private val feeUM: FeeUM,
private val amountUM: AmountState,
private val analyticsEventHandler: AnalyticsEventHandler,
private val cryptoCurrency: CryptoCurrency,
private val appCurrency: AppCurrency,
) : Transformer<ConfirmUM> {
override fun transform(prevState: ConfirmUM): ConfirmUM {
val state = prevState as? ConfirmUM.Content ?: return prevState
val feeUM = feeUM as? FeeUM.Content ?: return prevState
return state.copy(
sendingFooter = getSendingFooterText(),
notifications = buildList {
addTooHighNotification(feeUM.feeSelectorUM)
addTooLowNotification(feeUM)
@ -52,4 +65,103 @@ internal class SendConfirmationNotificationsTransformer(
add(NotificationUM.Warning.TooHigh(diff))
}
}
private fun getSendingFooterText(): TextReference {
val feeUM = feeUM as? FeeUM.Content
val amountUM = amountUM as? AmountState.Data
val fee = (feeUM?.feeSelectorUM as? FeeSelectorUM.Content)?.selectedFee
if (fee == null || amountUM == null) return TextReference.EMPTY
val fiatAmountValue = amountUM.amountTextField.fiatAmount.value
val fiatFeeValue = feeUM.rate?.let { fee.amount.value?.multiply(it) }
val fiatSendingValue = if (feeUM.isFeeConvertibleToFiat) {
fiatFeeValue?.let { fiatAmountValue?.plus(it) }
} else {
fiatAmountValue
}
val fiatSending = fiatSendingValue.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
val fiatFee = formatFiatFee(
amount = fee.amount,
isFeeConvertibleToFiat = feeUM.isFeeConvertibleToFiat,
isFeeApproximate = feeUM.isFeeApproximate,
)
return if (feeUM.isTronToken && fee is Fee.Tron) {
getTokenFeeSendingText(
fee = fee,
fiatFee = fiatFee,
fiatSending = fiatSending,
)
} else {
resourceReference(
id = if (feeUM.isFeeConvertibleToFiat) {
R.string.send_summary_transaction_description
} else {
R.string.send_summary_transaction_description_no_fiat_fee
},
formatArgs = wrappedList(fiatSending, fiatFee),
)
}
}
private fun formatFiatFee(amount: Amount?, isFeeConvertibleToFiat: Boolean, isFeeApproximate: Boolean): String {
return if (isFeeConvertibleToFiat) {
amount?.value.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
} else {
amount?.value.format {
crypto(
decimals = amount?.decimals ?: 0,
symbol = amount?.currencySymbol.orEmpty(),
).fee(
canBeLower = isFeeApproximate,
)
}
}
}
private fun getTokenFeeSendingText(fee: Fee.Tron, fiatFee: String, fiatSending: String): TextReference {
val suffix = when {
fee.remainingEnergy == 0L -> {
resourceReference(
R.string.send_summary_transaction_description_suffix_including,
wrappedList(fiatFee),
)
}
fee.feeEnergy <= fee.remainingEnergy -> {
resourceReference(
R.string.send_summary_transaction_description_suffix_fee_covered,
wrappedList(fee.feeEnergy),
)
}
else -> {
resourceReference(
R.string.send_summary_transaction_description_suffix_fee_reduced,
wrappedList(fee.remainingEnergy),
)
}
}
val prefix = resourceReference(
R.string.send_summary_transaction_description_prefix,
wrappedList(fiatSending),
)
return combinedReference(prefix, COMMA_SEPARATOR, suffix)
}
companion object {
private val COMMA_SEPARATOR = stringReference(", ")
}
}

View file

@ -23,12 +23,10 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.Keyboard
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.core.ui.utils.toTimeFormat
@ -83,21 +81,22 @@ internal fun SendConfirmContent(
)
}
}
SendingText(confirmUM = confirmUM)
SpacerHMax()
SendingText(footerText = confirmUM?.sendingFooter ?: TextReference.EMPTY)
}
}
@Composable
private fun SendingText(confirmUM: ConfirmUM.Content?, modifier: Modifier = Modifier) {
var isVisibleProxy by remember { mutableStateOf(confirmUM != null) }
private fun SendingText(footerText: TextReference, modifier: Modifier = Modifier) {
var isVisibleProxy by remember { mutableStateOf(footerText != TextReference.EMPTY) }
val keyboard by keyboardAsState()
// the text should appear when the keyboard is closed
LaunchedEffect(confirmUM != null, keyboard) {
if (confirmUM != null && keyboard is Keyboard.Opened) {
LaunchedEffect(footerText != TextReference.EMPTY, keyboard) {
if (footerText != TextReference.EMPTY && keyboard is Keyboard.Opened) {
return@LaunchedEffect
}
isVisibleProxy = confirmUM != null
isVisibleProxy = footerText != TextReference.EMPTY
}
AnimatedVisibility(
@ -107,9 +106,8 @@ private fun SendingText(confirmUM: ConfirmUM.Content?, modifier: Modifier = Modi
exit = fadeOut(tween(durationMillis = 300)),
label = "Animate show sending state text",
) {
val wrappedConfirmUM = remember(this) { requireNotNull(confirmUM?.sendingFooter) }
Text(
text = wrappedConfirmUM.resolveAnnotatedReference(),
text = footerText.resolveAnnotatedReference(),
textAlign = TextAlign.Center,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,

View file

@ -29,6 +29,7 @@ interface SendAmountReduceListener {
internal class DefaultSendAmountReduceTrigger @Inject constructor() :
SendAmountReduceTrigger,
SendAmountReduceListener {
override val reduceToTriggerFlow = MutableSharedFlow<BigDecimal>()
override val reduceByTriggerFlow = MutableSharedFlow<ReduceByData>()
override val ignoreReduceTriggerFlow = MutableSharedFlow<Unit>()

View file

@ -3,25 +3,21 @@ package com.tangem.features.send.v2.subcomponents.amount.di
import com.tangem.features.send.v2.subcomponents.amount.DefaultSendAmountReduceTrigger
import com.tangem.features.send.v2.subcomponents.amount.SendAmountReduceListener
import com.tangem.features.send.v2.subcomponents.amount.SendAmountReduceTrigger
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
internal object SendAmountModule {
internal interface SendAmountModule {
@Provides
@Singleton
fun provideSendAmountReduceTrigger(): SendAmountReduceTrigger {
return DefaultSendAmountReduceTrigger()
}
@Binds
fun provideSendAmountReduceTrigger(impl: DefaultSendAmountReduceTrigger): SendAmountReduceTrigger
@Provides
@Singleton
fun provideSendAmountReduceListener(): SendAmountReduceListener {
return DefaultSendAmountReduceTrigger()
}
@Binds
fun provideSendAmountReduceListener(impl: DefaultSendAmountReduceTrigger): SendAmountReduceListener
}

View file

@ -212,7 +212,7 @@ internal class SendAmountModel @Inject constructor(
flow = uiState,
flow2 = params.currentRoute,
transform = { state, route -> state to route },
).distinctUntilChanged().onEach { (state, route) ->
).onEach { (state, route) ->
params.callback.onNavigationResult(
NavigationUM.Content(
title = resourceReference(R.string.send_amount_label),

View file

@ -10,6 +10,9 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
import com.tangem.domain.tokens.GetNetworkAddressesUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
@ -58,6 +61,8 @@ internal class SendDestinationModel @Inject constructor(
private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase,
private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase,
private val isUtxoConsolidationAvailableUseCase: IsUtxoConsolidationAvailableUseCase,
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
private val parseQrCodeUseCase: ParseQrCodeUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model(), SendDestinationClickIntents {
private val params: SendDestinationComponentParams = paramsContainer.require()
@ -76,12 +81,14 @@ internal class SendDestinationModel @Inject constructor(
init {
initSenderAddress()
configDestinationNavigation()
subscribeOnQRScannerResult()
initialState()
}
private fun initSenderAddress() {
modelScope.launch {
senderAddresses.value = getNetworkAddressesUseCase.invokeSync(userWalletId, cryptoCurrency.network)
.filter { cryptoCurrency.id == it.cryptoCurrency.id }
}
senderAddresses.onEach {
getWalletsAndRecent()
@ -143,6 +150,28 @@ internal class SendDestinationModel @Inject constructor(
)
}
private fun subscribeOnQRScannerResult() {
listenToQrScanningUseCase(SourceType.SEND)
.getOrElse { emptyFlow() }
.onEach(::onQrCodeScanned)
.launchIn(modelScope)
}
private fun onQrCodeScanned(address: String) {
val parsedQrCode = parseQrCodeUseCase(address, cryptoCurrency).getOrNull() ?: return
_uiState.update(
SendDestinationPredefinedStateTransformer(
address = parsedQrCode.address,
memo = parsedQrCode.memo,
),
)
validate(
address = parsedQrCode.address,
memo = parsedQrCode.memo,
type = EnterAddressSource.QRCode,
)
}
private fun getWalletsAndRecent() {
combine(
flow = getWalletsUseCase().conflate().map {
@ -263,7 +292,7 @@ internal class SendDestinationModel @Inject constructor(
flow = uiState,
flow2 = params.currentRoute,
transform = { state, route -> state to route },
).distinctUntilChanged().onEach { (state, route) ->
).onEach { (state, route) ->
params.callback.onNavigationResult(
NavigationUM.Content(
title = resourceReference(R.string.send_recipient_label),

View file

@ -1,6 +1,7 @@
package com.tangem.features.send.v2.subcomponents.destination.model.transformers
import arrow.core.Either
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.transaction.error.AddressValidation
import com.tangem.domain.transaction.error.AddressValidationResult
@ -8,6 +9,7 @@ import com.tangem.domain.transaction.error.ValidateMemoError
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toPersistentList
internal class SendDestinationValidationResultTransformer(
private val addressValidationResult: AddressValidationResult,
@ -28,6 +30,8 @@ internal class SendDestinationValidationResultTransformer(
}
}.leftOrNull()
val shouldDisableMemo = shouldDisableMemo()
return state.copy(
isValidating = false,
isPrimaryButtonEnabled = isValidAddress && isValidMemo,
@ -36,9 +40,16 @@ internal class SendDestinationValidationResultTransformer(
isError = state.addressTextField.value.isNotEmpty() && !isValidAddress,
),
memoTextField = state.memoTextField?.copy(
value = state.memoTextField.value.takeIf { !shouldDisableMemo }.orEmpty(),
isError = state.memoTextField.value.isNotEmpty() && !isValidMemo,
isEnabled = !shouldDisableMemo(),
isEnabled = !shouldDisableMemo,
),
recent = state.recent.map { recent ->
recent.copy(isVisible = !isValidAddress && (recent.isLoading || recent.title != TextReference.EMPTY))
}.toPersistentList(),
wallets = state.wallets.map { wallet ->
wallet.copy(isVisible = !isValidAddress && (wallet.isLoading || wallet.title != TextReference.EMPTY))
}.toPersistentList(),
)
}

View file

@ -23,19 +23,13 @@ internal fun DestinationBlock(
isEditingDisabled: Boolean,
onClick: () -> Unit,
) {
val destinationUM = destinationUM as? DestinationUM.Content ?: return
val backgroundColor = if (isEditingDisabled) {
TangemTheme.colors.button.disabled
} else {
TangemTheme.colors.background.action
}
if (destinationUM !is DestinationUM.Content) return
Column(
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(backgroundColor)
.background(TangemTheme.colors.background.action)
.clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick)
.padding(TangemTheme.dimens.spacing12),
) {

View file

@ -278,7 +278,7 @@ internal class SendFeeModel @Inject constructor(
flow = uiState,
flow2 = params.currentRoute,
transform = { state, route -> state to route },
).distinctUntilChanged().onEach { (state, _) ->
).onEach { (state, _) ->
params.callback.onNavigationResult(
NavigationUM.Content(
title = resourceReference(R.string.common_fee_selector_title),

View file

@ -32,17 +32,11 @@ internal fun FeeBlock(feeUM: FeeUM, isClickEnabled: Boolean, onClick: () -> Unit
val feeSelectorUM = feeUM.feeSelectorUM as? FeeSelectorUM.Content
val isEditingDisabled = feeSelectorUM?.fees is TransactionFee.Single
val backgroundColor = if (isEditingDisabled) {
TangemTheme.colors.button.disabled
} else {
TangemTheme.colors.background.action
}
Column(
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(backgroundColor)
.background(TangemTheme.colors.background.action)
.clickable(enabled = isClickEnabled && !isEditingDisabled, onClick = onClick)
.padding(12.dp),
) {

View file

@ -29,17 +29,11 @@ internal fun RecipientBlock(
isEditingDisabled: Boolean,
onClick: () -> Unit,
) {
val backgroundColor = if (isEditingDisabled) {
TangemTheme.colors.button.disabled
} else {
TangemTheme.colors.background.action
}
Column(
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(backgroundColor)
.background(TangemTheme.colors.background.action)
.clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick)
.padding(TangemTheme.dimens.spacing12),
) {

View file

@ -71,7 +71,7 @@ internal fun StakingConfirmationContent(
isClickable = !isTransactionInProgress,
onClick = clickIntents::openValidators,
)
StakingFeeBlock(feeState = state.feeState, isTransactionSent = isTransactionSent)
StakingFeeBlock(feeState = state.feeState)
NotificationsBlock(notifications = state.notifications)
Spacer(Modifier)
}

View file

@ -32,17 +32,12 @@ import com.tangem.features.staking.impl.presentation.state.FeeState
import java.math.BigDecimal
@Composable
internal fun StakingFeeBlock(feeState: FeeState, isTransactionSent: Boolean) {
val backgroundColor = if (isTransactionSent) {
TangemTheme.colors.background.action
} else {
TangemTheme.colors.button.disabled
}
internal fun StakingFeeBlock(feeState: FeeState) {
Column(
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(backgroundColor)
.background(TangemTheme.colors.background.action)
.padding(TangemTheme.dimens.spacing12),
) {
Text(
@ -144,10 +139,7 @@ private fun BoxScope.FeeError(feeState: FeeState) {
@Composable
private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: FeeState) {
TangemThemePreview {
StakingFeeBlock(
feeState = value,
isTransactionSent = false,
)
StakingFeeBlock(feeState = value)
}
}