diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/MenuIntegration.kt b/app/src/main/java/org/mozilla/fenix/tabstray/MenuIntegration.kt new file mode 100644 index 0000000000..42d55680b4 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/MenuIntegration.kt @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.tabstray + +import android.content.Context +import android.view.View +import androidx.annotation.VisibleForTesting +import androidx.cardview.widget.CardView +import androidx.core.content.ContextCompat +import com.google.android.material.tabs.TabLayout +import mozilla.components.browser.menu.BrowserMenu +import mozilla.components.browser.menu.BrowserMenuBuilder +import mozilla.components.browser.state.store.BrowserStore +import org.mozilla.fenix.R + +/** + * A wrapper class that building the tabs tray menu that handles item clicks. + */ +class MenuIntegration( + private val context: Context, + private val browserStore: BrowserStore, + private val tabsTrayStore: TabsTrayStore, + private val tabLayout: TabLayout, + private val navigationInteractor: NavigationInteractor +) { + private val tabsTrayItemMenu by lazy { + TabsTrayMenu( + context = context, + browserStore = browserStore, + tabLayout = tabLayout, + onItemTapped = ::handleMenuClicked + ) + } + + private val isPrivateMode: Boolean + get() = tabsTrayStore.state.selectedPage == Page.PrivateTabs + + /** + * Builds the internal menu items list. See [BrowserMenuBuilder.build]. + */ + fun build() = tabsTrayItemMenu.menuBuilder.build(context) + + @VisibleForTesting + internal fun handleMenuClicked(item: TabsTrayMenu.Item) = when (item) { + is TabsTrayMenu.Item.ShareAllTabs -> + navigationInteractor.onShareTabsOfTypeClicked(isPrivateMode) + is TabsTrayMenu.Item.OpenTabSettings -> + navigationInteractor.onTabSettingsClicked() + is TabsTrayMenu.Item.CloseAllTabs -> + navigationInteractor.onCloseAllTabsClicked(isPrivateMode) + is TabsTrayMenu.Item.OpenRecentlyClosed -> + navigationInteractor.onOpenRecentlyClosedClicked() + is TabsTrayMenu.Item.SelectTabs -> { + /* TODO implement when mulitiselect call is available */ + } + } +} + +/** + * Invokes [BrowserMenu.show] and applies the default theme color background. + */ +fun BrowserMenu.showWithTheme(view: View) { + show(view).also { popupMenu -> + (popupMenu.contentView as? CardView)?.setCardBackgroundColor( + ContextCompat.getColor( + view.context, + R.color.foundation_normal_theme + ) + ) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt index e0c3a77a19..bccf6995da 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -10,13 +10,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatDialogFragment -import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.content.ContextCompat import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.tabs.TabLayout import kotlinx.android.synthetic.main.component_tabstray2.* import kotlinx.android.synthetic.main.component_tabstray2.view.* import kotlinx.android.synthetic.main.tabs_tray_tab_counter2.* @@ -26,7 +23,6 @@ import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.ext.components import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.home.HomeScreenViewModel @@ -38,13 +34,6 @@ import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsInteractor class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { - private lateinit var navigationInteractor: NavigationInteractor - - private val tabLayout: TabLayout? get() = - view?.tab_layout - - private val isPrivateModeSelected: Boolean get() = - tabLayout?.selectedTabPosition == TrayPagerAdapter.POSITION_PRIVATE_TABS private lateinit var tabsTrayStore: TabsTrayStore private lateinit var browserTrayInteractor: BrowserTrayInteractor private lateinit var behavior: BottomSheetBehavior @@ -85,19 +74,9 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { val containerView = inflater.inflate(R.layout.fragment_tab_tray_dialog, container, false) val view: View = LayoutInflater.from(containerView.context) .inflate(R.layout.component_tabstray2, containerView as ViewGroup, true) - val activity = activity as HomeActivity behavior = BottomSheetBehavior.from(view.tab_wrapper) - navigationInteractor = - DefaultNavigationInteractor( - browserStore = activity.components.core.store, - navController = findNavController(), - metrics = activity.components.analytics.metrics, - dismissTabTray = ::dismissAllowingStateLoss, - dismissTabTrayAndNavigateHome = ::dismissTabTrayAndNavigateHome - ) - tabsTrayStore = StoreProvider.get(this) { TabsTrayStore() } return containerView @@ -106,7 +85,6 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { @ExperimentalCoroutinesApi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupMenu(view) browserTrayInteractor = DefaultBrowserTrayInteractor( tabsTrayStore, @@ -116,12 +94,22 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { this ) + val navigationInteractor = + DefaultNavigationInteractor( + browserStore = requireComponents.core.store, + navController = findNavController(), + metrics = requireComponents.analytics.metrics, + dismissTabTray = ::dismissAllowingStateLoss, + dismissTabTrayAndNavigateHome = ::dismissTabTrayAndNavigateHome + ) + val syncedTabsTrayInteractor = SyncedTabsInteractor( requireComponents.analytics.metrics, requireActivity() as HomeActivity, this ) + setupMenu(view, navigationInteractor) setupPager( view.context, tabsTrayStore, @@ -195,40 +183,23 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { } } - private fun setupMenu(view: View) { + private fun setupMenu(view: View, navigationInteractor: NavigationInteractor) { view.tab_tray_overflow.setOnClickListener { anchor -> - val tabTrayItemMenu = - TabsTrayMenu( - context = view.context, - browserStore = requireComponents.core.store, - tabLayout = tab_layout - ) { - when (it) { - is TabsTrayMenu.Item.ShareAllTabs -> - navigationInteractor.onShareTabsOfTypeClicked(isPrivateModeSelected) - is TabsTrayMenu.Item.OpenTabSettings -> - navigationInteractor.onTabSettingsClicked() - is TabsTrayMenu.Item.CloseAllTabs -> - navigationInteractor.onCloseAllTabsClicked(isPrivateModeSelected) - is TabsTrayMenu.Item.OpenRecentlyClosed -> - navigationInteractor.onOpenRecentlyClosedClicked() - is TabsTrayMenu.Item.SelectTabs -> - { /* TODO implement when mulitiselect call is available */ } - } - } requireComponents.analytics.metrics.track(Event.TabsTrayMenuOpened) - val menu = tabTrayItemMenu.menuBuilder.build(view.context) - menu.show(anchor).also { popupMenu -> - (popupMenu.contentView as? CardView)?.setCardBackgroundColor( - ContextCompat.getColor( - view.context, - R.color.foundation_normal_theme - ) - ) - } + + val menu = MenuIntegration( + context = requireContext(), + browserStore = requireComponents.core.store, + tabsTrayStore = TabsTrayStore(), + tabLayout = tab_layout, + navigationInteractor = navigationInteractor + ).build() + + menu.showWithTheme(anchor) } } + private val homeViewModel: HomeScreenViewModel by activityViewModels() private fun dismissTabTrayAndNavigateHome(sessionId: String) { diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt index bb83646662..283f447640 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt @@ -6,6 +6,7 @@ package org.mozilla.fenix.tabstray import mozilla.components.concept.tabstray.Tab import mozilla.components.lib.state.Action +import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.State import mozilla.components.lib.state.Store @@ -147,8 +148,10 @@ internal object TabsTrayReducer { * dispatched to the store. */ class TabsTrayStore( - initialState: TabsTrayState = TabsTrayState() + initialState: TabsTrayState = TabsTrayState(), + middlewares: List> = emptyList() ) : Store( initialState, - TabsTrayReducer::reduce + TabsTrayReducer::reduce, + middlewares ) diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/MenuIntegrationTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/MenuIntegrationTest.kt new file mode 100644 index 0000000000..b789d6e0c7 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/MenuIntegrationTest.kt @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.tabstray + +import io.mockk.mockk +import io.mockk.verify +import mozilla.components.support.test.middleware.CaptureActionsMiddleware +import org.junit.Assert.assertNotNull +import org.junit.Ignore +import org.junit.Test + +class MenuIntegrationTest { + + private val captureMiddleware = CaptureActionsMiddleware() + private val tabsTrayStore = TabsTrayStore(middlewares = listOf(captureMiddleware)) + private val interactor = mockk(relaxed = true) + + @Test + fun `WHEN the share all menu item is clicked THEN invoke the action`() { + val menu = MenuIntegration(mockk(), mockk(), tabsTrayStore, mockk(), interactor) + + menu.handleMenuClicked(TabsTrayMenu.Item.ShareAllTabs) + + verify { interactor.onShareTabsOfTypeClicked(false) } + } + + @Test + fun `WHEN the open settings menu item is clicked THEN invoke the action`() { + val menu = MenuIntegration(mockk(), mockk(), tabsTrayStore, mockk(), interactor) + + menu.handleMenuClicked(TabsTrayMenu.Item.OpenTabSettings) + + verify { interactor.onTabSettingsClicked() } + } + + @Test + fun `WHEN the close all menu item is clicked THEN invoke the action`() { + val menu = MenuIntegration(mockk(), mockk(), tabsTrayStore, mockk(), interactor) + + menu.handleMenuClicked(TabsTrayMenu.Item.CloseAllTabs) + + verify { interactor.onCloseAllTabsClicked(false) } + } + + @Test + fun `WHEN the recently menu item is clicked THEN invoke the action`() { + val menu = MenuIntegration(mockk(), mockk(), tabsTrayStore, mockk(), interactor) + + menu.handleMenuClicked(TabsTrayMenu.Item.OpenRecentlyClosed) + + verify { interactor.onOpenRecentlyClosedClicked() } + } + + @Ignore("Enable after we connect this menu item to the store") + @Test + fun `WHEN the select menu item is clicked THEN invoke the action`() { + val menu = MenuIntegration(mockk(), mockk(), tabsTrayStore, mockk(), interactor) + + menu.handleMenuClicked(TabsTrayMenu.Item.ShareAllTabs) + + assertNotNull(captureMiddleware.findLastAction(TabsTrayAction.EnterSelectMode::class)) + } +}