[fenix] Close https://github.com/mozilla-mobile/fenix/issues/22305: Add tab state into the TabsTrayStore

pull/600/head
Jonathan Almeida 3 years ago committed by mergify[bot]
parent e24a305ba0
commit 35556e4cc2

@ -4,11 +4,13 @@
package org.mozilla.fenix.tabstray
import mozilla.components.browser.state.state.ContentState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import org.mozilla.fenix.tabstray.browser.TabGroup
/**
* Value type that represents the state of the tabs tray.
@ -16,13 +18,20 @@ import mozilla.components.lib.state.Store
* @property selectedPage The current page in the tray can be on.
* @property mode Whether the browser tab list is in multi-select mode or not with the set of
* currently selected tabs.
* @property syncing Whether the Synced Tabs feature should fetch the latest tabs from paired
* devices.
* @property inactiveTabs The list of tabs are considered inactive.
* @property searchTermGroups The list of tab groups.
* @property normalTabs The list of normal tabs that do not fall under [inactiveTabs] or [searchTermGroups].
* @property privateTabs The list of tabs that are [ContentState.private].
* @property syncing Whether the Synced Tabs feature should fetch the latest tabs from paired devices.
* @property focusGroupTabId The search group tab id to focus. Defaults to null.
*/
data class TabsTrayState(
val selectedPage: Page = Page.NormalTabs,
val mode: Mode = Mode.Normal,
val inactiveTabs: List<TabSessionState> = emptyList(),
val searchTermGroups: List<TabGroup> = emptyList(),
val normalTabs: List<TabSessionState> = emptyList(),
val privateTabs: List<TabSessionState> = emptyList(),
val syncing: Boolean = false,
val focusGroupTabId: String? = null
) : State {
@ -126,6 +135,26 @@ sealed class TabsTrayAction : Action {
* Removes the [TabsTrayState.focusGroupTabId] of the [TabsTrayState].
*/
object ConsumeFocusGroupTabIdAction : TabsTrayAction()
/**
* Updates the list of tabs in [TabsTrayState.inactiveTabs].
*/
data class UpdateInactiveTabs(val tabs: List<TabSessionState>) : TabsTrayAction()
/**
* Updates the list of tab groups in [TabsTrayState.searchTermGroups].
*/
data class UpdateSearchGroupTabs(val groups: List<TabGroup>) : TabsTrayAction()
/**
* Updates the list of tabs in [TabsTrayState.normalTabs].
*/
data class UpdateNormalTabs(val tabs: List<TabSessionState>) : TabsTrayAction()
/**
* Updates the list of tabs in [TabsTrayState.privateTabs].
*/
data class UpdatePrivateTabs(val tabs: List<TabSessionState>) : TabsTrayAction()
}
/**
@ -158,6 +187,14 @@ internal object TabsTrayReducer {
state.copy(syncing = false)
is TabsTrayAction.ConsumeFocusGroupTabIdAction ->
state.copy(focusGroupTabId = null)
is TabsTrayAction.UpdateInactiveTabs ->
state.copy(inactiveTabs = action.tabs)
is TabsTrayAction.UpdateSearchGroupTabs ->
state.copy(searchTermGroups = action.groups)
is TabsTrayAction.UpdateNormalTabs ->
state.copy(normalTabs = action.tabs)
is TabsTrayAction.UpdatePrivateTabs ->
state.copy(privateTabs = action.tabs)
}
}
}

@ -12,6 +12,8 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.maxActiveTime
import org.mozilla.fenix.ext.toSearchGroup
import org.mozilla.fenix.tabstray.TabsTrayAction
import org.mozilla.fenix.tabstray.TabsTrayStore
import org.mozilla.fenix.tabstray.ext.browserAdapter
import org.mozilla.fenix.tabstray.ext.hasSearchTerm
import org.mozilla.fenix.tabstray.ext.inactiveTabsAdapter
@ -27,18 +29,23 @@ import org.mozilla.fenix.utils.Settings
class TabSorter(
private val settings: Settings,
private val metrics: MetricController,
private val concatAdapter: ConcatAdapter
private val concatAdapter: ConcatAdapter? = null,
private val tabsTrayStore: TabsTrayStore? = null
) : TabsTray {
private var shouldReportMetrics: Boolean = true
private val groupsSet = mutableSetOf<String>()
override fun updateTabs(tabs: List<TabSessionState>, selectedTabId: String?) {
val inactiveTabs = tabs.getInactiveTabs(settings)
val searchTermTabs = tabs.getSearchGroupTabs(settings)
val normalTabs = tabs - inactiveTabs - searchTermTabs
val privateTabs = tabs.filter { it.content.private }
tabsTrayStore?.dispatch(TabsTrayAction.UpdatePrivateTabs(privateTabs))
val normalTabs = tabs - privateTabs
val inactiveTabs = normalTabs.getInactiveTabs(settings)
val searchTermTabs = normalTabs.getSearchGroupTabs(settings)
val regularTabs = normalTabs - inactiveTabs - searchTermTabs
// Inactive tabs
concatAdapter.inactiveTabsAdapter.updateTabs(inactiveTabs, selectedTabId)
tabsTrayStore?.dispatch(TabsTrayAction.UpdateInactiveTabs(inactiveTabs))
// Tab groups
// We don't need to provide a selectedId, because the [TabGroupAdapter] has that built-in with support from
@ -47,15 +54,17 @@ class TabSorter(
groupsSet.clear()
groupsSet.addAll(groups.map { it.searchTerm })
concatAdapter.tabGroupAdapter.submitList(groups)
concatAdapter?.tabGroupAdapter?.submitList(groups)
tabsTrayStore?.dispatch(TabsTrayAction.UpdateSearchGroupTabs(groups))
// Normal tabs.
val totalNormalTabs = (normalTabs + remainderTabs)
concatAdapter.browserAdapter.updateTabs(totalNormalTabs, selectedTabId)
val totalNormalTabs = (regularTabs + remainderTabs)
concatAdapter?.browserAdapter?.updateTabs(totalNormalTabs, selectedTabId)
tabsTrayStore?.dispatch(TabsTrayAction.UpdateNormalTabs(totalNormalTabs))
// Normal tab title header.
concatAdapter.titleHeaderAdapter
.handleListChanges(totalNormalTabs.isNotEmpty() && groups.isNotEmpty())
concatAdapter?.titleHeaderAdapter
?.handleListChanges(totalNormalTabs.isNotEmpty() && groups.isNotEmpty())
if (shouldReportMetrics) {
shouldReportMetrics = false

@ -4,6 +4,7 @@
package org.mozilla.fenix.tabstray
import mozilla.components.browser.state.state.createTab
import org.junit.Assert.assertEquals
import org.junit.Test
@ -20,4 +21,52 @@ class TabsTrayStoreReducerTest {
assertEquals(expectedState, resultState)
}
@Test
fun `WHEN UpdateInactiveTabs THEN inactive tabs are added`() {
val inactiveTabs = listOf(
createTab("https://mozilla.org")
)
val initialState = TabsTrayState()
val expectedState = initialState.copy(inactiveTabs = inactiveTabs)
val resultState = TabsTrayReducer.reduce(
initialState,
TabsTrayAction.UpdateInactiveTabs(inactiveTabs)
)
assertEquals(expectedState, resultState)
}
@Test
fun `WHEN UpdateNormalTabs THEN normal tabs are added`() {
val normalTabs = listOf(
createTab("https://mozilla.org")
)
val initialState = TabsTrayState()
val expectedState = initialState.copy(normalTabs = normalTabs)
val resultState = TabsTrayReducer.reduce(
initialState,
TabsTrayAction.UpdateNormalTabs(normalTabs)
)
assertEquals(expectedState, resultState)
}
@Test
fun `WHEN UpdatePrivateTabs THEN private tabs are added`() {
val privateTabs = listOf(
createTab("https://mozilla.org", private = true)
)
val initialState = TabsTrayState()
val expectedState = initialState.copy(privateTabs = privateTabs)
val resultState = TabsTrayReducer.reduce(
initialState,
TabsTrayAction.UpdatePrivateTabs(privateTabs)
)
assertEquals(expectedState, resultState)
}
}

@ -16,6 +16,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.tabstray.TabsTrayStore
import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.INACTIVE_TABS_FEATURE_NAME
import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.TABS_TRAY_FEATURE_NAME
import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.TAB_GROUP_FEATURE_NAME
@ -31,6 +32,7 @@ class TabSorterTest {
private val settings: Settings = mockk()
private val metrics: MetricController = mockk()
private var inactiveTimestamp = 0L
private val tabsTrayStore = TabsTrayStore()
@Before
fun setUp() {
@ -42,7 +44,7 @@ class TabSorterTest {
@Test
fun `WHEN updated with one normal tab THEN adapter have only one normal tab and no header`() {
val adapter = ConcatAdapter(
InactiveTabsAdapter(context, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME),
TitleHeaderAdapter(),
BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME)
@ -66,7 +68,7 @@ class TabSorterTest {
@Test
fun `WHEN updated with one normal tab and two search term tab THEN adapter have normal tab and a search group`() {
val adapter = ConcatAdapter(
InactiveTabsAdapter(context, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME),
TitleHeaderAdapter(),
BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME)
@ -102,7 +104,7 @@ class TabSorterTest {
@Test
fun `WHEN updated with one normal tab, one inactive tab and two search term tab THEN adapter have normal tab, inactive tab and a search group`() {
val adapter = ConcatAdapter(
InactiveTabsAdapter(context, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME),
TitleHeaderAdapter(),
BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME)
@ -145,7 +147,7 @@ class TabSorterTest {
fun `WHEN inactive tabs is off THEN adapter have no inactive tab`() {
every { settings.inactiveTabsAreEnabled }.answers { false }
val adapter = ConcatAdapter(
InactiveTabsAdapter(context, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME),
TitleHeaderAdapter(),
BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME)
@ -188,7 +190,7 @@ class TabSorterTest {
fun `WHEN search term tabs is off THEN adapter have no search term group`() {
every { settings.searchTermTabGroupsAreEnabled }.answers { false }
val adapter = ConcatAdapter(
InactiveTabsAdapter(context, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME),
TitleHeaderAdapter(),
BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME)
@ -232,7 +234,7 @@ class TabSorterTest {
every { settings.inactiveTabsAreEnabled }.answers { false }
every { settings.searchTermTabGroupsAreEnabled }.answers { false }
val adapter = ConcatAdapter(
InactiveTabsAdapter(context, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME),
TitleHeaderAdapter(),
BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME)
@ -273,7 +275,7 @@ class TabSorterTest {
@Test
fun `WHEN only one search term tab THEN there is no search group`() {
val adapter = ConcatAdapter(
InactiveTabsAdapter(context, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME),
TitleHeaderAdapter(),
BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME)
@ -300,7 +302,7 @@ class TabSorterTest {
@Test
fun `WHEN remove second last one search term tab THEN search group is kept even if there's only one tab`() {
val adapter = ConcatAdapter(
InactiveTabsAdapter(context, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings),
TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME),
TitleHeaderAdapter(),
BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME)

Loading…
Cancel
Save