diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt index 504d24280b5f..20fcdbd1433a 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt @@ -621,7 +621,6 @@ class BrowserTabViewModelTest { subscriptions = subscriptions, sslCertificatesFeature = mockSSLCertificatesFeature, bypassedSSLCertificatesRepository = mockBypassedSSLCertificatesRepository, - extendedOnboardingFeatureToggles = mockExtendedOnboardingFeatureToggles, userBrowserProperties = mockUserBrowserProperties, history = mockNavigationHistory, ) @@ -5456,6 +5455,7 @@ class BrowserTabViewModelTest { } private suspend fun givenFireButtonPulsing() { + whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle) whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING) dismissedCtaDaoChannel.send(listOf(DismissedCta(CtaId.DAX_DIALOG_TRACKERS_FOUND))) } diff --git a/app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaViewModelTest.kt b/app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaViewModelTest.kt index 66e78111292a..c41cafba0861 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaViewModelTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaViewModelTest.kt @@ -130,6 +130,7 @@ class CtaViewModelTest { private lateinit var testee: CtaViewModel val context: Context = InstrumentationRegistry.getInstrumentation().targetContext + val mockEnabledToggle: Toggle = mock { on { it.isEnabled() } doReturn true } @Before fun before() { @@ -138,8 +139,8 @@ class CtaViewModelTest { .allowMainThreadQueries() .build() - val mockToggle: Toggle = mock { on { it.isEnabled() } doReturn true } - whenever(mockExtendedOnboardingFeatureToggles.aestheticUpdates()).thenReturn(mockToggle) + val mockDisabledToggle: Toggle = mock { on { it.isEnabled() } doReturn false } + whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockDisabledToggle) whenever(mockAppInstallStore.installTimestamp).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)) whenever(mockUserAllowListRepository.isDomainInUserAllowList(any())).thenReturn(false) whenever(mockDismissedCtaDao.dismissedCtas()).thenReturn(db.dismissedCtaDao().dismissedCtas()) @@ -718,6 +719,48 @@ class CtaViewModelTest { verify(mockDismissedCtaDao).insert(DismissedCta(CtaId.DAX_END)) } + @Test + fun givenNoBrowserCtasExperimentWhenRefreshCtaOnHomeTabThenSkipOnboardingHomeCtas() = runTest { + givenDaxOnboardingActive() + whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle) + whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(false) + whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true) + + val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false) + assertFalse(value is DaxBubbleCta.DaxIntroSearchOptionsCta) + } + + @Test + fun givenNoBrowserCtasExperimentWhenFirstRefreshCtaOnHomeTabThenDontReturnWidgetCta() = runTest { + givenDaxOnboardingActive() + whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle) + whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(false) + + val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false) + assertFalse(value is HomePanelCta.AddWidgetAuto) + } + + @Test + fun givenNoBrowserCtasExperimentWhenRefreshCtaOnHomeTabAndIntroShownThenReturnWidgetCta() = runTest { + givenDaxOnboardingActive() + whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle) + whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(true) + whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true) + + val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false) + assertTrue(value is HomePanelCta.AddWidgetAuto) + } + + @Test + fun givenNoBrowserCtasExperimentWhenRefreshCtaWhileBrowsingThenReturnNull() = runTest { + givenDaxOnboardingActive() + whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle) + val site = site(url = "http://www.facebook.com", entity = TestEntity("Facebook", "Facebook", 9.0)) + + val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = site) + assertNull(value) + } + private suspend fun givenDaxOnboardingActive() { whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING) } diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt index da9bef0946bc..6bca76b3e84c 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt @@ -125,7 +125,6 @@ import com.duckduckgo.app.global.model.domainMatchesUrl import com.duckduckgo.app.location.GeoLocationPermissions import com.duckduckgo.app.location.data.LocationPermissionType import com.duckduckgo.app.location.data.LocationPermissionsRepository -import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingFeatureToggles import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.OnboardingExperimentPixel import com.duckduckgo.app.pixels.AppPixelName import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_BANNER_DISMISSED @@ -264,7 +263,6 @@ class BrowserTabViewModel @Inject constructor( private val privacyProtectionsToggleUsageListener: PrivacyProtectionsToggleUsageListener, private val privacyProtectionsPopupExperimentExternalPixels: PrivacyProtectionsPopupExperimentExternalPixels, private val faviconsFetchingPrompt: FaviconsFetchingPrompt, - private val extendedOnboardingFeatureToggles: ExtendedOnboardingFeatureToggles, private val subscriptions: Subscriptions, private val sslCertificatesFeature: SSLCertificatesFeature, private val bypassedSSLCertificatesRepository: BypassedSSLCertificatesRepository, @@ -3174,12 +3172,10 @@ class BrowserTabViewModel @Inject constructor( return when (onboardingCta) { is OnboardingDaxDialogCta.DaxSerpCta -> { viewModelScope.launch { - if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) { - val cta = withContext(dispatchers.io()) { ctaViewModel.getSiteSuggestionsDialogCta() } - ctaViewState.value = currentCtaViewState().copy(cta = cta) - if (cta == null) { - command.value = HideOnboardingDaxDialog(onboardingCta) - } + val cta = withContext(dispatchers.io()) { ctaViewModel.getSiteSuggestionsDialogCta() } + ctaViewState.value = currentCtaViewState().copy(cta = cta) + if (cta == null) { + command.value = HideOnboardingDaxDialog(onboardingCta) } } null @@ -3193,12 +3189,10 @@ class BrowserTabViewModel @Inject constructor( browserViewState.value = currentBrowserViewState().copy(showPrivacyShield = HighlightableButton.Visible(highlighted = false)) } viewModelScope.launch { - if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) { - val cta = withContext(dispatchers.io()) { ctaViewModel.getFireDialogCta() } - ctaViewState.value = currentCtaViewState().copy(cta = cta) - if (cta == null) { - command.value = HideOnboardingDaxDialog(onboardingCta) - } + val cta = withContext(dispatchers.io()) { ctaViewModel.getFireDialogCta() } + ctaViewState.value = currentCtaViewState().copy(cta = cta) + if (cta == null) { + command.value = HideOnboardingDaxDialog(onboardingCta) } } null @@ -3218,22 +3212,20 @@ class BrowserTabViewModel @Inject constructor( } fun onFireMenuSelected() { - if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) { - val cta = currentCtaViewState().cta - if (cta is OnboardingDaxDialogCta.DaxFireButtonCta) { - onUserDismissedCta() - command.value = HideOnboardingDaxDialog(cta) - } - if (currentBrowserViewState().fireButton.isHighlighted()) { - viewModelScope.launch { - ctaViewModel.dismissPulseAnimation() - } + val cta = currentCtaViewState().cta + if (cta is OnboardingDaxDialogCta.DaxFireButtonCta) { + onUserDismissedCta() + command.value = HideOnboardingDaxDialog(cta) + } + if (currentBrowserViewState().fireButton.isHighlighted()) { + viewModelScope.launch { + ctaViewModel.dismissPulseAnimation() } } } fun onPrivacyShieldSelected() { - if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled() && currentBrowserViewState().showPrivacyShield.isHighlighted()) { + if (currentBrowserViewState().showPrivacyShield.isHighlighted()) { browserViewState.value = currentBrowserViewState().copy(showPrivacyShield = HighlightableButton.Visible(highlighted = false)) pixel.fire( pixel = PrivacyDashboardPixels.PRIVACY_DASHBOARD_FIRST_TIME_OPENED, diff --git a/app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt b/app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt index fd1e9df22ea8..5c3709529e33 100644 --- a/app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt @@ -195,17 +195,20 @@ class CtaViewModel @Inject constructor( } private suspend fun getHomeCta(): Cta? { - val onboardingEnabled = extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled() return when { - canShowDaxIntroCta() && onboardingEnabled -> { + canShowDaxIntroCta() && extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> { + dismissedCtaDao.insert(DismissedCta(CtaId.DAX_INTRO)) + null + } + canShowDaxIntroCta() && !extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> { DaxBubbleCta.DaxIntroSearchOptionsCta(onboardingStore, appInstallStore) } - canShowDaxIntroVisitSiteCta() && onboardingEnabled -> { + canShowDaxIntroVisitSiteCta() && !extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> { DaxBubbleCta.DaxIntroVisitSiteOptionsCta(onboardingStore, appInstallStore) } - canShowDaxCtaEndOfJourney() && onboardingEnabled -> { + canShowDaxCtaEndOfJourney() && !extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> { DaxBubbleCta.DaxEndCta(onboardingStore, appInstallStore) } @@ -247,10 +250,15 @@ class CtaViewModel @Inject constructor( (daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogSerpShown() || daxDialogTrackersFoundShown()) private suspend fun canShowDaxDialogCta(): Boolean { - if (!daxOnboardingActive() || hideTips()) { - return false + return when { + !daxOnboardingActive() || hideTips() -> false + extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> { + settingsDataStore.hideTips = true + userStageStore.stageCompleted(AppStage.DAX_ONBOARDING) + false + } + else -> true } - return true } @WorkerThread @@ -269,40 +277,39 @@ class CtaViewModel @Inject constructor( if (!canShowDaxDialogCta()) return null - if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) { - // Trackers blocked - if (!daxDialogTrackersFoundShown() && !isSerpUrl(it.url) && it.orderedTrackerBlockedEntities().isNotEmpty()) { - return OnboardingDaxDialogCta.DaxTrackersBlockedCta( - onboardingStore, - appInstallStore, - it.orderedTrackerBlockedEntities(), - ) - } + // Trackers blocked + if (!daxDialogTrackersFoundShown() && !isSerpUrl(it.url) && it.orderedTrackerBlockedEntities().isNotEmpty()) { + return OnboardingDaxDialogCta.DaxTrackersBlockedCta( + onboardingStore, + appInstallStore, + it.orderedTrackerBlockedEntities(), + ) + } - // Is major network - if (it.entity != null) { - it.entity?.let { entity -> - if (!daxDialogNetworkShown() && OnboardingDaxDialogCta.mainTrackerNetworks.contains(entity.displayName)) { - return OnboardingDaxDialogCta.DaxMainNetworkCta(onboardingStore, appInstallStore, entity.displayName, host) - } + // Is major network + if (it.entity != null) { + it.entity?.let { entity -> + if (!daxDialogNetworkShown() && OnboardingDaxDialogCta.mainTrackerNetworks.contains(entity.displayName)) { + return OnboardingDaxDialogCta.DaxMainNetworkCta(onboardingStore, appInstallStore, entity.displayName, host) } } + } - // SERP - if (isSerpUrl(it.url) && !daxDialogSerpShown()) { - return OnboardingDaxDialogCta.DaxSerpCta(onboardingStore, appInstallStore) - } + // SERP + if (isSerpUrl(it.url) && !daxDialogSerpShown()) { + return OnboardingDaxDialogCta.DaxSerpCta(onboardingStore, appInstallStore) + } - // No trackers blocked - if (!isSerpUrl(it.url) && !daxDialogOtherShown() && !daxDialogTrackersFoundShown() && !daxDialogNetworkShown()) { - return OnboardingDaxDialogCta.DaxNoTrackersCta(onboardingStore, appInstallStore) - } + // No trackers blocked + if (!isSerpUrl(it.url) && !daxDialogOtherShown() && !daxDialogTrackersFoundShown() && !daxDialogNetworkShown()) { + return OnboardingDaxDialogCta.DaxNoTrackersCta(onboardingStore, appInstallStore) + } - // End - if (canShowDaxCtaEndOfJourney() && daxDialogFireEducationShown()) { - return OnboardingDaxDialogCta.DaxEndCta(onboardingStore, appInstallStore) - } + // End + if (canShowDaxCtaEndOfJourney() && daxDialogFireEducationShown()) { + return OnboardingDaxDialogCta.DaxEndCta(onboardingStore, appInstallStore) } + return null } } diff --git a/app/src/main/java/com/duckduckgo/app/onboarding/ui/page/extendedonboarding/ExtendedOnboardingFeatureToggles.kt b/app/src/main/java/com/duckduckgo/app/onboarding/ui/page/extendedonboarding/ExtendedOnboardingFeatureToggles.kt index 0c9e66f54672..53c7d5e18175 100644 --- a/app/src/main/java/com/duckduckgo/app/onboarding/ui/page/extendedonboarding/ExtendedOnboardingFeatureToggles.kt +++ b/app/src/main/java/com/duckduckgo/app/onboarding/ui/page/extendedonboarding/ExtendedOnboardingFeatureToggles.kt @@ -19,6 +19,7 @@ package com.duckduckgo.app.onboarding.ui.page.extendedonboarding import com.duckduckgo.anvil.annotations.ContributesRemoteFeature import com.duckduckgo.di.scopes.AppScope import com.duckduckgo.feature.toggles.api.Toggle +import com.duckduckgo.feature.toggles.api.Toggle.Experiment @ContributesRemoteFeature( scope = AppScope::class, @@ -31,4 +32,8 @@ interface ExtendedOnboardingFeatureToggles { @Toggle.DefaultValue(true) fun aestheticUpdates(): Toggle + + @Toggle.DefaultValue(false) + @Experiment + fun noBrowserCtas(): Toggle }