diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 3349a0933d..e998374c66 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -170,7 +170,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { private val onActivityResultCallbacks = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { - TangemLogger.i("onCreate") + TangemLogger.i("onCreate: data=${intent?.data}, extras=${intent?.extras?.keySet()}") // We need to call it before onCreate to prevent unnecessary activity recreation installAppTheme() 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 index b43b358a5c..796b6804ef 100644 --- 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 @@ -14,12 +14,14 @@ class AppsFlyerDeepLinkListener @Inject constructor( override fun onDeepLinking(p0: DeepLinkResult) { when (p0.status) { DeepLinkResult.Status.FOUND -> { - referralParamsHandler.handle(deepLink = p0.deepLink) + referralParamsHandler.handleDeeplink(deepLink = p0.deepLink) } DeepLinkResult.Status.NOT_FOUND -> { + referralParamsHandler.handleNoDeeplink() TangemLogger.i("No deep link found") } DeepLinkResult.Status.ERROR -> { + referralParamsHandler.handleNoDeeplink() TangemLogger.e("Deep link error: ${p0.error}") } } 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 25980d4028..70a24bee80 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 @@ -7,6 +7,7 @@ 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 @@ -23,14 +24,7 @@ class AppsFlyerReferralParamsHandler @Inject constructor( ) { private val mutex = Mutex() - - fun handle(deepLink: DeepLink) { - handle( - deepLinkValue = deepLink.deepLinkValue, - deepLinkSub1 = deepLink.getStringValue(DEEP_LINK_SUB_1), - deepLinkSub2 = deepLink.getStringValue(DEEP_LINK_SUB_2), - ) - } + private val deepLinkDeferred = CompletableDeferred() fun handle(params: Map) { handle( @@ -40,6 +34,31 @@ class AppsFlyerReferralParamsHandler @Inject constructor( ) } + fun handleDeeplink(deepLink: DeepLink) { + handle( + deepLinkValue = deepLink.deepLinkValue, + deepLinkSub1 = deepLink.getStringValue(DEEP_LINK_SUB_1), + deepLinkSub2 = deepLink.getStringValue(DEEP_LINK_SUB_2), + ) + deepLinkDeferred.complete(deepLink.deepLinkValue) + } + + fun handleNoDeeplink() { + deepLinkDeferred.complete(null) + } + + 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 + } + } + private fun handle(deepLinkValue: String?, deepLinkSub1: String?, deepLinkSub2: String?) { TangemLogger.i("AppsFlyer deeplink received: value=$deepLinkValue") when (deepLinkValue) { 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 6a7f07951e..6be9860e0c 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 @@ -32,7 +32,6 @@ import com.tangem.core.ui.message.DialogMessage import com.tangem.core.ui.message.EventMessageAction import com.tangem.core.ui.message.SnackbarMessage import com.tangem.datasource.local.appsflyer.AppsFlyerDeeplinkSource -import com.tangem.datasource.local.appsflyer.AppsFlyerStore import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.scan.ScanResponse @@ -54,6 +53,7 @@ import com.tangem.hot.sdk.TangemHotSdk import com.tangem.hot.sdk.android.create import com.tangem.sdk.api.BackupServiceHolder import com.tangem.tap.common.SnackbarHandler +import com.tangem.tap.common.analytics.appsflyer.AppsFlyerReferralParamsHandler import com.tangem.tap.features.hot.TangemHotSDKProxy import com.tangem.tap.features.root.RootDetectedWarningComponent import com.tangem.tap.features.scanfails.ScanFailsComponent @@ -70,6 +70,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull +import kotlin.time.Duration.Companion.seconds @Suppress("LongParameterList", "LargeClass") internal class DefaultRoutingComponent @AssistedInject constructor( @@ -88,7 +90,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val userWalletsListRepository: UserWalletsListRepository, private val cardRepository: CardRepository, private val onboardingRepository: OnboardingRepository, - private val appsFlyerStore: AppsFlyerStore, + private val appsFlyerReferralParamsHandler: AppsFlyerReferralParamsHandler, private val trackingContextProxy: TrackingContextProxy, private val scanFailsComponentFactory: ScanFailsComponent.Factory, private val scanFailsRequesterProxy: ScanFailsRequesterProxy, @@ -212,11 +214,10 @@ internal class DefaultRoutingComponent @AssistedInject constructor( FeatureToggles.AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING, ) TangemLogger.i("[TangemPay][HWO] Feature toggle enabled=$isHotWalletOnboardingEnabled") - if (isHotWalletOnboardingEnabled) { - val tangemPayHotWalletOnboardingDeepLink = appsFlyerStore.getDeeplink( - AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding, - ) + val tangemPayHotWalletOnboardingDeepLink = withTimeoutOrNull(2.seconds) { + appsFlyerReferralParamsHandler.waitForDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) + } TangemLogger.i("[TangemPay][HWO] Deep link present=${tangemPayHotWalletOnboardingDeepLink != null}") if (tangemPayHotWalletOnboardingDeepLink != null) { val hotWalletRoute = AppRoute.TangemPayHotWalletOnboarding 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 index 20f770e186..2a3e53fea0 100644 --- 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 @@ -29,15 +29,19 @@ class AppsFlyerDeepLinkListenerTest { @ProvideTestModels fun onDeepLinking(model: OnDeepLinkingModel) = runTest { if (model.shouldHandle) { - every { referralParamsHandler.handle(deepLink = model.deepLinkResult.deepLink) } just Runs + every { referralParamsHandler.handleDeeplink(deepLink = model.deepLinkResult.deepLink) } just Runs + } else { + every { referralParamsHandler.handleNoDeeplink() } just Runs } listener.onDeepLinking(p0 = model.deepLinkResult) if (model.shouldHandle) { - coVerify { referralParamsHandler.handle(deepLink = model.deepLinkResult.deepLink) } + coVerify { referralParamsHandler.handleDeeplink(deepLink = model.deepLinkResult.deepLink) } + verify(inverse = true) { referralParamsHandler.handleNoDeeplink() } } else { - coVerify(inverse = true) { referralParamsHandler.handle(deepLink = any()) } + coVerify(inverse = true) { referralParamsHandler.handleDeeplink(deepLink = any()) } + verify { referralParamsHandler.handleNoDeeplink() } } } 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 42ca5e34cc..c2a6c12ac1 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 @@ -1,6 +1,8 @@ package com.tangem.tap.common.analytics.appsflyer import com.appsflyer.deeplink.DeepLink +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 @@ -15,6 +17,7 @@ import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.params.ParameterizedTest @@ -46,7 +49,7 @@ class AppsFlyerReferralParamsHandlerTest { @ParameterizedTest @ProvideTestModels fun handle(model: HandleDeepLinkModel) = runTest { - handler.handle(deepLink = model.deepLink) + handler.handleDeeplink(deepLink = model.deepLink) if (model.shouldStore) { val value = AppsFlyerConversionData(refcode = SUCCESS_REFCODE, campaign = SUCCESS_CAMPAIGN) @@ -165,6 +168,78 @@ class AppsFlyerReferralParamsHandlerTest { data class HandleParamsModel(val params: Map, val shouldStore: Boolean) + @Nested + inner class WaitForDeeplink { + + private val localStore: AppsFlyerStore = mockk(relaxUnitFun = true) + private val localHandler = AppsFlyerReferralParamsHandler( + appsFlyerStore = localStore, + coroutineScope = TestAppCoroutineScope(), + setShouldShowMobileWalletPromoUseCase = mockk { coEvery { this@mockk.invoke(true) } returns Unit.right() }, + ) + + @Test + fun `GIVEN cached deeplink WHEN waitForDeeplink THEN returns cached value`() = runTest { + // GIVEN + coEvery { + localStore.getDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) + } returns "tpay_mobileonboard" + + // WHEN + val result = localHandler.waitForDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) + + // THEN + assertThat(result).isEqualTo("tpay_mobileonboard") + } + + @Test + fun `GIVEN no cache and matching deeplink WHEN handleDeeplink then waitForDeeplink THEN returns deeplink value`() = runTest { + // GIVEN + coEvery { localStore.getDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) } returns null + val deepLink = mockk { + every { deepLinkValue } returns "tpay_mobileonboard" + every { getStringValue(any()) } returns null + } + + // WHEN + localHandler.handleDeeplink(deepLink) + val result = localHandler.waitForDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) + + // THEN + assertThat(result).isEqualTo("tpay_mobileonboard") + } + + @Test + fun `GIVEN no cache and non-matching deeplink WHEN handleDeeplink then waitForDeeplink THEN returns null`() = runTest { + // GIVEN + coEvery { localStore.getDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) } returns null + val deepLink = mockk { + every { deepLinkValue } returns "referral" + every { getStringValue(any()) } returns null + } + + // WHEN + localHandler.handleDeeplink(deepLink) + val result = localHandler.waitForDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) + + // THEN + assertThat(result).isNull() + } + + @Test + fun `GIVEN no cache WHEN handleNoDeeplink then waitForDeeplink THEN returns null`() = runTest { + // GIVEN + coEvery { localStore.getDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) } returns null + + // WHEN + localHandler.handleNoDeeplink() + val result = localHandler.waitForDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) + + // THEN + assertThat(result).isNull() + } + } + private companion object Companion { const val SUCCESS_REFCODE = "valid_refcode" const val SUCCESS_CAMPAIGN = "valid_campaign"