Updated on 2026-08-14

This commit is contained in:
Tangem 2022-01-27 07:29:39 +00:00
commit b40982c43a
31 changed files with 359 additions and 208 deletions

View file

@ -110,6 +110,9 @@ dependencies {
implementation 'com.google.firebase:firebase-config-ktx'
implementation 'com.google.firebase:firebase-analytics-ktx'
// AppsFlyer
implementation 'com.appsflyer:af-android-sdk:6.5.1'
// WalletConnect
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'

View file

@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
@ -35,7 +36,8 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:fullBackupContent">
<activity

@ -1 +1 @@
Subproject commit e80938f7d9ba378a91ac544a9170597e64afa696
Subproject commit 42803296690ad6914ec2c59c5fdc3f0fb30f8afc

View file

@ -1,11 +1,13 @@
package com.tangem.tap
import android.app.Application
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.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.images.PicassoHelper
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
@ -28,9 +30,9 @@ import org.rekotlin.Store
import timber.log.Timber
val store = Store(
reducer = ::appReducer,
middleware = AppState.getMiddleware(),
state = AppState()
reducer = ::appReducer,
middleware = AppState.getMiddleware(),
state = AppState()
)
lateinit var preferencesStorage: PreferencesStorage
lateinit var currenciesRepository: CurrenciesRepository
@ -61,6 +63,8 @@ class TapApplication : Application() {
loadConfigs()
BlockchainSdkRetrofitBuilder.enableNetworkLogging = BuildConfig.DEBUG
initAppsFlyer()
}
private fun loadConfigs() {
@ -82,4 +86,12 @@ class TapApplication : Application() {
store.dispatch(GlobalAction.SetFeedbackManager(FeedbackManager(infoHolder, logWriter)))
}
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)
store.dispatch(GlobalAction.SetAnanlyticHandlers(analyticsHandler))
}
}

View file

@ -4,12 +4,66 @@ enum class AnalyticsEvent(val event: String) {
CARD_IS_SCANNED("card_is_scanned"),
TRANSACTION_IS_SENT("transaction_is_sent"),
READY_TO_SCAN("ready_to_scan"),
APP_RATING_DISPLAYED("rate_app_warning_displayed"),
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"),
GET_CARD("get_card"),
}
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"),
SOURCE("source"),
}
enum class GetCardSourceParams(val param: String) {
WELCOME("welcome"),
ONBOARDING_BUY_MORE("wallet_onboaring_buy_more_cards"),
ONBOARDING("wallet_onboarding")
}
object Analytics {
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 }
enum class WcAction { PersonalSign, SignTransaction, SendTransaction }
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()
}
}

View file

