Updated on 2026-08-14

This commit is contained in:
Tangem 2022-08-08 17:12:07 +04:00
commit 4de32b1119
34 changed files with 660 additions and 368 deletions

View file

@ -92,9 +92,9 @@ dependencies {
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "com.tangem:blockchain:${versions.tamgem_blockchain_sdk}"
implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tamgem_card_sdk}"
implementation "com.tangem.tangem-sdk-kotlin:android:${versions.tamgem_card_sdk}"
implementation "com.tangem:blockchain:${versions.tangem_blockchain_sdk}"
implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tangem_card_sdk}"
implementation "com.tangem.tangem-sdk-kotlin:android:${versions.tangem_card_sdk}"
// WebView
implementation "androidx.browser:browser:1.3.0"

View file

@ -0,0 +1,51 @@
package com.tangem.tap.common.extensions
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.common.BlockchainError
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.features.demo.DemoTransactionSender
/**
[REDACTED_AUTHOR]
*/
fun AnalyticsHandler.logSendTransactionError(
error: BlockchainError,
action: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>? = mapOf(),
card: Card? = null,
) {
when (val blockchainSdkError = (error as BlockchainSdkError)) {
is BlockchainSdkError.WrappedTangemError -> {
val tangemSdkError = (blockchainSdkError.tangemError as? TangemSdkError) ?: return
logCardSdkError(
error = tangemSdkError,
actionToLog = action,
parameters = parameters,
card = card,
)
}
else -> {
when {
blockchainSdkError.customMessage.contains(DemoTransactionSender.ID) -> return
else -> {
val params = parameters?.toMutableMap() ?: mutableMapOf()
params[AnalyticsParam.ACTION] = action.key
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
params[AnalyticsParam.ERROR_DESCRIPTION] = "${error.javaClass.simpleName}: ${error.customMessage}"
params[AnalyticsParam.ERROR_KEY] = "BlockchainSdkError"
FirebaseCrashlytics.getInstance().apply {
params.forEach { setCustomKey(it.key.param, it.value) }
recordException(error)
}
}
}
}
}
}

View file

@ -44,10 +44,6 @@ sealed class TapError(
val customMessage: String = "Unsupported state:"
) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError"))
sealed class XmlError {
object AssetAccountNotCreated : TapError(R.string.send_error_no_account_xlm)
}
sealed class WalletManager {
object CreationError : CustomError("Can't create wallet manager")
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)

View file

@ -7,6 +7,7 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.Companion.toKeccak
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.CommonSigner
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
@ -24,6 +25,7 @@ import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.logSendTransactionError
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
@ -38,8 +40,8 @@ import com.tangem.tap.tangemSdk
import com.tangem.tap.tangemSdkManager
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
import java.math.BigDecimal
import timber.log.Timber
import java.math.BigDecimal
class WalletConnectSdkHelper {
@ -69,7 +71,7 @@ class WalletConnectSdkHelper {
(walletManager as? EthereumGasLoader)?.getGasPrice()) {
is Result.Success -> result.data.toBigDecimal()
is Result.Failure -> {
Timber.e(result.error)
(result.error as? Throwable)?.let { Timber.e(it) }
return null
}
null -> return null
@ -143,13 +145,11 @@ class WalletConnectSdkHelper {
HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash
}
is SimpleResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
Analytics.ActionToLog.WalletConnectTransaction,
)
}
Timber.e(result.error)
store.state.globalState.analyticsHandlers?.logSendTransactionError(
result.error,
Analytics.ActionToLog.WalletConnectTransaction,
)
Timber.e(result.error as BlockchainSdkError)
null
}
}

View file

