Updated on 2026-08-14

This commit is contained in:
Tangem 2022-09-15 22:27:01 +04:00
commit 9101b558f1
36 changed files with 374 additions and 290 deletions

View file

@ -29,7 +29,6 @@
<codeStyleSettings language="Groovy">
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
<option name="LINE_COMMENT_ADD_SPACE_ON_REFORMAT" value="true" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
@ -174,9 +173,8 @@
<option name="RIGHT_MARGIN" value="120" />
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
<option name="LINE_COMMENT_ADD_SPACE_ON_REFORMAT" value="true" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="0" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="0" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="EXTENDS_LIST_WRAP" value="5" />

View file

@ -420,6 +420,16 @@
"networkId" : "stellar/test"
}
]
},
{
"id" : "polkadot",
"symbol" : "DOT",
"name" : "Polkadot",
"networks" : [
{
"networkId" : "polkadot/test"
}
]
}
],

View file

@ -28,6 +28,7 @@ fun Blockchain.getRoundIconRes(): Int {
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_round
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_round
Blockchain.Gnosis -> R.drawable.ic_gnosis_round
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_round
else -> R.drawable.ic_tangem_logo
}
}
@ -55,6 +56,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color
Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_no_color
else -> R.drawable.ic_tangem_logo
}
}

View file