@ -2,14 +2,75 @@ package com.tangem.tap.common.analytics
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.extensions.filterNotNull
interface AnalyticsHandler {
fun triggerEvent(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null)
fun logCardSdkError(
abstract class AnalyticsHandler {
abstract fun triggerEvent(
event: AnalyticsEvent,
card: Card? = null,
blockchain: String? = null,
params: Map<String, String> = emptyMap()
)
abstract fun logCardSdkError(
error: TangemSdkError,
actionToLog: FirebaseAnalyticsHandler.ActionToLog,
parameters: Map<FirebaseAnalyticsHandler.AnalyticsParam, String>? = null,
actionToLog: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>? = null,
card: Card? = null
)
abstract fun logError(
error: Throwable,
params: Map<String, String> = emptyMap()
)
protected fun prepareParams(card: Card?, blockchain: String? = null): Map<String, String> {
return mapOf(
AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue,
AnalyticsParam.BATCH_ID.param to card?.batchId,
AnalyticsParam.BLOCKCHAIN.param to blockchain,
).filterNotNull()
}
fun logWcEvent(event: Analytics.WcAnalyticsEvent) {
when (event) {
is Analytics.WcAnalyticsEvent.Action -> {
triggerEvent(
event = AnalyticsEvent.WC_SUCCESS_RESPONSE,
params = mapOf(
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action.name
)
)
}
is Analytics.WcAnalyticsEvent.Error -> {
val params = mapOf(
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action?.name,
AnalyticsParam.ERROR_DESCRIPTION.param to event.error.message
)
.filterNotNull()
logError(event.error, params)
}
is Analytics.WcAnalyticsEvent.InvalidRequest ->
triggerEvent(
event = AnalyticsEvent.WC_INVALID_REQUEST,
params = mapOf(
AnalyticsParam.WALLET_CONNECT_REQUEST.param to event.json
).filterNotNull()
)
is Analytics.WcAnalyticsEvent.Session -> {
val analyticsEvent = when (event.event) {
Analytics.WcSessionEvent.Disconnect -> AnalyticsEvent.WC_SESSION_DISCONNECTED
Analytics.WcSessionEvent.Connect -> AnalyticsEvent.WC_NEW_SESSION
}
triggerEvent(
event = analyticsEvent,
params = mapOf(
AnalyticsParam.WALLET_CONNECT_DAPP_URL.param to event.url
).filterNotNull()
)
}
}
}
}

View file

@ -0,0 +1,33 @@
package com.tangem.tap.common.analytics
import android.app.Application
import com.appsflyer.AppsFlyerLib
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
class AppsFlyerAnalyticsHandler(val context: Application): AnalyticsHandler() {
override fun triggerEvent(
event: AnalyticsEvent,
card: Card?,
blockchain: String?,
params: Map<String, String>
) {
AppsFlyerLib.getInstance().logEvent(context,
event.event, prepareParams(card, blockchain))
}
override fun logCardSdkError(
error: TangemSdkError,
actionToLog: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>?,
card: Card?
) {
//TODO
}
override fun logError(error: Throwable, params: Map<String, String>) {
//TODO
}
}

View file

@ -7,11 +7,16 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.extensions.filterNotNull
object FirebaseAnalyticsHandler : AnalyticsHandler {
override fun triggerEvent(event: AnalyticsEvent, card: Card?, blockchain: String?) {
Firebase.analytics.logEvent(event.event, setData(card, blockchain))
object FirebaseAnalyticsHandler : AnalyticsHandler() {
override fun triggerEvent(
event: AnalyticsEvent,
card: Card?,
blockchain: String?,
params: Map<String, String>
) {
val fullParams = prepareParams(card, blockchain) + params
Firebase.analytics.logEvent(event.event, fullParams.toBundle())
}
fun logException(name: String, throwable: Throwable) {
@ -22,16 +27,20 @@ object FirebaseAnalyticsHandler : AnalyticsHandler {
))
}
private fun Map<String, String>.toBundle(): Bundle {
return bundleOf(*this.toList().toTypedArray())
}
override fun logCardSdkError(
error: TangemSdkError,
actionToLog: ActionToLog,
actionToLog: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>?,
card: Card?,
) {
if (error is TangemSdkError.UserCancelled) return
val params = parameters?.toMutableMap() ?: mutableMapOf()
if (card != null) params + getParamsFromCard(card)
if (card != null) params + prepareParams(card)
params[AnalyticsParam.ACTION] = actionToLog.key
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
params[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName
@ -44,100 +53,8 @@ object FirebaseAnalyticsHandler : AnalyticsHandler {
FirebaseCrashlytics.getInstance().recordException(cardError)
}
private fun getParamsFromCard(card: Card): Map<AnalyticsParam, String> {
return mapOf(
AnalyticsParam.FIRMWARE to card.firmwareVersion.stringValue,
AnalyticsParam.BATCH_ID to card.batchId
).filterNotNull()
override fun logError(error: Throwable, params: Map<String, String>) {
params.forEach { (key, value) -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) }
FirebaseCrashlytics.getInstance().recordException(error)
}
private fun setData(card: Card?, blockchain: String?): Bundle {
if (card == null) return bundleOf()
return bundleOf(
AnalyticsParam.BLOCKCHAIN.param to (blockchain),
AnalyticsParam.BATCH_ID.param to card.batchId,
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.stringValue
)
}
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,41 @@
package com.tangem.tap.common.analytics
import android.app.Application
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
class GlobalAnalyticsHandler(val analyticsHandlers: List<AnalyticsHandler>) :
AnalyticsHandler() {
override fun triggerEvent(
event: AnalyticsEvent,
card: Card?,
blockchain: String?,
params: Map<String, String>
) {
analyticsHandlers.forEach { it.triggerEvent(event, card, blockchain) }
}
override fun logCardSdkError(
error: TangemSdkError,
actionToLog: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>?,
card: Card?
) {
analyticsHandlers.forEach { it.logCardSdkError(error, actionToLog, parameters, card) }
}
override fun logError(error: Throwable, params: Map<String, String>) {
analyticsHandlers.forEach { it.logError(error, params) }
}
companion object {
fun createDefaultAnalyticHandlers(context: Application): GlobalAnalyticsHandler {
return GlobalAnalyticsHandler(
listOf(
FirebaseAnalyticsHandler,
AppsFlyerAnalyticsHandler(context)
)
)
}
}
}

View file

@ -18,8 +18,8 @@ fun Store<*>.dispatchOnMain(action: Action) {
}
}
suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse, addAnalytics: Boolean = true) {
store.state.globalState.tapWalletManager.onCardScanned(scanResponse, addAnalytics)
suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) {
store.state.globalState.tapWalletManager.onCardScanned(scanResponse)
}
fun Store<*>.dispatchOpenUrl(url: String) {

View file

@ -3,6 +3,7 @@ package com.tangem.tap.common.redux.global
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.redux.*
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.config.ConfigManager
@ -69,6 +70,7 @@ sealed class GlobalAction : Action {
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction()
data class SetAnanlyticHandlers(val analyticsHandlers: GlobalAnalyticsHandler) : GlobalAction()
data class SendFeedback(val emailData: EmailData) : GlobalAction()
data class UpdateFeedbackInfo(val walletManagers: List<WalletManager>) : GlobalAction()

View file

@ -4,7 +4,6 @@ import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.Result
import com.tangem.tap.*
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.withMainContext
@ -89,9 +88,8 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
is GlobalAction.ScanCard -> {
scope.launch {
val result = tangemSdkManager.scanProduct(
FirebaseAnalyticsHandler,
store.state.globalState.analyticsHandlers,
currenciesRepository,
action.shouldDeriveWC,
action.messageResId
)
withMainContext {

View file

@ -46,6 +46,8 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
globalState.copy(configManager = action.configManager)
}
is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
is GlobalAction.SetAnanlyticHandlers ->
globalState.copy(analyticsHandlers = action.analyticsHandlers)
is GlobalAction.UpdateWalletSignedHashes -> {
val card = globalState.scanResponse?.card ?: return globalState
val wallet = card.wallet(action.walletPublicKey) ?: return globalState

View file

@ -1,5 +1,6 @@
package com.tangem.tap.common.redux.global
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.PayIdManager
@ -28,6 +29,7 @@ data class GlobalState(
val dialog: StateDialog? = null,
val moonpayStatus: MoonpayStatus? = null,
val resources: AndroidResources = AndroidResources(),
val analyticsHandlers: AnalyticsHandler? = null,
) : StateType

View file

@ -19,9 +19,9 @@ import com.tangem.operations.CommandResponse
import com.tangem.operations.pins.CheckUserCodesCommand
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.ByteArrayKey
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
import com.tangem.tap.domain.tasks.DerivationTask
@ -39,12 +39,11 @@ import kotlin.coroutines.suspendCoroutine
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
suspend fun scanProduct(
analyticsHandler: AnalyticsHandler,
analyticsHandler: AnalyticsHandler?,
currenciesRepository: CurrenciesRepository,
shouldDeriveWC: Boolean,
messageRes: Int? = null,
): CompletionResult<ScanResponse> {
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
analyticsHandler?.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository), null, message)
@ -62,13 +61,23 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
}
private fun sendScanFailuresToAnalytics(
analyticsHandler: AnalyticsHandler,
analyticsHandler: AnalyticsHandler?,
result: CompletionResult<ScanResponse>
) {
if (result is CompletionResult.Failure && result.error is TangemSdkError) {
(result.error as? TangemSdkError)?.let { error ->
analyticsHandler.logCardSdkError(error, FirebaseAnalyticsHandler.ActionToLog.Scan)
}
when (result) {
is CompletionResult.Success ->
analyticsHandler?.triggerEvent(
event = AnalyticsEvent.CARD_IS_SCANNED,
card = result.data.card,
blockchain = result.data.walletData?.blockchain
)
is CompletionResult.Failure ->
(result.error as? TangemSdkError)?.let { error ->
analyticsHandler?.logCardSdkError(
error = error,
actionToLog = Analytics.ActionToLog.Scan
)
}
}
}

View file

@ -2,8 +2,6 @@ package com.tangem.tap.domain
import com.tangem.blockchain.common.*
import com.tangem.common.services.Result
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.FiatCurrencyName
@ -71,10 +69,7 @@ class TapWalletManager {
handleFiatRatesResult(results)
}
suspend fun onCardScanned(data: ScanResponse, addAnalyticsEvent: Boolean = false) {
if (addAnalyticsEvent) {
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card, data.walletData?.blockchain)
}
suspend fun onCardScanned(data: ScanResponse) {
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card)
updateConfigManager(data)

View file

@ -11,6 +11,7 @@ data class Config(
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
val appsFlyerDevKey: String = "",
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
val isSendingToPayIdEnabled: Boolean = true,
val isTopUpEnabled: Boolean = false,
@ -84,7 +85,8 @@ class ConfigManager(
blockchairAuthorizationToken = values.blockchairAuthorizationToken,
blockcypherTokens = values.blockcypherTokens,
infuraProjectId = values.infuraProjectId
)
),
appsFlyerDevKey = values.appsFlyerDevKey,
)
defaultConfig = defaultConfig.copy(
coinMarketCapKey = values.coinMarketCapKey,
@ -95,7 +97,8 @@ class ConfigManager(
blockchairAuthorizationToken = values.blockchairAuthorizationToken,
blockcypherTokens = values.blockcypherTokens,
infuraProjectId = values.infuraProjectId
)
),
appsFlyerDevKey = values.appsFlyerDevKey,
)
}

View file

@ -19,6 +19,7 @@ class ConfigValueModel(
val blockchairAuthorizationToken: String?,
val blockcypherTokens: Set<String>?,
val infuraProjectId: String?,
val appsFlyerDevKey: String,
)
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {

View file

@ -11,12 +11,17 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.network.createMoshi
import com.tangem.tap.tangemSdkManager
class TwinCardsManager(private val card: Card, assetReader: AssetReader) {
class TwinCardsManager(
private val card: Card,
assetReader: AssetReader,
val analyticsHandler: AnalyticsHandler?
) {
private val currentCardId: String = card.cardId
@ -31,9 +36,9 @@ class TwinCardsManager(private val card: Card, assetReader: AssetReader) {
is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString()
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
analyticsHandler?.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
Analytics.ActionToLog.CreateWallet,
card = card
)
}
@ -61,9 +66,9 @@ class TwinCardsManager(private val card: Card, assetReader: AssetReader) {
}
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
analyticsHandler?.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
Analytics.ActionToLog.CreateWallet,
card = card
)
}
@ -81,9 +86,9 @@ class TwinCardsManager(private val card: Card, assetReader: AssetReader) {
is CompletionResult.Success -> Result.Success(response.data)
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
analyticsHandler?.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WriteIssuerData,
Analytics.ActionToLog.WriteIssuerData,
card = card
)
}

View file

@ -2,7 +2,7 @@ package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.guard
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.walletconnect.*
@ -23,17 +23,7 @@ import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.collections.MutableMap
import kotlin.collections.filter
import kotlin.collections.first
import kotlin.collections.forEach
import kotlin.collections.get
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mutableMapOf
import kotlin.collections.set
import kotlin.collections.toMap
import kotlin.collections.toMutableMap
class WalletConnectManager {
@ -171,9 +161,9 @@ class WalletConnectManager {
}
private fun onSessionClosed(session: WCSession) {
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Session(
FirebaseAnalyticsHandler.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url
store.state.globalState.analyticsHandlers?.logWcEvent(
Analytics.WcAnalyticsEvent.Session(
Analytics.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url
)
)
sessions.remove(session)
@ -312,9 +302,9 @@ class WalletConnectManager {
)
)
}
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Session(
FirebaseAnalyticsHandler.WcSessionEvent.Connect, peer.url
store.state.globalState.analyticsHandlers?.logWcEvent(
Analytics.WcAnalyticsEvent.Session(
Analytics.WcSessionEvent.Connect, peer.url
)
)
}
@ -326,9 +316,9 @@ class WalletConnectManager {
}
client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSendTransaction: $transaction")
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
FirebaseAnalyticsHandler.WcAction.SendTransaction
store.state.globalState.analyticsHandlers?.logWcEvent(
Analytics.WcAnalyticsEvent.Action(
Analytics.WcAction.SendTransaction
)
)
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
@ -344,9 +334,9 @@ class WalletConnectManager {
}
client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSignTransaction: $transaction")
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
FirebaseAnalyticsHandler.WcAction.SignTransaction
store.state.globalState.analyticsHandlers?.logWcEvent(
Analytics.WcAnalyticsEvent.Action(
Analytics.WcAction.SignTransaction
)
)
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
@ -362,9 +352,9 @@ class WalletConnectManager {
}
client.onEthSign = { id: Long, message: WCEthereumSignMessage ->
Timber.d("onEthSign: $message")
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
FirebaseAnalyticsHandler.WcAction.PersonalSign
store.state.globalState.analyticsHandlers?.logWcEvent(
Analytics.WcAnalyticsEvent.Action(
Analytics.WcAction.PersonalSign
)
)
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
@ -417,9 +407,9 @@ class WalletConnectManager {
val message = EthSignHelper.tryToParseEthTypedMessage(data)
if (message != null) {
Timber.d("onEthSign_v4: $message")
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
FirebaseAnalyticsHandler.WcAction.PersonalSign
store.state.globalState.analyticsHandlers?.logWcEvent(
Analytics.WcAnalyticsEvent.Action(
Analytics.WcAction.PersonalSign
)
)
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->

View file

@ -13,7 +13,7 @@ import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
@ -136,9 +136,9 @@ class WalletConnectSdkHelper {
}
is SimpleResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WalletConnectTransaction,
Analytics.ActionToLog.WalletConnectTransaction,
)
}
Timber.e(result.error)
@ -167,9 +167,9 @@ class WalletConnectSdkHelper {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign,
Analytics.ActionToLog.WalletConnectSign,
)
}
Timber.e(result.error.customMessage)
@ -196,9 +196,9 @@ class WalletConnectSdkHelper {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WalletConnectTransaction,
Analytics.ActionToLog.WalletConnectTransaction,
)
}
Timber.e(result.error.customMessage)
@ -273,9 +273,9 @@ class WalletConnectSdkHelper {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign,
Analytics.ActionToLog.WalletConnectSign,
)
}
Timber.e(result.error.customMessage)

View file

@ -5,7 +5,8 @@ import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.Result
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
@ -118,9 +119,9 @@ class DetailsMiddleware {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.PurgeWallet,
Analytics.ActionToLog.PurgeWallet,
card = store.state.detailsState.scanResponse?.card
)
}
@ -206,11 +207,11 @@ class DetailsMiddleware {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
store.state.globalState.analyticsHandlers?.logCardSdkError(
error = error,
actionToLog = FirebaseAnalyticsHandler.ActionToLog.ChangeSecOptions,
actionToLog = Analytics.ActionToLog.ChangeSecOptions,
parameters = mapOf(
FirebaseAnalyticsHandler.AnalyticsParam.NEW_SECURITY_OPTION to
AnalyticsParam.NEW_SECURITY_OPTION to
(selectedOption?.name ?: "")
),
card = store.state.detailsState.scanResponse?.card

View file

@ -1,5 +1,8 @@
package com.tangem.tap.features.home.redux
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.GetCardSourceParams
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.onCardScanned
@ -46,7 +49,13 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
}
}
is HomeAction.ReadCard -> handleReadCard()
is HomeAction.GoToShop -> store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI)
is HomeAction.GoToShop -> {
store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI)
store.state.globalState.analyticsHandlers?.triggerEvent(
event = AnalyticsEvent.GET_CARD,
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.WELCOME.param)
)
}
}
next(action)
}

