Skip to content

Android: Add dismiss option to contextual onboarding #5866

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

Merged
Merged
58 changes: 58 additions & 0 deletions .maestro/onboarding/onboarding_dismiss_all_dialogs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
appId: com.duckduckgo.mobile.android
name: "Onboarding: Ensuring we can navigate through pre-onboarding flow"
tags:
- onboardingTest
---
# Pre-requisite: None

- retry:
maxRetries: 3
commands:
- launchApp:
clearState: true
- runFlow: ../shared/pre_onboarding.yaml
- assertVisible:
id: daxDialogDismissButton
- tapOn:
id: daxDialogDismissButton
- assertVisible:
id: ddgLogo
- inputText: "https://www.search-company.site/#ad-id-14"
- pressKey: Enter
- assertVisible:
text: ".*As you tap and scroll, I'll block pesky trackers..*"
- assertVisible:
id: daxDialogDismissButton
- tapOn:
id: daxDialogDismissButton
- assertNotVisible:
text: ".*As you tap and scroll, I'll block pesky trackers..*"
- assertVisible:
id: "ad-id-14"
- tapOn:
id: "ad-id-14"
- assertVisible:
text: ".*was trying to track you here. I blocked them!.*"
- assertVisible:
id: daxDialogDismissButton
- tapOn:
id: daxDialogDismissButton
- assertNotVisible:
text: ".*was trying to track you here. I blocked them!.*"
- assertVisible:
text: "Publisher site"
- tapOn:
text: "Publisher site"
- assertVisible:
text: ".*Remember: every time you browse with me a creepy ad loses its wings..*"
- assertVisible:
id: daxDialogDismissButton
- tapOn:
id: daxDialogDismissButton
- assertNotVisible:
text: ".*Remember: every time you browse with me a creepy ad loses its wings..*"
- tapOn:
id: "com.duckduckgo.mobile.android:id/tabCount"
- tapOn: "New Tab"
- assertVisible:
text: ".*Try our search widget!.*"
19 changes: 19 additions & 0 deletions .maestro/onboarding/onboarding_dismiss_try_a_search_dialog.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
appId: com.duckduckgo.mobile.android
name: "Onboarding: Ensuring we can navigate through pre-onboarding flow"
tags:
- onboardingTest
---
# Pre-requisite: None

- retry:
maxRetries: 3
commands:
- launchApp:
clearState: true
- runFlow: ../shared/pre_onboarding.yaml
- assertVisible:
id: daxDialogDismissButton
- tapOn:
id: daxDialogDismissButton
- assertVisible:
id: ddgLogo
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import com.duckduckgo.app.browser.commands.Command
import com.duckduckgo.app.browser.commands.Command.CloseCustomTab
import com.duckduckgo.app.browser.commands.Command.EscapeMaliciousSite
import com.duckduckgo.app.browser.commands.Command.HideBrokenSitePromptCta
import com.duckduckgo.app.browser.commands.Command.HideOnboardingDaxBubbleCta
import com.duckduckgo.app.browser.commands.Command.HideOnboardingDaxDialog
import com.duckduckgo.app.browser.commands.Command.HideWarningMaliciousSite
import com.duckduckgo.app.browser.commands.Command.LaunchNewTab
Expand Down Expand Up @@ -144,8 +145,8 @@ import com.duckduckgo.app.cta.ui.BrokenSitePromptDialogCta
import com.duckduckgo.app.cta.ui.Cta
import com.duckduckgo.app.cta.ui.CtaViewModel
import com.duckduckgo.app.cta.ui.DaxBubbleCta
import com.duckduckgo.app.cta.ui.DaxBubbleCta.DaxIntroSearchOptionsCta
import com.duckduckgo.app.cta.ui.HomePanelCta
import com.duckduckgo.app.cta.ui.OnboardingDaxDialogCta.DaxFireButtonCta
import com.duckduckgo.app.cta.ui.OnboardingDaxDialogCta.DaxMainNetworkCta
import com.duckduckgo.app.cta.ui.OnboardingDaxDialogCta.DaxSerpCta
import com.duckduckgo.app.cta.ui.OnboardingDaxDialogCta.DaxTrackersBlockedCta
Expand All @@ -172,7 +173,7 @@ 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_DAX_CTA_DISMISS_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 @@ -186,7 +187,8 @@ 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.statistics.pixels.Pixel.PixelValues.DAX_INITIAL_CTA
import com.duckduckgo.app.statistics.pixels.Pixel.PixelValues.DAX_SERP_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 @@ -5723,16 +5725,6 @@ class BrowserTabViewModelTest {
verify(refreshPixelSender).sendCustomTabRefreshPixel()
}

