Updated on 2026-08-14

This commit is contained in:
Tangem 2022-09-16 14:00:12 +03:00
commit 27f43629fa
29 changed files with 187 additions and 271 deletions

View file

@ -1,18 +1,18 @@
package com.tangem.tap
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.appsflyer.AppsFlyerLib
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.tangem.Log
import com.tangem.LogFormat
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.domain.DomainLayer
import com.tangem.domain.common.LogConfig
import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.AndroidAssetReader
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
import com.tangem.tap.common.feedback.FeedbackManager
@ -22,10 +22,9 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.shop.TangemShopService
import com.tangem.tap.domain.configurable.config.Config
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
import com.tangem.tap.domain.configurable.config.FeaturesRemoteLoader
import com.tangem.tap.domain.configurable.warningMessage.RemoteWarningLoader
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
@ -38,7 +37,7 @@ import timber.log.Timber
val store = Store(
reducer = ::appReducer,
middleware = AppState.getMiddleware(),
state = AppState()
state = AppState(),
)
lateinit var foregroundActivityObserver: ForegroundActivityObserver
@ -49,36 +48,28 @@ lateinit var walletConnectRepository: WalletConnectRepository
lateinit var shopService: TangemShopService
class TapApplication : Application(), ImageLoaderFactory {
override fun onCreate() {
super.onCreate()
DomainLayer.init()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
this.minimumFetchIntervalInSeconds = 60
})
} else {
Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
this.minimumFetchIntervalInSeconds = 3600
})
}
NetworkConnectivity.createInstance(store, this)
preferencesStorage = PreferencesStorage(this)
currenciesRepository = CurrenciesRepository(
this, store.state.domainNetworks.tangemTechService
)
walletConnectRepository = WalletConnectRepository(this)
foregroundActivityObserver = ForegroundActivityObserver()
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
initFeedbackManager()
loadConfigs()
DomainLayer.init()
NetworkConnectivity.createInstance(store, this)
preferencesStorage = PreferencesStorage(this)
currenciesRepository = CurrenciesRepository(this, store.state.domainNetworks.tangemTechService)
walletConnectRepository = WalletConnectRepository(this)
val configLoader = FeaturesLocalLoader(AndroidAssetReader(this), MoshiConverter.defaultMoshi())
initConfigManager(configLoader, ::initWithConfigDependency)
initWarningMessagesManager()
BlockchainSdkRetrofitBuilder.enableNetworkLogging = LogConfig.network.blockchainSdkNetwork
initAppsFlyer()
}
override fun newImageLoader(): ImageLoader {
@ -88,64 +79,71 @@ class TapApplication : Application(), ImageLoaderFactory {
)
}
private fun loadConfigs() {
val moshi = MoshiConverter.defaultMoshi()
val localLoader = FeaturesLocalLoader(this, moshi)
val remoteLoader = FeaturesRemoteLoader(moshi)
val configManager = ConfigManager(localLoader, remoteLoader)
configManager.load { config ->
private fun initConfigManager(loader: FeaturesLocalLoader, onComplete: (Config) -> Unit) {
val configManager = ConfigManager()
configManager.load(loader) { config ->
store.dispatch(GlobalAction.SetConfigManager(configManager))
shopService = TangemShopService(
application = this,
shopifyShop = config.shopify!!
)
store.state.globalState.feedbackManager?.initChat(
context = this,
zendeskConfig = config.zendesk!!
)
onComplete(config)
}
val warningsManager = WarningMessagesManager(RemoteWarningLoader(moshi))
warningsManager.load { store.dispatch(GlobalAction.SetWarningManager(warningsManager)) }
}
private fun initFeedbackManager() {
val infoHolder = AdditionalFeedbackInfo()
infoHolder.setAppVersion(this)
val logLevels = listOf(
Log.Level.ApduCommand,
Log.Level.Apdu,
Log.Level.Tlv,
Log.Level.Nfc,
Log.Level.Command,
Log.Level.Session,
Log.Level.View,
Log.Level.Network,
Log.Level.Error,
)
val logWriter = TangemLogCollector(
levels = logLevels,
messageFormatter = LogFormat.StairsFormatter(),
)
Log.addLogger(logWriter)
store.dispatch(
GlobalAction.SetFeedbackManager(
FeedbackManager(
infoHolder = infoHolder,
logCollector = logWriter,
preferencesStorage = preferencesStorage,
logEnabled = LogConfig.zendesk,
),
),
)
private fun initWithConfigDependency(config: Config) {
shopService = TangemShopService(this, config.shopify!!)
initAppsFlyer(this, config)
initFeedbackManager(this, preferencesStorage, config)
}
private fun initAppsFlyer() {
val devKey = store.state.globalState.configManager?.config?.appsFlyerDevKey ?: return
AppsFlyerLib.getInstance().init(devKey, null, this)
AppsFlyerLib.getInstance().start(this)
val analyticsHandler = GlobalAnalyticsHandler.createDefaultAnalyticHandlers(this)
private fun initAppsFlyer(context: Context, config: Config) {
AppsFlyerLib.getInstance().init(config.appsFlyerDevKey, null, context)
AppsFlyerLib.getInstance().start(context)
val analyticsHandler = GlobalAnalyticsHandler.createDefaultAnalyticHandlers(context)
store.dispatch(GlobalAction.SetAnanlyticHandlers(analyticsHandler))
}
private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) {
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply {
appVersion = try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
pInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
"x.y.z"
}
}
fun initTangemLogCollector(): TangemLogCollector {
val logLevels = listOf(
Log.Level.ApduCommand,
Log.Level.Apdu,
Log.Level.Tlv,
Log.Level.Nfc,
Log.Level.Command,
Log.Level.Session,
Log.Level.View,
Log.Level.Network,
Log.Level.Error,
)
return TangemLogCollector(logLevels, LogFormat.StairsFormatter())
}
val additionalFeedbackInfo = initAdditionalFeedbackInfo(context)
val tangemLogCollector = initTangemLogCollector()
Log.addLogger(tangemLogCollector)
val feedbackManager = FeedbackManager(
infoHolder = additionalFeedbackInfo,
logCollector = tangemLogCollector,
preferencesStorage = preferencesStorage,
logEnabled = LogConfig.zendesk,
)
feedbackManager.initChat(
context = context,
zendeskConfig = config.zendesk!!,
)
store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager))
}
private fun initWarningMessagesManager() {
store.dispatch(GlobalAction.SetWarningManager(WarningMessagesManager()))
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.tap.common
import android.content.Context
import com.tangem.tap.common.extensions.readAssetAsString
/**
[REDACTED_AUTHOR]
*/
interface AssetReader {
fun readAssetAsString(name: String): String
}
class AndroidAssetReader(
private val context: Context,
) : AssetReader {
override fun readAssetAsString(name: String): String {
return context.readAssetAsString(name)
}
}

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics
import android.app.Application
import android.content.Context
import com.appsflyer.AFInAppEventParameterName
import com.appsflyer.AFInAppEventType
import com.appsflyer.AppsFlyerLib
@ -8,7 +8,7 @@ import com.shopify.buy3.Storefront
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
class AppsFlyerAnalyticsHandler(val context: Application): AnalyticsHandler() {
class AppsFlyerAnalyticsHandler(val context: Context): AnalyticsHandler() {
override fun triggerEvent(
event: AnalyticsEvent,
@ -16,8 +16,7 @@ class AppsFlyerAnalyticsHandler(val context: Application): AnalyticsHandler() {
blockchain: String?,
params: Map<String, String>
) {
AppsFlyerLib.getInstance().logEvent(context,
event.event, prepareParams(card, blockchain))
AppsFlyerLib.getInstance().logEvent(context, event.event, prepareParams(card, blockchain))
}
override fun logCardSdkError(

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics
import android.app.Application
import android.content.Context
import com.shopify.buy3.Storefront
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
@ -42,7 +42,7 @@ class GlobalAnalyticsHandler(val analyticsHandlers: List<AnalyticsHandler>) :
}
companion object {
fun createDefaultAnalyticHandlers(context: Application): GlobalAnalyticsHandler {
fun createDefaultAnalyticHandlers(context: Context): GlobalAnalyticsHandler {
return GlobalAnalyticsHandler(
listOf(
FirebaseAnalyticsHandler,

View file

@ -1,7 +1,5 @@
package com.tangem.tap.common.feedback
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
@ -45,15 +43,6 @@ class AdditionalFeedbackInfo {
var fee: String = ""
var token: String = ""
fun setAppVersion(context: Context) {
try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
appVersion = pInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
}
fun setCardInfo(data: ScanResponse) {
cardId = data.card.cardId
cardBlockchain = data.walletData?.blockchain ?: ""

View file

@ -31,13 +31,13 @@ sealed class TapError(
object InsufficientBalance : TapError(R.string.send_error_insufficient_balance)
object BlockchainInternalError : TapError(R.string.send_error_blockchain_internal)
object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance)
data class AmountLowerExistentialDeposit(override val args: List<Any>) : TapError(R.string.send_error_minimum_balance_format)
object FeeExceedsBalance : TapError(R.string.send_validation_invalid_fee)
object TotalExceedsBalance : TapError(R.string.send_validation_invalid_total)
object InvalidAmountValue : TapError(R.string.send_validation_invalid_amount)
object InvalidFeeValue : TapError(R.string.send_error_invalid_fee_value)
data class DustAmount(override val args: List<Any>) : TapError(R.string.send_error_dust_amount_format)
object DustChange : TapError(R.string.send_error_dust_change)
data class CreateAccountUnderfunded(override val args: List<Any>) : TapError(R.string.send_error_no_target_account)
data class UnsupportedState(
val stateError: String,

View file

@ -1,12 +1,8 @@
package com.tangem.tap.domain.configurable.config
import android.content.Context
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.readAssetAsString
import com.tangem.tap.common.AssetReader
import com.tangem.tap.domain.configurable.Loader
import timber.log.Timber
@ -14,8 +10,8 @@ import timber.log.Timber
[REDACTED_AUTHOR]
*/
class FeaturesLocalLoader(
private val context: Context,
private val moshi: Moshi,
private val assetReader: AssetReader,
private val moshi: Moshi,
) : Loader<ConfigModel> {
override fun load(onComplete: (ConfigModel) -> Unit) {
@ -23,8 +19,8 @@ class FeaturesLocalLoader(
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
val valuesAdapter: JsonAdapter<ConfigValueModel> = moshi.adapter(ConfigValueModel::class.java)
val jsonFeatures = context.readAssetAsString(Loader.featuresName)
val jsonConfigValues = context.readAssetAsString(Loader.configValuesName)
val jsonFeatures = assetReader.readAssetAsString(Loader.featuresName)
val jsonConfigValues = assetReader.readAssetAsString(Loader.configValuesName)
ConfigModel(featureAdapter.fromJson(jsonFeatures), valuesAdapter.fromJson(jsonConfigValues))
} catch (ex: Exception) {
@ -33,31 +29,4 @@ class FeaturesLocalLoader(
}
onComplete(config)
}
}
class FeaturesRemoteLoader(
private val moshi: Moshi,
) : Loader<ConfigModel> {
override fun load(onComplete: (ConfigModel) -> Unit) {
val emptyConfig = ConfigModel.empty()
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener {
if (it.isSuccessful) {
val config = remoteConfig.getValue(Loader.featuresName)
val jsonConfig = config.asString()
if (jsonConfig.isEmpty()) {
onComplete(emptyConfig)
return@addOnCompleteListener
}
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
onComplete(ConfigModel(featureAdapter.fromJson(jsonConfig), null))
} else {
onComplete(emptyConfig)
}
}.addOnFailureListener {
FirebaseAnalyticsHandler.logException("remote_config_error.features", it)
onComplete(emptyConfig)
}
}
}

View file

@ -24,26 +24,19 @@ data class Config(
val zendesk: ZendeskConfig? = null,
)
class ConfigManager(
private val localLoader: Loader<ConfigModel>,
private val remoteLoader: Loader<ConfigModel>
) {
class ConfigManager {
var config: Config = Config()
private set
private var defaultConfig = Config()
fun load(onComplete: ((config: Config) -> Unit)? = null) {
localLoader.load { configModel ->
fun load(configLoader: Loader<ConfigModel>, onComplete: ((config: Config) -> Unit)? = null) {
configLoader.load { configModel ->
setupFeature(configModel.features)
setupKey(configModel.configValues)
onComplete?.invoke(config)
}
// Uncomment to enable remote config
// remoteLoader.load { config ->
// setupFeature(config.features)
// }
}
fun turnOff(name: String) {

View file

@ -1,52 +0,0 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.domain.configurable.Loader
/**
[REDACTED_AUTHOR]
*/
class RemoteWarningLoader(
private val moshi: Moshi,
) : Loader<List<WarningMessage>> {
override fun load(onComplete: (List<WarningMessage>) -> Unit) {
val emptyConfig = listOf<WarningMessage>()
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener {
if (!it.isSuccessful) {
onComplete(emptyConfig)
return@addOnCompleteListener
}
val config = remoteConfig.getValue(Loader.warnings)
val jsonConfig = config.asString()
if (jsonConfig.isEmpty()) {
onComplete(emptyConfig)
return@addOnCompleteListener
}
val adapterType = Types.newParameterizedType(List::class.java, WarningMessage::class.java)
val warningsAdapter: JsonAdapter<List<WarningMessage>> = moshi.adapter(adapterType)
try {
val warnings = warningsAdapter.fromJson(jsonConfig) ?: listOf()
onComplete(warnings)
} catch (ex: Exception) {
handleError(ex)
onComplete(emptyConfig)
}
}.addOnFailureListener {
handleError(it)
onComplete(emptyConfig)
}
}
private fun handleError(ex: Exception) {
FirebaseAnalyticsHandler.logException("remote_config_error.warnings", ex)
}
}

View file

@ -1,35 +1,17 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.extensions.containsAny
import com.tangem.tap.common.extensions.removeBy
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
class WarningMessagesManager(
private val warningLoader: RemoteWarningLoader,
) {
class WarningMessagesManager {
private val warningsList: MutableList<WarningMessage> = mutableListOf()
fun load(onComplete: VoidCallback? = null) {
// exclude annoying remote debug warnings
if (BuildConfig.DEBUG) {
onComplete?.invoke()
return
}
warningLoader.load { remoteList ->
warningsList.clear()
warningsList.addAll(remoteList)
sortByPriority()
onComplete?.invoke()
}
}
fun addWarning(warning: WarningMessage) {
if (findWarning(warning) == null) {
warningsList.add(warning)
@ -37,7 +19,10 @@ class WarningMessagesManager(
}
}
fun getWarnings(location: WarningMessage.Location, forBlockchains: List<Blockchain> = emptyList()): List<WarningMessage> {
fun getWarnings(
location: WarningMessage.Location,
forBlockchains: List<Blockchain> = emptyList(),
): List<WarningMessage> {
return warningsList
.filter { !it.isHidden && it.location.contains(location) }
.filter {
@ -94,7 +79,7 @@ class WarningMessagesManager(
null,
R.string.alert_title,
R.string.alert_developer_card,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
fun alreadySignedHashesWarning(): WarningMessage = WarningMessage(
@ -106,7 +91,7 @@ class WarningMessagesManager(
null,
R.string.alert_title,
R.string.alert_card_signed_transactions,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
fun signedHashesMultiWalletWarning(): WarningMessage = WarningMessage(
@ -131,7 +116,7 @@ class WarningMessagesManager(
null,
R.string.warning_rate_app_title,
R.string.warning_rate_app_message,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
@ -147,7 +132,7 @@ class WarningMessagesManager(
null,
R.string.warning_failed_to_verify_card_title,
R.string.warning_failed_to_verify_card_message,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage = WarningMessage(
@ -160,7 +145,7 @@ class WarningMessagesManager(
titleResId = R.string.alert_title,
messageResId = R.string.warning_low_signatures_format,
origin = WarningMessage.Origin.Local,
messageFormatArg = remainingSignatures.toString()
messageFormatArg = remainingSignatures.toString(),
)
fun testCardWarning(): WarningMessage = WarningMessage(
@ -172,7 +157,7 @@ class WarningMessagesManager(
null,
R.string.alert_title,
R.string.warning_testnet_card_message,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
fun demoCardWarning(): WarningMessage = WarningMessage(
@ -184,7 +169,7 @@ class WarningMessagesManager(
null,
R.string.alert_title,
R.string.alert_demo_message,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
const val REMAINING_SIGNATURES_WARNING = 10

View file

@ -13,6 +13,7 @@ import com.tangem.common.extensions.toHexString
import com.tangem.domain.common.ScanResponse
import com.tangem.network.common.MoshiConverter
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.tap.common.AssetReader
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.tangemSdkManager
@ -119,10 +120,6 @@ class TwinCardsManager(
}
}
interface AssetReader {
fun readAssetAsString(name: String): String
}
private class Issuer(
val id: String,
val privateKey: String,

View file

@ -4,9 +4,9 @@ import com.tangem.Message
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.AssetReader
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.twins.AssetReader
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
import org.rekotlin.Action

View file

@ -15,10 +15,10 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.tap.common.AndroidAssetReader
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.readAssetAsString
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.navigation.AppScreen
@ -26,7 +26,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.common.redux.navigation.ShareElement
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
import com.tangem.tap.domain.twins.AssetReader
import com.tangem.tap.domain.twins.TwinsCardWidget
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
@ -46,13 +45,6 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
private lateinit var twinsWidget: TwinsCardWidget
private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
private val assetReader: AssetReader by lazy {
object : AssetReader {
override fun readAssetAsString(name: String): String =
requireContext().readAssetAsString(name)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
@ -250,7 +242,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
store.dispatch(
TwinCardsAction.Wallet.LaunchFirstStep(
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
assetReader
AndroidAssetReader(requireContext()),
)
)
}

View file

@ -120,6 +120,7 @@ private fun filterErrorsForAmountField(errors: EnumSet<TransactionError>): EnumS
TransactionError.TotalExceedsBalance -> {
val notAcceptable = listOf(TransactionError.AmountExceedsBalance, TransactionError.FeeExceedsBalance)
if (!showIntoAmountField.containsAll(notAcceptable)) showIntoAmountField.add(it)
showIntoAmountField.remove(TransactionError.AmountLowerExistentialDeposit)
}
else -> showIntoAmountField.add(it)
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.send.redux.middlewares
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
import com.tangem.blockchain.common.Amount
@ -301,12 +302,21 @@ fun createValidateTransactionError(
val tapErrors = errorList.map {
when (it) {
TransactionError.AmountExceedsBalance -> TapError.AmountExceedsBalance
TransactionError.AmountLowerExistentialDeposit -> {
if (walletManager is ExistentialDepositProvider) {
val args = listOf(walletManager.getExistentialDeposit().stripZeroPlainString())
TapError.AmountLowerExistentialDeposit(args)
} else {
TapError.UnknownError
}
}
TransactionError.FeeExceedsBalance -> TapError.FeeExceedsBalance
TransactionError.TotalExceedsBalance -> TapError.TotalExceedsBalance
TransactionError.InvalidAmountValue -> TapError.InvalidAmountValue
TransactionError.InvalidFeeValue -> TapError.InvalidFeeValue
TransactionError.DustAmount -> {
TapError.DustAmount(listOf(walletManager.dustValue?.stripZeroPlainString() ?: "0"))
val args = listOf(walletManager.dustValue?.stripZeroPlainString() ?: "0")
TapError.DustAmount(args)
}
TransactionError.DustChange -> TapError.DustChange
else -> TapError.UnknownError

View file

@ -218,7 +218,7 @@ class ReceiptReducer : SendInternalReducer {
}
private fun String.addPrecisionSign(): String {
val result = if (feeState.feeIsApproximate) "$CAN_BE_LOWER_SIGN $this" else ""
val result = if (feeState.feeIsApproximate) "$CAN_BE_LOWER_SIGN $this" else this
return result.trim()
}
}

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.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.module.ModuleMessageConverter
import com.tangem.tangem_sdk_new.extensions.localizedDescription
@ -47,9 +48,13 @@ private class BlockchainSdkErrorConverter(
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)
val resStringId = when (message.blockchain) {
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.string.no_account_polkadot
else -> R.string.send_error_no_target_account
}
val reserveValueString = message.minReserve.value?.stripZeroPlainString() ?: "0"
val argument = "$reserveValueString ${message.minReserve.currencySymbol}"
context.getString(resStringId, argument)
}
else -> message.customMessage
}

View file

@ -81,7 +81,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
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)
tilXlmMemo.error = fg.getText(R.string.send_extras_error_invalid_memo)
}
} else {
tilXlmMemo.error = null
@ -91,7 +91,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
if (infoState.xrpDestinationTag.error != null) {
if (infoState.xrpDestinationTag.error == TransactionExtraError.INVALID_DESTINATION_TAG) {
tilDestinationTag.error =
fg.getText(R.string.send_error_invalid_destination_tag)
fg.getText(R.string.send_extras_error_invalid_destination_tag)
}
} else {
tilDestinationTag.error = null
@ -103,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)
tilBinanceMemo.error = fg.getText(R.string.send_extras_error_invalid_memo)
}
} else {
tilBinanceMemo.error = null

View file

@ -5,7 +5,7 @@
<string name="send_error_invalid_fee_value">Falsche Gebühr</string>
<string name="send_error_dust_amount_format">Minimaler Betrag ist %s</string>
<string name="send_error_dust_change">Restbestand zu klein</string>
<string name="send_error_no_target_account">Das Zielkonto ist nicht erstellt. Der abzusendende Betrag soll %s %s + Gebühr oder mehr sein</string>
<string name="send_error_no_target_account">Das Zielkonto ist nicht erstellt. Der abzusendende Betrag soll %s + Gebühr oder mehr sein</string>
<string name="send_error_no_account_xlm">Um ein Konto zu erstellen, senden Sie 1+ XLM an diese Adresse</string>
<string name="send_error_fee_request_failed">Erhalt der Gebühr fehlgeschlagen</string>
<string name="send_error_unknown">Unbekannter Fehler</string>
@ -17,4 +17,11 @@
<string name="send_validation_amount_exceeds_balance">Der Betrag geht über die Bilanz hinaus</string>
<string name="send_validation_invalid_total">Der Gesamtbetrag geht über die Bilanz hinaus</string>
<string name="send_validation_invalid_fee">Die Gebühr geht über die Bilanz hinaus</string>
<string name="send_error_minimum_balance_format">Minimum balance is %s</string>
<string name="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_extras_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>
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>
</resources>

View file

@ -86,9 +86,4 @@
<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

@ -5,7 +5,7 @@
<string name="send_error_invalid_fee_value">Commission non valide</string>
<string name="send_error_dust_amount_format">Le montant minimal est de %s</string>
<string name="send_error_dust_change">Le reste est trop petit</string>
<string name="send_error_no_target_account">Le compte cible n\'a pas été créé. Le montant à envoyer doit être de %s %s + commissions ou plus</string>
<string name="send_error_no_target_account">Le compte cible n\'a pas été créé. Le montant à envoyer doit être de %s + commissions ou plus</string>
<string name="send_error_no_account_xlm">Pour créer un compte, envoyez 1+ XLM à cette adresse</string>
<string name="send_error_fee_request_failed">Échec de réception des commissions</string>
<string name="send_error_unknown">Erreur inconnue</string>
@ -17,4 +17,11 @@
<string name="xtz_withdrawal_message_warning">Pour ne pas payer une commission élevée la prochaine fois que vous rechargez votre portefeuille, veuillez réduire le montant de %s XTZ</string>
<string name="xtz_withdrawal_message_reduce">Réduire de %s XTZ</string>
<string name="xtz_withdrawal_message_ignore">Non, envoyer toute la somme</string>
<string name="send_error_minimum_balance_format">Minimum balance is %s</string>
<string name="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_extras_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>
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>
</resources>

View file

@ -86,9 +86,4 @@
<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

@ -7,7 +7,7 @@
<string name="send_error_dust_change">L\'importo residuo è molto basso</string>
<string name="send_error_unknown">Errore sconosciuto</string>
<string name="send_validation_amount_exceeds_balance">L\'importo supera il saldo</string>
<string name="send_error_no_target_account">Per creare un account, invia %s %s a questo indirizzo</string>
<string name="send_error_no_target_account">Per creare un account, invia %s a questo indirizzo</string>
<string name="send_error_fee_request_failed">Impossibile ottenere la commissione</string>
<string name="send_error_no_account_xlm">Per creare un account, invia 1+ XLM a questo indirizzo</string>
<string name="send_validation_invalid_address">Indirizzo non valido</string>
@ -17,4 +17,11 @@
<string name="xtz_withdrawal_message_warning">Per evitare di pagare una commissione maggiore la prossima volta che ricarichi il tuo portafoglio, riduci l\'importo di %s XTZ</string>
<string name="xtz_withdrawal_message_reduce">Riduci di %s XTZ</string>
<string name="xtz_withdrawal_message_ignore">No, invia l\'intero importo</string>
<string name="send_error_minimum_balance_format">Minimum balance is %s</string>
<string name="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_extras_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>
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>
</resources>

View file

@ -86,9 +86,4 @@
<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

@ -7,7 +7,7 @@
<string name="send_error_dust_change">Сдача слишком мала</string>
<string name="send_error_no_account_xlm">Для создания учетной записи отправьте 1+ XLM на этот адрес</string>
<string name="send_error_fee_request_failed">Не удалось получить комиссию</string>
<string name="send_error_no_target_account">Целевая учетная запись не создана. Сумма для отправки должна быть %1$s %2$s + плата за создание или больше</string>
<string name="send_error_no_target_account">Целевая учетная запись не создана. Сумма для отправки должна быть %s + плата за создание или больше</string>
<string name="send_error_unknown">Неизвестная ошибка</string>
<string name="send_validation_invalid_address">Неверный адрес</string>
<string name="send_validation_invalid_amount">Недопустимая сумма</string>
@ -17,4 +17,6 @@
<string name="xtz_withdrawal_message_warning">Чтобы не платить повышенную комиссию при следующем пополнении кошелька, уменьшите сумму на %s XTZ</string>
<string name="xtz_withdrawal_message_reduce">Уменьшить на %s XTZ</string>
<string name="xtz_withdrawal_message_ignore">Нет, отправить все</string>
<string name="send_error_minimum_balance_format">Минимальный баланс: %s</string>
<string name="no_account_polkadot">Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта.</string>
</resources>

View file

@ -99,7 +99,7 @@
<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="send_extras_error_invalid_memo">Недопустимый Memo. Он не будет добавлен в транзакцию.</string>
<string name="send_extras_error_invalid_destination_tag">Недопустимый Tag. Он не будет добавлен в транзакцию.</string>
<string name="warning_existential_deposit_message">Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены.</string>
</resources>

View file

@ -7,7 +7,7 @@
<string name="send_error_dust_change">Change is too small</string>
<string name="send_error_no_account_xlm">To create account send 1+ XLM to this address</string>
<string name="send_error_fee_request_failed">Failed to get fee</string>
<string name="send_error_no_target_account">Target account is not created. Amount to send should be %s %s + fee or more to create</string>
<string name="send_error_no_target_account">Target account is not created. Amount to send should be %s + fee or more to create</string>
<string name="send_error_unknown">Unknown error</string>
<string name="send_validation_invalid_address">Invalid address</string>
<string name="send_validation_invalid_amount">Invalid amount</string>
@ -17,4 +17,11 @@
<string name="xtz_withdrawal_message_warning">To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ</string>
<string name="xtz_withdrawal_message_reduce">Reduce by %s XTZ</string>
<string name="xtz_withdrawal_message_ignore">No, send all</string>
<string name="send_error_minimum_balance_format">Minimum balance is %s</string>
<string name="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_extras_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>
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>
</resources>

View file

@ -99,9 +99,4 @@
<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

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