Updated on 2026-08-14

This commit is contained in:
Tangem 2021-06-29 15:37:45 +03:00
commit 636c714683
42 changed files with 5430 additions and 1027 deletions

View file

@ -8,4 +8,8 @@ enum class AnalyticsEvent(val event: String) {
APP_RATING_DISMISS("dismiss_rate_app_warning"),
APP_RATING_NEGATIVE("negative_rate_app_feedback"),
APP_RATING_POSITIVE("positive_rate_app_feedback"),
WC_SUCCESS_RESPONSE("wallet_connect_success_response"),
WC_INVALID_REQUEST("wallet_connect_invalid_request"),
WC_NEW_SESSION("wallet_connect_new_session"),
WC_SESSION_DISCONNECTED("wallet_connect_session_disconnected"),
}

View file

@ -1,8 +1,16 @@
package com.tangem.tap.common.analytics
import com.tangem.TangemSdkError
import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.card.Card
interface AnalyticsHandler {
fun triggerEvent(event: AnalyticsEvent, card: Card? = null)
fun triggerEvent(event: AnalyticsEvent, card: Card? = null, blockchain: Blockchain? = null)
fun logCardSdkError(
error: TangemSdkError,
actionToLog: FirebaseAnalyticsHandler.ActionToLog,
parameters: Map<FirebaseAnalyticsHandler.AnalyticsParam, String>? = null,
card: Card? = null
)
}

View file

