Updated on 2026-08-14
This commit is contained in:
commit
f80f0f32ee
19 changed files with 264 additions and 259 deletions
|
|
@ -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),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(", ")
|
||||
}
|
||||
}
|
||||
|
|
@ -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(", ")
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue