Updated on 2026-08-14
This commit is contained in:
parent
94703ae459
commit
34ffa9a1e7
20 changed files with 158 additions and 21 deletions
|
|
@ -74,6 +74,7 @@ dependencies {
|
||||||
implementation(projects.data.tokens)
|
implementation(projects.data.tokens)
|
||||||
implementation(projects.data.txhistory)
|
implementation(projects.data.txhistory)
|
||||||
implementation(projects.data.wallets)
|
implementation(projects.data.wallets)
|
||||||
|
implementation(projects.data.analytics)
|
||||||
|
|
||||||
/** Features */
|
/** Features */
|
||||||
implementation(projects.features.onboarding)
|
implementation(projects.features.onboarding)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||||
import com.tangem.blockchain.common.WalletManagerFactory
|
import com.tangem.blockchain.common.WalletManagerFactory
|
||||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
|
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||||
import com.tangem.datasource.api.common.MoshiConverter
|
import com.tangem.datasource.api.common.MoshiConverter
|
||||||
|
|
@ -216,6 +217,9 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var sendFeatureToggles: SendFeatureToggles
|
lateinit var sendFeatureToggles: SendFeatureToggles
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var oneTimeEventFilter: OneTimeEventFilter
|
||||||
// endregion Injected
|
// endregion Injected
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
|
@ -353,6 +357,8 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
||||||
factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder())
|
factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder())
|
||||||
factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder())
|
factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder())
|
||||||
|
|
||||||
|
factory.addFilter(oneTimeEventFilter)
|
||||||
|
|
||||||
val buildData = AnalyticsHandlerBuilder.Data(
|
val buildData = AnalyticsHandlerBuilder.Data(
|
||||||
application = application,
|
application = application,
|
||||||
config = config,
|
config = config,
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ class AmplitudeAnalyticsHandler(
|
||||||
|
|
||||||
override fun id(): String = ID
|
override fun id(): String = ID
|
||||||
|
|
||||||
override fun send(event: String, params: Map<String, String>) {
|
override fun send(eventId: String, params: Map<String, String>) {
|
||||||
client.logEvent(event, params)
|
client.logEvent(eventId, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ class AppsFlyerAnalyticsHandler(
|
||||||
|
|
||||||
override fun id(): String = ID
|
override fun id(): String = ID
|
||||||
|
|
||||||
override fun send(event: String, params: Map<String, String>) {
|
override fun send(eventId: String, params: Map<String, String>) {
|
||||||
client.logEvent(event, params)
|
client.logEvent(eventId, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun send(event: AnalyticsEvent) {
|
override fun send(event: AnalyticsEvent) {
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ class FirebaseAnalyticsHandler(
|
||||||
|
|
||||||
override fun id(): String = ID
|
override fun id(): String = ID
|
||||||
|
|
||||||
override fun send(event: String, params: Map<String, String>) {
|
override fun send(eventId: String, params: Map<String, String>) {
|
||||||
client.logEvent(event, params)
|
client.logEvent(eventId, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun send(event: AnalyticsEvent) {
|
override fun send(event: AnalyticsEvent) {
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
plugins {
|
plugins {
|
||||||
alias(deps.plugins.android.library)
|
alias(deps.plugins.kotlin.jvm)
|
||||||
alias(deps.plugins.kotlin.android)
|
|
||||||
alias(deps.plugins.kotlin.kapt)
|
alias(deps.plugins.kotlin.kapt)
|
||||||
alias(deps.plugins.hilt.android)
|
|
||||||
id("configuration")
|
id("configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
/** DI */
|
/** DI */
|
||||||
implementation(deps.hilt.android)
|
implementation(deps.hilt.core)
|
||||||
kapt(deps.hilt.kapt)
|
kapt(deps.hilt.kapt)
|
||||||
|
|
||||||
/** Core shouldn't depends on core, but in case with utils and logging its necessary */
|
/** Core shouldn't depends on core, but in case with utils and logging its necessary */
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
|
|
||||||
implementation(projects.core.analytics.models)
|
implementation(projects.core.analytics.models)
|
||||||
|
|
||||||
|
implementation(deps.kotlin.coroutines)
|
||||||
}
|
}
|
||||||
|
|
@ -8,4 +8,7 @@ open class AnalyticsEvent(
|
||||||
val event: String,
|
val event: String,
|
||||||
var params: Map<String, String> = mapOf(),
|
var params: Map<String, String> = mapOf(),
|
||||||
val error: Throwable? = null,
|
val error: Throwable? = null,
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
val id: String = "[$category] $event"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.tangem.core.analytics.models
|
||||||
|
|
||||||
|
interface OneTimeAnalyticsEvent {
|
||||||
|
|
||||||
|
val oneTimeEventId: String
|
||||||
|
}
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest package="com.tangem.core.analytics" />
|
|
||||||
|
|
@ -16,7 +16,7 @@ interface AnalyticsEventFilter {
|
||||||
* An internal filter check that, on external or internal conditions, recognizes the possibility of
|
* An internal filter check that, on external or internal conditions, recognizes the possibility of
|
||||||
* sending an event.
|
* sending an event.
|
||||||
*/
|
*/
|
||||||
fun canBeSent(event: AnalyticsEvent): Boolean
|
suspend fun canBeSent(event: AnalyticsEvent): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a check to see if the event can be dispatched by a specific handler
|
* Performs a check to see if the event can be dispatched by a specific handler
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,11 @@ interface AnalyticsEventHandler {
|
||||||
interface AnalyticsHandler : AnalyticsEventHandler {
|
interface AnalyticsHandler : AnalyticsEventHandler {
|
||||||
fun id(): String
|
fun id(): String
|
||||||
|
|
||||||
fun send(event: String, params: Map<String, String> = emptyMap())
|
fun send(eventId: String, params: Map<String, String> = emptyMap())
|
||||||
|
|
||||||
override fun send(event: AnalyticsEvent) {
|
override fun send(event: AnalyticsEvent) {
|
||||||
send(prepareEventString(event), event.params)
|
send(event.id, event.params)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun prepareEventString(event: AnalyticsEvent): String = "[${event.category}] ${event.event}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ErrorEventHandler {
|
interface ErrorEventHandler {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package com.tangem.core.analytics.di
|
||||||
|
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
|
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||||
|
import com.tangem.core.analytics.repository.AnalyticsRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -10,11 +12,16 @@ import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
class AnalyticsModule {
|
internal object AnalyticsModule {
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideAnalyticsHandler(): AnalyticsEventHandler {
|
fun provideAnalyticsHandler(): AnalyticsEventHandler {
|
||||||
return Analytics // todo replace after refactoring calling Analytics in whole project
|
return Analytics // todo replace after refactoring calling Analytics in whole project
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideOneTimeEventFilter(analyticsRepository: AnalyticsRepository): OneTimeEventFilter {
|
||||||
|
return OneTimeEventFilter(analyticsRepository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.tangem.core.analytics.filter
|
||||||
|
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsEventFilter
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsHandler
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
|
import com.tangem.core.analytics.models.OneTimeAnalyticsEvent
|
||||||
|
import com.tangem.core.analytics.repository.AnalyticsRepository
|
||||||
|
|
||||||
|
class OneTimeEventFilter(
|
||||||
|
private val analyticsRepository: AnalyticsRepository,
|
||||||
|
) : AnalyticsEventFilter {
|
||||||
|
|
||||||
|
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is OneTimeAnalyticsEvent
|
||||||
|
|
||||||
|
override suspend fun canBeSent(event: AnalyticsEvent): Boolean {
|
||||||
|
if (event !is OneTimeAnalyticsEvent) return true
|
||||||
|
|
||||||
|
val isSent = analyticsRepository.checkIsEventSent(event.oneTimeEventId)
|
||||||
|
|
||||||
|
if (!isSent) {
|
||||||
|
analyticsRepository.setIsEventSent(event.oneTimeEventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return !isSent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean {
|
||||||
|
return canBeAppliedTo(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.core.analytics.repository
|
||||||
|
|
||||||
|
interface AnalyticsRepository {
|
||||||
|
|
||||||
|
suspend fun checkIsEventSent(eventId: String): Boolean
|
||||||
|
|
||||||
|
suspend fun setIsEventSent(eventId: String)
|
||||||
|
}
|
||||||
|
|
@ -34,6 +34,8 @@ object PreferencesKeys {
|
||||||
val BALANCE_HIDING_SETTINGS_KEY by lazy { stringPreferencesKey(name = "balanceHidingSettings") }
|
val BALANCE_HIDING_SETTINGS_KEY by lazy { stringPreferencesKey(name = "balanceHidingSettings") }
|
||||||
|
|
||||||
val WALLETS_SCROLL_PREVIEW_KEY by lazy { booleanPreferencesKey(name = "walletsScrollPreview") }
|
val WALLETS_SCROLL_PREVIEW_KEY by lazy { booleanPreferencesKey(name = "walletsScrollPreview") }
|
||||||
|
|
||||||
|
val SENT_ONE_TIME_EVENTS_KEY by lazy { stringPreferencesKey(name = "sentOneTimeEvents") }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore<Preferences> */
|
/** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore<Preferences> */
|
||||||
|
|
|
||||||
1
data/analytics/.gitignore
vendored
Normal file
1
data/analytics/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/buld
|
||||||
32
data/analytics/build.gradle.kts
Normal file
32
data/analytics/build.gradle.kts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
plugins {
|
||||||
|
alias(deps.plugins.android.library)
|
||||||
|
alias(deps.plugins.kotlin.android)
|
||||||
|
alias(deps.plugins.kotlin.kapt)
|
||||||
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.tangem.data.analytics"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
/** Project - Analytics */
|
||||||
|
implementation(projects.core.analytics)
|
||||||
|
implementation(projects.core.analytics.models)
|
||||||
|
|
||||||
|
/** Project - Data */
|
||||||
|
implementation(projects.core.datasource)
|
||||||
|
implementation(projects.data.common)
|
||||||
|
|
||||||
|
/** AndroidX */
|
||||||
|
implementation(deps.androidx.datastore)
|
||||||
|
|
||||||
|
/** DI */
|
||||||
|
implementation(deps.hilt.core)
|
||||||
|
kapt(deps.hilt.kapt)
|
||||||
|
|
||||||
|
/** Other */
|
||||||
|
implementation(deps.kotlin.coroutines)
|
||||||
|
implementation(deps.timber)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.tangem.data.analytics
|
||||||
|
|
||||||
|
import com.tangem.core.analytics.repository.AnalyticsRepository
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
|
import com.tangem.datasource.local.preferences.utils.getObjectListSync
|
||||||
|
|
||||||
|
internal class DefaultAnalyticsRepository(
|
||||||
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
|
) : AnalyticsRepository {
|
||||||
|
|
||||||
|
override suspend fun checkIsEventSent(eventId: String): Boolean {
|
||||||
|
val sentEvents = appPreferencesStore
|
||||||
|
.getObjectListSync<String>(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY)
|
||||||
|
|
||||||
|
return eventId in sentEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setIsEventSent(eventId: String) {
|
||||||
|
appPreferencesStore.editData { mutablePreferences ->
|
||||||
|
val sentEvents = mutablePreferences.getObject<List<String>>(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY)
|
||||||
|
val updatedSentEvents = sentEvents.orEmpty() + eventId
|
||||||
|
|
||||||
|
mutablePreferences.setObject(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY, updatedSentEvents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.tangem.data.analytics.di
|
||||||
|
|
||||||
|
import com.tangem.core.analytics.repository.AnalyticsRepository
|
||||||
|
import com.tangem.data.analytics.DefaultAnalyticsRepository
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object AnalyticsDataModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideAnalyticsRepository(appPreferencesStore: AppPreferencesStore): AnalyticsRepository {
|
||||||
|
return DefaultAnalyticsRepository(appPreferencesStore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -128,7 +128,6 @@ include(":domain:app-theme:models")
|
||||||
include(":domain:balance-hiding")
|
include(":domain:balance-hiding")
|
||||||
include(":domain:balance-hiding:models")
|
include(":domain:balance-hiding:models")
|
||||||
include(":domain:transaction")
|
include(":domain:transaction")
|
||||||
|
|
||||||
// endregion Domain modules
|
// endregion Domain modules
|
||||||
|
|
||||||
// region Data modules
|
// region Data modules
|
||||||
|
|
@ -142,4 +141,5 @@ include(":data:source:preferences")
|
||||||
include(":data:settings")
|
include(":data:settings")
|
||||||
include(":data:txhistory")
|
include(":data:txhistory")
|
||||||
include(":data:wallets")
|
include(":data:wallets")
|
||||||
|
include(":data:analytics")
|
||||||
// endregion Data modules
|
// endregion Data modules
|
||||||
Loading…
Add table
Add a link
Reference in a new issue