Skip to content

Commit b13e47d

Browse files
malmsteindaxmobile
andauthored
Move BrowserPopupMenu to Browser-ui (#7117)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1174433894299346/task/1211921638789392?focus=true ### Description This PR moves the BrowserMenu to the browser-ui module for ease of use Lint is going to complain because of strings, this will be fixed on a stacked PR ### Steps to test this PR _Browser mode_ - [x] Open the app and visit a site - [x] Open the Browser Menu - [x] Verify all expected items are visible _New Tab mode_ - [x] Open the app and open a new Tab - [x] Open the Browser Menu - [x] Verify all expected items are visible _Custom Tabs_ - [x] Open the app and set it as default browser - [x] Open a third party app that opens links in Custom Tabs (Reddit) - [x] Open the Browser Menu - [x] Verify all expected items are visible _Malicious sites_ - [x] Open the app and navigate to "https://privacy-test-pages.site/security/badware/malware.html” - [x] Open the Browser Menu - [x] Verify that only a few options appear, nothing related to site is visible (print, add bookmark, etc…) - [x] Continue to site - [x] Open the Browser Menu - [x] Verify all expected items are visible (same as any other website) --------- Co-authored-by: Dax The Translator <daxmobile@duckduckgo.com>
1 parent f06d284 commit b13e47d

File tree

79 files changed

+2388
-1026
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+2388
-1026
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ import com.duckduckgo.app.browser.viewstate.FindInPageViewState
132132
import com.duckduckgo.app.browser.viewstate.GlobalLayoutViewState
133133
import com.duckduckgo.app.browser.viewstate.HighlightableButton
134134
import com.duckduckgo.app.browser.viewstate.LoadingViewState
135-
import com.duckduckgo.app.browser.viewstate.VpnMenuState
136135
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout.Action.LearnMore
137136
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout.Action.LeaveSite
138137
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout.Action.ReportError
@@ -225,6 +224,7 @@ import com.duckduckgo.browser.api.autocomplete.AutoComplete.AutoCompleteSuggesti
225224
import com.duckduckgo.browser.api.autocomplete.AutoCompleteSettings
226225
import com.duckduckgo.browser.api.brokensite.BrokenSiteContext
227226
import com.duckduckgo.browser.api.webviewcompat.WebViewCompatWrapper
227+
import com.duckduckgo.browser.ui.browsermenu.VpnMenuState
228228
import com.duckduckgo.common.test.CoroutineTestRule
229229
import com.duckduckgo.common.test.InstantSchedulersRule
230230
import com.duckduckgo.common.ui.tabs.SwipingTabsFeature

app/src/androidTest/java/com/duckduckgo/espresso/BasicJourneyTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,6 @@ class BasicJourneyTest {
4949
onView(allOf(withId(R.id.browserMenu), isClickable())).perform(click())
5050

5151
// check that the forward arrow is visible
52-
onView(withId(R.id.forwardMenuItem)).check(matches(isDisplayed()))
52+
onView(withContentDescription("Forward")).check(matches(isDisplayed()))
5353
}
5454
}

app/src/androidTest/java/com/duckduckgo/espresso/DaxDialogsJourneyTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ class DaxDialogsJourneyTest {
5757
onView(isRoot()).perform(waitForView(withId(R.id.browserMenu)))
5858
onView(allOf(withId(R.id.browserMenu), isClickable())).perform(click())
5959

60-
onView(withId(R.id.forwardMenuItem)).check(matches(isDisplayed()))
60+
onView(withContentDescription("Forward")).check(matches(isDisplayed()))
6161
}
6262
}