@Test
fun givenHighlightsExperimentWhenUserClickedSkipInExperimentFireDialogThenSendCancelPixel() {
val cta = DaxFireButtonCta(mockOnboardingStore, mockAppInstallStore)
setCta(cta)

testee.onUserClickCtaSecondaryButton(cta)

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

@Test
fun whenPageIsChangedWithWebViewErrorResponseThenPixelIsFired() = runTest {
testee.onReceivedError(BAD_URL, "example2.com")
Expand Down Expand Up @@ -6067,6 +6059,30 @@ class BrowserTabViewModelTest {
assertCommandIssued<ReportBrokenSiteError>()
}

@Test
fun whenUserClicksDaxIntroSearchOptionsCtaDismissButtonThenHideOnboardingDaxBubbleCtaCommandIssuedAndPixelFired() = runTest {
val cta = DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore)

testee.onUserClickCtaDismissButton(cta)

assertCommandIssued<HideOnboardingDaxBubbleCta> {
assertEquals(cta, this.daxBubbleCta)
}
verify(mockPixel).fire(ONBOARDING_DAX_CTA_DISMISS_BUTTON, mapOf(PixelParameter.CTA_SHOWN to DAX_INITIAL_CTA))
}

@Test
fun whenUserClicksDaxSerpCtaDismissButtonThenHideOnboardingDaxDialogCommandIssuedAndPixelFired() = runTest {
val cta = DaxSerpCta(mockOnboardingStore, mockAppInstallStore)

testee.onUserClickCtaDismissButton(cta)

assertCommandIssued<HideOnboardingDaxDialog> {
assertEquals(cta, this.onboardingCta)
}
verify(mockPixel).fire(ONBOARDING_DAX_CTA_DISMISS_BUTTON, mapOf(PixelParameter.CTA_SHOWN to DAX_SERP_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 @@ -216,11 +216,29 @@ class CtaViewModelTest {
}

@Test
fun whenCtaDismissedPixelIsFired() = runTest {
fun whenCtaDismissedThenCancelPixelIsFired() = runTest {
testee.onUserDismissedCta(HomePanelCta.AddWidgetAuto)
verify(mockPixel).fire(eq(WIDGET_CTA_DISMISSED), any(), any(), eq(Count))
}

@Test
fun whenOnboardingCtaDismissedViaCloseBtnThenPixelIsFired() = runTest {
val testCta = DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore)

testee.onUserDismissedCta(testCta, true)

verify(mockPixel).fire(eq(ONBOARDING_DAX_CTA_DISMISS_BUTTON), any(), any(), eq(Count))
}

@Test
fun whenOnboardingCtaDismissedWithoutCloseBtnThenPixelIsNotFired() = runTest {
val testCta = DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore)

testee.onUserDismissedCta(testCta)

verify(mockPixel, never()).fire(eq(ONBOARDING_DAX_CTA_DISMISS_BUTTON), any(), any(), eq(Count))
}

@Test
fun whenNonSurveyCtaDismissedCtaThenDatabaseNotified() = runTest {
testee.onUserDismissedCta(HomePanelCta.AddWidgetAuto)
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1927,6 +1927,7 @@ class BrowserTabFragment :
is Command.LaunchScreen -> launchScreen(it.screen, it.payload)
is Command.HideOnboardingDaxDialog -> hideOnboardingDaxDialog(it.onboardingCta)
is Command.HideBrokenSitePromptCta -> hideBrokenSitePromptCta(it.brokenSitePromptDialogCta)
is Command.HideOnboardingDaxBubbleCta -> hideOnboardingDaxBubbleCta(it.daxBubbleCta)
is Command.ShowRemoveSearchSuggestionDialog -> showRemoveSearchSuggestionDialog(it.suggestion)
is Command.AutocompleteItemRemoved -> autocompleteItemRemoved()
is Command.OpenDuckPlayerSettings -> globalActivityStarter.start(binding.root.context, DuckPlayerSettingsNoParams)
Expand Down Expand Up @@ -2840,6 +2841,13 @@ class BrowserTabFragment :
brokenSitePromptDialogCta.hideOnboardingCta(binding)
}

private fun hideOnboardingDaxBubbleCta(daxBubbleCta: DaxBubbleCta) {
daxBubbleCta.hideDaxBubbleCta(binding)
hideDaxBubbleCta()
renderer.showNewTab()
showKeyboard()
}

private fun hideDaxBubbleCta() {
newBrowserTab.browserBackground.setImageResource(0)
daxDialogIntroBubble.root.gone()
Expand Down Expand Up @@ -4113,6 +4121,10 @@ class BrowserTabFragment :
setOnSecondaryCtaClicked {
viewModel.onUserClickCtaSecondaryButton(configuration)
}

setOnDismissCtaClicked {
viewModel.onUserClickCtaDismissButton(configuration)
}
}
viewModel.setBrowserBackground(appTheme.isLightModeEnabled())
viewModel.onCtaShown()
Expand All @@ -4138,6 +4150,9 @@ class BrowserTabFragment :
{ viewModel.onUserClickCtaSecondaryButton(configuration) },
onTypingAnimationFinished,
onSuggestedOptionsSelected,
{
viewModel.onUserClickCtaDismissButton(configuration)
},
)
viewModel.setOnboardingDialogBackground(appTheme.isLightModeEnabled())
viewModel.onCtaShown()
Expand Down
28 changes: 18 additions & 10 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import com.duckduckgo.app.browser.commands.Command.GenerateWebViewPreviewImage
import com.duckduckgo.app.browser.commands.Command.HandleNonHttpAppLink
import com.duckduckgo.app.browser.commands.Command.HideBrokenSitePromptCta
import com.duckduckgo.app.browser.commands.Command.HideKeyboard
import com.duckduckgo.app.browser.commands.Command.HideOnboardingDaxBubbleCta
import com.duckduckgo.app.browser.commands.Command.HideOnboardingDaxDialog
import com.duckduckgo.app.browser.commands.Command.HideSSLError
import com.duckduckgo.app.browser.commands.Command.HideWarningMaliciousSite
Expand Down Expand Up @@ -246,7 +247,6 @@ import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_RESULT_DELETED
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_RESULT_DELETED_DAILY
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_SEARCH_PHRASE_SELECTION
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_SEARCH_WEBSITE_SELECTION
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.AppPixelName.TAB_MANAGER_CLICKED_DAILY
Expand All @@ -260,7 +260,6 @@ 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 @@ -2809,16 +2808,25 @@ 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)
} else if (cta is BrokenSitePromptDialogCta) {
if (cta is BrokenSitePromptDialogCta) {
onBrokenSiteCtaDismissButtonClicked(cta)
}
if (cta is OnboardingDaxDialogCta.DaxFireButtonCta) {
pixel.fire(ONBOARDING_DAX_CTA_CANCEL_BUTTON, mapOf(PixelParameter.CTA_SHOWN to DAX_FIRE_DIALOG_CTA))
val updatedCta = ctaViewModel.getEndStaticDialogCta()
ctaViewState.value = currentCtaViewState().copy(cta = updatedCta)
}
}

