Skip to content

Commit

Permalink
Privacy Pro onboarding experiment (#4909)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1201807753394693/1207814889563330/f

### Description
Added a new onboarding dialog containing information about Privacy Pro

### Steps to test this PR
_Pre steps_
- [x] Change privacy-config URL for `PRIVACY_REMOTE_CONFIG_URL =
"https://jsonblob.com/api/1279046082855034880"`
- [x] Make sure you remove DuckDuckGo folder under Downloads directory

_privacyPro dialog (Learn More)_
- [x] Change JsonBlob variant filters in order to assign `mr` which is
the experimental variant
- under `experimentalVariants` set `privacyProEligible` to false/true
for `mr` and `mq` `weight` to 0 so you get assigned `mr`
>         "filters": {
>           "privacyProEligible": false/true
>         }
- [x] Fresh install
- [x] Go to a site
- [x] Dismiss trackers dialog
- [x] Open a new tab
- [x] End dialog will appear
- [x] Dismiss dialog tapping in High Five! button
- [x] Check new privacy pro message is shown
- [x] Check keyboard is hidden
- [x] Tap on "learn more"
- [x] Check you are redirected to Privacy Pro
- [x] Open browser
- [x] Check you don't see any onboarding Dax dialog in the new tab page

_privacyPro dialog (Skip)_
- [x] Change JsonBlob variant filters in order to assign `mr` which is
the experimental variant
- under `experimentalVariants` set `privacyProEligible` to false/true
for `mr` and `mq` `weight` to 0 so you get assigned `mr`
>         "filters": {
>           "privacyProEligible": false/true
>         }
- [x] Fresh install
- [x] Go to a site
- [x] Dismiss trackers dialog
- [x] Tap on Fire Button -> Clear data
- [x] End dialog will appear
- [x] Perform a search
- [x] Open a new tab
- [x] Check new privacy pro message is shown
- [x] Check keyboard is hidden
- [x] Tap on "skip"
- [x] check Privacy pro dialog is dismissed and New Tab Page is shown

_Control variant_
- [x] Change JsonBlob variant filters in order to assign `mq` which is
the control variant
- under `experimentalVariants` set `privacyProEligible` to false/true
for `mq` and `mr` `weight` to 0 so you get assigned `mq`
>         "filters": {
>           "privacyProEligible": false/true
>         }
- [x] Fresh install
- [x] Go to a site
- [x] Dismiss trackers dialog
- [x] Open a new tab
- [x] Check end dialog is shown
- [x] Dismiss dialog and check onboarding is finished so new tab page is
shown

### UI changes Privacy Pro dialog
| Light Mode | Dark Mode |
| ------ | ----- |

![privacy-pro-dialog-light](https://github.com/user-attachments/assets/73d777c2-bb86-4878-b32e-81ff14affb3e)|![privacy-pro-dialog-dark](https://github.com/user-attachments/assets/0b6eea0d-161f-4605-ae7b-4c764e91f76f)|

### UI changes End dialog
| Before | After |
| ------ | ----- |

![Screenshot_20240828_181212_DuckDuckGo](https://github.com/user-attachments/assets/f42cd8d6-fb33-413b-8288-df45ab270ddc)|![end-dialog-light](https://github.com/user-attachments/assets/73f20dd2-abe0-4183-a513-87bdaeceb342)|
  • Loading branch information
nalcalag authored Sep 5, 2024
1 parent 9874850 commit cf5889c
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2471,6 +2471,14 @@ class BrowserTabViewModelTest {
assertCommandIssued<Command.LaunchAddWidget>()
}

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

@Test
fun whenUserDismissedCtaThenFirePixel() = runTest {
val cta = HomePanelCta.AddWidgetAuto
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ class CtaViewModelTest {
private lateinit var testee: CtaViewModel

val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
val mockEnabledToggle: Toggle = mock { on { it.isEnabled() } doReturn true }
private val mockEnabledToggle: Toggle = mock { on { it.isEnabled() } doReturn true }
private val mockDisabledToggle: Toggle = mock { on { it.isEnabled() } doReturn false }

@Before
fun before() {
Expand All @@ -139,6 +140,7 @@ class CtaViewModelTest {

val mockDisabledToggle: Toggle = mock { on { it.isEnabled() } doReturn false }
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockDisabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.privacyProCta()).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 Down Expand Up @@ -697,24 +699,40 @@ class CtaViewModelTest {
}

@Test
fun givenNoBrowserCtasExperimentWhenRefreshCtaOnHomeTabAndIntroShownThenReturnWidgetCta() = runTest {
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)
}

@Test
fun givenPrivacyProCtaExperimentWhenRefreshCtaOnHomeTabThenReturnPrivacyProCta() = runTest {
givenDaxOnboardingActive()
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.privacyProCta()).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)
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true)

val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
assertTrue(value is HomePanelCta.AddWidgetAuto)
assertTrue(value is DaxBubbleCta.DaxPrivacyProCta)
}

@Test
fun givenNoBrowserCtasExperimentWhenRefreshCtaWhileBrowsingThenReturnNull() = runTest {
fun givenPrivacyProCtaExperimentDisabledWhenRefreshCtaOnHomeTabThenDontReturnPrivacyProCta() = runTest {
givenDaxOnboardingActive()
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
val site = site(url = "http://www.facebook.com", entity = TestEntity("Facebook", "Facebook", 9.0))
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockDisabledToggle)
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)
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true)

val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = site)
assertNull(value)
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
assertFalse(value is DaxBubbleCta.DaxPrivacyProCta)
}

