diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4fdc8ffde8..61936049d7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -108,6 +108,9 @@ configurations.androidTestImplementation { exclude(module = "protobuf-lite") } +tasks.withType().configureEach { + useJUnitPlatform() +} dependencies { implementation(projects.domain.legacy) @@ -393,11 +396,10 @@ dependencies { implementation(files("libs/dexprotector-annotations.jar")) /** Testing libraries */ + testImplementation(projects.test.core) testImplementation(projects.common.test) - testImplementation(deps.test.coroutine) testImplementation(deps.test.junit) - testImplementation(deps.test.mockk) - testImplementation(deps.test.truth) + testRuntimeOnly(deps.test.junit5.engine) androidTestImplementation(deps.test.junit.android) androidTestImplementation(deps.test.espresso) androidTestImplementation(deps.test.espresso.intents) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ab376d6124..ebc857a78a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,9 +11,15 @@ - - - + + + @@ -36,7 +42,6 @@ @@ -286,9 +292,20 @@ + android:path="/pay-app" + android:scheme="https" /> + + + + + + + + + @@ -326,6 +343,6 @@ - - + + diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index 59dd05efbe..c40ccb2d5a 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -46,6 +46,7 @@ import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.hot.sdk.TangemHotSdk import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler +import com.tangem.tap.common.analytics.handlers.appsflyer.AppsFlyerClient import com.tangem.tap.common.log.TangemAppLoggerInitializer import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.proxy.AppStateHolder @@ -157,4 +158,6 @@ interface ApplicationEntryPoint { fun getTrackingContextProxy(): TrackingContextProxy fun getABTestsManager(): ABTestsManager + + fun getAppsFlyerClientFactory(): AppsFlyerClient.Factory } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index abbd2d80de..bf7f5f336a 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -66,6 +66,7 @@ import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler import com.tangem.tap.common.analytics.handlers.appsflyer.AppsFlyerAnalyticsHandler +import com.tangem.tap.common.analytics.handlers.appsflyer.AppsFlyerClient import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler import com.tangem.tap.common.images.createCoilImageLoader import com.tangem.tap.common.log.TangemAppLoggerInitializer @@ -248,6 +249,9 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration. private val abTestsManager: ABTestsManager get() = entryPoint.getABTestsManager() + private val appsFlyerClientFactory: AppsFlyerClient.Factory + get() = entryPoint.getAppsFlyerClientFactory() + // endregion private val appScope = MainScope() @@ -421,7 +425,7 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration. val factory = AnalyticsFactory() factory.addHandlerBuilder(AmplitudeAnalyticsHandler.Builder()) factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder()) - factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder()) + factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder(appsFlyerClientFactory)) factory.addFilter(oneTimeEventFilter) factory.addFilter(AppsFlyerEventFilter()) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerDeepLinkListener.kt b/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerDeepLinkListener.kt new file mode 100644 index 0000000000..2b3e47076b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerDeepLinkListener.kt @@ -0,0 +1,94 @@ +package com.tangem.tap.common.analytics.appsflyer + +import androidx.annotation.VisibleForTesting +import com.appsflyer.deeplink.DeepLink +import com.appsflyer.deeplink.DeepLinkListener +import com.appsflyer.deeplink.DeepLinkResult +import com.tangem.datasource.local.appsflyer.AppsFlyerConversionStore +import com.tangem.domain.wallets.models.AppsFlyerConversionData +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +@Singleton +class AppsFlyerDeepLinkListener @Inject constructor( + private val appsFlyerConversionStore: AppsFlyerConversionStore, + dispatchers: CoroutineDispatcherProvider, +) : DeepLinkListener { + + private val coroutineScope = CoroutineScope(dispatchers.io + SupervisorJob()) + private val mutex = Mutex() + + override fun onDeepLinking(p0: DeepLinkResult) { + when (p0.status) { + DeepLinkResult.Status.FOUND -> { + onDeepLinkFound(p0.deepLink) + } + DeepLinkResult.Status.NOT_FOUND -> { + Timber.i("No deep link found") + } + DeepLinkResult.Status.ERROR -> { + Timber.e("Deep link error: ${p0.error}") + } + } + } + + private fun onDeepLinkFound(deepLink: DeepLink) { + if (deepLink.deepLinkValue != REFERRAL_DEEP_LINK_VALUE) { + Timber.i("Ignoring deep link with value: ${deepLink.deepLinkValue ?: "null"}") + return + } + + val refcode = deepLink.getStringValue(REFCODE_PARAM_KEY) + val campaign = deepLink.getStringValue(CAMPAIGN_PARAM_KEY) + + @Suppress("NullableToStringCall") + Timber.i("refcode=$refcode\ncampaign=$campaign") + + if (!isValidParam(refcode)) { + Timber.e("Deep link conversion data is invalid") + return + } + + storeConversionData(refcode = refcode, campaign = campaign) + } + + @OptIn(ExperimentalContracts::class) + private fun isValidParam(value: String?): Boolean { + contract { + returns(true) implies (value != null) + } + + return value != null && value.isNotBlank() && !value.equals("null", ignoreCase = true) + } + + private fun storeConversionData(refcode: String, campaign: String?) { + coroutineScope.launch { + mutex.withLock { + appsFlyerConversionStore.store( + value = AppsFlyerConversionData(refcode = refcode, campaign = campaign), + ) + } + } + } + + companion object Companion { + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + const val REFERRAL_DEEP_LINK_VALUE = "referral" + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + const val REFCODE_PARAM_KEY = "deep_link_sub1" + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + const val CAMPAIGN_PARAM_KEY = "deep_link_sub2" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsClient.kt index 231660ff27..27ced0a864 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsClient.kt @@ -6,59 +6,36 @@ import com.appsflyer.AppsFlyerLib import com.appsflyer.attribution.AppsFlyerRequestListener import com.tangem.core.analytics.api.EventLogger import com.tangem.core.analytics.api.UserIdHolder +import com.tangem.tap.common.analytics.appsflyer.AppsFlyerDeepLinkListener import com.tangem.tap.common.analytics.handlers.firebase.UnderscoreAnalyticsEventConverter +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dagger.hilt.android.qualifiers.ApplicationContext import timber.log.Timber interface AppsFlyerAnalyticsClient : EventLogger, UserIdHolder -internal class AppsFlyerClient( - private val context: Context, - key: String, +class AppsFlyerClient @AssistedInject constructor( + @Assisted apiKey: String, + @ApplicationContext private val context: Context, + appsFlyerDeepLinkListener: AppsFlyerDeepLinkListener, ) : AppsFlyerAnalyticsClient { private val appsFlyerLib: AppsFlyerLib = AppsFlyerLib.getInstance() private val eventConverter = UnderscoreAnalyticsEventConverter() - private val logEventListener = object : AppsFlyerRequestListener { - override fun onSuccess() { - Timber.tag("AppsFlyerClient").i("AppsFlyerRequestListener send") - } - - override fun onError(p0: Int, p1: String) { - Timber.tag("AppsFlyerClient").e("AppsFlyerRequestListener onError: $p0, $p1") - } - } init { - val conversionListener = object : AppsFlyerConversionListener { - override fun onConversionDataSuccess(p0: Map?) { - Timber.i("AppsFlyer conversion data success: ${p0.orEmpty()}") - } - override fun onConversionDataFail(p0: String?) { - Timber.e("AppsFlyer conversion data failure: ${p0.orEmpty()}") - } - override fun onAppOpenAttribution(p0: Map?) { - Timber.i("AppsFlyer app open attribution: ${p0.orEmpty()}") - } - override fun onAttributionFailure(p0: String?) { - Timber.e("AppsFlyer attribution failure: ${p0.orEmpty()}") - } - } - - appsFlyerLib.run { + with(appsFlyerLib) { setAppId(context.packageName) setDebugLog(true) - init(key, conversionListener, context) - Timber.i("Starting AppsFlyer SDK") - start(context, key, object : AppsFlyerRequestListener { - override fun onSuccess() { - Timber.i("AppsFlyer initialized successfully") - } + init(apiKey, ConversionListener, context) - override fun onError(p0: Int, p1: String) { - Timber.e("AppsFlyer initialization error: $p0, $p1") - } - }) + subscribeForDeepLink(appsFlyerDeepLinkListener) + + Timber.i("Starting AppsFlyer SDK") + start(context, apiKey, InitializationListener) Timber.i("AppsFlyer SDK started") } } @@ -77,7 +54,47 @@ internal class AppsFlyerClient( context, event, eventConverter.convertEventParams(params), - logEventListener, + LogEventListener, ) } + + private object ConversionListener : AppsFlyerConversionListener { + override fun onConversionDataSuccess(p0: Map?) { + Timber.i("AppsFlyer conversion data success: ${p0.orEmpty()}") + } + override fun onConversionDataFail(p0: String?) { + Timber.e("AppsFlyer conversion data failure: ${p0.orEmpty()}") + } + override fun onAppOpenAttribution(p0: Map?) { + Timber.i("AppsFlyer app open attribution: ${p0.orEmpty()}") + } + override fun onAttributionFailure(p0: String?) { + Timber.e("AppsFlyer attribution failure: ${p0.orEmpty()}") + } + } + + private object InitializationListener : AppsFlyerRequestListener { + override fun onSuccess() { + Timber.d("AppsFlyer initialized successfully") + } + + override fun onError(p0: Int, p1: String) { + Timber.e("AppsFlyer initialization error: $p0, $p1") + } + } + + private object LogEventListener : AppsFlyerRequestListener { + override fun onSuccess() { + Timber.tag("AppsFlyerClient").i("AppsFlyerRequestListener send") + } + + override fun onError(p0: Int, p1: String) { + Timber.tag("AppsFlyerClient").e("AppsFlyerRequestListener onError: $p0, $p1") + } + } + + @AssistedFactory + interface Factory { + fun create(apiKey: String): AppsFlyerClient + } } \ No newline at end of file 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 26675e0fef..40dfd9b37a 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 @@ -47,7 +47,10 @@ class AppsFlyerAnalyticsHandler( const val ID = "AppsFlyer" } - class Builder : AnalyticsHandlerBuilder { + internal class Builder( + private val appsFlyerClientFactory: AppsFlyerClient.Factory, + ) : AnalyticsHandlerBuilder { + init { Timber.tag("AppsFlyer").i("AppsFlyer Analytics Handler Builder created") } @@ -58,7 +61,7 @@ class AppsFlyerAnalyticsHandler( AppsFlyerLogClient(data.jsonConverter) } else { Timber.tag("AppsFlyer").i("AppsFlyer log disabled, real client created") - AppsFlyerClient(data.application, data.config.appsFlyerApiKey) + appsFlyerClientFactory.create(apiKey = data.config.appsFlyerApiKey) }, ) } diff --git a/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerDeepLinkListenerTest.kt b/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerDeepLinkListenerTest.kt new file mode 100644 index 0000000000..d940d4fdfa --- /dev/null +++ b/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerDeepLinkListenerTest.kt @@ -0,0 +1,120 @@ +package com.tangem.tap.common.analytics.appsflyer + +import com.appsflyer.deeplink.DeepLink +import com.appsflyer.deeplink.DeepLinkResult +import com.tangem.datasource.local.appsflyer.AppsFlyerConversionStore +import com.tangem.domain.wallets.models.AppsFlyerConversionData +import com.tangem.tap.common.analytics.appsflyer.AppsFlyerDeepLinkListener.Companion.CAMPAIGN_PARAM_KEY +import com.tangem.tap.common.analytics.appsflyer.AppsFlyerDeepLinkListener.Companion.REFCODE_PARAM_KEY +import com.tangem.test.core.ProvideTestModels +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import io.mockk.clearMocks +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.params.ParameterizedTest + +/** +[REDACTED_AUTHOR] + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AppsFlyerDeepLinkListenerTest { + + private val appsFlyerConversionStore: AppsFlyerConversionStore = mockk(relaxUnitFun = true) + private val listener = AppsFlyerDeepLinkListener( + appsFlyerConversionStore = appsFlyerConversionStore, + dispatchers = TestingCoroutineDispatcherProvider(), + ) + + @AfterEach + fun tearDown() { + clearMocks(appsFlyerConversionStore) + } + + @ParameterizedTest + @ProvideTestModels + fun onDeepLinking(model: OnDeepLinkingModel) = runTest { + listener.onDeepLinking(p0 = model.deepLinkResult) + + if (model.shouldStore) { + val value = AppsFlyerConversionData(refcode = SUCCESS_REFCODE, campaign = SUCCESS_CAMPAIGN) + coVerify { appsFlyerConversionStore.store(value = value) } + } else { + coVerify(inverse = true) { appsFlyerConversionStore.store(value = any()) } + } + } + + private fun provideTestModels(): List { + return listOf( + // DeepLinkResult.Status.NOT_FOUND + OnDeepLinkingModel( + deepLinkResult = DeepLinkResult(null, null), + shouldStore = false, + ), + // DeepLinkResult.Status.ERROR + OnDeepLinkingModel( + deepLinkResult = DeepLinkResult(null, DeepLinkResult.Error.NETWORK), + shouldStore = false, + ), + // DeepLinkResult.Status.FOUND + OnDeepLinkingModel( + deepLinkResult = createFoundDeepLink( + deepLinkValue = AppsFlyerDeepLinkListener.REFERRAL_DEEP_LINK_VALUE, + refcode = SUCCESS_REFCODE, + campaign = SUCCESS_CAMPAIGN, + ), + shouldStore = true, + ), + OnDeepLinkingModel( + deepLinkResult = createFoundDeepLink( + deepLinkValue = AppsFlyerDeepLinkListener.REFERRAL_DEEP_LINK_VALUE, + refcode = "", + campaign = SUCCESS_CAMPAIGN, + ), + shouldStore = false, + ), + OnDeepLinkingModel( + deepLinkResult = createFoundDeepLink( + deepLinkValue = AppsFlyerDeepLinkListener.REFERRAL_DEEP_LINK_VALUE, + refcode = null, + campaign = SUCCESS_CAMPAIGN, + ), + shouldStore = false, + ), + OnDeepLinkingModel( + deepLinkResult = createFoundDeepLink(deepLinkValue = "some_other_deep_link_value"), + shouldStore = false, + ), + ) + } + + data class OnDeepLinkingModel( + val deepLinkResult: DeepLinkResult, + val shouldStore: Boolean, + ) + + private fun createFoundDeepLink( + deepLinkValue: String, + refcode: String? = null, + campaign: String? = null, + ): DeepLinkResult { + val deeplink = mockk { + every { this@mockk.deepLinkValue } returns deepLinkValue + every { this@mockk.getStringValue(REFCODE_PARAM_KEY) } returns refcode + every { this@mockk.getStringValue(CAMPAIGN_PARAM_KEY) } returns campaign + } + + return mockk { + every { this@mockk.status } returns DeepLinkResult.Status.FOUND + every { this@mockk.deepLink } returns deeplink + } + } + + private companion object { + const val SUCCESS_REFCODE = "valid_refcode" + const val SUCCESS_CAMPAIGN = "valid_campaign" + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/converters/WalletIdBodyConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/converters/WalletIdBodyConverter.kt index 0e65ff47c9..dba595b6ff 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/converters/WalletIdBodyConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/converters/WalletIdBodyConverter.kt @@ -12,6 +12,8 @@ object WalletIdBodyConverter { walletId = userWallet.walletId.stringValue, name = userWallet.name, walletType = WalletType.from(userWallet), + refcode = conversionData?.refcode, + campaign = conversionData?.campaign, cards = publicKeys?.map { publicKeyById -> CardInfoBody( cardId = publicKeyById.key, diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/AppsFlyerConversionStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/AppsFlyerConversionStoreModule.kt new file mode 100644 index 0000000000..d9222d32e3 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/AppsFlyerConversionStoreModule.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.di + +import com.tangem.datasource.local.appsflyer.AppsFlyerConversionStore +import com.tangem.datasource.local.appsflyer.DefaultAppsFlyerConversionStore +import com.tangem.datasource.local.preferences.AppPreferencesStore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object AppsFlyerConversionStoreModule { + + @Provides + @Singleton + fun provideAppFlyerConversionStore(appPreferencesStore: AppPreferencesStore): AppsFlyerConversionStore { + return DefaultAppsFlyerConversionStore(appPreferencesStore = appPreferencesStore) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/AppsFlyerConversionStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/AppsFlyerConversionStore.kt new file mode 100644 index 0000000000..99fa70fc9b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/AppsFlyerConversionStore.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.local.appsflyer + +import com.tangem.domain.wallets.models.AppsFlyerConversionData + +interface AppsFlyerConversionStore { + + suspend fun get(): AppsFlyerConversionData? + + suspend fun store(value: AppsFlyerConversionData) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/ConversionDataConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/ConversionDataConverter.kt new file mode 100644 index 0000000000..4afca6f7b6 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/ConversionDataConverter.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.local.appsflyer + +import com.tangem.domain.wallets.models.AppsFlyerConversionData +import com.tangem.utils.converter.TwoWayConverter + +internal object ConversionDataConverter : TwoWayConverter { + + override fun convert(value: AppsFlyerConversionData): ConversionDataDTO { + return ConversionDataDTO( + refcode = value.refcode, + campaign = value.campaign, + ) + } + + override fun convertBack(value: ConversionDataDTO): AppsFlyerConversionData { + return AppsFlyerConversionData( + refcode = value.refcode, + campaign = value.campaign, + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/DefaultAppsFlyerConversionStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/DefaultAppsFlyerConversionStore.kt new file mode 100644 index 0000000000..50a7e53100 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/DefaultAppsFlyerConversionStore.kt @@ -0,0 +1,42 @@ +package com.tangem.datasource.local.appsflyer + +import androidx.datastore.preferences.core.stringPreferencesKey +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull +import com.tangem.datasource.local.preferences.utils.storeObject +import com.tangem.domain.wallets.models.AppsFlyerConversionData +import timber.log.Timber + +internal class DefaultAppsFlyerConversionStore( + private val appPreferencesStore: AppPreferencesStore, +) : AppsFlyerConversionStore { + + override suspend fun get(): AppsFlyerConversionData? { + val dto = appPreferencesStore.getObjectSyncOrNull(KEY) ?: return null + + return ConversionDataConverter.convertBack(value = dto).also { + Timber.i("Getting conversion data from store: $it") + } + } + + override suspend fun store(value: AppsFlyerConversionData) { + Timber.i("Storing conversion data to store: $value") + + val dto = ConversionDataConverter.convert(value) + + appPreferencesStore.storeObject(KEY, dto) + } + + private companion object { + + val KEY = stringPreferencesKey("APPS_FLYER_CONVERSION_DATA") + } +} + +@JsonClass(generateAdapter = true) +internal data class ConversionDataDTO( + @Json(name = "refcode") val refcode: String, + @Json(name = "campaign") val campaign: String?, +) \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt index 2b4e65a095..56153829bb 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt @@ -72,6 +72,8 @@ class UserTokensSaver( val enrichedResponse = response.enrichIf(userWalletId = userWalletId, condition = useEnricher).copy( walletName = userWallet.name, walletType = WalletType.from(userWallet), + refcode = conversionData?.refcode, + campaign = conversionData?.campaign, ) pushLegacy(userWalletId = userWalletId, response = enrichedResponse, onFailSend = onFailSend) diff --git a/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/AppsFlyerConversionData.kt b/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/AppsFlyerConversionData.kt new file mode 100644 index 0000000000..4a8055e472 --- /dev/null +++ b/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/AppsFlyerConversionData.kt @@ -0,0 +1,3 @@ +package com.tangem.domain.wallets.models + +data class AppsFlyerConversionData(val refcode: String, val campaign: String?) \ No newline at end of file