Updated on 2026-08-14

This commit is contained in:
Tangem 2022-09-15 22:33:53 +04:00
commit 02f350d339
66 changed files with 759 additions and 649 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

@ -1,24 +0,0 @@
package com.tangem.wallet
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("com.tangem.wallet", appContext.packageName)
}
}

View file

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

View file

@ -4,6 +4,7 @@ import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.View
import android.view.Window
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat
@ -85,6 +86,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
}
private fun systemActions() {
WindowCompat.setDecorFitsSystemWindows(window, false)
val windowInsetsController = WindowInsetsControllerCompat(window, binding.root)

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

@ -51,11 +51,10 @@ suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
fun WalletManager?.getToUpUrl(): String? {
val globalState = store.state.globalState
val exchangeManager = globalState.exchangeManager ?: return null
val wallet = this?.wallet ?: return null
val defaultAddress = wallet.address
return exchangeManager.getUrl(
return globalState.exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Buy,
blockchain = wallet.blockchain,
cryptoCurrencyName = wallet.blockchain.currency,

View file

@ -0,0 +1,8 @@
package com.tangem.tap.common.feature
/**
[REDACTED_AUTHOR]
*/
interface Feature {
fun featureIsSwitchedOn():Boolean
}

View file

@ -25,7 +25,7 @@ data class GlobalState(
val appCurrency: FiatCurrency = FiatCurrency.Default,
val scanCardFailsCounter: Int = 0,
val dialog: StateDialog? = null,
val exchangeManager: CurrencyExchangeManager? = null,
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
val resources: AndroidResources = AndroidResources(),
val analyticsHandlers: AnalyticsHandler? = null,
val userCountryCode: String? = null,

View file

@ -2,7 +2,6 @@ package com.tangem.tap.features.details.ui.cardsettings
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@ -10,6 +9,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -51,14 +54,12 @@ fun CardSettingsReadCard(
Column(
modifier = modifier
.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween,
) {
Box(
modifier = modifier
.fillMaxWidth(),
) {
.fillMaxWidth()
.padding(bottom = 40.dp),
) {
Image(
modifier = modifier
.fillMaxWidth()
@ -77,16 +78,13 @@ fun CardSettingsReadCard(
contentDescription = "",
contentScale = ContentScale.FillWidth,
)
}
Spacer(modifier = Modifier.weight(1f))
Column(
modifier = modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 32.dp),
) {
Text(
text = stringResource(id = R.string.scan_card_settings_title),
color = colorResource(id = R.color.text_primary_1),
@ -97,6 +95,9 @@ fun CardSettingsReadCard(
text = stringResource(id = R.string.scan_card_settings_message),
color = colorResource(id = R.color.text_secondary),
style = TangemTypography.body1,
modifier = modifier
.verticalScroll(rememberScrollState())
.weight(weight = 1f, fill = false),
)
Spacer(modifier = modifier.size(29.dp))
DetailsMainButton(
@ -113,11 +114,13 @@ fun CardSettings(
state: CardSettingsScreenState,
modifier: Modifier = Modifier,
) {
Column(
if (state.cardDetails == null) return
LazyColumn(
modifier = modifier
.fillMaxWidth(),
) {
state.cardDetails?.map {
items(state.cardDetails) {
val paddingBottom = when (it) {
is CardInfo.CardId, is CardInfo.Issuer -> 12.dp
is CardInfo.SignedHashes -> 14.dp
@ -156,9 +159,7 @@ fun CardSettings(
style = TangemTypography.body2,
)
}
}
}
}

View file

@ -1,18 +1,18 @@
package com.tangem.tap.features.details.ui.resetcard
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.IconToggleButton
import androidx.compose.material.Text
@ -36,18 +36,11 @@ fun ResetCardScreen(
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.background(colorResource(id = R.color.background_primary))) {
Image(
painter = painterResource(id = R.drawable.ic_reset_background),
contentDescription = "",
modifier = modifier.offset(y = (-16).dp),
)
SettingsScreensScaffold(
content = { ResetCardView(state = state, modifier = modifier) },
onBackClick = onBackPressed,
backgroundColor = Color.Transparent,
)
}
}
@Composable
@ -57,81 +50,91 @@ fun ResetCardView(
) {
Column(
modifier = modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Bottom,
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.SpaceBetween,
) {
Box(
modifier = modifier,
) {
Image(
painter = painterResource(id = R.drawable.ic_reset_background),
contentDescription = "",
modifier = modifier.offset(y = (-82).dp),
)
ScreenTitle(titleRes = R.string.reset_card_to_factory_navigation_title)
}
Spacer(
modifier = modifier.weight(1f),
)
Column(
modifier = modifier
.defaultMinSize(20.dp)
.weight(1f),
)
Text(
text = stringResource(id = R.string.common_attention),
modifier = modifier.padding(start = 20.dp, end = 20.dp),
style = TangemTypography.headline3,
color = colorResource(id = R.color.text_primary_1),
)
Spacer(modifier = modifier.size(24.dp))
Text(
text = stringResource(id = R.string.reset_card_to_factory_message),
modifier = modifier.padding(start = 20.dp, end = 20.dp),
style = TangemTypography.body1,
color = colorResource(id = R.color.text_secondary),
)
Spacer(modifier = modifier.size(44.dp))
Row(
modifier = modifier
.fillMaxWidth()
.padding(end = 20.dp),
.offset(y = (-32).dp),
verticalArrangement = Arrangement.Bottom,
) {
IconToggleButton(
checked = state.accepted,
onCheckedChange = state.onAcceptWarningToggleClick,
modifier = modifier.padding(start = 20.dp, end = 20.dp),
) {
Icon(
painter = painterResource(
if (state.accepted) {
R.drawable.ic_accepted
} else {
R.drawable.ic_unticked
},
),
contentDescription = null,
tint = if (state.accepted) {
colorResource(id = R.color.icon_accent)
} else {
colorResource(id = R.color.icon_secondary)
},
)
}
Text(
text = stringResource(id = R.string.reset_card_to_factory_warning_message),
style = TangemTypography.body2,
text = stringResource(id = R.string.common_attention),
modifier = modifier.padding(start = 20.dp, end = 20.dp),
style = TangemTypography.headline3,
color = colorResource(id = R.color.text_primary_1),
)
Spacer(modifier = modifier.size(24.dp))
Text(
text = stringResource(id = R.string.reset_card_to_factory_message),
modifier = modifier
.padding(start = 20.dp, end = 20.dp),
style = TangemTypography.body1,
color = colorResource(id = R.color.text_secondary),
)
}
Spacer(modifier = modifier.size(32.dp))
Box(
modifier = modifier
.padding(start = 16.dp, end = 16.dp, bottom = 32.dp),
) {
DetailsMainButton(
title = stringResource(id = R.string.reset_card_to_factory_button_title),
onClick = state.onResetButtonClick,
enabled = state.resetButtonEnabled,
)
}
Spacer(modifier = modifier.size(44.dp))
Row(
modifier = modifier
.fillMaxWidth()
.padding(end = 20.dp),
) {
IconToggleButton(
checked = state.accepted,
onCheckedChange = state.onAcceptWarningToggleClick,
modifier = modifier.padding(start = 20.dp, end = 20.dp),
) {
Icon(
painter = painterResource(
if (state.accepted) {
R.drawable.ic_accepted
} else {
R.drawable.ic_unticked
},
),
contentDescription = null,
tint = if (state.accepted) {
colorResource(id = R.color.icon_accent)
} else {
colorResource(id = R.color.icon_secondary)
},
)
}
Text(
text = stringResource(id = R.string.reset_card_to_factory_warning_message),
style = TangemTypography.body2,
color = colorResource(id = R.color.text_secondary),
)
}
Spacer(modifier = modifier.size(32.dp))
Box(
modifier = modifier
.padding(start = 16.dp, end = 16.dp),
) {
DetailsMainButton(
title = stringResource(id = R.string.reset_card_to_factory_button_title),
onClick = state.onResetButtonClick,
enabled = state.resetButtonEnabled,
)
}
}
}
}

View file

@ -6,10 +6,11 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.RadioButton
import androidx.compose.material.RadioButtonDefaults
import androidx.compose.material.Text
@ -22,6 +23,7 @@ import androidx.compose.ui.unit.dp
import com.tangem.tap.common.compose.TangemTypography
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.details.ui.common.DetailsMainButton
import com.tangem.tap.features.details.ui.common.ScreenTitle
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
import com.tangem.wallet.R
@ -33,7 +35,7 @@ fun SecurityModeScreen(
) {
SettingsScreensScaffold(
content = { SecurityModeOptions(state = state, modifier = modifier) },
titleRes = R.string.card_settings_security_mode,
// titleRes = R.string.card_settings_security_mode,
onBackClick = onBackPressed,
)
}
@ -47,10 +49,13 @@ fun SecurityModeOptions(
Column(
modifier = modifier
.fillMaxSize()
.padding(bottom = 28.dp)
.offset(y = (-16).dp),
.verticalScroll(rememberScrollState())
.padding(bottom = 28.dp),
verticalArrangement = Arrangement.SpaceBetween,
) {
ScreenTitle(titleRes = R.string.card_settings_security_mode, modifier.padding(bottom = 36.dp))
state.availableOptions.map {
SecurityOption(option = it, state = state, modifier = modifier)
}

View file

@ -9,6 +9,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import com.google.zxing.Result
import com.otaliastudios.cameraview.CameraView
@ -23,6 +24,7 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
store.dispatch(NavigationAction.PopBackTo())
@ -54,7 +56,7 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler {
override fun handleResult(result: Result) {
store.dispatch(NavigationAction.PopBackTo())
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, false) }
if (!result.text.isNullOrBlank()) {
store.dispatch(WalletConnectAction.OpenSession(result.text))
}

View file

@ -26,7 +26,7 @@ data class OnboardingNoteState(
get() = steps.indexOf(currentStep)
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { _, _ ->
store.state.globalState.exchangeManager?.availableForBuy(walletBalance.currency) ?: false
store.state.globalState.exchangeManager.availableForBuy(walletBalance.currency)
}
}

View file

@ -56,7 +56,7 @@ data class TwinCardsState(
get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { _, _ ->
store.state.globalState.exchangeManager?.availableForBuy(walletBalance.currency) ?: false
store.state.globalState.exchangeManager.availableForBuy(walletBalance.currency)
}
}

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

@ -3,8 +3,19 @@ package com.tangem.tap.features.send.redux.reducers
import com.tangem.blockchain.blockchains.stellar.StellarMemo
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.TransactionExtrasAction.*
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.send.redux.TransactionExtrasAction.BinanceMemo
import com.tangem.tap.features.send.redux.TransactionExtrasAction.Prepare
import com.tangem.tap.features.send.redux.TransactionExtrasAction.Release
import com.tangem.tap.features.send.redux.TransactionExtrasAction.XlmMemo
import com.tangem.tap.features.send.redux.TransactionExtrasAction.XrpDestinationTag
import com.tangem.tap.features.send.redux.states.BinanceMemoState
import com.tangem.tap.features.send.redux.states.InputViewValue
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.send.redux.states.TransactionExtraError
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
import com.tangem.tap.features.send.redux.states.XlmMemoState
import com.tangem.tap.features.send.redux.states.XlmMemoType
import com.tangem.tap.features.send.redux.states.XrpDestinationTagState
/**
[REDACTED_AUTHOR]
@ -53,42 +64,33 @@ class TransactionExtrasReducer : SendInternalReducer {
}
private fun handleXlmMemo(
action: XlmMemo,
sendState: SendState,
infoState: TransactionExtrasState,
action: XlmMemo,
sendState: SendState,
infoState: TransactionExtrasState,
): SendState {
fun clearMemo(memo: XlmMemoState): XlmMemoState = memo.copy(text = null, id = null, error = null)
val result = when (action) {
// is XlmMemo.ChangeSelectedMemo -> {
// val inputViewValue = InputViewValue("", false)
// val memo = infoState.xlmMemo?.copy(
// viewFieldValue = inputViewValue,
// selectedMemoType = action.memoType,
// ) ?: XlmMemoState(inputViewValue, action.memoType)
//
// infoState.copy(xlmMemo = clearMemo(memo))
// }
is XlmMemo.HandleUserInput -> {
val inputViewValue = InputViewValue(action.data, true)
var memo = infoState.xlmMemo?.copy(viewFieldValue = inputViewValue)
?: XlmMemoState(inputViewValue)
?: XlmMemoState(inputViewValue)
memo = clearMemo(memo)
memo = when (infoState.xlmMemo?.selectedMemoType) {
XlmMemoType.TEXT -> memo.copy(text = StellarMemo.Text(action.data))
XlmMemoType.ID -> {
val id = action.data.toBigIntegerOrNull()
if (id != null) {
if (id > XlmMemoState.MAX_NUMBER) {
memo.copy(error = TransactionExtraError.INVALID_XLM_MEMO)
} else {
memo.copy(id = StellarMemo.Id(id))
}
memo = when (memo.selectedMemoType) {
XlmMemoType.TEXT -> {
if (XlmMemoState.isAssignableValue(action.data)) {
memo.copy(text = StellarMemo.Text(action.data))
} else {
memo
memo.copy(error = TransactionExtraError.INVALID_XLM_MEMO)
}
}
XlmMemoType.ID -> {
if (XlmMemoState.isAssignableValue(action.data)) {
memo.copy(id = StellarMemo.Id(action.data.toBigInteger()))
} else {
memo.copy(error = TransactionExtraError.INVALID_XLM_MEMO)
}
}
null -> memo
}
infoState.copy(xlmMemo = memo)
}
@ -117,16 +119,16 @@ class TransactionExtrasReducer : SendInternalReducer {
}
private fun handleXrpTag(
action: XrpDestinationTag,
sendState: SendState,
infoState: TransactionExtrasState,
action: XrpDestinationTag,
sendState: SendState,
infoState: TransactionExtrasState,
): SendState {
val result = when (action) {
is XrpDestinationTag.HandleUserInput -> {
val tag = action.data.toLongOrNull()
if (tag != null) {
val input = InputViewValue(action.data, true)
val tagState = if (tag <= XrpDestinationTagState.MAX_NUMBER){
val tagState = if (tag <= XrpDestinationTagState.MAX_NUMBER) {
XrpDestinationTagState(input, tag)
} else {
XrpDestinationTagState(input, error = TransactionExtraError.INVALID_DESTINATION_TAG)

View file

@ -1,19 +1,20 @@
package com.tangem.tap.features.send.redux.states
import androidx.core.text.isDigitsOnly
import com.tangem.blockchain.blockchains.stellar.StellarMemo
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
import java.math.BigInteger
data class AddressPayIdState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val normalFieldValue: String? = null,
val truncatedFieldValue: String? = null,
val destinationWalletAddress: String? = null,
val error: AddressPayIdVerifyAction.Error? = null,
val truncateHandler: ((String) -> String)? = null,
val sendingToPayIdEnabled: Boolean = false,
val pasteIsEnabled: Boolean = false,
val inputIsEnabled: Boolean = true
val viewFieldValue: InputViewValue = InputViewValue(""),
val normalFieldValue: String? = null,
val truncatedFieldValue: String? = null,
val destinationWalletAddress: String? = null,
val error: AddressPayIdVerifyAction.Error? = null,
val truncateHandler: ((String) -> String)? = null,
val sendingToPayIdEnabled: Boolean = false,
val pasteIsEnabled: Boolean = false,
val inputIsEnabled: Boolean = true,
) : SendScreenState {
override val stateId: StateId = StateId.ADDRESS_PAY_ID
@ -26,9 +27,9 @@ data class AddressPayIdState(
}
data class TransactionExtrasState(
val xlmMemo: XlmMemoState? = null,
val binanceMemo: BinanceMemoState? = null,
val xrpDestinationTag: XrpDestinationTagState? = null
val xlmMemo: XlmMemoState? = null,
val binanceMemo: BinanceMemoState? = null,
val xrpDestinationTag: XrpDestinationTagState? = null,
) : IdStateHolder {
override val stateId: StateId = StateId.TRANSACTION_EXTRAS
}
@ -38,11 +39,10 @@ enum class XlmMemoType {
}
data class XlmMemoState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val selectedMemoType: XlmMemoType = XlmMemoType.ID,
val text: StellarMemo.Text? = null,
val id: StellarMemo.Id? = null,
val error: TransactionExtraError? = null,
val viewFieldValue: InputViewValue = InputViewValue(""),
val text: StellarMemo.Text? = null,
val id: StellarMemo.Id? = null,
val error: TransactionExtraError? = null,
) {
val memo: StellarMemo?
get() = when (selectedMemoType) {
@ -50,16 +50,37 @@ data class XlmMemoState(
XlmMemoType.ID -> id
}
val selectedMemoType: XlmMemoType
get() = determineMemoType(viewFieldValue.value)
companion object {
val MAX_NUMBER: BigInteger = BigInteger("FFFFFFFFFFFFFFFF", 16)
fun determineMemoType(value: String): XlmMemoType = when {
value.isNotEmpty() && value.isDigitsOnly() -> XlmMemoType.ID
else -> XlmMemoType.TEXT
}
fun isAssignableValue(value: String): Boolean = when (determineMemoType(value)) {
XlmMemoType.TEXT -> {
// from org.stellar.sdk.MemoText
value.toByteArray().size <= 28
}
XlmMemoType.ID -> {
try {
// from com.tangem.blockchain.blockchains.stellar.StellarMemo.toStellarSdkMemo
value.toBigInteger() in BigInteger.ZERO..(Long.MAX_VALUE.toBigInteger() * 2.toBigInteger())
} catch (ex: NumberFormatException) {
false
}
}
}
}
}
data class BinanceMemoState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val memo: BigInteger? = null,
val error: TransactionExtraError? = null
) {
val error: TransactionExtraError? = null,
) {
companion object {
val MAX_NUMBER: BigInteger = BigInteger("FFFFFFFFFFFFFFFF", 16)
}
@ -67,9 +88,9 @@ data class BinanceMemoState(
// tag must contains only digits
data class XrpDestinationTagState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val tag: Long? = null,
val error: TransactionExtraError? = null
val viewFieldValue: InputViewValue = InputViewValue(""),
val tag: Long? = null,
val error: TransactionExtraError? = null,
) {
companion object {
const val MAX_NUMBER: Long = 4294967295

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

@ -2,7 +2,6 @@ package com.tangem.tap.features.send.ui.stateSubscribers
import android.app.Dialog
import android.content.Context
import android.text.InputType
import android.text.SpannableStringBuilder
import android.view.View
import android.view.ViewGroup
@ -20,7 +19,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
@ -32,15 +30,14 @@ import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.send.redux.states.StateId
import com.tangem.tap.features.send.redux.states.TransactionExtraError
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 +59,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)
@ -84,14 +78,10 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
showView(binanceMemoContainer, infoState.binanceMemo)
infoState.xlmMemo?.let {
etXlmMemo.inputType = when (it.selectedMemoType) {
XlmMemoType.TEXT -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER
}
if (!it.viewFieldValue.isFromUserInput) etXlmMemo.setText(it.viewFieldValue.value)
if (it.error != null) {
if (it.error == TransactionExtraError.INVALID_XLM_MEMO) {
tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo)
}
} else {
tilXlmMemo.error = null
@ -113,7 +103,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
infoState.binanceMemo?.let {
if (infoState.binanceMemo.error != null) {
if (infoState.binanceMemo.error == TransactionExtraError.INVALID_BINANCE_MEMO) {
tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo)
}
} else {
tilBinanceMemo.error = null
@ -144,6 +134,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 +157,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 +229,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 +259,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 +287,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 +322,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

@ -1,6 +1,11 @@
package com.tangem.tap.features.wallet.models
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.extensions.isAboveZero
import com.tangem.tap.common.extensions.toFormattedString
import java.math.BigDecimal
@ -10,8 +15,8 @@ data class PendingTransaction(
val type: PendingTransactionType,
) {
val address: String? = when (type) {
PendingTransactionType.Incoming -> transactionData.sourceAddress
PendingTransactionType.Outgoing -> transactionData.destinationAddress
PendingTransactionType.Incoming -> nullIfUnknown(transactionData.sourceAddress)
PendingTransactionType.Outgoing -> nullIfUnknown(transactionData.destinationAddress)
PendingTransactionType.Unknown -> null
}
@ -20,6 +25,8 @@ data class PendingTransaction(
val amountValueUi: String? = amountValue?.toFormattedString(transactionData.amount.decimals)
val currency: String = transactionData.amount.currencySymbol
fun nullIfUnknown(address: String):String? = if (address == "unknown") null else address
}
enum class PendingTransactionType { Incoming, Outgoing, Unknown }

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

@ -7,9 +7,6 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.extensions.isZero
import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
@ -17,12 +14,18 @@ import com.tangem.tap.common.toggleWidget.WidgetState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
import com.tangem.tap.features.wallet.models.*
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.models.WalletWarning
import com.tangem.tap.features.wallet.models.hasPendingTransactions
import com.tangem.tap.features.wallet.models.hasSendableAmounts
import com.tangem.tap.features.wallet.models.isSendableAmount
import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount
import com.tangem.tap.features.wallet.redux.reducers.findProgressState
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.store
import org.rekotlin.StateType
import java.math.BigDecimal
@ -48,21 +51,15 @@ data class WalletState(
// if you do not delegate - the application crashes on startup,
// because twinCardsState has not been created yet
val twinCardsState: TwinCardsState by ReadOnlyProperty<Any, TwinCardsState> { thisRef, property ->
val twinCardsState: TwinCardsState by ReadOnlyProperty<Any, TwinCardsState> { _, _ ->
store.state.twinCardsState
}
val isTangemTwins: Boolean
get() = store.state.globalState.scanResponse?.isTangemTwins() == true
val primaryWallet: WalletData? = wallets.firstOrNull()
?.walletsData?.firstOrNull()
val primaryWalletManager: WalletManager? =
if (wallets.isNotEmpty()) wallets[0].walletManager else null
val shouldShowDetails: Boolean =
primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
val isExchangeServiceFeatureOn: Boolean
get() = store.state.globalState.exchangeManager.featureIsSwitchedOn()
val blockchains: List<Blockchain>
get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain }
@ -76,6 +73,14 @@ data class WalletState(
val walletManagers: List<WalletManager>
get() = wallets.mapNotNull { it.walletManager }
val primaryWallet: WalletData? = wallets.firstOrNull()?.walletsData?.firstOrNull()
val primaryWalletManager: WalletManager? = if (wallets.isNotEmpty()) wallets[0].walletManager else null
val shouldShowDetails: Boolean =
primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
fun getWalletManager(currency: Currency?): WalletManager? {
if (currency?.blockchain == null) return null
return getWalletStore(currency)?.walletManager
@ -248,32 +253,6 @@ data class WalletState(
return updatedWallets + remainingWallets
}
fun updateTradeCryptoState(
exchangeManager: CurrencyExchangeManager?,
walletData: WalletData
): WalletData {
return walletData.copy(
tradeCryptoState = TradeCryptoState.from(
exchangeManager,
walletData
)
)
}
fun updateTradeCryptoState(
exchangeManager: CurrencyExchangeManager?,
walletDataList: List<WalletData>
): List<WalletData> {
return walletDataList.map {
it.copy(
tradeCryptoState = TradeCryptoState.from(
exchangeManager,
it
)
)
}
}
private fun updateTotalBalance(): WalletState {
val walletsData = this.wallets
.flatMap(WalletStore::walletsData)
@ -308,6 +287,7 @@ data class WalletState(
companion object {
const val UNKNOWN_AMOUNT_SIGN = ""
const val ROUGH_SIGN = ""
const val CAN_BE_LOWER_SIGN = "<"
}
}
@ -346,29 +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"
}
}
data class TradeCryptoState(
val isAvailableToSell: () -> Boolean = { false },
val isAvailableToBuy: () -> Boolean = { false },
) {
companion object {
fun from(
exchangeManager: CurrencyExchangeManager?,
walletData: WalletData
): TradeCryptoState {
val exchanger = exchangeManager ?: return walletData.tradeCryptoState
val currency = walletData.currency
return TradeCryptoState(
isAvailableToSell = { exchanger.availableForSell(currency) },
isAvailableToBuy = { exchanger.availableForBuy(currency) },
)
}
}
}
@ -378,13 +335,19 @@ data class WalletData(
val walletAddresses: WalletAddresses? = null,
val currencyData: BalanceWidgetData = BalanceWidgetData(),
val updatingWallet: Boolean = false,
val tradeCryptoState: TradeCryptoState = TradeCryptoState(),
val fiatRateString: String? = null,
val fiatRate: BigDecimal? = null,
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)
val isAvailableToSell: Boolean
get() = store.state.globalState.exchangeManager.availableForSell(currency)
fun shouldShowMultipleAddress(): Boolean {
val listOfAddresses = walletAddresses?.list ?: return false
return listOfAddresses.size > 1
@ -398,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

@ -37,20 +37,18 @@ class TradeCryptoMiddleware {
action: WalletAction.TradeCryptoAction.Buy,
) {
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
store.dispatchOnMain(
WalletAction.DialogAction.RussianCardholdersWarningDialog
)
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog)
return
}
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
val exchangeManager = store.state.globalState.exchangeManager ?: return
val card = store.state.globalState.scanResponse?.card ?: return
val appCurrency = store.state.globalState.appCurrency
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
if (addresses.isEmpty()) return
val exchangeManager = store.state.globalState.exchangeManager
val appCurrency = store.state.globalState.appCurrency
val currency = selectedWalletData.currency
if (currency is Currency.Token && currency.blockchain.isTestnet()) {
@ -81,15 +79,13 @@ class TradeCryptoMiddleware {
private fun proceedSellAction() {
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
val exchangeManager = store.state.globalState.exchangeManager ?: return
val appCurrency = store.state.globalState.appCurrency
val appCurrency = store.state.globalState.appCurrency
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
if (addresses.isEmpty()) return
val currency = selectedWalletData.currency
exchangeManager.getUrl(
store.state.globalState.exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Sell,
blockchain = currency.blockchain,
cryptoCurrencyName = currency.currencySymbol,
@ -100,8 +96,8 @@ class TradeCryptoMiddleware {
private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) {
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
val walletManager =
store.state.walletState.getWalletManager(selectedWalletData.currency)
val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency)
store.dispatchOnMain(PrepareSendScreen(
coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin),
coinRate = selectedWalletData.fiatRate,
@ -116,11 +112,10 @@ class TradeCryptoMiddleware {
}
private fun openReceiptUrl(transactionId: String) {
val exchangeManager = store.state.globalState.exchangeManager ?: return
store.dispatchOnMain(NavigationAction.PopBackTo())
exchangeManager.getSellCryptoReceiptUrl(CurrencyExchangeManager.Action.Sell, transactionId)?.let {
store.dispatchOnMain(NavigationAction.OpenUrl(it))
}
store.state.globalState.exchangeManager.getSellCryptoReceiptUrl(
action = CurrencyExchangeManager.Action.Sell,
transactionId = transactionId,
)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
}
}

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

@ -13,7 +13,9 @@ import com.tangem.tap.features.wallet.models.Currency
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.*
import com.tangem.tap.features.wallet.redux.ProgressState
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.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
@ -38,8 +40,6 @@ class OnWalletLoadedReducer {
val walletData = walletState.getWalletData(blockchainNetwork) ?: return walletState
val fiatCurrency = store.state.globalState.appCurrency
val exchangeManager = store.state.globalState.exchangeManager
val coinAmountValue = wallet.amounts[AmountType.Coin]?.value
val formattedAmount = coinAmountValue?.toFormattedCurrencyString(
wallet.blockchain.decimals(),
@ -71,7 +71,6 @@ class OnWalletLoadedReducer {
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(isCoinSendButtonEnabled),
currency = Currency.fromBlockchainNetwork(blockchainNetwork),
tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData),
)
val tokens = wallet.getTokens().mapNotNull { token ->
@ -104,7 +103,6 @@ class OnWalletLoadedReducer {
),
pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled),
tradeCryptoState = TradeCryptoState.from(exchangeManager, tokenWalletData),
)
}
val newWallets = tokens + newWalletData
@ -118,8 +116,6 @@ class OnWalletLoadedReducer {
if (wallet.blockchain != walletState.primaryBlockchain) return walletState
val fiatCurrencyName = store.state.globalState.appCurrency.code
val exchangeManager = store.state.globalState.exchangeManager
val token = wallet.getFirstToken()
val tokenData = if (token != null) {
val tokenAmount = wallet.getTokenAmount(token)
@ -167,7 +163,6 @@ class OnWalletLoadedReducer {
),
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(sendButtonEnabled),
tradeCryptoState = TradeCryptoState.from(exchangeManager, walletState.primaryWallet),
)
val wallets = listOfNotNull(walletData)
val updatedStore = walletState.getWalletStore(walletData?.currency)?.updateWallets(wallets)

View file

@ -6,7 +6,11 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.common.extensions.mapNotNullValues
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.toFiatRateString
import com.tangem.tap.common.extensions.toFiatString
import com.tangem.tap.common.extensions.toFiatValue
import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.common.extensions.toFormattedFiatValue
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.getArtworkUrl
@ -14,10 +18,18 @@ import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.features.wallet.redux.ErrorType
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletAddresses
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.WalletStore
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.store
import org.rekotlin.Action
import java.math.BigDecimal
@ -35,7 +47,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
if (action !is WalletAction) return state.walletState
val exchangeManager = store.state.globalState.exchangeManager
var newState = state.walletState
when (action) {
@ -144,10 +155,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
currencySymbol = walletData.currencyData.currencySymbol,
),
mainButton = WalletMainButton.SendButton(false),
tradeCryptoState = TradeCryptoState.from(
exchangeManager,
walletData
)
)
}
)
@ -171,13 +178,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
currencySymbol = wallet.currencyData.currencySymbol,
),
mainButton = WalletMainButton.SendButton(false),
tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet)
)
}
val wallets = newState.updateTradeCryptoState(
exchangeManager,
newState.replaceSomeWallets(newWallets)
)
val wallets = newState.replaceSomeWallets(newWallets)
val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(wallets)
newState = newState.updateWalletStore(walletStore)
}
@ -210,14 +213,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
)
}
var updatedWalletStore = newState.getWalletStore(action.blockchain)
val updatedWalletStore = newState.getWalletStore(action.blockchain)
?.updateWallets(listOfNotNull(walletData))
updatedWalletStore =
updatedWalletStore?.updateWallets(
newState.updateTradeCryptoState(exchangeManager, updatedWalletStore.walletsData)
)
newState = newState.updateWalletStore(updatedWalletStore)
}
@ -248,11 +246,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
)
}
val updatedWallets =
newState.updateTradeCryptoState(
exchangeManager,
walletStore!!.updateWallets(listOfNotNull(newWalletData) + tokenWallets).walletsData
)
val updatedWallets = walletStore!!.updateWallets(listOfNotNull(newWalletData) + tokenWallets).walletsData
newState = newState.updateWalletsData(updatedWallets)

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
@ -140,7 +141,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
setupAddressCard(selectedWallet)
setupNoInternetHandling(state)
setupBalanceData(selectedWallet.currencyData)
setupButtons(selectedWallet)
setupButtons(selectedWallet, state.isExchangeServiceFeatureOn)
handleCurrencyIcon(selectedWallet)
handleWarnings(selectedWallet)
@ -186,7 +187,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
)
}
private fun setupButtons(selectedWallet: WalletData) = with(binding) {
private fun setupButtons(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) = with(binding) {
lWalletDetails.btnCopy.setOnClickListener {
selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
store.dispatch(WalletAction.CopyAddress(addressString, requireContext()))
@ -199,8 +200,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
}
rowButtons.updateButtonsVisibility(
buyAllowed = selectedWallet.tradeCryptoState.isAvailableToBuy(),
sellAllowed = selectedWallet.tradeCryptoState.isAvailableToSell(),
exchangeServiceFeatureOn = isExchangeServiceFeatureOn,
buyAllowed = selectedWallet.isAvailableToBuy,
sellAllowed = selectedWallet.isAvailableToSell,
sendAllowed = selectedWallet.mainButton.enabled,
)
}

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

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

@ -12,12 +12,10 @@ internal class WalletDetailsButtonsRow @JvmOverloads constructor(
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : LinearLayout(context, attrs, defStyleAttr) {
private val binding = ViewWalletDetailsButtonsRowBinding.inflate(
LayoutInflater.from(context),
this,
)
var onBuyClick: (() -> Unit)? = null
var onSellClick: (() -> Unit)? = null
var onTradeClick: (() -> Unit)? = null
@ -35,10 +33,13 @@ internal class WalletDetailsButtonsRow @JvmOverloads constructor(
}
fun updateButtonsVisibility(
exchangeServiceFeatureOn: Boolean,
buyAllowed: Boolean,
sellAllowed: Boolean,
sendAllowed: Boolean,
) = with(binding) {
containerExchangeButtons.isVisible = exchangeServiceFeatureOn
btnBuy.isVisible = (buyAllowed && !sellAllowed) || (!buyAllowed && !sellAllowed)
btnBuy.isEnabled = buyAllowed
btnSell.isVisible = !buyAllowed && sellAllowed

View file

@ -58,7 +58,7 @@ class SingleWalletView : WalletView() {
state.primaryWallet ?: return
setupTwinCards(state.twinCardsState, binding)
setupButtons(state.primaryWallet, binding)
setupButtons(state.primaryWallet, binding, state.isExchangeServiceFeatureOn)
setupAddressCard(state.primaryWallet, binding)
showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
setupBalance(state, state.primaryWallet)
@ -97,18 +97,23 @@ class SingleWalletView : WalletView() {
}
}
private fun setupButtons(state: WalletData, binding: FragmentWalletBinding) = with(binding) {
setupRowButtons(state, rowButtons)
private fun setupButtons(
walletData: WalletData,
binding: FragmentWalletBinding,
isExchangeServiceFeatureEnabled: Boolean,
) = with(binding) {
setupRowButtons(walletData, rowButtons, isExchangeServiceFeatureEnabled)
lAddress.btnCopy.setOnClickListener {
state.walletAddresses?.selectedAddress?.address?.let { addressString ->
walletData.walletAddresses?.selectedAddress?.address?.let { addressString ->
store.dispatch(WalletAction.CopyAddress(addressString, fragment!!.requireContext()))
}
}
lAddress.btnShowQr.setOnClickListener {
state.walletAddresses?.selectedAddress?.let { selectedAddress ->
walletData.walletAddresses?.selectedAddress?.let { selectedAddress ->
store.dispatch(
WalletAction.DialogAction.QrCode(
currency = state.currency,
currency = walletData.currency,
selectedAddress = selectedAddress,
),
)
@ -116,13 +121,16 @@ class SingleWalletView : WalletView() {
}
}
private fun setupRowButtons(state: WalletData, rowButtons: WalletDetailsButtonsRow) {
val allowedToBuy = state.tradeCryptoState.isAvailableToBuy()
val allowedToSell = state.tradeCryptoState.isAvailableToSell()
private fun setupRowButtons(
walletData: WalletData,
rowButtons: WalletDetailsButtonsRow,
isExchangeServiceFeatureEnabled: Boolean,
) {
rowButtons.updateButtonsVisibility(
buyAllowed = allowedToBuy,
sellAllowed = allowedToSell,
sendAllowed = state.mainButton.enabled,
exchangeServiceFeatureOn = isExchangeServiceFeatureEnabled,
buyAllowed = walletData.isAvailableToBuy,
sellAllowed = walletData.isAvailableToSell,
sendAllowed = walletData.mainButton.enabled,
)
rowButtons.onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) }
@ -130,7 +138,7 @@ class SingleWalletView : WalletView() {
rowButtons.onTradeClick = { store.dispatch(WalletAction.DialogAction.ChooseTradeActionDialog) }
rowButtons.onSendClick = {
when (state.mainButton) {
when (walletData.mainButton) {
is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send())
is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet)
}

View file

@ -12,11 +12,17 @@ class CardExchangeRules(
val cardProvider: () -> Card?,
) : ExchangeRules {
override fun featureIsSwitchedOn(): Boolean {
val card = cardProvider() ?: return false
return !card.isStart2Coin
}
override fun isBuyAllowed(): Boolean {
val card = cardProvider() ?: return false
return when {
card.isDemoCard() -> false
card.isDemoCard() -> true
card.isStart2Coin -> false
else -> true
}
@ -36,7 +42,7 @@ class CardExchangeRules(
val card = cardProvider() ?: return false
return when {
card.isDemoCard() -> false
card.isDemoCard() -> true
card.isStart2Coin -> false
else -> true
}

View file

@ -26,6 +26,8 @@ class CurrencyExchangeManager(
private val primaryRules: ExchangeRules,
) : ExchangeService, ExchangeUrlBuilder {
override fun featureIsSwitchedOn(): Boolean = primaryRules.featureIsSwitchedOn()
override suspend fun update() {
buyService.update()
sellService.update()
@ -74,6 +76,14 @@ class CurrencyExchangeManager(
}
enum class Action { Buy, Sell }
companion object {
fun dummy(): CurrencyExchangeManager = CurrencyExchangeManager(
buyService = ExchangeService.dummy(),
sellService = ExchangeService.dummy(),
primaryRules = ExchangeRules.dummy(),
)
}
}
suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(

View file

@ -1,19 +1,44 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.feature.Feature
import com.tangem.tap.features.wallet.models.Currency
interface ExchangeService: ExchangeRules {
suspend fun update()
}
interface ExchangeRules {
interface Exchanger {
fun isBuyAllowed(): Boolean
fun isSellAllowed(): Boolean
fun availableForBuy(currency: Currency):Boolean
fun availableForSell(currency: Currency):Boolean
}
interface ExchangeService: Feature, Exchanger {
suspend fun update()
companion object {
fun dummy(): ExchangeService = object : ExchangeService {
override fun featureIsSwitchedOn(): Boolean = false
override suspend fun update() {}
override fun isBuyAllowed(): Boolean = false
override fun isSellAllowed(): Boolean = false
override fun availableForBuy(currency: Currency): Boolean = false
override fun availableForSell(currency: Currency): Boolean = false
}
}
}
interface ExchangeRules: Feature, Exchanger {
companion object {
fun dummy(): ExchangeRules = object : ExchangeRules {
override fun featureIsSwitchedOn(): Boolean = false
override fun isBuyAllowed(): Boolean = false
override fun isSellAllowed(): Boolean = false
override fun availableForBuy(currency: Currency): Boolean = false
override fun availableForSell(currency: Currency): Boolean = false
}
}
}
interface ExchangeUrlBuilder {
fun getUrl(
action: CurrencyExchangeManager.Action,

View file

@ -4,15 +4,6 @@ import com.squareup.moshi.Json
import retrofit2.http.GET
import retrofit2.http.Path
/**
[REDACTED_AUTHOR]
*/
private val CurrenciesUrl = "https://api.mercuryo.io/v1.6/lib/currencies"
interface MercuryoApi {

View file

@ -28,6 +28,8 @@ class MercuryoService(
private val blockchainsAvailableToBuy = mutableListOf<Blockchain>()
private val tokensAvailableToBy = mutableMapOf<String, MutableList<Blockchain>>()
override fun featureIsSwitchedOn(): Boolean = true
override suspend fun update() {
when (val result = performRequest { api.currencies(apiVersion) }) {
is Result.Success -> {
@ -130,6 +132,7 @@ class MercuryoService(
private fun blockchainFromCurrencyName(currencyName: String): Blockchain? = when (currencyName) {
"BNB" -> Blockchain.BSC
"ETH" -> Blockchain.Ethereum
"ADA" -> Blockchain.CardanoShelley
else -> Blockchain.values().find { it.currency.lowercase() == currencyName.lowercase() }
}
}

View file

@ -29,6 +29,8 @@ class MoonPayService(
private var status: MoonPayStatus? = null
override fun featureIsSwitchedOn(): Boolean = true
override suspend fun update() {
withIOContext {
performRequest {

View file

@ -47,7 +47,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingBottom="32dp">
android:paddingBottom="92dp">
<ImageView
android:id="@+id/iv_card"
@ -93,6 +93,17 @@
app:barrierDirection="bottom"
app:constraint_referenced_ids="iv_card,tv_twin_card_number" />
<include
android:id="@+id/l_wallet_backup_warning"
layout="@layout/layout_wallet_backup_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_warning_messages"
android:layout_width="match_parent"
@ -105,17 +116,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier" />
<include
android:id="@+id/l_wallet_backup_warning"
layout="@layout/layout_wallet_backup_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" />
<include
android:id="@+id/l_card_total_balance"
layout="@layout/layout_card_total_balance"
@ -176,16 +176,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
<com.tangem.tap.features.wallet.ui.view.WalletDetailsButtonsRow
android:id="@+id/row_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_add_token"
style="@style/BaseTapButton"
@ -207,4 +197,14 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.tangem.tap.features.wallet.ui.view.WalletDetailsButtonsRow
android:id="@+id/row_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="32dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -153,6 +153,7 @@
android:layout_height="wrap_content"
android:background="@color/backgroundLightGray"
android:ellipsize="middle"
android:inputType="text|textNoSuggestions"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:singleLine="true"

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

@ -8,6 +8,7 @@
tools:parentTag="android.widget.LinearLayout">
<FrameLayout
android:id="@+id/container_exchange_buttons"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
@ -48,7 +49,6 @@
android:id="@+id/btn_send"
style="@style/TapButtonWithIcon"
android:layout_width="0dp"
android:layout_marginStart="6dp"
android:layout_weight="1"
android:text="@string/wallet_button_send"
app:icon="@drawable/ic_send" />

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,19 @@
<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="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</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,19 @@
<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="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</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,19 @@
<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="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</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

@ -147,10 +147,6 @@
<string name="common_add">Добавить</string>
<string name="common_remove">Удалить</string>
<string name="common_search">Поиск</string>
<string name="send_extras_hint_memo">Памятка</string>
<string name="send_extras_hint_destination_tag">Тег назначения</string>
<string name="send_error_invalid_destination_tag">Недопустимый тег назначения. Он не будет добавлен в транзакцию.</string>
<string name="send_error_invalid_memo_id">Недопустимый идентификатор памятки. Он не будет добавлен в транзакцию.</string>
<string name="details_section_title_app">Приложение</string>
<string name="details_row_title_send_feedback">Отправить отзыв</string>
<string name="alert_app_feedback_sent_title">Успешно отправлено</string>

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,19 @@
<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="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_error_invalid_memo">Недопустимый Memo. Он не будет добавлен в транзакцию.</string>
<string name="send_error_invalid_destination_tag">Недопустимый Tag. Он не будет добавлен в транзакцию.</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,19 @@
<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="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</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

@ -40,12 +40,6 @@
<string name="common_remove" translatable="false">Remove</string>
<string name="common_search" translatable="false">Search</string>
<string name="send_extras_hint_memo" translatable="false">Memo</string>
<string name="send_extras_hint_destination_tag" translatable="false">Destination tag</string>
<string name="send_error_invalid_destination_tag" translatable="false">Invalid destination tag. It won\'t be added to the transaction</string>
<string name="send_error_invalid_memo_id" translatable="false">Invalid Memo ID. It won\'t be added to the transaction</string>
<string name="details_section_title_app" translatable="false">App</string>
<string name="details_row_title_send_feedback" translatable="false">Send feedback</string>
<string name="alert_app_feedback_sent_title" translatable="false">Sent successfully</string>

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-104',
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)
}
}