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 @@ -7455,43 +7455,6 @@ class BrowserTabViewModelTest {
)
}

@Test
fun whenInputScreenEnabledAndExternalIntentProcessingCompletedThenLaunchInputScreenCommandTriggered() =
runTest {
val initialTabId = "initial-tab"
val initialTab =
TabEntity(
tabId = initialTabId,
url = "https://example.com",
title = "EX",
skipHome = false,
viewed = true,
position = 0,
)
val ntpTabId = "ntp-tab"
val ntpTab = TabEntity(tabId = ntpTabId, url = null, title = "", skipHome = false, viewed = true, position = 0)
whenever(mockTabRepository.getTab(initialTabId)).thenReturn(initialTab)
whenever(mockTabRepository.getTab(ntpTabId)).thenReturn(ntpTab)
flowSelectedTab.emit(initialTab)

testee.loadData(tabId = ntpTabId, initialUrl = null, skipHome = false, isExternal = false)
mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow.emit(true)
mockHasPendingTabLaunchFlow.emit(true)

// Switch to a new tab with no URL
flowSelectedTab.emit(ntpTab)

// Complete external intent processing
mockHasPendingTabLaunchFlow.emit(false)

verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
val commands = commandCaptor.allValues
assertTrue(
"LaunchInputScreen command should be triggered when external intent processing completes",
commands.any { it is Command.LaunchInputScreen },
)
}

@Test
fun whenInputScreenEnabledAndDuckAiOpenThenLaunchInputScreenCommandSuppressed() =
runTest {
Expand Down Expand Up @@ -7525,51 +7488,6 @@ class BrowserTabViewModelTest {
)
}

@Test
fun whenInputScreenEnabledAndDuckAiClosedThenLaunchInputScreenCommandTriggered() =
runTest {
val initialTabId = "initial-tab"
val initialTab =
TabEntity(
tabId = initialTabId,
url = "https://example.com",
title = "EX",
skipHome = false,
viewed = true,
position = 0,
)
val ntpTabId = "ntp-tab"
val ntpTab =
TabEntity(
tabId = ntpTabId,
url = null,
title = "",
skipHome = false,
viewed = true,
position = 0,
)
whenever(mockTabRepository.getTab(initialTabId)).thenReturn(initialTab)
whenever(mockTabRepository.getTab(ntpTabId)).thenReturn(ntpTab)
flowSelectedTab.emit(initialTab)

testee.loadData(tabId = ntpTabId, initialUrl = null, skipHome = false, isExternal = false)
mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow.emit(true)
mockHasPendingDuckAiOpenFlow.emit(true)

// Switch to a new tab with no URL
flowSelectedTab.emit(ntpTab)

// Close Duck.ai
mockHasPendingDuckAiOpenFlow.emit(false)

verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
val commands = commandCaptor.allValues
assertTrue(
"LaunchInputScreen command should be triggered when Duck.ai is closed",
commands.any { it is Command.LaunchInputScreen },
)
}

