Skip to content

Commit 46e5d0c

Browse files
authored
do not open the Input Screen when user pref toggled on and immediately off (#6615)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1208671518894266/task/1211091584703241?focus=true ### Description Fixes an issue where toggling the experimental address bar on and then immediately off again in "AI Features" settings still caused the Input Screen to open when returning to the browser (if left on a New Tab Page). #### Root Cause The Input Screen preferences flow in the `BrowserTabViewModel` (and its nested flow that launches the Input Screen on NTP) was being restarted every time the preference changed. As a result, toggling the setting caused a re-evaluation, which queued a command to launch the Input Screen when returning to the browser. #### Fix Instead of observing the Input Screen preference flow directly, we now filter out events when the preference is disabled. This prevents unnecessary re-evaluations on every toggle, while still ensuring that whenever the tab changes, the current preference state is correctly respected. ### Steps to test this PR - [x] Install a clean build of the app. - [x] Open a new tab and ensure that the address bar gets focused but the Input Screen doesn't open. - [x] Go to Settings -> AI Features - [x] Enabled Experimental Address Bar - [x] Disabled Experimental Address Bar - [x] Go back to the browser and verify that Input Screen is not opened - [x] (optional) Try some additional test steps from #6604 to ensure that the feature still works as expected otherwise
1 parent 50b482f commit 46e5d0c

File tree

4 files changed

+44
-17
lines changed

4 files changed

+44
-17
lines changed

.maestro/input_screen/input_screen_preference_management.yaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ tags:
2626
- action: back
2727
- action: back
2828

29-
# verify that the Input Screen doesn't automatically open when on new tab page
29+
# verify that the Input Screen doesn't automatically open when coming back from settings
3030
- assertNotVisible:
3131
id: "inputModeWidget"
3232
- assertVisible:
@@ -39,6 +39,16 @@ tags:
3939
- assertVisible:
4040
id: "inputModeWidget"
4141

42+
# verify that opening new tab automatically opens the input screen
43+
- inputText: "eff.org"
44+
- tapOn:
45+
id: "actionSend"
46+
- runFlow: ../shared/browser_screen/click_on_tabs_button.yaml
47+
- tapOn: "New Tab"
48+
- assertVisible:
49+
id: "inputModeWidget"
50+
- action: back
51+
4252
# disable global AI setting and verify the Input Screen is disabled
4353
- action: back
4454
- runFlow: ../shared/open_ai_settings_screen.yaml

.rules/maestro-ui-tests.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ The Maestro tests are organized into the following (non-exhaustive) main categor
2828
- `custom_tabs` - Custom tabs functionality tests
2929
- `duckplayer` - DuckPlayer tests. Some of these can only be run locally.
3030
- `favorites` - Favorites management tests
31-
- `fire_button` - Fire button (data clearing) tests
31+
- `fire_button` - Fire button (data clearing) tests
32+
- `input_screen` - Input Screen and Experimental Address Bar that provides Search and Duck.ai toggle switch tests
3233
- `notifications_permissions_android13_plus` - Notification permission tests (Android 13+ only)
3334
- `onboarding` - User onboarding flow tests
3435
- `ppro` - Privacy Pro subscription tests

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7073,6 +7073,30 @@ class BrowserTabViewModelTest {
70737073
)
70747074
}
70757075

7076+
@Test
7077+
fun whenInputScreenPrefChangesToEnabledThenLaunchInputScreenCommandNotTriggered() = runTest {
7078+
val initialTabId = "initial-tab"
7079+
val initialTab = TabEntity(tabId = initialTabId, url = "https://example.com", title = "EX", skipHome = false, viewed = true, position = 0)
7080+
val ntpTabId = "ntp-tab"
7081+
val ntpTab = TabEntity(tabId = ntpTabId, url = null, title = "", skipHome = false, viewed = true, position = 0)
7082+
whenever(mockTabRepository.getTab(initialTabId)).thenReturn(initialTab)
7083+
whenever(mockTabRepository.getTab(ntpTabId)).thenReturn(ntpTab)
7084+
flowSelectedTab.emit(initialTab)
7085+
7086+
testee.loadData(ntpTabId, null, false, false)
7087+
mockDuckAiFeatureStateInputScreenFlow.emit(false)
7088+
7089+
flowSelectedTab.emit(ntpTab)
7090+
mockDuckAiFeatureStateInputScreenFlow.emit(true)
7091+
7092+
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
7093+
val commands = commandCaptor.allValues
7094+
assertFalse(
7095+
"LaunchInputScreen command should NOT be triggered when preference is toggled",
7096+
commands.any { it is Command.LaunchInputScreen },
7097+
)
7098+
}
7099+
70767100
private fun aCredential(): LoginCredentials {
70777101
return LoginCredentials(domain = null, username = null, password = null)
70787102
}

app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,6 @@ import kotlinx.coroutines.flow.distinctUntilChangedBy
379379
import kotlinx.coroutines.flow.drop
380380
import kotlinx.coroutines.flow.filter
381381
import kotlinx.coroutines.flow.flatMapLatest
382-
import kotlinx.coroutines.flow.flowOf
383382
import kotlinx.coroutines.flow.flowOn
384383
import kotlinx.coroutines.flow.launchIn
385384
import kotlinx.coroutines.flow.map
@@ -733,20 +732,13 @@ class BrowserTabViewModel @Inject constructor(
733732
}
734733
.launchIn(viewModelScope)
735734

736-
// observe when user open a new tab page and launch the input screen
737-
duckAiFeatureState.showInputScreen
738-
.flatMapLatest { inputScreenEnabled ->
739-
if (inputScreenEnabled) {
740-
tabRepository.flowSelectedTab
741-
.distinctUntilChangedBy { selectedTab -> selectedTab?.tabId } // only observe when the tab changes and ignore further updates
742-
.filter { selectedTab ->
743-
// if the tab managed by this view model has just been activated, and it's a new tab (it has no URL), then fire an event
744-
val isActiveTab = selectedTab?.tabId == tabId
745-
isActiveTab && selectedTab?.url.isNullOrBlank()
746-
}
747-
} else {
748-
flowOf()
749-
}
735+
// observe when user opens a new tab and launch the input screen, if the feature is enabled
736+
tabRepository.flowSelectedTab
737+
.distinctUntilChangedBy { selectedTab -> selectedTab?.tabId } // only observe when the tab changes and ignore further updates
738+
.filter { selectedTab ->
739+
// if the tab managed by this view model has just been activated, and it's a new tab (it has no URL), then fire an event
740+
val isActiveTab = ::tabId.isInitialized && selectedTab?.tabId == tabId
741+
duckAiFeatureState.showInputScreen.value && isActiveTab && selectedTab?.url.isNullOrBlank()
750742
}
751743
.flowOn(dispatchers.main()) // don't use the immediate dispatcher so that the tabId field has a chance to initialize
752744
.onEach {

0 commit comments

Comments
 (0)