Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-16 19:58:38 +03:00
commit afff7892bb
721 changed files with 24513 additions and 7275 deletions

View file

@ -71,6 +71,7 @@ dependencies {
implementation(projects.domain.txhistory)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.transaction)
implementation(projects.domain.transaction.models)
implementation(projects.domain.card)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)

View file

@ -5,7 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE
import com.tangem.core.analytics.models.AnalyticsParam.Key.VALIDATION
import com.tangem.core.analytics.models.AnalyticsParam.OnOffState
@ -105,7 +105,7 @@ internal sealed class SendAnalyticEvents(
) : SendAnalyticEvents(
event = "Transaction Sent Screen Opened",
params = mapOf(
TOKEN to token,
TOKEN_PARAM to token,
FEE_TYPE to feeType.value,
),
)
@ -119,13 +119,13 @@ internal sealed class SendAnalyticEvents(
/** If not enough fee notification is present */
data class NoticeNotEnoughFee(val token: String, val blockchain: String) : SendAnalyticEvents(
event = "Notice - Not Enough Fee",
params = mapOf(TOKEN to token, BLOCKCHAIN to blockchain),
params = mapOf(TOKEN_PARAM to token, BLOCKCHAIN to blockchain),
)
/** If transaction delays notification is present */
data class NoticeTransactionDelays(val token: String) : SendAnalyticEvents(
event = "Notice - Transaction Delays Are Possible",
params = mapOf(TOKEN to token),
params = mapOf(TOKEN_PARAM to token),
)
data object NoticeFeeCoverage : SendAnalyticEvents(
@ -135,7 +135,7 @@ internal sealed class SendAnalyticEvents(
/** If error occurs during send transactions */
data class TransactionError(val token: String) : SendAnalyticEvents(
event = "Error - Transaction Rejected",
params = mapOf(TOKEN to token),
params = mapOf(TOKEN_PARAM to token),
)
// endregion
}

View file

@ -101,7 +101,7 @@ internal sealed class SendNotification(val config: NotificationConfig) {
title = resourceReference(R.string.send_notification_existential_deposit_title),
subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)),
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
text = resourceReference(R.string.send_notification_leave_button, wrappedList(deposit)),
text = resourceReference(R.string.common_ok),
onClick = onConfirmClick,
),
)

View file

