Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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 @@ -459,6 +459,8 @@ class BrowserTabViewModelTest {

private val mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow = MutableStateFlow(false)

private val mockDuckAiFeatureStateFullScreenModeFlow = MutableStateFlow(false)

private val mockExternalIntentProcessingState: ExternalIntentProcessingState = mock()

private val mockVpnMenuStateProvider: VpnMenuStateProvider = mock()
Expand Down Expand Up @@ -607,6 +609,7 @@ class BrowserTabViewModelTest {

private val exampleUrl = "http://example.com"
private val shortExampleUrl = "example.com"
private val duckChatURL = "https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=5"

private val selectedTab = TabEntity("TAB_ID", exampleUrl, position = 0, sourceTabId = "TAB_ID_SOURCE")
private val flowSelectedTab = MutableStateFlow(selectedTab)
Expand Down Expand Up @@ -692,6 +695,7 @@ class BrowserTabViewModelTest {
whenever(mockDuckAiFeatureState.showPopupMenuShortcut).thenReturn(MutableStateFlow(false))
whenever(mockDuckAiFeatureState.showInputScreen).thenReturn(mockDuckAiFeatureStateInputScreenFlow)
whenever(mockDuckAiFeatureState.showInputScreenAutomaticallyOnNewTab).thenReturn(mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow)
whenever(mockDuckAiFeatureState.showFullScreenMode).thenReturn(mockDuckAiFeatureStateFullScreenModeFlow)
whenever(mockExternalIntentProcessingState.hasPendingTabLaunch).thenReturn(mockHasPendingTabLaunchFlow)
whenever(mockExternalIntentProcessingState.hasPendingDuckAiOpen).thenReturn(mockHasPendingDuckAiOpenFlow)
whenever(mockVpnMenuStateProvider.getVpnMenuState()).thenReturn(flowOf(VpnMenuState.Hidden))
Expand Down Expand Up @@ -765,6 +769,8 @@ class BrowserTabViewModelTest {

fakeContentScopeScriptsSubscriptionEventPluginPoint = FakeContentScopeScriptsSubscriptionEventPluginPoint()

whenever(mockDuckChat.getDuckChatUrl(any(), any())).thenReturn(duckChatURL)

testee =
BrowserTabViewModel(
statisticsUpdater = mockStatisticsUpdater,
Expand Down Expand Up @@ -6341,6 +6347,22 @@ class BrowserTabViewModelTest {
verify(mockDuckChat).openDuckChat()
}

@Test
fun whenDuckChatMenuItemClickedAndFullScreenModeThenDontOpenDuckChatScreen() =
runTest {
mockDuckAiFeatureStateFullScreenModeFlow.emit(true)
whenever(mockDuckChat.wasOpenedBefore()).thenReturn(false)
whenever(mockOmnibarConverter.convertQueryToUrl(duckChatURL, null)).thenReturn(duckChatURL)

testee.onDuckChatMenuClicked()

verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())

assertTrue(commandCaptor.lastValue is Navigate)

verify(mockDuckChat, never()).openDuckChat()
}

@Test
fun whenDuckChatMenuItemClickedAndItWasUsedBeforeThenOpenDuckChatAndSendPixel() =
runTest {
Expand Down
24 changes: 9 additions & 15 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,6 @@ open class BrowserActivity : DuckDuckGoActivity() {
command.duckChatSessionActive,
command.withTransition,
command.tabs,
command.fullScreenMode,
)

Command.LaunchTabSwitcher -> currentTab?.launchTabSwitcherAfterTabsUndeleted()
Expand Down Expand Up @@ -837,24 +836,19 @@ open class BrowserActivity : DuckDuckGoActivity() {
duckChatSessionActive: Boolean,
withTransition: Boolean,
tabs: Int,
fullScreenMode: Boolean,
) {
if (fullScreenMode) {
currentTab?.submitQuery(url!!)
} else {
duckAiFragment?.let { fragment ->
if (duckChatSessionActive) {
restoreDuckChat(fragment, withTransition)
} else {
launchNewDuckChat(url, withTransition, tabs)
}
} ?: run {
duckAiFragment?.let { fragment ->
if (duckChatSessionActive) {
restoreDuckChat(fragment, withTransition)
} else {
launchNewDuckChat(url, withTransition, tabs)
}
} ?: run {
launchNewDuckChat(url, withTransition, tabs)
}

currentTab?.getOmnibar()?.omnibarView?.omnibarTextInput?.let {
hideKeyboard(it)
}
currentTab?.getOmnibar()?.omnibarView?.omnibarTextInput?.let {
hideKeyboard(it)
}
}

Expand Down
49 changes: 34 additions & 15 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,7 @@ class BrowserTabViewModel @Inject constructor(
query: String,
queryOrigin: QueryOrigin = QueryOrigin.FromUser,
) {
logcat { "Duck.ai: onUserSubmittedQuery $query" }
navigationAwareLoginDetector.onEvent(NavigationEvent.UserAction.NewQuerySubmitted)

if (query.isBlank()) {
Expand Down Expand Up @@ -1132,19 +1133,17 @@ class BrowserTabViewModel @Inject constructor(
val verticalParameter = extractVerticalParameter(url)
var urlToNavigate = queryUrlConverter.convertQueryToUrl(trimmedInput, verticalParameter, queryOrigin)

logcat { "Duck.ai: urlToNavigate $urlToNavigate" }

when (val type = specialUrlDetector.determineType(trimmedInput)) {
is ShouldLaunchDuckChatLink -> {
runCatching {
if (duckAiFeatureState.showFullScreenMode.value) {
site?.nextUrl = urlToNavigate
command.value = NavigationCommand.Navigate(urlToNavigate, getUrlHeaders(urlToNavigate))
logcat { "Duck.ai: ShouldLaunchDuckChatLink $urlToNavigate" }
val queryParameter = urlToNavigate.toUri().getQueryParameter(QUERY)
if (queryParameter != null) {
duckChat.openDuckChatWithPrefill(queryParameter)
} else {
val queryParameter = urlToNavigate.toUri().getQueryParameter(QUERY)
if (queryParameter != null) {
duckChat.openDuckChatWithPrefill(queryParameter)
} else {
duckChat.openDuckChat()
}
duckChat.openDuckChat()
}
return
}
Expand Down Expand Up @@ -1544,6 +1543,7 @@ class BrowserTabViewModel @Inject constructor(

when (stateChange) {
is WebNavigationStateChange.NewPage -> {
logcat { "Duck.ai: WebNavigationStateChange.NewPage ${stateChange.url.toUri()}" }
val uri = stateChange.url.toUri()
viewModelScope.launch(dispatchers.io()) {
if (duckPlayer.getDuckPlayerState() == ENABLED && duckPlayer.isSimulatedYoutubeNoCookie(uri)) {
Expand All @@ -1569,6 +1569,7 @@ class BrowserTabViewModel @Inject constructor(

is WebNavigationStateChange.PageCleared -> pageCleared()
is WebNavigationStateChange.UrlUpdated -> {
logcat { "Duck.ai: urlUpdated ${stateChange.url}" }
val uri = stateChange.url.toUri()
viewModelScope.launch(dispatchers.io()) {
if (duckPlayer.getDuckPlayerState() == ENABLED && duckPlayer.isSimulatedYoutubeNoCookie(uri)) {
Expand Down Expand Up @@ -1626,7 +1627,7 @@ class BrowserTabViewModel @Inject constructor(
url: String,
title: String?,
) {
logcat(VERBOSE) { "Page changed: $url" }
logcat(VERBOSE) { "Duck.ai: Page changed: $url" }
cleanupBlobDownloadReplyProxyMaps()

hasCtaBeenShownForCurrentPage.set(false)
Expand Down Expand Up @@ -1899,6 +1900,7 @@ class BrowserTabViewModel @Inject constructor(
}

override fun pageRefreshed(refreshedUrl: String) {
logcat { "Duck.ai pageRefreshed URL: $url refreshedUrl $refreshedUrl" }
if (url == null || refreshedUrl == url) {
logcat(VERBOSE) { "Page refreshed: $refreshedUrl" }
pageChanged(refreshedUrl, title)
Expand Down Expand Up @@ -2185,6 +2187,7 @@ class BrowserTabViewModel @Inject constructor(
}

private fun onSiteChanged() {
logcat { "Duck.ai: onSiteChanged" }
httpsUpgraded = false
site?.isDesktopMode = currentBrowserViewState().isDesktopBrowsingMode
viewModelScope.launch {
Expand Down Expand Up @@ -4470,7 +4473,13 @@ class BrowserTabViewModel @Inject constructor(
val params = duckChat.createWasUsedBeforePixelParams()
pixel.fire(DuckChatPixelName.DUCK_CHAT_OPEN_BROWSER_MENU, parameters = params)
}
duckChat.openDuckChat()

if (duckAiFeatureState.showFullScreenMode.value) {
val url = duckChat.getDuckChatUrl("", false)
onUserSubmittedQuery(url)
} else {
duckChat.openDuckChat()
}
}

fun onDuckChatOmnibarButtonClicked(
Expand All @@ -4481,10 +4490,20 @@ class BrowserTabViewModel @Inject constructor(
viewModelScope.launch {
command.value = HideKeyboardForChat
}
when {
hasFocus && isNtp && query.isNullOrBlank() -> duckChat.openDuckChat()
hasFocus -> duckChat.openDuckChatWithAutoPrompt(query ?: "")
else -> duckChat.openDuckChat()

if (duckAiFeatureState.showFullScreenMode.value) {
val url = when {
hasFocus && isNtp && query.isNullOrBlank() -> duckChat.getDuckChatUrl(query ?: "", false)
hasFocus -> duckChat.getDuckChatUrl(query ?: "", true)
else -> duckChat.getDuckChatUrl(query ?: "", false)
}
onUserSubmittedQuery(url)
} else {
when {
hasFocus && isNtp && query.isNullOrBlank() -> duckChat.openDuckChat()
hasFocus -> duckChat.openDuckChatWithAutoPrompt(query ?: "")
else -> duckChat.openDuckChat()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ class BrowserViewModel @Inject constructor(
val duckChatSessionActive: Boolean,
val withTransition: Boolean,
val tabs: Int,
val fullScreenMode: Boolean,
) : Command()
}

Expand Down Expand Up @@ -507,10 +506,9 @@ class BrowserViewModel @Inject constructor(
duckChatSessionActive: Boolean,
withTransition: Boolean,
) {
val duckAiFullScreenMode = duckAiFeatureState.showFullScreenMode.value
logcat(INFO) { "Duck.ai openDuckChat duckChatSessionActive $duckChatSessionActive" }
val tabsCount = tabs.value?.size ?: 0
sendCommand(OpenDuckChat(duckChatUrl, duckChatSessionActive, withTransition, tabsCount, duckAiFullScreenMode))
sendCommand(OpenDuckChat(duckChatUrl, duckChatSessionActive, withTransition, tabsCount))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ class BrowserWebViewClient @Inject constructor(
url: String?,
favicon: Bitmap?,
) {
logcat { "Duck.ai onPageStarted webViewUrl: ${webView.url} URL: $url lastPageStarted $lastPageStarted" }
url?.let {
// See https://app.asana.com/0/0/1206159443951489/f (WebView limitations)
if (it != ABOUT_BLANK && start == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.duckduckgo.app.browser.SpecialUrlDetector.UrlType
import com.duckduckgo.app.browser.applinks.ExternalAppIntentFlagsFeature
import com.duckduckgo.app.browser.duckchat.AIChatQueryDetectionFeature
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
import com.duckduckgo.duckchat.api.DuckAiFeatureState
import com.duckduckgo.duckchat.api.DuckChat
import com.duckduckgo.duckplayer.api.DuckPlayer
import com.duckduckgo.privacy.config.api.AmpLinkType
Expand All @@ -49,6 +50,7 @@ class SpecialUrlDetectorImpl(
private val externalAppIntentFlagsFeature: ExternalAppIntentFlagsFeature,
private val duckPlayer: DuckPlayer,
private val duckChat: DuckChat,
private val duckAiFeatureState: DuckAiFeatureState,
private val aiChatQueryDetectionFeature: AIChatQueryDetectionFeature,
private val androidBrowserConfigFeature: AndroidBrowserConfigFeature,
) : SpecialUrlDetector {
Expand Down Expand Up @@ -107,7 +109,7 @@ class SpecialUrlDetectorImpl(

val uri = uriString.toUri()

if (duckChat.isDuckChatUrl(uri)) {
if (duckChat.isDuckChatUrl(uri) && !duckAiFeatureState.showFullScreenMode.value) {
return UrlType.ShouldLaunchDuckChatLink
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import com.duckduckgo.downloads.api.FileDownloader
import com.duckduckgo.downloads.impl.AndroidFileDownloader
import com.duckduckgo.downloads.impl.DataUriDownloader
import com.duckduckgo.downloads.impl.FileDownloadCallback
import com.duckduckgo.duckchat.api.DuckAiFeatureState
import com.duckduckgo.duckchat.api.DuckChat
import com.duckduckgo.duckplayer.api.DuckPlayer
import com.duckduckgo.experiments.api.VariantManager
Expand Down Expand Up @@ -193,6 +194,7 @@ class BrowserModule {
externalAppIntentFlagsFeature: ExternalAppIntentFlagsFeature,
duckPlayer: DuckPlayer,
duckChat: DuckChat,
duckChaFeatureState: DuckAiFeatureState,
aiChatQueryDetectionFeature: AIChatQueryDetectionFeature,
androidBrowserConfigFeature: AndroidBrowserConfigFeature,
): SpecialUrlDetector = SpecialUrlDetectorImpl(
Expand All @@ -203,6 +205,7 @@ class BrowserModule {
externalAppIntentFlagsFeature,
duckPlayer,
duckChat,
duckChaFeatureState,
aiChatQueryDetectionFeature,
androidBrowserConfigFeature,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ class TabDataRepository @Inject constructor(
site: Site?,
) {
databaseExecutor().scheduleDirect {
logcat { "Duck.ai: updateUrlAndTitle url: ${site?.url} title: ${site?.title}" }
tabsDao.updateUrlAndTitle(tabId, site?.url, site?.title, viewed = true)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.duckduckgo.app.browser.SpecialUrlDetectorImpl.Companion.SMS_MAX_LENGT
import com.duckduckgo.app.browser.applinks.ExternalAppIntentFlagsFeature
import com.duckduckgo.app.browser.duckchat.AIChatQueryDetectionFeature
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
import com.duckduckgo.duckchat.api.DuckAiFeatureState
import com.duckduckgo.duckchat.api.DuckChat
import com.duckduckgo.duckplayer.api.DuckPlayer
import com.duckduckgo.feature.toggles.api.FakeFeatureToggleFactory
Expand All @@ -43,6 +44,7 @@ import com.duckduckgo.privacy.config.api.TrackingParameters
import com.duckduckgo.subscriptions.api.Subscriptions
import junit.framework.TestCase.assertNull
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
Expand Down Expand Up @@ -73,12 +75,16 @@ class SpecialUrlDetectorImplTest {

val mockDuckChat: DuckChat = mock()

val mockDuckAiFeature: DuckAiFeatureState = mock()

val mockAIChatQueryDetectionFeature: AIChatQueryDetectionFeature = mock()

val mockAIChatQueryDetectionFeatureToggle: Toggle = mock()

val androidBrowserConfigFeature: AndroidBrowserConfigFeature = FakeFeatureToggleFactory.create(AndroidBrowserConfigFeature::class.java)

private val mockDuckAiFullScreenMode = MutableStateFlow(false)

@Before
fun setup() = runTest {
testee = spy(
Expand All @@ -92,12 +98,14 @@ class SpecialUrlDetectorImplTest {
duckChat = mockDuckChat,
aiChatQueryDetectionFeature = mockAIChatQueryDetectionFeature,
androidBrowserConfigFeature = androidBrowserConfigFeature,
duckAiFeatureState = mockDuckAiFeature,
),
)
whenever(mockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(emptyList())
whenever(mockDuckPlayer.willNavigateToDuckPlayer(any())).thenReturn(false)
whenever(mockAIChatQueryDetectionFeatureToggle.isEnabled()).thenReturn(false)
whenever(mockAIChatQueryDetectionFeature.self()).thenReturn(mockAIChatQueryDetectionFeatureToggle)
whenever(mockDuckAiFeature.showFullScreenMode).thenReturn(mockDuckAiFullScreenMode)
androidBrowserConfigFeature.handleIntentScheme().setRawStoredState(State(true))
androidBrowserConfigFeature.validateIntentResolution().setRawStoredState(State(true))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ interface DuckChat {
*/
fun openDuckChatWithPrefill(query: String)

/**
* Returns the Duck Chat URL to be used
*/
fun getDuckChatUrl(query: String, autoPrompt: Boolean): String

/**
* Determines whether a given [Uri] is a DuckChat URL.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,13 @@ class RealDuckChat @Inject constructor(
openDuckChat(parameters, forceNewSession = true)
}

override fun getDuckChatUrl(query: String, autoPrompt: Boolean): String {
logcat { "Duck.ai: getDuckChatUrl query $query autoPrompt $autoPrompt" }
val parameters = addChatParameters(query, autoPrompt = autoPrompt)
val url = appendParameters(parameters, duckChatLink)
return url
}

private fun addChatParameters(
query: String,
autoPrompt: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@
package com.duckduckgo.duckchat.impl.inputscreen.ui.state

data class InputScreenVisibilityState(
val submitButtonVisible: Boolean,
val voiceInputButtonVisible: Boolean,
val autoCompleteSuggestionsVisible: Boolean,
val bottomFadeVisible: Boolean,
val showChatLogo: Boolean,
val showSearchLogo: Boolean,
val newLineButtonVisible: Boolean,
val mainButtonsVisible: Boolean,
val searchMode: Boolean,
val submitButtonVisible: Boolean = false,
val voiceInputButtonVisible: Boolean = false,
val autoCompleteSuggestionsVisible: Boolean = false,
val bottomFadeVisible: Boolean = false,
val showChatLogo: Boolean = true,
val showSearchLogo: Boolean = true,
val newLineButtonVisible: Boolean = false,
val mainButtonsVisible: Boolean = false,
val searchMode: Boolean = false,
val fullScreenMode: Boolean = false,
) {
val actionButtonsContainerVisible: Boolean = submitButtonVisible || voiceInputButtonVisible || newLineButtonVisible
}
Loading
Loading