diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt index 7ac4ae728058..b76615c17729 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt @@ -222,6 +222,7 @@ import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.common.utils.device.DeviceInfo import com.duckduckgo.common.utils.plugins.PluginPoint import com.duckduckgo.common.utils.plugins.headers.CustomHeadersProvider +import com.duckduckgo.daxprompts.impl.ReactivateUsersExperiment import com.duckduckgo.downloads.api.DownloadStateListener import com.duckduckgo.downloads.api.FileDownloader import com.duckduckgo.downloads.api.FileDownloader.PendingFileDownload @@ -553,6 +554,7 @@ class BrowserTabViewModelTest { private val mockSiteErrorHandler: StringSiteErrorHandler = mock() private val mockSiteHttpErrorHandler: HttpCodeSiteErrorHandler = mock() private val mockSubscriptionsJSHelper: SubscriptionsJSHelper = mock() + private val mockReactivateUsersExperiment: ReactivateUsersExperiment = mock() private val selectedTab = TabEntity("TAB_ID", "https://example.com", position = 0, sourceTabId = "TAB_ID_SOURCE") @@ -728,7 +730,14 @@ class BrowserTabViewModelTest { httpErrorPixels = { mockHttpErrorPixels }, duckPlayer = mockDuckPlayer, duckChat = mockDuckChat, - duckPlayerJSHelper = DuckPlayerJSHelper(mockDuckPlayer, mockAppBuildConfig, mockPixel, mockDuckDuckGoUrlDetector, mockPagesSettingPlugin), + duckPlayerJSHelper = DuckPlayerJSHelper( + mockDuckPlayer, + mockAppBuildConfig, + mockPixel, + mockDuckDuckGoUrlDetector, + mockPagesSettingPlugin, + mockReactivateUsersExperiment, + ), duckChatJSHelper = mockDuckChatJSHelper, refreshPixelSender = refreshPixelSender, changeOmnibarPositionFeature = changeOmnibarPositionFeature, diff --git a/app/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/DefaultBrowserDetector.kt b/app/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/DefaultBrowserDetector.kt index 001d12c65d1e..6098082142bf 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/DefaultBrowserDetector.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/DefaultBrowserDetector.kt @@ -33,12 +33,6 @@ import com.squareup.anvil.annotations.ContributesMultibinding import javax.inject.Inject import timber.log.Timber -interface DefaultBrowserDetector { - fun deviceSupportsDefaultBrowserConfiguration(): Boolean - fun isDefaultBrowser(): Boolean - fun hasDefaultBrowser(): Boolean -} - @ContributesMultibinding(scope = AppScope::class, boundType = BrowserFeatureStateReporterPlugin::class) @ContributesBinding(scope = AppScope::class, boundType = DefaultBrowserDetector::class) class AndroidDefaultBrowserDetector @Inject constructor( diff --git a/app/src/main/java/com/duckduckgo/app/browser/duckplayer/DuckPlayerJSHelper.kt b/app/src/main/java/com/duckduckgo/app/browser/duckplayer/DuckPlayerJSHelper.kt index 762f57406621..fca31102e507 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/duckplayer/DuckPlayerJSHelper.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/duckplayer/DuckPlayerJSHelper.kt @@ -44,6 +44,7 @@ import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Daily import com.duckduckgo.appbuildconfig.api.AppBuildConfig import com.duckduckgo.common.utils.plugins.PluginPoint +import com.duckduckgo.daxprompts.impl.ReactivateUsersExperiment import com.duckduckgo.duckplayer.api.DuckPlayer import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerOrigin.AUTO import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerOrigin.OVERLAY @@ -74,6 +75,7 @@ class DuckPlayerJSHelper @Inject constructor( private val pixel: Pixel, private val duckDuckGoUrlDetector: DuckDuckGoUrlDetector, private val pagesSettingPlugin: PluginPoint, + private val reactivateUsersExperiment: ReactivateUsersExperiment, ) { private suspend fun getUserPreferences(featureName: String, method: String, id: String): JsCallbackData { val userValues = duckPlayer.getUserPreferences() @@ -175,6 +177,9 @@ class DuckPlayerJSHelper @Inject constructor( data.getJSONObject("params").getString(it) } duckPlayer.sendDuckPlayerPixel(pixelName, paramsMap) + if (pixelName == "play.use") { + reactivateUsersExperiment.fireDuckPlayerUseIfInExperiment() + } } suspend fun processJsCallbackMessage( diff --git a/app/src/main/java/com/duckduckgo/app/launch/LaunchBridgeActivity.kt b/app/src/main/java/com/duckduckgo/app/launch/LaunchBridgeActivity.kt index 6796c4339c29..ef1ee4d90d96 100644 --- a/app/src/main/java/com/duckduckgo/app/launch/LaunchBridgeActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/launch/LaunchBridgeActivity.kt @@ -17,6 +17,8 @@ package com.duckduckgo.app.launch import android.os.Bundle +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.lifecycleScope import com.duckduckgo.anvil.annotations.InjectWith @@ -24,14 +26,48 @@ import com.duckduckgo.app.browser.BrowserActivity import com.duckduckgo.app.browser.R import com.duckduckgo.app.onboarding.ui.OnboardingActivity import com.duckduckgo.common.ui.DuckDuckGoActivity +import com.duckduckgo.daxprompts.api.DaxPromptBrowserComparisonNoParams +import com.duckduckgo.daxprompts.api.DaxPromptDuckPlayerNoParams +import com.duckduckgo.daxprompts.impl.ui.DaxPromptBrowserComparisonActivity.Companion.DAX_PROMPT_BROWSER_COMPARISON_SET_DEFAULT_EXTRA +import com.duckduckgo.daxprompts.impl.ui.DaxPromptDuckPlayerActivity.Companion.DAX_PROMPT_DUCK_PLAYER_ACTIVITY_URL_EXTRA import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.navigation.api.GlobalActivityStarter +import javax.inject.Inject import kotlinx.coroutines.launch +import timber.log.Timber @InjectWith(ActivityScope::class) class LaunchBridgeActivity : DuckDuckGoActivity() { private val viewModel: LaunchViewModel by bindViewModel() + private val startDaxPromptDuckPlayerActivityForResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode == RESULT_OK) { + val url = result.data?.getStringExtra(DAX_PROMPT_DUCK_PLAYER_ACTIVITY_URL_EXTRA) + Timber.d("Received RESULT_OK from DaxPromptDuckPlayerActivity with extra: $url.") + viewModel.onDaxPromptDuckPlayerActivityResult(url) + } else { + Timber.d("Received non-OK result from DaxPromptDuckPlayerActivity.") + viewModel.onDaxPromptDuckPlayerActivityResult() + } + } + + private val startDaxPromptBrowserComparisonActivityForResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode == RESULT_OK) { + val show = result.data?.getBooleanExtra(DAX_PROMPT_BROWSER_COMPARISON_SET_DEFAULT_EXTRA, false) + Timber.d("Received RESULT_OK from DaxPromptBrowserComparisonActivity with extra: $show") + viewModel.onDaxPromptBrowserComparisonActivityResult(show) + } else { + Timber.d("Received non-OK result from DaxPromptBrowserComparisonActivity") + viewModel.onDaxPromptBrowserComparisonActivityResult() + } + } + + @Inject + lateinit var globalActivityStarter: GlobalActivityStarter + override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) @@ -59,6 +95,24 @@ class LaunchBridgeActivity : DuckDuckGoActivity() { is LaunchViewModel.Command.Home -> { showHome() } + + is LaunchViewModel.Command.DaxPromptDuckPlayer -> { + showDaxPromptDuckPlayer() + } + + is LaunchViewModel.Command.CloseDaxPrompt -> { + lifecycleScope.launch { viewModel.showOnboardingOrHome() } + } + + is LaunchViewModel.Command.PlayVideoInDuckPlayer -> { + startActivity(BrowserActivity.intent(this, queryExtra = it.url)) + overridePendingTransition(0, 0) + finish() + } + + is LaunchViewModel.Command.DaxPromptBrowserComparison -> { + showDaxPromptBrowserComparison() + } } } @@ -72,4 +126,18 @@ class LaunchBridgeActivity : DuckDuckGoActivity() { overridePendingTransition(0, 0) finish() } + + private fun showDaxPromptDuckPlayer() { + val intentDaxPromptDuckPlayer = + globalActivityStarter.startIntent(this, DaxPromptDuckPlayerNoParams) + intentDaxPromptDuckPlayer?.let { startDaxPromptDuckPlayerActivityForResult.launch(it) } + } + + private fun showDaxPromptBrowserComparison() { + val intentDaxPromptComparisonChart = + globalActivityStarter.startIntent(this, DaxPromptBrowserComparisonNoParams) + intentDaxPromptComparisonChart?.let { + startDaxPromptBrowserComparisonActivityForResult.launch(it) + } + } } diff --git a/app/src/main/java/com/duckduckgo/app/launch/LaunchViewModel.kt b/app/src/main/java/com/duckduckgo/app/launch/LaunchViewModel.kt index d273b98eb3f8..1c2132b96ac0 100644 --- a/app/src/main/java/com/duckduckgo/app/launch/LaunchViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/launch/LaunchViewModel.kt @@ -18,11 +18,17 @@ package com.duckduckgo.app.launch import androidx.lifecycle.ViewModel import com.duckduckgo.anvil.annotations.ContributesViewModel +import com.duckduckgo.app.global.install.AppInstallStore import com.duckduckgo.app.onboarding.store.UserStageStore import com.duckduckgo.app.onboarding.store.isNewUser import com.duckduckgo.app.referral.AppInstallationReferrerStateListener import com.duckduckgo.app.referral.AppInstallationReferrerStateListener.Companion.MAX_REFERRER_WAIT_TIME_MS import com.duckduckgo.common.utils.SingleLiveEvent +import com.duckduckgo.daxprompts.api.DaxPrompts +import com.duckduckgo.daxprompts.api.DaxPrompts.ActionType.NONE +import com.duckduckgo.daxprompts.api.DaxPrompts.ActionType.SHOW_CONTROL +import com.duckduckgo.daxprompts.api.DaxPrompts.ActionType.SHOW_VARIANT_BROWSER_COMPARISON +import com.duckduckgo.daxprompts.api.DaxPrompts.ActionType.SHOW_VARIANT_DUCKPLAYER import com.duckduckgo.di.scopes.ActivityScope import javax.inject.Inject import kotlinx.coroutines.withTimeoutOrNull @@ -32,6 +38,8 @@ import timber.log.Timber class LaunchViewModel @Inject constructor( private val userStageStore: UserStageStore, private val appReferrerStateListener: AppInstallationReferrerStateListener, + private val daxPrompts: DaxPrompts, + private val appInstallStore: AppInstallStore, ) : ViewModel() { @@ -40,11 +48,34 @@ class LaunchViewModel @Inject constructor( sealed class Command { data object Onboarding : Command() data class Home(val replaceExistingSearch: Boolean = false) : Command() + data object DaxPromptDuckPlayer : Command() + data class PlayVideoInDuckPlayer(val url: String) : Command() + data object DaxPromptBrowserComparison : Command() + data object CloseDaxPrompt : Command() } suspend fun determineViewToShow() { waitForReferrerData() + when (daxPrompts.evaluate()) { + SHOW_CONTROL, NONE -> { + Timber.d("Control / None action") + showOnboardingOrHome() + } + + SHOW_VARIANT_DUCKPLAYER -> { + Timber.d("Variant Duck Player action") + command.value = Command.DaxPromptDuckPlayer + } + + SHOW_VARIANT_BROWSER_COMPARISON -> { + Timber.d("Variant Browser Comparison action") + command.value = Command.DaxPromptBrowserComparison + } + } + } + + suspend fun showOnboardingOrHome() { if (userStageStore.isNewUser()) { command.value = Command.Onboarding } else { @@ -52,6 +83,21 @@ class LaunchViewModel @Inject constructor( } } + fun onDaxPromptDuckPlayerActivityResult(url: String? = null) { + if (url != null) { + command.value = Command.PlayVideoInDuckPlayer(url) + } else { + command.value = Command.CloseDaxPrompt + } + } + + fun onDaxPromptBrowserComparisonActivityResult(showComparisonChart: Boolean? = false) { + if (showComparisonChart != null) { + appInstallStore.defaultBrowser = showComparisonChart + } + command.value = Command.CloseDaxPrompt + } + private suspend fun waitForReferrerData() { val startTime = System.currentTimeMillis() diff --git a/app/src/test/java/com/duckduckgo/app/launch/LaunchViewModelTest.kt b/app/src/test/java/com/duckduckgo/app/launch/LaunchViewModelTest.kt index 673efb56cd95..036175e96651 100644 --- a/app/src/test/java/com/duckduckgo/app/launch/LaunchViewModelTest.kt +++ b/app/src/test/java/com/duckduckgo/app/launch/LaunchViewModelTest.kt @@ -18,12 +18,17 @@ package com.duckduckgo.app.launch import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer +import com.duckduckgo.app.global.install.AppInstallStore +import com.duckduckgo.app.launch.LaunchViewModel.Command.DaxPromptBrowserComparison +import com.duckduckgo.app.launch.LaunchViewModel.Command.DaxPromptDuckPlayer import com.duckduckgo.app.launch.LaunchViewModel.Command.Home import com.duckduckgo.app.launch.LaunchViewModel.Command.Onboarding import com.duckduckgo.app.onboarding.store.AppStage import com.duckduckgo.app.onboarding.store.UserStageStore import com.duckduckgo.app.referral.StubAppReferrerFoundStateListener import com.duckduckgo.common.test.CoroutineTestRule +import com.duckduckgo.daxprompts.api.DaxPrompts +import com.duckduckgo.daxprompts.api.DaxPrompts.ActionType import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Rule @@ -44,6 +49,8 @@ class LaunchViewModelTest { private val userStageStore = mock() private val mockCommandObserver: Observer = mock() + private val mockDaxPrompts: DaxPrompts = mock() + private val mockAppInstallStore: AppInstallStore = mock() private lateinit var testee: LaunchViewModel @@ -57,7 +64,10 @@ class LaunchViewModelTest { testee = LaunchViewModel( userStageStore, StubAppReferrerFoundStateListener("xx"), + mockDaxPrompts, + mockAppInstallStore, ) + whenever(mockDaxPrompts.evaluate()).thenReturn(ActionType.NONE) whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.NEW) testee.command.observeForever(mockCommandObserver) @@ -71,7 +81,10 @@ class LaunchViewModelTest { testee = LaunchViewModel( userStageStore, StubAppReferrerFoundStateListener("xx", mockDelayMs = 1_000), + mockDaxPrompts, + mockAppInstallStore, ) + whenever(mockDaxPrompts.evaluate()).thenReturn(ActionType.NONE) whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.NEW) testee.command.observeForever(mockCommandObserver) @@ -85,7 +98,10 @@ class LaunchViewModelTest { testee = LaunchViewModel( userStageStore, StubAppReferrerFoundStateListener("xx", mockDelayMs = Long.MAX_VALUE), + mockDaxPrompts, + mockAppInstallStore, ) + whenever(mockDaxPrompts.evaluate()).thenReturn(ActionType.NONE) whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.NEW) testee.command.observeForever(mockCommandObserver) @@ -99,7 +115,10 @@ class LaunchViewModelTest { testee = LaunchViewModel( userStageStore, StubAppReferrerFoundStateListener("xx"), + mockDaxPrompts, + mockAppInstallStore, ) + whenever(mockDaxPrompts.evaluate()).thenReturn(ActionType.NONE) whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING) testee.command.observeForever(mockCommandObserver) testee.determineViewToShow() @@ -111,7 +130,10 @@ class LaunchViewModelTest { testee = LaunchViewModel( userStageStore, StubAppReferrerFoundStateListener("xx", mockDelayMs = 1_000), + mockDaxPrompts, + mockAppInstallStore, ) + whenever(mockDaxPrompts.evaluate()).thenReturn(ActionType.NONE) whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING) testee.command.observeForever(mockCommandObserver) testee.determineViewToShow() @@ -123,10 +145,43 @@ class LaunchViewModelTest { testee = LaunchViewModel( userStageStore, StubAppReferrerFoundStateListener("xx", mockDelayMs = Long.MAX_VALUE), + mockDaxPrompts, + mockAppInstallStore, ) + whenever(mockDaxPrompts.evaluate()).thenReturn(ActionType.NONE) whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING) testee.command.observeForever(mockCommandObserver) testee.determineViewToShow() verify(mockCommandObserver).onChanged(any()) } + + @Test + fun whenEvaluateReturnsDuckPlayerVariantThenCommandIsDaxPromptDuckPlayer() = runTest { + testee = LaunchViewModel( + userStageStore, + StubAppReferrerFoundStateListener("xx", mockDelayMs = Long.MAX_VALUE), + mockDaxPrompts, + mockAppInstallStore, + ) + whenever(mockDaxPrompts.evaluate()).thenReturn(ActionType.SHOW_VARIANT_DUCKPLAYER) + whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING) + testee.command.observeForever(mockCommandObserver) + testee.determineViewToShow() + verify(mockCommandObserver).onChanged(any()) + } + + @Test + fun whenEvaluateReturnsBrowserComparisonVariantThenCommandIsDaxPromptBrowserComparison() = runTest { + testee = LaunchViewModel( + userStageStore, + StubAppReferrerFoundStateListener("xx", mockDelayMs = Long.MAX_VALUE), + mockDaxPrompts, + mockAppInstallStore, + ) + whenever(mockDaxPrompts.evaluate()).thenReturn(ActionType.SHOW_VARIANT_BROWSER_COMPARISON) + whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING) + testee.command.observeForever(mockCommandObserver) + testee.determineViewToShow() + verify(mockCommandObserver).onChanged(any()) + } } diff --git a/browser-api/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/DefaultBrowserDetector.kt b/browser-api/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/DefaultBrowserDetector.kt new file mode 100644 index 000000000000..4e390a878009 --- /dev/null +++ b/browser-api/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/DefaultBrowserDetector.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.browser.defaultbrowsing + +interface DefaultBrowserDetector { + fun deviceSupportsDefaultBrowserConfiguration(): Boolean + fun isDefaultBrowser(): Boolean + fun hasDefaultBrowser(): Boolean +} diff --git a/common/common-ui/src/main/res/drawable/ic_ads_blocked_color_24.xml b/common/common-ui/src/main/res/drawable/ic_ads_blocked_color_24.xml index 2d50e2c75390..157529632353 100644 --- a/common/common-ui/src/main/res/drawable/ic_ads_blocked_color_24.xml +++ b/common/common-ui/src/main/res/drawable/ic_ads_blocked_color_24.xml @@ -1,3 +1,19 @@ + + + { + return listOf( + MetricsPixel( + metric = METRIC_DUCK_PLAYER_USE, + value = "1", + toggle = toggles.reactivateUsersExperimentMay25(), + conversionWindow = listOf( + ConversionWindow(lowerWindow = 0, upperWindow = 0), + ConversionWindow(lowerWindow = 0, upperWindow = 5), + ConversionWindow(lowerWindow = 0, upperWindow = 7), + ConversionWindow(lowerWindow = 0, upperWindow = 14), + ConversionWindow(lowerWindow = 5, upperWindow = 7), + ConversionWindow(lowerWindow = 8, upperWindow = 14), + ), + ), + MetricsPixel( + metric = METRIC_SET_BROWSER_AS_DEFAULT, + value = "1", + toggle = toggles.reactivateUsersExperimentMay25(), + conversionWindow = listOf( + ConversionWindow(lowerWindow = 0, upperWindow = 0), + ConversionWindow(lowerWindow = 0, upperWindow = 7), + ConversionWindow(lowerWindow = 5, upperWindow = 7), + ConversionWindow(lowerWindow = 8, upperWindow = 14), + ), + ), + MetricsPixel( + metric = METRIC_DUCK_PLAYER_CLICK, + value = "1", + toggle = toggles.reactivateUsersExperimentMay25(), + conversionWindow = listOf( + ConversionWindow(lowerWindow = 0, upperWindow = 0), + ), + ), + MetricsPixel( + metric = METRIC_CHOOSE_YOUR_BROWSER_CLICK, + value = "1", + toggle = toggles.reactivateUsersExperimentMay25(), + conversionWindow = listOf( + ConversionWindow(lowerWindow = 0, upperWindow = 0), + ), + ), + MetricsPixel( + metric = METRIC_CLOSE_SCREEN, + value = "1", + toggle = toggles.reactivateUsersExperimentMay25(), + conversionWindow = listOf( + ConversionWindow(lowerWindow = 0, upperWindow = 0), + ), + ), + MetricsPixel( + metric = METRIC_PLUS_EVEN_MORE_PROTECTIONS_LINK_CLICK, + value = "1", + toggle = toggles.reactivateUsersExperimentMay25(), + conversionWindow = listOf( + ConversionWindow(lowerWindow = 0, upperWindow = 0), + ), + ), + ) + } + + suspend fun getDuckPlayerUseMetric(): MetricsPixel? { + return this.getMetrics().firstOrNull { it.metric == METRIC_DUCK_PLAYER_USE } + } + + suspend fun getSetBrowserAsDefaultMetric(): MetricsPixel? { + return this.getMetrics().firstOrNull { it.metric == METRIC_SET_BROWSER_AS_DEFAULT } + } + + suspend fun getDuckPlayerClickMetric(): MetricsPixel? { + return this.getMetrics().firstOrNull { it.metric == METRIC_DUCK_PLAYER_CLICK } + } + + suspend fun getChooseYourBrowserClickMetric(): MetricsPixel? { + return this.getMetrics().firstOrNull { it.metric == METRIC_CHOOSE_YOUR_BROWSER_CLICK } + } + + suspend fun getCloseScreenMetric(): MetricsPixel? { + return this.getMetrics().firstOrNull { it.metric == METRIC_CLOSE_SCREEN } + } + + suspend fun getPlusEvenMoreProtectionsLinkClickMetric(): MetricsPixel? { + return this.getMetrics().firstOrNull { it.metric == METRIC_PLUS_EVEN_MORE_PROTECTIONS_LINK_CLICK } + } + + companion object { + internal const val METRIC_DUCK_PLAYER_USE = "duckPlayerUse" + internal const val METRIC_SET_BROWSER_AS_DEFAULT = "setBrowserAsDefault" + internal const val METRIC_DUCK_PLAYER_CLICK = "duckPlayerClick" + internal const val METRIC_CHOOSE_YOUR_BROWSER_CLICK = "chooseYourBrowserClick" + internal const val METRIC_CLOSE_SCREEN = "closeScreen" + internal const val METRIC_PLUS_EVEN_MORE_PROTECTIONS_LINK_CLICK = "plusEvenMoreProtectionsLinkClick" + } +} diff --git a/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/RealDaxPrompts.kt b/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/RealDaxPrompts.kt index 6e4789785b91..e656598ea35c 100644 --- a/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/RealDaxPrompts.kt +++ b/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/RealDaxPrompts.kt @@ -16,27 +16,87 @@ package com.duckduckgo.daxprompts.impl +import com.duckduckgo.app.browser.defaultbrowsing.DefaultBrowserDetector +import com.duckduckgo.app.global.DefaultRoleBrowserDialog +import com.duckduckgo.browser.api.UserBrowserProperties import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.daxprompts.api.DaxPrompts +import com.duckduckgo.daxprompts.api.DaxPrompts.ActionType import com.duckduckgo.daxprompts.impl.repository.DaxPromptsRepository import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.duckplayer.api.DuckPlayer +import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk import com.squareup.anvil.annotations.ContributesBinding import dagger.SingleInstanceIn +import java.util.Date import javax.inject.Inject import kotlinx.coroutines.withContext +private const val EXISTING_USER_DAY_COUNT_THRESHOLD = 28 +private const val EXISTING_USER_DAYS_INACTIVE_MILLIS = 7 * 24 * 60 * 60 * 1000 // 7 days + @SingleInstanceIn(AppScope::class) @ContributesBinding(AppScope::class, boundType = DaxPrompts::class) class RealDaxPrompts @Inject constructor( private val daxPromptsRepository: DaxPromptsRepository, + private val reactivateUsersExperiment: ReactivateUsersExperiment, + private val userBrowserProperties: UserBrowserProperties, + private val defaultBrowserDetector: DefaultBrowserDetector, + private val defaultRoleBrowserDialog: DefaultRoleBrowserDialog, + private val duckPlayer: DuckPlayer, private val dispatchers: DispatcherProvider, ) : DaxPrompts { - override suspend fun shouldShowDuckPlayerPrompt(): Boolean = withContext(dispatchers.io()) { + override suspend fun evaluate(): ActionType { + return withContext(dispatchers.io()) { + if (!isEligible()) { + return@withContext ActionType.NONE + } + + if (reactivateUsersExperiment.isControl()) { + ActionType.SHOW_CONTROL + } else if (reactivateUsersExperiment.isDuckPlayerPrompt()) { + if (shouldShowDuckPlayerPrompt()) ActionType.SHOW_VARIANT_DUCKPLAYER else ActionType.NONE + } else if (reactivateUsersExperiment.isBrowserComparisonPrompt()) { + if (shouldShowBrowserComparisonPrompt()) ActionType.SHOW_VARIANT_BROWSER_COMPARISON else ActionType.NONE + } else { + ActionType.NONE + } + } + } + + private suspend fun isEligible(): Boolean { + return withContext(dispatchers.io()) { + if (userBrowserProperties.daysSinceInstalled() < EXISTING_USER_DAY_COUNT_THRESHOLD) { + return@withContext false + } + + val sevenDaysAgo = Date(Date().time - EXISTING_USER_DAYS_INACTIVE_MILLIS) + if (userBrowserProperties.daysUsedSince(sevenDaysAgo) > 0L) { + return@withContext false + } + + if (duckPlayer.getDuckPlayerState() != DuckPlayer.DuckPlayerState.ENABLED) { + return@withContext false + } + + if (duckPlayer.getUserPreferences().privatePlayerMode != AlwaysAsk) { + return@withContext false + } + + if (defaultBrowserDetector.isDefaultBrowser()) { + return@withContext false + } + + defaultRoleBrowserDialog.shouldShowDialog() + } + } + + private suspend fun shouldShowDuckPlayerPrompt(): Boolean = withContext(dispatchers.io()) { daxPromptsRepository.getDaxPromptsShowDuckPlayer() } - override suspend fun shouldShowBrowserComparisonPrompt(): Boolean = withContext(dispatchers.io()) { + private suspend fun shouldShowBrowserComparisonPrompt(): Boolean = withContext(dispatchers.io()) { daxPromptsRepository.getDaxPromptsShowBrowserComparison() } } diff --git a/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonActivity.kt b/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonActivity.kt index d4d33e3d8a06..6291a993db56 100644 --- a/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonActivity.kt +++ b/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonActivity.kt @@ -31,6 +31,7 @@ import android.view.View import android.view.WindowManager import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.lifecycle.Lifecycle @@ -77,6 +78,11 @@ class DaxPromptBrowserComparisonActivity : DuckDuckGoActivity() { super.onCreate(savedInstanceState) setContentView(binding.root) + if (isDarkThemeEnabled()) { + renderDarkUi() + } else { + renderLightUi() + } configureClickableLinks() setupListeners() setupObservers() @@ -88,6 +94,29 @@ class DaxPromptBrowserComparisonActivity : DuckDuckGoActivity() { markAsShown() } + private fun renderDarkUi() { + binding.orangeShape.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.background_shape_dark)) + binding.daxPromptBrowserComparisonContainer.setBackgroundColor(getColor(R.color.daxPromptBackgroundDark)) + binding.daxPromptBrowserComparisonMessageContainer.background = ContextCompat.getDrawable(this, R.drawable.background_dax_message_dark) + binding.daxPromptBrowserComparisonPrimaryButton.background = ContextCompat.getDrawable(this, R.drawable.background_button_dark_with_ripple) + binding.daxPromptBrowserComparisonPrimaryButton.setTextColor(getColor(com.duckduckgo.mobile.android.R.color.black)) + binding.daxPromptBrowserComparisonChart.featureIcon1.setImageDrawable( + ContextCompat.getDrawable( + this, + R.drawable.ic_comparison_chart_search_dark, + ), + ) + } + + private fun renderLightUi() { + binding.orangeShape.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.background_shape)) + binding.daxPromptBrowserComparisonContainer.setBackgroundColor(getColor(R.color.daxPromptBackground)) + binding.daxPromptBrowserComparisonMessageContainer.background = ContextCompat.getDrawable(this, R.drawable.background_dax_message) + binding.daxPromptBrowserComparisonPrimaryButton.background = ContextCompat.getDrawable(this, R.drawable.background_button_with_ripple) + binding.daxPromptBrowserComparisonPrimaryButton.setTextColor(getColor(com.duckduckgo.mobile.android.R.color.white)) + binding.daxPromptBrowserComparisonChart.featureIcon1.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_comparison_chart_search)) + } + private fun configureClickableLinks() { with(binding.daxPromptBrowserComparisonMoreLink) { text = addClickableLinks() diff --git a/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonViewModel.kt b/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonViewModel.kt index 5fe97124cd0f..742b0f851e13 100644 --- a/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonViewModel.kt +++ b/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonViewModel.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.duckduckgo.anvil.annotations.ContributesViewModel import com.duckduckgo.app.global.DefaultRoleBrowserDialog +import com.duckduckgo.daxprompts.impl.ReactivateUsersExperiment import com.duckduckgo.daxprompts.impl.repository.DaxPromptsRepository import com.duckduckgo.di.scopes.ActivityScope import javax.inject.Inject @@ -36,6 +37,7 @@ import logcat.logcat class DaxPromptBrowserComparisonViewModel @Inject constructor( private val defaultRoleBrowserDialog: DefaultRoleBrowserDialog, private val daxPromptsRepository: DaxPromptsRepository, + private val reactivateUsersExperiment: ReactivateUsersExperiment, private val applicationContext: Context, ) : ViewModel() { @@ -47,6 +49,7 @@ class DaxPromptBrowserComparisonViewModel @Inject constructor( fun onMoreLinkClicked() { viewModelScope.launch { + reactivateUsersExperiment.firePlusEvenMoreProtectionsLinkClick() command.send(Command.OpenDetailsPage(BROWSER_COMPARISON_MORE_URL)) } } @@ -54,6 +57,7 @@ class DaxPromptBrowserComparisonViewModel @Inject constructor( fun onCloseButtonClicked() { viewModelScope.launch { command.send(Command.CloseScreen()) + reactivateUsersExperiment.fireCloseScreen() } } @@ -63,6 +67,7 @@ class DaxPromptBrowserComparisonViewModel @Inject constructor( val intent = defaultRoleBrowserDialog.createIntent(applicationContext) if (intent != null) { command.send(Command.BrowserComparisonChart(intent)) + reactivateUsersExperiment.fireChooseYourBrowserClick() } else { logcat { "Default browser dialog not available" } command.send(Command.CloseScreen()) @@ -78,6 +83,7 @@ class DaxPromptBrowserComparisonViewModel @Inject constructor( defaultRoleBrowserDialog.dialogShown() viewModelScope.launch { command.send(Command.CloseScreen(true)) + reactivateUsersExperiment.fireSetBrowserAsDefault() } } diff --git a/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerActivity.kt b/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerActivity.kt index 9009fb3421bd..475ed3e3786d 100644 --- a/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerActivity.kt +++ b/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerActivity.kt @@ -20,6 +20,7 @@ import android.content.Intent import android.graphics.Color import android.os.Bundle import android.view.WindowManager +import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.lifecycle.Lifecycle @@ -30,6 +31,7 @@ import com.duckduckgo.anvil.annotations.InjectWith import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.daxprompts.api.DaxPromptDuckPlayerNoParams +import com.duckduckgo.daxprompts.impl.R import com.duckduckgo.daxprompts.impl.databinding.ActivityDaxPromptDuckPlayerBinding import com.duckduckgo.di.scopes.ActivityScope import kotlinx.coroutines.flow.launchIn @@ -45,6 +47,11 @@ class DaxPromptDuckPlayerActivity : DuckDuckGoActivity() { super.onCreate(savedInstanceState) setContentView(binding.root) + if (isDarkThemeEnabled()) { + renderDarkUi() + } else { + renderLightUi() + } setupListeners() setupObservers() } @@ -55,6 +62,20 @@ class DaxPromptDuckPlayerActivity : DuckDuckGoActivity() { markAsShown() } + private fun renderDarkUi() { + binding.orangeShape.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.background_shape_dark)) + binding.daxPromptDuckPlayerContainer.setBackgroundColor(getColor(R.color.daxPromptBackgroundDark)) + binding.daxPromptDuckPlayerMessageContainer.background = ContextCompat.getDrawable(this, R.drawable.background_dax_message_dark) + binding.daxPromptDuckPlayerPrimaryButton.background = ContextCompat.getDrawable(this, R.drawable.background_button_dark_with_ripple) + } + + private fun renderLightUi() { + binding.orangeShape.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.background_shape)) + binding.daxPromptDuckPlayerContainer.setBackgroundColor(getColor(R.color.daxPromptBackground)) + binding.daxPromptDuckPlayerMessageContainer.background = ContextCompat.getDrawable(this, R.drawable.background_dax_message) + binding.daxPromptDuckPlayerPrimaryButton.background = ContextCompat.getDrawable(this, R.drawable.background_button_with_ripple) + } + private fun setupListeners() { binding.daxPromptDuckPlayerCloseButton.setOnClickListener { viewModel.onCloseButtonClicked() @@ -62,9 +83,6 @@ class DaxPromptDuckPlayerActivity : DuckDuckGoActivity() { binding.daxPromptDuckPlayerPrimaryButton.setOnClickListener { viewModel.onPrimaryButtonClicked() } - binding.daxPromptDuckPlayerSecondaryButton.setOnClickListener { - viewModel.onSecondaryButtonClicked() - } } private fun setupObservers() { @@ -88,12 +106,6 @@ class DaxPromptDuckPlayerActivity : DuckDuckGoActivity() { setResult(RESULT_OK, resultIntent) finish() } - - is DaxPromptDuckPlayerViewModel.Command.Dismiss -> { - viewModel.updateDuckPlayerSettings() - setResult(RESULT_OK) - finish() - } } } diff --git a/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerViewModel.kt b/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerViewModel.kt index b9d9f04b4346..b233eb2bd66e 100644 --- a/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerViewModel.kt +++ b/dax-prompts/dax-prompts-impl/src/main/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerViewModel.kt @@ -19,10 +19,9 @@ package com.duckduckgo.daxprompts.impl.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.duckduckgo.anvil.annotations.ContributesViewModel +import com.duckduckgo.daxprompts.impl.ReactivateUsersExperiment import com.duckduckgo.daxprompts.impl.repository.DaxPromptsRepository import com.duckduckgo.di.scopes.ActivityScope -import com.duckduckgo.duckplayer.api.DuckPlayer -import com.duckduckgo.duckplayer.api.PrivatePlayerMode import javax.inject.Inject import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel @@ -32,8 +31,8 @@ import kotlinx.coroutines.launch @ContributesViewModel(ActivityScope::class) class DaxPromptDuckPlayerViewModel @Inject constructor( - private val duckPlayer: DuckPlayer, private val daxPromptsRepository: DaxPromptsRepository, + private val reactivateUsersExperiment: ReactivateUsersExperiment, ) : ViewModel() { private val command = Channel(1, BufferOverflow.DROP_OLDEST) @@ -45,24 +44,14 @@ class DaxPromptDuckPlayerViewModel @Inject constructor( fun onCloseButtonClicked() { viewModelScope.launch { command.send(Command.CloseScreen) + reactivateUsersExperiment.fireCloseScreen() } } fun onPrimaryButtonClicked() { viewModelScope.launch { command.send(Command.TryDuckPlayer(DUCK_PLAYER_DEMO_URL)) - } - } - - fun onSecondaryButtonClicked() { - viewModelScope.launch { - command.send(Command.Dismiss) - } - } - - fun updateDuckPlayerSettings() { - viewModelScope.launch { - duckPlayer.setUserPreferences(overlayInteracted = false, privatePlayerMode = PrivatePlayerMode.AlwaysAsk.value) + reactivateUsersExperiment.fireDuckPlayerClick() } } @@ -75,10 +64,9 @@ class DaxPromptDuckPlayerViewModel @Inject constructor( sealed class Command { data object CloseScreen : Command() data class TryDuckPlayer(val url: String) : Command() - data object Dismiss : Command() } companion object { - internal const val DUCK_PLAYER_DEMO_URL = "duck://player/yKWIA-Pys4c" + internal const val DUCK_PLAYER_DEMO_URL = "https://www.youtube.com/watch?v=yKWIA-Pys4c" } } diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button.xml new file mode 100644 index 000000000000..af326f69f70a --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button_dark.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button_dark.xml new file mode 100644 index 000000000000..cb72b4be73d7 --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button_dark.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button_dark_with_ripple.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button_dark_with_ripple.xml new file mode 100644 index 000000000000..6e04496de7a0 --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button_dark_with_ripple.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button_with_ripple.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button_with_ripple.xml new file mode 100644 index 000000000000..288654460e87 --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_button_with_ripple.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_dax_message.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_dax_message.xml index 5c774b3eda23..a74eec18f018 100644 --- a/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_dax_message.xml +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_dax_message.xml @@ -15,9 +15,7 @@ --> - + + android:radius="36dp" /> \ No newline at end of file diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_dax_message_dark.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_dax_message_dark.xml new file mode 100644 index 000000000000..6da60dbae5fa --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_dax_message_dark.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_shape.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_shape.xml new file mode 100644 index 000000000000..949bd13769b3 --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_shape.xml @@ -0,0 +1,9 @@ + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_shape_dark.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_shape_dark.xml new file mode 100644 index 000000000000..970f1d6bdbe0 --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/background_shape_dark.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/dax.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/dax.xml index 044703dbbb2a..2a14a4f6c42c 100644 --- a/dax-prompts/dax-prompts-impl/src/main/res/drawable/dax.xml +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/dax.xml @@ -1,28 +1,12 @@ - - - + - + @@ -181,7 +165,7 @@ - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/dax_with_magnifying_glass.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/dax_with_magnifying_glass.xml new file mode 100644 index 000000000000..fb01d3bd8c0e --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/dax_with_magnifying_glass.xml @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/dax_with_magnifying_glass_no_feet.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/dax_with_magnifying_glass_no_feet.xml new file mode 100644 index 000000000000..6165256898e3 --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/dax_with_magnifying_glass_no_feet.xml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_close_16.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_close_16.xml index d2854cbe2646..c29a44fbf552 100644 --- a/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_close_16.xml +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_close_16.xml @@ -20,6 +20,6 @@ android:viewportWidth="16" android:viewportHeight="16"> \ No newline at end of file diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_ads.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_ads.xml new file mode 100644 index 000000000000..7bd7eeaa46c4 --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_ads.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_cookies.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_cookies.xml new file mode 100644 index 000000000000..59f302d8b31a --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_cookies.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_search.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_search.xml new file mode 100644 index 000000000000..0385596e79dd --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_search.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_search_dark.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_search_dark.xml new file mode 100644 index 000000000000..71679f62236f --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_search_dark.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_shield.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_shield.xml new file mode 100644 index 000000000000..913f1c2945a8 --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_comparison_chart_shield.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_duck_player.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_duck_player.xml index 719594dbec18..e05b2b7abac9 100644 --- a/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_duck_player.xml +++ b/dax-prompts/dax-prompts-impl/src/main/res/drawable/ic_duck_player.xml @@ -15,72 +15,11 @@ --> - - - - - - - - - - - - - - - - + android:width="20dp" + android:height="20dp" + android:viewportWidth="20" + android:viewportHeight="20"> - - - - - - - - - - + android:fillColor="?attr/daxColorButtonPrimaryText" + android:pathData="M15.983,8.699C16.987,9.275 16.987,10.725 15.983,11.301L5.247,17.461C4.247,18.035 3,17.313 3,16.16L3,3.84C3,2.687 4.247,1.965 5.247,2.539L15.983,8.699Z" /> diff --git a/dax-prompts/dax-prompts-impl/src/main/res/drawable/orange_shape.xml b/dax-prompts/dax-prompts-impl/src/main/res/drawable/orange_shape.xml deleted file mode 100644 index 41a8bf9c5412..000000000000 --- a/dax-prompts/dax-prompts-impl/src/main/res/drawable/orange_shape.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - diff --git a/dax-prompts/dax-prompts-impl/src/main/res/layout-land/activity_dax_prompt_browser_comparison.xml b/dax-prompts/dax-prompts-impl/src/main/res/layout-land/activity_dax_prompt_browser_comparison.xml new file mode 100644 index 000000000000..59d527e46f2d --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/layout-land/activity_dax_prompt_browser_comparison.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/layout-land/activity_dax_prompt_duck_player.xml b/dax-prompts/dax-prompts-impl/src/main/res/layout-land/activity_dax_prompt_duck_player.xml new file mode 100644 index 000000000000..b69a56976f89 --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/layout-land/activity_dax_prompt_duck_player.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/layout-sw600dp-land/activity_dax_prompt_browser_comparison.xml b/dax-prompts/dax-prompts-impl/src/main/res/layout-sw600dp-land/activity_dax_prompt_browser_comparison.xml new file mode 100644 index 000000000000..7206ea5bca5e --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/layout-sw600dp-land/activity_dax_prompt_browser_comparison.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/layout-sw600dp-land/activity_dax_prompt_duck_player.xml b/dax-prompts/dax-prompts-impl/src/main/res/layout-sw600dp-land/activity_dax_prompt_duck_player.xml new file mode 100644 index 000000000000..4294b6255169 --- /dev/null +++ b/dax-prompts/dax-prompts-impl/src/main/res/layout-sw600dp-land/activity_dax_prompt_duck_player.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/layout/activity_dax_prompt_browser_comparison.xml b/dax-prompts/dax-prompts-impl/src/main/res/layout/activity_dax_prompt_browser_comparison.xml index 9b5abbd9e104..7206ea5bca5e 100644 --- a/dax-prompts/dax-prompts-impl/src/main/res/layout/activity_dax_prompt_browser_comparison.xml +++ b/dax-prompts/dax-prompts-impl/src/main/res/layout/activity_dax_prompt_browser_comparison.xml @@ -20,10 +20,22 @@ android:id="@+id/daxPromptBrowserComparisonContainer" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/yellowBackground" + android:background="@color/daxPromptBackground" tools:context=".ui.DaxPromptBrowserComparisonActivity" tools:ignore="InvalidColorAttribute"> + + + app:typography="body1_bold" /> - + + + app:layout_constraintStart_toStartOf="parent"> - - - - - - - + android:text="@string/dax_prompt_browser_comparison_message_title" + app:typography="title" /> - + - + - + - + - - \ No newline at end of file + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/layout/activity_dax_prompt_duck_player.xml b/dax-prompts/dax-prompts-impl/src/main/res/layout/activity_dax_prompt_duck_player.xml index d9ac6864fb42..23c18a3bf997 100644 --- a/dax-prompts/dax-prompts-impl/src/main/res/layout/activity_dax_prompt_duck_player.xml +++ b/dax-prompts/dax-prompts-impl/src/main/res/layout/activity_dax_prompt_duck_player.xml @@ -20,9 +20,21 @@ android:id="@+id/daxPromptDuckPlayerContainer" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/yellowBackground" + android:background="@color/daxPromptBackground" tools:context=".ui.DaxPromptDuckPlayerActivity"> + + + app:typography="body1_bold" /> - + + + app:layout_constraintStart_toStartOf="parent"> - - - - - - - + android:text="@string/dax_prompt_duck_player_message_title" + app:typography="title" /> - + - + - - + - - \ No newline at end of file + diff --git a/dax-prompts/dax-prompts-impl/src/main/res/layout/browser_comparison_chart.xml b/dax-prompts/dax-prompts-impl/src/main/res/layout/browser_comparison_chart.xml index 56ede149fdf1..a42854fbd85d 100644 --- a/dax-prompts/dax-prompts-impl/src/main/res/layout/browser_comparison_chart.xml +++ b/dax-prompts/dax-prompts-impl/src/main/res/layout/browser_comparison_chart.xml @@ -80,7 +80,7 @@ app:layout_constraintEnd_toStartOf="@id/feature1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/feature1" - app:srcCompat="@drawable/ic_find_search_color_24" /> + app:srcCompat="@drawable/ic_comparison_chart_search" /> + app:srcCompat="@drawable/ic_comparison_chart_shield" /> + app:srcCompat="@drawable/ic_comparison_chart_cookies" /> + app:srcCompat="@drawable/ic_comparison_chart_ads" /> - #ffe181 + #ffe080 + #074daf \ No newline at end of file diff --git a/dax-prompts/dax-prompts-impl/src/main/res/values/strings-daxprompts.xml b/dax-prompts/dax-prompts-impl/src/main/res/values/strings-daxprompts.xml index 14de499bcaf5..67b4feec3bde 100644 --- a/dax-prompts/dax-prompts-impl/src/main/res/values/strings-daxprompts.xml +++ b/dax-prompts/dax-prompts-impl/src/main/res/values/strings-daxprompts.xml @@ -25,8 +25,8 @@ Browser Comparison Close DuckDuckGo has protections other browsers don’t. - Choose Your Browser - Plus Even More Protections... + Open Links With DuckDuckGo + Plus even more protections... Search privately by default Block 3rd-party trackers diff --git a/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/RealDaxPromptsTest.kt b/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/RealDaxPromptsTest.kt index 6afb707dd928..523cdf07ca95 100644 --- a/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/RealDaxPromptsTest.kt +++ b/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/RealDaxPromptsTest.kt @@ -16,16 +16,26 @@ package com.duckduckgo.daxprompts.impl +import com.duckduckgo.app.browser.defaultbrowsing.DefaultBrowserDetector +import com.duckduckgo.app.global.DefaultRoleBrowserDialog +import com.duckduckgo.browser.api.UserBrowserProperties import com.duckduckgo.common.test.CoroutineTestRule +import com.duckduckgo.daxprompts.api.DaxPrompts.ActionType import com.duckduckgo.daxprompts.impl.repository.DaxPromptsRepository +import com.duckduckgo.duckplayer.api.DuckPlayer +import com.duckduckgo.duckplayer.api.DuckPlayer.UserPreferences +import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk +import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Disabled +import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Enabled import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.kotlin.any import org.mockito.kotlin.mock -import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @ExperimentalCoroutinesApi @@ -37,49 +47,210 @@ class RealDaxPromptsTest { private lateinit var testee: RealDaxPrompts private val mockRepository: DaxPromptsRepository = mock() + private val mockReactivateUsersExperiment: ReactivateUsersExperiment = mock() + private val mockUserBrowserProperties: UserBrowserProperties = mock() + private val mockDefaultBrowserDetector: DefaultBrowserDetector = mock() + private val mockDefaultRoleBrowserDialog: DefaultRoleBrowserDialog = mock() + private val mockDuckPlayer: DuckPlayer = mock() @Before fun setup() { - testee = RealDaxPrompts(mockRepository, coroutineTestRule.testDispatcherProvider) + testee = RealDaxPrompts( + mockRepository, + mockReactivateUsersExperiment, + mockUserBrowserProperties, + mockDefaultBrowserDetector, + mockDefaultRoleBrowserDialog, + mockDuckPlayer, + coroutineTestRule.testDispatcherProvider, + ) } @Test - fun whenShouldShowDuckPlayerPromptCalledThenReturnValueFromRepository() = runTest { + fun whenUserIsNotEligibleThenReturnNone() = runTest { + mockUserIsNotEligible() + + val result = testee.evaluate() + + assertEquals(ActionType.NONE, result) + } + + @Test + fun whenUserIsEligibleAndInControlGroupThenReturnShowControl() = runTest { + mockUserIsEligible() + whenever(mockReactivateUsersExperiment.isControl()).thenReturn(true) + + val result = testee.evaluate() + + assertEquals(ActionType.SHOW_CONTROL, result) + } + + @Test + fun whenUserIsEligibleAndInDuckPlayerGroupAndShouldShowPromptThenReturnShowVariant1() = runTest { + mockUserIsEligible() + whenever(mockReactivateUsersExperiment.isControl()).thenReturn(false) + whenever(mockReactivateUsersExperiment.isDuckPlayerPrompt()).thenReturn(true) whenever(mockRepository.getDaxPromptsShowDuckPlayer()).thenReturn(true) - val result = testee.shouldShowDuckPlayerPrompt() + val result = testee.evaluate() - assertEquals(true, result) - verify(mockRepository).getDaxPromptsShowDuckPlayer() + assertEquals(ActionType.SHOW_VARIANT_DUCKPLAYER, result) } @Test - fun whenShouldShowDuckPlayerPromptCalledWithFalseValueThenReturnFalse() = runTest { + fun whenUserIsEligibleAndInDuckPlayerGroupAndShouldNotShowPromptThenReturnNone() = runTest { + mockUserIsEligible() + whenever(mockReactivateUsersExperiment.isControl()).thenReturn(false) + whenever(mockReactivateUsersExperiment.isDuckPlayerPrompt()).thenReturn(true) whenever(mockRepository.getDaxPromptsShowDuckPlayer()).thenReturn(false) - val result = testee.shouldShowDuckPlayerPrompt() + val result = testee.evaluate() - assertEquals(false, result) - verify(mockRepository).getDaxPromptsShowDuckPlayer() + assertEquals(ActionType.NONE, result) } @Test - fun whenShouldShowBrowserComparisonPromptCalledThenReturnValueFromRepository() = runTest { + fun whenUserIsEligibleAndInBrowserComparisonGroupAndShouldShowPromptThenReturnShowVariant2() = runTest { + mockUserIsEligible() + whenever(mockReactivateUsersExperiment.isControl()).thenReturn(false) + whenever(mockReactivateUsersExperiment.isDuckPlayerPrompt()).thenReturn(false) + whenever(mockReactivateUsersExperiment.isBrowserComparisonPrompt()).thenReturn(true) + whenever(mockDefaultBrowserDetector.isDefaultBrowser()).thenReturn(false) + whenever(mockDefaultRoleBrowserDialog.shouldShowDialog()).thenReturn(true) whenever(mockRepository.getDaxPromptsShowBrowserComparison()).thenReturn(true) - val result = testee.shouldShowBrowserComparisonPrompt() + val result = testee.evaluate() - assertEquals(true, result) - verify(mockRepository).getDaxPromptsShowBrowserComparison() + assertEquals(ActionType.SHOW_VARIANT_BROWSER_COMPARISON, result) } @Test - fun whenShouldShowBrowserComparisonPromptCalledWithFalseValueThenReturnFalse() = runTest { + fun whenUserIsEligibleAndInBrowserComparisonGroupAndShouldNotShowPromptThenReturnNone() = runTest { + mockUserIsEligible() + whenever(mockReactivateUsersExperiment.isControl()).thenReturn(false) + whenever(mockReactivateUsersExperiment.isDuckPlayerPrompt()).thenReturn(false) + whenever(mockReactivateUsersExperiment.isBrowserComparisonPrompt()).thenReturn(true) whenever(mockRepository.getDaxPromptsShowBrowserComparison()).thenReturn(false) - val result = testee.shouldShowBrowserComparisonPrompt() + val result = testee.evaluate() + + assertEquals(ActionType.NONE, result) + } + + @Test + fun whenUserIsEligibleButNotInAnyExperimentGroupThenReturnNone() = runTest { + mockUserIsEligible() + whenever(mockReactivateUsersExperiment.isControl()).thenReturn(false) + whenever(mockReactivateUsersExperiment.isDuckPlayerPrompt()).thenReturn(false) + whenever(mockReactivateUsersExperiment.isBrowserComparisonPrompt()).thenReturn(false) + + val result = testee.evaluate() + + assertEquals(ActionType.NONE, result) + } + + @Test + fun whenDaysSinceInstalledLessThanThresholdThenUserIsNotEligible() = runTest { + whenever(mockUserBrowserProperties.daysSinceInstalled()).thenReturn(27) + whenever(mockUserBrowserProperties.daysUsedSince(any())).thenReturn(0) + whenever(mockDefaultBrowserDetector.isDefaultBrowser()).thenReturn(false) + whenever(mockDefaultRoleBrowserDialog.shouldShowDialog()).thenReturn(true) + whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(false, AlwaysAsk)) + + val result = testee.evaluate() + + assertEquals(ActionType.NONE, result) + } + + @Test + fun whenUserHasUsedAppInLast7DaysThenUserIsNotEligible() = runTest { + whenever(mockUserBrowserProperties.daysSinceInstalled()).thenReturn(30) + whenever(mockUserBrowserProperties.daysUsedSince(any())).thenReturn(1) + whenever(mockDefaultBrowserDetector.isDefaultBrowser()).thenReturn(false) + whenever(mockDefaultRoleBrowserDialog.shouldShowDialog()).thenReturn(true) + whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(false, AlwaysAsk)) + + val result = testee.evaluate() + + assertEquals(ActionType.NONE, result) + } + + @Test + fun whenIsDefaultBrowserThenUserIsNotEligible() = runTest { + whenever(mockUserBrowserProperties.daysSinceInstalled()).thenReturn(30) + whenever(mockUserBrowserProperties.daysUsedSince(any())).thenReturn(0) + whenever(mockDefaultBrowserDetector.isDefaultBrowser()).thenReturn(true) + whenever(mockDefaultRoleBrowserDialog.shouldShowDialog()).thenReturn(true) + whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(false, AlwaysAsk)) + + val result = testee.evaluate() + + assertEquals(ActionType.NONE, result) + } + + @Test + fun whenShouldShowDialogIsFalseThenUserIsNotEligible() = runTest { + whenever(mockUserBrowserProperties.daysSinceInstalled()).thenReturn(30) + whenever(mockUserBrowserProperties.daysUsedSince(any())).thenReturn(0) + whenever(mockDefaultBrowserDetector.isDefaultBrowser()).thenReturn(false) + whenever(mockDefaultRoleBrowserDialog.shouldShowDialog()).thenReturn(false) + whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(false, AlwaysAsk)) + + val result = testee.evaluate() + + assertEquals(ActionType.NONE, result) + } + + @Test + fun whenDuckPlayerIsEnabledThenUserIsNotEligible() = runTest { + whenever(mockUserBrowserProperties.daysSinceInstalled()).thenReturn(30) + whenever(mockUserBrowserProperties.daysUsedSince(any())).thenReturn(0) + whenever(mockDefaultBrowserDetector.isDefaultBrowser()).thenReturn(false) + whenever(mockDefaultRoleBrowserDialog.shouldShowDialog()).thenReturn(true) + whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(false, Enabled)) + + val result = testee.evaluate() + + assertEquals(ActionType.NONE, result) + } + + @Test + fun whenDuckPlayerIsDisabledThenUserIsNotEligible() = runTest { + whenever(mockUserBrowserProperties.daysSinceInstalled()).thenReturn(30) + whenever(mockUserBrowserProperties.daysUsedSince(any())).thenReturn(0) + whenever(mockDefaultBrowserDetector.isDefaultBrowser()).thenReturn(false) + whenever(mockDefaultRoleBrowserDialog.shouldShowDialog()).thenReturn(true) + whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(false, Disabled)) + + val result = testee.evaluate() + + assertEquals(ActionType.NONE, result) + } + + @Test + fun whenDuckPlayerIsFeatureFlagDisabledThenUserIsNotEligible() = runTest { + whenever(mockUserBrowserProperties.daysSinceInstalled()).thenReturn(30) + whenever(mockUserBrowserProperties.daysUsedSince(any())).thenReturn(0) + whenever(mockDefaultBrowserDetector.isDefaultBrowser()).thenReturn(false) + whenever(mockDefaultRoleBrowserDialog.shouldShowDialog()).thenReturn(true) + whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(false, AlwaysAsk)) + whenever(mockDuckPlayer.getDuckPlayerState()).thenReturn(DuckPlayer.DuckPlayerState.DISABLED) + + val result = testee.evaluate() + + assertEquals(ActionType.NONE, result) + } + + private fun mockUserIsEligible() = runBlocking { + whenever(mockUserBrowserProperties.daysSinceInstalled()).thenReturn(30) + whenever(mockUserBrowserProperties.daysUsedSince(any())).thenReturn(0) + whenever(mockDefaultBrowserDetector.isDefaultBrowser()).thenReturn(false) + whenever(mockDefaultRoleBrowserDialog.shouldShowDialog()).thenReturn(true) + whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(false, AlwaysAsk)) + whenever(mockDuckPlayer.getDuckPlayerState()).thenReturn(DuckPlayer.DuckPlayerState.ENABLED) + } - assertEquals(false, result) - verify(mockRepository).getDaxPromptsShowBrowserComparison() + private fun mockUserIsNotEligible() { + whenever(mockUserBrowserProperties.daysSinceInstalled()).thenReturn(27) } } diff --git a/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonViewModelTest.kt b/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonViewModelTest.kt index 344703201236..26f28a73293c 100644 --- a/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonViewModelTest.kt +++ b/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptBrowserComparisonViewModelTest.kt @@ -23,6 +23,7 @@ import androidx.test.platform.app.InstrumentationRegistry import app.cash.turbine.test import com.duckduckgo.app.global.DefaultRoleBrowserDialog import com.duckduckgo.common.test.CoroutineTestRule +import com.duckduckgo.daxprompts.impl.ReactivateUsersExperiment import com.duckduckgo.daxprompts.impl.repository.DaxPromptsRepository import com.duckduckgo.daxprompts.impl.ui.DaxPromptBrowserComparisonViewModel.Companion.BROWSER_COMPARISON_MORE_URL import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -33,6 +34,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -46,7 +48,8 @@ class DaxPromptBrowserComparisonViewModelTest { private lateinit var testee: DaxPromptBrowserComparisonViewModel private val mockDefaultRoleBrowserDialog: DefaultRoleBrowserDialog = mock() - private val mockDaxPromptsRepository: DaxPromptsRepository = org.mockito.kotlin.mock() + private val mockDaxPromptsRepository: DaxPromptsRepository = mock() + private val mockReactivateUsersExperiment: ReactivateUsersExperiment = mock() private val mockApplicationContext: Context = InstrumentationRegistry.getInstrumentation().targetContext @Before @@ -54,6 +57,7 @@ class DaxPromptBrowserComparisonViewModelTest { testee = DaxPromptBrowserComparisonViewModel( mockDefaultRoleBrowserDialog, mockDaxPromptsRepository, + mockReactivateUsersExperiment, mockApplicationContext, ) } @@ -66,6 +70,7 @@ class DaxPromptBrowserComparisonViewModelTest { assertEquals(DaxPromptBrowserComparisonViewModel.Command.OpenDetailsPage(BROWSER_COMPARISON_MORE_URL), awaitItem()) cancelAndIgnoreRemainingEvents() } + verify(mockReactivateUsersExperiment).firePlusEvenMoreProtectionsLinkClick() } @Test @@ -76,6 +81,7 @@ class DaxPromptBrowserComparisonViewModelTest { assertEquals(DaxPromptBrowserComparisonViewModel.Command.CloseScreen(), awaitItem()) cancelAndIgnoreRemainingEvents() } + verify(mockReactivateUsersExperiment).fireCloseScreen() } @Test @@ -90,6 +96,7 @@ class DaxPromptBrowserComparisonViewModelTest { assertEquals(DaxPromptBrowserComparisonViewModel.Command.BrowserComparisonChart(mockIntent), awaitItem()) cancelAndIgnoreRemainingEvents() } + verify(mockReactivateUsersExperiment).fireChooseYourBrowserClick() } @Test @@ -103,6 +110,7 @@ class DaxPromptBrowserComparisonViewModelTest { assertEquals(DaxPromptBrowserComparisonViewModel.Command.CloseScreen(), awaitItem()) cancelAndIgnoreRemainingEvents() } + verify(mockReactivateUsersExperiment, never()).fireChooseYourBrowserClick() } @Test @@ -115,6 +123,7 @@ class DaxPromptBrowserComparisonViewModelTest { assertEquals(DaxPromptBrowserComparisonViewModel.Command.CloseScreen(), awaitItem()) cancelAndIgnoreRemainingEvents() } + verify(mockReactivateUsersExperiment, never()).fireChooseYourBrowserClick() } @Test @@ -126,6 +135,7 @@ class DaxPromptBrowserComparisonViewModelTest { assertEquals(DaxPromptBrowserComparisonViewModel.Command.CloseScreen(true), awaitItem()) cancelAndIgnoreRemainingEvents() } + verify(mockReactivateUsersExperiment).fireSetBrowserAsDefault() } @Test diff --git a/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerViewModelTest.kt b/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerViewModelTest.kt index 6f42cac76789..c74b4b09c32f 100644 --- a/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerViewModelTest.kt +++ b/dax-prompts/dax-prompts-impl/src/test/java/com/duckduckgo/daxprompts/impl/ui/DaxPromptDuckPlayerViewModelTest.kt @@ -18,9 +18,8 @@ package com.duckduckgo.daxprompts.impl.ui import app.cash.turbine.test import com.duckduckgo.common.test.CoroutineTestRule +import com.duckduckgo.daxprompts.impl.ReactivateUsersExperiment import com.duckduckgo.daxprompts.impl.repository.DaxPromptsRepository -import com.duckduckgo.duckplayer.api.DuckPlayer -import com.duckduckgo.duckplayer.api.PrivatePlayerMode import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -37,12 +36,12 @@ class DaxPromptDuckPlayerViewModelTest { val coroutineTestRule: CoroutineTestRule = CoroutineTestRule() private lateinit var testee: DaxPromptDuckPlayerViewModel - private val mockDuckPlayer: DuckPlayer = mock() private val mockDaxPromptsRepository: DaxPromptsRepository = mock() + private val mockReactivationExperiment: ReactivateUsersExperiment = mock() @Before fun setup() { - testee = DaxPromptDuckPlayerViewModel(mockDuckPlayer, mockDaxPromptsRepository) + testee = DaxPromptDuckPlayerViewModel(mockDaxPromptsRepository, mockReactivationExperiment) } @Test @@ -53,6 +52,7 @@ class DaxPromptDuckPlayerViewModelTest { assertEquals(DaxPromptDuckPlayerViewModel.Command.CloseScreen, awaitItem()) cancelAndIgnoreRemainingEvents() } + verify(mockReactivationExperiment).fireCloseScreen() } @Test @@ -64,25 +64,6 @@ class DaxPromptDuckPlayerViewModelTest { assertEquals(DaxPromptDuckPlayerViewModel.DUCK_PLAYER_DEMO_URL, command.url) cancelAndIgnoreRemainingEvents() } - } - - @Test - fun whenSecondaryButtonClickedThenEmitsDismissCommand() = runTest { - testee.onSecondaryButtonClicked() - - testee.commands().test { - assertEquals(DaxPromptDuckPlayerViewModel.Command.Dismiss, awaitItem()) - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun whenUpdateDuckPlayerSettingsCalledThenSetUserPreferencesWithCorrectParameters() = runTest { - testee.updateDuckPlayerSettings() - - verify(mockDuckPlayer).setUserPreferences( - overlayInteracted = false, - privatePlayerMode = PrivatePlayerMode.AlwaysAsk.value, - ) + verify(mockReactivationExperiment).fireDuckPlayerClick() } }