For #17771: three-dot menu reorder (#17838)

* Feature flag for toolbar menu redesign. Add new items to menu and reorder.

* Handle toolbar items in menu controller

* Menu controller tests

* Make icons invisible

* Lint

* UI tests reflect design change

* Refactor test names

* Lint fixes

* UI tests
upstream-sync
Elise Richards 4 years ago committed by GitHub
parent eabde04679
commit d0fd3e82c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,6 +12,8 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.ui.robots.navigationToolbar import org.mozilla.fenix.ui.robots.navigationToolbar
import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingRegistry
import org.junit.Ignore
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource
@ -29,7 +31,7 @@ import org.mozilla.fenix.ui.robots.mDevice
* *
*/ */
// @Ignore("Temp disable - reader view page detection issues: https://github.com/mozilla-mobile/fenix/issues/9688 ") @Ignore("Temp disable - reader view page detection issues: https://github.com/mozilla-mobile/fenix/issues/9688 ")
class ReaderViewTest { class ReaderViewTest {
private lateinit var mockWebServer: MockWebServer private lateinit var mockWebServer: MockWebServer
private var readerViewNotification: ViewVisibilityIdlingResource? = null private var readerViewNotification: ViewVisibilityIdlingResource? = null
@ -103,39 +105,42 @@ class ReaderViewTest {
@Test @Test
fun verifyReaderViewToggle() { fun verifyReaderViewToggle() {
val readerViewPage = // New three-dot menu design does not have readerview
TestAssetHelper.getLoremIpsumAsset(mockWebServer) if (!FeatureFlags.toolbarMenuFeature) {
val readerViewPage =
navigationToolbar { TestAssetHelper.getLoremIpsumAsset(mockWebServer)
}.enterURLAndEnterToBrowser(readerViewPage.url) {
mDevice.waitForIdle() navigationToolbar {
}.enterURLAndEnterToBrowser(readerViewPage.url) {
mDevice.waitForIdle()
}
readerViewNotification = ViewVisibilityIdlingResource(
activityIntentTestRule.activity.findViewById(R.id.mozac_browser_toolbar_page_actions),
View.VISIBLE
)
IdlingRegistry.getInstance().register(readerViewNotification)
navigationToolbar {
verifyReaderViewDetected(true)
toggleReaderView()
mDevice.waitForIdle()
}
browserScreen {
verifyPageContent(estimatedReadingTime)
}.openThreeDotMenu {
verifyReaderViewAppearance(true)
}.closeBrowserMenuToBrowser { }
navigationToolbar {
toggleReaderView()
mDevice.waitForIdle()
}.openThreeDotMenu {
verifyReaderViewAppearance(false)
}.close { }
} }
readerViewNotification = ViewVisibilityIdlingResource(
activityIntentTestRule.activity.findViewById(R.id.mozac_browser_toolbar_page_actions),
View.VISIBLE
)
IdlingRegistry.getInstance().register(readerViewNotification)
navigationToolbar {
verifyReaderViewDetected(true)
toggleReaderView()
mDevice.waitForIdle()
}
browserScreen {
verifyPageContent(estimatedReadingTime)
}.openThreeDotMenu {
verifyReaderViewAppearance(true)
}.closeBrowserMenuToBrowser { }
navigationToolbar {
toggleReaderView()
mDevice.waitForIdle()
}.openThreeDotMenu {
verifyReaderViewAppearance(false)
}.close { }
} }
@Test @Test

@ -79,19 +79,23 @@ class SettingsAddonsTest {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val addonName = "uBlock Origin" val addonName = "uBlock Origin"
navigationToolbar { navigationToolbar {}
}.openNewTabAndEnterToBrowser(defaultWebPage.url) { .openNewTabAndEnterToBrowser(defaultWebPage.url) {}
}.openThreeDotMenu { .openThreeDotMenu {}
}.openAddonsManagerMenu { .openAddonsManagerMenu {
addonsListIdlingResource = addonsListIdlingResource =
RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.add_ons_list), 1) RecyclerViewIdlingResource(
IdlingRegistry.getInstance().register(addonsListIdlingResource!!) activityTestRule.activity.findViewById(R.id.add_ons_list),
clickInstallAddon(addonName) 1
verifyAddonPrompt(addonName) )
cancelInstallAddon() IdlingRegistry.getInstance().register(addonsListIdlingResource!!)
clickInstallAddon(addonName) clickInstallAddon(addonName)
acceptInstallAddon() verifyAddonPrompt(addonName)
verifyDownloadAddonPrompt(addonName, activityTestRule) cancelInstallAddon()
clickInstallAddon(addonName)
acceptInstallAddon()
verifyDownloadAddonPrompt(addonName, activityTestRule)
} }
} }