@Test
Expand Down
16 changes: 14 additions & 2 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2399,8 +2399,13 @@ class BrowserTabFragment :
faviconPrompt.show()
}

private fun hideOnboardingDaxDialog(experimentCta: OnboardingDaxDialogCta) {
experimentCta.hideOnboardingCta(binding)
private fun hideOnboardingDaxDialog(onboardingCta: OnboardingDaxDialogCta) {
onboardingCta.hideOnboardingCta(binding)
}

private fun hideDaxBubbleCta() {
newBrowserTab.browserBackground.setBackgroundResource(0)
daxDialogIntroBubbleCta.root.gone()
}

private fun configureWebViewForBlobDownload(webView: DuckDuckGoWebView) {
Expand Down Expand Up @@ -3946,6 +3951,7 @@ class BrowserTabFragment :
}

viewState.daxOnboardingComplete -> {
hideDaxBubbleCta()
showNewTab()
}
}
Expand All @@ -3966,6 +3972,12 @@ class BrowserTabFragment :
showCta(daxDialogIntroBubbleCta.daxCtaContainer) {
setOnOptionClicked { userEnteredQuery(it.link) }
}
setOnPrimaryCtaClicked {
viewModel.onUserClickCtaOkButton(configuration)
}
setOnSecondaryCtaClicked {
viewModel.onUserClickCtaSecondaryButton(configuration)
}
}
newBrowserTab.newTabLayout.setOnClickListener { daxDialogIntroBubbleCta.dialogTextCta.finishAnimation() }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2528,7 +2528,8 @@ class BrowserTabViewModel @Inject constructor(
}

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

fun registerDaxBubbleCtaDismissed() {
Expand All @@ -2544,6 +2545,7 @@ class BrowserTabViewModel @Inject constructor(
val onboardingCommand = when (cta) {
is HomePanelCta.AddWidgetAuto, is HomePanelCta.AddWidgetInstructions -> LaunchAddWidget
is OnboardingDaxDialogCta -> onOnboardingCtaOkButtonClicked(cta)
is DaxBubbleCta -> onDaxBubbleCtaOkButtonClicked(cta)
else -> null
}
onboardingCommand?.let {
Expand All @@ -2554,6 +2556,10 @@ class BrowserTabViewModel @Inject constructor(
fun onUserClickCtaSecondaryButton(cta: Cta) {
viewModelScope.launch {
ctaViewModel.onUserDismissedCta(cta)
if (cta is DaxBubbleCta.DaxPrivacyProCta) {
val updatedCta = refreshCta()
ctaViewState.value = currentCtaViewState().copy(cta = updatedCta)
}
}
}

Expand Down Expand Up @@ -3252,9 +3258,7 @@ class BrowserTabViewModel @Inject constructor(
}

private fun onOnboardingCtaOkButtonClicked(onboardingCta: OnboardingDaxDialogCta): Command? {
viewModelScope.launch {
ctaViewModel.onUserDismissedCta(onboardingCta)
}
onUserDismissedCta(onboardingCta)
return when (onboardingCta) {
is OnboardingDaxDialogCta.DaxSerpCta -> {
viewModelScope.launch {
Expand Down Expand Up @@ -3288,6 +3292,22 @@ class BrowserTabViewModel @Inject constructor(
}
}

private fun onDaxBubbleCtaOkButtonClicked(cta: DaxBubbleCta): Command? {
onUserDismissedCta(cta)
return when (cta) {
is DaxBubbleCta.DaxPrivacyProCta -> LaunchPrivacyPro("https://duckduckgo.com/pro".toUri())
is DaxBubbleCta.DaxEndCta -> {
viewModelScope.launch {
val updatedCta = refreshCta()
ctaViewState.value = currentCtaViewState().copy(cta = updatedCta)
showOrHideKeyboard(updatedCta)
}
null
}
else -> null
}
}

private fun onDismissOnboardingDaxDialog(cta: OnboardingDaxDialogCta) {
if (cta is OnboardingDaxDialogCta.DaxTrackersBlockedCta) {
browserViewState.value = currentBrowserViewState().copy(showPrivacyShield = HighlightableButton.Visible(highlighted = false))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum class CtaId {
ADD_WIDGET,
DAX_INTRO,
DAX_INTRO_VISIT_SITE,
DAX_INTRO_PRIVACY_PRO,
DAX_FIRE_BUTTON,
DAX_DIALOG_SERP,
DAX_DIALOG_TRACKERS_FOUND,
Expand Down
Loading

0 comments on commit cf5889c

Please sign in to comment.