@ -1,7 +1,13 @@
package com.tangem.tap.features.demo
import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.toBlockchainCustomError
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.CompletionResult
@ -476,7 +482,7 @@ class DemoTransactionSender(
publicKey = walletManager.wallet.publicKey
)
return when (signerResponse) {
is CompletionResult.Success -> SimpleResult.Failure(Exception(ID))
is CompletionResult.Success -> SimpleResult.Failure(Exception(ID).toBlockchainCustomError())
is CompletionResult.Failure -> SimpleResult.fromTangemSdkError(signerResponse.error)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.details.redux
import com.tangem.common.card.Card
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTangemNote
import com.tangem.domain.common.isTangemTwin
@ -19,7 +20,6 @@ class DetailsReducer {
private fun internalReduce(action: Action, state: AppState): DetailsState {
if (action !is DetailsAction) return state.detailsState
val detailsState = state.detailsState
return when (action) {
is DetailsAction.PrepareScreen -> {
@ -40,7 +40,6 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
}
is DetailsAction.ChangeAppCurrency ->
detailsState.copy(appCurrency = action.fiatCurrency)
else -> detailsState
}
}
@ -48,7 +47,6 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
private fun handlePrepareScreen(
action: DetailsAction.PrepareScreen,
): DetailsState {
return DetailsState(
scanResponse = action.scanResponse,
wallets = action.wallets,
@ -99,7 +97,8 @@ private fun prepareSecurityOptions(card: Card): ManageSecurityState {
private fun isResetToFactoryAllowedByCard(card: Card): Boolean {
val notAllowedByAnyWallet = card.wallets.any { it.settings.isPermanent }
val notAllowedByCard = notAllowedByAnyWallet ||
(card.isWalletDataSupported && (!card.isTangemNote() && !card.settings.isBackupAllowed))
(card.isWalletDataSupported && (!card.isTangemNote() && !card.settings.isBackupAllowed)) ||
card.isSaltPay
return !notAllowedByCard
}
@ -140,7 +139,6 @@ private fun handleSecurityAction(
),
)
}
else -> state
}
}

View file

@ -48,7 +48,7 @@ fun AppSettingsScreen(
}
@Composable
fun AppSettings(
private fun AppSettings(
state: AppSettingsScreenState,
modifier: Modifier = Modifier,
) {
@ -69,13 +69,15 @@ fun AppSettings(
.fillMaxSize(),
) {
AppSettingsElement(
state = state, setting = PrivacySetting.SaveWallets,
state = state,
setting = PrivacySetting.SaveWallets,
onDialogStateChange = onDialogStateChange,
modifier = modifier,
)
Spacer(modifier = Modifier.size(32.dp))
AppSettingsElement(
state = state, setting = PrivacySetting.SaveAccessCode,
state = state,
setting = PrivacySetting.SaveAccessCode,
onDialogStateChange = onDialogStateChange,
modifier = modifier,
)
@ -83,7 +85,7 @@ fun AppSettings(
}
@Composable
fun AppSettingsElement(
private fun AppSettingsElement(
state: AppSettingsScreenState,
setting: PrivacySetting,
onDialogStateChange: (PrivacySetting?) -> Unit,
@ -103,7 +105,6 @@ fun AppSettingsElement(
modifier = modifier
.fillMaxWidth()
.padding(start = 20.dp),
// .clickable { state.onSettingToggled(element, !checked) },
verticalAlignment = Alignment.CenterVertically,
) {
Column(
@ -140,7 +141,7 @@ fun AppSettingsElement(
}
}
fun onCheckedChange(
private fun onCheckedChange(
element: PrivacySetting, enabled: Boolean,
onSettingToggled: (PrivacySetting, Boolean) -> Unit,
onDialogStateChange: (PrivacySetting?) -> Unit,
@ -154,7 +155,7 @@ fun onCheckedChange(
}
@Composable
fun SettingsAlertDialog(
private fun SettingsAlertDialog(
element: PrivacySetting,
onDialogStateChange: (PrivacySetting?) -> Unit,
onSettingToggled: (PrivacySetting, Boolean) -> Unit,

View file

@ -6,7 +6,6 @@ import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.indication
@ -19,7 +18,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -44,35 +42,26 @@ fun TangemSwitch(
val transition = updateTransition(checked, label = "SwitchState")
val color by transition.animateColor(
transitionSpec = {
tween(200, easing = FastOutLinearInEasing)
tween(durationMillis = 200, easing = FastOutLinearInEasing)
},
label = "",
) {
when (it) {
true -> enabledColor
false -> disabledColor
}
) { enabled ->
if (enabled) enabledColor else disabledColor
}
val interactionSource = remember { MutableInteractionSource() }
val clickable = Modifier.clickable(
interactionSource = interactionSource,
indication = null,
) {
if (!checked) {
onCheckedChange(true)
} else {
onCheckedChange(false)
}
}
Box(
modifier = Modifier
.then(clickable)
modifier = modifier
.clickable(
interactionSource = interactionSource,
indication = null,
) {
onCheckedChange(!checked)
}
.indication(
interactionSource = MutableInteractionSource(),
interactionSource = interactionSource,
indication = rememberRipple(
bounded = true,
radius = 100.dp,
bounded = false,
color = Color.Transparent,
),
),
@ -81,36 +70,27 @@ fun TangemSwitch(
modifier = modifier
.width(size)
.height(size / 2)
.indication(MutableInteractionSource(), null)
.indication(interactionSource, null)
.background(color = color, shape = RoundedCornerShape(100)),
contentAlignment = Alignment.CenterStart,
) {
val roundCardSize = this.maxWidth / 2
val xOffset by transition.animateDp(
transitionSpec = {
tween(150, easing = LinearOutSlowInEasing)
tween(durationMillis = 150, easing = LinearOutSlowInEasing)
},
label = "xOffset",
) { state ->
when (state) {
false -> 0.dp
true -> this.maxWidth - roundCardSize
}
) { enabled ->
if (enabled) this.maxWidth - roundCardSize else 0.dp
}
Card(
Box(
modifier = Modifier
.size(this.maxWidth / 2)
.offset(x = xOffset, y = 0.dp)
.padding(3.dp),
shape = RoundedCornerShape(100),
backgroundColor = Color.White,
border = BorderStroke(
if (!checked) 0.5.dp else 0.dp,
color = Color.LightGray,
),
) {
}
.padding(3.dp)
.background(color = Color.White, shape = RoundedCornerShape(100)),
)
}
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.Message
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.ToastNotificationAction
@ -155,7 +156,10 @@ sealed class SendAction : SendScreenAction {
val reduceAmount: BigDecimal,
) : Dialog()
data class SendTransactionFails(val errorMessage: String) : Dialog()
sealed class SendTransactionFails : Dialog() {
data class CardSdkError(val error: TangemSdkError): Dialog()
data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError): Dialog()
}
object Hide : Dialog()
}

View file

@ -22,6 +22,7 @@ import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.logSendTransactionError
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.AppDialog
@ -52,14 +53,13 @@ import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import com.tangem.wallet.R
import java.util.EnumSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Action
import org.rekotlin.Middleware
import timber.log.Timber
import java.util.*
/**
[REDACTED_AUTHOR]
@ -79,13 +79,19 @@ class SendMiddleware {
is SendActionUi.CheckIfTransactionDataWasProvided -> {
val transactionData = appState()?.sendState?.externalTransactionData
if (transactionData != null) {
store.dispatchOnMain(AddressPayIdVerifyAction.AddressVerification.SetWalletAddress(
transactionData.destinationAddress, false
))
store.dispatchOnMain(
AddressPayIdVerifyAction.AddressVerification.SetWalletAddress(
transactionData.destinationAddress, false,
),
)
store.dispatchOnMain(AmountActionUi.SetMainCurrency(MainCurrencyType.CRYPTO))
store.dispatchOnMain(AmountActionUi.HandleUserInput(transactionData.amount))
store.dispatchOnMain(AmountAction.SetAmount(transactionData.amount.toBigDecimal(),
false))
store.dispatchOnMain(
AmountAction.SetAmount(
transactionData.amount.toBigDecimal(),
false,
),
)
}
}
}
@ -93,7 +99,6 @@ class SendMiddleware {
}
}
}
}
private fun verifyAndSendTransaction(
@ -113,23 +118,31 @@ private fun verifyAndSendTransaction(
when {
hadTezosError -> {
val reduceAmount = walletManager.wallet.blockchain.minimalAmount()
dispatch(SendAction.Dialog.TezosWarningDialog(reduceCallback = {
dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
dispatch(AmountActionUi.CheckAmountToSend)
}, sendAllCallback = {
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData,
dispatch
)
}, reduceAmount))
dispatch(
SendAction.Dialog.TezosWarningDialog(
reduceCallback = {
dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
dispatch(AmountActionUi.CheckAmountToSend)
},
sendAllCallback = {
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData,
dispatch,
)
},
reduceAmount,
),
)
}
transactionErrors.isNotEmpty() -> {
dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager)))
}
else -> {
sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch)
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch,
)
}
}
}
@ -150,7 +163,9 @@ private fun sendTransaction(
transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) }
transactionExtras.binanceMemo?.memo?.let { txData = txData.copy(extras = BinanceTransactionExtras(it.toString())) }
transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it)) }
transactionExtras.xrpDestinationTag?.tag?.let {
txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it))
}
scope.launch {
val updateWalletResult = walletManager.safeUpdate()
@ -178,14 +193,14 @@ private fun sendTransaction(
val signer = TangemSigner(
card = card,
tangemSdk = tangemSdk,
initialMessage = action.messageForSigner
initialMessage = action.messageForSigner,
) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.totalSignedHashes,
walletPublicKey = walletManager.wallet.publicKey.seedKey,
remainingSignatures = signResponse.remainingSignatures
)
remainingSignatures = signResponse.remainingSignatures,
),
)
}
val sendResult = try {
@ -211,7 +226,7 @@ private fun sendTransaction(
store.state.globalState.analyticsHandlers?.triggerEvent(
event = AnalyticsEvent.TRANSACTION_IS_SENT,
card = card,
blockchain = walletManager.wallet.blockchain.currency
blockchain = walletManager.wallet.blockchain.currency,
)
dispatch(SendAction.SendSuccess)
@ -231,69 +246,48 @@ private fun sendTransaction(
}
}
is SimpleResult.Failure -> {
when (sendResult.error) {
store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
wallet = walletManager.wallet,
host = walletManager.currentHost,
amountToSend = amountToSend,
feeAmount = feeAmount,
destinationAddress = destinationAddress,
)
store.state.globalState.analyticsHandlers?.logSendTransactionError(
error = sendResult.error,
action = Analytics.ActionToLog.SendTransaction,
parameters = mapOf(AnalyticsParam.BLOCKCHAIN to walletManager.wallet.blockchain.currency),
card = card,
)
val error = (sendResult.error as? BlockchainSdkError) ?: return@withContext
when (error) {
is BlockchainSdkError.WrappedTangemError -> {
val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return@withContext
if (tangemSdkError is TangemSdkError.UserCancelled) return@withContext
dispatch(SendAction.Dialog.SendTransactionFails.CardSdkError(tangemSdkError))
}
is BlockchainSdkError.CreateAccountUnderfunded -> {
val error = sendResult.error as 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))))
}
is BlockchainSdkError.SendException -> {
sendResult.error?.let { FirebaseCrashlytics.getInstance().recordException(it) }
}
is Throwable -> {
val throwable = sendResult.error as Throwable
val message = throwable.message
val infoHolder = store.state.globalState.feedbackManager?.infoHolder
else -> {
when {
message == null -> {
dispatch(SendAction.SendError(TapError.UnknownError))
infoHolder?.updateOnSendError(
wallet = walletManager.wallet,
host = walletManager.currentHost,
amountToSend = amountToSend,
feeAmount = feeAmount,
destinationAddress = destinationAddress
error.customMessage.contains(DemoTransactionSender.ID) -> {
store.dispatchDialogShow(
AppDialog.SimpleOkDialogRes(
headerId = R.string.common_done,
messageId = R.string.alert_demo_feature_disabled,
onOk = { dispatch(NavigationAction.PopBackTo()) },
),
)
dispatch(SendAction.Dialog.SendTransactionFails("unknown error"))
}
message.contains("50002") -> {
// user was cancelled the operation by closing the Sdk bottom sheet
}
// make it easier latter by handling an appropriate enumError or, like on iOS,
// accept a string identifier of the error message
message.contains("Target account is not created. To create account send 1+ XLM.") -> {
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
message.contains(DemoTransactionSender.ID) -> {
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatchDialogShow(AppDialog.SimpleOkDialogRes(
R.string.common_done,
R.string.alert_demo_feature_disabled
) { dispatch(NavigationAction.PopBackTo()) })
}
else -> {
(sendResult.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
Analytics.ActionToLog.SendTransaction,
mapOf(
AnalyticsParam.BLOCKCHAIN
to walletManager.wallet.blockchain.currency),
card = card,
)
}
Timber.e(throwable)
FirebaseCrashlytics.getInstance().recordException(throwable)
dispatch(SendAction.SendError(TapError.CustomError(message)))
infoHolder?.updateOnSendError(
wallet = walletManager.wallet,
host = walletManager.currentHost,
amountToSend = amountToSend,
feeAmount = feeAmount,
destinationAddress = destinationAddress
)
dispatch(SendAction.Dialog.SendTransactionFails(message))
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error))
}
}
}
@ -328,7 +322,10 @@ fun extractErrorsForAmountField(errors: EnumSet<TransactionError>): EnumSet<Tran
return showIntoAmountField
}
fun createValidateTransactionError(errorList: EnumSet<TransactionError>, walletManager: WalletManager): TapError.ValidateTransactionErrors {
fun createValidateTransactionError(
errorList: EnumSet<TransactionError>,
walletManager: WalletManager,
): TapError.ValidateTransactionErrors {
val tapErrors = errorList.map {
when (it) {
TransactionError.AmountExceedsBalance -> TapError.AmountExceedsBalance

View file

@ -106,8 +106,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) &&
sendState.amountState.typeOfAmount is AmountType.Token) {
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

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.send.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tangem_sdk_new.extensions.localizedDescription
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.send.redux.SendAction
@ -14,14 +15,22 @@ import com.tangem.wallet.R
class SendTransactionFailsDialog {
companion object {
fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails): AlertDialog {
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)
}
private fun create(context: Context, errorMessage: String): AlertDialog {
return AlertDialog.Builder(context).apply {
setTitle(R.string.alert_failed_to_send_transaction_title)
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, dialog.errorMessage))
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage))
setNeutralButton(R.string.alert_button_send_feedback) { _, _ ->
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(dialog.errorMessage)))
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage)))
}
setPositiveButton(R.string.common_no) { _, _ -> }
setPositiveButton(R.string.common_cancel) { _, _ -> }
setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) }
}.create()
}

View file

@ -8,7 +8,12 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.text.bold
import com.tangem.common.extensions.remove
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.enableError
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.update
import com.tangem.tap.common.redux.getMessageString
import com.tangem.tap.common.text.DecimalDigitsInputFilter
import com.tangem.tap.domain.MultiMessageError
@ -17,7 +22,17 @@ 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.*
import com.tangem.tap.features.send.redux.states.AddressPayIdState
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.ReceiptLayoutType
import com.tangem.tap.features.send.redux.states.ReceiptState
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.SendTransactionFailsDialog
@ -117,7 +132,13 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
dialog?.show()
}
}
is SendAction.Dialog.SendTransactionFails -> {
is SendAction.Dialog.SendTransactionFails.CardSdkError -> {
if (dialog == null) {
dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)
dialog?.show()
}
}
is SendAction.Dialog.SendTransactionFails.BlockchainSdkError -> {
if (dialog == null) {
dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)
dialog?.show()

View file

@ -1,7 +1,11 @@
package com.tangem.tap.features.wallet.ui
import android.os.Bundle
import android.view.*
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.annotation.ColorRes
@ -11,21 +15,32 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.TestActions
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.appendIfNotNull
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.onboarding.getQRReceiveMessage
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.redux.*
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.WalletData
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.loadCurrencyIcon
import com.tangem.tap.features.wallet.ui.test.TestWalletDetails
import com.tangem.tap.store
import com.tangem.wallet.R
@ -199,11 +214,12 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
}
private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) {
loadCurrencyIcon(
currencyImageView = ivCurrency,
currencyTextView = tvTokenLetter,
blockchain = wallet.currency.blockchain,
token = (wallet.currency as? Currency.Token)?.token
ivCurrency.load(
currency = wallet.currency,
derivationStyle = store.state.globalState
.scanResponse
?.card
?.derivationStyle,
)
}

View file

@ -14,6 +14,7 @@ import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import coil.load
import coil.size.Scale
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.tap.MainActivity
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
@ -32,6 +33,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView
import com.tangem.tap.features.wallet.ui.wallet.SaltPaySingleWalletView
import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView
import com.tangem.tap.features.wallet.ui.wallet.WalletView
import com.tangem.tap.store
@ -112,17 +114,22 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
override fun newState(state: WalletState) {
if (activity == null || view == null) return
if (state.isMultiwalletAllowed &&
state.primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
walletView is SingleWalletView
) {
walletView = MultiWalletView()
walletView.changeWalletView(this, binding)
} else if (!state.isMultiwalletAllowed && walletView is MultiWalletView) {
walletView = SingleWalletView()
walletView.changeWalletView(this, binding)
val isSaltPay = store.state.globalState.scanResponse?.card?.isSaltPay == true
when {
isSaltPay -> {
walletView = SaltPaySingleWalletView()
}
state.isMultiwalletAllowed &&
state.primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
walletView is SingleWalletView -> {
walletView = MultiWalletView()
}
!state.isMultiwalletAllowed && walletView is MultiWalletView -> {
walletView = SingleWalletView()
}
}
walletView.changeWalletView(this, binding)
walletView.onNewState(state)
if (!state.shouldShowDetails) {
@ -134,7 +141,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
setupNoInternetHandling(state)
setupCardImage(state.cardImage)
showWarningsIfPresent(state.mainWarningsList)
if (!isSaltPay) showWarningsIfPresent(state.mainWarningsList)
binding.srlWallet.isRefreshing = state.state == ProgressState.Refreshing
binding.srlWallet.setOnRefreshListener {

View file

@ -9,15 +9,12 @@ import androidx.recyclerview.widget.RecyclerView
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tap.common.extensions.getRoundIconRes
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
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.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.images.loadCurrencyIcon
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ItemCurrencyWalletBinding
@ -67,12 +64,6 @@ class WalletAdapter
// Skip changes when on refreshing status
if (status == BalanceStatus.Refreshing) return@with
val isCustomCurrency = wallet.currency.isCustomCurrency(
derivationStyle = store.state.globalState
.scanResponse
?.card
?.derivationStyle
)
val statusMessage = when (status) {
BalanceStatus.TransactionInProgress -> {
root.getString(R.string.wallet_balance_tx_in_progress)
@ -87,7 +78,8 @@ class WalletAdapter
BalanceStatus.UnknownBlockchain,
BalanceStatus.Loading,
BalanceStatus.Refreshing,
null -> null
null,
-> null
}
if (status == null || status == BalanceStatus.Loading) {
@ -98,11 +90,12 @@ class WalletAdapter
lContent.root.show()
}
loadCurrencyIcon(
currencyImageView = ivCurrency,
currencyTextView = tvTokenLetter,
token = (wallet.currency as? Currency.Token)?.token,
blockchain = wallet.currency.blockchain,
ivCurrency.load(
currency = wallet.currency,
derivationStyle = store.state.globalState
.scanResponse
?.card
?.derivationStyle,
)
lContent.tvCurrency.text = wallet.currencyData.currency
@ -116,10 +109,6 @@ class WalletAdapter
lContent.tvExchangeRate.text = wallet.fiatRateString
?: root.getString(id = R.string.token_item_no_rate)
badgeCustomBalance.isVisible = isCustomCurrency
ivBlockchain.isVisible = wallet.currency.isToken()
ivBlockchain.setImageResource(wallet.currency.blockchain.getRoundIconRes())
cardWallet.setOnClickListener {
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
}

View file

@ -19,22 +19,7 @@ import com.tangem.wallet.R
private const val QCX = "QCX"
private const val VOYR = "VOYRME"
fun loadCurrencyIcon(
currencyImageView: CurrencyIconView,
currencyTextView: TextView,
token: Token?,
blockchain: Blockchain,
) {
CurrencyIconLoader(
currencyImageView = currencyImageView.imageView,
currencyTextView = currencyTextView,
token = token,
blockchain = blockchain
)
.load()
}
private class CurrencyIconLoader(
class CurrencyIconRequest(
private val currencyImageView: ImageFilterView,
private val currencyTextView: TextView,
private val token: Token?,

View file

@ -3,27 +3,69 @@ package com.tangem.tap.features.wallet.ui.images
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 com.google.android.material.card.MaterialCardView
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
class CurrencyIconView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : MaterialCardView(context, attrs, defStyleAttr) {
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val binding = ViewCurrencyIconBinding.inflate(
LayoutInflater.from(context),
this
this,
)
val imageView: ImageFilterView
get() = binding.iv
private val currencyImageView: ImageFilterView
get() = binding.ivCurrency
private val currencyTextView: TextView
get() = binding.tvTokenLetter
private var isBlockchainIconVisible: Boolean
get() = binding.ivBlockchain.isVisible
set(value) = binding.ivBlockchain::isVisible.set(value)
private var isBadgeVisible: Boolean
get() = binding.badge.isVisible
set(value) = binding.badge::isVisible.set(value)
@DrawableRes
private var blockchainIconRes: Int? = null
set(value) {
if (value != null && value != field && isBlockchainIconVisible) {
binding.ivBlockchain.setImageResource(value)
field = value
}
}
init {
elevation = 0f
cardElevation = 0f
radius = dpToPx(6f)
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()
CurrencyIconRequest(
currencyImageView = currencyImageView,
currencyTextView = currencyTextView,
token = (currency as? Currency.Token)?.token,
blockchain = currency.blockchain,
).load()
}
}

View file

@ -25,14 +25,8 @@ import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentWalletBinding
class MultiWalletView : WalletView {
private var fragment: WalletFragment? = null
private var binding: FragmentWalletBinding? = null
class MultiWalletView : WalletView() {
private lateinit var walletsAdapter: WalletAdapter
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
@ -47,6 +41,7 @@ class MultiWalletView : WalletView {
lAddress.root.hide()
lButtonsShort.root.hide()
lButtonsLong.root.hide()
lSingleWalletBalance.root.hide()
rvMultiwallet.show()
btnAddToken.show()
setupWalletCardNumber(binding)
@ -64,15 +59,6 @@ class MultiWalletView : WalletView {
}
}
override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
}
override fun removeFragment() {
this.fragment = null
this.binding = null
}
override fun onViewCreated() {
setupWalletsRecyclerView()

View file

@ -0,0 +1,51 @@
package com.tangem.tap.features.wallet.ui.wallet
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.animateVisibility
import com.tangem.tap.common.extensions.formatAmountAsSpannedString
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.databinding.LayoutSingleWalletBalanceBinding
import java.math.BigDecimal
data class SaltPayBalanceWidgetData(
val state: ProgressState? = null,
val currencySymbol: String? = null,
val currency: String? = null,
val fiatAmount: BigDecimal? = null,
val fiatCurrency: FiatCurrency? = null,
)
class SaltPayBalanceWidget(
private val binding: LayoutSingleWalletBalanceBinding,
private val data: SaltPayBalanceWidgetData,
) {
fun setup(): Unit = with(binding) {
if (data.state == ProgressState.Loading) {
veilBalance.veil()
veilBalanceCrypto.veil()
} else {
veilBalance.unVeil()
veilBalanceCrypto.unVeil()
}
tvProcessing.animateVisibility(
show = data.state == ProgressState.Error,
)
veilBalanceCrypto.animateVisibility(
show = data.state != ProgressState.Error,
)
tvBalance.text = data.fiatAmount?.formatAmountAsSpannedString(
currencySymbol = data.fiatCurrency?.symbol ?: "",
)
tvBalanceCrypto.text = data.currency
tvCurrencyName.text = data.fiatCurrency?.code
tvCurrencyName.setOnClickListener {
store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
}
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.tap.features.wallet.ui.wallet
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.store
import com.tangem.wallet.databinding.FragmentWalletBinding
class SaltPaySingleWalletView : WalletView() {
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
showSingleWalletView(binding)
}
private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
rvMultiwallet.hide()
btnAddToken.hide()
rvPendingTransaction.hide()
tvTwinCardNumber.hide()
lCardBalance.root.hide()
lAddress.root.hide()
lSingleWalletBalance.root.show()
}
override fun onViewCreated() {
}
override fun onNewState(state: WalletState) {
val binding = binding ?: return
state.primaryWallet ?: return
setupBalance(state, state.primaryWallet, binding)
}
private fun setupBalance(state: WalletState, primaryWallet: WalletData, binding: FragmentWalletBinding) {
binding.lSingleWalletBalance.root.show()
SaltPayBalanceWidget(
binding = binding.lSingleWalletBalance,
data = SaltPayBalanceWidgetData(
state = state.state,
currencySymbol = primaryWallet.currencyData.currencySymbol,
currency = primaryWallet.currencyData.amountFormatted,
fiatAmount = primaryWallet.currencyData.fiatAmount,
fiatCurrency = store.state.globalState.appCurrency,
),
).setup()
}
}

View file

@ -21,22 +21,8 @@ import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentWalletBinding
class SingleWalletView : WalletView {
class SingleWalletView : WalletView() {
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
private var fragment: WalletFragment? = null
private var binding: FragmentWalletBinding? = null
override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
}
override fun removeFragment() {
fragment = null
binding = null
}
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
@ -49,13 +35,13 @@ class SingleWalletView : WalletView {
rvPendingTransaction.hide()
lCardBalance.root.show()
lAddress.root.show()
lSingleWalletBalance.root.hide()
}
override fun onViewCreated() {
setupTransactionsRecyclerView()
}
private fun setupTransactionsRecyclerView() {
val fragment = fragment ?: return
pendingTransactionAdapter = PendingTransactionsAdapter()
@ -65,7 +51,6 @@ class SingleWalletView : WalletView {
}
override fun onNewState(state: WalletState) {
val fragment = fragment ?: return
val binding = binding ?: return
state.primaryWallet ?: return
@ -81,7 +66,6 @@ class SingleWalletView : WalletView {
binding?.rvPendingTransaction?.show(pendingTransactions.isNotEmpty())
}
private fun setupBalance(state: WalletState, primaryWallet: WalletData) {
val fragment = fragment ?: return
binding?.apply {
@ -90,13 +74,13 @@ class SingleWalletView : WalletView {
binding = this.lCardBalance,
fragment = fragment,
data = primaryWallet.currencyData,
isTwinCard = state.isTangemTwins
isTwinCard = state.isTangemTwins,
).setup()
}
}
private fun setupTwinCards(
twinCardsState: TwinCardsState?, binding: FragmentWalletBinding
twinCardsState: TwinCardsState?, binding: FragmentWalletBinding,
) = with(binding) {
twinCardsState?.cardNumber?.let { cardNumber ->
tvTwinCardNumber.show()
@ -113,11 +97,9 @@ class SingleWalletView : WalletView {
}
private fun setupButtons(
state: WalletData, isTwinsWallet: Boolean, binding: FragmentWalletBinding
state: WalletData, isTwinsWallet: Boolean, binding: FragmentWalletBinding,
) = with(binding) {
setupButtonsType(state, binding)
val tradeState = state.tradeCryptoState
val btnConfirm = if (tradeState.isAvailableToSell() || tradeState.isAvailableToBuy()) {
lButtonsShort.btnConfirm
@ -137,8 +119,8 @@ class SingleWalletView : WalletView {
store.dispatch(
WalletAction.DialogAction.QrCode(
currency = state.currency,
selectedAddress = selectedAddress
)
selectedAddress = selectedAddress,
),
)
}
}
@ -207,7 +189,6 @@ class SingleWalletView : WalletView {
}
}
private fun setupAddressCard(state: WalletData, binding: FragmentWalletBinding) = with(binding.lAddress) {
if (state.walletAddresses != null && state.currency is Currency.Blockchain) {
binding.lAddress.root.show()
@ -215,7 +196,6 @@ class SingleWalletView : WalletView {
(binding.lAddress.root as? ViewGroup)?.beginDelayedTransition()
chipGroupAddressType.show()
chipGroupAddressType.fitChipsByGroupWidth()
val checkedId =
MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
@ -234,8 +214,8 @@ class SingleWalletView : WalletView {
store.dispatch(
WalletAction.ExploreAddress(
state.walletAddresses.selectedAddress.exploreUrl,
fragment!!.requireContext()
)
fragment!!.requireContext(),
),
)
}
} else {

View file

@ -4,16 +4,20 @@ import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.databinding.FragmentWalletBinding
interface WalletView {
abstract class WalletView {
protected var fragment: WalletFragment? = null
protected var binding: FragmentWalletBinding? = null
fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
}
fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding)
fun removeFragment()
fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)
fun onViewCreated()
fun onNewState(state: WalletState)
fun removeFragment() {
fragment = null
binding = null
}
abstract fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)
abstract fun onViewCreated()
abstract fun onNewState(state: WalletState)
}

View file

@ -148,6 +148,16 @@
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
<include
android:id="@+id/l_single_wallet_balance"
layout="@layout/layout_single_wallet_balance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
<include
android:id="@+id/l_address"
layout="@layout/layout_address"

View file

@ -27,52 +27,12 @@
<com.tangem.tap.features.wallet.ui.images.CurrencyIconView
android:id="@+id/iv_currency"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_token_letter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:textColor="@android:color/white"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/iv_currency"
app:layout_constraintEnd_toEndOf="@id/iv_currency"
app:layout_constraintStart_toStartOf="@id/iv_currency"
app:layout_constraintTop_toTopOf="@id/iv_currency"
tools:text="J" />
<ImageView
android:id="@+id/iv_blockchain"
android:layout_width="18dp"
android:layout_height="18dp"
android:padding="2dp"
android:translationX="4dp"
android:translationY="-4dp"
android:background="@drawable/shape_circle"
android:backgroundTint="@color/backgroundWhite"
android:contentDescription="@null"
android:visibility="gone"
app:layout_constraintTop_toTopOf="@id/iv_currency"
app:layout_constraintEnd_toEndOf="@id/iv_currency"
tools:src="@drawable/ic_eth"
tools:visibility="visible" />
<View
android:id="@+id/badge_custom_balance"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/shape_badge_custom_currency"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/iv_currency"
app:layout_constraintEnd_toEndOf="@id/iv_currency"
tools:visibility="visible" />
<include
layout="@layout/item_currency_wallet_content"
android:id="@+id/l_content"

View file

@ -41,26 +41,12 @@
<com.tangem.tap.features.wallet.ui.images.CurrencyIconView
android:id="@+id/iv_currency"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_token_letter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:textColor="@android:color/white"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/iv_currency"
app:layout_constraintEnd_toEndOf="@id/iv_currency"
app:layout_constraintStart_toStartOf="@id/iv_currency"
app:layout_constraintTop_toTopOf="@id/iv_currency"
tools:text="J" />
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"

View file

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_balance"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="18dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="18dp"
android:animateLayoutChanges="true">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/main_page_balance"
android:textColor="@color/iconGray"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@id/tv_currency_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.skydoves.androidveil.VeilLayout
android:id="@+id/veil_balance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layout_constraintBottom_toTopOf="@id/tv_processing"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title"
app:veilLayout_baseColor="@color/lightGray0"
app:veilLayout_highlightColor="@color/lightGray1"
app:veilLayout_layout="@layout/card_total_balance_shimmer"
app:veilLayout_radius="4dp"
app:veilLayout_shimmerEnable="true"
app:veilLayout_veiled="true"
tools:veilLayout_veiled="false">
<TextView
android:id="@+id/tv_balance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:minWidth="152dp"
android:textColor="@color/darkGray6"
android:textSize="26sp"
android:textStyle="bold"
tools:text="22 325.40 $" />
</com.skydoves.androidveil.VeilLayout>
<com.skydoves.androidveil.VeilLayout
android:id="@+id/veil_balance_crypto"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_marginTop="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/veil_balance"
app:veilLayout_baseColor="@color/lightGray0"
app:veilLayout_highlightColor="@color/lightGray1"
app:veilLayout_layout="@layout/card_total_balance_shimmer"
app:veilLayout_radius="4dp"
app:veilLayout_shimmerEnable="true"
app:veilLayout_veiled="true"
tools:veilLayout_veiled="false"
tools:visibility="visible">
<TextView
android:id="@+id/tv_balance_crypto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:maxLines="1"
android:minWidth="152dp"
android:textColor="@color/text_tertiary"
android:textSize="12sp"
tools:text="5.13123123123 ETH"
tools:visibility="visible" />
</com.skydoves.androidveil.VeilLayout>
<TextView
android:id="@+id/tv_processing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/wallet_balance_blockchain_unreachable"
android:textColor="@color/warning"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/veil_balance"
tools:visibility="visible" />
<TextView
android:id="@+id/tv_currency_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/darkGray1"
android:textSize="16sp"
app:drawableEndCompat="@drawable/ic_arrow_angle_down"
app:drawableTint="@color/darkGray1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="USD" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -2,20 +2,65 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@null"
android:elevation="0dp"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
tools:parentTag="androidx.cardview.widget.CardView">
android:layout_width="48dp"
android:layout_height="48dp"
tools:layout_gravity="center"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/iv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter"
tools:src="@drawable/ic_eth" />
<androidx.cardview.widget.CardView
android:id="@+id/container_iv_currency"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@null"
android:elevation="0dp"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/iv_currency"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter"
tools:src="@drawable/ic_eth" />
<TextView
android:id="@+id/tv_token_letter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="25sp"
android:textStyle="bold"
tools:text="J" />
</androidx.cardview.widget.CardView>
<ImageView
android:id="@+id/iv_blockchain"
android:layout_width="18dp"
android:layout_height="18dp"
android:background="@drawable/shape_circle"
android:backgroundTint="@color/backgroundWhite"
android:contentDescription="@null"
android:padding="2dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_eth"
tools:visibility="visible" />
<View
android:id="@+id/badge"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/shape_badge_custom_currency"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/container_iv_currency"
app:layout_constraintEnd_toEndOf="@id/container_iv_currency"
tools:visibility="visible" />
</merge>

View file

@ -160,7 +160,7 @@
<string name="alert_failed_to_send_email_title">Не удалось отправить электронное письмо</string>
<string name="alert_failed_to_send_email_message">Причина: %s</string>
<string name="alert_failed_to_send_transaction_title">Не могу отправить транзакцию</string>
<string name="alert_failed_to_send_transaction_message">Причина: %s. Хотите отправить отзыв?</string>
<string name="alert_failed_to_send_transaction_message">Причина: %s</string>
<string name="alert_troubleshooting_scan_card_title">У Вас возникли трудности со сканированием вашей карты?</string>
<string name="alert_troubleshooting_scan_card_message">Пожалуйста, попробуйте приложить карту точно так, как показано на анимации, или обратитесь в поддержку</string>
<string name="alert_troubleshooting_scan_card_ok">Отмена</string>

View file

@ -55,7 +55,7 @@
<string name="alert_failed_to_send_email_title" translatable="false">Failed to send email</string>
<string name="alert_failed_to_send_email_message" translatable="false">Reason: %s</string>
<string name="alert_failed_to_send_transaction_title" translatable="false">Can\'t send a transaction</string>
<string name="alert_failed_to_send_transaction_message" translatable="false">Reason: %s. Do you want to send feedback?</string>
<string name="alert_failed_to_send_transaction_message" translatable="false">Reason: %s</string>
<string name="alert_troubleshooting_scan_card_title" translatable="false">Are you having difficulty scanning your card?</string>
<string name="alert_troubleshooting_scan_card_message" translatable="false">Please try to tap the card exactly as shown in the animation or request support.</string>
<string name="alert_troubleshooting_scan_card_ok" translatable="false">I\'m okay</string>

View file

@ -1,8 +1,9 @@
ext.versions = [
kotlin : '1.6.10',
build_gradle : '7.1.3',
tamgem_card_sdk : 'develop-157',
tamgem_blockchain_sdk: 'develop-99',
tangem_card_sdk : 'develop-159',
tangem_blockchain_sdk: 'develop-100',
// tangem_blockchain_sdk: '0.0.1',
]
ext.environmentConfig = [

View file

@ -59,9 +59,9 @@ dependencies {
implementation implementation(project(path: ':network'))
implementation implementation(project(path: ':common'))
implementation "com.tangem:blockchain:${versions.tamgem_blockchain_sdk}"
implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tamgem_card_sdk}"
implementation "com.tangem.tangem-sdk-kotlin:android:${versions.tamgem_card_sdk}"
implementation "com.tangem:blockchain:${versions.tangem_blockchain_sdk}"
implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tangem_card_sdk}"
implementation "com.tangem.tangem-sdk-kotlin:android:${versions.tangem_card_sdk}"
// Kotlin coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'

View file

@ -9,20 +9,18 @@ import java.util.*
[REDACTED_AUTHOR]
*/
object TapWorkarounds {
fun isStart2CoinIssuer(cardIssuer: String?): Boolean {
return cardIssuer?.lowercase(Locale.US) == START_2_COIN_ISSUER
}
val Card.isStart2Coin: Boolean
get() = isStart2CoinIssuer(issuer.name)
val Card.isSaltPay: Boolean
get() = false //TODO fix when we know which cards are SaltPay cards
val Card.isTestCard: Boolean
get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH)
val Card.useOldStyleDerivation: Boolean
get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95"
val Card.derivationStyle: DerivationStyle?
get() = if (!settings.isHDWalletAllowed) {
null
@ -42,21 +40,19 @@ object TapWorkarounds {
return false
}
fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId)
fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId) || isSaltPay
fun isTangemWalletBatch(card: Card): Boolean = tangemWalletBatches.contains(card.batchId)
fun Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId]
fun Card.getTangemNoteBlockchain(): Blockchain? =
tangemNoteBatches[batchId] ?: if (isSaltPay) Blockchain.Gnosis else null
private const val START_2_COIN_ISSUER = "start2coin"
private const val TEST_CARD_BATCH = "99FF"
private const val TEST_CARD_ID_STARTS_WITH = "FF99"
private val excludedBatches = listOf(
"0027",
"0030",
"0031",
"0035"
"0035",
)
private val excludedIssuers = listOf(

View file

@ -12,7 +12,7 @@ dependencies {
implementation implementation(project(path: ':common'))
// Tangem sdk's
implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tamgem_card_sdk}"
implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tangem_card_sdk}"
// Kotlin coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'