From 77683181c72911d499b355abe85cd8fd2a4e95d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 Jul 2026 18:03:15 +0500 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../AppsFlyerReferralParamsHandler.kt | 37 ++++---- .../di/UserWalletsListRepositoryModule.kt | 6 +- .../DefaultUserWalletsListRepository.kt | 13 +-- .../component/impl/DefaultRoutingComponent.kt | 60 +++++++----- .../AppsFlyerReferralParamsHandlerTest.kt | 94 +++++++++++++++++-- .../DefaultUserWalletsListRepositoryTest.kt | 8 +- .../local/appsflyer/AppsFlyerStore.kt | 2 + .../appsflyer/DefaultAppsFlyerRepository.kt | 5 + .../data/appsflyer/di/AppsFlyerDataModule.kt | 6 ++ .../appsflyer/AppsFlyerDeeplinkSource.kt | 1 + .../repository/AppsFlyerRepository.kt | 2 + .../usecase/IsReferralInstallUseCase.kt | 15 +++ features/home/impl/build.gradle.kts | 4 +- .../features/home/impl/model/HomeModel.kt | 6 +- .../DefaultMobileWalletPromoRepository.kt | 26 ----- .../referral/di/ReferralRepositoryModule.kt | 10 -- .../domain/MobileWalletPromoRepository.kt | 8 -- .../SetShouldShowMobileWalletPromoUseCase.kt | 18 ---- .../ShouldShowMobileWalletPromoUseCase.kt | 12 --- 20 files changed, 189 insertions(+), 145 deletions(-) create mode 100644 domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/usecase/IsReferralInstallUseCase.kt delete mode 100644 features/referral/data/src/main/java/com/tangem/feature/referral/data/DefaultMobileWalletPromoRepository.kt delete mode 100644 features/referral/domain/src/main/java/com/tangem/feature/referral/domain/MobileWalletPromoRepository.kt delete mode 100644 features/referral/domain/src/main/java/com/tangem/feature/referral/domain/SetShouldShowMobileWalletPromoUseCase.kt delete mode 100644 features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ShouldShowMobileWalletPromoUseCase.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c307bb3f9b..530687a6d1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -113,6 +113,7 @@ dependencies { implementation(projects.domain.account) implementation(projects.domain.account.status) implementation(projects.domain.addressBook) + implementation(projects.domain.appsflyer) implementation(projects.domain.models) implementation(projects.domain.core) api(projects.domain.common) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandler.kt index 70a24bee80..c7edd65901 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandler.kt @@ -4,13 +4,10 @@ import com.appsflyer.deeplink.DeepLink import com.tangem.datasource.local.appsflyer.AppsFlyerDeeplinkSource import com.tangem.datasource.local.appsflyer.AppsFlyerStore import com.tangem.domain.wallets.models.AppsFlyerConversionData -import com.tangem.feature.referral.domain.SetShouldShowMobileWalletPromoUseCase import com.tangem.utils.coroutines.AppCoroutineScope import com.tangem.utils.logging.TangemLogger import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import javax.inject.Inject import javax.inject.Singleton import kotlin.contracts.ExperimentalContracts @@ -19,11 +16,9 @@ import kotlin.contracts.contract @Singleton class AppsFlyerReferralParamsHandler @Inject constructor( private val appsFlyerStore: AppsFlyerStore, - private val setShouldShowMobileWalletPromoUseCase: SetShouldShowMobileWalletPromoUseCase, private val coroutineScope: AppCoroutineScope, ) { - private val mutex = Mutex() private val deepLinkDeferred = CompletableDeferred() fun handle(params: Map) { @@ -48,15 +43,17 @@ class AppsFlyerReferralParamsHandler @Inject constructor( } suspend fun waitForDeeplink(deeplinkSource: AppsFlyerDeeplinkSource): String? { - val deeplinkFromCache = appsFlyerStore.getDeeplink(deeplinkSource) - return if (deeplinkFromCache == null) { - val value = when (deeplinkSource) { - AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding -> TANGEM_PAY_HOT_WALLET_ONBOARDING_DEEP_LINK_VALUE - } - deepLinkDeferred.await().takeIf { it == value } - } else { - deeplinkFromCache + appsFlyerStore.getDeeplink(deeplinkSource)?.let { return it } + + val expectedValue = when (deeplinkSource) { + AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding -> TANGEM_PAY_HOT_WALLET_ONBOARDING_DEEP_LINK_VALUE + AppsFlyerDeeplinkSource.Referral -> REFERRAL_DEEP_LINK_VALUE } + val resolvedValue = deepLinkDeferred.await().takeIf { it == expectedValue } + + // The deep link may have been persisted to the store while we were awaiting (e.g. from + // conversion-data handling, which stores the deep link but doesn't complete the deferred). + return resolvedValue ?: appsFlyerStore.getDeeplink(deeplinkSource) } private fun handle(deepLinkValue: String?, deepLinkSub1: String?, deepLinkSub2: String?) { @@ -79,6 +76,10 @@ class AppsFlyerReferralParamsHandler @Inject constructor( @Suppress("NullableToStringCall") TangemLogger.i("refcode=$deepLinkSub1\ncampaign=$deepLinkSub2") + coroutineScope.launch { + appsFlyerStore.storeDeeplink(AppsFlyerDeeplinkSource.Referral, REFERRAL_DEEP_LINK_VALUE) + } + if (!isValidParam(deepLinkSub1)) { TangemLogger.e("Deeplink conversion data is invalid") return @@ -98,13 +99,9 @@ class AppsFlyerReferralParamsHandler @Inject constructor( private fun storeConversionData(refcode: String, campaign: String?) { coroutineScope.launch { - mutex.withLock { - setShouldShowMobileWalletPromoUseCase(true) - .onLeft { TangemLogger.e("Error", it) } - appsFlyerStore.storeIfAbsent( - value = AppsFlyerConversionData(refcode = refcode, campaign = campaign), - ) - } + appsFlyerStore.storeIfAbsent( + value = AppsFlyerConversionData(refcode = refcode, campaign = campaign), + ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt index f9323c265d..066cfc843e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt @@ -9,6 +9,7 @@ import com.tangem.common.services.secure.SecureStorage import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.utils.TrackingContextProxy import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.appsflyer.usecase.ClearAppsFlyerDeeplinkUseCase import com.tangem.domain.common.wallets.UserWalletSelectedHandler import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.hotwallet.repository.HotWalletRepository @@ -17,7 +18,6 @@ import com.tangem.domain.visa.model.VisaActivationRemoteState import com.tangem.domain.visa.model.VisaCardActivationStatus import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository import com.tangem.domain.wallets.hot.HotWalletPasswordRequester -import com.tangem.feature.referral.domain.MobileWalletPromoRepository import com.tangem.hot.sdk.TangemHotSdk import com.tangem.sdk.storage.AndroidSecureStorage import com.tangem.sdk.storage.AndroidSecureStorageV2 @@ -57,7 +57,7 @@ internal object UserWalletsListRepositoryModule { trackingContextProxy: TrackingContextProxy, analyticsEventHandler: AnalyticsEventHandler, hotWalletRepository: HotWalletRepository, - mobileWalletPromoRepository: MobileWalletPromoRepository, + clearAppsFlyerDeeplinkUseCase: ClearAppsFlyerDeeplinkUseCase, userWalletSelectedHandler: Lazy, ): UserWalletsListRepository { val moshi = buildMoshi() @@ -109,7 +109,7 @@ internal object UserWalletsListRepositoryModule { trackingContextProxy = trackingContextProxy, analyticsEventHandler = analyticsEventHandler, hotWalletRepository = hotWalletRepository, - mobileWalletPromoRepository = mobileWalletPromoRepository, + clearAppsFlyerDeeplinkUseCase = clearAppsFlyerDeeplinkUseCase, userWalletSelectedHandler = userWalletSelectedHandler, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt index 5c6d10635a..e2a1e3fd27 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt @@ -14,6 +14,8 @@ import com.tangem.core.analytics.utils.TrackingContextProxy import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getSyncOrDefault +import com.tangem.domain.appsflyer.AppsFlyerDeeplinkSource +import com.tangem.domain.appsflyer.usecase.ClearAppsFlyerDeeplinkUseCase import com.tangem.domain.common.wallets.UserWalletSelectedHandler import com.tangem.domain.common.wallets.UserWalletTransformAction import com.tangem.domain.common.wallets.UserWalletsListRepository @@ -26,7 +28,6 @@ import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository import com.tangem.domain.wallets.hot.HotWalletPasswordRequester -import com.tangem.feature.referral.domain.MobileWalletPromoRepository import com.tangem.hot.sdk.TangemHotSdk import com.tangem.hot.sdk.model.HotWalletId import com.tangem.sdk.api.TangemSdkManager @@ -60,7 +61,7 @@ internal class DefaultUserWalletsListRepository( private val trackingContextProxy: TrackingContextProxy, private val analyticsEventHandler: AnalyticsEventHandler, private val hotWalletRepository: HotWalletRepository, - private val mobileWalletPromoRepository: MobileWalletPromoRepository, + private val clearAppsFlyerDeeplinkUseCase: ClearAppsFlyerDeeplinkUseCase, private val userWalletSelectedHandler: Lazy, ) : UserWalletsListRepository { @@ -620,12 +621,12 @@ internal class DefaultUserWalletsListRepository( } private suspend fun onFirstWalletCreated() { - // reset flag (that is set from AF deeplink) after creating a new wallet - mobileWalletPromoRepository.setShouldShowMobileWalletPromo(false) + // reset the referral attribution (set from AF deeplink) after creating a new wallet + clearAppsFlyerDeeplinkUseCase(AppsFlyerDeeplinkSource.Referral) } private suspend fun onAllWalletsDeleted() { - // reset flag (that is set from AF deeplink) after removing the last wallet - mobileWalletPromoRepository.setShouldShowMobileWalletPromo(false) + // reset the referral attribution (set from AF deeplink) after removing the last wallet + clearAppsFlyerDeeplinkUseCase(AppsFlyerDeeplinkSource.Referral) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index 6e5488d67b..305d341eda 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -43,7 +43,6 @@ import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.settings.NeverRequestPermissionUseCase import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase -import com.tangem.feature.referral.domain.ShouldShowMobileWalletPromoUseCase import com.tangem.features.hotwallet.HotAccessCodeRequestComponent import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent @@ -69,6 +68,8 @@ import com.tangem.wallet.R import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull import kotlin.time.Duration.Companion.seconds @@ -102,7 +103,6 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase, private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, private val featureTogglesManager: FeatureTogglesManager, - private val shouldShowMobileWalletPromoUseCase: ShouldShowMobileWalletPromoUseCase, ) : RoutingComponent, AppComponentContext by context, SnackbarHandler { @@ -210,23 +210,8 @@ internal class DefaultRoutingComponent @AssistedInject constructor( } private suspend fun navigateForEmptyWallets(): AppRoute { - val isHotWalletOnboardingEnabled = featureTogglesManager.isFeatureEnabled( - FeatureToggles.AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING, - ) - TangemLogger.i("[TangemPay][HWO] Feature toggle enabled=$isHotWalletOnboardingEnabled") - val afterEmptyRoute: AppRoute = if (isHotWalletOnboardingEnabled) { - val tangemPayHotWalletOnboardingDeepLink = withTimeoutOrNull(2.seconds) { - appsFlyerReferralParamsHandler.waitForDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) - } - TangemLogger.i("[TangemPay][HWO] Deep link present=${tangemPayHotWalletOnboardingDeepLink != null}") - if (tangemPayHotWalletOnboardingDeepLink != null) { - AppRoute.TangemPayHotWalletOnboarding - } else { - getDefaultRoute() - } - } else { - getDefaultRoute() - } + val afterEmptyRoute = resolveAppsFlyerOnboardingRoute() + ?: AppRoute.Home(launchMode = launchMode) val shouldAskPushPermission = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrNull() ?: return afterEmptyRoute @@ -246,16 +231,41 @@ internal class DefaultRoutingComponent @AssistedInject constructor( } } - private suspend fun getDefaultRoute(): AppRoute { - val isHideStoriesForReferralEnabled = featureTogglesManager.isFeatureEnabled( + private suspend fun resolveAppsFlyerOnboardingRoute(): AppRoute? = coroutineScope { + val tangemPayRoute = async { resolveTangemPayHotWalletOnboardingRoute() } + val referralRoute = async { resolveReferralRoute() } + tangemPayRoute.await() ?: referralRoute.await() + } + + private suspend fun resolveTangemPayHotWalletOnboardingRoute(): AppRoute? { + val isEnabled = featureTogglesManager.isFeatureEnabled( + FeatureToggles.AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING, + ) + TangemLogger.i("[TangemPay][HWO] Feature toggle enabled=$isEnabled") + if (!isEnabled) return null + + val deepLink = awaitAppsFlyerDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) + TangemLogger.i("[TangemPay][HWO] Deep link present=${deepLink != null}") + return if (deepLink != null) AppRoute.TangemPayHotWalletOnboarding else null + } + + private suspend fun resolveReferralRoute(): AppRoute? { + val isEnabled = featureTogglesManager.isFeatureEnabled( FeatureToggles.TWI_1512_HIDE_STORIES_FOR_REFERRAL_ENABLED, ) - // Referral users skip the Home stories screen and land directly on the - // mobile wallet creation flow. - return if (isHideStoriesForReferralEnabled && shouldShowMobileWalletPromoUseCase()) { + if (!isEnabled) return null + + val referralDeepLink = awaitAppsFlyerDeeplink(AppsFlyerDeeplinkSource.Referral) + return if (referralDeepLink != null) { AppRoute.CreateWalletStart(mode = AppRoute.CreateWalletStart.Mode.HotWallet) } else { - AppRoute.Home(launchMode = launchMode) + null + } + } + + private suspend fun awaitAppsFlyerDeeplink(source: AppsFlyerDeeplinkSource): String? { + return withTimeoutOrNull(2.seconds) { + appsFlyerReferralParamsHandler.waitForDeeplink(source) } } diff --git a/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandlerTest.kt b/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandlerTest.kt index 385abb101c..206f23f70a 100644 --- a/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandlerTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandlerTest.kt @@ -5,9 +5,7 @@ import com.google.common.truth.Truth.assertThat import com.tangem.datasource.local.appsflyer.AppsFlyerDeeplinkSource import com.tangem.datasource.local.appsflyer.AppsFlyerStore import com.tangem.domain.wallets.models.AppsFlyerConversionData -import com.tangem.feature.referral.domain.SetShouldShowMobileWalletPromoUseCase import com.tangem.test.core.ProvideTestModels -import arrow.core.right import com.tangem.test.core.TestAppCoroutineScope import io.mockk.clearMocks import io.mockk.coEvery @@ -28,13 +26,9 @@ import org.junit.jupiter.params.ParameterizedTest class AppsFlyerReferralParamsHandlerTest { private val appsFlyerStore: AppsFlyerStore = mockk(relaxUnitFun = true) - private val setShouldShowMobileWalletPromoUseCase: SetShouldShowMobileWalletPromoUseCase = mockk { - coEvery { this@mockk.invoke(true) } returns Unit.right() - } private val handler = AppsFlyerReferralParamsHandler( appsFlyerStore = appsFlyerStore, coroutineScope = TestAppCoroutineScope(), - setShouldShowMobileWalletPromoUseCase = setShouldShowMobileWalletPromoUseCase, ) @AfterEach @@ -175,7 +169,6 @@ class AppsFlyerReferralParamsHandlerTest { private val localHandler = AppsFlyerReferralParamsHandler( appsFlyerStore = localStore, coroutineScope = TestAppCoroutineScope(), - setShouldShowMobileWalletPromoUseCase = mockk { coEvery { this@mockk.invoke(true) } returns Unit.right() }, ) @Test @@ -240,6 +233,93 @@ class AppsFlyerReferralParamsHandlerTest { } } + @Nested + inner class WaitForReferralDeeplink { + + private val localStore: AppsFlyerStore = mockk(relaxUnitFun = true) + private val localHandler = AppsFlyerReferralParamsHandler( + appsFlyerStore = localStore, + coroutineScope = TestAppCoroutineScope(), + ) + + @Test + fun `GIVEN cached referral deeplink WHEN waitForDeeplink THEN returns cached value`() = runTest { + // GIVEN + coEvery { localStore.getDeeplink(AppsFlyerDeeplinkSource.Referral) } returns "referral" + + // WHEN + val result = localHandler.waitForDeeplink(AppsFlyerDeeplinkSource.Referral) + + // THEN + assertThat(result).isEqualTo("referral") + } + + @Test + fun `GIVEN no cache and referral deeplink WHEN handleDeeplink then waitForDeeplink THEN returns referral value`() = + runTest { + // GIVEN + coEvery { localStore.getDeeplink(AppsFlyerDeeplinkSource.Referral) } returns null + val deepLink = mockk { + every { deepLinkValue } returns "referral" + every { getStringValue(any()) } returns SUCCESS_REFCODE + } + + // WHEN + localHandler.handleDeeplink(deepLink) + val result = localHandler.waitForDeeplink(AppsFlyerDeeplinkSource.Referral) + + // THEN + assertThat(result).isEqualTo("referral") + coVerify { localStore.storeDeeplink(AppsFlyerDeeplinkSource.Referral, "referral") } + } + + @Test + fun `GIVEN no cache and non-referral deeplink WHEN handleDeeplink then waitForDeeplink THEN returns null`() = + runTest { + // GIVEN + coEvery { localStore.getDeeplink(AppsFlyerDeeplinkSource.Referral) } returns null + val deepLink = mockk { + every { deepLinkValue } returns "tpay_mobileonboard" + every { getStringValue(any()) } returns null + } + + // WHEN + localHandler.handleDeeplink(deepLink) + val result = localHandler.waitForDeeplink(AppsFlyerDeeplinkSource.Referral) + + // THEN + assertThat(result).isNull() + coVerify(inverse = true) { localStore.storeDeeplink(AppsFlyerDeeplinkSource.Referral, any()) } + } + + @Test + fun `GIVEN no cache WHEN handleNoDeeplink then waitForDeeplink THEN returns null`() = runTest { + // GIVEN + coEvery { localStore.getDeeplink(AppsFlyerDeeplinkSource.Referral) } returns null + + // WHEN + localHandler.handleNoDeeplink() + val result = localHandler.waitForDeeplink(AppsFlyerDeeplinkSource.Referral) + + // THEN + assertThat(result).isNull() + } + + @Test + fun `GIVEN deeplink stored during wait WHEN waitForDeeplink THEN returns stored value`() = runTest { + // GIVEN cache is empty on the initial read but populated (e.g. from conversion-data + // handling) by the time we re-check after awaiting the deferred + coEvery { localStore.getDeeplink(AppsFlyerDeeplinkSource.Referral) } returns null andThen "referral" + + // WHEN the deferred resolves without a matching value (UDL reported no deep link) + localHandler.handleNoDeeplink() + val result = localHandler.waitForDeeplink(AppsFlyerDeeplinkSource.Referral) + + // THEN the value persisted during the wait is preferred + assertThat(result).isEqualTo("referral") + } + } + private companion object Companion { const val SUCCESS_REFCODE = "valid_refcode" const val SUCCESS_CAMPAIGN = "valid_campaign" diff --git a/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepositoryTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepositoryTest.kt index 08b434e498..6593737113 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepositoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepositoryTest.kt @@ -7,13 +7,13 @@ import com.tangem.common.test.domain.wallet.MockUserWalletFactory import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.utils.TrackingContextProxy import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.appsflyer.usecase.ClearAppsFlyerDeeplinkUseCase import com.tangem.domain.common.wallets.UserWalletSelectedHandler import com.tangem.domain.hotwallet.repository.HotWalletRepository import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository import com.tangem.domain.wallets.hot.HotWalletPasswordRequester -import com.tangem.feature.referral.domain.MobileWalletPromoRepository import com.tangem.hot.sdk.TangemHotSdk import com.tangem.sdk.api.TangemSdkManager import com.tangem.utils.Provider @@ -45,7 +45,7 @@ internal class DefaultUserWalletsListRepositoryTest { private val trackingContextProxy: TrackingContextProxy = mockk(relaxed = true) private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true) private val hotWalletRepository: HotWalletRepository = mockk(relaxed = true) - private val mobileWalletPromoRepository: MobileWalletPromoRepository = mockk(relaxed = true) + private val clearAppsFlyerDeeplinkUseCase: ClearAppsFlyerDeeplinkUseCase = mockk(relaxed = true) private val userWalletSelectedHandler: UserWalletSelectedHandler = mockk(relaxed = true) private val walletA = MockUserWalletFactory.create().copy(walletId = UserWalletId("0011"), name = "Wallet A") @@ -61,7 +61,7 @@ internal class DefaultUserWalletsListRepositoryTest { selectedUserWalletRepository, userWalletEncryptionKeysRepository, trackingContextProxy, - mobileWalletPromoRepository, + clearAppsFlyerDeeplinkUseCase, userWalletSelectedHandler, ) @@ -82,7 +82,7 @@ internal class DefaultUserWalletsListRepositoryTest { trackingContextProxy = trackingContextProxy, analyticsEventHandler = analyticsEventHandler, hotWalletRepository = hotWalletRepository, - mobileWalletPromoRepository = mobileWalletPromoRepository, + clearAppsFlyerDeeplinkUseCase = clearAppsFlyerDeeplinkUseCase, userWalletSelectedHandler = Lazy { userWalletSelectedHandler }, ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/AppsFlyerStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/AppsFlyerStore.kt index 06f57bada6..b4c8e2018d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/AppsFlyerStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/appsflyer/AppsFlyerStore.kt @@ -23,9 +23,11 @@ interface AppsFlyerStore { enum class AppsFlyerDeeplinkSource { TangemPayHotWalletOnboarding, + Referral, ; fun toStoreKey() = when (this) { TangemPayHotWalletOnboarding -> "tangem_pay_hot_wallet_onboarding" + Referral -> "referral" } } \ No newline at end of file diff --git a/data/appsflyer/src/main/java/com/tangem/data/appsflyer/DefaultAppsFlyerRepository.kt b/data/appsflyer/src/main/java/com/tangem/data/appsflyer/DefaultAppsFlyerRepository.kt index 0e9c5adbcc..680731d723 100644 --- a/data/appsflyer/src/main/java/com/tangem/data/appsflyer/DefaultAppsFlyerRepository.kt +++ b/data/appsflyer/src/main/java/com/tangem/data/appsflyer/DefaultAppsFlyerRepository.kt @@ -10,11 +10,16 @@ internal class DefaultAppsFlyerRepository @Inject constructor( private val appsFlyerStore: AppsFlyerStore, ) : AppsFlyerRepository { + override suspend fun getDeeplink(source: AppsFlyerDeeplinkSource): String? { + return appsFlyerStore.getDeeplink(source.toStoreSource()) + } + override suspend fun clearDeeplink(source: AppsFlyerDeeplinkSource) { appsFlyerStore.clearDeeplink(source.toStoreSource()) } private fun AppsFlyerDeeplinkSource.toStoreSource() = when (this) { AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding -> StoreDeeplinkSource.TangemPayHotWalletOnboarding + AppsFlyerDeeplinkSource.Referral -> StoreDeeplinkSource.Referral } } \ No newline at end of file diff --git a/data/appsflyer/src/main/java/com/tangem/data/appsflyer/di/AppsFlyerDataModule.kt b/data/appsflyer/src/main/java/com/tangem/data/appsflyer/di/AppsFlyerDataModule.kt index 27bcc1077f..9e23a23f3e 100644 --- a/data/appsflyer/src/main/java/com/tangem/data/appsflyer/di/AppsFlyerDataModule.kt +++ b/data/appsflyer/src/main/java/com/tangem/data/appsflyer/di/AppsFlyerDataModule.kt @@ -3,6 +3,7 @@ package com.tangem.data.appsflyer.di import com.tangem.data.appsflyer.DefaultAppsFlyerRepository import com.tangem.domain.appsflyer.repository.AppsFlyerRepository import com.tangem.domain.appsflyer.usecase.ClearAppsFlyerDeeplinkUseCase +import com.tangem.domain.appsflyer.usecase.IsReferralInstallUseCase import dagger.Binds import dagger.Module import dagger.Provides @@ -26,5 +27,10 @@ internal interface AppsFlyerDataModule { ): ClearAppsFlyerDeeplinkUseCase { return ClearAppsFlyerDeeplinkUseCase(appsFlyerRepository) } + + @Provides + fun provideIsReferralInstallUseCase(appsFlyerRepository: AppsFlyerRepository): IsReferralInstallUseCase { + return IsReferralInstallUseCase(appsFlyerRepository) + } } } \ No newline at end of file diff --git a/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/AppsFlyerDeeplinkSource.kt b/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/AppsFlyerDeeplinkSource.kt index 07eca402ff..d516a1ef10 100644 --- a/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/AppsFlyerDeeplinkSource.kt +++ b/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/AppsFlyerDeeplinkSource.kt @@ -2,4 +2,5 @@ package com.tangem.domain.appsflyer enum class AppsFlyerDeeplinkSource { TangemPayHotWalletOnboarding, + Referral, } \ No newline at end of file diff --git a/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/repository/AppsFlyerRepository.kt b/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/repository/AppsFlyerRepository.kt index 911eba519c..3ed7bde4d3 100644 --- a/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/repository/AppsFlyerRepository.kt +++ b/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/repository/AppsFlyerRepository.kt @@ -4,5 +4,7 @@ import com.tangem.domain.appsflyer.AppsFlyerDeeplinkSource interface AppsFlyerRepository { + suspend fun getDeeplink(source: AppsFlyerDeeplinkSource): String? + suspend fun clearDeeplink(source: AppsFlyerDeeplinkSource) } \ No newline at end of file diff --git a/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/usecase/IsReferralInstallUseCase.kt b/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/usecase/IsReferralInstallUseCase.kt new file mode 100644 index 0000000000..398ada8802 --- /dev/null +++ b/domain/appsflyer/src/main/kotlin/com/tangem/domain/appsflyer/usecase/IsReferralInstallUseCase.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.appsflyer.usecase + +import com.tangem.domain.appsflyer.AppsFlyerDeeplinkSource +import com.tangem.domain.appsflyer.repository.AppsFlyerRepository + +/** + * Returns whether the app install was attributed to an AppsFlyer referral deep link. + */ +class IsReferralInstallUseCase( + private val appsFlyerRepository: AppsFlyerRepository, +) { + suspend operator fun invoke(): Boolean { + return appsFlyerRepository.getDeeplink(AppsFlyerDeeplinkSource.Referral) != null + } +} \ No newline at end of file diff --git a/features/home/impl/build.gradle.kts b/features/home/impl/build.gradle.kts index afa7941725..2cd201d377 100644 --- a/features/home/impl/build.gradle.kts +++ b/features/home/impl/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation(projects.common.routing) /** Domain */ + implementation(projects.domain.appsflyer) implementation(projects.domain.common) implementation(projects.domain.models) implementation(projects.domain.core) @@ -41,9 +42,6 @@ dependencies { implementation(projects.domain.feedback.models) implementation(projects.domain.referral) - /** Referral */ - implementation(projects.features.referral.domain) - /** AndroidX libraries */ implementation(deps.androidx.activity.compose) implementation(deps.lifecycle.runtime.ktx) diff --git a/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt b/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt index 147e146fa6..ff2f441713 100644 --- a/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt +++ b/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt @@ -18,6 +18,7 @@ import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.message.dialog.Dialogs +import com.tangem.domain.appsflyer.usecase.IsReferralInstallUseCase import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.analytics.IntroductionProcess import com.tangem.domain.card.analytics.Shop @@ -32,7 +33,6 @@ import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase import com.tangem.domain.wallets.usecase.SaveWalletUseCase -import com.tangem.feature.referral.domain.ShouldShowMobileWalletPromoUseCase import com.tangem.features.home.api.HomeComponent import com.tangem.features.home.impl.ui.state.HomeUM import com.tangem.features.home.impl.ui.state.Stories @@ -66,7 +66,7 @@ internal class HomeModel @Inject constructor( private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase, private val urlOpener: UrlOpener, private val userWalletsListRepository: UserWalletsListRepository, - private val shouldShowMobileWalletPromoUseCase: ShouldShowMobileWalletPromoUseCase, + private val isReferralInstallUseCase: IsReferralInstallUseCase, @GlobalUiMessageSender private val uiMessageSender: UiMessageSender, ) : Model() { @@ -136,7 +136,7 @@ internal class HomeModel @Inject constructor( private fun onGetStartedClick() { debouncer.debounce(modelScope) { modelScope.launch { - val mode = if (shouldShowMobileWalletPromoUseCase()) { + val mode = if (isReferralInstallUseCase()) { AppRoute.CreateWalletStart.Mode.HotWallet } else { AppRoute.CreateWalletStart.Mode.ColdWallet diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/data/DefaultMobileWalletPromoRepository.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/data/DefaultMobileWalletPromoRepository.kt deleted file mode 100644 index 1fd76e4838..0000000000 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/data/DefaultMobileWalletPromoRepository.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.feature.referral.data - -import androidx.datastore.preferences.core.booleanPreferencesKey -import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.utils.getSyncOrDefault -import com.tangem.feature.referral.domain.MobileWalletPromoRepository -import javax.inject.Inject - -internal class DefaultMobileWalletPromoRepository @Inject constructor( - private val appPreferencesStore: AppPreferencesStore, -) : MobileWalletPromoRepository { - - override suspend fun shouldShowMobileWalletPromo(): Boolean { - return appPreferencesStore.getSyncOrDefault(key = SHOULD_SHOW_MOBILE_WALLET_PROMO_KEY, default = false) - } - - override suspend fun setShouldShowMobileWalletPromo(shouldShowPromo: Boolean) { - appPreferencesStore.editData { preferences -> - preferences[SHOULD_SHOW_MOBILE_WALLET_PROMO_KEY] = shouldShowPromo - } - } - - private companion object { - val SHOULD_SHOW_MOBILE_WALLET_PROMO_KEY = booleanPreferencesKey("should_show_mobile_wallet_promo") - } -} \ No newline at end of file diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt index af0876c251..25f02ed82b 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt @@ -2,13 +2,10 @@ package com.tangem.feature.referral.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.feature.referral.converters.ReferralConverter -import com.tangem.feature.referral.data.DefaultMobileWalletPromoRepository import com.tangem.feature.referral.data.ExternalReferralRepository import com.tangem.feature.referral.data.ReferralRepositoryImpl -import com.tangem.feature.referral.domain.MobileWalletPromoRepository import com.tangem.feature.referral.domain.ReferralRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -56,11 +53,4 @@ class ReferralRepositoryModule { excludedBlockchains = excludedBlockchains, ) } - - @Provides - @Singleton - fun provideMobileWalletPromoRepository(appPreferencesStore: AppPreferencesStore): MobileWalletPromoRepository = - DefaultMobileWalletPromoRepository( - appPreferencesStore = appPreferencesStore, - ) } \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/MobileWalletPromoRepository.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/MobileWalletPromoRepository.kt deleted file mode 100644 index 95cba17c9d..0000000000 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/MobileWalletPromoRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.feature.referral.domain - -interface MobileWalletPromoRepository { - - suspend fun shouldShowMobileWalletPromo(): Boolean - - suspend fun setShouldShowMobileWalletPromo(shouldShowPromo: Boolean) -} \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/SetShouldShowMobileWalletPromoUseCase.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/SetShouldShowMobileWalletPromoUseCase.kt deleted file mode 100644 index 335669eabf..0000000000 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/SetShouldShowMobileWalletPromoUseCase.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.feature.referral.domain - -import arrow.core.Either -import com.tangem.domain.common.wallets.UserWalletsListRepository -import javax.inject.Inject - -class SetShouldShowMobileWalletPromoUseCase @Inject constructor( - private val mobileWalletPromoRepository: MobileWalletPromoRepository, - private val userWalletsListRepository: UserWalletsListRepository, -) { - - suspend operator fun invoke(shouldShowPromo: Boolean): Either = Either.catch { - val wallets = userWalletsListRepository.userWallets.value - if (wallets.isNullOrEmpty()) { - mobileWalletPromoRepository.setShouldShowMobileWalletPromo(shouldShowPromo) - } - } -} \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ShouldShowMobileWalletPromoUseCase.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ShouldShowMobileWalletPromoUseCase.kt deleted file mode 100644 index 09a3b358cc..0000000000 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ShouldShowMobileWalletPromoUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.feature.referral.domain - -import javax.inject.Inject - -class ShouldShowMobileWalletPromoUseCase @Inject constructor( - private val mobileWalletPromoRepository: MobileWalletPromoRepository, -) { - - suspend operator fun invoke(): Boolean { - return mobileWalletPromoRepository.shouldShowMobileWalletPromo() - } -} \ No newline at end of file