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