fun onUserClickCtaDismissButton(cta: Cta) {
viewModelScope.launch {
ctaViewModel.onUserDismissedCta(cta, viaCloseBtn = true)
if (cta is DaxBubbleCta) {
command.value = HideOnboardingDaxBubbleCta(cta)
} else if (cta is OnboardingDaxDialogCta) {
command.value = HideOnboardingDaxDialog(cta)
if (cta is OnboardingDaxDialogCta.DaxTrackersBlockedCta) {
if (currentBrowserViewState().showPrivacyShield.isHighlighted()) {
browserViewState.value = currentBrowserViewState().copy(showPrivacyShield = HighlightableButton.Visible(highlighted = false))
ctaViewModel.dismissPulseAnimation()
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import com.duckduckgo.app.browser.model.BasicAuthenticationCredentials
import com.duckduckgo.app.browser.model.BasicAuthenticationRequest
import com.duckduckgo.app.browser.viewstate.SavedSiteChangedViewState
import com.duckduckgo.app.cta.ui.BrokenSitePromptDialogCta
import com.duckduckgo.app.cta.ui.DaxBubbleCta
import com.duckduckgo.app.cta.ui.OnboardingDaxDialogCta
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteEntity
import com.duckduckgo.autofill.api.domain.app.LoginCredentials
Expand Down Expand Up @@ -257,6 +258,7 @@ sealed class Command {
) : Command()
data class HideOnboardingDaxDialog(val onboardingCta: OnboardingDaxDialogCta) : Command()
data class HideBrokenSitePromptCta(val brokenSitePromptDialogCta: BrokenSitePromptDialogCta) : Command()
data class HideOnboardingDaxBubbleCta(val daxBubbleCta: DaxBubbleCta) : Command()
data class ShowRemoveSearchSuggestionDialog(val suggestion: AutoCompleteSuggestion) : Command()
data object AutocompleteItemRemoved : Command()
object OpenDuckPlayerSettings : Command()
Expand Down
Loading
Loading