Skip to content
Merged
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 @@ -2450,24 +2450,6 @@ class BrowserTabViewModelTest {
verify(mockPixel).fire(cta.shownPixel!!, cta.pixelShownParameters())
}

@Test
fun whenRegisterDaxBubbleCtaDismissedThenRegisterInDatabase() = runTest {
val cta = DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore)
testee.ctaViewState.value = CtaViewState(cta = cta)

testee.registerDaxBubbleCtaDismissed()
verify(mockDismissedCtaDao).insert(DismissedCta(cta.ctaId))
}

@Test
fun whenRegisterDaxBubbleCtaDismissedThenCtaChangedToNull() = runTest {
val cta = DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore)
testee.ctaViewState.value = CtaViewState(cta = cta)

testee.registerDaxBubbleCtaDismissed()
assertNull(testee.ctaViewState.value!!.cta)
}

@Test
fun whenRefreshCtaIfCtaAlreadyShownForCurrentPageThenReturnNull() = runTest {
setBrowserShowing(isBrowsing = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,35 +241,35 @@ class CtaViewModelTest {

@Test
fun whenRegisterDaxBubbleIntroCtaThenDatabaseNotified() = runTest {
testee.registerDaxBubbleCtaDismissed(DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore))
testee.onUserDismissedCta(DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore))
verify(mockDismissedCtaDao).insert(DismissedCta(CtaId.DAX_INTRO))
}

@Test
fun whenRegisterDaxBubbleIntroVisitSiteCtaThenDatabaseNotified() = runTest {
testee.registerDaxBubbleCtaDismissed(DaxBubbleCta.DaxIntroVisitSiteOptionsCta(mockOnboardingStore, mockAppInstallStore))
testee.onUserDismissedCta(DaxBubbleCta.DaxIntroVisitSiteOptionsCta(mockOnboardingStore, mockAppInstallStore))
verify(mockDismissedCtaDao).insert(DismissedCta(CtaId.DAX_INTRO_VISIT_SITE))
}

@Test
fun whenRegisterDaxBubbleEndCtaThenDatabaseNotified() = runTest {
testee.registerDaxBubbleCtaDismissed(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
testee.onUserDismissedCta(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
verify(mockDismissedCtaDao).insert(DismissedCta(CtaId.DAX_END))
}

@Test
fun whenRegisterCtaAndUserHasPendingOnboardingCtasThenStageNotCompleted() = runTest {
givenDaxOnboardingActive()
givenShownDaxOnboardingCtas(emptyList())
testee.registerDaxBubbleCtaDismissed(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
testee.onUserDismissedCta(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
verify(mockUserStageStore, times(0)).stageCompleted(any())
}

@Test
fun whenRegisterCtaAndAllDaxOnboardingCtasShownThenStageCompleted() = runTest {
givenDaxOnboardingActive()
givenShownDaxOnboardingCtas(requiredDaxOnboardingCtas)
testee.registerDaxBubbleCtaDismissed(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
testee.onUserDismissedCta(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
verify(mockUserStageStore).stageCompleted(AppStage.DAX_ONBOARDING)
}

Expand Down Expand Up @@ -662,7 +662,7 @@ class CtaViewModelTest {

@Test
fun whenRegisterDismissedDaxIntroVisitSiteCtaThenDatabaseNotified() = runTest {
testee.registerDaxBubbleCtaDismissed(
testee.onUserDismissedCta(
DaxBubbleCta.DaxIntroVisitSiteOptionsCta(
mockOnboardingStore,
mockAppInstallStore,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1515,7 +1515,6 @@ class BrowserTabFragment :
clientBrandHintProvider.setOn(webView?.safeSettings, url)
hideKeyboard()
omnibar.hideFindInPage()
viewModel.registerDaxBubbleCtaDismissed()
webView?.loadUrl(url, headers)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2704,14 +2704,6 @@ class BrowserTabViewModel @Inject constructor(
command.value = if (shouldHideKeyboard) HideKeyboard else ShowKeyboard
}

fun registerDaxBubbleCtaDismissed() {
viewModelScope.launch {
val cta = ctaViewState.value?.cta ?: return@launch
ctaViewModel.registerDaxBubbleCtaDismissed(cta)
ctaViewState.value = currentCtaViewState().copy(cta = null)
}
}

fun onUserClickCtaOkButton(cta: Cta) {
viewModelScope.launch {
ctaViewModel.onUserClickCtaOkButton(cta)
Expand Down
53 changes: 33 additions & 20 deletions app/src/main/java/com/duckduckgo/app/cta/ui/Cta.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ interface DaxCta {
val onboardingStore: OnboardingStore
val appInstallStore: AppInstallStore
var ctaPixelParam: String
val markAsReadOnShow: Boolean
get() = false

companion object {
const val MAX_DAYS_ALLOWED = 3
Expand All @@ -79,9 +81,6 @@ interface Cta {
}

interface OnboardingDaxCta {
val markAsReadOnShow: Boolean
get() = false

fun showOnboardingCta(
binding: FragmentBrowserTabBinding,
onPrimaryCtaClicked: () -> Unit,
Expand Down Expand Up @@ -510,6 +509,7 @@ sealed class DaxBubbleCta(
onTypingAnimationFinished: () -> Unit,
) {
ctaView = view
clearDialog()
val daxTitle = view.context.getString(title)
val daxText = view.context.getString(description)
val optionsViews: List<DaxButton> = listOf(
Expand All @@ -523,33 +523,27 @@ sealed class DaxBubbleCta(
view.findViewById<DaxButton>(R.id.primaryCta).show()
view.findViewById<DaxButton>(R.id.primaryCta).alpha = 0f
view.findViewById<DaxButton>(R.id.primaryCta).text = view.context.getString(it)
} ?: view.findViewById<DaxButton>(R.id.primaryCta).gone()
}

secondaryCta?.let {
view.findViewById<DaxButton>(R.id.secondaryCta).show()
view.findViewById<DaxButton>(R.id.secondaryCta).alpha = 0f
view.findViewById<DaxButton>(R.id.secondaryCta).text = view.context.getString(it)
} ?: view.findViewById<DaxButton>(R.id.secondaryCta).gone()
}

placeholder?.let {
view.findViewById<ImageView>(R.id.placeholder).show()
view.findViewById<ImageView>(R.id.placeholder).alpha = 0f
view.findViewById<ImageView>(R.id.placeholder).setImageResource(it)
} ?: view.findViewById<ImageView>(R.id.placeholder).gone()

if (options.isNullOrEmpty()) {
view.findViewById<DaxButton>(R.id.daxDialogOption1).gone()
view.findViewById<DaxButton>(R.id.daxDialogOption2).gone()
view.findViewById<DaxButton>(R.id.daxDialogOption3).gone()
view.findViewById<DaxButton>(R.id.daxDialogOption4).gone()
} else {
options?.let {
optionsViews.forEachIndexed { index, buttonView ->
if (it.size > index) {
it[index].setOptionView(buttonView)
} else {
buttonView.gone()
}
}

options?.let {
optionsViews.forEachIndexed { index, buttonView ->
buttonView.show()
if (it.size > index) {
it[index].setOptionView(buttonView)
} else {
buttonView.gone()
}
}
}
Expand Down Expand Up @@ -588,6 +582,23 @@ sealed class DaxBubbleCta(
view.findViewById<View>(R.id.cardContainer).setOnClickListener { afterAnimation() }
}

private fun clearDialog() {
ctaView?.findViewById<DaxButton>(R.id.primaryCta)?.alpha = 0f
ctaView?.findViewById<DaxButton>(R.id.primaryCta)?.gone()
ctaView?.findViewById<DaxButton>(R.id.secondaryCta)?.alpha = 0f
ctaView?.findViewById<DaxButton>(R.id.secondaryCta)?.gone()
ctaView?.findViewById<ImageView>(R.id.placeholder)?.alpha = 0f
ctaView?.findViewById<ImageView>(R.id.placeholder)?.gone()
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption1)?.alpha = 0f
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption1)?.gone()
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption2)?.alpha = 0f
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption2)?.gone()
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption3)?.alpha = 0f
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption3)?.gone()
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption4)?.alpha = 0f
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption4)?.gone()
}

fun setOnPrimaryCtaClicked(onButtonClicked: () -> Unit) {
ctaView?.findViewById<DaxButton>(R.id.primaryCta)?.setOnClickListener {
onButtonClicked.invoke()
Expand All @@ -612,6 +623,8 @@ sealed class DaxBubbleCta(
}
}

override val markAsReadOnShow: Boolean = true

override fun pixelCancelParameters(): Map<String, String> = mapOf(Pixel.PixelParameter.CTA_SHOWN to ctaPixelParam)

override fun pixelOkParameters(): Map<String, String> = mapOf(Pixel.PixelParameter.CTA_SHOWN to ctaPixelParam)
Expand Down
35 changes: 11 additions & 24 deletions app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class CtaViewModel @Inject constructor(
pixel.fire(it, cta.pixelShownParameters())
}
}
if (cta is OnboardingDaxDialogCta && cta.markAsReadOnShow) {
if (cta is DaxCta && cta.markAsReadOnShow) {
dismissedCtaDao.insert(DismissedCta(cta.ctaId))
}
if (cta is BrokenSitePromptDialogCta) {
Expand All @@ -145,15 +145,6 @@ class CtaViewModel @Inject constructor(
}
}

suspend fun registerDaxBubbleCtaDismissed(cta: Cta) {
withContext(dispatchers.io()) {
if (cta is DaxBubbleCta) {
dismissedCtaDao.insert(DismissedCta(cta.ctaId))
completeStageIfDaxOnboardingCompleted()
}
}
}

private suspend fun completeStageIfDaxOnboardingCompleted() {
if (daxOnboardingActive() && allOnboardingCtasShown()) {
Timber.d("Completing DAX ONBOARDING")
Expand Down Expand Up @@ -267,19 +258,14 @@ class CtaViewModel @Inject constructor(
@WorkerThread
private suspend fun canShowDaxIntroVisitSiteCta(): Boolean =
daxOnboardingActive() && daxDialogIntroShown() && !hideTips() &&
!(daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogTrackersFoundShown())
!(daxDialogIntroVisitSiteShown() || daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogTrackersFoundShown())

@WorkerThread
private suspend fun canShowDaxCtaEndOfJourney(): Boolean = daxOnboardingActive() &&
!daxDialogEndShown() &&
daxDialogIntroShown() &&
!hideTips() &&
(daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogSerpShown() || daxDialogTrackersFoundShown())

private suspend fun canShowPrivacyProCta(): Boolean {
return daxOnboardingActive() && !hideTips() && !daxDialogPrivacyProShown() &&
subscriptions.isEligible() && extendedOnboardingFeatureToggles.privacyProCta().isEnabled()
}
private suspend fun canShowDaxCtaEndOfJourney(): Boolean = daxOnboardingActive() && !daxDialogEndShown() && daxDialogIntroShown() && !hideTips()

@WorkerThread
private suspend fun canShowPrivacyProCta(): Boolean = daxOnboardingActive() && !hideTips() && !daxDialogPrivacyProShown() &&
subscriptions.isEligible() && extendedOnboardingFeatureToggles.privacyProCta().isEnabled()

@WorkerThread
private suspend fun getBrowserCta(site: Site?): Cta? {
Expand Down Expand Up @@ -360,21 +346,22 @@ class CtaViewModel @Inject constructor(

private fun daxDialogIntroShown(): Boolean = dismissedCtaDao.exists(CtaId.DAX_INTRO)

private fun daxDialogIntroVisitSiteShown(): Boolean = dismissedCtaDao.exists(CtaId.DAX_INTRO_VISIT_SITE)

// We only want to show New Tab when the Home CTAs from Onboarding has finished
// https://app.asana.com/0/1157893581871903/1207769731595075/f
suspend fun areBubbleDaxDialogsCompleted(): Boolean {
return withContext(dispatchers.io()) {
val noBrowserCtaExperiment = extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled()
val bubbleCtasShown = daxDialogEndShown() && (daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogTrackersFoundShown())
noBrowserCtaExperiment || bubbleCtasShown || hideTips() || !userStageStore.daxOnboardingActive()
noBrowserCtaExperiment || daxDialogEndShown() || hideTips()
}
}

suspend fun areInContextDaxDialogsCompleted(): Boolean {
return withContext(dispatchers.io()) {
val noBrowserCtaExperiment = extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled()
val inContextDaxCtasShown = daxDialogSerpShown() && daxDialogTrackersFoundShown() && daxDialogFireEducationShown() && daxDialogEndShown()
noBrowserCtaExperiment || inContextDaxCtasShown || hideTips() || !userStageStore.daxOnboardingActive()
noBrowserCtaExperiment || inContextDaxCtasShown || hideTips()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,16 +166,12 @@ class OnboardingDaxDialogTests {
}

@Test
fun whenDaxDialogEndShownButOtherDialogsNotShownThenOnboardingNotComplete() = runTest {
whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
whenever(settingsDataStore.hideTips).thenReturn(false)
whenever(dismissedCtaDao.exists(DAX_END)).thenReturn(true)
whenever(dismissedCtaDao.exists(DAX_DIALOG_OTHER)).thenReturn(false)
whenever(dismissedCtaDao.exists(DAX_DIALOG_TRACKERS_FOUND)).thenReturn(false)
whenever(dismissedCtaDao.exists(DAX_DIALOG_NETWORK)).thenReturn(false)
fun whenOnboardingDismissedThenOnboardingComplete() = runTest {
whenever(settingsDataStore.hideTips).thenReturn(true)
whenever(dismissedCtaDao.exists(DAX_END)).thenReturn(false)

val onboardingComplete = testee.areBubbleDaxDialogsCompleted()
assertFalse(onboardingComplete)
assertTrue(onboardingComplete)
}

@Test
Expand Down Expand Up @@ -221,17 +217,4 @@ class OnboardingDaxDialogTests {
val inContextDaxDialogsComplete = testee.areInContextDaxDialogsCompleted()
assertTrue(inContextDaxDialogsComplete)
}

@Test
fun whenOnboardingCompleteThenAreInContextDialogsCompletedIsTrue() = runTest {
whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.ESTABLISHED)
whenever(settingsDataStore.hideTips).thenReturn(false)
whenever(dismissedCtaDao.exists(DAX_DIALOG_SERP)).thenReturn(false)
whenever(dismissedCtaDao.exists(DAX_DIALOG_TRACKERS_FOUND)).thenReturn(false)
whenever(dismissedCtaDao.exists(DAX_FIRE_BUTTON)).thenReturn(false)
whenever(dismissedCtaDao.exists(DAX_END)).thenReturn(false)

val inContextDaxDialogsComplete = testee.areInContextDaxDialogsCompleted()
assertTrue(inContextDaxDialogsComplete)
}
}
Loading