Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Onboarding: Release privacy pro onboarding dialog to all users #5222

Draft
wants to merge 8 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ import com.duckduckgo.app.onboarding.store.AppStage.ESTABLISHED
import com.duckduckgo.app.onboarding.store.OnboardingStore
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingFeatureToggles
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingPixelsPlugin
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.HighlightsOnboardingExperimentManager
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_BANNER_SHOWN
Expand Down Expand Up @@ -209,6 +210,9 @@ import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Disabled
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Enabled
import com.duckduckgo.feature.toggles.api.FakeFeatureToggleFactory
import com.duckduckgo.feature.toggles.api.FakeToggleStore
import com.duckduckgo.feature.toggles.api.FeatureToggle
import com.duckduckgo.feature.toggles.api.FeatureToggles
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.feature.toggles.api.Toggle.State
import com.duckduckgo.history.api.HistoryEntry.VisitedPage
Expand Down Expand Up @@ -463,7 +467,10 @@ class BrowserTabViewModelTest {

private val mockEnabledToggle: Toggle = mock { on { it.isEnabled() } doReturn true }

private val mockDisabledToggle: Toggle = mock { on { it.isEnabled() } doReturn false }
private val mockDisabledToggle: Toggle = mock {
on { it.isEnabled() } doReturn false
on { it.isEnabled(any()) } doReturn false
}

private val mockPrivacyProtectionsPopupManager: PrivacyProtectionsPopupManager = mock()

Expand All @@ -490,6 +497,9 @@ class BrowserTabViewModelTest {
private var fakeAndroidConfigBrowserFeature = FakeFeatureToggleFactory.create(AndroidBrowserConfigFeature::class.java)
private val mockAutocompleteTabsFeature: AutocompleteTabsFeature = mock()
private val fakeCustomHeadersPlugin = FakeCustomHeadersProvider(emptyMap())
private val extendedOnboardingFeatureToggles = FeatureToggles.Builder(FakeToggleStore(), featureName = "extendedOnboarding").build()
.create(ExtendedOnboardingFeatureToggles::class.java)
private val extendedOnboardingPixelsPlugin = ExtendedOnboardingPixelsPlugin(extendedOnboardingFeatureToggles)

@Before
fun before() = runTest {
Expand Down Expand Up @@ -517,6 +527,7 @@ class BrowserTabViewModelTest {
lazyFaviconManager,
)

whenever(mockExtendedOnboardingFeatureToggles.testPrivacyProOnboardingCopyNov24()).thenReturn(mockDisabledToggle)
whenever(mockHighlightsOnboardingExperimentManager.isHighlightsEnabled()).thenReturn(false)
whenever(mockDuckPlayer.observeUserPreferences()).thenReturn(flowOf(UserPreferences(false, Disabled)))
whenever(mockDismissedCtaDao.dismissedCtas()).thenReturn(dismissedCtaDaoChannel.consumeAsFlow())
Expand Down Expand Up @@ -557,6 +568,7 @@ class BrowserTabViewModelTest {
subscriptions = mock(),
duckPlayer = mockDuckPlayer,
highlightsOnboardingExperimentManager = mockHighlightsOnboardingExperimentManager,
extendedOnboardingPixelsPlugin = extendedOnboardingPixelsPlugin,
)

val siteFactory = SiteFactoryImpl(
Expand Down Expand Up @@ -2564,10 +2576,15 @@ class BrowserTabViewModelTest {

@Test
fun whenUserClickedLearnMoreExperimentBubbleCtaButtonThenLaunchPrivacyPro() {
val cta = DaxBubbleCta.DaxPrivacyProCta(mockOnboardingStore, mockAppInstallStore)
val cta = DaxBubbleCta.DaxPrivacyProCta(
mockOnboardingStore,
mockAppInstallStore,
R.string.onboardingPrivacyProDaxDialogTitle,
R.string.onboardingPrivacyProDaxDialogDescription,
)
setCta(cta)
testee.onUserClickCtaOkButton(cta)
assertCommandIssued<Command.LaunchPrivacyPro>()
assertCommandIssued<LaunchPrivacyPro>()
}

@Test
Expand Down Expand Up @@ -6002,6 +6019,7 @@ class BrowserTabViewModelTest {
override suspend fun onToggleOff(origin: PrivacyToggleOrigin) {
toggleOff++
}

override suspend fun onToggleOn(origin: PrivacyToggleOrigin) {
toggleOn++
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.duckduckgo.app.onboarding.store.AppStage
import com.duckduckgo.app.onboarding.store.OnboardingStore
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingFeatureToggles
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingPixelsPlugin
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.HighlightsOnboardingExperimentManager
import com.duckduckgo.app.pixels.AppPixelName.*
import com.duckduckgo.app.privacy.db.UserAllowListRepository
Expand All @@ -57,6 +58,8 @@ import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.DISABLED
import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.ENABLED
import com.duckduckgo.duckplayer.api.DuckPlayer.UserPreferences
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk
import com.duckduckgo.feature.toggles.api.FakeToggleStore
import com.duckduckgo.feature.toggles.api.FeatureToggles
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.subscriptions.api.Subscriptions
import java.util.concurrent.TimeUnit
Expand Down Expand Up @@ -124,6 +127,10 @@ class CtaViewModelTest {
CtaId.DAX_END,
)

private val extendedOnboardingFeatureToggles = FeatureToggles.Builder(FakeToggleStore(), featureName = "extendedOnboarding").build()
.create(ExtendedOnboardingFeatureToggles::class.java)
private val extendedOnboardingPixelsPlugin = ExtendedOnboardingPixelsPlugin(extendedOnboardingFeatureToggles)

private lateinit var testee: CtaViewModel

val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
Expand All @@ -138,7 +145,7 @@ class CtaViewModelTest {

val mockDisabledToggle: Toggle = mock { on { it.isEnabled() } doReturn false }
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockDisabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.privacyProCta()).thenReturn(mockDisabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.testPrivacyProOnboardingCopyNov24()).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())
Expand All @@ -165,6 +172,7 @@ class CtaViewModelTest {
subscriptions = mockSubscriptions,
duckPlayer = mockDuckPlayer,
highlightsOnboardingExperimentManager = mockHighlightsOnboardingExperimentManager,
extendedOnboardingPixelsPlugin = extendedOnboardingPixelsPlugin,
)
}

Expand All @@ -174,26 +182,26 @@ class CtaViewModelTest {
}

@Test
fun whenCtaShownAndCtaIsDaxAndCanNotSendPixelThenPixelIsNotFired() {
fun whenCtaShownAndCtaIsDaxAndCanNotSendPixelThenPixelIsNotFired() = runTest {
testee.onCtaShown(DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore))
verify(mockPixel, never()).fire(eq(SURVEY_CTA_SHOWN), any(), any(), eq(Count))
}

@Test
fun whenCtaShownAndCtaIsDaxAndCanSendPixelThenPixelIsFired() {
fun whenCtaShownAndCtaIsDaxAndCanSendPixelThenPixelIsFired() = runTest {
whenever(mockOnboardingStore.onboardingDialogJourney).thenReturn("s:0")
testee.onCtaShown(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
verify(mockPixel, never()).fire(eq(SURVEY_CTA_SHOWN), any(), any(), eq(Count))
}

@Test
fun whenCtaShownAndCtaIsNotDaxThenPixelIsFired() {
fun whenCtaShownAndCtaIsNotDaxThenPixelIsFired() = runTest {
testee.onCtaShown(HomePanelCta.AddWidgetAuto)
verify(mockPixel).fire(eq(WIDGET_CTA_SHOWN), any(), any(), eq(Count))
}

@Test
fun whenCtaLaunchedPixelIsFired() {
fun whenCtaLaunchedPixelIsFired() = runTest {
testee.onUserClickCtaOkButton(HomePanelCta.AddWidgetAuto)
verify(mockPixel).fire(eq(WIDGET_CTA_LAUNCHED), any(), any(), eq(Count))
}
Expand Down Expand Up @@ -669,14 +677,14 @@ class CtaViewModelTest {
}

@Test
fun whenCtaShownIfCtaIsNotMarkedAsReadOnShowThenCtaNotInsertedInDatabase() {
fun whenCtaShownIfCtaIsNotMarkedAsReadOnShowThenCtaNotInsertedInDatabase() = runTest {
testee.onCtaShown(OnboardingDaxDialogCta.DaxSerpCta(mockOnboardingStore, mockAppInstallStore))

verify(mockDismissedCtaDao, never()).insert(DismissedCta(CtaId.DAX_DIALOG_SERP))
}

@Test
fun whenCtaShownIfCtaIsMarkedAsReadOnShowThenCtaInsertedInDatabase() {
fun whenCtaShownIfCtaIsMarkedAsReadOnShowThenCtaInsertedInDatabase() = runTest {
testee.onCtaShown(OnboardingDaxDialogCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore, mockSettingsDataStore))

verify(mockDismissedCtaDao).insert(DismissedCta(CtaId.DAX_END))
Expand Down Expand Up @@ -717,7 +725,7 @@ class CtaViewModelTest {
fun givenPrivacyProCtaExperimentWhenRefreshCtaOnHomeTabThenReturnPrivacyProCta() = runTest {
givenDaxOnboardingActive()
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.privacyProCta()).thenReturn(mockEnabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.testPrivacyProOnboardingCopyNov24()).thenReturn(mockEnabledToggle)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(true)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO_VISIT_SITE)).thenReturn(true)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_END)).thenReturn(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3847,7 +3847,7 @@ class BrowserTabFragment :
when (configuration) {
is HomePanelCta -> showHomeCta(configuration)
is DaxBubbleCta.DaxExperimentIntroSearchOptionsCta, is DaxBubbleCta.DaxExperimentIntroVisitSiteOptionsCta,
is DaxBubbleCta.DaxExperimentEndCta,
is DaxBubbleCta.DaxExperimentEndCta, is DaxBubbleCta.DaxExperimentPrivacyProCta,
-> showDaxExperimentOnboardingBubbleCta(configuration as DaxBubbleCta)
is DaxBubbleCta -> showDaxOnboardingBubbleCta(configuration)
is OnboardingDaxDialogCta -> showOnboardingDialogCta(configuration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2619,7 +2619,7 @@ class BrowserTabViewModel @Inject constructor(
}

private fun showOrHideKeyboard(cta: Cta?) {
val shouldHideKeyboard = cta is HomePanelCta || cta is DaxBubbleCta.DaxPrivacyProCta
val shouldHideKeyboard = cta is HomePanelCta || cta is DaxBubbleCta.DaxPrivacyProCta || cta is DaxBubbleCta.DaxExperimentPrivacyProCta
command.value = if (shouldHideKeyboard) HideKeyboard else ShowKeyboard
}

Expand All @@ -2632,7 +2632,9 @@ class BrowserTabViewModel @Inject constructor(
}

fun onUserClickCtaOkButton(cta: Cta) {
ctaViewModel.onUserClickCtaOkButton(cta)
viewModelScope.launch {
ctaViewModel.onUserClickCtaOkButton(cta)
}
val onboardingCommand = when (cta) {
is HomePanelCta.AddWidgetAuto, is HomePanelCta.AddWidgetInstructions -> LaunchAddWidget
is OnboardingDaxDialogCta -> onOnboardingCtaOkButtonClicked(cta)
Expand All @@ -2647,7 +2649,8 @@ class BrowserTabViewModel @Inject constructor(
fun onUserClickCtaSecondaryButton(cta: Cta) {
viewModelScope.launch {
ctaViewModel.onUserDismissedCta(cta)
if (cta is DaxBubbleCta.DaxPrivacyProCta) {
ctaViewModel.onUserClickCtaSkipButton(cta)
if (cta is DaxBubbleCta.DaxPrivacyProCta || cta is DaxBubbleCta.DaxExperimentPrivacyProCta) {
val updatedCta = refreshCta()
ctaViewState.value = currentCtaViewState().copy(cta = updatedCta)
}
Expand Down Expand Up @@ -3433,7 +3436,12 @@ class BrowserTabViewModel @Inject constructor(
private fun onDaxBubbleCtaOkButtonClicked(cta: DaxBubbleCta): Command? {
onUserDismissedCta(cta)
return when (cta) {
is DaxBubbleCta.DaxPrivacyProCta -> LaunchPrivacyPro("https://duckduckgo.com/pro?origin=funnel_pro_android_onboarding".toUri())
is DaxBubbleCta.DaxPrivacyProCta, is DaxBubbleCta.DaxExperimentPrivacyProCta -> {
val cohortOrigin = ctaViewModel.getCohortOrigin()
LaunchPrivacyPro(
"https://duckduckgo.com/pro?origin=funnel_pro_android_onboarding$cohortOrigin".toUri(),
)
}
is DaxBubbleCta.DaxEndCta, is DaxBubbleCta.DaxExperimentEndCta -> {
viewModelScope.launch {
val updatedCta = refreshCta()
Expand Down
26 changes: 24 additions & 2 deletions app/src/main/java/com/duckduckgo/app/cta/ui/Cta.kt
Original file line number Diff line number Diff line change
Expand Up @@ -968,10 +968,12 @@ sealed class DaxBubbleCta(
data class DaxPrivacyProCta(
override val onboardingStore: OnboardingStore,
override val appInstallStore: AppInstallStore,
val titleRes: Int,
val descriptionRes: Int,
) : DaxBubbleCta(
ctaId = CtaId.DAX_INTRO_PRIVACY_PRO,
title = R.string.onboardingPrivacyProDaxDialogTitle,
description = R.string.onboardingPrivacyProDaxDialogDescription,
title = titleRes,
description = descriptionRes,
placeholder = com.duckduckgo.mobile.android.R.drawable.ic_privacy_pro_128,
primaryCta = R.string.onboardingPrivacyProDaxDialogOkButton,
secondaryCta = R.string.onboardingPrivacyProDaxDialogCancelButton,
Expand Down Expand Up @@ -1030,6 +1032,26 @@ sealed class DaxBubbleCta(
appInstallStore = appInstallStore,
)

data class DaxExperimentPrivacyProCta(
override val onboardingStore: OnboardingStore,
override val appInstallStore: AppInstallStore,
val titleRes: Int,
val descriptionRes: Int,
) : DaxBubbleCta(
ctaId = CtaId.DAX_INTRO_PRIVACY_PRO,
title = titleRes,
description = descriptionRes,
placeholder = com.duckduckgo.mobile.android.R.drawable.ic_privacy_pro_128,
primaryCta = R.string.onboardingPrivacyProDaxDialogOkButton,
secondaryCta = R.string.onboardingPrivacyProDaxDialogCancelButton,
shownPixel = AppPixelName.ONBOARDING_DAX_CTA_SHOWN,
okPixel = AppPixelName.ONBOARDING_DAX_CTA_OK_BUTTON,
cancelPixel = AppPixelName.ONBOARDING_DAX_CTA_CANCEL_BUTTON,
ctaPixelParam = Pixel.PixelValues.DAX_PRIVACY_PRO,
onboardingStore = onboardingStore,
appInstallStore = appInstallStore,
)

data class DaxDialogIntroOption(
val optionText: String,
@DrawableRes val iconRes: Int,
Expand Down
Loading
Loading