@ -5,19 +5,23 @@ import androidx.annotation.ColorInt
import androidx.core.graphics.luminance
import androidx.core.graphics.toColorInt
import com.tangem.blockchain.common.Token
import com.tangem.wallet.R
@ColorInt
fun Token.getColor(): Int {
return try {
("#" + this.contractAddress.subSequence(2..7).toString())
.toColorInt()
fun Token.getColor(isTestnet: Boolean = false): Int {
val defaultColor = "#C7C7CC".toColorInt() // equivalent to R.color.lightGray4
return if (isTestnet)
defaultColor
else try {
("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt()
} catch (exception: Exception) {
R.color.lightGray4
defaultColor
}
}
@ColorInt
fun Token.getTextColor(): Int {
return if (this.getColor().luminance > 0.5) Color.BLACK else Color.WHITE
fun Token.getTextColor(isTestnet: Boolean = false): Int = when {
isTestnet -> Color.WHITE
this.getColor().luminance > 0.5 -> Color.BLACK
else -> Color.WHITE
}

View file

@ -113,15 +113,11 @@ sealed class FeeActionUi : SendScreenActionUi {
}
sealed class FeeAction : SendScreenAction {
enum class Error {
ADDRESS_OR_AMOUNT_IS_EMPTY,
REQUEST_FAILED
}
object RequestFee : FeeAction()
sealed class FeeCalculation : FeeAction() {
data class SetFeeResult(val fee: List<Amount>) : FeeCalculation()
data class SetFeeError(val error: Error) : FeeCalculation()
object ClearResult : FeeCalculation()
}
data class ChangeLayoutVisibility(
@ -160,6 +156,12 @@ sealed class SendAction : SendScreenAction {
data class CardSdkError(val error: TangemSdkError): Dialog()
data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError): Dialog()
}
data class RequestFeeError(
val error: com.tangem.blockchain.common.BlockchainSdkError,
val onRetry: () -> Unit,
) : Dialog()
object Hide : Dialog()
}

View file

@ -4,11 +4,17 @@ import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionError
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.AmountAction
import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.FeeActionUi
import com.tangem.tap.features.send.redux.ReceiptAction
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.redux.states.SendState
import org.rekotlin.Action
import java.math.BigDecimal
import java.util.*
/**
[REDACTED_AUTHOR]
@ -62,18 +68,11 @@ class AmountMiddleware {
val amountToSend = Amount(typedAmount, sendState.getTotalAmountToSend(inputCrypto))
val transactionErrors = walletManager.validateTransaction(amountToSend, sendState.feeState.currentFee)
transactionErrors.remove(TransactionError.TezosSendAll)
if (transactionErrors.isEmpty()) {
val amountFieldErrors = filterErrorsForAmountField(transactionErrors)
if (amountFieldErrors.isEmpty()) {
dispatch(AmountAction.SetAmountError(null))
} else {
val amountErrors = extractErrorsForAmountField(transactionErrors)
if (amountErrors.isNotEmpty()) {
transactionErrors.removeAll(amountErrors)
dispatch(AmountAction.SetAmountError(createValidateTransactionError(amountErrors, walletManager)))
}
if (transactionErrors.isNotEmpty()) {
dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager)))
}
dispatch(AmountAction.SetAmountError(createValidateTransactionError(amountFieldErrors, walletManager)))
}
dispatch(ReceiptAction.RefreshReceipt)
dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState()))
@ -104,4 +103,28 @@ class AmountMiddleware {
dispatch(AmountActionUi.SetMainCurrency(type))
}
}
private fun filterErrorsForAmountField(errors: EnumSet<TransactionError>): EnumSet<TransactionError> {
val showIntoAmountField = EnumSet.noneOf(TransactionError::class.java)
errors.forEach {
when (it) {
TransactionError.AmountExceedsBalance -> {
showIntoAmountField.remove(TransactionError.TotalExceedsBalance)
showIntoAmountField.add(it)
}
TransactionError.FeeExceedsBalance -> {
showIntoAmountField.remove(TransactionError.TotalExceedsBalance)
showIntoAmountField.add(it)
}
TransactionError.TotalExceedsBalance -> {
val notAcceptable = listOf(TransactionError.AmountExceedsBalance, TransactionError.FeeExceedsBalance)
if (!showIntoAmountField.containsAll(notAcceptable)) showIntoAmountField.add(it)
}
else -> showIntoAmountField.add(it)
}
}
showIntoAmountField.remove(TransactionError.TezosSendAll)
return showIntoAmountField
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.send.redux.middlewares
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
@ -31,8 +32,7 @@ class RequestFeeMiddleware {
val scanResponse = appState.globalState.scanResponse ?: return
if (!SendState.isReadyToRequestFee()) {
dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.ADDRESS_OR_AMOUNT_IS_EMPTY))
// dispatch(FeeAction.ChangeLayoutVisibility(main = false, chipGroup = true))
dispatch(FeeAction.FeeCalculation.ClearResult)
dispatch(ReceiptAction.RefreshReceipt)
dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState()))
return
@ -66,14 +66,21 @@ class RequestFeeMiddleware {
}
}
is Result.Failure -> {
dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.REQUEST_FAILED))
dispatch(FeeAction.FeeCalculation.ClearResult)
dispatch(FeeAction.ChangeLayoutVisibility(main = false))
val blockchainSdkError = feeResult.error as? BlockchainSdkError ?: return@withContext
dispatch(
SendAction.Dialog.RequestFeeError(
error = blockchainSdkError,
onRetry = { dispatch(FeeAction.RequestFee) },
),
)
}
}
dispatch(AmountActionUi.CheckAmountToSend)
}
}
}
}
@ -93,13 +100,13 @@ class FeeMock {
suspend fun feeStandard(blockchain: Blockchain): List<Amount> = listOf(
Amount(0.001500.toBigDecimal(), blockchain),
Amount(0.0030.toBigDecimal(), blockchain),
Amount(0.0045001.toBigDecimal(), blockchain)
Amount(0.0045001.toBigDecimal(), blockchain),
)
suspend fun feeStandardBig(blockchain: Blockchain): List<Amount> = listOf(
Amount(1.76.toBigDecimal(), blockchain),
Amount(2.30.toBigDecimal(), blockchain),
Amount(3.45.toBigDecimal(), blockchain)
Amount(3.45.toBigDecimal(), blockchain),
)
suspend fun feeSingle(blockchain: Blockchain): List<Amount> = listOf(Amount(0.0015.toBigDecimal(), blockchain))

View file

@ -114,9 +114,8 @@ private fun verifyAndSendTransaction(
val amountToSend = Amount(typedAmount, sendState.getTotalAmountToSend())
val transactionErrors = walletManager.validateTransaction(amountToSend, feeAmount)
val hadTezosError = transactionErrors.remove(TransactionError.TezosSendAll)
when {
hadTezosError -> {
transactionErrors.contains(TransactionError.TezosSendAll) -> {
val reduceAmount = walletManager.wallet.blockchain.minimalAmount()
dispatch(
SendAction.Dialog.TezosWarningDialog(
@ -135,9 +134,6 @@ private fun verifyAndSendTransaction(
),
)
}
transactionErrors.isNotEmpty() -> {
dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager)))
}
else -> {
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
@ -272,10 +268,8 @@ private fun sendTransaction(
dispatch(SendAction.Dialog.SendTransactionFails.CardSdkError(tangemSdkError))
}
is BlockchainSdkError.CreateAccountUnderfunded -> {
// from XLM, XRP
val reserve = error.minReserve.value?.stripZeroPlainString() ?: "0"
val symbol = error.minReserve.currencySymbol
dispatch(SendAction.SendError(TapError.CreateAccountUnderfunded(listOf(reserve, symbol))))
// from XLM, XRP, Polkadot
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error))
}
else -> {
when {
@ -300,29 +294,6 @@ private fun sendTransaction(
}
}
fun extractErrorsForAmountField(errors: EnumSet<TransactionError>): EnumSet<TransactionError> {
val showIntoAmountField = EnumSet.noneOf(TransactionError::class.java)
errors.forEach {
when (it) {
TransactionError.AmountExceedsBalance -> {
showIntoAmountField.remove(TransactionError.TotalExceedsBalance)
showIntoAmountField.add(it)
}
TransactionError.FeeExceedsBalance -> {
showIntoAmountField.remove(TransactionError.TotalExceedsBalance)
showIntoAmountField.add(it)
}
TransactionError.TotalExceedsBalance -> {
val notAcceptable = listOf(TransactionError.AmountExceedsBalance, TransactionError.FeeExceedsBalance)
if (!showIntoAmountField.containsAll(notAcceptable)) showIntoAmountField.add(it)
}
TransactionError.InvalidAmountValue -> showIntoAmountField.add(it)
TransactionError.InvalidFeeValue -> showIntoAmountField.add(it)
}
}
return showIntoAmountField
}
fun createValidateTransactionError(
errorList: EnumSet<TransactionError>,
walletManager: WalletManager,

View file

@ -1,13 +1,9 @@
package com.tangem.tap.features.send.redux.reducers
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.FeeActionUi
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.states.FeePrecision
import com.tangem.tap.features.send.redux.states.FeeState
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.SendState
@ -28,10 +24,9 @@ class FeeReducer : SendInternalReducer {
is FeeActionUi.ChangeSelectedFee -> {
val currentFee = createValueOfFeeAmount(action.feeType, state.feeList)
state.copy(
selectedFeeType = action.feeType,
currentFee = currentFee
selectedFeeType = action.feeType,
currentFee = currentFee,
)
}
is FeeActionUi.ChangeIncludeFee -> state.copy(feeIsIncluded = action.isIncluded)
}
@ -41,14 +36,14 @@ class FeeReducer : SendInternalReducer {
private fun handleAction(action: FeeAction, sendState: SendState, state: FeeState): SendState {
val result = when (action) {
is FeeAction.RequestFee -> {
state.copy(error = null)
state
}
is FeeAction.ChangeLayoutVisibility -> {
fun getVisibility(current: Boolean, proposed: Boolean?): Boolean = proposed ?: current
state.copy(
mainLayoutIsVisible = getVisibility(state.mainLayoutIsVisible, action.main),
controlsLayoutIsVisible = getVisibility(state.controlsLayoutIsVisible, action.controls),
feeChipGroupIsVisible = getVisibility(state.feeChipGroupIsVisible, action.chipGroup)
mainLayoutIsVisible = getVisibility(state.mainLayoutIsVisible, action.main),
controlsLayoutIsVisible = getVisibility(state.controlsLayoutIsVisible, action.controls),
feeChipGroupIsVisible = getVisibility(state.feeChipGroupIsVisible, action.chipGroup),
)
}
is FeeAction.FeeCalculation.SetFeeResult -> {
@ -58,30 +53,27 @@ class FeeReducer : SendInternalReducer {
val currentFee = createValueOfFeeAmount(feeType, fees)
state.copy(
selectedFeeType = feeType,
feeList = fees,
currentFee = currentFee,
error = null,
feePrecision = getFeePrecision(sendState)
selectedFeeType = feeType,
feeList = fees,
currentFee = currentFee,
feeIsApproximate = isFeeApproximate(sendState),
)
} else {
val feeType = getCurrentFeeType(state)
val currentFee = createValueOfFeeAmount(feeType, fees)
state.copy(
selectedFeeType = feeType,
feeList = fees,
currentFee = currentFee,
error = null,
feePrecision = getFeePrecision(sendState)
selectedFeeType = feeType,
feeList = fees,
currentFee = currentFee,
feeIsApproximate = isFeeApproximate(sendState),
)
}
}
is FeeAction.FeeCalculation.SetFeeError -> {
FeeAction.FeeCalculation.ClearResult -> {
state.copy(
feeList = null,
currentFee = null,
error = action.error
feeList = null,
currentFee = null,
)
}
}
@ -104,20 +96,12 @@ class FeeReducer : SendInternalReducer {
}
}
private fun getFeePrecision(sendState: SendState): FeePrecision {
val blockchain = sendState.walletManager?.wallet?.blockchain
return if (
(blockchain?.fullNameWithoutTestnet == Blockchain.Arbitrum.fullName ||
blockchain?.fullNameWithoutTestnet == Blockchain.Tron.fullName ||
blockchain?.fullNameWithoutTestnet == Blockchain.Gnosis.fullName) &&
sendState.amountState.typeOfAmount is AmountType.Token
) {
FeePrecision.CAN_BE_LOWER
} else {
FeePrecision.PRECISE
}
}
private fun isFeeApproximate(sendState: SendState): Boolean {
val blockchain = sendState.walletManager?.wallet?.blockchain ?: return false
val amountType = sendState.amountState.typeOfAmount
return blockchain.isFeeApproximate(amountType)
}
private fun getCurrentFeeType(state: FeeState): FeeType {
return if (state.selectedFeeType == FeeType.SINGLE) FeeType.NORMAL else state.selectedFeeType

View file

@ -6,7 +6,18 @@ import com.tangem.tap.common.extensions.scaleToFiat
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.send.redux.states.AmountState
import com.tangem.tap.features.send.redux.states.FeeState
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.redux.states.ReceiptCrypto
import com.tangem.tap.features.send.redux.states.ReceiptFiat
import com.tangem.tap.features.send.redux.states.ReceiptLayoutType
import com.tangem.tap.features.send.redux.states.ReceiptState
import com.tangem.tap.features.send.redux.states.ReceiptSymbols
import com.tangem.tap.features.send.redux.states.ReceiptTokenCrypto
import com.tangem.tap.features.send.redux.states.ReceiptTokenFiat
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.wallet.redux.WalletState.Companion.CAN_BE_LOWER_SIGN
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
import com.tangem.tap.store
import java.math.BigDecimal
@ -91,7 +102,7 @@ class ReceiptReducer : SendInternalReducer {
if (feeState.feeIsIncluded) {
return ReceiptCrypto(
amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString().addPrecisionSign(),
totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
feeFiat = feeFiat,
willSentFiat = convertToFiatPrecision(amountState.amountToSendCrypto),
@ -101,7 +112,7 @@ class ReceiptReducer : SendInternalReducer {
val totalCrypto = amountState.amountToSendCrypto.plus(feeCrypto)
return ReceiptCrypto(
amountCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString().addPrecisionSign(),
totalCrypto = totalCrypto.stripZeroPlainString(),
feeFiat = feeFiat,
willSentFiat = convertToFiatPrecision(totalCrypto),
@ -125,8 +136,7 @@ class ReceiptReducer : SendInternalReducer {
val totalFiat = amountFiat.plus(feeFiat)
ReceiptTokenFiat(
amountFiat = amountFiat.scaleToFiat(true).stripZeroPlainString(),
feeFiat = feeFiat.scaleToFiat(true)
.stripZeroPlainString().addPrecisionSign(),
feeFiat = feeFiat.scaleToFiat(true).stripZeroPlainString().addPrecisionSign(),
totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(),
willSentToken = tokensToSend.stripZeroPlainString(),
willSentFeeCoin = feeCoin.stripZeroPlainString(),
@ -207,6 +217,8 @@ class ReceiptReducer : SendInternalReducer {
}
}
private fun String.addPrecisionSign(): String =
("${feeState.feePrecision.symbol} $this").trim()
private fun String.addPrecisionSign(): String {
val result = if (feeState.feeIsApproximate) "$CAN_BE_LOWER_SIGN $this" else ""
return result.trim()
}
}

View file

@ -59,10 +59,10 @@ private class SendReducer : SendInternalReducer {
is SendAction.Dialog.TezosWarningDialog -> sendState.copy(dialog = action)
is SendAction.Dialog.SendTransactionFails.CardSdkError -> sendState.copy(dialog = action)
is SendAction.Dialog.SendTransactionFails.BlockchainSdkError -> sendState.copy(dialog = action)
is SendAction.Dialog.RequestFeeError -> sendState.copy(dialog = action)
is SendAction.Dialog.Hide -> sendState.copy(dialog = null)
is SendAction.Warnings.Set -> sendState.copy(sendWarningsList = action.warningList)
is SendAction.SendSpecificTransaction ->
handleSendSpecificTransactionAction(action, sendState)
is SendAction.SendSpecificTransaction -> handleSendSpecificTransactionAction(action, sendState)
else -> return sendState
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.features.send.redux.states
import com.tangem.blockchain.common.Amount
import com.tangem.tap.features.send.redux.FeeAction
import java.math.BigDecimal
/**
@ -11,26 +10,21 @@ enum class FeeType {
SINGLE, LOW, NORMAL, PRIORITY
}
enum class FeePrecision(val symbol: String) {
PRECISE(""), CAN_BE_LOWER("<")
}
data class FeeState(
val selectedFeeType: FeeType = FeeType.NORMAL,
val feeList: List<Amount>? = null,
val currentFee: Amount? = null,
val feeIsIncluded: Boolean = false,
val mainLayoutIsVisible: Boolean = false,
val controlsLayoutIsVisible: Boolean = false,
val feeChipGroupIsVisible: Boolean = true,
val includeFeeSwitcherIsEnabled: Boolean = true,
val error: FeeAction.Error? = null,
val feePrecision: FeePrecision = FeePrecision.PRECISE
val selectedFeeType: FeeType = FeeType.NORMAL,
val feeList: List<Amount>? = null,
val currentFee: Amount? = null,
val feeIsIncluded: Boolean = false,
val feeIsApproximate: Boolean = false,
val mainLayoutIsVisible: Boolean = false,
val controlsLayoutIsVisible: Boolean = false,
val feeChipGroupIsVisible: Boolean = true,
val includeFeeSwitcherIsEnabled: Boolean = true,
) : SendScreenState {
override val stateId: StateId = StateId.FEE
fun isReady(): Boolean = error == null && currentFee != null
fun isReady(): Boolean = currentFee != null
fun getCurrentFeeValue(): BigDecimal = currentFee?.value ?: BigDecimal.ZERO
}

View file

@ -0,0 +1,32 @@
package com.tangem.tap.features.send.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.store
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
class RequestFeeErrorDialog {
companion object {
fun create(context: Context, dialog: SendAction.Dialog.RequestFeeError): AlertDialog {
val errorMessage = dialog.error.customMessage
return AlertDialog.Builder(context).apply {
setTitle(R.string.send_error_fee_request_failed)
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage))
setNegativeButton(R.string.alert_button_send_feedback) { _, _ ->
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage)))
}
setPositiveButton(R.string.common_retry) { _, _ -> dialog.onRetry() }
setNeutralButton(R.string.common_cancel) { _, _ -> }
setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) }
}.create()
}
}
}

View file

@ -2,7 +2,10 @@ package com.tangem.tap.features.send.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.module.ModuleMessageConverter
import com.tangem.tangem_sdk_new.extensions.localizedDescription
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.send.redux.SendAction
@ -13,14 +16,14 @@ import com.tangem.wallet.R
[REDACTED_AUTHOR]
*/
class SendTransactionFailsDialog {
companion object {
fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.CardSdkError): AlertDialog {
return create(context, dialog.error.localizedDescription(context))
}
fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.BlockchainSdkError): AlertDialog {
return create(context, dialog.error.customMessage)
val errorConverter = BlockchainSdkErrorConverter(context)
return create(context, errorConverter.convert(dialog.error))
}
private fun create(context: Context, errorMessage: String): AlertDialog {
@ -35,4 +38,20 @@ class SendTransactionFailsDialog {
}.create()
}
}
}
private class BlockchainSdkErrorConverter(
private val context: Context,
) : ModuleMessageConverter<BlockchainSdkError, String> {
override fun convert(message: BlockchainSdkError): String {
return when (message) {
is BlockchainSdkError.CreateAccountUnderfunded -> {
val reserve = message.minReserve.value?.stripZeroPlainString() ?: "0"
val symbol = message.minReserve.currencySymbol
context.getString(R.string.send_error_no_target_account, reserve, symbol)
}
else -> message.customMessage
}
}
}

View file

@ -20,7 +20,6 @@ import com.tangem.tap.domain.MultiMessageError
import com.tangem.tap.domain.assembleErrors
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.send.redux.states.AddressPayIdState
import com.tangem.tap.features.send.redux.states.AmountState
@ -35,12 +34,12 @@ import com.tangem.tap.features.send.redux.states.TransactionExtrasState
import com.tangem.tap.features.send.redux.states.XlmMemoType
import com.tangem.tap.features.send.ui.FeeUiHelper
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.send.ui.dialogs.RequestFeeErrorDialog
import com.tangem.tap.features.send.ui.dialogs.SendTransactionFailsDialog
import com.tangem.tap.features.send.ui.dialogs.TezosWarningDialog
import com.tangem.tap.features.wallet.redux.WalletState.Companion.ROUGH_SIGN
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.store
import com.tangem.wallet.R
/**
@ -62,10 +61,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
when (it) {
StateId.SEND_SCREEN -> handleSendScreen(fg, state)
StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState)
StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(
fg,
state.transactionExtrasState
)
StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(fg, state.transactionExtrasState)
StateId.AMOUNT -> handleAmountState(fg, state.amountState)
StateId.FEE -> handleFeeState(fg, state.feeState)
StateId.RECEIPT -> handleReceiptState(fg, state.receiptState)
@ -144,6 +140,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
dialog?.show()
}
}
is SendAction.Dialog.RequestFeeError -> {
if (dialog == null) {
dialog = RequestFeeErrorDialog.create(fg.requireContext(), state.dialog)
dialog?.show()
}
}
else -> {
dialog?.dismiss()
dialog = null
@ -161,7 +163,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
toolbar.title = fg.getString(
R.string.send_title_currency_format,
state.amountState.mainCurrency.currencySymbol
state.amountState.mainCurrency.currencySymbol,
)
}
@ -233,11 +235,11 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
val balanceText = when (state.mainCurrency.type) {
MainCurrencyType.FIAT -> fg.getString(
R.string.send_balance_subtitle_format,
state.viewBalanceValue, state.mainCurrency.currencySymbol
state.viewBalanceValue, state.mainCurrency.currencySymbol,
).remove(":")
MainCurrencyType.CRYPTO -> fg.getString(
R.string.send_balance_subtitle_format,
state.mainCurrency.currencySymbol, state.viewBalanceValue
state.mainCurrency.currencySymbol, state.viewBalanceValue,
)
}
@ -263,14 +265,6 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
swIncludeFee.isChecked = state.feeIsIncluded
}
if (state.error == FeeAction.Error.REQUEST_FAILED) {
fg.showRetrySnackbar(
fg.requireContext().getString(R.string.send_error_fee_request_failed)
) {
store.dispatch(FeeAction.RequestFee)
}
}
val chipId = FeeUiHelper.toId(state.selectedFeeType)
if (chipGroup.checkedChipId != chipId && chipId != View.NO_ID) chipGroup.check(chipId)
}
@ -299,7 +293,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
val willSent = getString(
R.string.send_total_subtitle_format,
receipt.willSentCrypto, receipt.symbols.crypto
receipt.willSentCrypto, receipt.symbols.crypto,
)
llTotalContainer.tvWillBeSentValue.update(willSent)
@ -334,7 +328,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
val willSent = getString(
R.string.send_total_subtitle_asset_format,
receipt.symbols.token ?: "", receipt.willSentToken,
receipt.symbols.crypto, receipt.willSentFeeCoin
receipt.symbols.crypto, receipt.willSentFeeCoin,
)
llTotalContainer.tvWillBeSentValue.update(willSent)
}

View file

@ -3,6 +3,12 @@ package com.tangem.tap.features.wallet.models
sealed class WalletWarning(
val showingPosition: Int,
) {
data class ExistentialDeposit(
val currencyName: String,
val currencySymbols: String,
val existentialDepositString: String,
) : WalletWarning(1)
object TransactionInProgress : WalletWarning(10)
data class BalanceNotEnoughForFee(val blockchainFullName: String) : WalletWarning(30)
data class Rent(val walletRent: WalletRent) : WalletWarning(40)

View file

@ -131,9 +131,7 @@ sealed class WalletAction : Action {
object Scan : WalletAction()
data class Send(val amount: Amount? = null) : WalletAction() {
data class ChooseCurrency(val amounts: List<Amount>?) : WalletAction()
}
data class Send(val amount: Amount? = null) : WalletAction()
object EmptyField : WalletAction(), ErrorAction {
override val error = TapError.PayIdEmptyField

View file

@ -287,6 +287,7 @@ data class WalletState(
companion object {
const val UNKNOWN_AMOUNT_SIGN = ""
const val ROUGH_SIGN = ""
const val CAN_BE_LOWER_SIGN = "<"
}
}
@ -325,9 +326,6 @@ data class Artwork(
const val MARTA_CARD_ID = "BC02"
const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png"
const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png"
const val TEMP_CARDANO =
"https://verify.tangem.com/card/artwork?artworkId=card_ru039&CID=CB19000000040976&publicKey=0416E29423A6CC77CD07CBA52873E8F6F894B1AFB18EB3688ACC2C8D8E5AC84B80B0BA1B17B85E578E47044CE96BCFF3FB4499FA4941CAD3C1EF300A492B5B9659"
}
}
@ -342,6 +340,7 @@ data class WalletData(
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
val currency: Currency,
val walletRent: WalletRent? = null,
val existentialDepositString: String? = null,
) {
val isAvailableToBuy: Boolean
get() = store.state.globalState.exchangeManager.availableForBuy(currency)
@ -362,18 +361,42 @@ data class WalletData(
fun assembleWarnings(): List<WalletWarning> {
val walletWarnings = mutableListOf<WalletWarning>()
assembleNonTypedWarnings(walletWarnings)
assembleBlockchainWarnings(walletWarnings)
assembleTokenWarnings(walletWarnings)
return walletWarnings.sortedBy { it.showingPosition }
}
private fun assembleNonTypedWarnings(walletWarnings: MutableList<WalletWarning>) {
if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) {
walletWarnings.add(WalletWarning.TransactionInProgress)
}
}
private fun assembleBlockchainWarnings(walletWarnings: MutableList<WalletWarning>) {
if (!currency.isBlockchain()) return
if (existentialDepositString != null) {
val warning = WalletWarning.ExistentialDeposit(
currencyName = currency.currencyName,
currencySymbols = currency.currencySymbol,
existentialDepositString = existentialDepositString,
)
walletWarnings.add(warning)
}
if (walletRent != null) {
walletWarnings.add(WalletWarning.Rent(walletRent))
}
if (!currency.isBlockchain() && (blockchainAmountIsEmpty() && !tokenAmountIsEmpty())) {
val fullName = currency.blockchain.fullName
walletWarnings.add(WalletWarning.BalanceNotEnoughForFee(fullName))
}
}
return walletWarnings.sortedBy { it.showingPosition }
private fun assembleTokenWarnings(walletWarnings: MutableList<WalletWarning>){
if (!currency.isToken()) return
val blockchainFullName = currency.blockchain.fullName
if ((blockchainAmountIsEmpty() && !tokenAmountIsEmpty())) {
walletWarnings.add(WalletWarning.BalanceNotEnoughForFee(blockchainFullName))
}
}
private fun blockchainAmountIsEmpty(): Boolean = currencyData.blockchainAmount?.isZero() == true

View file

@ -12,7 +12,13 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.extensions.shareText
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
@ -22,11 +28,11 @@ import com.tangem.tap.domain.loadedRates
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransactionType
import com.tangem.tap.features.wallet.models.filterByCoin
import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.features.wallet.models.getSendableAmounts
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletState
@ -36,7 +42,6 @@ import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import java.math.BigDecimal
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
@ -44,6 +49,7 @@ import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
import timber.log.Timber
import java.math.BigDecimal
class WalletMiddleware {
private val tradeCryptoMiddleware = TradeCryptoMiddleware()
@ -276,7 +282,7 @@ class WalletMiddleware {
}
} else {
if (amounts?.size ?: 0 > 1) {
WalletAction.Send.ChooseCurrency(amounts)
WalletAction.DialogAction.ChooseCurrency(amounts)
} else {
val amountToSend = amounts?.first()
PrepareSendScreen(

View file

@ -1,15 +1,25 @@
package com.tangem.tap.features.wallet.redux.reducers
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.guard
import com.tangem.tap.common.extensions.toFiatString
import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.*
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.models.filterByToken
import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletMainButton
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
import com.tangem.tap.features.wallet.redux.WalletStore
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.features.wallet.ui.TokenData
@ -25,6 +35,7 @@ class MultiWalletReducer {
it.wallet.blockchain == blockchain.blockchain &&
(it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath)
} ?: return@mapNotNull null
val wallet = walletManager.wallet
val cardToken = if (!state.isMultiwalletAllowed) {
wallet.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
@ -44,6 +55,7 @@ class MultiWalletReducer {
blockchain.blockchain,
blockchain.derivationPath
),
existentialDepositString = getExistentialDeposit(walletManager),
)
WalletStore(
@ -79,6 +91,7 @@ class MultiWalletReducer {
action.blockchain.blockchain,
action.blockchain.derivationPath
),
existentialDepositString = getExistentialDeposit(walletManager),
)
val walletStore = WalletStore(
walletManager = walletManager,
@ -166,6 +179,10 @@ class MultiWalletReducer {
it.walletRent != null
}?.walletRent
}
private fun getExistentialDeposit(walletManager: WalletManager?): String? {
return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit()?.toPlainString()
}
}
private fun addTokens(

View file

@ -41,6 +41,7 @@ import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter
import com.tangem.tap.features.wallet.ui.images.load
import com.tangem.tap.features.wallet.ui.test.TestWalletDetails
import com.tangem.tap.store
import com.tangem.wallet.R

View file

@ -15,10 +15,16 @@ class WalletWarningConverter(
override fun convert(message: WalletWarning): WalletWarningDescription {
val warningMessage = when (message) {
is WalletWarning.ExistentialDeposit -> {
context.getString(
R.string.warning_existential_deposit_message,
message.currencyName, message.currencySymbols, message.existentialDepositString,
)
}
is WalletWarning.BalanceNotEnoughForFee -> {
context.getString(
R.string.token_details_send_blocked_fee_format,
message.blockchainFullName, message.blockchainFullName
message.blockchainFullName, message.blockchainFullName,
)
}
WalletWarning.TransactionInProgress -> {
@ -27,7 +33,7 @@ class WalletWarningConverter(
is WalletWarning.Rent -> {
context.getString(
R.string.solana_rent_warning,
message.walletRent.minRentValue, message.walletRent.rentExemptValue
message.walletRent.minRentValue, message.walletRent.rentExemptValue,
)
}
}

View file

@ -15,6 +15,7 @@ import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.images.load
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ItemCurrencyWalletBinding
@ -62,7 +63,7 @@ class WalletAdapter
fun bind(wallet: WalletData) = with(binding) {
val status = wallet.currencyData.status
// Skip changes when on refreshing status
if (status == BalanceStatus.Refreshing || wallet.fiatRateString == null) return@with
if (status == BalanceStatus.Refreshing) return@with
val statusMessage = when (status) {
BalanceStatus.TransactionInProgress -> {

View file

@ -21,7 +21,7 @@ private const val VOYR = "VOYRME"
class CurrencyIconRequest(
private val currencyImageView: ImageFilterView,
private val currencyTextView: TextView,
private val currencyTextView: TextView?,
private val token: Token?,
private val blockchain: Blockchain,
) {
@ -38,7 +38,7 @@ class CurrencyIconRequest(
loadBlockchainIconBase(
onStart = {
currencyImageView.colorFilter = null
}
},
)
}
@ -46,7 +46,7 @@ class CurrencyIconRequest(
loadBlockchainIconBase(
onStart = {
currencyImageView.saturation = 0f
}
},
)
}
@ -57,7 +57,11 @@ class CurrencyIconRequest(
},
onSuccess = {
currencyImageView.colorFilter = null
}
},
onError = {
currencyImageView.setColorFilter(it.getColor())
currencyTextView?.setTextColor(it.getTextColor())
},
)
}
@ -65,7 +69,12 @@ class CurrencyIconRequest(
loadTokenIconBase(
onStart = {
currencyImageView.saturation = 0f
}
},
onError = {
currencyImageView.saturation = 0f
currencyImageView.setColorFilter(it.getColor(true))
currencyTextView?.setTextColor(it.getTextColor(true))
},
)
}
@ -94,15 +103,19 @@ class CurrencyIconRequest(
data = getTokenIcon(token, blockchain),
placeholderRes = R.drawable.shape_circle,
onStart = {
currencyTextView.text = token.symbol.take(1)
currencyTextView.setTextColor(token.getTextColor())
currencyTextView?.text = token.name.take(1)
currencyTextView?.setTextColor(token.getTextColor())
onStart(token)
},
onSuccess = {
currencyTextView.text = null
currencyTextView?.text = null
onSuccess(token)
},
onError = { onError(token) },
onError = {
// for some reason the onStart doesn't call if an error occurs
currencyTextView?.text = token.name.take(1)
onError(token)
},
)
}
}
@ -122,7 +135,7 @@ private inline fun ImageView.loadIcon(
.listener(
onStart = { onStart() },
onSuccess = { _, _ -> onSuccess() },
onError = { _, _ -> onError() }
onError = { _, _ -> onError() },
)
.target(imageView = this)
.build()

View file

@ -4,13 +4,11 @@ import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.constraintlayout.utils.widget.ImageFilterView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.extensions.getRoundIconRes
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.wallet.databinding.ViewCurrencyIconBinding
import kotlin.math.roundToInt
@ -25,46 +23,50 @@ class CurrencyIconView @JvmOverloads constructor(
this,
)
private val currencyImageView: ImageFilterView
val currencyImageView: ImageFilterView
get() = binding.ivCurrency
private val currencyTextView: TextView
val currencyTextView: TextView
get() = binding.tvTokenLetter
private var isBlockchainIconVisible: Boolean
get() = binding.ivBlockchain.isVisible
set(value) = binding.ivBlockchain::isVisible.set(value)
val blockchainBadge: ImageFilterView
get() = binding.ivBlockchainBadge
private var isBadgeVisible: Boolean
get() = binding.badge.isVisible
set(value) = binding.badge::isVisible.set(value)
var isBlockchainBadgeVisible: Boolean
get() = binding.ivBlockchainBadge.isVisible
set(value) = binding.ivBlockchainBadge::isVisible.set(value)
@DrawableRes
private var blockchainIconRes: Int? = null
set(value) {
if (value != null && value != field && isBlockchainIconVisible) {
binding.ivBlockchain.setImageResource(value)
field = value
}
}
var isCustomCurrencyBadgeVisible: Boolean
get() = binding.customBadge.isVisible
set(value) = binding.customBadge::isVisible.set(value)
init {
minWidth = dpToPx(48f).roundToInt()
minHeight = dpToPx(48f).roundToInt()
}
}
fun load(
currency: Currency,
derivationStyle: DerivationStyle?,
) {
isBlockchainIconVisible = currency.isToken()
isBadgeVisible = currency.isCustomCurrency(derivationStyle)
blockchainIconRes = currency.blockchain.getRoundIconRes()
fun CurrencyIconView.load(
currency: Currency,
derivationStyle: DerivationStyle?,
) {
isCustomCurrencyBadgeVisible = currency.isCustomCurrency(derivationStyle)
CurrencyIconRequest(
currencyImageView = currencyImageView,
currencyTextView = currencyTextView,
token = (currency as? Currency.Token)?.token,
blockchain = currency.blockchain,
).load()
if (currency.isToken()) {
// load a blockchain icon into the blockchain badge
isBlockchainBadgeVisible = true
CurrencyIconRequest(
currencyImageView = currencyImageView,
currencyTextView = currencyTextView,
token = (currency as? Currency.Token)?.token,
currencyImageView = blockchainBadge,
currencyTextView = null,
token = null,
blockchain = currency.blockchain,
).load()
}

View file

@ -35,12 +35,13 @@
android:textColor="@android:color/white"
android:textSize="25sp"
android:textStyle="bold"
android:translationY="-1dp"
tools:text="J" />
</androidx.cardview.widget.CardView>
<ImageView
android:id="@+id/iv_blockchain"
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/iv_blockchain_badge"
android:layout_width="18dp"
android:layout_height="18dp"
android:background="@drawable/shape_circle"
@ -54,7 +55,7 @@
tools:visibility="visible" />
<View
android:id="@+id/badge"
android:id="@+id/custom_badge"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/shape_badge_custom_currency"

View file

@ -38,30 +38,25 @@
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
<string name="alert_manage_tokens_unsupported_message">Tokens in Solana network are not supported by this card due to firmware limitation.</string>
<string name="warning_token_send_unsupported_message">Do not transfer your funds to tokens in this blockchain, otherwise it may lead to their irretrievable loss.</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="alert_funds_restoration_message">If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds</string>
<string name="common_custom">Custom</string>
<string name="common_notice">Notice</string>
<string name="common_attention">Attention</string>
<string name="common_server_unavailable">The server is not available, please try again later</string>
<string name="token_details_hide_token">Hide token</string>
<string name="token_details_hide_alert_title">Hide %s</string>
<string name="token_details_hide_alert_hide">Hide</string>
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
<string name="token_details_unable_hide_alert_title">Unable to hide %s</string>
<string name="token_details_unable_hide_alert_message">The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
<string name="wallet_currency_subtitle">%s network</string>
<string name="card_settings_title">Card Settings</string>
<string name="card_settings_security_mode">Security Mode</string>
<string name="card_settings_security_mode_footer">Selected application protection method</string>
@ -81,18 +76,15 @@
<string name="app_settings_off_saved_access_code_alert_message">This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.</string>
<string name="wallet_connect_title">WalletConnect</string>
<string name="wallet_connect_subtitle">Connect to Dapps</string>
<string name="details_row_title_create_backup">Link More Cards</string>
<string name="details_row_title_create_backup_footer">You can synchronize up to three cards into one wallet. It can only be done once.</string>
<string name="details_row_privacy_policy">Privacy policy</string>
<string name="reset_card_to_factory_navigation_title">Reset to factory settings</string>
<string name="reset_card_to_factory_message">This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="reset_card_to_factory_warning_message">I understand that after performing this action, I will no longer have access to the current wallet</string>
<string name="reset_card_to_factory_button_title">Reset the card</string>
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="wallet_connect_select_network">Select network</string>
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
</resources>

View file

@ -38,30 +38,25 @@
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
<string name="alert_manage_tokens_unsupported_message">Tokens in Solana network are not supported by this card due to firmware limitation.</string>
<string name="warning_token_send_unsupported_message">Do not transfer your funds to tokens in this blockchain, otherwise it may lead to their irretrievable loss.</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="alert_funds_restoration_message">If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds</string>
<string name="common_custom">Custom</string>
<string name="common_server_unavailable">The server is not available, please try again later</string>
<string name="common_notice">Notice</string>
<string name="common_attention">Attention</string>
<string name="token_details_hide_token">Hide token</string>
<string name="token_details_hide_alert_title">Hide %s</string>
<string name="token_details_hide_alert_hide">Hide</string>
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
<string name="token_details_unable_hide_alert_title">Unable to hide %s</string>
<string name="token_details_unable_hide_alert_message">The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
<string name="wallet_currency_subtitle">%s network</string>
<string name="card_settings_title">Card Settings</string>
<string name="card_settings_security_mode">Security Mode</string>
<string name="card_settings_security_mode_footer">Selected application protection method</string>
@ -81,18 +76,15 @@
<string name="app_settings_off_saved_access_code_alert_message">This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.</string>
<string name="wallet_connect_title">WalletConnect</string>
<string name="wallet_connect_subtitle">Connect to Dapps</string>
<string name="details_row_title_create_backup">Link More Cards</string>
<string name="details_row_title_create_backup_footer">You can synchronize up to three cards into one wallet. It can only be done once.</string>
<string name="details_row_privacy_policy">Privacy policy</string>
<string name="reset_card_to_factory_navigation_title">Reset to factory settings</string>
<string name="reset_card_to_factory_message">This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="reset_card_to_factory_warning_message">I understand that after performing this action, I will no longer have access to the current wallet</string>
<string name="reset_card_to_factory_button_title">Reset the card</string>
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="wallet_connect_select_network">Select network</string>
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
</resources>

View file

@ -38,31 +38,25 @@
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
<string name="alert_manage_tokens_unsupported_message">Tokens in Solana network are not supported by this card due to firmware limitation.</string>
<string name="warning_token_send_unsupported_message">Do not transfer your funds to tokens in this blockchain, otherwise it may lead to their irretrievable loss.</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="alert_funds_restoration_message">If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds</string>
<string name="common_custom">Custom</string>
<string name="common_server_unavailable">The server is not available, please try again later</string>
<string name="common_notice">Notice</string>
<string name="common_attention">Attention</string>
<string name="token_details_hide_token">Hide token</string>
<string name="token_details_hide_alert_title">Hide %s</string>
<string name="token_details_hide_alert_hide">Hide</string>
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
<string name="token_details_unable_hide_alert_title">Unable to hide %s</string>
<string name="token_details_unable_hide_alert_message">The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
<string name="wallet_currency_subtitle">%s network</string>
<string name="card_settings_title">Card Settings</string>
<string name="card_settings_security_mode">Security Mode</string>
<string name="card_settings_security_mode_footer">Selected application protection method</string>
@ -82,18 +76,15 @@
<string name="app_settings_off_saved_access_code_alert_message">This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.</string>
<string name="wallet_connect_title">WalletConnect</string>
<string name="wallet_connect_subtitle">Connect to Dapps</string>
<string name="details_row_title_create_backup">Link More Cards</string>
<string name="details_row_title_create_backup_footer">You can synchronize up to three cards into one wallet. It can only be done once.</string>
<string name="details_row_privacy_policy">Privacy policy</string>
<string name="reset_card_to_factory_navigation_title">Reset to factory settings</string>
<string name="reset_card_to_factory_message">This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="reset_card_to_factory_warning_message">I understand that after performing this action, I will no longer have access to the current wallet</string>
<string name="reset_card_to_factory_button_title">Reset the card</string>
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="wallet_connect_select_network">Select network</string>
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
</resources>

View file

@ -38,47 +38,36 @@
<string name="alert_demo_message">Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие.</string>
<string name="alert_demo_feature_disabled">Эта функция недоступна в демонстрационном режиме</string>
<string name="token_details_send_blocked_fee_format">Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек.</string>
<string name="currency_subtitle_expanded">Доступные сети</string>
<string name="alert_manage_tokens_addresses_message">Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства.</string>
<string name="alert_manage_tokens_unsupported_message">Токены в сети Solana не поддерживаются этой картой из-за ограничений прошивки.</string>
<string name="warning_token_send_unsupported_message">Не осуществляйте перевод на токены в данной сети, иначе это может привести к их безвозвратной утере.</string>
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
<string name="alert_funds_restoration_message">Если вы совершили ошибку с выбором сети при переводе средств с биржи, эта инструкция поможет вам восстановить средства</string>
<string name="common_custom">Пользовательский</string>
<string name="common_notice">Уведомление</string>
<string name="common_attention">Внимание</string>
<string name="common_server_unavailable">Сервер недоступен, повторите попытку позднее</string>
<string name="main_page_balance">Баланс</string>
<string name="main_processing_full_amount">В сумме учтены не все монеты</string>
<string name="main_manage_tokens">Управление токенами</string>
<string name="token_item_no_rate">Нет цены</string>
<string name="token_details_hide_token">Скрыть токен</string>
<string name="token_details_hide_alert_title">Скрыть %s</string>
<string name="token_details_hide_alert_hide">Скрыть</string>
<string name="token_details_hide_alert_message">Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.</string>
<string name="token_details_unable_hide_alert_title">Невозможно скрыть %s</string>
<string name="token_details_unable_hide_alert_message">Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети.</string>
<string name="wallet_connect_network_not_found_format">Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново.</string>
<string name="wallet_connect_scanner_error_unsupported_network">Сеть не поддерживается. Пожалуйста, выберите другую сеть.</string>
<string name="wallet_currency_subtitle">Сеть %s</string>
<string name="main_no_backup_warning_title">Бэкап кошелька не был произведен</string>
<string name="main_no_backup_warning_subtitle">Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру</string>
<string name="russian_bank_card_warning_title">Карты банков РФ в данный момент не принимаются</string>
<string name="russian_bank_card_warning_subtitle">У вас есть карта банка другой страны или платежной системы UnionPay?</string>
<string name="common_yes">Да</string>
<string name="details_ask_a_question">Чат</string>
<string name="chat_bot_name">Tangem Bot</string>
<string name="card_settings_title">Настройки карты</string>
<string name="card_settings_security_mode">Тип безопасности</string>
<string name="card_settings_security_mode_footer">Выбранный способ защиты приложения</string>
@ -98,18 +87,15 @@
<string name="app_settings_off_saved_access_code_alert_message">Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком.</string>
<string name="wallet_connect_title">WalletConnect</string>
<string name="wallet_connect_subtitle">Подключение к Dapps</string>
<string name="details_row_title_create_backup">Добавить еще карты</string>
<string name="details_row_title_create_backup_footer">Вы можете объединить до трех карт в одном кошельке. Это можно сделать только один раз.</string>
<string name="details_row_privacy_policy">Privacy policy</string>
<string name="reset_card_to_factory_navigation_title">Сброс к заводским настройкам</string>
<string name="reset_card_to_factory_message">Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа</string>
<string name="reset_card_to_factory_warning_message">Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку</string>
<string name="reset_card_to_factory_button_title">Сбросить карту</string>
<string name="card_settings_reset_card_to_factory">Сброс к заводским настройкам</string>
<string name="card_settings_reset_card_to_factory_footer">Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа.</string>
<string name="wallet_connect_select_network">Выберите сеть</string>
<string name="warning_existential_deposit_message">Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены.</string>
</resources>

View file

@ -38,49 +38,38 @@
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
<string name="alert_manage_tokens_unsupported_message">Tokens in Solana network are not supported by this card due to firmware limitation.</string>
<string name="warning_token_send_unsupported_message">Do not transfer your funds to tokens in this blockchain, otherwise it may lead to their irretrievable loss.</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="alert_funds_restoration_message">If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds</string>
<string name="common_custom">Custom</string>
<string name="common_notice">Notice</string>
<string name="common_attention">Attention</string>
<string name="common_server_unavailable">The server is not available, please try again later</string>
<string name="main_page_balance">Total balance</string>
<string name="main_processing_full_amount">The amount does not include some of your funds</string>
<string name="main_manage_tokens">Manage tokens</string>
<string name="token_item_no_rate">No rate</string>
<string name="token_details_hide_token">Hide token</string>
<string name="token_details_hide_alert_title">Hide %s</string>
<string name="token_details_hide_alert_hide">Hide</string>
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
<string name="token_details_unable_hide_alert_title">Unable to hide %s</string>
<string name="token_details_unable_hide_alert_message">The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
<string name="wallet_currency_subtitle">%s network</string>
<string name="main_no_backup_warning_title">Your wallet has not been backed up</string>
<string name="main_no_backup_warning_subtitle">To protect your assets, we advise you to carry out this procedure</string>
<string name="wallet_hide_token" translatable="false">Remove token</string>
<string name="russian_bank_card_warning_title">Russian bank cards are not accepted at the moment</string>
<string name="russian_bank_card_warning_subtitle">Do you have a bank card of another country or a UnionPay card?</string>
<string name="common_yes">Yes</string>
<string name="common_no">No</string>
<string name="details_ask_a_question">Chat</string>
<string name="chat_bot_name">Tangem Bot</string>
<string name="card_settings_title">Card Settings</string>
<string name="card_settings_security_mode">Security Mode</string>
<string name="card_settings_security_mode_footer">Selected application protection method</string>
@ -100,18 +89,15 @@
<string name="app_settings_off_saved_access_code_alert_message">This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.</string>
<string name="wallet_connect_title">WalletConnect</string>
<string name="wallet_connect_subtitle">Connect to Dapps</string>
<string name="details_row_title_create_backup">Link More Cards</string>
<string name="details_row_title_create_backup_footer">You can synchronize up to three cards into one wallet. It can only be done once.</string>
<string name="details_row_privacy_policy">Privacy policy</string>
<string name="reset_card_to_factory_navigation_title">Reset to factory settings</string>
<string name="reset_card_to_factory_message">This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="reset_card_to_factory_warning_message">I understand that after performing this action, I will no longer have access to the current wallet</string>
<string name="reset_card_to_factory_button_title">Reset the card</string>
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="wallet_connect_select_network">Select network</string>
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
</resources>

View file

@ -2,7 +2,7 @@ ext.versions = [
kotlin : '1.6.10',
build_gradle : '7.1.3',
tangem_card_sdk : 'develop-159',
tangem_blockchain_sdk: 'develop-105',
tangem_blockchain_sdk: 'develop-111',
// tangem_blockchain_sdk: '0.0.1',
]

View file

@ -5,7 +5,7 @@ import com.tangem.blockchain.common.Blockchain
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
return when (networkId) {
"arbitrum-one" -> Blockchain.Arbitrum
"arbitrum-one/test" -> Blockchain.ArbitrumTestnet
"arbitrum/test" -> Blockchain.ArbitrumTestnet
"avalanche", "avalanche-2" -> Blockchain.Avalanche
"avalanche/test", "avalanche-2/test" -> Blockchain.AvalancheTestnet
"binancecoin" -> Blockchain.Binance
@ -38,6 +38,9 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
"tron/test" -> Blockchain.TronTestnet
"xrp", "ripple" -> Blockchain.XRP
"xdai" -> Blockchain.Gnosis
"polkadot" -> Blockchain.Polkadot
"polkadot/test" -> Blockchain.PolkadotTestnet
"kusama" -> Blockchain.Kusama
else -> null
}
}
@ -80,6 +83,9 @@ fun Blockchain.toNetworkId(): String {
Blockchain.Tron -> "tron"
Blockchain.TronTestnet -> "tron/test"
Blockchain.Gnosis -> "xdai"
Blockchain.Polkadot -> "polkadot"
Blockchain.PolkadotTestnet -> "polkadot/test"
Blockchain.Kusama -> "kusama"
}
}
@ -97,14 +103,16 @@ fun Blockchain.toCoinId(): String {
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> "avalanche-2"
Blockchain.Solana, Blockchain.SolanaTestnet -> "solana"
Blockchain.Fantom, Blockchain.FantomTestnet -> "fantom"
Blockchain.Tron, Blockchain.TronTestnet -> "tron"
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> "polkadot"
Blockchain.Ducatus -> "ducatus"
Blockchain.Litecoin -> "litecoin"
Blockchain.RSK -> "rootstock"
Blockchain.Tezos -> "tezos"
Blockchain.XRP -> "ripple"
Blockchain.Dogecoin -> "dogecoin"
Blockchain.Tron, Blockchain.TronTestnet -> "tron"
Blockchain.Gnosis -> "xdai"
Blockchain.Kusama -> "kusama"
Blockchain.Unknown -> "unknown"
}
}

View file

@ -18,8 +18,7 @@ fun Card.supportedBlockchains(): List<Blockchain> {
Blockchain.fromCurve(EllipticCurve.Secp256k1)
}
else -> {
Blockchain.fromCurve(EllipticCurve.Secp256k1) +
Blockchain.fromCurve(EllipticCurve.Ed25519)
(Blockchain.fromCurve(EllipticCurve.Secp256k1) + Blockchain.fromCurve(EllipticCurve.Ed25519)).distinct()
}
}
val filtered = supportedBlockchains.filter { isTestCard == it.isTestnet() }

View file

@ -0,0 +1,20 @@
package com.tangem.domain.features
import com.google.common.truth.Truth
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toNetworkId
import org.junit.Test
class BlockchainTests {
@Test
fun allNetworkIdsAreImplemented() {
val unimplementedIds = Blockchain.values()
.toMutableList()
.apply { remove(Blockchain.Unknown) }
.map { it to Blockchain.fromNetworkId(it.toNetworkId()) }
.mapNotNull { if(it.second == null) it.first else null }
Truth.assertThat(unimplementedIds).isEmpty()
}
}

View file

@ -1,16 +0,0 @@
package com.tangem.domain.features
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}