diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e3f7a4fcde..a3a05911c7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -74,6 +74,7 @@ dependencies { implementation(projects.data.tokens) implementation(projects.data.txhistory) implementation(projects.data.wallets) + implementation(projects.data.analytics) /** Features */ implementation(projects.features.onboarding) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 420a6ff091..0aecd2719a 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -14,6 +14,7 @@ import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.datasource.api.common.MoshiConverter @@ -216,6 +217,9 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var sendFeatureToggles: SendFeatureToggles + + @Inject + lateinit var oneTimeEventFilter: OneTimeEventFilter // endregion Injected override fun onCreate() { @@ -353,6 +357,8 @@ internal class TapApplication : Application(), ImageLoaderFactory { factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder()) factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder()) + factory.addFilter(oneTimeEventFilter) + val buildData = AnalyticsHandlerBuilder.Data( application = application, config = config, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt index a1daeb7ccf..5eed564c97 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt @@ -9,8 +9,8 @@ class AmplitudeAnalyticsHandler( override fun id(): String = ID - override fun send(event: String, params: Map) { - client.logEvent(event, params) + override fun send(eventId: String, params: Map) { + client.logEvent(eventId, params) } companion object { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt index c4b12fc278..710c7308e6 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt @@ -12,8 +12,8 @@ class AppsFlyerAnalyticsHandler( override fun id(): String = ID - override fun send(event: String, params: Map) { - client.logEvent(event, params) + override fun send(eventId: String, params: Map) { + client.logEvent(eventId, params) } override fun send(event: AnalyticsEvent) { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt index e445597e8f..10b399382c 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt @@ -14,8 +14,8 @@ class FirebaseAnalyticsHandler( override fun id(): String = ID - override fun send(event: String, params: Map) { - client.logEvent(event, params) + override fun send(eventId: String, params: Map) { + client.logEvent(eventId, params) } override fun send(event: AnalyticsEvent) { diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts index 29b4cfb591..663fb0f33e 100644 --- a/core/analytics/build.gradle.kts +++ b/core/analytics/build.gradle.kts @@ -1,19 +1,18 @@ plugins { - alias(deps.plugins.android.library) - alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.jvm) alias(deps.plugins.kotlin.kapt) - alias(deps.plugins.hilt.android) id("configuration") } dependencies { /** DI */ - implementation(deps.hilt.android) + implementation(deps.hilt.core) kapt(deps.hilt.kapt) /** Core shouldn't depends on core, but in case with utils and logging its necessary */ implementation(projects.core.utils) - implementation(projects.core.analytics.models) + + implementation(deps.kotlin.coroutines) } \ No newline at end of file diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt index e60b14d1ee..025a6c1038 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt @@ -8,4 +8,7 @@ open class AnalyticsEvent( val event: String, var params: Map = mapOf(), val error: Throwable? = null, -) \ No newline at end of file +) { + + val id: String = "[$category] $event" +} \ No newline at end of file diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/OneTimeAnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/OneTimeAnalyticsEvent.kt new file mode 100644 index 0000000000..82d9991c71 --- /dev/null +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/OneTimeAnalyticsEvent.kt @@ -0,0 +1,6 @@ +package com.tangem.core.analytics.models + +interface OneTimeAnalyticsEvent { + + val oneTimeEventId: String +} \ No newline at end of file diff --git a/core/analytics/src/main/AndroidManifest.xml b/core/analytics/src/main/AndroidManifest.xml deleted file mode 100644 index 9270c6ee97..0000000000 --- a/core/analytics/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt index 4de7e4aee8..dc94652208 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt @@ -16,7 +16,7 @@ interface AnalyticsEventFilter { * An internal filter check that, on external or internal conditions, recognizes the possibility of * 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 diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt index 0a5f3179de..7bedfebff3 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt @@ -12,13 +12,11 @@ interface AnalyticsEventHandler { interface AnalyticsHandler : AnalyticsEventHandler { fun id(): String - fun send(event: String, params: Map = emptyMap()) + fun send(eventId: String, params: Map = emptyMap()) 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 { diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt b/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt index 3b12478c96..b4bb704732 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt @@ -2,6 +2,8 @@ package com.tangem.core.analytics.di import com.tangem.core.analytics.Analytics 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.Provides import dagger.hilt.InstallIn @@ -10,11 +12,16 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -class AnalyticsModule { +internal object AnalyticsModule { @Singleton @Provides fun provideAnalyticsHandler(): AnalyticsEventHandler { return Analytics // todo replace after refactoring calling Analytics in whole project } + + @Provides + fun provideOneTimeEventFilter(analyticsRepository: AnalyticsRepository): OneTimeEventFilter { + return OneTimeEventFilter(analyticsRepository) + } } \ No newline at end of file diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt b/core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt new file mode 100644 index 0000000000..c224eff75b --- /dev/null +++ b/core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt @@ -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) + } +} \ No newline at end of file diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/repository/AnalyticsRepository.kt b/core/analytics/src/main/java/com/tangem/core/analytics/repository/AnalyticsRepository.kt new file mode 100644 index 0000000000..859220527a --- /dev/null +++ b/core/analytics/src/main/java/com/tangem/core/analytics/repository/AnalyticsRepository.kt @@ -0,0 +1,8 @@ +package com.tangem.core.analytics.repository + +interface AnalyticsRepository { + + suspend fun checkIsEventSent(eventId: String): Boolean + + suspend fun setIsEventSent(eventId: String) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 6eda0886c9..0ad9c10237 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -34,6 +34,8 @@ object PreferencesKeys { val BALANCE_HIDING_SETTINGS_KEY by lazy { stringPreferencesKey(name = "balanceHidingSettings") } 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 */ diff --git a/data/analytics/.gitignore b/data/analytics/.gitignore new file mode 100644 index 0000000000..3a48006eea --- /dev/null +++ b/data/analytics/.gitignore @@ -0,0 +1 @@ +/buld diff --git a/data/analytics/build.gradle.kts b/data/analytics/build.gradle.kts new file mode 100644 index 0000000000..258c49ea37 --- /dev/null +++ b/data/analytics/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt b/data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt new file mode 100644 index 0000000000..9235045bae --- /dev/null +++ b/data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt @@ -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(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY) + + return eventId in sentEvents + } + + override suspend fun setIsEventSent(eventId: String) { + appPreferencesStore.editData { mutablePreferences -> + val sentEvents = mutablePreferences.getObject>(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY) + val updatedSentEvents = sentEvents.orEmpty() + eventId + + mutablePreferences.setObject(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY, updatedSentEvents) + } + } +} \ No newline at end of file diff --git a/data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt b/data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt new file mode 100644 index 0000000000..ed3e2b04ac --- /dev/null +++ b/data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt @@ -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) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 59ac1ba906..1abc2626ae 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -128,7 +128,6 @@ include(":domain:app-theme:models") include(":domain:balance-hiding") include(":domain:balance-hiding:models") include(":domain:transaction") - // endregion Domain modules // region Data modules @@ -142,4 +141,5 @@ include(":data:source:preferences") include(":data:settings") include(":data:txhistory") include(":data:wallets") +include(":data:analytics") // endregion Data modules \ No newline at end of file