@Test
fun whenEvaluateSerpLogoStateCalledWithDuckDuckGoUrlAndFeatureEnabledThenExtractSerpLogoCommandIssued() {
whenever(mockSerpEasterEggLogoToggles.feature()).thenReturn(mockEnabledToggle)
Expand Down
59 changes: 55 additions & 4 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.activity.viewModels
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Lifecycle.State.STARTED
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
Expand Down Expand Up @@ -90,6 +93,7 @@ import com.duckduckgo.app.statistics.pixels.Pixel.PixelParameter
import com.duckduckgo.app.tabs.TabManagerFeatureFlags
import com.duckduckgo.app.tabs.model.TabEntity
import com.duckduckgo.app.tabs.ui.DefaultSnackbar
import com.duckduckgo.app.tabs.ui.TabSwitcherActivity
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.autofill.api.emailprotection.EmailProtectionLinkVerifier
import com.duckduckgo.browser.api.ui.BrowserScreens.BookmarksScreenNoParams
Expand All @@ -110,7 +114,9 @@ import com.duckduckgo.common.utils.playstore.PlayStoreUtils
import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.duckchat.api.DuckAiFeatureState
import com.duckduckgo.duckchat.api.DuckChat
import com.duckduckgo.duckchat.api.viewmodel.DuckChatSharedViewModel
import com.duckduckgo.duckchat.impl.ui.DuckChatWebViewFragment
import com.duckduckgo.duckchat.impl.ui.DuckChatWebViewFragment.Companion.KEY_DUCK_AI_TABS
import com.duckduckgo.duckchat.impl.ui.DuckChatWebViewFragment.Companion.KEY_DUCK_AI_URL
import com.duckduckgo.navigation.api.GlobalActivityStarter
import com.duckduckgo.savedsites.impl.bookmarks.BookmarksActivity.Companion.SAVED_SITE_URL_EXTRA
Expand Down Expand Up @@ -248,6 +254,7 @@ open class BrowserActivity : DuckDuckGoActivity() {
}

private val viewModel: BrowserViewModel by bindViewModel()
private val duckChatViewModel: DuckChatSharedViewModel by viewModels()

private var instanceStateBundles: CombinedInstanceState? = null

Expand Down Expand Up @@ -359,6 +366,8 @@ open class BrowserActivity : DuckDuckGoActivity() {
viewModel.viewState.observe(this) {
renderer.renderBrowserViewState(it)
}
observeDuckChatSharedCommands()

viewModel.awaitClearDataFinishedNotification()
initializeServiceWorker()

Expand Down Expand Up @@ -753,7 +762,7 @@ open class BrowserActivity : DuckDuckGoActivity() {
is Command.ShowSystemDefaultAppsActivity -> showSystemDefaultAppsActivity(command.intent)
is Command.ShowSystemDefaultBrowserDialog -> showSystemDefaultBrowserDialog(command.intent)
is Command.ShowUndoDeleteTabsMessage -> showTabsDeletedSnackbar(command.tabIds)
is Command.OpenDuckChat -> openDuckChat(command.duckChatUrl, command.duckChatSessionActive, command.withTransition)
is Command.OpenDuckChat -> openDuckChat(command.duckChatUrl, command.duckChatSessionActive, command.withTransition, command.tabs)
Command.LaunchTabSwitcher -> currentTab?.launchTabSwitcherAfterTabsUndeleted()
}
}
Expand Down Expand Up @@ -839,7 +848,7 @@ open class BrowserActivity : DuckDuckGoActivity() {
globalActivityStarter.start(this, DownloadsScreenNoParams)
}

private fun closeDuckChat() {
fun closeDuckChat() {
isDuckChatVisible = false
externalIntentProcessingState.onDuckAiClosed()
val fragment = duckAiFragment
Expand All @@ -856,15 +865,16 @@ open class BrowserActivity : DuckDuckGoActivity() {
url: String?,
duckChatSessionActive: Boolean,
withTransition: Boolean,
tabs: Int,
) {
duckAiFragment?.let { fragment ->
if (duckChatSessionActive) {
restoreDuckChat(fragment, withTransition)
} else {
launchNewDuckChat(url, withTransition)
launchNewDuckChat(url, withTransition, tabs)
}
} ?: run {
launchNewDuckChat(url, withTransition)
launchNewDuckChat(url, withTransition, tabs)
}

currentTab?.getOmnibar()?.omnibarView?.omnibarTextInput?.let {
Expand All @@ -875,6 +885,7 @@ open class BrowserActivity : DuckDuckGoActivity() {
private fun launchNewDuckChat(
duckChatUrl: String?,
withTransition: Boolean,
tabs: Int,
) {
val wasFragmentVisible = duckAiFragment?.isVisible ?: false
val fragment =
Expand All @@ -883,6 +894,7 @@ open class BrowserActivity : DuckDuckGoActivity() {
arguments =
Bundle().apply {
putString(KEY_DUCK_AI_URL, duckChatUrl)
putInt(KEY_DUCK_AI_TABS, tabs)
}
}
}
Expand Down Expand Up @@ -975,6 +987,45 @@ open class BrowserActivity : DuckDuckGoActivity() {
)
}

private val tabSwitcherActivityResult =
registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
// Handle any result data if needed
result.data?.let { intent ->
intent.extras?.let { extras ->
val deletedTabIds = extras.getStringArrayList(TabSwitcherActivity.EXTRA_KEY_DELETED_TAB_IDS)
if (!deletedTabIds.isNullOrEmpty()) {
onTabsDeletedInTabSwitcher(deletedTabIds)
}
}
}
}
}

private fun observeDuckChatSharedCommands() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
duckChatViewModel.command.collect { command ->
when (command) {
DuckChatSharedViewModel.Command.LaunchFire -> launchFire()
DuckChatSharedViewModel.Command.LaunchTabSwitcher -> {
val intent = TabSwitcherActivity.intent(this@BrowserActivity)
tabSwitcherActivityResult.launch(intent)
}
is DuckChatSharedViewModel.Command.SearchRequested -> {
closeDuckChat()
currentTab?.submitQuery(command.query)
}

is DuckChatSharedViewModel.Command.OpenTab -> {
openExistingTab(command.tabId)
}
}
}
}
}
}

override fun onAttachFragment(fragment: androidx.fragment.app.Fragment) {
super.onAttachFragment(fragment)
hideMockupOmnibar()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,7 @@ class BrowserTabFragment :
}

private fun launchInputScreen(query: String) {
logcat { "Duck.ai: launchInputScreen" }
val isTopOmnibar = omnibar.omnibarType != OmnibarType.SINGLE_BOTTOM
val intent =
globalActivityStarter.startIntent(
Expand Down
Loading
Loading