@ -3,34 +3,143 @@ package com.tangem.tap.common.analytics
import android.os.Bundle
import androidx.core.os.bundleOf
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import com.tangem.TangemSdkError
import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.card.Card
import com.tangem.tap.common.extensions.filterNotNull
object FirebaseAnalyticsHandler : AnalyticsHandler {
override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
Firebase.analytics.logEvent(event.event, setCardData(card))
override fun triggerEvent(event: AnalyticsEvent, card: Card?, blockchain: Blockchain?) {
Firebase.analytics.logEvent(event.event, setData(card, blockchain))
}
fun logException(name: String, throwable: Throwable) {
Firebase.analytics.logEvent(name, bundleOf(
"message" to (throwable.message ?: "none"),
"cause_message" to (throwable.cause?.message ?: "none"),
"stack_trace" to throwable.stackTraceToString()
"message" to (throwable.message ?: "none"),
"cause_message" to (throwable.cause?.message ?: "none"),
"stack_trace" to throwable.stackTraceToString()
))
}
private fun setCardData(card: Card?): Bundle {
override fun logCardSdkError(
error: TangemSdkError,
actionToLog: ActionToLog,
parameters: Map<AnalyticsParam, String>?,
card: Card?,
) {
if (error is TangemSdkError.UserCancelled) return
val params = parameters?.toMutableMap() ?: mutableMapOf()
if (card != null) params + getParamsFromCard(card)
params[AnalyticsParam.ACTION] = actionToLog.key
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
params[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName
params[AnalyticsParam.ERROR_KEY] = "TangemSdkError"
params.forEach {
FirebaseCrashlytics.getInstance().setCustomKey(it.key.param, it.value)
}
val cardError = TangemSdk.map(error)
FirebaseCrashlytics.getInstance().recordException(cardError)
}
private fun getParamsFromCard(card: Card): Map<AnalyticsParam, String> {
return mapOf(
AnalyticsParam.FIRMWARE to card.firmwareVersion.version,
AnalyticsParam.BATCH_ID to card.cardData?.batchId
).filterNotNull()
}
private fun setData(card: Card?, blockchain: Blockchain?): Bundle {
if (card == null) return bundleOf()
return bundleOf(
AnalyticsParam.BLOCKCHAIN.param to card.cardData?.blockchainName,
AnalyticsParam.BATCH_ID.param to card.cardData?.batchId,
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.version
AnalyticsParam.BLOCKCHAIN.param to (blockchain?.currency
?: card.cardData?.blockchainName),
AnalyticsParam.BATCH_ID.param to card.cardData?.batchId,
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.version
)
}
private enum class AnalyticsParam(val param: String) {
fun logWcEvent(event: WcAnalyticsEvent) {
when (event) {
is WcAnalyticsEvent.Action -> {
Firebase.analytics.logEvent(
AnalyticsEvent.WC_SUCCESS_RESPONSE.event, bundleOf(
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action.name
)
)
}
is WcAnalyticsEvent.Error -> {
mapOf(
AnalyticsParam.WALLET_CONNECT_ACTION to event.action?.name,
AnalyticsParam.ERROR_DESCRIPTION to event.error.message
)
.filterNotNull()
.forEach {
FirebaseCrashlytics.getInstance().setCustomKey(it.key.param, it.value)
}
FirebaseCrashlytics.getInstance().recordException(event.error)
}
is WcAnalyticsEvent.InvalidRequest ->
Firebase.analytics.logEvent(
AnalyticsEvent.WC_INVALID_REQUEST.event, bundleOf(
AnalyticsParam.WALLET_CONNECT_REQUEST.param to event.json
)
)
is WcAnalyticsEvent.Session -> {
val analyticsEvent = when (event.event) {
WcSessionEvent.Disconnect -> AnalyticsEvent.WC_SESSION_DISCONNECTED
WcSessionEvent.Connect -> AnalyticsEvent.WC_NEW_SESSION
}
Firebase.analytics.logEvent(
analyticsEvent.event, bundleOf(
AnalyticsParam.WALLET_CONNECT_DAPP_URL.param to event.url
)
)
}
}
}
enum class AnalyticsParam(val param: String) {
BLOCKCHAIN("blockchain"),
BATCH_ID("batch_id"),
FIRMWARE("firmware"),
ACTION("action"),
ERROR_DESCRIPTION("error_description"),
ERROR_CODE("error_code"),
NEW_SECURITY_OPTION("new_security_option"),
ERROR_KEY("Tangem SDK error key"),
WALLET_CONNECT_ACTION("wallet_connect_action"),
WALLET_CONNECT_REQUEST("wallet_connect_request"),
WALLET_CONNECT_DAPP_URL("wallet_connect_dapp_url"),
}
}
enum class ActionToLog(val key: String) {
Scan("tap_scan_task"),
SendTransaction("send_transaction"),
WalletConnectSign("wallet_connect_personal_sign"),
WalletConnectTransaction("wallet_connect_tx_sign"),
ReadPinSettings("read_pin_settings"),
ChangeSecOptions("change_sec_options"),
CreateWallet("create_wallet"),
PurgeWallet("purge_wallet"),
WriteIssuerData("write_issuer_data"),
}
enum class WcSessionEvent { Disconnect, Connect }
sealed class WcAnalyticsEvent {
data class Error(val error: Throwable, val action: WcAction?) :
WcAnalyticsEvent()
data class Session(val event: WcSessionEvent, val url: String?) : WcAnalyticsEvent()
data class Action(val action: WcAction) : WcAnalyticsEvent()
data class InvalidRequest(val json: String?) : WcAnalyticsEvent()
}
enum class WcAction { PersonalSign, SignTransaction, SendTransaction }
}

View file

@ -0,0 +1,77 @@
package com.tangem.tap.common.analytics
import com.tangem.TangemSdkError
object TangemSdk {
// This mapping is performed to group errors in FirebaseCrashlytics.
// At the moment, the errors in Crashlytics can only be grouped by their place of creation (class and line).
fun map(error: TangemSdkError): TangemSdkError {
return when (error) {
is TangemSdkError.TagLost -> TangemSdkError.TagLost()
is TangemSdkError.ExtendedLengthNotSupported -> TangemSdkError.ExtendedLengthNotSupported()
is TangemSdkError.SerializeCommandError -> TangemSdkError.SerializeCommandError()
is TangemSdkError.DeserializeApduFailed -> TangemSdkError.DeserializeApduFailed()
is TangemSdkError.EncodingFailedTypeMismatch -> TangemSdkError.EncodingFailedTypeMismatch(
error.customMessage)
is TangemSdkError.EncodingFailed -> TangemSdkError.EncodingFailed(error.customMessage)
is TangemSdkError.DecodingFailedMissingTag -> TangemSdkError.DecodingFailedMissingTag(
error.customMessage)
is TangemSdkError.DecodingFailedTypeMismatch -> TangemSdkError.DecodingFailedTypeMismatch(
error.customMessage)
is TangemSdkError.DecodingFailed -> TangemSdkError.DecodingFailed(error.customMessage)
is TangemSdkError.InvalidResponse -> TangemSdkError.InvalidResponse()
is TangemSdkError.UnknownStatus -> TangemSdkError.UnknownStatus(error.statusWord)
is TangemSdkError.ErrorProcessingCommand -> TangemSdkError.ErrorProcessingCommand()
is TangemSdkError.InvalidState -> TangemSdkError.InvalidState()
is TangemSdkError.InsNotSupported -> TangemSdkError.InsNotSupported()
is TangemSdkError.InvalidParams -> TangemSdkError.InvalidParams()
is TangemSdkError.NeedEncryption -> TangemSdkError.NeedEncryption()
is TangemSdkError.FileNotFound -> TangemSdkError.FileNotFound()
is TangemSdkError.WalletNotFound -> TangemSdkError.WalletNotFound()
is TangemSdkError.AlreadyPersonalized -> TangemSdkError.AlreadyPersonalized()
is TangemSdkError.CannotBeDepersonalized -> TangemSdkError.CannotBeDepersonalized()
is TangemSdkError.Pin1Required -> TangemSdkError.Pin1Required()
is TangemSdkError.CardReadWrongWallet -> TangemSdkError.CardReadWrongWallet()
is TangemSdkError.CardWithMaxZeroWallets -> TangemSdkError.CardWithMaxZeroWallets()
is TangemSdkError.AlreadyCreated -> TangemSdkError.AlreadyCreated()
is TangemSdkError.WalletIndexExceedsMaxValue -> TangemSdkError.WalletIndexExceedsMaxValue()
is TangemSdkError.MaxNumberOfWalletsCreated -> TangemSdkError.MaxNumberOfWalletsCreated()
is TangemSdkError.WalletIndexNotCorrect -> TangemSdkError.WalletIndexNotCorrect()
is TangemSdkError.PurgeWalletProhibited -> TangemSdkError.PurgeWalletProhibited()
is TangemSdkError.Pin1CannotBeChanged -> TangemSdkError.Pin1CannotBeChanged()
is TangemSdkError.Pin2CannotBeChanged -> TangemSdkError.Pin2CannotBeChanged()
is TangemSdkError.Pin1CannotBeDefault -> TangemSdkError.Pin1CannotBeDefault()
is TangemSdkError.NoRemainingSignatures -> TangemSdkError.NoRemainingSignatures()
is TangemSdkError.EmptyHashes -> TangemSdkError.EmptyHashes()
is TangemSdkError.HashSizeMustBeEqual -> TangemSdkError.HashSizeMustBeEqual()
is TangemSdkError.WalletIsNotCreated -> TangemSdkError.WalletIsNotCreated()
is TangemSdkError.SignHashesNotAvailable -> TangemSdkError.SignHashesNotAvailable()
is TangemSdkError.TooManyHashesInOneTransaction -> TangemSdkError.TooManyHashesInOneTransaction()
is TangemSdkError.ExtendedDataSizeTooLarge -> TangemSdkError.ExtendedDataSizeTooLarge()
is TangemSdkError.NotPersonalized -> TangemSdkError.NotPersonalized()
is TangemSdkError.NotActivated -> TangemSdkError.NotActivated()
is TangemSdkError.WalletIsPurged -> TangemSdkError.WalletIsPurged()
is TangemSdkError.Pin2OrCvcRequired -> TangemSdkError.Pin2OrCvcRequired()
is TangemSdkError.VerificationFailed -> TangemSdkError.VerificationFailed()
is TangemSdkError.DataSizeTooLarge -> TangemSdkError.DataSizeTooLarge()
is TangemSdkError.MissingCounter -> TangemSdkError.MissingCounter()
is TangemSdkError.OverwritingDataIsProhibited -> TangemSdkError.OverwritingDataIsProhibited()
is TangemSdkError.DataCannotBeWritten -> TangemSdkError.DataCannotBeWritten()
is TangemSdkError.MissingIssuerPubicKey -> TangemSdkError.MissingIssuerPubicKey()
is TangemSdkError.CardVerificationFailed -> TangemSdkError.CardVerificationFailed()
is TangemSdkError.WrongPin1 -> TangemSdkError.WrongPin1()
is TangemSdkError.WrongPin2 -> TangemSdkError.WrongPin2()
is TangemSdkError.UnknownError -> TangemSdkError.UnknownError()
is TangemSdkError.UserCancelled -> TangemSdkError.UserCancelled()
is TangemSdkError.Busy -> TangemSdkError.Busy()
is TangemSdkError.MissingPreflightRead -> TangemSdkError.MissingPreflightRead()
is TangemSdkError.WrongCardNumber -> TangemSdkError.WrongCardNumber()
is TangemSdkError.WrongCardType -> TangemSdkError.WrongCardType()
is TangemSdkError.CardError -> TangemSdkError.CardError()
is TangemSdkError.FirmwareNotSupported -> TangemSdkError.FirmwareNotSupported()
is TangemSdkError.WalletError -> TangemSdkError.WalletError()
}
}
}

View file

@ -0,0 +1,4 @@
package com.tangem.tap.common.extensions
fun <K, V> Map<out K?, V?>.filterNotNull(): Map<K, V> =
filter { it.key != null && it.value != null } as Map<K, V>

View file

@ -1,7 +1,9 @@
package com.tangem.tap.common.extensions
import android.graphics.PorterDuff
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import com.tangem.blockchain.common.Blockchain
@ -48,6 +50,7 @@ fun Picasso.loadCurrenciesIcon(
imageView.colorFilter = null
textView.text = null
}
if (blockchain.isTestnet()) imageView.tint(R.color.tint)
}
})
}
@ -70,12 +73,20 @@ private fun setOfflineCurrencyImage(
} else {
setBlockchainImage(imageView, textView, blockchain)
}
if (blockchain.isTestnet()) imageView.tint(R.color.tint)
}
fun ImageView.tint(colorRes: Int) {
// val color = ContextCompat.getColor(context, colorRes);
// ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color));
this.setColorFilter(ContextCompat.getColor(context, colorRes), PorterDuff.Mode.DARKEN);
}
private fun setBlockchainImage(
imageView: ImageView,
textView: TextView,
blockchain: Blockchain
blockchain: Blockchain,
) {
imageView.setImageResource(blockchain.getIconRes())
imageView.colorFilter = null
@ -85,7 +96,7 @@ private fun setBlockchainImage(
private fun setTokenImage(
imageView: ImageView,
textView: TextView,
token: Token
token: Token,
) {
imageView.setImageResource(R.drawable.shape_circle)
imageView.setColorFilter(token.getColor())

View file

@ -37,7 +37,7 @@ fun BigDecimal.toFiatString(rateValue: BigDecimal, fiatCurrencyName: FiatCurrenc
fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
val fiatValue = rateValue.multiply(this)
return fiatValue.setScale(2, RoundingMode.DOWN)
return fiatValue.setScale(2, RoundingMode.HALF_UP)
}
fun BigDecimal.toFormattedFiatValue(fiatCurrencyName: FiatCurrencyName): String {