app/src/androidTest/java/com/duckduckgo/espresso/privacy/RequestBlockingTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ class RequestBlockingTest {
8888
IdlingRegistry.getInstance().register(idlingResourceForDisableProtections)
8989

9090
onView(allOf(withId(R.id.browserMenu), isClickable())).perform(click())
91-
onView(isRoot()).perform(waitForView(withId(R.id.privacyProtectionMenuItem)))
92-
onView(withId(R.id.privacyProtectionMenuItem)).perform(click())
91+
onView(isRoot()).perform(waitForView(withText("Disable Privacy Protection")))
92+
onView(withText("Disable Privacy Protection")).perform(click())
9393

9494
// handle the privacy protection toggle check screen showing
9595
onView(isRoot()).perform(ViewActions.pressBack())

app/src/androidTest/java/com/duckduckgo/espresso/privacy/SurrogatesTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ class SurrogatesTest {
9595
IdlingRegistry.getInstance().register(idlingResourceForDisableProtections)
9696

9797
onView(allOf(withId(R.id.browserMenu), isClickable())).perform(ViewActions.click())
98-
onView(isRoot()).perform(waitForView(withId(R.id.privacyProtectionMenuItem)))
99-
onView(withId(R.id.privacyProtectionMenuItem)).perform(ViewActions.click())
98+
onView(isRoot()).perform(waitForView(withText("Disable Privacy Protection")))
99+
onView(withText("Disable Privacy Protection")).perform(ViewActions.click())
100100

101101
// handle the privacy protection toggle check screen showing
102102
onView(isRoot()).perform(ViewActions.pressBack())

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

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ import com.duckduckgo.app.browser.history.NavigationHistorySheet
132132
import com.duckduckgo.app.browser.history.NavigationHistorySheet.NavigationHistorySheetListener
133133
import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore
134134
import com.duckduckgo.app.browser.logindetection.DOMLoginDetector
135-
import com.duckduckgo.app.browser.menu.BrowserPopupMenu
135+
import com.duckduckgo.app.browser.menu.BrowserMenuViewStateFactory
136136
import com.duckduckgo.app.browser.menu.VpnMenuStore
137137
import com.duckduckgo.app.browser.model.BasicAuthenticationCredentials
138138
import com.duckduckgo.app.browser.model.BasicAuthenticationRequest
@@ -174,7 +174,6 @@ import com.duckduckgo.app.browser.viewstate.LoadingViewState
174174
import com.duckduckgo.app.browser.viewstate.OmnibarViewState
175175
import com.duckduckgo.app.browser.viewstate.PrivacyShieldViewState
176176
import com.duckduckgo.app.browser.viewstate.SavedSiteChangedViewState
177-
import com.duckduckgo.app.browser.viewstate.VpnMenuState
178177
import com.duckduckgo.app.browser.webauthn.WebViewPasskeyInitializer
179178
import com.duckduckgo.app.browser.webshare.WebShareChooser
180179
import com.duckduckgo.app.browser.webshare.WebViewCompatWebShareChooser
@@ -249,6 +248,8 @@ import com.duckduckgo.browser.api.ui.BrowserScreens.PrivateSearchScreenNoParams
249248
import com.duckduckgo.browser.api.ui.BrowserScreens.WebViewActivityWithParams
250249
import com.duckduckgo.browser.api.webviewcompat.WebViewCompatWrapper
251250
import com.duckduckgo.browser.ui.autocomplete.BrowserAutoCompleteSuggestionsAdapter
251+
import com.duckduckgo.browser.ui.browsermenu.BrowserMenu
252+
import com.duckduckgo.browser.ui.browsermenu.VpnMenuState
252253
import com.duckduckgo.common.ui.DuckDuckGoActivity
253254
import com.duckduckgo.common.ui.DuckDuckGoFragment
254255
import com.duckduckgo.common.ui.store.BrowserAppTheme
@@ -608,7 +609,7 @@ class BrowserTabFragment :
608609

609610
private val skipHome get() = requireArguments().getBoolean(SKIP_HOME_ARG)
610611

611-
private lateinit var popupMenu: BrowserPopupMenu
612+
private lateinit var popupMenu: BrowserMenu
612613
private lateinit var ctaBottomSheet: PromoBottomSheetDialog
613614
private lateinit var widgetBottomSheetDialog: AlternativeHomeScreenWidgetBottomSheetDialog
614615

@@ -1305,17 +1306,11 @@ class BrowserTabFragment :
13051306
}
13061307

13071308
private fun createPopupMenu() {
1308-
val popupMenuResourceType =
1309-
when (omnibar.omnibarType) {
1310-
OmnibarType.SINGLE_TOP -> BrowserPopupMenu.ResourceType.TOP
1311-
OmnibarType.SINGLE_BOTTOM, OmnibarType.SPLIT -> BrowserPopupMenu.ResourceType.BOTTOM
1312-
}
1313-
13141309
popupMenu =
1315-
BrowserPopupMenu(
1310+
BrowserMenu(
13161311
context = requireContext(),
13171312
layoutInflater = layoutInflater,
1318-
popupMenuResourceType = popupMenuResourceType,
1313+
omnibarType = omnibar.omnibarType,
13191314
)
13201315
popupMenu.apply {
13211316
onMenuItemClicked(forwardMenuItem) {
@@ -1435,7 +1430,6 @@ class BrowserTabFragment :
14351430

14361431
private fun launchPopupMenu(anchorToNavigationBar: Boolean, addExtraDelay: Boolean = false) {
14371432
val isFocusedNtp = omnibar.viewMode == ViewMode.NewTab && omnibar.getText().isEmpty() && omnibar.omnibarTextInput.hasFocus()
1438-
14391433
val delay = if (addExtraDelay) POPUP_MENU_DELAY * 2 else POPUP_MENU_DELAY
14401434
// small delay added to let keyboard disappear and avoid jarring transition
14411435
binding.rootView.postDelayed(delay) {
@@ -1448,7 +1442,6 @@ class BrowserTabFragment :
14481442
vpnMenuStore.incrementVpnMenuShownCount()
14491443
}
14501444
}
1451-
14521445
if (anchorToNavigationBar) {
14531446
val anchorView = browserNavigationBarIntegration.navigationBarView.popupMenuAnchor
14541447
popupMenu.showAnchoredView(requireActivity(), binding.rootView, anchorView)
@@ -1770,6 +1763,16 @@ class BrowserTabFragment :
17701763
browserNavigationBarIntegration.configureBrowserViewMode()
17711764
}
17721765

1766+
private fun showDuckAI(browserViewState: BrowserViewState) {
1767+
val browseMenuState = BrowserMenuViewStateFactory.create(
1768+
omnibarViewMode = ViewMode.DuckAI,
1769+
viewState = browserViewState,
1770+
customTabsMode = tabDisplayedInCustomTabScreen,
1771+
)
1772+
popupMenu.render(browseMenuState)
1773+
omnibar.setViewMode(ViewMode.DuckAI)
1774+
}
1775+
17731776
private fun showMaliciousWarning(
17741777
siteUrl: Uri,
17751778
feed: Feed,
@@ -2354,7 +2357,7 @@ class BrowserTabFragment :
23542357
is Command.SubmitChat -> duckChat.openDuckChatWithAutoPrompt(it.query)
23552358
is Command.EnqueueCookiesAnimation -> enqueueCookiesAnimation(it.isCosmetic)
23562359
is Command.PageStarted -> onPageStarted()
2357-
is Command.EnableDuckAIFullScreen -> omnibar.setViewMode(ViewMode.DuckAI)
2360+
is Command.EnableDuckAIFullScreen -> showDuckAI(it.browserViewState)
23582361
is Command.DisableDuckAIFullScreen -> omnibar.setViewMode(ViewMode.Browser(it.url))
23592362
}
23602363
}
@@ -4563,7 +4566,13 @@ class BrowserTabFragment :
45634566

45644567
browserNavigationBarIntegration.configureFireButtonHighlight(highlighted = viewState.fireButton.isHighlighted())
45654568

4566-
popupMenu.renderState(browserShowing, viewState, tabDisplayedInCustomTabScreen)
4569+
val browseMenuState = BrowserMenuViewStateFactory.create(
4570+
omnibarViewMode = omnibar.viewMode,
4571+
viewState = viewState,
4572+
customTabsMode = tabDisplayedInCustomTabScreen,
4573+
)
4574+
logcat { "BrowserMenu: viewMode ${omnibar.viewMode} render browseMenuState $browseMenuState" }
4575+
popupMenu.render(browseMenuState)
45674576

45684577
renderFullscreenMode(viewState)
45694578
privacyProtectionsPopup.setViewState(viewState.privacyProtectionsPopupViewState)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ import com.duckduckgo.app.browser.viewstate.LoadingViewState
214214
import com.duckduckgo.app.browser.viewstate.OmnibarViewState
215215
import com.duckduckgo.app.browser.viewstate.PrivacyShieldViewState
216216
import com.duckduckgo.app.browser.viewstate.SavedSiteChangedViewState
217-
import com.duckduckgo.app.browser.viewstate.VpnMenuState
218217
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout
219218
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout.Action.LearnMore
220219
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout.Action.LeaveSite
@@ -295,6 +294,7 @@ import com.duckduckgo.browser.api.brokensite.BrokenSiteData
295294
import com.duckduckgo.browser.api.brokensite.BrokenSiteData.ReportFlow.MENU
296295
import com.duckduckgo.browser.api.brokensite.BrokenSiteData.ReportFlow.RELOAD_THREE_TIMES_WITHIN_20_SECONDS
297296
import com.duckduckgo.browser.api.webviewcompat.WebViewCompatWrapper
297+
import com.duckduckgo.browser.ui.browsermenu.VpnMenuState
298298
import com.duckduckgo.common.ui.tabs.SwipingTabsFeatureProvider
299299
import com.duckduckgo.common.utils.AppUrl
300300
import com.duckduckgo.common.utils.AppUrl.ParamKey.QUERY
@@ -1969,7 +1969,7 @@ class BrowserTabViewModel @Inject constructor(
19691969
if (duckAiFeatureState.showFullScreenMode.value) {
19701970
if (duckDuckGoUrlDetector.isDuckDuckGoChatUrl(it)) {
19711971
logcat { "Duck.ai: AI Chat page loaded $it" }
1972-
command.value = Command.EnableDuckAIFullScreen
1972+
command.value = Command.EnableDuckAIFullScreen(currentBrowserViewState())
19731973
} else {
19741974
command.value = Command.DisableDuckAIFullScreen(url)
19751975
}

app/src/main/java/com/duckduckgo/app/browser/commands/Command.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import com.duckduckgo.app.browser.WebViewErrorResponse
3535
import com.duckduckgo.app.browser.history.NavigationHistoryEntry
3636
import com.duckduckgo.app.browser.model.BasicAuthenticationCredentials
3737
import com.duckduckgo.app.browser.model.BasicAuthenticationRequest
38+
import com.duckduckgo.app.browser.viewstate.BrowserViewState
3839
import com.duckduckgo.app.browser.viewstate.SavedSiteChangedViewState
3940
import com.duckduckgo.app.cta.ui.BrokenSitePromptDialogCta
4041
import com.duckduckgo.app.cta.ui.DaxBubbleCta
@@ -504,6 +505,6 @@ sealed class Command {
504505
) : Command()
505506
data object PageStarted : Command()
506507

507-
data object EnableDuckAIFullScreen : Command()
508+
data class EnableDuckAIFullScreen(val browserViewState: BrowserViewState) : Command()
508509
data class DisableDuckAIFullScreen(val url: String) : Command()
509510
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.browser.menu
18+
19+
import com.duckduckgo.app.browser.SSLErrorType.NONE
20+
import com.duckduckgo.app.browser.omnibar.Omnibar
21+
import com.duckduckgo.app.browser.viewstate.BrowserViewState
22+
import com.duckduckgo.browser.ui.browsermenu.BrowserMenuViewState
23+
24+
object BrowserMenuViewStateFactory {
25+
fun create(
26+
omnibarViewMode: Omnibar.ViewMode,
27+
viewState: BrowserViewState,
28+
customTabsMode: Boolean,
29+
): BrowserMenuViewState {
30+
return if (customTabsMode) {
31+
createCustomTabsViewState(viewState)
32+
} else {
33+
when (omnibarViewMode) {
34+
Omnibar.ViewMode.NewTab -> createNewTabPageViewState(viewState)
35+
Omnibar.ViewMode.DuckAI -> createDuckAiViewState(viewState)
36+
Omnibar.ViewMode.Error -> createNewTabPageViewState(viewState)
37+
Omnibar.ViewMode.SSLWarning -> createNewTabPageViewState(viewState)
38+
Omnibar.ViewMode.MaliciousSiteWarning -> createNewTabPageViewState(viewState)
39+
else -> createBrowserViewState(browserViewState = viewState)
40+
}
41+
}
42+
}
43+
44+
private fun createCustomTabsViewState(
45+
browserViewState: BrowserViewState,
46+
): BrowserMenuViewState.CustomTabs {
47+
return BrowserMenuViewState.CustomTabs(
48+
canGoBack = browserViewState.canGoBack,
49+
canGoForward = browserViewState.canGoForward,
50+
canSharePage = browserViewState.canSharePage,
51+
canChangeBrowsingMode = browserViewState.canChangeBrowsingMode,
52+
isDesktopBrowsingMode = browserViewState.isDesktopBrowsingMode,
53+
canChangePrivacyProtection = browserViewState.canChangePrivacyProtection,
54+
isPrivacyProtectionDisabled = browserViewState.isPrivacyProtectionDisabled,
55+
)
56+
}
57+
58+
private fun createNewTabPageViewState(
59+
browserViewState: BrowserViewState,
60+
): BrowserMenuViewState.NewTabPage {
61+
return BrowserMenuViewState.NewTabPage(
62+
showDuckChatOption = browserViewState.showDuckChatOption,
63+
vpnMenuState = browserViewState.vpnMenuState,
64+
showAutofill = browserViewState.showAutofill,
65+
)
66+
}
67+
68+
private fun createDuckAiViewState(
69+
browserViewState: BrowserViewState,
70+
): BrowserMenuViewState.DuckAi {
71+
return BrowserMenuViewState.DuckAi(
72+
canPrintPage = browserViewState.canPrintPage,
73+
canReportSite = browserViewState.canReportSite,
74+
)
75+
}
76+
77+
private fun createBrowserViewState(
78+
browserViewState: BrowserViewState,
79+
): BrowserMenuViewState.Browser {
80+
return BrowserMenuViewState.Browser(
81+
canGoBack = browserViewState.canGoBack,
82+
canGoForward = browserViewState.canGoForward,
83+
showDuckChatOption = browserViewState.showDuckChatOption,
84+
canSharePage = browserViewState.canSharePage,
85+
showSelectDefaultBrowserMenuItem = browserViewState.showSelectDefaultBrowserMenuItem,
86+
canSaveSite = browserViewState.canSaveSite,
87+
isBookmark = browserViewState.bookmark != null,
88+
canFireproofSite = browserViewState.canFireproofSite,
89+
isFireproofWebsite = browserViewState.isFireproofWebsite,
90+
isEmailSignedIn = browserViewState.isEmailSignedIn,
91+
canChangeBrowsingMode = browserViewState.canChangeBrowsingMode,
92+
isDesktopBrowsingMode = browserViewState.isDesktopBrowsingMode,
93+
hasPreviousAppLink = browserViewState.previousAppLink != null,
94+
canFindInPage = browserViewState.canFindInPage,
95+
addToHomeVisible = browserViewState.addToHomeVisible,
96+
addToHomeEnabled = browserViewState.addToHomeEnabled,
97+
canChangePrivacyProtection = browserViewState.canChangePrivacyProtection,
98+
isPrivacyProtectionDisabled = browserViewState.isPrivacyProtectionDisabled,
99+
canReportSite = browserViewState.canReportSite,
100+
showAutofill = browserViewState.showAutofill,
101+
isSSLError = browserViewState.sslError != NONE,
102+
canPrintPage = browserViewState.canPrintPage,
103+
)
104+
}
105+
}

0 commit comments

Comments
 (0)