View file

@ -130,7 +130,11 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
}
}
is TwinCardsAction.Wallet.LaunchFirstStep -> {
val manager = TwinCardsManager(getScanResponse().card, action.reader)
val manager = TwinCardsManager(
card = getScanResponse().card,
assetReader = action.reader,
analyticsHandler = store.state.globalState.analyticsHandlers
)
store.dispatch(TwinCardsAction.CardsManager.Set(manager))
scope.launch {

View file

@ -4,6 +4,9 @@ import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.operations.backup.BackupService
import com.tangem.tap.*
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.GetCardSourceParams
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.withMainContext
@ -212,7 +215,13 @@ private fun handleBackupAction(action: BackupAction) {
}
}
is BackupAction.GoToShop -> {
store.dispatchOpenUrl(BUY_WALLET_URL)
backupState.buyAdditionalCardsUrl?.let { url ->
store.dispatchOpenUrl(url)
store.state.globalState.analyticsHandlers?.triggerEvent(
event = AnalyticsEvent.GET_CARD,
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.ONBOARDING.param)
)
}
}
is BackupAction.FinishAddingBackupCards -> {
if (backupService.addedBackupCardsCount == 1) {

View file

@ -12,7 +12,6 @@ import com.google.android.material.tabs.TabLayoutMediator
import com.squareup.picasso.Picasso
import com.tangem.common.CardIdFormatter
import com.tangem.common.core.CardIdDisplayFormat
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget
@ -20,7 +19,6 @@ import com.tangem.tap.common.postUi
import com.tangem.tap.features.FragmentOnBackPressedHandler
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.onboarding.products.wallet.redux.*
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
import com.tangem.tap.store
import com.tangem.wallet.R
@ -377,7 +375,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.shop_menu -> {
store.dispatchOpenUrl(BUY_WALLET_URL)
store.dispatch(BackupAction.GoToShop)
true
}
else -> super.onOptionsItemSelected(item)

View file

@ -9,8 +9,9 @@ import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.Result
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
@ -180,7 +181,7 @@ private fun sendTransaction(
tangemSdk.config.linkedTerminal = isLinkedTerminal
when (sendResult) {
is SimpleResult.Success -> {
FirebaseAnalyticsHandler.triggerEvent(
store.state.globalState.analyticsHandlers?.triggerEvent(
event = AnalyticsEvent.TRANSACTION_IS_SENT,
card = card,
blockchain = walletManager.wallet.blockchain.currency
@ -239,11 +240,11 @@ private fun sendTransaction(
}
else -> {
(sendResult.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.SendTransaction,
Analytics.ActionToLog.SendTransaction,
mapOf(
FirebaseAnalyticsHandler.AnalyticsParam.BLOCKCHAIN
AnalyticsParam.BLOCKCHAIN
to walletManager.wallet.blockchain.currency),
card = card,
)

View file

@ -10,7 +10,7 @@ import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.*
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
@ -104,13 +104,13 @@ class WalletMiddleware {
when (result) {
is CompletionResult.Success -> {
val scanNoteResponse = globalState.scanResponse?.copy(card = result.data)
scanNoteResponse?.let { store.onCardScanned(scanNoteResponse, false) }
scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) }
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
Analytics.ActionToLog.CreateWallet,
card = store.state.detailsState.scanResponse?.card
)
}

View file

@ -7,7 +7,6 @@ import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.isGreaterThan
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.domain.TapWorkarounds.isTestCard
@ -75,7 +74,7 @@ class WarningsMiddleware {
preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds()
}
if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) {
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISPLAYED)
store.state.globalState.analyticsHandlers?.triggerEvent(AnalyticsEvent.APP_RATING_DISPLAYED)
addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
}
}

View file

@ -10,7 +10,6 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import com.google.android.play.core.review.ReviewManagerFactory
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.getActivity
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
@ -102,13 +101,14 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
WarningMessage.Type.AppRating -> {
view.group_controls_temporary.hide()
view.group_controls_rating.show()
val analyticsHandler = store.state.globalState.analyticsHandlers
view.btn_close.setOnClickListener {
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS)
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS)
store.dispatch(GlobalAction.HideWarningMessage(warning))
store.dispatch(WalletAction.Warnings.AppRating.RemindLater)
}
view.btn_can_be_better.setOnClickListener {
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
store.dispatch(GlobalAction.HideWarningMessage(warning))
store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail()))
@ -116,7 +116,7 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
view.btn_really_cool.setOnClickListener {
val activity = view.context.getActivity() ?: return@setOnClickListener
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
val reviewManager = ReviewManagerFactory.create(activity)
val task = reviewManager.requestReviewFlow()

View file

@ -1,4 +1,4 @@
ext.versions = [
kotlin : '1.5.30',
build_gradle: '7.0.2',
kotlin : '1.6.10',
build_gradle: '7.0.4',
]