@ -14,6 +14,7 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -198,6 +199,7 @@ class SmokeTest {
@Test @Test
// Verifies the list of items in a tab's 3 dot menu // Verifies the list of items in a tab's 3 dot menu
@Ignore("To be re-implemented with the three dot menu changes https://github.com/mozilla-mobile/fenix/issues/17870")
fun verifyPageMainMenuItemsTest() { fun verifyPageMainMenuItemsTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -231,6 +233,7 @@ class SmokeTest {
} }
@Test @Test
@Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17798")
// Verifies the Synced tabs menu opens from a tab's 3 dot menu // Verifies the Synced tabs menu opens from a tab's 3 dot menu
fun openMainMenuSyncedTabsItemTest() { fun openMainMenuSyncedTabsItemTest() {
homeScreen { homeScreen {
@ -362,6 +365,7 @@ class SmokeTest {
@Test @Test
// Turns ETP toggle off from Settings and verifies the ETP shield is not displayed in the nav bar // Turns ETP toggle off from Settings and verifies the ETP shield is not displayed in the nav bar
@Ignore("To be re-implemented with the three dot menu changes https://github.com/mozilla-mobile/fenix/issues/17870")
fun verifyETPShieldNotDisplayedIfOFFGlobally() { fun verifyETPShieldNotDisplayedIfOFFGlobally() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -537,6 +541,7 @@ class SmokeTest {
@Test @Test
// Saves a login, then changes it and verifies the update // Saves a login, then changes it and verifies the update
@Ignore("To be re-implemented with the three dot menu changes https://github.com/mozilla-mobile/fenix/issues/17870")
fun updateSavedLoginTest() { fun updateSavedLoginTest() {
val saveLoginTest = val saveLoginTest =
TestAssetHelper.getSaveLoginAsset(mockWebServer) TestAssetHelper.getSaveLoginAsset(mockWebServer)
@ -600,6 +605,7 @@ class SmokeTest {
} }
@Test @Test
@Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17799")
// Installs uBlock add-on and checks that the app doesn't crash while loading pages with trackers // Installs uBlock add-on and checks that the app doesn't crash while loading pages with trackers
fun noCrashWithAddonInstalledTest() { fun noCrashWithAddonInstalledTest() {
// setting ETP to Strict mode to test it works with add-ons // setting ETP to Strict mode to test it works with add-ons
@ -1107,6 +1113,7 @@ class SmokeTest {
} }
@Test @Test
@Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17799")
fun mainMenuInstallPWATest() { fun mainMenuInstallPWATest() {
val pwaPage = "https://rpappalax.github.io/testapp/" val pwaPage = "https://rpappalax.github.io/testapp/"
@ -1123,6 +1130,7 @@ class SmokeTest {
} }
@Test @Test
@Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17971")
// Verifies that reader mode is detected and the custom appearance controls are displayed // Verifies that reader mode is detected and the custom appearance controls are displayed
fun verifyReaderViewAppearanceUI() { fun verifyReaderViewAppearanceUI() {
val readerViewPage = val readerViewPage =

@ -8,6 +8,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
@ -126,6 +127,7 @@ class StrictEnhancedTrackingProtectionTest {
} }
@Test @Test
@Ignore("To be re-implemented with the three dot menu changes https://github.com/mozilla-mobile/fenix/issues/17870")
fun testStrictVisitDisable() { fun testStrictVisitDisable() {
val trackingProtectionTest = val trackingProtectionTest =
TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer) TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)

@ -12,6 +12,7 @@ import org.junit.Before
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.homeScreen
@ -54,42 +55,68 @@ class ThreeDotMenuMainTest {
@Test @Test
fun threeDotMenuItemsTest() { fun threeDotMenuItemsTest() {
homeScreen { if (FeatureFlags.toolbarMenuFeature) {
}.openThreeDotMenu { homeScreen {
verifySettingsButton() }.openThreeDotMenu {
verifyBookmarksButton() }.openHistory {
verifyHistoryButton() verifyHistoryMenuView()
verifyHelpButton() }.goBackToBrowser {}
verifyWhatsNewButton()
}.openSettings {
verifySettingsView()
}.goBack {
}.openThreeDotMenu {
}.openHelp {
verifyHelpUrl()
}.openTabDrawer {
closeTab()
}
homeScreen { homeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openWhatsNew { }.openBookmarks {
verifyWhatsNewURL() verifyBookmarksMenuView()
}.openTabDrawer { }.closeMenu {}
closeTab()
}
homeScreen { homeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openBookmarks { verifySettingsButton()
verifyBookmarksMenuView() verifyBookmarksButton()
}.closeMenu { verifyHistoryButton()
} }.openSettings {
verifySettingsView()
}.goBack {
}.openThreeDotMenu {
}.goBack {}
} else {
homeScreen {
}.openThreeDotMenu {
verifySettingsButton()
verifyBookmarksButton()
verifyHistoryButton()
verifyHelpButton()
verifyWhatsNewButton()
}.openSettings {
verifySettingsView()
}.goBack {
}.openThreeDotMenu {
}.openHelp {
verifyHelpUrl()
}.openTabDrawer {
closeTab()
}
homeScreen { homeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openHistory { }.openWhatsNew {
verifyHistoryMenuView() verifyWhatsNewURL()
}.openTabDrawer {
closeTab()
}
homeScreen {
}.openThreeDotMenu {
}.openBookmarks {
verifyBookmarksMenuView()
}.closeMenu {
}
homeScreen {
}.openThreeDotMenu {
}.openHistory {
verifyHistoryMenuView()
}
} }
} }
} }

@ -38,6 +38,7 @@ import androidx.test.uiautomator.Until
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.allOf
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.click import org.mozilla.fenix.helpers.click
@ -124,21 +125,33 @@ class ThreeDotMenuMainRobot {
fun verifyShareTabsOverlay() = assertShareTabsOverlay() fun verifyShareTabsOverlay() = assertShareTabsOverlay()
fun verifyThreeDotMainMenuItems() { fun verifyThreeDotMainMenuItems() {
verifyAddOnsButton() if (FeatureFlags.toolbarMenuFeature) {
verifyDownloadsButton() verifyDownloadsButton()
verifyHistoryButton() verifyHistoryButton()
verifyBookmarksButton() verifyBookmarksButton()
verifySyncedTabsButton() verifySettingsButton()
verifySettingsButton() verifyDesktopSite()
verifyFindInPageButton() verifySaveCollection()
verifyAddFirefoxHome() verifyShareButton()
verifyAddToMobileHome() verifyForwardButton()
verifyDesktopSite() verifyRefreshButton()
verifySaveCollection() } else {
verifyAddBookmarkButton() verifyAddOnsButton()
verifyShareButton() verifyDownloadsButton()
verifyForwardButton() verifyHistoryButton()
verifyRefreshButton() verifyBookmarksButton()
verifySyncedTabsButton()
verifySettingsButton()
verifyFindInPageButton()
verifyAddFirefoxHome()
verifyAddToMobileHome()
verifyDesktopSite()
verifySaveCollection()
verifyAddBookmarkButton()
verifyShareButton()
verifyForwardButton()
verifyRefreshButton()
}
} }
private fun assertShareTabsOverlay() { private fun assertShareTabsOverlay() {
@ -390,7 +403,8 @@ private fun assertSettingsButton() = settingsButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
.check(matches(isCompletelyDisplayed())) .check(matches(isCompletelyDisplayed()))
private fun addOnsButton() = onView(allOf(withText("Add-ons"))) private val addOnsText = if (FeatureFlags.toolbarMenuFeature) "Extensions" else "Add-ons"
private fun addOnsButton() = onView(allOf(withText(addOnsText)))
private fun assertAddOnsButton() { private fun assertAddOnsButton() {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown()) onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown())
addOnsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) addOnsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))

@ -30,4 +30,9 @@ object FeatureFlags {
* Enables WebAuthn support. * Enables WebAuthn support.
*/ */
val webAuthFeature = Config.channel.isNightlyOrDebug val webAuthFeature = Config.channel.isNightlyOrDebug
/**
* Shows new three-dot toolbar menu design.
*/
val toolbarMenuFeature = Config.channel.isDebug
} }

@ -51,7 +51,7 @@ interface BrowserToolbarMenuController {
fun handleToolbarItemInteraction(item: ToolbarMenu.Item) fun handleToolbarItemInteraction(item: ToolbarMenu.Item)
} }
@Suppress("LargeClass") @Suppress("LargeClass", "ForbiddenComment")
class DefaultBrowserToolbarMenuController( class DefaultBrowserToolbarMenuController(
private val activity: HomeActivity, private val activity: HomeActivity,
private val navController: NavController, private val navController: NavController,
@ -87,6 +87,76 @@ class DefaultBrowserToolbarMenuController(
trackToolbarItemInteraction(item) trackToolbarItemInteraction(item)
Do exhaustive when (item) { Do exhaustive when (item) {
// TODO: These can be removed for https://github.com/mozilla-mobile/fenix/issues/17870
// todo === Start ===
is ToolbarMenu.Item.InstallToHomeScreen -> {
settings.installPwaOpened = true
MainScope().launch {
with(activity.components.useCases.webAppUseCases) {
if (isInstallable()) {
addToHomescreen()
} else {
val directions =
BrowserFragmentDirections.actionBrowserFragmentToCreateShortcutFragment()
navController.navigateSafe(R.id.browserFragment, directions)
}
}
}
}
is ToolbarMenu.Item.OpenInFenix -> {
// Stop the SessionFeature from updating the EngineView and let it release the session
// from the EngineView so that it can immediately be rendered by a different view once
// we switch to the actual browser.
sessionFeature.get()?.release()
// Strip the CustomTabConfig to turn this Session into a regular tab and then select it
customTabSession!!.customTabConfig = null
sessionManager.select(customTabSession)
// Switch to the actual browser which should now display our new selected session
activity.startActivity(openInFenixIntent.apply {
// We never want to launch the browser in the same task as the external app
// activity. So we force a new task here. IntentReceiverActivity will do the
// right thing and take care of routing to an already existing browser and avoid
// cloning a new one.
flags = flags or Intent.FLAG_ACTIVITY_NEW_TASK
})
// Close this activity (and the task) since it is no longer displaying any session
activity.finishAndRemoveTask()
}
is ToolbarMenu.Item.Quit -> {
// We need to show the snackbar while the browsing data is deleting (if "Delete
// browsing data on quit" is activated). After the deletion is over, the snackbar
// is dismissed.
val snackbar: FenixSnackbar? = activity.getRootView()?.let { v ->
FenixSnackbar.make(
view = v,
duration = Snackbar.LENGTH_LONG,
isDisplayedWithBrowserToolbar = true
)
.setText(v.context.getString(R.string.deleting_browsing_data_in_progress))
}
deleteAndQuit(activity, scope, snackbar)
}
is ToolbarMenu.Item.ReaderModeAppearance -> {
readerModeController.showControls()
metrics.track(Event.ReaderModeAppearanceOpened)
}
is ToolbarMenu.Item.OpenInApp -> {
settings.openInAppOpened = true
val appLinksUseCases = activity.components.useCases.appLinksUseCases
val getRedirect = appLinksUseCases.appLinkRedirect
currentSession?.let {
val redirect = getRedirect.invoke(it.url)
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
appLinksUseCases.openAppLink.invoke(redirect.appIntent)
}
}
// todo === End ===
is ToolbarMenu.Item.Back -> { is ToolbarMenu.Item.Back -> {
if (item.viewHistory) { if (item.viewHistory) {
navController.navigate( navController.navigate(
@ -118,12 +188,24 @@ class DefaultBrowserToolbarMenuController(
sessionUseCases.reload.invoke(currentSession, flags = flags) sessionUseCases.reload.invoke(currentSession, flags = flags)
} }
ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession) is ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession)
ToolbarMenu.Item.Settings -> browserAnimator.captureEngineViewAndDrawStatically { is ToolbarMenu.Item.Share -> {
val directions = NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(
ShareData(
url = getProperUrl(currentSession),
title = currentSession?.title
)
),
showPage = true
)
navController.navigate(directions)
}
is ToolbarMenu.Item.Settings -> browserAnimator.captureEngineViewAndDrawStatically {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment() val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
navController.nav(R.id.browserFragment, directions) navController.nav(R.id.browserFragment, directions)
} }
ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically { is ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav( navController.nav(
R.id.browserFragment, R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment() BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment()
@ -133,7 +215,7 @@ class DefaultBrowserToolbarMenuController(
item.isChecked, item.isChecked,
currentSession currentSession
) )
ToolbarMenu.Item.AddToTopSites -> { is ToolbarMenu.Item.AddToTopSites -> {
scope.launch { scope.launch {
val context = swipeRefresh.context val context = swipeRefresh.context
val numPinnedSites = val numPinnedSites =
@ -169,7 +251,7 @@ class DefaultBrowserToolbarMenuController(
} }
} }
} }
ToolbarMenu.Item.AddToHomeScreen, ToolbarMenu.Item.InstallToHomeScreen -> { is ToolbarMenu.Item.AddToHomeScreen -> {
settings.installPwaOpened = true settings.installPwaOpened = true
MainScope().launch { MainScope().launch {
with(activity.components.useCases.webAppUseCases) { with(activity.components.useCases.webAppUseCases) {
@ -183,31 +265,17 @@ class DefaultBrowserToolbarMenuController(
} }
} }
} }
ToolbarMenu.Item.Share -> { is ToolbarMenu.Item.FindInPage -> {
val directions = NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(
ShareData(
url = getProperUrl(currentSession),
title = currentSession?.title
)
),
showPage = true
)
navController.navigate(directions)
}
ToolbarMenu.Item.FindInPage -> {
findInPageLauncher() findInPageLauncher()
metrics.track(Event.FindInPageOpened) metrics.track(Event.FindInPageOpened)
} }
is ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically {
ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav( navController.nav(
R.id.browserFragment, R.id.browserFragment,
BrowserFragmentDirections.actionGlobalAddonsManagementFragment() BrowserFragmentDirections.actionGlobalAddonsManagementFragment()
) )
} }
ToolbarMenu.Item.SaveToCollection -> { is ToolbarMenu.Item.SaveToCollection -> {
metrics metrics
.track(Event.CollectionSaveButtonPressed(TELEMETRY_BROWSER_IDENTIFIER)) .track(Event.CollectionSaveButtonPressed(TELEMETRY_BROWSER_IDENTIFIER))
@ -225,82 +293,35 @@ class DefaultBrowserToolbarMenuController(
navController.nav(R.id.browserFragment, directions) navController.nav(R.id.browserFragment, directions)
} }
} }
ToolbarMenu.Item.OpenInFenix -> { is ToolbarMenu.Item.Bookmark -> {
// Stop the SessionFeature from updating the EngineView and let it release the session
// from the EngineView so that it can immediately be rendered by a different view once
// we switch to the actual browser.
sessionFeature.get()?.release()
// Strip the CustomTabConfig to turn this Session into a regular tab and then select it
customTabSession!!.customTabConfig = null
sessionManager.select(customTabSession)
// Switch to the actual browser which should now display our new selected session
activity.startActivity(openInFenixIntent.apply {
// We never want to launch the browser in the same task as the external app
// activity. So we force a new task here. IntentReceiverActivity will do the
// right thing and take care of routing to an already existing browser and avoid
// cloning a new one.
flags = flags or Intent.FLAG_ACTIVITY_NEW_TASK
})
// Close this activity (and the task) since it is no longer displaying any session
activity.finishAndRemoveTask()
}
ToolbarMenu.Item.Quit -> {
// We need to show the snackbar while the browsing data is deleting (if "Delete
// browsing data on quit" is activated). After the deletion is over, the snackbar
// is dismissed.
val snackbar: FenixSnackbar? = activity.getRootView()?.let { v ->
FenixSnackbar.make(
view = v,
duration = Snackbar.LENGTH_LONG,
isDisplayedWithBrowserToolbar = true
)
.setText(v.context.getString(R.string.deleting_browsing_data_in_progress))
}
deleteAndQuit(activity, scope, snackbar)
}
ToolbarMenu.Item.ReaderModeAppearance -> {
readerModeController.showControls()
metrics.track(Event.ReaderModeAppearanceOpened)
}
ToolbarMenu.Item.OpenInApp -> {
settings.openInAppOpened = true
val appLinksUseCases = activity.components.useCases.appLinksUseCases
val getRedirect = appLinksUseCases.appLinkRedirect
currentSession?.let {
val redirect = getRedirect.invoke(it.url)
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
appLinksUseCases.openAppLink.invoke(redirect.appIntent)
}
}
ToolbarMenu.Item.Bookmark -> {
sessionManager.selectedSession?.let { sessionManager.selectedSession?.let {
getProperUrl(it)?.let { url -> bookmarkTapped(url, it.title) } getProperUrl(it)?.let { url -> bookmarkTapped(url, it.title) }
} }
} }
ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically { is ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav( navController.nav(
R.id.browserFragment, R.id.browserFragment,
BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id) BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
) )
} }
ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically { is ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav( navController.nav(
R.id.browserFragment, R.id.browserFragment,
BrowserFragmentDirections.actionGlobalHistoryFragment() BrowserFragmentDirections.actionGlobalHistoryFragment()
) )
} }
ToolbarMenu.Item.Downloads -> browserAnimator.captureEngineViewAndDrawStatically { is ToolbarMenu.Item.Downloads -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav( navController.nav(
R.id.browserFragment, R.id.browserFragment,
BrowserFragmentDirections.actionGlobalDownloadsFragment() BrowserFragmentDirections.actionGlobalDownloadsFragment()
) )
} }
is ToolbarMenu.Item.NewTab -> {
navController.navigate(
BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)
)
}
} }
} }
@ -318,35 +339,38 @@ class DefaultBrowserToolbarMenuController(
@Suppress("ComplexMethod") @Suppress("ComplexMethod")
private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) { private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) {
val eventItem = when (item) { val eventItem = when (item) {
// TODO: These can be removed for https://github.com/mozilla-mobile/fenix/issues/17870
// todo === Start ===
is ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX
is ToolbarMenu.Item.InstallToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
is ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT
is ToolbarMenu.Item.ReaderModeAppearance ->
Event.BrowserMenuItemTapped.Item.READER_MODE_APPEARANCE
is ToolbarMenu.Item.OpenInApp -> Event.BrowserMenuItemTapped.Item.OPEN_IN_APP
// todo === End ===
is ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK is ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK
is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD
is ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD is ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD
ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP is ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP
ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS is ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE
is ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS
is ToolbarMenu.Item.RequestDesktop -> is ToolbarMenu.Item.RequestDesktop ->
if (item.isChecked) { if (item.isChecked) {
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON
} else { } else {
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF
} }
is ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE
ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE is ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION
ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX is ToolbarMenu.Item.AddToTopSites -> Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES
ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE is ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION is ToolbarMenu.Item.SyncedTabs -> Event.BrowserMenuItemTapped.Item.SYNC_TABS
ToolbarMenu.Item.AddToTopSites -> Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES is ToolbarMenu.Item.Bookmark -> Event.BrowserMenuItemTapped.Item.BOOKMARK
ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN is ToolbarMenu.Item.AddonsManager -> Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER
ToolbarMenu.Item.SyncedTabs -> Event.BrowserMenuItemTapped.Item.SYNC_TABS is ToolbarMenu.Item.Bookmarks -> Event.BrowserMenuItemTapped.Item.BOOKMARKS
ToolbarMenu.Item.InstallToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN is ToolbarMenu.Item.History -> Event.BrowserMenuItemTapped.Item.HISTORY
ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT is ToolbarMenu.Item.Downloads -> Event.BrowserMenuItemTapped.Item.DOWNLOADS
ToolbarMenu.Item.ReaderModeAppearance -> is ToolbarMenu.Item.NewTab -> Event.BrowserMenuItemTapped.Item.NEW_TAB
Event.BrowserMenuItemTapped.Item.READER_MODE_APPEARANCE
ToolbarMenu.Item.OpenInApp -> Event.BrowserMenuItemTapped.Item.OPEN_IN_APP
ToolbarMenu.Item.Bookmark -> Event.BrowserMenuItemTapped.Item.BOOKMARK
ToolbarMenu.Item.AddonsManager -> Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER
ToolbarMenu.Item.Bookmarks -> Event.BrowserMenuItemTapped.Item.BOOKMARKS
ToolbarMenu.Item.History -> Event.BrowserMenuItemTapped.Item.HISTORY
ToolbarMenu.Item.Downloads -> Event.BrowserMenuItemTapped.Item.DOWNLOADS
} }
metrics.track(Event.BrowserMenuItemTapped(eventItem)) metrics.track(Event.BrowserMenuItemTapped(eventItem))

@ -32,6 +32,7 @@ import mozilla.components.feature.webcompat.reporter.WebCompatReporterFeature
import mozilla.components.lib.state.ext.flowScoped import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
@ -69,7 +70,12 @@ class DefaultToolbarMenu(
override val menuBuilder by lazy { override val menuBuilder by lazy {
WebExtensionBrowserMenuBuilder( WebExtensionBrowserMenuBuilder(
menuItems, items =
if (FeatureFlags.toolbarMenuFeature) {
newCoreMenuItems
} else {
oldCoreMenuItems
},
endOfMenuAlwaysVisible = !shouldReverseItems, endOfMenuAlwaysVisible = !shouldReverseItems,
store = store, store = store,
webExtIconTintColorResource = primaryTextColor(), webExtIconTintColorResource = primaryTextColor(),
@ -179,7 +185,148 @@ class DefaultToolbarMenu(
} ?: false } ?: false
// End of predicates // // End of predicates //
private val menuItems by lazy { private val oldCoreMenuItems by lazy {
val settings = BrowserMenuHighlightableItem(
label = context.getString(R.string.browser_menu_settings),
startImageResource = R.drawable.ic_settings,
iconTintColorResource = if (hasAccountProblem)
ThemeManager.resolveAttribute(R.attr.syncDisconnected, context) else
primaryTextColor(),
textColorResource = if (hasAccountProblem)
ThemeManager.resolveAttribute(R.attr.primaryText, context) else
primaryTextColor(),
highlight = BrowserMenuHighlight.HighPriority(
endImageResource = R.drawable.ic_sync_disconnected,
backgroundTint = context.getColorFromAttr(R.attr.syncDisconnectedBackground),
canPropagate = false
),
isHighlighted = { hasAccountProblem }
) {
onItemTapped.invoke(ToolbarMenu.Item.Settings)
}
val desktopMode = BrowserMenuImageSwitch(
imageResource = R.drawable.ic_desktop,
label = context.getString(R.string.browser_menu_desktop_site),
initialState = {
selectedSession?.content?.desktopMode ?: false
}
) { checked ->
onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked))
}
val addToTopSites = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_add_to_top_sites),
imageResource = R.drawable.ic_top_sites,
iconTintColorResource = primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.AddToTopSites)
}
val addToHomescreen = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_add_to_homescreen),
imageResource = R.drawable.ic_add_to_homescreen,
iconTintColorResource = primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.AddToHomeScreen)
}
val syncedTabs = BrowserMenuImageText(
label = context.getString(R.string.synced_tabs),
imageResource = R.drawable.ic_synced_tabs,
iconTintColorResource = primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.SyncedTabs)
}
val installToHomescreen = BrowserMenuHighlightableItem(
label = context.getString(R.string.browser_menu_install_on_homescreen),
startImageResource = R.drawable.ic_add_to_homescreen,
iconTintColorResource = primaryTextColor(),
highlight = BrowserMenuHighlight.LowPriority(
label = context.getString(R.string.browser_menu_install_on_homescreen),
notificationTint = getColor(context, R.color.whats_new_notification_color)
),
isHighlighted = {
!context.settings().installPwaOpened
}
) {
onItemTapped.invoke(ToolbarMenu.Item.InstallToHomeScreen)
}
val findInPage = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_find_in_page),
imageResource = R.drawable.mozac_ic_search,
iconTintColorResource = primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.FindInPage)
}
val reportSiteIssuePlaceholder = WebExtensionPlaceholderMenuItem(
id = WebCompatReporterFeature.WEBCOMPAT_REPORTER_EXTENSION_ID
)
val saveToCollection = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_save_to_collection_2),
imageResource = R.drawable.ic_tab_collection,
iconTintColorResource = primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.SaveToCollection)
}
val deleteDataOnQuit = BrowserMenuImageText(
label = context.getString(R.string.delete_browsing_data_on_quit_action),
imageResource = R.drawable.ic_exit,
iconTintColorResource = primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.Quit)
}
val readerAppearance = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_read_appearance),
imageResource = R.drawable.ic_readermode_appearance,
iconTintColorResource = primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.ReaderModeAppearance)
}
val openInApp = BrowserMenuHighlightableItem(
label = context.getString(R.string.browser_menu_open_app_link),
startImageResource = R.drawable.ic_open_in_app,
iconTintColorResource = primaryTextColor(),
highlight = BrowserMenuHighlight.LowPriority(
label = context.getString(R.string.browser_menu_open_app_link),
notificationTint = getColor(context, R.color.whats_new_notification_color)
),
isHighlighted = { !context.settings().openInAppOpened }
) {
onItemTapped.invoke(ToolbarMenu.Item.OpenInApp)
}
val historyItem = BrowserMenuImageText(
context.getString(R.string.library_history),
R.drawable.ic_history,
primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.History)
}
val bookmarksItem = BrowserMenuImageText(
context.getString(R.string.library_bookmarks),
R.drawable.ic_bookmark_filled,
primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.Bookmarks)
}
val downloadsItem = BrowserMenuImageText(
context.getString(R.string.library_downloads),
R.drawable.ic_download,
primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.Downloads)
}
// Predicates that are called once, during screen init // Predicates that are called once, during screen init
val shouldShowSaveToCollection = (context.asActivity() as? HomeActivity) val shouldShowSaveToCollection = (context.asActivity() as? HomeActivity)
?.browsingModeManager?.mode == BrowsingMode.Normal ?.browsingModeManager?.mode == BrowsingMode.Normal
@ -216,151 +363,149 @@ class DefaultToolbarMenu(
} }
} }
private val settings = BrowserMenuHighlightableItem( private val newCoreMenuItems by lazy {
label = context.getString(R.string.browser_menu_settings), val newTabItem = BrowserMenuImageText(
startImageResource = R.drawable.ic_settings, context.getString(R.string.library_new_tab),
iconTintColorResource = if (hasAccountProblem) R.drawable.ic_bookmark_filled,
ThemeManager.resolveAttribute(R.attr.syncDisconnected, context) else disabledTextColor()
primaryTextColor(), ) {
textColorResource = if (hasAccountProblem) onItemTapped.invoke(ToolbarMenu.Item.NewTab)
ThemeManager.resolveAttribute(R.attr.primaryText, context) else }
primaryTextColor(),
highlight = BrowserMenuHighlight.HighPriority(
endImageResource = R.drawable.ic_sync_disconnected,
backgroundTint = context.getColorFromAttr(R.attr.syncDisconnectedBackground),
canPropagate = false
),
isHighlighted = { hasAccountProblem }
) {
onItemTapped.invoke(ToolbarMenu.Item.Settings)
}
private val desktopMode = BrowserMenuImageSwitch( val bookmarksItem = BrowserMenuImageText(
imageResource = R.drawable.ic_desktop, context.getString(R.string.library_bookmarks),
label = context.getString(R.string.browser_menu_desktop_site), R.drawable.ic_bookmark_filled,
initialState = { disabledTextColor()
selectedSession?.content?.desktopMode ?: false ) {
onItemTapped.invoke(ToolbarMenu.Item.Bookmarks)
} }
) { checked ->
onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked))
}
private val addToTopSites = BrowserMenuImageText( val historyItem = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_add_to_top_sites), context.getString(R.string.library_history),
imageResource = R.drawable.ic_top_sites, R.drawable.ic_history,
iconTintColorResource = primaryTextColor() disabledTextColor()
) { ) {
onItemTapped.invoke(ToolbarMenu.Item.AddToTopSites) onItemTapped.invoke(ToolbarMenu.Item.History)
} }
private val addToHomescreen = BrowserMenuImageText( val downloadsItem = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_add_to_homescreen), context.getString(R.string.library_downloads),
imageResource = R.drawable.ic_add_to_homescreen, R.drawable.ic_download,
iconTintColorResource = primaryTextColor() disabledTextColor()
) { ) {
onItemTapped.invoke(ToolbarMenu.Item.AddToHomeScreen) onItemTapped.invoke(ToolbarMenu.Item.Downloads)
} }
private val syncedTabs = BrowserMenuImageText( val extensionsItem = BrowserMenuImageText(
label = context.getString(R.string.synced_tabs), context.getString(R.string.browser_menu_extensions),
imageResource = R.drawable.ic_synced_tabs, R.drawable.ic_addons_extensions,
iconTintColorResource = primaryTextColor() disabledTextColor()
) { ) {
onItemTapped.invoke(ToolbarMenu.Item.SyncedTabs) onItemTapped.invoke(ToolbarMenu.Item.AddonsManager)
} }
private val installToHomescreen = BrowserMenuHighlightableItem( val syncedTabsItem = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_install_on_homescreen), context.getString(R.string.library_synced_tabs),
startImageResource = R.drawable.ic_add_to_homescreen, R.drawable.ic_synced_tabs,
iconTintColorResource = primaryTextColor(), disabledTextColor()
highlight = BrowserMenuHighlight.LowPriority( ) {
label = context.getString(R.string.browser_menu_install_on_homescreen), onItemTapped.invoke(ToolbarMenu.Item.SyncedTabs)
notificationTint = getColor(context, R.color.whats_new_notification_color)
),
isHighlighted = {
!context.settings().installPwaOpened
} }
) {
onItemTapped.invoke(ToolbarMenu.Item.InstallToHomeScreen)
}
private val findInPage = BrowserMenuImageText( val findInPageItem = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_find_in_page), label = context.getString(R.string.browser_menu_find_in_page),
imageResource = R.drawable.mozac_ic_search, imageResource = R.drawable.mozac_ic_search,
iconTintColorResource = primaryTextColor() iconTintColorResource = disabledTextColor()
) { ) {
onItemTapped.invoke(ToolbarMenu.Item.FindInPage) onItemTapped.invoke(ToolbarMenu.Item.FindInPage)
} }
private val reportSiteIssuePlaceholder = WebExtensionPlaceholderMenuItem( val desktopSiteItem = BrowserMenuImageSwitch(
id = WebCompatReporterFeature.WEBCOMPAT_REPORTER_EXTENSION_ID imageResource = R.drawable.ic_desktop,
) label = context.getString(R.string.browser_menu_desktop_site),
initialState = {
selectedSession?.content?.desktopMode ?: false
}
) { checked ->
onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked))
}
private val saveToCollection = BrowserMenuImageText( val addToHomeScreenItem = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_save_to_collection_2), label = context.getString(R.string.browser_menu_add_to_homescreen),
imageResource = R.drawable.ic_tab_collection, imageResource = R.drawable.ic_add_to_homescreen,
iconTintColorResource = primaryTextColor() iconTintColorResource = disabledTextColor()
) { ) {
onItemTapped.invoke(ToolbarMenu.Item.SaveToCollection) onItemTapped.invoke(ToolbarMenu.Item.AddToHomeScreen)
} }
private val deleteDataOnQuit = BrowserMenuImageText( val addToTopSitesItem = BrowserMenuImageText(
label = context.getString(R.string.delete_browsing_data_on_quit_action), label = context.getString(R.string.browser_menu_add_to_top_sites),
imageResource = R.drawable.ic_exit, imageResource = R.drawable.ic_top_sites,
iconTintColorResource = primaryTextColor() iconTintColorResource = disabledTextColor()
) { ) {
onItemTapped.invoke(ToolbarMenu.Item.Quit) onItemTapped.invoke(ToolbarMenu.Item.AddToTopSites)
} }
private val readerAppearance = BrowserMenuImageText( val saveToCollectionItem = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_read_appearance), label = context.getString(R.string.browser_menu_save_to_collection_2),
imageResource = R.drawable.ic_readermode_appearance, imageResource = R.drawable.ic_tab_collection,
iconTintColorResource = primaryTextColor() iconTintColorResource = disabledTextColor()
) { ) {
onItemTapped.invoke(ToolbarMenu.Item.ReaderModeAppearance) onItemTapped.invoke(ToolbarMenu.Item.SaveToCollection)
} }
private val openInApp = BrowserMenuHighlightableItem( val settingsItem = BrowserMenuHighlightableItem(
label = context.getString(R.string.browser_menu_open_app_link), label = context.getString(R.string.browser_menu_settings),
startImageResource = R.drawable.ic_open_in_app, startImageResource = R.drawable.ic_settings,
iconTintColorResource = primaryTextColor(), iconTintColorResource = disabledTextColor(),
highlight = BrowserMenuHighlight.LowPriority( textColorResource = if (hasAccountProblem)
label = context.getString(R.string.browser_menu_open_app_link), ThemeManager.resolveAttribute(R.attr.primaryText, context) else
notificationTint = getColor(context, R.color.whats_new_notification_color) primaryTextColor(),
), highlight = BrowserMenuHighlight.HighPriority(
isHighlighted = { !context.settings().openInAppOpened } endImageResource = R.drawable.ic_sync_disconnected,
) { backgroundTint = context.getColorFromAttr(R.attr.syncDisconnectedBackground),
onItemTapped.invoke(ToolbarMenu.Item.OpenInApp) canPropagate = false
} ),
isHighlighted = { hasAccountProblem }
) {
onItemTapped.invoke(ToolbarMenu.Item.Settings)
}
val historyItem = BrowserMenuImageText( val syncedTabsInTabsTray = context.components.settings
context.getString(R.string.library_history), .syncedTabsInTabsTray
R.drawable.ic_history,
primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.History)
}
val bookmarksItem = BrowserMenuImageText( val menuItems = listOfNotNull(
context.getString(R.string.library_bookmarks), newTabItem,
R.drawable.ic_bookmark_filled, BrowserMenuDivider(),
primaryTextColor() bookmarksItem,
) { historyItem,
onItemTapped.invoke(ToolbarMenu.Item.Bookmarks) downloadsItem,
} extensionsItem,
if (syncedTabsInTabsTray) null else syncedTabsItem,
BrowserMenuDivider(),
findInPageItem,
desktopSiteItem,
BrowserMenuDivider(),
addToHomeScreenItem.apply { visible = ::canAddToHomescreen },
addToTopSitesItem,
saveToCollectionItem,
BrowserMenuDivider(),
settingsItem,
BrowserMenuDivider(),
menuToolbar
)
val downloadsItem = BrowserMenuImageText( menuItems
context.getString(R.string.library_downloads),
R.drawable.ic_download,
primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.Downloads)
} }
@ColorRes @ColorRes
@VisibleForTesting @VisibleForTesting
internal fun primaryTextColor() = ThemeManager.resolveAttribute(R.attr.primaryText, context) internal fun primaryTextColor() = ThemeManager.resolveAttribute(R.attr.primaryText, context)
@ColorRes
@VisibleForTesting
internal fun disabledTextColor() = R.color.toolbar_menu_transparent
@VisibleForTesting @VisibleForTesting
internal fun registerForIsBookmarkedUpdates() { internal fun registerForIsBookmarkedUpdates() {
store.flowScoped(lifecycleOwner) { flow -> store.flowScoped(lifecycleOwner) { flow ->

@ -31,6 +31,7 @@ interface ToolbarMenu {
object Bookmarks : Item() object Bookmarks : Item()
object History : Item() object History : Item()
object Downloads : Item() object Downloads : Item()
object NewTab : Item()
} }
val menuBuilder: BrowserMenuBuilder val menuBuilder: BrowserMenuBuilder

@ -422,4 +422,7 @@
<!-- App Spinners colors --> <!-- App Spinners colors -->
<color name="spinner_selected_item">#1415141A</color> <color name="spinner_selected_item">#1415141A</color>
<!-- Toolbar menu icon colors -->
<color name="toolbar_menu_transparent">@android:color/transparent</color>
</resources> </resources>

@ -128,6 +128,8 @@
<string name="browser_menu_edit_bookmark">Edit bookmark</string> <string name="browser_menu_edit_bookmark">Edit bookmark</string>
<!-- Browser menu button that opens the addon manager --> <!-- Browser menu button that opens the addon manager -->
<string name="browser_menu_add_ons">Add-ons</string> <string name="browser_menu_add_ons">Add-ons</string>
<!-- Browser menu button that opens the addon extensions manager -->
<string name="browser_menu_extensions">Extensions</string>
<!-- Text displayed when there are no add-ons to be shown --> <!-- Text displayed when there are no add-ons to be shown -->
<string name="no_add_ons">No add-ons here</string> <string name="no_add_ons">No add-ons here</string>
<!-- Browser menu button that sends a user to help articles --> <!-- Browser menu button that sends a user to help articles -->
@ -517,6 +519,10 @@
<string name="library_desktop_bookmarks_unfiled">Other Bookmarks</string> <string name="library_desktop_bookmarks_unfiled">Other Bookmarks</string>
<!-- Option in Library to open History page --> <!-- Option in Library to open History page -->
<string name="library_history">History</string> <string name="library_history">History</string>
<!-- Option in Library to open a new tab -->
<string name="library_new_tab">New tab</string>
<!-- Option in Library to find text in page -->
<string name="library_find_in_page">Find in page</string>
<!-- Option in Library to open Synced Tabs page --> <!-- Option in Library to open Synced Tabs page -->
<string name="library_synced_tabs">Synced tabs</string> <string name="library_synced_tabs">Synced tabs</string>
<!-- Option in Library to open Reading List --> <!-- Option in Library to open Reading List -->

@ -45,6 +45,7 @@ import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -64,6 +65,7 @@ import org.mozilla.fenix.utils.Settings
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
@Suppress("ForbiddenComment")
class DefaultBrowserToolbarMenuControllerTest { class DefaultBrowserToolbarMenuControllerTest {
@get:Rule @get:Rule
@ -124,8 +126,121 @@ class DefaultBrowserToolbarMenuControllerTest {
unmockkObject(FenixSnackbar.Companion) unmockkObject(FenixSnackbar.Companion)
} }
// TODO: These can be removed for https://github.com/mozilla-mobile/fenix/issues/17870
// todo === Start ===
@Test @Test
fun handleToolbarBackPress() = runBlockingTest { fun handleToolbarBookmarkPressWithReaderModeInactive() = runBlockingTest {
if (!FeatureFlags.toolbarMenuFeature) {
val item = ToolbarMenu.Item.Bookmark
val title = "Mozilla"
val readerUrl = "moz-extension://1234"
val readerTab = createTab(
url = readerUrl,
readerState = ReaderState(active = false, activeUrl = "https://1234.org"),
title = title
)
browserStore =
BrowserStore(BrowserState(tabs = listOf(readerTab), selectedTabId = readerTab.id))
every { currentSession.id } returns readerTab.id
every { currentSession.title } returns title
every { currentSession.url } returns "https://mozilla.org"
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARK)) }
verify { bookmarkTapped("https://mozilla.org", title) }
}
}
@Test
fun `IF reader mode is active WHEN bookmark menu item is pressed THEN menu item is handled`() = runBlockingTest {
if (!FeatureFlags.toolbarMenuFeature) {
val item = ToolbarMenu.Item.Bookmark
val title = "Mozilla"
val readerUrl = "moz-extension://1234"
val readerTab = createTab(
url = readerUrl,
readerState = ReaderState(active = true, activeUrl = "https://mozilla.org"),
title = title
)
browserStore =
BrowserStore(BrowserState(tabs = listOf(readerTab), selectedTabId = readerTab.id))
every { currentSession.id } returns readerTab.id
every { currentSession.title } returns title
every { currentSession.url } returns readerUrl
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARK)) }
verify { bookmarkTapped("https://mozilla.org", title) }
}
}
@Test
fun `WHEN open in Fenix menu item is pressed THEN menu item is handled correctly`() = runBlockingTest {
if (!FeatureFlags.toolbarMenuFeature) {
val controller = createController(scope = this, customTabSession = currentSession)
val item = ToolbarMenu.Item.OpenInFenix
every { currentSession.customTabConfig } returns mockk()
every { activity.startActivity(any()) } just Runs
controller.handleToolbarItemInteraction(item)
verify { sessionFeature.release() }
verify { currentSession.customTabConfig = null }
verify { sessionManager.select(currentSession) }
verify { activity.startActivity(openInFenixIntent) }
verify { activity.finishAndRemoveTask() }
}
}
@Test
fun `WHEN quit menu item is pressed THEN menu item is handled correctly`() = runBlockingTest {
if (!FeatureFlags.toolbarMenuFeature) {
val item = ToolbarMenu.Item.Quit
val testScope = this
val controller = createController(scope = testScope)
controller.handleToolbarItemInteraction(item)
verify { deleteAndQuit(activity, testScope, null) }
}
}
@Test
fun handleToolbarOpenInAppPress() = runBlockingTest {
if (!FeatureFlags.toolbarMenuFeature) {
val item = ToolbarMenu.Item.OpenInApp
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { settings.openInAppOpened = true }
}
}
@Test
fun `WHEN reader mode menu item is pressed THEN handle appearance change`() = runBlockingTest {
val item = ToolbarMenu.Item.ReaderModeAppearance
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { readerModeController.showControls() }
verify { metrics.track(Event.ReaderModeAppearanceOpened) }
}
// todo === End ===
@Test
fun `WHEN backwards nav menu item is pressed THEN the session navigates back with active session`() = runBlockingTest {
val item = ToolbarMenu.Item.Back(false) val item = ToolbarMenu.Item.Back(false)
val controller = createController(scope = this) val controller = createController(scope = this)
@ -136,7 +251,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarBackLongPress() = runBlockingTest { fun `WHEN backwards nav menu item is long pressed THEN the session navigates back with no active session`() = runBlockingTest {
val item = ToolbarMenu.Item.Back(true) val item = ToolbarMenu.Item.Back(true)
val controller = createController(scope = this) val controller = createController(scope = this)
@ -149,7 +264,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarForwardPress() = runBlockingTest { fun `WHEN forward nav menu item is pressed THEN the session navigates forward to active session`() = runBlockingTest {
val item = ToolbarMenu.Item.Forward(false) val item = ToolbarMenu.Item.Forward(false)
val controller = createController(scope = this) val controller = createController(scope = this)
@ -160,7 +275,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarForwardLongPress() = runBlockingTest { fun `WHEN forward nav menu item is long pressed THEN the browser navigates forward with no active session`() = runBlockingTest {
val item = ToolbarMenu.Item.Forward(true) val item = ToolbarMenu.Item.Forward(true)
val controller = createController(scope = this) val controller = createController(scope = this)
@ -173,7 +288,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarReloadPress() = runBlockingTest { fun `WHEN reload nav menu item is pressed THEN the session reloads from cache`() = runBlockingTest {
val item = ToolbarMenu.Item.Reload(false) val item = ToolbarMenu.Item.Reload(false)
val controller = createController(scope = this) val controller = createController(scope = this)
@ -184,7 +299,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarReloadLongPress() = runBlockingTest { fun `WHEN reload nav menu item is long pressed THEN the session reloads with no cache`() = runBlockingTest {
val item = ToolbarMenu.Item.Reload(true) val item = ToolbarMenu.Item.Reload(true)
val controller = createController(scope = this) val controller = createController(scope = this)
@ -200,7 +315,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarStopPress() = runBlockingTest { fun `WHEN stop nav menu item is pressed THEN the session stops loading`() = runBlockingTest {
val item = ToolbarMenu.Item.Stop val item = ToolbarMenu.Item.Stop
val controller = createController(scope = this) val controller = createController(scope = this)
@ -211,7 +326,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarSettingsPress() = runBlockingTest { fun `WHEN settings menu item is pressed THEN menu item is handled`() = runBlockingTest {
val item = ToolbarMenu.Item.Settings val item = ToolbarMenu.Item.Settings
val controller = createController(scope = this) val controller = createController(scope = this)
@ -224,52 +339,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarBookmarkPressWithReaderModeInactive() = runBlockingTest { fun `WHEN bookmark menu item is pressed THEN navigate to bookmarks page`() = runBlockingTest {
val item = ToolbarMenu.Item.Bookmark
val title = "Mozilla"
val readerUrl = "moz-extension://1234"
val readerTab = createTab(
url = readerUrl,
readerState = ReaderState(active = false, activeUrl = "https://1234.org"),
title = title
)
browserStore =
BrowserStore(BrowserState(tabs = listOf(readerTab), selectedTabId = readerTab.id))
every { currentSession.id } returns readerTab.id
every { currentSession.title } returns title
every { currentSession.url } returns "https://mozilla.org"
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARK)) }
verify { bookmarkTapped("https://mozilla.org", title) }
}
@Test
fun handleToolbarBookmarkPressWithReaderModeActive() = runBlockingTest {
val item = ToolbarMenu.Item.Bookmark
val title = "Mozilla"
val readerUrl = "moz-extension://1234"
val readerTab = createTab(
url = readerUrl,
readerState = ReaderState(active = true, activeUrl = "https://mozilla.org"),
title = title
)
browserStore = BrowserStore(BrowserState(tabs = listOf(readerTab), selectedTabId = readerTab.id))
every { currentSession.id } returns readerTab.id
every { currentSession.title } returns title
every { currentSession.url } returns readerUrl
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARK)) }
verify { bookmarkTapped("https://mozilla.org", title) }
}
@Test
fun handleToolbarBookmarksPress() = runBlockingTest {
val item = ToolbarMenu.Item.Bookmarks val item = ToolbarMenu.Item.Bookmarks
val controller = createController(scope = this) val controller = createController(scope = this)
@ -282,7 +352,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarHistoryPress() = runBlockingTest { fun `WHEN history menu item is pressed THEN navigate to history page`() = runBlockingTest {
val item = ToolbarMenu.Item.History val item = ToolbarMenu.Item.History
val controller = createController(scope = this) val controller = createController(scope = this)
@ -295,7 +365,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarRequestDesktopOnPress() = runBlockingTest { fun `WHEN request desktop menu item is toggled On THEN desktop site is requested for the session`() = runBlockingTest {
val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase =
mockk(relaxed = true) mockk(relaxed = true)
val item = ToolbarMenu.Item.RequestDesktop(true) val item = ToolbarMenu.Item.RequestDesktop(true)
@ -315,7 +385,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarRequestDesktopOffPress() = runBlockingTest { fun `WHEN request desktop menu item is toggled Off THEN mobile site is requested for the session`() = runBlockingTest {
val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase =
mockk(relaxed = true) mockk(relaxed = true)
val item = ToolbarMenu.Item.RequestDesktop(false) val item = ToolbarMenu.Item.RequestDesktop(false)
@ -335,7 +405,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarAddToTopSitesPressed() = runBlockingTest { fun `WHEN Add To Top Sites menu item is pressed THEN add site AND show snackbar`() = runBlockingTest {
val item = ToolbarMenu.Item.AddToTopSites val item = ToolbarMenu.Item.AddToTopSites
val addPinnedSiteUseCase: TopSitesUseCases.AddPinnedSiteUseCase = mockk(relaxed = true) val addPinnedSiteUseCase: TopSitesUseCases.AddPinnedSiteUseCase = mockk(relaxed = true)
@ -353,7 +423,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarAddonsManagerPress() = runBlockingTest { fun `WHEN addon extensions menu item is pressed THEN navigate to addons manager`() = runBlockingTest {
val item = ToolbarMenu.Item.AddonsManager val item = ToolbarMenu.Item.AddonsManager
val controller = createController(scope = this) val controller = createController(scope = this)
@ -363,7 +433,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarAddToHomeScreenPress() = runBlockingTest { fun `WHEN Add To Home Screen menu item is pressed THEN add site`() = runBlockingTest {
val item = ToolbarMenu.Item.AddToHomeScreen val item = ToolbarMenu.Item.AddToHomeScreen
val controller = createController(scope = this) val controller = createController(scope = this)
@ -373,7 +443,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarSharePressWithReaderModeInactive() = runBlockingTest { fun `IF reader mode is inactive WHEN share menu item is pressed THEN navigate to share screen`() = runBlockingTest {
val item = ToolbarMenu.Item.Share val item = ToolbarMenu.Item.Share
val title = "Mozilla" val title = "Mozilla"
val readerUrl = "moz-extension://1234" val readerUrl = "moz-extension://1234"
@ -404,7 +474,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarSharePressWithReaderModeActive() = runBlockingTest { fun `IF reader mode is active WHEN share menu item is pressed THEN navigate to share screen`() = runBlockingTest {
val item = ToolbarMenu.Item.Share val item = ToolbarMenu.Item.Share
val title = "Mozilla" val title = "Mozilla"
val readerUrl = "moz-extension://1234" val readerUrl = "moz-extension://1234"
@ -435,7 +505,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarFindInPagePress() = runBlockingTest { fun `WHEN Find In Page menu item is pressed THEN launch finder`() = runBlockingTest {
val item = ToolbarMenu.Item.FindInPage val item = ToolbarMenu.Item.FindInPage
val controller = createController(scope = this) val controller = createController(scope = this)
@ -446,7 +516,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarSaveToCollectionPressWhenAtLeastOneCollectionExists() = runBlockingTest { fun `IF one or more collection exists WHEN Save To Collection menu item is pressed THEN navigate to save collection page`() = runBlockingTest {
val item = ToolbarMenu.Item.SaveToCollection val item = ToolbarMenu.Item.SaveToCollection
val cachedTabCollections: List<TabCollection> = mockk(relaxed = true) val cachedTabCollections: List<TabCollection> = mockk(relaxed = true)
every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollections every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollections
@ -474,7 +544,7 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarSaveToCollectionPressWhenNoCollectionsExists() = runBlockingTest { fun `IF no collection exists WHEN Save To Collection menu item is pressed THEN navigate to create collection page`() = runBlockingTest {
val item = ToolbarMenu.Item.SaveToCollection val item = ToolbarMenu.Item.SaveToCollection
val cachedTabCollectionsEmpty: List<TabCollection> = emptyList() val cachedTabCollectionsEmpty: List<TabCollection> = emptyList()
every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollectionsEmpty every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollectionsEmpty
@ -499,56 +569,22 @@ class DefaultBrowserToolbarMenuControllerTest {
} }
@Test @Test
fun handleToolbarOpenInFenixPress() = runBlockingTest { fun `WHEN New Tab menu item is pressed THEN navigate to a new tab home`() = runBlockingTest {
val controller = createController(scope = this, customTabSession = currentSession) val item = ToolbarMenu.Item.NewTab
val item = ToolbarMenu.Item.OpenInFenix
every { currentSession.customTabConfig } returns mockk()
every { activity.startActivity(any()) } just Runs
controller.handleToolbarItemInteraction(item)
verify { sessionFeature.release() }
verify { currentSession.customTabConfig = null }
verify { sessionManager.select(currentSession) }
verify { activity.startActivity(openInFenixIntent) }
verify { activity.finishAndRemoveTask() }
}
@Test
fun handleToolbarQuitPress() = runBlockingTest {
val item = ToolbarMenu.Item.Quit
val testScope = this
val controller = createController(scope = testScope)
controller.handleToolbarItemInteraction(item)
verify { deleteAndQuit(activity, testScope, null) }
}
@Test
fun handleToolbarReaderModeAppearancePress() = runBlockingTest {
val item = ToolbarMenu.Item.ReaderModeAppearance
val controller = createController(scope = this) val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item) controller.handleToolbarItemInteraction(item)
verify { readerModeController.showControls() } verify {
verify { metrics.track(Event.ReaderModeAppearanceOpened) } navController.navigate(
} directionsEq(
NavGraphDirections.actionGlobalHome(
@Test focusOnAddressBar = true
fun handleToolbarOpenInAppPress() = runBlockingTest { )
val item = ToolbarMenu.Item.OpenInApp )
)
val controller = createController(scope = this) }
controller.handleToolbarItemInteraction(item)
verify { settings.openInAppOpened = true }
} }
private fun createController( private fun createController(

Loading…
Cancel
Save