Skip to content

Commit

Permalink
Highlights Onboarding Experiment (#5116)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1201807753394693/1208486644029037/f

### Description
Implement Highlights Experiment on Android

### Steps to test this PR

_Pre steps_
- [x] Make `fun isHighlightsEnabled(): Boolean` in
`HighlightsOnboardingExperimentManager` to always return `true`
- [x] You can check copy changes in
https://app.asana.com/0/1202552961248957/1208294136875672/f

_Pre onboarding_
- Fresh install
- [x] Check background is updated to experiment assets
- [x] Check Dax dialogs UI is updated (new radius and edge)
- Tap on "Let's do it"
- [x] Check comparison chart has new icons for each chart item
- [x] Check copy is updated
- Tap on "Choose Your Browser" (doesn't matter if you set DDG as default
or not)
- [x] Check new address bar position dialog appear
- [x] Check new pixel
`m_preonboarding_address_bar_position_dialog_shown_unique` is fired
- Tap on Bottom option
- Tap on "Next"
- [x] Check new pixel
`m_preonboarding_bottom_address_bar_selected_unique` is fired
...

_Try a search Dax dialog_
- [x] Check new "Try a search" dialog appear with updated UI
(background, radius and edge)
- [x] Check dialog's copy is updated
- [x] Check there are only 3 search options
- Tap on `Surprise me!` option
- [x] Check you navigate to `baby ducklings` search
...

_SERP Dax dialog_
- [x] Check updated SERP dialog is displayed
- [x] Check pixel `m_odc_s` is fired with parameter `cta = s`
- Tap on a "Got it!"
...

_Site suggestions Dax dialog_
- [x] Check updated Site Suggestions dialog is displayed
- [x] Check pixel `m_odc_s` is fired with parameter `cta = visit_site`
- Tap on any site suggestion
...

_Trackers Blocked Dax dialog_
- [x] Check updated Trackers blocked Dax dialog is displayed
- [x] Check privacy shield is highlighted
- [x] Check pixel `m_odc_s` is fired with parameter `cta = t`
- Tap on privacy shield
- Go back to browser
- [x] Check highlight is gone
- Tap on `Got it!` button
...

_Fire Button Dax dialog_
- [x] Check updated Fire Dax dialog with 2 buttons is displayed
- [x] Check pixel `m_odc_s` is fired with parameter `cta = fd`
- [x] Check fire button highlight is on
- Tap on fire button
- [x] Check dialog is dismissed
- [x] Check fire button highlight is off
- Tap on Clear Data
...

_End onboarding dialog_
- [x] Check updated end Dax dialog is shown in new tab

_Fire Dax dialog 'Skip' button_
- Fresh install
- Repeat all steps until seeing Fire Dax dialog again
- Tap on 'Skip' secondary button
- [x] Check pixel `m_onboarding_dax_cta_cancel` is fired with parameter
`cta = fd`
- [x] Check updated End dialog appear
- [x] Check pixel `m_odc_s` is fired with parameter `cta = end`

_Fire Dax dialog 'Try it' button_
- Fresh install
- Repeat all steps until seeing Fire Dax dialog again
- Tap on 'Try it' secondary button
- [x] Check pixel `m_odc_ok` is fired with parameter `cta = fd`
- [x] Check Fire button options are displayed and highlight is removed
- Tap on Cancel
- [x] Check Fire dialog has been dismissed
- [x] Check updated End dialog appear
- Refresh site
- [x] Check updated End Dax dialog is displayed
- [x] Check pixel `m_odc_s` is fired with parameter `cta = end`

_No Trackers Dax dialog_
- Fresh install
- Go to `wikipedia.org`
- [x] Check no trackers Dax dialog has the new UI
- [x] Check pixel `m_odc_s` is fired with parameter `cta = nt`
...

_Trackers Network Dax dialog_
- Go to `facebook.com`
- [x] Check network Dax dialog has the new UI
- [x] Check pixel `m_odc_s` is fired with parameter `cta = n`
...

### UI changes
| Before  | After |
| ------ | ----- |

|

|

---------

Co-authored-by: Mike Scamell <[email protected]>
Co-authored-by: Ondrej Ruttkay <[email protected]>
  • Loading branch information
3 people authored Oct 23, 2024
1 parent a67eee1 commit f6be916
Show file tree
Hide file tree
Showing 62 changed files with 2,693 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,13 @@ 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.HighlightsOnboardingExperimentManager
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_BANNER_SHOWN
import com.duckduckgo.app.pixels.AppPixelName.DUCK_PLAYER_SETTING_ALWAYS_DUCK_PLAYER
import com.duckduckgo.app.pixels.AppPixelName.DUCK_PLAYER_SETTING_ALWAYS_OVERLAY_YOUTUBE
import com.duckduckgo.app.pixels.AppPixelName.DUCK_PLAYER_SETTING_NEVER_OVERLAY_YOUTUBE
import com.duckduckgo.app.pixels.AppPixelName.ONBOARDING_DAX_CTA_CANCEL_BUTTON
import com.duckduckgo.app.pixels.AppPixelName.ONBOARDING_SEARCH_CUSTOM
import com.duckduckgo.app.pixels.AppPixelName.ONBOARDING_VISIT_SITE_CUSTOM
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
Expand All @@ -165,6 +167,7 @@ import com.duckduckgo.app.statistics.pixels.Pixel.PixelParameter
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Count
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Daily
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Unique
import com.duckduckgo.app.statistics.pixels.Pixel.PixelValues.DAX_FIRE_DIALOG_CTA
import com.duckduckgo.app.surrogates.SurrogateResponse
import com.duckduckgo.app.tabs.model.TabEntity
import com.duckduckgo.app.tabs.model.TabRepository
Expand Down Expand Up @@ -486,6 +489,7 @@ class BrowserTabViewModelTest {
private val mockUserBrowserProperties: UserBrowserProperties = mock()
private val mockAutoCompleteRepository: AutoCompleteRepository = mock()
private val changeOmnibarPositionFeature: ChangeOmnibarPositionFeature = mock()
private val mockHighlightsOnboardingExperimentManager: HighlightsOnboardingExperimentManager = mock()

@Before
fun before() = runTest {
Expand All @@ -511,6 +515,7 @@ class BrowserTabViewModelTest {
lazyFaviconManager,
)

whenever(mockHighlightsOnboardingExperimentManager.isHighlightsEnabled()).thenReturn(false)
whenever(mockDuckPlayer.observeUserPreferences()).thenReturn(flowOf(UserPreferences(false, Disabled)))
whenever(mockDismissedCtaDao.dismissedCtas()).thenReturn(dismissedCtaDaoChannel.consumeAsFlow())
whenever(mockTabRepository.flowTabs).thenReturn(flowOf(emptyList()))
Expand Down Expand Up @@ -548,6 +553,7 @@ class BrowserTabViewModelTest {
extendedOnboardingFeatureToggles = mockExtendedOnboardingFeatureToggles,
subscriptions = mock(),
duckPlayer = mockDuckPlayer,
highlightsOnboardingExperimentManager = mockHighlightsOnboardingExperimentManager,
)

val siteFactory = SiteFactoryImpl(
Expand Down Expand Up @@ -581,6 +587,7 @@ class BrowserTabViewModelTest {
whenever(mockPrivacyProtectionsPopupManager.viewState).thenReturn(flowOf(PrivacyProtectionsPopupViewState.Gone))
whenever(mockAppBuildConfig.buildType).thenReturn("debug")
whenever(mockDuckPlayer.observeUserPreferences()).thenReturn(flowOf(UserPreferences(false, AlwaysAsk)))
whenever(mockHighlightsOnboardingExperimentManager.isHighlightsEnabled()).thenReturn(false)

testee = BrowserTabViewModel(
statisticsUpdater = mockStatisticsUpdater,
Expand Down Expand Up @@ -649,6 +656,7 @@ class BrowserTabViewModelTest {
loadingBarExperimentManager = loadingBarExperimentManager,
refreshPixelSender = refreshPixelSender,
changeOmnibarPositionFeature = changeOmnibarPositionFeature,
highlightsOnboardingExperimentManager = mockHighlightsOnboardingExperimentManager,
)

testee.loadData("abc", null, false, false)
Expand Down Expand Up @@ -5982,6 +5990,16 @@ class BrowserTabViewModelTest {
verify(refreshPixelSender).sendCustomTabRefreshPixel()
}

@Test
fun givenHighlightsExperimentWhenUserClickedSkipInExperimentFireDialogThenSendCancelPixel() {
val cta = OnboardingDaxDialogCta.DaxExperimentFireButtonCta(mockOnboardingStore, mockAppInstallStore)
setCta(cta)

testee.onUserClickCtaSecondaryButton(cta)

verify(mockPixel).fire(ONBOARDING_DAX_CTA_CANCEL_BUTTON, mapOf(PixelParameter.CTA_SHOWN to DAX_FIRE_DIALOG_CTA))
}

private fun aCredential(): LoginCredentials {
return LoginCredentials(domain = null, username = null, password = null)
}
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.HighlightsOnboardingExperimentManager
import com.duckduckgo.app.pixels.AppPixelName.*
import com.duckduckgo.app.privacy.db.UserAllowListRepository
import com.duckduckgo.app.privacy.model.HttpsStatus
Expand Down Expand Up @@ -113,6 +114,8 @@ class CtaViewModelTest {

private val mockSubscriptions: Subscriptions = mock()

private val mockHighlightsOnboardingExperimentManager: HighlightsOnboardingExperimentManager = mock()

private val requiredDaxOnboardingCtas: List<CtaId> = listOf(
CtaId.DAX_INTRO,
CtaId.DAX_DIALOG_SERP,
Expand Down Expand Up @@ -162,6 +165,7 @@ class CtaViewModelTest {
extendedOnboardingFeatureToggles = mockExtendedOnboardingFeatureToggles,
subscriptions = mockSubscriptions,
duckPlayer = mockDuckPlayer,
highlightsOnboardingExperimentManager = mockHighlightsOnboardingExperimentManager,
)
}

Expand Down Expand Up @@ -765,6 +769,28 @@ class CtaViewModelTest {
assertNull(value)
}

@Test
fun givenHighlightsExperimentWhenRefreshCtaOnHomeTabAndIntroCtaWasNotPreviouslyShownThenExperimentIntroCtaShown() = runTest {
givenDaxOnboardingActive()
whenever(mockHighlightsOnboardingExperimentManager.isHighlightsEnabled()).thenReturn(true)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(false)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_DIALOG_SERP)).thenReturn(true)

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

@Test
fun givenHighlightsExperimentWhenGettingFireDialogOnboardingDialogThenReturnExperimentFireDialogCta() = runTest {
givenDaxOnboardingActive()
whenever(mockHighlightsOnboardingExperimentManager.isHighlightsEnabled()).thenReturn(true)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_FIRE_BUTTON)).thenReturn(false)

val value = testee.getFireDialogCta()

assertTrue(value is OnboardingDaxDialogCta.DaxExperimentFireButtonCta)
}

private suspend fun givenDaxOnboardingActive() {
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
}
Expand Down
81 changes: 60 additions & 21 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,12 @@ class BrowserTabFragment :
private val daxDialogOnboardingCta
get() = binding.includeOnboardingDaxDialog

private val daxDialogIntroBubbleCtaExperiment
get() = binding.includeNewBrowserTab.includeDaxDialogIntroBubbleCtaExperiment

private val daxDialogOnboardingCtaExperiment
get() = binding.includeOnboardingDaxDialogExperiment

// Optimization to prevent against excessive work generating WebView previews; an existing job will be cancelled if a new one is launched
private var bitmapGeneratorJob: Job? = null

Expand Down Expand Up @@ -1730,12 +1736,27 @@ class BrowserTabFragment :
contentScopeScripts.sendSubscriptionEvent(it.cssData)
duckPlayerScripts.sendSubscriptionEvent(it.duckPlayerData)
}
is Command.SetBrowserBackground -> setBrowserBackgroundRes(it.backgroundRes)
is Command.SetOnboardingDialogBackground -> setOnboardingDialogBackgroundRes(it.backgroundRes)
is Command.LaunchFireDialogFromOnboardingDialog -> {
hideOnboardingDaxDialog(it.onboardingCta)
browserActivity?.launchFire()
}
else -> {
// NO OP
}
}
}

private fun setBrowserBackgroundRes(backgroundRes: Int) {
newBrowserTab.browserBackground.setImageResource(backgroundRes)
}

private fun setOnboardingDialogBackgroundRes(backgroundRes: Int) {
daxDialogOnboardingCta.onboardingDaxDialogBackground.setImageResource(backgroundRes)
daxDialogOnboardingCtaExperiment.onboardingDaxDialogBackground.setImageResource(backgroundRes)
}

private fun showRemoveSearchSuggestionDialog(suggestion: AutoCompleteSuggestion) {
storeAutocompletePosition()
hideKeyboardRetainFocus()
Expand Down Expand Up @@ -2668,8 +2689,9 @@ class BrowserTabFragment :
}

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

private fun configureWebViewForBlobDownload(webView: DuckDuckGoWebView) {
Expand Down Expand Up @@ -3944,6 +3966,9 @@ class BrowserTabFragment :
private fun showCta(configuration: Cta) {
when (configuration) {
is HomePanelCta -> showHomeCta(configuration)
is DaxBubbleCta.DaxExperimentIntroSearchOptionsCta, is DaxBubbleCta.DaxExperimentIntroVisitSiteOptionsCta,
is DaxBubbleCta.DaxExperimentEndCta,
-> showDaxExperimentOnboardingBubbleCta(configuration as DaxBubbleCta)
is DaxBubbleCta -> showDaxOnboardingBubbleCta(configuration)
is OnboardingDaxDialogCta -> showOnboardingDialogCta(configuration)
}
Expand All @@ -3962,49 +3987,61 @@ class BrowserTabFragment :
viewModel.onUserClickCtaSecondaryButton(configuration)
}
}
newBrowserTab.newTabLayout.setOnClickListener { daxDialogIntroBubbleCta.dialogTextCta.finishAnimation() }
viewModel.setBrowserExperimentBackground(appTheme.isLightModeEnabled())
viewModel.onCtaShown()
}

