parent
a443509c8b
commit
01568d5859
@ -0,0 +1,93 @@
|
|||||||
|
/* 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 androidx.navigation.NavController
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.concept.engine.prompt.ShareData
|
||||||
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
|
import org.mozilla.fenix.home.HomeFragment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For interacting with UI that is specifically for [TabsTrayFragment[]] and other browser
|
||||||
|
* tab tray views.
|
||||||
|
*/
|
||||||
|
interface NavigationInteractor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when tab tray should be dismissed.
|
||||||
|
*/
|
||||||
|
fun onTabTrayDismissed()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when user clicks the share tabs button.
|
||||||
|
*/
|
||||||
|
fun onShareTabsOfTypeClicked(private: Boolean)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when user clicks the tab settings button.
|
||||||
|
*/
|
||||||
|
fun onTabSettingsClicked()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when user clicks the close all tabs button.
|
||||||
|
*/
|
||||||
|
fun onCloseAllTabsClicked(private: Boolean)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when user clicks the recently closed tabs menu button.
|
||||||
|
*/
|
||||||
|
fun onOpenRecentlyClosedClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A default implementation of [NavigationInteractor].
|
||||||
|
*/
|
||||||
|
class DefaultNavigationInteractor(
|
||||||
|
private val browserStore: BrowserStore,
|
||||||
|
private val navController: NavController,
|
||||||
|
private val metrics: MetricController,
|
||||||
|
private val dismissTabTray: () -> Unit,
|
||||||
|
private val dismissTabTrayAndNavigateHome: (String) -> Unit
|
||||||
|
) : NavigationInteractor {
|
||||||
|
|
||||||
|
override fun onTabTrayDismissed() {
|
||||||
|
dismissTabTray()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabSettingsClicked() {
|
||||||
|
navController.navigate(TabsTrayFragmentDirections.actionGlobalTabSettingsFragment())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOpenRecentlyClosedClicked() {
|
||||||
|
navController.navigate(TabsTrayFragmentDirections.actionGlobalRecentlyClosed())
|
||||||
|
metrics.track(Event.RecentlyClosedTabsOpened)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShareTabsOfTypeClicked(private: Boolean) {
|
||||||
|
val tabs = browserStore.state.getNormalOrPrivateTabs(private)
|
||||||
|
val data = tabs.map {
|
||||||
|
ShareData(url = it.content.url, title = it.content.title)
|
||||||
|
}
|
||||||
|
val directions = TabsTrayFragmentDirections.actionGlobalShareFragment(
|
||||||
|
data = data.toTypedArray()
|
||||||
|
)
|
||||||
|
navController.navigate(directions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
override fun onCloseAllTabsClicked(private: Boolean) {
|
||||||
|
val sessionsToClose = if (private) {
|
||||||
|
HomeFragment.ALL_PRIVATE_TABS
|
||||||
|
} else {
|
||||||
|
HomeFragment.ALL_NORMAL_TABS
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissTabTrayAndNavigateHome(sessionsToClose)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/* 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 com.google.android.material.tabs.TabLayout
|
||||||
|
import mozilla.components.browser.menu.BrowserMenuBuilder
|
||||||
|
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
|
||||||
|
import mozilla.components.browser.state.selector.normalTabs
|
||||||
|
import mozilla.components.browser.state.selector.privateTabs
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.tabstray.ext.isNormalModeSelected
|
||||||
|
import org.mozilla.fenix.tabstray.ext.isPrivateModeSelected
|
||||||
|
|
||||||
|
class TabsTrayMenu(
|
||||||
|
private val context: Context,
|
||||||
|
browserStore: BrowserStore,
|
||||||
|
private val tabLayout: TabLayout,
|
||||||
|
private val onItemTapped: (Item) -> Unit = {}
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val checkOpenTabs =
|
||||||
|
when {
|
||||||
|
tabLayout.isNormalModeSelected() ->
|
||||||
|
browserStore.state.normalTabs.isNotEmpty()
|
||||||
|
tabLayout.isPrivateModeSelected() ->
|
||||||
|
browserStore.state.privateTabs.isNotEmpty()
|
||||||
|
else ->
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
private val shouldShowSelectOrShare = { tabLayout.isNormalModeSelected() && checkOpenTabs }
|
||||||
|
|
||||||
|
sealed class Item {
|
||||||
|
object ShareAllTabs : Item()
|
||||||
|
object OpenTabSettings : Item()
|
||||||
|
object SelectTabs : Item()
|
||||||
|
object CloseAllTabs : Item()
|
||||||
|
object OpenRecentlyClosed : Item()
|
||||||
|
}
|
||||||
|
|
||||||
|
val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
|
||||||
|
|
||||||
|
private val menuItems by lazy {
|
||||||
|
listOf(
|
||||||
|
SimpleBrowserMenuItem(
|
||||||
|
context.getString(R.string.tabs_tray_select_tabs),
|
||||||
|
textColorResource = R.color.primary_text_normal_theme
|
||||||
|
) {
|
||||||
|
onItemTapped.invoke(Item.SelectTabs)
|
||||||
|
}.apply { visible = shouldShowSelectOrShare },
|
||||||
|
|
||||||
|
SimpleBrowserMenuItem(
|
||||||
|
context.getString(R.string.tab_tray_menu_item_share),
|
||||||
|
textColorResource = R.color.primary_text_normal_theme
|
||||||
|
) {
|
||||||
|
context.components.analytics.metrics.track(Event.TabsTrayShareAllTabsPressed)
|
||||||
|
onItemTapped.invoke(Item.ShareAllTabs)
|
||||||
|
}.apply { visible = shouldShowSelectOrShare },
|
||||||
|
|
||||||
|
SimpleBrowserMenuItem(
|
||||||
|
context.getString(R.string.tab_tray_menu_tab_settings),
|
||||||
|
textColorResource = R.color.primary_text_normal_theme
|
||||||
|
) {
|
||||||
|
onItemTapped.invoke(Item.OpenTabSettings)
|
||||||
|
},
|
||||||
|
|
||||||
|
SimpleBrowserMenuItem(
|
||||||
|
context.getString(R.string.tab_tray_menu_recently_closed),
|
||||||
|
textColorResource = R.color.primary_text_normal_theme
|
||||||
|
) {
|
||||||
|
onItemTapped.invoke(Item.OpenRecentlyClosed)
|
||||||
|
},
|
||||||
|
|
||||||
|
SimpleBrowserMenuItem(
|
||||||
|
context.getString(R.string.tab_tray_menu_item_close),
|
||||||
|
textColorResource = R.color.primary_text_normal_theme
|
||||||
|
) {
|
||||||
|
context.components.analytics.metrics.track(Event.TabsTrayCloseAllTabsPressed)
|
||||||
|
onItemTapped.invoke(Item.CloseAllTabs)
|
||||||
|
}.apply { visible = { checkOpenTabs } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
/* 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.ext
|
||||||
|
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import org.mozilla.fenix.tabstray.TrayPagerAdapter
|
||||||
|
|
||||||
|
fun TabLayout.isNormalModeSelected(): Boolean {
|
||||||
|
return selectedTabPosition == TrayPagerAdapter.POSITION_NORMAL_TABS
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TabLayout.isPrivateModeSelected(): Boolean {
|
||||||
|
return selectedTabPosition == TrayPagerAdapter.POSITION_PRIVATE_TABS
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
/* 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 androidx.navigation.NavController
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import mozilla.components.browser.state.state.BrowserState
|
||||||
|
import mozilla.components.browser.state.state.TabSessionState
|
||||||
|
import mozilla.components.browser.state.state.createTab
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
|
|
||||||
|
class NavigationInteractorTest {
|
||||||
|
private lateinit var store: BrowserStore
|
||||||
|
private lateinit var navigationInteractor: NavigationInteractor
|
||||||
|
private val testTab: TabSessionState = createTab(url = "https://mozilla.org")
|
||||||
|
private val navController: NavController = mockk(relaxed = true)
|
||||||
|
private val metrics: MetricController = mockk(relaxed = true)
|
||||||
|
private val dismissTabTray: () -> Unit = mockk(relaxed = true)
|
||||||
|
private val dismissTabTrayAndNavigateHome: (String) -> Unit = mockk(relaxed = true)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
store = BrowserStore(initialState = BrowserState(tabs = listOf(testTab)))
|
||||||
|
navigationInteractor = DefaultNavigationInteractor(
|
||||||
|
store,
|
||||||
|
navController,
|
||||||
|
metrics,
|
||||||
|
dismissTabTray,
|
||||||
|
dismissTabTrayAndNavigateHome
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `navigation interactor calls the overridden functions`() {
|
||||||
|
var tabTrayDismissed = false
|
||||||
|
var tabSettingsClicked = false
|
||||||
|
var openRecentlyClosedClicked = false
|
||||||
|
var shareTabsOfTypeClicked = false
|
||||||
|
var closeAllTabsClicked = false
|
||||||
|
|
||||||
|
class TestNavigationInteractor : NavigationInteractor {
|
||||||
|
|
||||||
|
override fun onTabTrayDismissed() {
|
||||||
|
tabTrayDismissed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabSettingsClicked() {
|
||||||
|
tabSettingsClicked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOpenRecentlyClosedClicked() {
|
||||||
|
openRecentlyClosedClicked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShareTabsOfTypeClicked(private: Boolean) {
|
||||||
|
shareTabsOfTypeClicked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCloseAllTabsClicked(private: Boolean) {
|
||||||
|
closeAllTabsClicked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val navigationInteractor: NavigationInteractor = TestNavigationInteractor()
|
||||||
|
navigationInteractor.onTabTrayDismissed()
|
||||||
|
assert(tabTrayDismissed)
|
||||||
|
navigationInteractor.onTabSettingsClicked()
|
||||||
|
assert(tabSettingsClicked)
|
||||||
|
navigationInteractor.onOpenRecentlyClosedClicked()
|
||||||
|
assert(openRecentlyClosedClicked)
|
||||||
|
navigationInteractor.onShareTabsOfTypeClicked(true)
|
||||||
|
assert(shareTabsOfTypeClicked)
|
||||||
|
navigationInteractor.onCloseAllTabsClicked(true)
|
||||||
|
assert(closeAllTabsClicked)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onTabTrayDismissed calls dismissTabTray on DefaultNaviationInteractor`() {
|
||||||
|
navigationInteractor.onTabTrayDismissed()
|
||||||
|
verify(exactly = 1) { dismissTabTray() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onTabSettingsClicked calls navigation on DefaultNaviationInteractor`() {
|
||||||
|
navigationInteractor.onTabSettingsClicked()
|
||||||
|
verify(exactly = 1) { navController.navigate(TabsTrayFragmentDirections.actionGlobalTabSettingsFragment()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onOpenRecentlyClosedClicked calls navigation on DefaultNaviationInteractor`() {
|
||||||
|
navigationInteractor.onOpenRecentlyClosedClicked()
|
||||||
|
verify(exactly = 1) { navController.navigate(TabsTrayFragmentDirections.actionGlobalRecentlyClosed()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onCloseAllTabsClicked calls navigation on DefaultNaviationInteractor`() {
|
||||||
|
navigationInteractor.onCloseAllTabsClicked(false)
|
||||||
|
verify(exactly = 1) { dismissTabTrayAndNavigateHome(any()) }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue