From d0fd3e82c51f63532b89ecdd7fe5d373609108fc Mon Sep 17 00:00:00 2001 From: Elise Richards Date: Thu, 11 Feb 2021 18:55:12 -0600 Subject: [PATCH] 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 --- .../org/mozilla/fenix/ui/ReaderViewTest.kt | 71 ++-- .../mozilla/fenix/ui/SettingsAddonsTest.kt | 30 +- .../java/org/mozilla/fenix/ui/SmokeTest.kt | 8 + .../StrictEnhancedTrackingProtectionTest.kt | 2 + .../mozilla/fenix/ui/ThreeDotMenuMainTest.kt | 93 +++-- .../fenix/ui/robots/ThreeDotMenuMainRobot.kt | 46 +- .../java/org/mozilla/fenix/FeatureFlags.kt | 5 + .../toolbar/BrowserToolbarMenuController.kt | 222 +++++----- .../components/toolbar/DefaultToolbarMenu.kt | 395 ++++++++++++------ .../fenix/components/toolbar/ToolbarMenu.kt | 1 + app/src/main/res/values/colors.xml | 3 + app/src/main/res/values/strings.xml | 6 + ...DefaultBrowserToolbarMenuControllerTest.kt | 256 +++++++----- 13 files changed, 709 insertions(+), 429 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt index d405215abd..980c05aeea 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt @@ -12,6 +12,8 @@ import org.junit.Rule import org.junit.Test import org.mozilla.fenix.ui.robots.navigationToolbar import androidx.test.espresso.IdlingRegistry +import org.junit.Ignore +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R import org.mozilla.fenix.helpers.AndroidAssetDispatcher 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 { private lateinit var mockWebServer: MockWebServer private var readerViewNotification: ViewVisibilityIdlingResource? = null @@ -103,39 +105,42 @@ class ReaderViewTest { @Test fun verifyReaderViewToggle() { - val readerViewPage = - TestAssetHelper.getLoremIpsumAsset(mockWebServer) - - navigationToolbar { - }.enterURLAndEnterToBrowser(readerViewPage.url) { - mDevice.waitForIdle() + // New three-dot menu design does not have readerview + if (!FeatureFlags.toolbarMenuFeature) { + val readerViewPage = + TestAssetHelper.getLoremIpsumAsset(mockWebServer) + + 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 diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt index 79057f88cf..9bd09b80b9 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt @@ -79,19 +79,23 @@ class SettingsAddonsTest { val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val addonName = "uBlock Origin" - navigationToolbar { - }.openNewTabAndEnterToBrowser(defaultWebPage.url) { - }.openThreeDotMenu { - }.openAddonsManagerMenu { - addonsListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.add_ons_list), 1) - IdlingRegistry.getInstance().register(addonsListIdlingResource!!) - clickInstallAddon(addonName) - verifyAddonPrompt(addonName) - cancelInstallAddon() - clickInstallAddon(addonName) - acceptInstallAddon() - verifyDownloadAddonPrompt(addonName, activityTestRule) + navigationToolbar {} + .openNewTabAndEnterToBrowser(defaultWebPage.url) {} + .openThreeDotMenu {} + .openAddonsManagerMenu { + addonsListIdlingResource = + RecyclerViewIdlingResource( + activityTestRule.activity.findViewById(R.id.add_ons_list), + 1 + ) + IdlingRegistry.getInstance().register(addonsListIdlingResource!!) + clickInstallAddon(addonName) + verifyAddonPrompt(addonName) + cancelInstallAddon() + clickInstallAddon(addonName) + acceptInstallAddon() + + verifyDownloadAddonPrompt(addonName, activityTestRule) } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt index a97a7d4c46..18584a0b66 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt @@ -14,6 +14,7 @@ import androidx.test.uiautomator.UiDevice import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R @@ -198,6 +199,7 @@ class SmokeTest { @Test // 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() { val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) @@ -231,6 +233,7 @@ class SmokeTest { } @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 fun openMainMenuSyncedTabsItemTest() { homeScreen { @@ -362,6 +365,7 @@ class SmokeTest { @Test // 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() { val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) @@ -537,6 +541,7 @@ class SmokeTest { @Test // 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() { val saveLoginTest = TestAssetHelper.getSaveLoginAsset(mockWebServer) @@ -600,6 +605,7 @@ class SmokeTest { } @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 fun noCrashWithAddonInstalledTest() { // setting ETP to Strict mode to test it works with add-ons @@ -1107,6 +1113,7 @@ class SmokeTest { } @Test + @Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17799") fun mainMenuInstallPWATest() { val pwaPage = "https://rpappalax.github.io/testapp/" @@ -1123,6 +1130,7 @@ class SmokeTest { } @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 fun verifyReaderViewAppearanceUI() { val readerViewPage = diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/StrictEnhancedTrackingProtectionTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/StrictEnhancedTrackingProtectionTest.kt index 2107f9d102..c3583b70db 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/StrictEnhancedTrackingProtectionTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/StrictEnhancedTrackingProtectionTest.kt @@ -8,6 +8,7 @@ import androidx.test.platform.app.InstrumentationRegistry import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.ext.settings @@ -126,6 +127,7 @@ class StrictEnhancedTrackingProtectionTest { } @Test + @Ignore("To be re-implemented with the three dot menu changes https://github.com/mozilla-mobile/fenix/issues/17870") fun testStrictVisitDisable() { val trackingProtectionTest = TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt index 088136fcd5..86645af120 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt @@ -12,6 +12,7 @@ import org.junit.Before import org.junit.BeforeClass import org.junit.Rule import org.junit.Test +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.ui.robots.homeScreen @@ -54,42 +55,68 @@ class ThreeDotMenuMainTest { @Test fun threeDotMenuItemsTest() { - homeScreen { - }.openThreeDotMenu { - verifySettingsButton() - verifyBookmarksButton() - verifyHistoryButton() - verifyHelpButton() - verifyWhatsNewButton() - }.openSettings { - verifySettingsView() - }.goBack { - }.openThreeDotMenu { - }.openHelp { - verifyHelpUrl() - }.openTabDrawer { - closeTab() - } + if (FeatureFlags.toolbarMenuFeature) { + homeScreen { + }.openThreeDotMenu { + }.openHistory { + verifyHistoryMenuView() + }.goBackToBrowser {} - homeScreen { - }.openThreeDotMenu { - }.openWhatsNew { - verifyWhatsNewURL() - }.openTabDrawer { - closeTab() - } + homeScreen { + }.openThreeDotMenu { + }.openBookmarks { + verifyBookmarksMenuView() + }.closeMenu {} - homeScreen { - }.openThreeDotMenu { - }.openBookmarks { - verifyBookmarksMenuView() - }.closeMenu { - } + homeScreen { + }.openThreeDotMenu { + verifySettingsButton() + verifyBookmarksButton() + verifyHistoryButton() + }.openSettings { + verifySettingsView() + }.goBack { + }.openThreeDotMenu { + }.goBack {} + } else { + homeScreen { + }.openThreeDotMenu { + verifySettingsButton() + verifyBookmarksButton() + verifyHistoryButton() + verifyHelpButton() + verifyWhatsNewButton() + }.openSettings { + verifySettingsView() + }.goBack { + }.openThreeDotMenu { + }.openHelp { + verifyHelpUrl() + }.openTabDrawer { + closeTab() + } - homeScreen { - }.openThreeDotMenu { - }.openHistory { - verifyHistoryMenuView() + homeScreen { + }.openThreeDotMenu { + }.openWhatsNew { + verifyWhatsNewURL() + }.openTabDrawer { + closeTab() + } + + homeScreen { + }.openThreeDotMenu { + }.openBookmarks { + verifyBookmarksMenuView() + }.closeMenu { + } + + homeScreen { + }.openThreeDotMenu { + }.openHistory { + verifyHistoryMenuView() + } } + } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt index da61cb3808..cc2304f332 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt @@ -38,6 +38,7 @@ import androidx.test.uiautomator.Until import org.hamcrest.Matcher import org.hamcrest.Matchers.allOf import org.junit.Assert.assertTrue +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.click @@ -124,21 +125,33 @@ class ThreeDotMenuMainRobot { fun verifyShareTabsOverlay() = assertShareTabsOverlay() fun verifyThreeDotMainMenuItems() { - verifyAddOnsButton() - verifyDownloadsButton() - verifyHistoryButton() - verifyBookmarksButton() - verifySyncedTabsButton() - verifySettingsButton() - verifyFindInPageButton() - verifyAddFirefoxHome() - verifyAddToMobileHome() - verifyDesktopSite() - verifySaveCollection() - verifyAddBookmarkButton() - verifyShareButton() - verifyForwardButton() - verifyRefreshButton() + if (FeatureFlags.toolbarMenuFeature) { + verifyDownloadsButton() + verifyHistoryButton() + verifyBookmarksButton() + verifySettingsButton() + verifyDesktopSite() + verifySaveCollection() + verifyShareButton() + verifyForwardButton() + verifyRefreshButton() + } else { + verifyAddOnsButton() + verifyDownloadsButton() + verifyHistoryButton() + verifyBookmarksButton() + verifySyncedTabsButton() + verifySettingsButton() + verifyFindInPageButton() + verifyAddFirefoxHome() + verifyAddToMobileHome() + verifyDesktopSite() + verifySaveCollection() + verifyAddBookmarkButton() + verifyShareButton() + verifyForwardButton() + verifyRefreshButton() + } } private fun assertShareTabsOverlay() { @@ -390,7 +403,8 @@ private fun assertSettingsButton() = settingsButton() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .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() { onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown()) addOnsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index 1092fff0e5..fd8be54a21 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -30,4 +30,9 @@ object FeatureFlags { * Enables WebAuthn support. */ val webAuthFeature = Config.channel.isNightlyOrDebug + + /** + * Shows new three-dot toolbar menu design. + */ + val toolbarMenuFeature = Config.channel.isDebug } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt index fae8e867ad..46dcdb1e60 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt @@ -51,7 +51,7 @@ interface BrowserToolbarMenuController { fun handleToolbarItemInteraction(item: ToolbarMenu.Item) } -@Suppress("LargeClass") +@Suppress("LargeClass", "ForbiddenComment") class DefaultBrowserToolbarMenuController( private val activity: HomeActivity, private val navController: NavController, @@ -87,6 +87,76 @@ class DefaultBrowserToolbarMenuController( trackToolbarItemInteraction(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 -> { if (item.viewHistory) { navController.navigate( @@ -118,12 +188,24 @@ class DefaultBrowserToolbarMenuController( sessionUseCases.reload.invoke(currentSession, flags = flags) } - ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession) - ToolbarMenu.Item.Settings -> browserAnimator.captureEngineViewAndDrawStatically { + is ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession) + 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() navController.nav(R.id.browserFragment, directions) } - ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically { + is ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically { navController.nav( R.id.browserFragment, BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment() @@ -133,7 +215,7 @@ class DefaultBrowserToolbarMenuController( item.isChecked, currentSession ) - ToolbarMenu.Item.AddToTopSites -> { + is ToolbarMenu.Item.AddToTopSites -> { scope.launch { val context = swipeRefresh.context val numPinnedSites = @@ -169,7 +251,7 @@ class DefaultBrowserToolbarMenuController( } } } - ToolbarMenu.Item.AddToHomeScreen, ToolbarMenu.Item.InstallToHomeScreen -> { + is ToolbarMenu.Item.AddToHomeScreen -> { settings.installPwaOpened = true MainScope().launch { with(activity.components.useCases.webAppUseCases) { @@ -183,31 +265,17 @@ class DefaultBrowserToolbarMenuController( } } } - ToolbarMenu.Item.Share -> { - val directions = NavGraphDirections.actionGlobalShareFragment( - data = arrayOf( - ShareData( - url = getProperUrl(currentSession), - title = currentSession?.title - ) - ), - showPage = true - ) - navController.navigate(directions) - } - - ToolbarMenu.Item.FindInPage -> { + is ToolbarMenu.Item.FindInPage -> { findInPageLauncher() metrics.track(Event.FindInPageOpened) } - - ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically { + is ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically { navController.nav( R.id.browserFragment, BrowserFragmentDirections.actionGlobalAddonsManagementFragment() ) } - ToolbarMenu.Item.SaveToCollection -> { + is ToolbarMenu.Item.SaveToCollection -> { metrics .track(Event.CollectionSaveButtonPressed(TELEMETRY_BROWSER_IDENTIFIER)) @@ -225,82 +293,35 @@ class DefaultBrowserToolbarMenuController( navController.nav(R.id.browserFragment, directions) } } - 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() - } - 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 -> { + is ToolbarMenu.Item.Bookmark -> { sessionManager.selectedSession?.let { getProperUrl(it)?.let { url -> bookmarkTapped(url, it.title) } } } - ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically { + is ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically { navController.nav( R.id.browserFragment, BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id) ) } - ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically { + is ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically { navController.nav( R.id.browserFragment, BrowserFragmentDirections.actionGlobalHistoryFragment() ) } - ToolbarMenu.Item.Downloads -> browserAnimator.captureEngineViewAndDrawStatically { + is ToolbarMenu.Item.Downloads -> browserAnimator.captureEngineViewAndDrawStatically { navController.nav( R.id.browserFragment, BrowserFragmentDirections.actionGlobalDownloadsFragment() ) } + is ToolbarMenu.Item.NewTab -> { + navController.navigate( + BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true) + ) + } } } @@ -318,35 +339,38 @@ class DefaultBrowserToolbarMenuController( @Suppress("ComplexMethod") private fun trackToolbarItemInteraction(item: ToolbarMenu.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.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD is ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD - ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP - ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS + is ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP + is ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE + is ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS is ToolbarMenu.Item.RequestDesktop -> if (item.isChecked) { Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON } else { Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF } - - ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE - ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX - ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE - ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION - ToolbarMenu.Item.AddToTopSites -> Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES - ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN - ToolbarMenu.Item.SyncedTabs -> Event.BrowserMenuItemTapped.Item.SYNC_TABS - ToolbarMenu.Item.InstallToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN - ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT - ToolbarMenu.Item.ReaderModeAppearance -> - 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 + is ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE + is ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION + is ToolbarMenu.Item.AddToTopSites -> Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES + is ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN + is ToolbarMenu.Item.SyncedTabs -> Event.BrowserMenuItemTapped.Item.SYNC_TABS + is ToolbarMenu.Item.Bookmark -> Event.BrowserMenuItemTapped.Item.BOOKMARK + is ToolbarMenu.Item.AddonsManager -> Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER + is ToolbarMenu.Item.Bookmarks -> Event.BrowserMenuItemTapped.Item.BOOKMARKS + is ToolbarMenu.Item.History -> Event.BrowserMenuItemTapped.Item.HISTORY + is ToolbarMenu.Item.Downloads -> Event.BrowserMenuItemTapped.Item.DOWNLOADS + is ToolbarMenu.Item.NewTab -> Event.BrowserMenuItemTapped.Item.NEW_TAB } metrics.track(Event.BrowserMenuItemTapped(eventItem)) diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt index 0fc1a35445..ec34aa13b4 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt @@ -32,6 +32,7 @@ import mozilla.components.feature.webcompat.reporter.WebCompatReporterFeature import mozilla.components.lib.state.ext.flowScoped import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode @@ -69,7 +70,12 @@ class DefaultToolbarMenu( override val menuBuilder by lazy { WebExtensionBrowserMenuBuilder( - menuItems, + items = + if (FeatureFlags.toolbarMenuFeature) { + newCoreMenuItems + } else { + oldCoreMenuItems + }, endOfMenuAlwaysVisible = !shouldReverseItems, store = store, webExtIconTintColorResource = primaryTextColor(), @@ -179,7 +185,148 @@ class DefaultToolbarMenu( } ?: false // 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 val shouldShowSaveToCollection = (context.asActivity() as? HomeActivity) ?.browsingModeManager?.mode == BrowsingMode.Normal @@ -216,151 +363,149 @@ class DefaultToolbarMenu( } } - private 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) - } + private val newCoreMenuItems by lazy { + val newTabItem = BrowserMenuImageText( + context.getString(R.string.library_new_tab), + R.drawable.ic_bookmark_filled, + disabledTextColor() + ) { + onItemTapped.invoke(ToolbarMenu.Item.NewTab) + } - private val desktopMode = BrowserMenuImageSwitch( - imageResource = R.drawable.ic_desktop, - label = context.getString(R.string.browser_menu_desktop_site), - initialState = { - selectedSession?.content?.desktopMode ?: false + val bookmarksItem = BrowserMenuImageText( + context.getString(R.string.library_bookmarks), + R.drawable.ic_bookmark_filled, + disabledTextColor() + ) { + onItemTapped.invoke(ToolbarMenu.Item.Bookmarks) } - ) { checked -> - onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked)) - } - private 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 historyItem = BrowserMenuImageText( + context.getString(R.string.library_history), + R.drawable.ic_history, + disabledTextColor() + ) { + onItemTapped.invoke(ToolbarMenu.Item.History) + } - private 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 downloadsItem = BrowserMenuImageText( + context.getString(R.string.library_downloads), + R.drawable.ic_download, + disabledTextColor() + ) { + onItemTapped.invoke(ToolbarMenu.Item.Downloads) + } - private val syncedTabs = BrowserMenuImageText( - label = context.getString(R.string.synced_tabs), - imageResource = R.drawable.ic_synced_tabs, - iconTintColorResource = primaryTextColor() - ) { - onItemTapped.invoke(ToolbarMenu.Item.SyncedTabs) - } + val extensionsItem = BrowserMenuImageText( + context.getString(R.string.browser_menu_extensions), + R.drawable.ic_addons_extensions, + disabledTextColor() + ) { + onItemTapped.invoke(ToolbarMenu.Item.AddonsManager) + } - private 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 + val syncedTabsItem = BrowserMenuImageText( + context.getString(R.string.library_synced_tabs), + R.drawable.ic_synced_tabs, + disabledTextColor() + ) { + onItemTapped.invoke(ToolbarMenu.Item.SyncedTabs) } - ) { - onItemTapped.invoke(ToolbarMenu.Item.InstallToHomeScreen) - } - private 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 findInPageItem = BrowserMenuImageText( + label = context.getString(R.string.browser_menu_find_in_page), + imageResource = R.drawable.mozac_ic_search, + iconTintColorResource = disabledTextColor() + ) { + onItemTapped.invoke(ToolbarMenu.Item.FindInPage) + } - private val reportSiteIssuePlaceholder = WebExtensionPlaceholderMenuItem( - id = WebCompatReporterFeature.WEBCOMPAT_REPORTER_EXTENSION_ID - ) + val desktopSiteItem = 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)) + } - private 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 addToHomeScreenItem = BrowserMenuImageText( + label = context.getString(R.string.browser_menu_add_to_homescreen), + imageResource = R.drawable.ic_add_to_homescreen, + iconTintColorResource = disabledTextColor() + ) { + onItemTapped.invoke(ToolbarMenu.Item.AddToHomeScreen) + } - private 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 addToTopSitesItem = BrowserMenuImageText( + label = context.getString(R.string.browser_menu_add_to_top_sites), + imageResource = R.drawable.ic_top_sites, + iconTintColorResource = disabledTextColor() + ) { + onItemTapped.invoke(ToolbarMenu.Item.AddToTopSites) + } - private 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 saveToCollectionItem = BrowserMenuImageText( + label = context.getString(R.string.browser_menu_save_to_collection_2), + imageResource = R.drawable.ic_tab_collection, + iconTintColorResource = disabledTextColor() + ) { + onItemTapped.invoke(ToolbarMenu.Item.SaveToCollection) + } - private 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 settingsItem = BrowserMenuHighlightableItem( + label = context.getString(R.string.browser_menu_settings), + startImageResource = R.drawable.ic_settings, + iconTintColorResource = disabledTextColor(), + 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 historyItem = BrowserMenuImageText( - context.getString(R.string.library_history), - R.drawable.ic_history, - primaryTextColor() - ) { - onItemTapped.invoke(ToolbarMenu.Item.History) - } + val syncedTabsInTabsTray = context.components.settings + .syncedTabsInTabsTray - val bookmarksItem = BrowserMenuImageText( - context.getString(R.string.library_bookmarks), - R.drawable.ic_bookmark_filled, - primaryTextColor() - ) { - onItemTapped.invoke(ToolbarMenu.Item.Bookmarks) - } + val menuItems = listOfNotNull( + newTabItem, + BrowserMenuDivider(), + bookmarksItem, + historyItem, + downloadsItem, + extensionsItem, + if (syncedTabsInTabsTray) null else syncedTabsItem, + BrowserMenuDivider(), + findInPageItem, + desktopSiteItem, + BrowserMenuDivider(), + addToHomeScreenItem.apply { visible = ::canAddToHomescreen }, + addToTopSitesItem, + saveToCollectionItem, + BrowserMenuDivider(), + settingsItem, + BrowserMenuDivider(), + menuToolbar + ) - val downloadsItem = BrowserMenuImageText( - context.getString(R.string.library_downloads), - R.drawable.ic_download, - primaryTextColor() - ) { - onItemTapped.invoke(ToolbarMenu.Item.Downloads) + menuItems } @ColorRes @VisibleForTesting internal fun primaryTextColor() = ThemeManager.resolveAttribute(R.attr.primaryText, context) + @ColorRes + @VisibleForTesting + internal fun disabledTextColor() = R.color.toolbar_menu_transparent + @VisibleForTesting internal fun registerForIsBookmarkedUpdates() { store.flowScoped(lifecycleOwner) { flow -> diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt index 27b47c3090..87cb80ae3e 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt @@ -31,6 +31,7 @@ interface ToolbarMenu { object Bookmarks : Item() object History : Item() object Downloads : Item() + object NewTab : Item() } val menuBuilder: BrowserMenuBuilder diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 5f2d1b1a91..f8d09ea358 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -422,4 +422,7 @@ #1415141A + + + @android:color/transparent diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 515b2d760a..e6c09d865a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,6 +128,8 @@ Edit bookmark Add-ons + + Extensions No add-ons here @@ -517,6 +519,10 @@ Other Bookmarks History + + New tab + + Find in page Synced tabs diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt index 4082077ae6..211cf0fc90 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt @@ -45,6 +45,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.R @@ -64,6 +65,7 @@ import org.mozilla.fenix.utils.Settings @OptIn(ExperimentalCoroutinesApi::class) @RunWith(FenixRobolectricTestRunner::class) +@Suppress("ForbiddenComment") class DefaultBrowserToolbarMenuControllerTest { @get:Rule @@ -124,8 +126,121 @@ class DefaultBrowserToolbarMenuControllerTest { unmockkObject(FenixSnackbar.Companion) } + // TODO: These can be removed for https://github.com/mozilla-mobile/fenix/issues/17870 + // todo === Start === @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 controller = createController(scope = this) @@ -136,7 +251,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 controller = createController(scope = this) @@ -149,7 +264,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 controller = createController(scope = this) @@ -160,7 +275,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 controller = createController(scope = this) @@ -173,7 +288,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 controller = createController(scope = this) @@ -184,7 +299,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 controller = createController(scope = this) @@ -200,7 +315,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @Test - fun handleToolbarStopPress() = runBlockingTest { + fun `WHEN stop nav menu item is pressed THEN the session stops loading`() = runBlockingTest { val item = ToolbarMenu.Item.Stop val controller = createController(scope = this) @@ -211,7 +326,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @Test - fun handleToolbarSettingsPress() = runBlockingTest { + fun `WHEN settings menu item is pressed THEN menu item is handled`() = runBlockingTest { val item = ToolbarMenu.Item.Settings val controller = createController(scope = this) @@ -224,52 +339,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @Test - fun handleToolbarBookmarkPressWithReaderModeInactive() = 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 { + fun `WHEN bookmark menu item is pressed THEN navigate to bookmarks page`() = runBlockingTest { val item = ToolbarMenu.Item.Bookmarks val controller = createController(scope = this) @@ -282,7 +352,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @Test - fun handleToolbarHistoryPress() = runBlockingTest { + fun `WHEN history menu item is pressed THEN navigate to history page`() = runBlockingTest { val item = ToolbarMenu.Item.History val controller = createController(scope = this) @@ -295,7 +365,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 = mockk(relaxed = true) val item = ToolbarMenu.Item.RequestDesktop(true) @@ -315,7 +385,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 = mockk(relaxed = true) val item = ToolbarMenu.Item.RequestDesktop(false) @@ -335,7 +405,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 addPinnedSiteUseCase: TopSitesUseCases.AddPinnedSiteUseCase = mockk(relaxed = true) @@ -353,7 +423,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @Test - fun handleToolbarAddonsManagerPress() = runBlockingTest { + fun `WHEN addon extensions menu item is pressed THEN navigate to addons manager`() = runBlockingTest { val item = ToolbarMenu.Item.AddonsManager val controller = createController(scope = this) @@ -363,7 +433,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @Test - fun handleToolbarAddToHomeScreenPress() = runBlockingTest { + fun `WHEN Add To Home Screen menu item is pressed THEN add site`() = runBlockingTest { val item = ToolbarMenu.Item.AddToHomeScreen val controller = createController(scope = this) @@ -373,7 +443,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 title = "Mozilla" val readerUrl = "moz-extension://1234" @@ -404,7 +474,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 title = "Mozilla" val readerUrl = "moz-extension://1234" @@ -435,7 +505,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @Test - fun handleToolbarFindInPagePress() = runBlockingTest { + fun `WHEN Find In Page menu item is pressed THEN launch finder`() = runBlockingTest { val item = ToolbarMenu.Item.FindInPage val controller = createController(scope = this) @@ -446,7 +516,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 cachedTabCollections: List = mockk(relaxed = true) every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollections @@ -474,7 +544,7 @@ class DefaultBrowserToolbarMenuControllerTest { } @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 cachedTabCollectionsEmpty: List = emptyList() every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollectionsEmpty @@ -499,56 +569,22 @@ class DefaultBrowserToolbarMenuControllerTest { } @Test - fun handleToolbarOpenInFenixPress() = runBlockingTest { - 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 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 + fun `WHEN New Tab menu item is pressed THEN navigate to a new tab home`() = runBlockingTest { + val item = ToolbarMenu.Item.NewTab val controller = createController(scope = this) controller.handleToolbarItemInteraction(item) - verify { readerModeController.showControls() } - verify { metrics.track(Event.ReaderModeAppearanceOpened) } - } - - @Test - fun handleToolbarOpenInAppPress() = runBlockingTest { - val item = ToolbarMenu.Item.OpenInApp - - val controller = createController(scope = this) - - controller.handleToolbarItemInteraction(item) - - verify { settings.openInAppOpened = true } + verify { + navController.navigate( + directionsEq( + NavGraphDirections.actionGlobalHome( + focusOnAddressBar = true + ) + ) + ) + } } private fun createController(