Skip to content

Commit 001199d

Browse files
authored
open Input Screen from widgets, when enabled (#6931)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1208671518894266/task/1211589372131521?focus=true ### Description When enabled, open the Input Screen from when the app is opened from a widget. ### Steps to test this PR - [x] Install the app from this branch. - [x] Ensure you have some favorites added. - [x] Go to Settings -> AI Features and enable the Search & Duck.ai address bar. - [x] Add a widget (variant with favorites) to the home screen. - [x] Click on the widget's text box and verify that the Input Screen doesn't open. - [x] Go to Feature Flag Inventory and enable `showInputScreenOnSystemSearchLaunch`. - [x] Force close and reopen the app. - [x] Click on the widget's text box and verify that the Input Screen opens. - [x] Verify that `m_sfbw_l` pixel is sent. - [x] Minimize it. - [x] Click on the widget's text box again and verify that the Input Screen opens. - [x] Click the back button and verify the screen closes and goes back to the home screen. - [x] Click on the widget's text box again and verify that the Input Screen opens. - [x] Try submitting a search query, verify it opens the browser in the new tab and there's nothing else left in the task bar. - [x] Click on the widget's text box again and verify that the Input Screen opens. - [x] Try submitting a chat prompt, verify it opens the browser in Duck.ai fragment and there's nothing else left in the task bar. - [x] Click on the Duck.ai icon in the widget and verify the browser in Duck.ai fragment. - [x] Go to Settings -> AI Features and disable Duck.ai altogether. - [x] Click on the widget's text box and verify that the Input Screen doesn't open. - [x] Go to Settings -> AI Features and enable Duck.ai again. - [x] Click on the widget's text box and verify that the Input Screen opens. - [x] Click on the favorite in the widget and verify it opens directly in the browser. ### Known issues - Installed apps can't be searched on the Input Screen when launched from the widget. - Clicking the "voice" button on the widget doesn't immediately open voice search. Both will be resolved in follow up PRs.
1 parent f8dfd8b commit 001199d

File tree

5 files changed

+106
-2
lines changed

5 files changed

+106
-2
lines changed

app/src/main/java/com/duckduckgo/app/systemsearch/SystemSearchActivity.kt

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import android.widget.ImageView
3030
import android.widget.TextView
3131
import android.widget.Toast
3232
import android.widget.Toast.LENGTH_SHORT
33+
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
3334
import androidx.core.content.ContextCompat
3435
import androidx.core.text.toSpannable
3536
import androidx.core.view.isVisible
@@ -73,6 +74,7 @@ import com.duckduckgo.browser.api.autocomplete.AutoComplete.AutoCompleteSuggesti
7374
import com.duckduckgo.browser.api.ui.BrowserScreens.PrivateSearchScreenNoParams
7475
import com.duckduckgo.browser.ui.autocomplete.BrowserAutoCompleteSuggestionsAdapter
7576
import com.duckduckgo.browser.ui.omnibar.OmnibarPosition
77+
import com.duckduckgo.browser.ui.omnibar.OmnibarPosition.TOP
7678
import com.duckduckgo.common.ui.DuckDuckGoActivity
7779
import com.duckduckgo.common.ui.view.KeyboardAwareEditText
7880
import com.duckduckgo.common.ui.view.addBottomShadow
@@ -85,6 +87,10 @@ import com.duckduckgo.common.utils.extensions.html
8587
import com.duckduckgo.common.utils.extensions.showKeyboard
8688
import com.duckduckgo.common.utils.text.TextChangedWatcher
8789
import com.duckduckgo.di.scopes.ActivityScope
90+
import com.duckduckgo.duckchat.api.DuckAiFeatureState
91+
import com.duckduckgo.duckchat.api.inputscreen.InputScreenActivityParams
92+
import com.duckduckgo.duckchat.api.inputscreen.InputScreenActivityResultCodes
93+
import com.duckduckgo.duckchat.api.inputscreen.InputScreenActivityResultParams
8894
import com.duckduckgo.navigation.api.GlobalActivityStarter
8995
import com.duckduckgo.savedsites.api.models.SavedSite
9096
import com.duckduckgo.savedsites.impl.dialogs.EditSavedSiteDialogFragment
@@ -148,6 +154,34 @@ class SystemSearchActivity : DuckDuckGoActivity() {
148154
private lateinit var duckAi: ImageView
149155
private lateinit var omnibarDivider: View
150156

157+
@Inject
158+
lateinit var duckAiFeatureState: DuckAiFeatureState
159+
160+
private val inputScreenLauncher =
161+
registerForActivityResult(StartActivityForResult()) { result ->
162+
val data = result.data ?: return@registerForActivityResult
163+
164+
when (result.resultCode) {
165+
InputScreenActivityResultCodes.NEW_SEARCH_REQUESTED -> {
166+
data.getStringExtra(InputScreenActivityResultParams.SEARCH_QUERY_PARAM)?.let { query ->
167+
launchBrowser(query)
168+
}
169+
}
170+
171+
InputScreenActivityResultCodes.SWITCH_TO_TAB_REQUESTED -> {
172+
data.getStringExtra(InputScreenActivityResultParams.TAB_ID_PARAM)?.let { tabId ->
173+
launchBrowser(query = "", openExistingTabId = tabId)
174+
}
175+
}
176+
177+
RESULT_CANCELED -> {
178+
data.getStringExtra(InputScreenActivityResultParams.CANCELED_DRAFT_PARAM)?.let { query ->
179+
finish()
180+
}
181+
}
182+
}
183+
}
184+
151185
private val textChangeWatcher =
152186
object : TextChangedWatcher() {
153187
override fun afterTextChanged(editable: Editable) {
@@ -191,7 +225,11 @@ class SystemSearchActivity : DuckDuckGoActivity() {
191225
if (savedInstanceState == null) {
192226
intent?.let {
193227
sendLaunchPixels(it)
194-
handleVoiceSearchLaunch(it)
228+
if (duckAiFeatureState.showInputScreenOnSystemSearchLaunch.value) {
229+
launchInputScreen(isTopOmnibar = isOmnibarAtTop)
230+
} else {
231+
handleVoiceSearchLaunch(it)
232+
}
195233
}
196234
}
197235

@@ -220,7 +258,21 @@ class SystemSearchActivity : DuckDuckGoActivity() {
220258
dataClearerForegroundAppRestartPixel.registerIntent(intent)
221259
viewModel.resetViewState()
222260
sendLaunchPixels(intent)
223-
handleVoiceSearchLaunch(intent)
261+
if (duckAiFeatureState.showInputScreenOnSystemSearchLaunch.value) {
262+
val isOmnibarAtTop = settingsDataStore.omnibarPosition == OmnibarPosition.TOP
263+
launchInputScreen(isTopOmnibar = isOmnibarAtTop)
264+
} else {
265+
handleVoiceSearchLaunch(intent)
266+
}
267+
}
268+
269+
private fun launchInputScreen(isTopOmnibar: Boolean) {
270+
globalActivityStarter.startIntent(
271+
this,
272+
InputScreenActivityParams(query = "", isTopOmnibar = isTopOmnibar),
273+
)?.let {
274+
inputScreenLauncher.launch(it)
275+
}
224276
}
225277

226278
private fun sendLaunchPixels(intent: Intent) {

duckchat/duckchat-api/src/main/java/com/duckduckgo/duckchat/api/DuckAiFeatureState.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,9 @@ interface DuckAiFeatureState {
5858
* Indicates whether the Setting for allowing Duck.ai chats to be deleted with the Fire Button is enabled
5959
*/
6060
val showClearDuckAIChatHistory: StateFlow<Boolean>
61+
62+
/**
63+
* Indicates whether the Input Screen should be shown when user open the app from system widgets
64+
*/
65+
val showInputScreenOnSystemSearchLaunch: StateFlow<Boolean>
6166
}

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/RealDuckChat.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ class RealDuckChat @Inject constructor(
260260

261261
private val _chatState = MutableStateFlow(ChatState.HIDE)
262262
private val keepSession = MutableStateFlow(false)
263+
private val _showInputScreenOnSystemSearchLaunch = MutableStateFlow(false)
263264

264265
private val jsonAdapter: JsonAdapter<DuckChatSettingJson> by lazy {
265266
moshi.adapter(DuckChatSettingJson::class.java)
@@ -279,6 +280,7 @@ class RealDuckChat @Inject constructor(
279280
private var keepSessionAliveInMinutes: Int = DEFAULT_SESSION_ALIVE
280281
private var clearChatHistory: Boolean = true
281282
private var inputScreenMainButtonsEnabled = false
283+
private var showInputScreenOnSystemSearchLaunchEnabled: Boolean = true
282284

283285
init {
284286
if (isMainProcess) {
@@ -389,6 +391,8 @@ class RealDuckChat @Inject constructor(
389391

390392
override val showClearDuckAIChatHistory: StateFlow<Boolean> = _showClearDuckAIChatHistory.asStateFlow()
391393

394+
override val showInputScreenOnSystemSearchLaunch: StateFlow<Boolean> = _showInputScreenOnSystemSearchLaunch.asStateFlow()
395+
392396
override val chatState: StateFlow<ChatState> = _chatState.asStateFlow()
393397

394398
override fun isImageUploadEnabled(): Boolean = isImageUploadEnabled
@@ -600,6 +604,7 @@ class RealDuckChat @Inject constructor(
600604
duckAiInputScreenBottomBarEnabled = duckChatFeature.inputScreenBottomBarSupport().isEnabled()
601605
clearChatHistory = duckChatFeature.clearHistory().isEnabled()
602606
showAIChatAddressBarChoiceScreen = duckChatFeature.showAIChatAddressBarChoiceScreen().isEnabled()
607+
showInputScreenOnSystemSearchLaunchEnabled = duckChatFeature.showInputScreenOnSystemSearchLaunch().isEnabled()
603608
inputScreenMainButtonsEnabled = duckChatFeature.showMainButtonsInInputScreen().isEnabled()
604609

605610
val showMainButtons = duckChatFeature.showMainButtonsInInputScreen().isEnabled()
@@ -642,6 +647,8 @@ class RealDuckChat @Inject constructor(
642647

643648
_inputScreenBottomBarEnabled.value = showInputScreen && duckAiInputScreenBottomBarEnabled
644649

650+
_showInputScreenOnSystemSearchLaunch.value = showInputScreen && showInputScreenOnSystemSearchLaunchEnabled
651+
645652
val showInBrowserMenu =
646653
duckChatFeatureRepository.shouldShowInBrowserMenu() &&
647654
isDuckChatFeatureEnabled && isDuckChatUserEnabled

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/feature/DuckChatFeature.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ interface DuckChatFeature {
6868
@Toggle.DefaultValue(DefaultFeatureValue.TRUE)
6969
fun showInputScreenAutomaticallyOnNewTab(): Toggle
7070

71+
/**
72+
* @return `true` when the Input Screen should be shown when user open the app from system widgets
73+
* If the remote feature is not present defaults to `disabled`
74+
*/
75+
@Toggle.DefaultValue(DefaultFeatureValue.FALSE)
76+
fun showInputScreenOnSystemSearchLaunch(): Toggle
77+
7178
/**
7279
* @return `true` when the Input Screen can present a bottom input box, if user has the omnibar also set to the bottom position.
7380
* If disabled, the Input Screen should always show the input box at the top of the screen.

duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/RealDuckChatTest.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,39 @@ class RealDuckChatTest {
10141014
verify(mockPixel).fire(DUCK_CHAT_NEW_ADDRESS_BAR_PICKER_CANCELLED)
10151015
}
10161016

1017+
@Test
1018+
fun `when input screen disabled then don't show input screen when launched from system search`() = runTest {
1019+
duckChatFeature.duckAiInputScreen().setRawStoredState(State(false))
1020+
whenever(mockDuckChatFeatureRepository.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(true))
1021+
duckChatFeature.showInputScreenOnSystemSearchLaunch().setRawStoredState(State(true))
1022+
1023+
testee.onPrivacyConfigDownloaded()
1024+
1025+
assertFalse(testee.showInputScreenOnSystemSearchLaunch.value)
1026+
}
1027+
1028+
@Test
1029+
fun `when input screen enabled but feature disabled then don't show input screen when launched from system search`() = runTest {
1030+
duckChatFeature.duckAiInputScreen().setRawStoredState(State(true))
1031+
whenever(mockDuckChatFeatureRepository.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(true))
1032+
duckChatFeature.showInputScreenOnSystemSearchLaunch().setRawStoredState(State(false))
1033+
1034+
testee.onPrivacyConfigDownloaded()
1035+
1036+
assertFalse(testee.showInputScreenOnSystemSearchLaunch.value)
1037+
}
1038+
1039+
@Test
1040+
fun `when input screen enabled and feature flag enabled then show input screen when launched from system search`() = runTest {
1041+
duckChatFeature.duckAiInputScreen().setRawStoredState(State(true))
1042+
whenever(mockDuckChatFeatureRepository.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(true))
1043+
duckChatFeature.showInputScreenOnSystemSearchLaunch().setRawStoredState(State(true))
1044+
1045+
testee.onPrivacyConfigDownloaded()
1046+
1047+
assertTrue(testee.showInputScreenOnSystemSearchLaunch.value)
1048+
}
1049+
10171050
companion object {
10181051
val SETTINGS_JSON = """
10191052
{

0 commit comments

Comments
 (0)