if (appTheme.isLightModeEnabled()) {
newBrowserTab.browserBackground.setBackgroundResource(R.drawable.onboarding_experiment_background_bitmap_light)
} else {
newBrowserTab.browserBackground.setBackgroundResource(R.drawable.onboarding_experiment_background_bitmap_dark)
private fun showDaxExperimentOnboardingBubbleCta(configuration: DaxBubbleCta) {
hideNewTab()
configuration.apply {
showCta(daxDialogIntroBubbleCtaExperiment.daxCtaContainer) {
setOnOptionClicked { userEnteredQuery(it.link) }
}
setOnPrimaryCtaClicked {
viewModel.onUserClickCtaOkButton(configuration)
}
setOnSecondaryCtaClicked {
viewModel.onUserClickCtaSecondaryButton(configuration)
}
}

viewModel.setBrowserExperimentBackground(appTheme.isLightModeEnabled())
viewModel.onCtaShown()
}

@SuppressLint("ClickableViewAccessibility")
private fun showOnboardingDialogCta(configuration: OnboardingDaxDialogCta) {
hideNewTab()
val onTypingAnimationFinished = if (configuration is OnboardingDaxDialogCta.DaxTrackersBlockedCta) {
val onTypingAnimationFinished = if (configuration is OnboardingDaxDialogCta.DaxTrackersBlockedCta ||
configuration is OnboardingDaxDialogCta.DaxExperimentTrackersBlockedCta
) {
{ viewModel.onOnboardingDaxTypingAnimationFinished() }
} else {
{}
}
configuration.showOnboardingCta(binding, { viewModel.onUserClickCtaOkButton(configuration) }, onTypingAnimationFinished)
configuration.showOnboardingCta(
binding,
{ viewModel.onUserClickCtaOkButton(configuration) },
{ viewModel.onUserClickCtaSecondaryButton(configuration) },
onTypingAnimationFinished,
)
if (configuration is OnboardingDaxDialogCta.DaxSiteSuggestionsCta) {
configuration.setOnOptionClicked(
daxDialogOnboardingCta,
) {
userEnteredQuery(it.link)
viewModel.onUserClickCtaOkButton(configuration)
}
}
if (appTheme.isLightModeEnabled()) {
binding.includeOnboardingDaxDialog.onboardingDaxDialogBackground
.setBackgroundResource(R.drawable.onboarding_experiment_background_bitmap_light)
} else {
binding.includeOnboardingDaxDialog.onboardingDaxDialogBackground
.setBackgroundResource(R.drawable.onboarding_experiment_background_bitmap_dark)
if (configuration is OnboardingDaxDialogCta.DaxExperimentSiteSuggestionsCta) {
configuration.setOnOptionClicked(
daxDialogOnboardingCtaExperiment,
) {
userEnteredQuery(it.link)
}
}
binding.webViewContainer.setOnClickListener { daxDialogIntroBubbleCta.dialogTextCta.finishAnimation() }
viewModel.setOnboardingDialogExperimentBackground(appTheme.isLightModeEnabled())
viewModel.onCtaShown()
}

private fun removeNewTabLayoutClickListener() {
newBrowserTab.newTabLayout.setOnClickListener(null)
}

private fun showHomeCta(
configuration: HomePanelCta,
) {
Expand Down Expand Up @@ -4074,6 +4111,8 @@ class BrowserTabFragment :
private fun hideDaxCta() {
daxDialogOnboardingCta.dialogTextCta.cancelAnimation()
daxDialogOnboardingCta.daxCtaContainer.gone()
daxDialogOnboardingCtaExperiment.dialogTextCta.cancelAnimation()
daxDialogOnboardingCtaExperiment.daxCtaContainer.gone()
}

fun renderHomeCta() {
Expand Down
Loading

0 comments on commit f6be916

Please sign in to comment.