@ -1,5 +1,7 @@
package com.tangem.features.send.impl.presentation.state
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.converter.Converter
@ -15,7 +17,7 @@ internal class SendTransactionAlertConverter(
is SendTransactionError.TangemSdkError -> SendAlertState.TransactionError(
code = value.code.toString(),
cause = null,
causeTextReference = value.messageReference,
causeTextReference = resourceReference(value.messageRes, wrappedList(value.args)),
onConfirmClick = { clickIntents.onFailedTxEmailClick(value.code.toString()) },
)
is SendTransactionError.BlockchainSdkError -> SendAlertState.TransactionError(

View file

@ -72,7 +72,10 @@ internal class FeeConverter(
normalFee
} else {
when (normalFee) {
is Fee.Ethereum -> ethereumCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues)
is Fee.Ethereum.Legacy -> ethereumCustomFeeConverter.convertBack(
normalFee = normalFee,
value = customValues,
)
is Fee.Bitcoin -> bitcoinCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues)
is Fee.Kaspa -> kaspaCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues)
else -> {

View file

@ -1,10 +1,12 @@
package com.tangem.features.send.impl.presentation.state.fee
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.extensions.isZero
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.AmountType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
import com.tangem.features.send.impl.presentation.state.SendNotification
@ -15,6 +17,7 @@ import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import com.tangem.blockchain.common.AmountType as SdkAmountType
/**
* Factory to produce fee state for [SendUiState]
@ -94,7 +97,7 @@ internal class FeeStateFactory(
feeState = feeState.copy(
feeSelectorState = updatedFeeSelectorState,
fee = fee,
isFeeApproximate = isFeeApproximate(fee),
isFeeApproximate = isFeeApproximate(state.amountState),
),
)
}
@ -180,11 +183,29 @@ internal class FeeStateFactory(
return noErrors && (isNotEmptyCustom || isNotCustom)
}
private fun isFeeApproximate(fee: Fee): Boolean {
private fun isFeeApproximate(state: AmountState): Boolean {
val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider() ?: return false
val amount = (state as? AmountState.Data)?.amountTextField?.cryptoAmount ?: return false
return isFeeApproximateUseCase(
networkId = cryptoCurrencyStatus.currency.network.id,
amountType = fee.amount.type,
amountType = amount.type.toSdkAmountType(),
)
}
private fun AmountType.toSdkAmountType(): SdkAmountType {
return when (this) {
AmountType.CoinType -> SdkAmountType.Coin
is AmountType.FiatType -> error("unsupported type FiatType")
AmountType.ReserveType -> SdkAmountType.Reserve
is AmountType.TokenType -> SdkAmountType.Token(
Token(
name = this.token.name,
symbol = this.token.symbol,
contractAddress = this.token.contractAddress,
decimals = this.token.decimals,
id = this.token.id.rawCurrencyId,
),
)
}
}
}

View file

@ -49,7 +49,7 @@ internal class SendFeeCustomFieldConverter(
override fun convert(value: Fee): ImmutableList<SendTextField.CustomFee> {
return when (value) {
is Fee.Ethereum -> ethereumCustomFeeConverter.convert(value)
is Fee.Ethereum.Legacy -> ethereumCustomFeeConverter.convert(value)
is Fee.Bitcoin -> bitcoinCustomFeeConverter.convert(value)
is Fee.Kaspa -> kaspaCustomFeeConverter.convert(value)
else -> persistentListOf()

View file

@ -27,9 +27,9 @@ internal class EthereumCustomFeeConverter(
private val stateRouterProvider: Provider<StateRouter>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val feeCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
) : CustomFeeConverter<Fee.Ethereum> {
) : CustomFeeConverter<Fee.Ethereum.Legacy> {
override fun convert(value: Fee.Ethereum): ImmutableList<SendTextField.CustomFee> {
override fun convert(value: Fee.Ethereum.Legacy): ImmutableList<SendTextField.CustomFee> {
val feeValue = value.amount.value
val feeCurrency = feeCryptoCurrencyStatusProvider()?.value
return persistentListOf(
@ -91,7 +91,10 @@ internal class EthereumCustomFeeConverter(
)
}
override fun convertBack(normalFee: Fee.Ethereum, value: ImmutableList<SendTextField.CustomFee>): Fee.Ethereum {
override fun convertBack(
normalFee: Fee.Ethereum.Legacy,
value: ImmutableList<SendTextField.CustomFee>,
): Fee.Ethereum.Legacy {
val feeAmount = value[FEE_AMOUNT].value.parseToBigDecimal(value[FEE_AMOUNT].decimals)
val gasPrice = value[GAS_PRICE].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger()
val gasLimit = value[GAS_LIMIT].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger()

View file

@ -6,7 +6,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButton
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.resolveReference
@ -45,24 +45,24 @@ internal fun SendEventEffect(event: StateEvent<SendEvent>, snackbarHostState: Sn
@Composable
internal fun SendAlert(state: SendAlertState, onDismiss: () -> Unit) {
val confirmButton: DialogButton
val dismissButton: DialogButton?
val confirmButton: DialogButtonUM
val dismissButton: DialogButtonUM?
val onActionClick = state.onConfirmClick
if (onActionClick != null) {
confirmButton = DialogButton(
confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
onClick = {
onActionClick()
onDismiss()
},
)
dismissButton = DialogButton(
dismissButton = DialogButtonUM(
title = stringResource(id = R.string.common_cancel),
onClick = onDismiss,
)
} else {
confirmButton = DialogButton(
confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
onClick = onDismiss,
)

View file

@ -163,13 +163,15 @@ private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentS
when (state.type) {
SendUiStateType.Amount -> AmountScreenContent(
amountState = uiState.amountState,
isBalanceHiding = uiState.isBalanceHidden,
isBalanceHidden = uiState.isBalanceHidden,
clickIntents = uiState.clickIntents,
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
)
SendUiStateType.EditAmount -> AmountScreenContent(
amountState = uiState.editAmountState!!,
isBalanceHiding = uiState.isBalanceHidden,
amountState = uiState.editAmountState,
isBalanceHidden = uiState.isBalanceHidden,
clickIntents = uiState.clickIntents,
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
)
SendUiStateType.Recipient -> SendRecipientContent(
uiState = uiState.recipientState,

View file

@ -33,8 +33,8 @@ import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData
import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub
import com.tangem.core.ui.components.containers.FooterContainer
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.StringsSigns.STARS
import kotlinx.collections.immutable.ImmutableList
private const val ADDRESS_FIELD_KEY = "ADDRESS_FIELD_KEY"
@ -205,7 +205,7 @@ private fun LazyListScope.listItem(
AnimateRecentAppearance(item.isVisible) {
ListItemWithIcon(
title = title,
subtitle = if (isBalanceHidden) STARS else item.subtitle.resolveReference(),
subtitle = item.subtitle.orMaskWithStars(isBalanceHidden).resolveReference(),
info = item.timestamp?.resolveReference(),
subtitleEndOffset = item.subtitleEndOffset,
subtitleIconRes = item.subtitleIconRes,

View file

@ -26,8 +26,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.common.ui.amountScreen.ui.AmountBlock
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
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.res.TangemThemePreview
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.previewdata.ConfirmStatePreviewData
@ -69,8 +73,14 @@ private fun LazyListScope.blocks(uiState: SendUiState) {
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
) {
TransactionDoneTitle(
titleRes = R.string.sent_transaction_sent_title,
date = timestamp,
title = resourceReference(R.string.sent_transaction_sent_title),
subtitle = resourceReference(
R.string.send_date_format,
wrappedList(
timestamp.toTimeFormat(DateTimeFormatters.dateFormatter),
timestamp.toTimeFormat(),
),
),
)
}
RecipientBlock(