For #21898 - Remove search term tab groups from the Tabs Tray
This commit is contained in:
parent
da4328e53f
commit
7e59a644d5
|
@ -281,7 +281,6 @@ class TabsTrayFragment : AppCompatDialogFragment() {
|
|||
tabsTrayStore
|
||||
),
|
||||
store = requireContext().components.core.store,
|
||||
defaultTabPartitionsFilter = { tabPartitions -> tabPartitions[SEARCH_TERM_TAB_GROUPS] }
|
||||
),
|
||||
owner = this,
|
||||
view = view
|
||||
|
|
|
@ -8,7 +8,6 @@ import androidx.annotation.VisibleForTesting
|
|||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
import org.mozilla.fenix.GleanMetrics.Metrics
|
||||
import org.mozilla.fenix.GleanMetrics.SearchTerms
|
||||
import org.mozilla.fenix.GleanMetrics.TabsTray
|
||||
|
||||
/**
|
||||
|
@ -17,7 +16,6 @@ import org.mozilla.fenix.GleanMetrics.TabsTray
|
|||
class TabsTrayMiddleware : Middleware<TabsTrayState, TabsTrayAction> {
|
||||
|
||||
private var shouldReportInactiveTabMetrics: Boolean = true
|
||||
private var shouldReportSearchGroupMetrics: Boolean = true
|
||||
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<TabsTrayState, TabsTrayAction>,
|
||||
|
@ -35,31 +33,6 @@ class TabsTrayMiddleware : Middleware<TabsTrayState, TabsTrayAction> {
|
|||
Metrics.inactiveTabsCount.set(action.tabs.size.toLong())
|
||||
}
|
||||
}
|
||||
is TabsTrayAction.UpdateTabPartitions -> {
|
||||
if (shouldReportSearchGroupMetrics) {
|
||||
shouldReportSearchGroupMetrics = false
|
||||
val tabGroups = action.tabPartition?.tabGroups ?: emptyList()
|
||||
|
||||
SearchTerms.numberOfSearchTermGroup.record(
|
||||
SearchTerms.NumberOfSearchTermGroupExtra(
|
||||
tabGroups.size.toString()
|
||||
)
|
||||
)
|
||||
|
||||
if (tabGroups.isNotEmpty()) {
|
||||
val tabsPerGroup = tabGroups.map { it.tabIds.size }
|
||||
val averageTabsPerGroup = tabsPerGroup.average()
|
||||
SearchTerms.averageTabsPerGroup.record(
|
||||
SearchTerms.AverageTabsPerGroupExtra(
|
||||
averageTabsPerGroup.toString()
|
||||
)
|
||||
)
|
||||
|
||||
val tabGroupSizeMapping = tabsPerGroup.map { generateTabGroupSizeMappedValue(it) }
|
||||
SearchTerms.groupSizeDistribution.accumulateSamples(tabGroupSizeMapping.toList())
|
||||
}
|
||||
}
|
||||
}
|
||||
is TabsTrayAction.EnterSelectMode -> {
|
||||
TabsTray.enterMultiselectMode.record(TabsTray.EnterMultiselectModeExtra(false))
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package org.mozilla.fenix.tabstray
|
||||
|
||||
import mozilla.components.browser.state.state.ContentState
|
||||
import mozilla.components.browser.state.state.TabPartition
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.lib.state.Action
|
||||
import mozilla.components.lib.state.Middleware
|
||||
|
@ -20,7 +19,6 @@ import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsListItem
|
|||
* @property mode Whether the browser tab list is in multi-select mode or not with the set of
|
||||
* currently selected tabs.
|
||||
* @property inactiveTabs The list of tabs are considered inactive.
|
||||
* @property searchTermPartition The tab partition for search term groups.
|
||||
* @property normalTabs The list of normal tabs that do not fall under [inactiveTabs] or search term groups.
|
||||
* @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.
|
||||
|
@ -30,7 +28,6 @@ data class TabsTrayState(
|
|||
val selectedPage: Page = Page.NormalTabs,
|
||||
val mode: Mode = Mode.Normal,
|
||||
val inactiveTabs: List<TabSessionState> = emptyList(),
|
||||
val searchTermPartition: TabPartition? = null,
|
||||
val normalTabs: List<TabSessionState> = emptyList(),
|
||||
val privateTabs: List<TabSessionState> = emptyList(),
|
||||
val syncedTabs: List<SyncedTabsListItem> = emptyList(),
|
||||
|
@ -143,11 +140,6 @@ sealed class TabsTrayAction : Action {
|
|||
*/
|
||||
data class UpdateInactiveTabs(val tabs: List<TabSessionState>) : TabsTrayAction()
|
||||
|
||||
/**
|
||||
* Updates the list of tab groups in [TabsTrayState.searchTermPartition].
|
||||
*/
|
||||
data class UpdateTabPartitions(val tabPartition: TabPartition?) : TabsTrayAction()
|
||||
|
||||
/**
|
||||
* Updates the list of tabs in [TabsTrayState.normalTabs].
|
||||
*/
|
||||
|
@ -196,8 +188,6 @@ internal object TabsTrayReducer {
|
|||
state.copy(focusGroupTabId = null)
|
||||
is TabsTrayAction.UpdateInactiveTabs ->
|
||||
state.copy(inactiveTabs = action.tabs)
|
||||
is TabsTrayAction.UpdateTabPartitions ->
|
||||
state.copy(searchTermPartition = action.tabPartition)
|
||||
is TabsTrayAction.UpdateNormalTabs ->
|
||||
state.copy(normalTabs = action.tabs)
|
||||
is TabsTrayAction.UpdatePrivateTabs ->
|
||||
|
|
|
@ -18,8 +18,6 @@ import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter
|
|||
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
|
||||
import org.mozilla.fenix.tabstray.browser.InactiveTabsAdapter
|
||||
import org.mozilla.fenix.tabstray.browser.InactiveTabsInteractor
|
||||
import org.mozilla.fenix.tabstray.browser.TabGroupAdapter
|
||||
import org.mozilla.fenix.tabstray.browser.TitleHeaderAdapter
|
||||
import org.mozilla.fenix.tabstray.viewholders.AbstractPageViewHolder
|
||||
import org.mozilla.fenix.tabstray.viewholders.NormalBrowserPageViewHolder
|
||||
import org.mozilla.fenix.tabstray.viewholders.PrivateBrowserPageViewHolder
|
||||
|
@ -51,8 +49,6 @@ class TrayPagerAdapter(
|
|||
inactiveTabsInteractor = inactiveTabsInteractor,
|
||||
featureName = INACTIVE_TABS_FEATURE_NAME,
|
||||
),
|
||||
TabGroupAdapter(context, browserInteractor, tabsTrayStore, TAB_GROUP_FEATURE_NAME, lifecycleOwner),
|
||||
TitleHeaderAdapter(),
|
||||
BrowserTabsAdapter(context, browserInteractor, tabsTrayStore, TABS_TRAY_FEATURE_NAME, lifecycleOwner)
|
||||
)
|
||||
}
|
||||
|
@ -139,7 +135,6 @@ class TrayPagerAdapter(
|
|||
|
||||
// Telemetry keys for identifying from which app features the a was opened / closed.
|
||||
const val TABS_TRAY_FEATURE_NAME = "Tabs tray"
|
||||
const val TAB_GROUP_FEATURE_NAME = "Tab group"
|
||||
const val INACTIVE_TABS_FEATURE_NAME = "Inactive tabs"
|
||||
|
||||
val POSITION_NORMAL_TABS = Page.NormalTabs.ordinal
|
||||
|
|
|
@ -37,7 +37,6 @@ import org.mozilla.fenix.ext.components
|
|||
import org.mozilla.fenix.ext.increaseTapArea
|
||||
import org.mozilla.fenix.ext.removeAndDisable
|
||||
import org.mozilla.fenix.ext.removeTouchDelegate
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.ext.showAndEnable
|
||||
import org.mozilla.fenix.ext.toShortUrl
|
||||
import org.mozilla.fenix.selection.SelectionHolder
|
||||
|
@ -245,11 +244,8 @@ abstract class AbstractBrowserTabViewHolder(
|
|||
val touchStart = touchStartPoint
|
||||
val selected = holder.selectedItems
|
||||
val selectsOnlyThis = (selected.size == 1 && selected.contains(item))
|
||||
val featureEnabled = FeatureFlags.tabReorderingFeature &&
|
||||
!itemView.context.settings().searchTermTabGroupsAreEnabled
|
||||
if (featureEnabled && selectsOnlyThis && touchStart != null) {
|
||||
// In a tab group, we do not use a AbstractBrowserTrayList as the parent,
|
||||
// so we should return early and mark the event as unhandled (return false).
|
||||
if (FeatureFlags.tabReorderingFeature && selectsOnlyThis && touchStart != null) {
|
||||
// If the parent is null then return early and mark the event as unhandled
|
||||
val parent = itemView.parent as? AbstractBrowserTrayList ?: return@setOnTouchListener false
|
||||
|
||||
// Prevent scrolling if the user tries to start drag vertically
|
||||
|
|
|
@ -10,8 +10,6 @@ import androidx.recyclerview.widget.ConcatAdapter
|
|||
import mozilla.components.browser.tabstray.TabViewHolder
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.tabstray.ext.browserAdapter
|
||||
import org.mozilla.fenix.tabstray.ext.tabGroupAdapter
|
||||
import org.mozilla.fenix.tabstray.ext.titleHeaderAdapter
|
||||
|
||||
class NormalBrowserTrayList @JvmOverloads constructor(
|
||||
context: Context,
|
||||
|
@ -25,14 +23,6 @@ class NormalBrowserTrayList @JvmOverloads constructor(
|
|||
NormalTabsBinding(tabsTrayStore, context.components.core.store, concatAdapter.browserAdapter)
|
||||
}
|
||||
|
||||
private val titleHeaderBinding by lazy {
|
||||
OtherHeaderBinding(tabsTrayStore) { concatAdapter.titleHeaderAdapter.handleListChanges(it) }
|
||||
}
|
||||
|
||||
private val tabGroupBinding by lazy {
|
||||
TabGroupBinding(tabsTrayStore) { concatAdapter.tabGroupAdapter.submitList(it) }
|
||||
}
|
||||
|
||||
private val touchHelper by lazy {
|
||||
TabsTouchHelper(
|
||||
interactionDelegate = concatAdapter.browserAdapter.interactor,
|
||||
|
@ -48,8 +38,6 @@ class NormalBrowserTrayList @JvmOverloads constructor(
|
|||
super.onAttachedToWindow()
|
||||
|
||||
normalTabsBinding.start()
|
||||
titleHeaderBinding.start()
|
||||
tabGroupBinding.start()
|
||||
|
||||
touchHelper.attachToRecyclerView(this)
|
||||
}
|
||||
|
@ -58,8 +46,6 @@ class NormalBrowserTrayList @JvmOverloads constructor(
|
|||
super.onDetachedFromWindow()
|
||||
|
||||
normalTabsBinding.stop()
|
||||
titleHeaderBinding.stop()
|
||||
tabGroupBinding.stop()
|
||||
|
||||
touchHelper.attachToRecyclerView(null)
|
||||
}
|
||||
|
|
|
@ -22,10 +22,9 @@ class NormalTabsBinding(
|
|||
private val tabsTray: TabsTray
|
||||
) : AbstractBinding<TabsTrayState>(store) {
|
||||
override suspend fun onState(flow: Flow<TabsTrayState>) {
|
||||
flow.ifChanged { Pair(it.normalTabs, it.searchTermPartition) }
|
||||
flow.ifChanged { it.normalTabs }
|
||||
.collect {
|
||||
// Getting the selectedTabId from the BrowserStore at a different time might lead to a race.
|
||||
tabsTray.updateTabs(it.normalTabs, it.searchTermPartition, browserStore.state.selectedTabId)
|
||||
tabsTray.updateTabs(it.normalTabs, null, browserStore.state.selectedTabId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/* 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.browser
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import mozilla.components.browser.state.state.isNotEmpty
|
||||
import mozilla.components.lib.state.helpers.AbstractBinding
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
||||
import org.mozilla.fenix.tabstray.TabsTrayState
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
|
||||
/**
|
||||
* A tabs observer that informs [showHeader] if an "Other tabs" title should be displayed in the tray.
|
||||
*/
|
||||
class OtherHeaderBinding(
|
||||
store: TabsTrayStore,
|
||||
private val showHeader: (Boolean) -> Unit
|
||||
) : AbstractBinding<TabsTrayState>(store) {
|
||||
override suspend fun onState(flow: Flow<TabsTrayState>) {
|
||||
flow.ifChanged { Pair(it.normalTabs, it.searchTermPartition) }
|
||||
.collect {
|
||||
if (
|
||||
it.normalTabs.isNotEmpty() &&
|
||||
it.searchTermPartition.isNotEmpty()
|
||||
) {
|
||||
showHeader(true)
|
||||
} else {
|
||||
showHeader(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
/* 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.browser
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.HORIZONTAL
|
||||
import androidx.recyclerview.widget.RecyclerView.VERTICAL
|
||||
import mozilla.components.browser.state.state.TabGroup
|
||||
import mozilla.components.browser.state.state.TabPartition
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.browser.tabstray.TabsTray
|
||||
import org.mozilla.fenix.components.Components
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.selection.SelectionHolder
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
|
||||
/**
|
||||
* The [ListAdapter] for displaying the list of search term tabs.
|
||||
*
|
||||
* @param context [Context] used for various platform interactions or accessing [Components]
|
||||
* @param browserTrayInteractor [BrowserTrayInteractor] handling tabs interactions in a tab tray.
|
||||
* @param featureName [String] representing the name of the feature displaying tabs. Used in telemetry reporting.
|
||||
* @param viewLifecycleOwner [LifecycleOwner] life cycle owner for the view.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
class TabGroupAdapter(
|
||||
private val context: Context,
|
||||
private val browserTrayInteractor: BrowserTrayInteractor,
|
||||
private val store: TabsTrayStore,
|
||||
override val featureName: String,
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : ListAdapter<TabGroup, TabGroupViewHolder>(DiffCallback), TabsTray, FeatureNameHolder {
|
||||
|
||||
/**
|
||||
* Tracks the selected tabs in multi-select mode.
|
||||
*/
|
||||
var selectionHolder: SelectionHolder<TabSessionState>? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabGroupViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||
|
||||
val orientation = if (context.components.settings.gridTabView) {
|
||||
HORIZONTAL
|
||||
} else {
|
||||
VERTICAL
|
||||
}
|
||||
return TabGroupViewHolder(
|
||||
view,
|
||||
orientation,
|
||||
browserTrayInteractor,
|
||||
store,
|
||||
selectionHolder,
|
||||
viewLifecycleOwner
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TabGroupViewHolder, position: Int) {
|
||||
val group = getItem(position)
|
||||
holder.bind(group)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int) = TabGroupViewHolder.LAYOUT_ID
|
||||
|
||||
/**
|
||||
* Notify the nested [RecyclerView] when this view has been attached.
|
||||
*/
|
||||
override fun onViewAttachedToWindow(holder: TabGroupViewHolder) {
|
||||
holder.rebind()
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the nested [RecyclerView] when this view has been detached.
|
||||
*/
|
||||
override fun onViewDetachedFromWindow(holder: TabGroupViewHolder) {
|
||||
holder.unbind()
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented; implementation is handled [List<Tab>.toSearchGroups]
|
||||
*/
|
||||
override fun updateTabs(tabs: List<TabSessionState>, tabPartition: TabPartition?, selectedTabId: String?) =
|
||||
throw UnsupportedOperationException("Use submitList instead.")
|
||||
|
||||
private object DiffCallback : DiffUtil.ItemCallback<TabGroup>() {
|
||||
override fun areItemsTheSame(oldItem: TabGroup, newItem: TabGroup) = oldItem.id == newItem.id
|
||||
override fun areContentsTheSame(oldItem: TabGroup, newItem: TabGroup) = oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TabGroup.containsTabId(tabId: String): Boolean {
|
||||
return tabIds.contains(tabId)
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/* 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.browser
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.map
|
||||
import mozilla.components.browser.state.state.TabGroup
|
||||
import mozilla.components.lib.state.helpers.AbstractBinding
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
||||
import org.mozilla.fenix.tabstray.TabsTrayState
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
|
||||
/**
|
||||
* A search-term tab group observer that updates the provided [tray].
|
||||
*/
|
||||
class TabGroupBinding(
|
||||
store: TabsTrayStore,
|
||||
private val tray: (List<TabGroup>) -> Unit
|
||||
) : AbstractBinding<TabsTrayState>(store) {
|
||||
override suspend fun onState(flow: Flow<TabsTrayState>) {
|
||||
flow.map { it.searchTermPartition?.tabGroups ?: emptyList() }
|
||||
.ifChanged()
|
||||
.collect {
|
||||
tray.invoke(it.filter { tabGroup -> tabGroup.tabIds.isNotEmpty() })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,183 +0,0 @@
|
|||
/* 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.browser
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.browser.tabstray.SelectableTabViewHolder
|
||||
import mozilla.components.browser.tabstray.TabsAdapter.Companion.PAYLOAD_DONT_HIGHLIGHT_SELECTED_ITEM
|
||||
import mozilla.components.browser.tabstray.TabsAdapter.Companion.PAYLOAD_HIGHLIGHT_SELECTED_ITEM
|
||||
import mozilla.components.browser.tabstray.TabsTrayStyling
|
||||
import mozilla.components.browser.thumbnails.loader.ThumbnailLoader
|
||||
import org.mozilla.fenix.FeatureFlags
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.databinding.TabTrayGridItemBinding
|
||||
import org.mozilla.fenix.databinding.TabTrayItemBinding
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.home.topsites.dpToPx
|
||||
import org.mozilla.fenix.selection.SelectionHolder
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
import org.mozilla.fenix.tabstray.browser.compose.ComposeListViewHolder
|
||||
import org.mozilla.fenix.tabstray.ext.MIN_COLUMN_WIDTH_DP
|
||||
|
||||
/**
|
||||
* The [ListAdapter] for displaying the list of tabs that have the same search term.
|
||||
*
|
||||
* @param context [Context] used for various platform interactions or accessing [Components]
|
||||
* @param interactor [BrowserTrayInteractor] handling tabs interactions in a tab tray.
|
||||
* @param store [TabsTrayStore] containing the complete state of tabs tray and methods to update that.
|
||||
* @param featureName [String] representing the name of the feature displaying tabs. Used in telemetry reporting.
|
||||
* @param viewLifecycleOwner [LifecycleOwner] life cycle owner for the view.
|
||||
*/
|
||||
class TabGroupListAdapter(
|
||||
private val context: Context,
|
||||
private val interactor: BrowserTrayInteractor,
|
||||
private val store: TabsTrayStore,
|
||||
private val selectionHolder: SelectionHolder<TabSessionState>?,
|
||||
private val featureName: String,
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : ListAdapter<TabSessionState, SelectableTabViewHolder>(DiffCallback) {
|
||||
|
||||
private val selectedItemAdapterBinding = SelectedItemAdapterBinding(store, this)
|
||||
private val imageLoader = ThumbnailLoader(context.components.core.thumbnailStorage)
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): SelectableTabViewHolder {
|
||||
return when {
|
||||
context.components.settings.gridTabView -> {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.tab_tray_grid_item, parent, false)
|
||||
view.layoutParams.width = view.dpToPx(MIN_COLUMN_WIDTH_DP.toFloat())
|
||||
BrowserTabViewHolder.GridViewHolder(imageLoader, interactor, store, selectionHolder, view, featureName)
|
||||
}
|
||||
else -> {
|
||||
if (FeatureFlags.composeTabsTray) {
|
||||
ComposeListViewHolder(
|
||||
interactor = interactor,
|
||||
tabsTrayStore = store,
|
||||
selectionHolder = selectionHolder,
|
||||
composeItemView = ComposeView(parent.context),
|
||||
featureName = featureName,
|
||||
viewLifecycleOwner = viewLifecycleOwner
|
||||
)
|
||||
} else {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.tab_tray_item, parent, false)
|
||||
BrowserTabViewHolder.ListViewHolder(
|
||||
imageLoader,
|
||||
interactor,
|
||||
store,
|
||||
selectionHolder,
|
||||
view,
|
||||
featureName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SelectableTabViewHolder, position: Int) {
|
||||
val tab = getItem(position)
|
||||
val selectedTabId = context.components.core.store.state.selectedTabId
|
||||
holder.bind(tab, tab.id == selectedTabId, TabsTrayStyling(), interactor)
|
||||
holder.tab?.let { holderTab ->
|
||||
when {
|
||||
context.components.settings.gridTabView -> {
|
||||
val gridBinding = TabTrayGridItemBinding.bind(holder.itemView)
|
||||
gridBinding.mozacBrowserTabstrayClose.setOnClickListener {
|
||||
interactor.close(holderTab, featureName)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val listBinding = TabTrayItemBinding.bind(holder.itemView)
|
||||
listBinding.mozacBrowserTabstrayClose.setOnClickListener {
|
||||
interactor.close(holderTab, featureName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Over-ridden [onBindViewHolder] that uses the payloads to notify the selected tab how to
|
||||
* display itself.
|
||||
*
|
||||
* N.B: this is a modified version of [BrowserTabsAdapter.onBindViewHolder].
|
||||
*/
|
||||
override fun onBindViewHolder(holder: SelectableTabViewHolder, position: Int, payloads: List<Any>) {
|
||||
val tabs = currentList
|
||||
val selectedTabId = context.components.core.store.state.selectedTabId
|
||||
val selectedIndex = tabs.indexOfFirst { it.id == selectedTabId }
|
||||
|
||||
if (tabs.isEmpty()) return
|
||||
|
||||
if (payloads.isEmpty()) {
|
||||
onBindViewHolder(holder, position)
|
||||
return
|
||||
}
|
||||
|
||||
if (position == selectedIndex) {
|
||||
if (payloads.contains(PAYLOAD_HIGHLIGHT_SELECTED_ITEM)) {
|
||||
holder.updateSelectedTabIndicator(true)
|
||||
} else if (payloads.contains(PAYLOAD_DONT_HIGHLIGHT_SELECTED_ITEM)) {
|
||||
holder.updateSelectedTabIndicator(false)
|
||||
}
|
||||
}
|
||||
|
||||
selectionHolder?.let {
|
||||
var selectedMaskView: View? = null
|
||||
when (getItemViewType(position)) {
|
||||
BrowserTabsAdapter.ViewType.GRID.layoutRes -> {
|
||||
val gridBinding = TabTrayGridItemBinding.bind(holder.itemView)
|
||||
selectedMaskView = gridBinding.checkboxInclude.selectedMask
|
||||
}
|
||||
BrowserTabsAdapter.ViewType.LIST.layoutRes -> {
|
||||
val listBinding = TabTrayItemBinding.bind(holder.itemView)
|
||||
selectedMaskView = listBinding.checkboxInclude.selectedMask
|
||||
}
|
||||
}
|
||||
holder.showTabIsMultiSelectEnabled(
|
||||
selectedMaskView,
|
||||
it.selectedItems.contains(holder.tab)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when {
|
||||
context.components.settings.gridTabView -> {
|
||||
BrowserTabsAdapter.ViewType.GRID.layoutRes
|
||||
}
|
||||
else -> {
|
||||
if (FeatureFlags.composeTabsTray) {
|
||||
BrowserTabsAdapter.ViewType.COMPOSE_LIST.layoutRes
|
||||
} else {
|
||||
BrowserTabsAdapter.ViewType.LIST.layoutRes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
selectedItemAdapterBinding.start()
|
||||
}
|
||||
|
||||
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||
selectedItemAdapterBinding.stop()
|
||||
}
|
||||
|
||||
private object DiffCallback : DiffUtil.ItemCallback<TabSessionState>() {
|
||||
override fun areItemsTheSame(oldItem: TabSessionState, newItem: TabSessionState) = oldItem.id == newItem.id
|
||||
override fun areContentsTheSame(oldItem: TabSessionState, newItem: TabSessionState) = oldItem == newItem
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
/* 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.browser
|
||||
|
||||
import android.view.View
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import mozilla.components.browser.state.selector.normalTabs
|
||||
import mozilla.components.browser.state.state.TabGroup
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.databinding.TabGroupItemBinding
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.selection.SelectionHolder
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
import org.mozilla.fenix.tabstray.TrayPagerAdapter
|
||||
|
||||
/**
|
||||
* A RecyclerView ViewHolder implementation for tab group items.
|
||||
*
|
||||
* @param itemView [View] that displays a "tab".
|
||||
* @param orientation [Int] orientation of the items. Horizontal for grid layout, vertical for list layout
|
||||
* @param interactor the [BrowserTrayInteractor] for tab interactions.
|
||||
* @param store the [TabsTrayStore] instance.
|
||||
* @param selectionHolder the store that holds the currently selected tabs.
|
||||
* @param viewLifecycleOwner [LifecycleOwner] life cycle owner for the view.
|
||||
*/
|
||||
class TabGroupViewHolder(
|
||||
itemView: View,
|
||||
val orientation: Int,
|
||||
val interactor: BrowserTrayInteractor,
|
||||
val store: TabsTrayStore,
|
||||
val selectionHolder: SelectionHolder<TabSessionState>? = null,
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : RecyclerView.ViewHolder(itemView) {
|
||||
private val binding = TabGroupItemBinding.bind(itemView)
|
||||
|
||||
private lateinit var groupListAdapter: TabGroupListAdapter
|
||||
|
||||
fun bind(
|
||||
tabGroup: TabGroup,
|
||||
) {
|
||||
val selectedTabId = itemView.context.components.core.store.state.selectedTabId
|
||||
val selectedIndex = tabGroup.tabIds.indexOfFirst { it == selectedTabId }
|
||||
|
||||
binding.tabGroupTitle.text = tabGroup.id
|
||||
binding.tabGroupList.apply {
|
||||
layoutManager = LinearLayoutManager(itemView.context, orientation, false)
|
||||
groupListAdapter = TabGroupListAdapter(
|
||||
context = itemView.context,
|
||||
interactor = interactor,
|
||||
store = store,
|
||||
selectionHolder = selectionHolder,
|
||||
featureName = TrayPagerAdapter.TAB_GROUP_FEATURE_NAME,
|
||||
viewLifecycleOwner
|
||||
)
|
||||
|
||||
adapter = groupListAdapter
|
||||
|
||||
val tabGroupTabs = itemView.context.components.core.store.state.normalTabs.filter {
|
||||
tabGroup.tabIds.contains(it.id)
|
||||
}
|
||||
|
||||
groupListAdapter.submitList(tabGroupTabs)
|
||||
scrollToPosition(selectedIndex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the nested [RecyclerView] that it has been detached.
|
||||
*/
|
||||
fun unbind() {
|
||||
groupListAdapter.onDetachedFromRecyclerView(binding.tabGroupList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the nested [RecyclerView] that it has been attached. This is so our observers know when to start again.
|
||||
*/
|
||||
fun rebind() {
|
||||
groupListAdapter.onAttachedToRecyclerView(binding.tabGroupList)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.tab_group_item
|
||||
}
|
||||
}
|
|
@ -4,13 +4,11 @@
|
|||
|
||||
package org.mozilla.fenix.tabstray.browser
|
||||
|
||||
import mozilla.components.browser.state.state.TabGroup
|
||||
import mozilla.components.browser.state.state.TabPartition
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.browser.tabstray.TabsTray
|
||||
import mozilla.components.feature.tabs.tabstray.TabsFeature
|
||||
import org.mozilla.fenix.ext.maxActiveTime
|
||||
import org.mozilla.fenix.tabstray.SEARCH_TERM_TAB_GROUPS_MIN_SIZE
|
||||
import org.mozilla.fenix.tabstray.TabsTrayAction
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
import org.mozilla.fenix.tabstray.ext.isActive
|
||||
|
@ -23,16 +21,12 @@ class TabSorter(
|
|||
private val settings: Settings,
|
||||
private val tabsTrayStore: TabsTrayStore? = null
|
||||
) : TabsTray {
|
||||
private val groupsSet = mutableSetOf<String>()
|
||||
|
||||
override fun updateTabs(tabs: List<TabSessionState>, tabPartition: TabPartition?, selectedTabId: String?) {
|
||||
val privateTabs = tabs.filter { it.content.private }
|
||||
val allNormalTabs = tabs - privateTabs
|
||||
val inactiveTabs = allNormalTabs.getInactiveTabs(settings)
|
||||
val tabGroups = tabPartition?.getTabGroups(settings, groupsSet) ?: emptyList()
|
||||
val tabGroupTabIds = tabGroups.flatMap { it.tabIds }
|
||||
val normalTabs = (allNormalTabs - inactiveTabs).filterNot { tabGroupTabIds.contains(it.id) }
|
||||
val minTabPartition = tabPartition?.let { TabPartition(tabPartition.id, tabGroups) }
|
||||
val normalTabs = allNormalTabs - inactiveTabs
|
||||
|
||||
// Private tabs
|
||||
tabsTrayStore?.dispatch(TabsTrayAction.UpdatePrivateTabs(privateTabs))
|
||||
|
@ -42,12 +36,6 @@ class TabSorter(
|
|||
|
||||
// Normal tabs
|
||||
tabsTrayStore?.dispatch(TabsTrayAction.UpdateNormalTabs(normalTabs))
|
||||
|
||||
// Search term tabs
|
||||
tabsTrayStore?.dispatch(TabsTrayAction.UpdateTabPartitions(minTabPartition))
|
||||
|
||||
groupsSet.clear()
|
||||
groupsSet.addAll(tabGroups.map { it.id })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,16 +50,3 @@ private fun List<TabSessionState>.getInactiveTabs(settings: Settings): List<TabS
|
|||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of tab groups based on our preferences.
|
||||
*/
|
||||
private fun TabPartition.getTabGroups(settings: Settings, groupsSet: Set<String>): List<TabGroup> {
|
||||
return if (settings.searchTermTabGroupsAreEnabled) {
|
||||
tabGroups.filter {
|
||||
it.tabIds.size >= SEARCH_TERM_TAB_GROUPS_MIN_SIZE || groupsSet.contains(it.id)
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/* 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.browser
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.databinding.TabTrayTitleHeaderItemBinding
|
||||
|
||||
/**
|
||||
* A [RecyclerView.Adapter] for tab header.
|
||||
*/
|
||||
class TitleHeaderAdapter :
|
||||
ListAdapter<TitleHeaderAdapter.Header, TitleHeaderAdapter.HeaderViewHolder>(DiffCallback) {
|
||||
|
||||
class Header
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||
return HeaderViewHolder(view)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int) = HeaderViewHolder.LAYOUT_ID
|
||||
|
||||
/* Do nothing */
|
||||
override fun onBindViewHolder(holder: HeaderViewHolder, position: Int) = Unit
|
||||
|
||||
fun handleListChanges(showHeader: Boolean) {
|
||||
val header = if (showHeader) {
|
||||
listOf(Header())
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
submitList(header)
|
||||
}
|
||||
|
||||
class HeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val binding = TabTrayTitleHeaderItemBinding.bind(itemView)
|
||||
|
||||
fun bind() {
|
||||
binding.tabTrayHeaderTitle.text =
|
||||
itemView.context.getString(R.string.tab_tray_header_title_1)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.tab_tray_title_header_item
|
||||
}
|
||||
}
|
||||
|
||||
private object DiffCallback : DiffUtil.ItemCallback<Header>() {
|
||||
override fun areItemsTheSame(oldItem: Header, newItem: Header) = true
|
||||
override fun areContentsTheSame(oldItem: Header, newItem: Header) = true
|
||||
}
|
||||
}
|
|
@ -6,9 +6,7 @@ package org.mozilla.fenix.tabstray.ext
|
|||
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter
|
||||
import org.mozilla.fenix.tabstray.browser.TitleHeaderAdapter
|
||||
import org.mozilla.fenix.tabstray.browser.InactiveTabsAdapter
|
||||
import org.mozilla.fenix.tabstray.browser.TabGroupAdapter
|
||||
|
||||
/**
|
||||
* A convenience binding for retrieving the [BrowserTabsAdapter] from the [ConcatAdapter].
|
||||
|
@ -21,15 +19,3 @@ internal val ConcatAdapter.browserAdapter
|
|||
*/
|
||||
internal val ConcatAdapter.inactiveTabsAdapter
|
||||
get() = adapters.find { it is InactiveTabsAdapter } as InactiveTabsAdapter
|
||||
|
||||
/**
|
||||
* A convenience binding for retrieving the [TabGroupAdapter] from the [ConcatAdapter].
|
||||
*/
|
||||
internal val ConcatAdapter.tabGroupAdapter
|
||||
get() = adapters.find { it is TabGroupAdapter } as TabGroupAdapter
|
||||
|
||||
/**
|
||||
* A convenience binding for retrieving the [TitleHeaderAdapter] from the [ConcatAdapter].
|
||||
*/
|
||||
internal val ConcatAdapter.titleHeaderAdapter
|
||||
get() = adapters.find { it is TitleHeaderAdapter } as TitleHeaderAdapter
|
||||
|
|
|
@ -7,11 +7,8 @@ package org.mozilla.fenix.tabstray.ext
|
|||
import mozilla.components.browser.state.selector.normalTabs
|
||||
import mozilla.components.browser.state.selector.privateTabs
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.browser.state.state.TabGroup
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import org.mozilla.fenix.ext.maxActiveTime
|
||||
import org.mozilla.fenix.tabstray.SEARCH_TERM_TAB_GROUPS
|
||||
import org.mozilla.fenix.tabstray.SEARCH_TERM_TAB_GROUPS_MIN_SIZE
|
||||
|
||||
/**
|
||||
* The currently selected tab if there's one that is private.
|
||||
|
@ -38,24 +35,13 @@ fun BrowserState.findPrivateTab(tabId: String): TabSessionState? {
|
|||
* The list of normal tabs in the tabs tray filtered appropriately based on feature flags.
|
||||
*/
|
||||
fun BrowserState.getNormalTrayTabs(
|
||||
searchTermTabGroupsAreEnabled: Boolean,
|
||||
inactiveTabsEnabled: Boolean
|
||||
): List<TabSessionState> {
|
||||
val tabGroupsTabIds = getTabGroups()?.flatMap { it.tabIds } ?: emptyList()
|
||||
return normalTabs.run {
|
||||
if (searchTermTabGroupsAreEnabled && tabGroupsTabIds.isNotEmpty() && inactiveTabsEnabled) {
|
||||
filter { it.isNormalTabActive(maxActiveTime) }.filter { tabGroupsTabIds.contains(it.id) }
|
||||
} else if (inactiveTabsEnabled) {
|
||||
if (inactiveTabsEnabled) {
|
||||
filter { it.isNormalTabActive(maxActiveTime) }
|
||||
} else if (searchTermTabGroupsAreEnabled && tabGroupsTabIds.isNotEmpty()) {
|
||||
filter { it.isNormalTab() }.filter { tabGroupsTabIds.contains(it.id) }
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BrowserState.getTabGroups(): List<TabGroup>? {
|
||||
return tabPartitions[SEARCH_TERM_TAB_GROUPS]?.tabGroups
|
||||
?.filter { it.tabIds.size >= SEARCH_TERM_TAB_GROUPS_MIN_SIZE }
|
||||
}
|
||||
|
|
|
@ -23,19 +23,14 @@ import org.mozilla.fenix.ext.maxActiveTime
|
|||
import org.mozilla.fenix.ext.potentialInactiveTabs
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.selection.SelectionHolder
|
||||
import org.mozilla.fenix.tabstray.TabsTrayAction
|
||||
import org.mozilla.fenix.tabstray.TabsTrayInteractor
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
import org.mozilla.fenix.tabstray.browser.containsTabId
|
||||
import org.mozilla.fenix.tabstray.ext.browserAdapter
|
||||
import org.mozilla.fenix.tabstray.ext.defaultBrowserLayoutColumns
|
||||
import org.mozilla.fenix.tabstray.ext.getNormalTrayTabs
|
||||
import org.mozilla.fenix.tabstray.ext.inactiveTabsAdapter
|
||||
import org.mozilla.fenix.tabstray.ext.isNormalTabActiveWithSearchTerm
|
||||
import org.mozilla.fenix.tabstray.ext.isNormalTabInactive
|
||||
import org.mozilla.fenix.tabstray.ext.observeFirstInsert
|
||||
import org.mozilla.fenix.tabstray.ext.tabGroupAdapter
|
||||
import org.mozilla.fenix.tabstray.ext.titleHeaderAdapter
|
||||
|
||||
/**
|
||||
* View holder for the normal tabs tray list.
|
||||
|
@ -68,10 +63,8 @@ class NormalBrowserPageViewHolder(
|
|||
) {
|
||||
val concatAdapter = adapter as ConcatAdapter
|
||||
val browserAdapter = concatAdapter.browserAdapter
|
||||
val tabGroupAdapter = concatAdapter.tabGroupAdapter
|
||||
val manager = setupLayoutManager(containerView.context, concatAdapter)
|
||||
browserAdapter.selectionHolder = this
|
||||
tabGroupAdapter.selectionHolder = this
|
||||
|
||||
observeTabsTrayInactiveTabsState(adapter)
|
||||
|
||||
|
@ -86,12 +79,9 @@ class NormalBrowserPageViewHolder(
|
|||
layoutManager: RecyclerView.LayoutManager
|
||||
) {
|
||||
val concatAdapter = adapter as ConcatAdapter
|
||||
val headerAdapter = concatAdapter.titleHeaderAdapter
|
||||
val browserAdapter = concatAdapter.browserAdapter
|
||||
val inactiveTabAdapter = concatAdapter.inactiveTabsAdapter
|
||||
val tabGroupAdapter = concatAdapter.tabGroupAdapter
|
||||
val inactiveTabsAreEnabled = containerView.context.settings().inactiveTabsAreEnabled
|
||||
val searchTermTabGroupsAreEnabled = containerView.context.settings().searchTermTabGroupsAreEnabled
|
||||
|
||||
val selectedTab = browserStore.state.selectedNormalTab ?: return
|
||||
// It's safe to read the state directly (i.e. won't cause bugs because of the store actions
|
||||
|
@ -117,53 +107,14 @@ class NormalBrowserPageViewHolder(
|
|||
}
|
||||
}
|
||||
|
||||
// Updates tabs into the search term group adapter.
|
||||
if (searchTermTabGroupsAreEnabled && (
|
||||
!focusGroupTabId.isNullOrEmpty() ||
|
||||
selectedTab.isNormalTabActiveWithSearchTerm(maxActiveTime)
|
||||
)
|
||||
) {
|
||||
val tabId = focusGroupTabId ?: selectedTab.id
|
||||
|
||||
tabGroupAdapter.observeFirstInsert {
|
||||
// With a grouping, we need to use the list of the adapter that is already grouped
|
||||
// together for the UI, so we know the final index of the grouping to scroll to.
|
||||
//
|
||||
// N.B: Why are we using currentList here and no where else? `currentList` is an API on top of
|
||||
// `ListAdapter` which is updated when the [ListAdapter.submitList] is invoked. For our BrowserAdapter
|
||||
// as an example, the updates are coming from [TabsFeature] which internally uses the internal
|
||||
// [DiffUtil.calculateDiff] directly to submit a changed list which evades the `ListAdapter` from being
|
||||
// notified of updates, so it therefore returns an empty list.
|
||||
tabGroupAdapter.currentList.forEachIndexed { groupIndex, group ->
|
||||
if (group.containsTabId(tabId)) {
|
||||
|
||||
// Index is based on tabs above (inactive) with our calculated index.
|
||||
val indexToScrollTo = inactiveTabAdapter.itemCount + groupIndex
|
||||
containerView.post { layoutManager.scrollToPosition(indexToScrollTo) }
|
||||
|
||||
if (focusGroupTabId != null) {
|
||||
tabsTrayStore.dispatch(TabsTrayAction.ConsumeFocusGroupTabId)
|
||||
}
|
||||
return@observeFirstInsert
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (focusGroupTabId.isNullOrEmpty()) {
|
||||
// Updates tabs into the normal browser tabs adapter.
|
||||
browserAdapter.observeFirstInsert {
|
||||
val activeTabsList = browserStore.state.getNormalTrayTabs(
|
||||
searchTermTabGroupsAreEnabled,
|
||||
inactiveTabsAreEnabled
|
||||
)
|
||||
val activeTabsList = browserStore.state.getNormalTrayTabs(inactiveTabsAreEnabled)
|
||||
activeTabsList.forEachIndexed { tabIndex, trayTab ->
|
||||
if (trayTab.id == selectedTab.id) {
|
||||
|
||||
// Index is based on tabs above (inactive + groups + header) with our calculated index.
|
||||
val indexToScrollTo = inactiveTabAdapter.itemCount +
|
||||
tabGroupAdapter.itemCount +
|
||||
headerAdapter.itemCount + tabIndex
|
||||
// Index is based on tabs above (inactive) with our calculated index.
|
||||
val indexToScrollTo = inactiveTabAdapter.itemCount + tabIndex
|
||||
|
||||
containerView.post { layoutManager.scrollToPosition(indexToScrollTo) }
|
||||
|
||||
|
@ -196,17 +147,12 @@ class NormalBrowserPageViewHolder(
|
|||
context: Context,
|
||||
concatAdapter: ConcatAdapter
|
||||
): GridLayoutManager {
|
||||
val headerAdapter = concatAdapter.titleHeaderAdapter
|
||||
val inactiveTabAdapter = concatAdapter.inactiveTabsAdapter
|
||||
val tabGroupAdapter = concatAdapter.tabGroupAdapter
|
||||
|
||||
val numberOfColumns = containerView.context.defaultBrowserLayoutColumns
|
||||
return GridLayoutManager(context, numberOfColumns).apply {
|
||||
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
||||
override fun getSpanSize(position: Int): Int {
|
||||
return if (position >= inactiveTabAdapter.itemCount + tabGroupAdapter.itemCount +
|
||||
headerAdapter.itemCount
|
||||
) {
|
||||
return if (position >= inactiveTabAdapter.itemCount) {
|
||||
1
|
||||
} else {
|
||||
numberOfColumns
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
<!-- 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/. -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginBottom="15dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/group_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@color/fx_mobile_icon_color_primary"
|
||||
app:srcCompat="@drawable/ic_search" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tab_group_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="11dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="@style/Header16TextStyle"
|
||||
android:textColor="@color/fx_mobile_text_color_primary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/group_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/group_icon"
|
||||
app:layout_constraintTop_toTopOf="@id/group_icon"
|
||||
tools:text="Cats" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/tab_group_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tab_group_title" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,25 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/tab_tray_header_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:clickable="false"
|
||||
android:clipToPadding="false"
|
||||
android:ellipsize="end"
|
||||
android:focusable="false"
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:text="@string/tab_tray_header_title_1"
|
||||
android:textAppearance="@style/Header16TextStyle"
|
||||
android:textColor="@color/fx_mobile_text_color_primary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
|
@ -722,7 +722,7 @@
|
|||
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
|
||||
<string name="pwa_site_controls_title_private">%1$s (Private Mode)</string>
|
||||
<!-- Title text for the normal tabs header in the tabs tray which are not part of any tab grouping. -->
|
||||
<string name="tab_tray_header_title_1">Other tabs</string>
|
||||
<string name="tab_tray_header_title_1" moz:removedIn="104" tools:ignore="UnusedResources">Other tabs</string>
|
||||
|
||||
<!-- History -->
|
||||
<!-- Text for the button to search all history -->
|
||||
|
|
|
@ -4,10 +4,7 @@
|
|||
|
||||
package org.mozilla.fenix.tabstray
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import mozilla.components.browser.state.state.TabGroup
|
||||
import mozilla.components.browser.state.state.TabPartition
|
||||
import mozilla.components.service.glean.testing.GleanTestRule
|
||||
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
|
@ -19,7 +16,6 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.GleanMetrics.Metrics
|
||||
import org.mozilla.fenix.GleanMetrics.SearchTerms
|
||||
import org.mozilla.fenix.GleanMetrics.TabsTray
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
|
@ -41,45 +37,6 @@ class TabsTrayMiddlewareTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN search term groups are updated AND there is at least one group THEN report the average tabs per group`() {
|
||||
assertNull(SearchTerms.averageTabsPerGroup.testGetValue())
|
||||
|
||||
store.dispatch(TabsTrayAction.UpdateTabPartitions(generateSearchTermTabGroupsForAverage()))
|
||||
store.waitUntilIdle()
|
||||
|
||||
assertNotNull(SearchTerms.averageTabsPerGroup.testGetValue())
|
||||
val event = SearchTerms.averageTabsPerGroup.testGetValue()!!
|
||||
assertEquals(1, event.size)
|
||||
assertEquals("5.0", event.single().extra!!["count"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN search term groups are updated AND there is at least one group THEN report the distribution of tab sizes`() {
|
||||
assertNull(SearchTerms.groupSizeDistribution.testGetValue())
|
||||
|
||||
store.dispatch(TabsTrayAction.UpdateTabPartitions(generateSearchTermTabGroupsForDistribution()))
|
||||
store.waitUntilIdle()
|
||||
|
||||
assertNotNull(SearchTerms.groupSizeDistribution.testGetValue())
|
||||
val event = SearchTerms.groupSizeDistribution.testGetValue()!!.values
|
||||
// Verify the distribution correctly describes the tab group sizes
|
||||
assertEquals(mapOf(0L to 0L, 1L to 1L, 2L to 1L, 3L to 1L, 4L to 1L), event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN search term groups are updated THEN report the count of search term tab groups`() {
|
||||
assertNull(SearchTerms.numberOfSearchTermGroup.testGetValue())
|
||||
|
||||
store.dispatch(TabsTrayAction.UpdateTabPartitions(null))
|
||||
store.waitUntilIdle()
|
||||
|
||||
assertNotNull(SearchTerms.numberOfSearchTermGroup.testGetValue())
|
||||
val event = SearchTerms.numberOfSearchTermGroup.testGetValue()!!
|
||||
assertEquals(1, event.size)
|
||||
assertEquals("0", event.single().extra!!["count"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN inactive tabs are updated THEN report the count of inactive tabs`() {
|
||||
|
||||
|
@ -133,30 +90,4 @@ class TabsTrayMiddlewareTest {
|
|||
assertEquals(1, snapshot.size)
|
||||
assertEquals("true", snapshot.single().extra?.getValue("tab_selected"))
|
||||
}
|
||||
|
||||
private fun generateSearchTermTabGroupsForAverage(): TabPartition {
|
||||
val group1 = TabGroup("", "", mockk(relaxed = true))
|
||||
val group2 = TabGroup("", "", mockk(relaxed = true))
|
||||
val group3 = TabGroup("", "", mockk(relaxed = true))
|
||||
|
||||
every { group1.tabIds.size } returns 8
|
||||
every { group2.tabIds.size } returns 4
|
||||
every { group3.tabIds.size } returns 3
|
||||
|
||||
return TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(group1, group2, group3))
|
||||
}
|
||||
|
||||
private fun generateSearchTermTabGroupsForDistribution(): TabPartition {
|
||||
val group1 = TabGroup("", "", mockk(relaxed = true))
|
||||
val group2 = TabGroup("", "", mockk(relaxed = true))
|
||||
val group3 = TabGroup("", "", mockk(relaxed = true))
|
||||
val group4 = TabGroup("", "", mockk(relaxed = true))
|
||||
|
||||
every { group1.tabIds.size } returns 8
|
||||
every { group2.tabIds.size } returns 4
|
||||
every { group3.tabIds.size } returns 2
|
||||
every { group4.tabIds.size } returns 12
|
||||
|
||||
return TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(group1, group2, group3, group4))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
/* 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.browser
|
||||
|
||||
import io.mockk.mockk
|
||||
import mozilla.components.browser.state.state.TabGroup
|
||||
import mozilla.components.browser.state.state.TabPartition
|
||||
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.tabstray.SEARCH_TERM_TAB_GROUPS
|
||||
import org.mozilla.fenix.tabstray.TabsTrayState
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
|
||||
class OtherHeaderBindingTest {
|
||||
|
||||
@get:Rule
|
||||
val coroutinesTestRule = MainCoroutineRule()
|
||||
|
||||
@Test
|
||||
fun `WHEN there are no tabs THEN show no header`() {
|
||||
val store = TabsTrayStore()
|
||||
var result: Boolean? = null
|
||||
val binding = OtherHeaderBinding(store) { result = it }
|
||||
|
||||
binding.start()
|
||||
|
||||
store.waitUntilIdle()
|
||||
|
||||
assertFalse(result!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN tabs for only groups THEN show no header`() {
|
||||
val store = TabsTrayStore(TabsTrayState(searchTermPartition = mockk()))
|
||||
var result: Boolean? = null
|
||||
val binding = OtherHeaderBinding(store) { result = it }
|
||||
|
||||
binding.start()
|
||||
|
||||
store.waitUntilIdle()
|
||||
|
||||
assertFalse(result!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN tabs for only normal tabs THEN show no header`() {
|
||||
val store = TabsTrayStore(TabsTrayState(normalTabs = listOf(mockk())))
|
||||
var result: Boolean? = null
|
||||
val binding = OtherHeaderBinding(store) { result = it }
|
||||
|
||||
binding.start()
|
||||
|
||||
store.waitUntilIdle()
|
||||
|
||||
assertFalse(result!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN normal tabs and groups exist THEN show header`() {
|
||||
val tabGroup = TabGroup("test", "", listOf("1", "2"))
|
||||
val store = TabsTrayStore(
|
||||
TabsTrayState(
|
||||
normalTabs = listOf(mockk()),
|
||||
searchTermPartition = TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(tabGroup))
|
||||
)
|
||||
)
|
||||
var result = false
|
||||
val binding = OtherHeaderBinding(store) { result = it }
|
||||
|
||||
binding.start()
|
||||
|
||||
store.waitUntilIdle()
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
/* 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.browser
|
||||
|
||||
import mozilla.components.browser.state.state.TabGroup
|
||||
import mozilla.components.browser.state.state.TabPartition
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.support.test.ext.joinBlocking
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.tabstray.SEARCH_TERM_TAB_GROUPS
|
||||
import org.mozilla.fenix.tabstray.TabsTrayAction
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
|
||||
class TabGroupBindingTest {
|
||||
val store = TabsTrayStore()
|
||||
var captured: List<TabGroup>? = null
|
||||
val binding = TabGroupBinding(store) { captured = it }
|
||||
|
||||
@get:Rule
|
||||
val coroutinesTestRule = MainCoroutineRule()
|
||||
|
||||
@After
|
||||
fun teardown() {
|
||||
binding.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the store is updated THEN notify the adapter`() {
|
||||
val expectedTabGroups = listOf(TabGroup("cats", "name", listOf("1", "2")))
|
||||
val tabPartition = TabPartition(SEARCH_TERM_TAB_GROUPS, expectedTabGroups)
|
||||
|
||||
assertNull(store.state.searchTermPartition?.tabGroups)
|
||||
|
||||
store.dispatch(TabsTrayAction.UpdateTabPartitions(tabPartition)).joinBlocking()
|
||||
|
||||
binding.start()
|
||||
|
||||
assertTrue(store.state.searchTermPartition?.tabGroups?.isNotEmpty() == true)
|
||||
|
||||
assertEquals(expectedTabGroups, captured)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the store is updated with empty tab group THEN notify the adapter`() {
|
||||
val expectedTabPartition = TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(TabGroup("cats", "name", emptyList())))
|
||||
|
||||
assertNull(store.state.searchTermPartition?.tabGroups)
|
||||
|
||||
store.dispatch(TabsTrayAction.UpdateTabPartitions(expectedTabPartition)).joinBlocking()
|
||||
|
||||
binding.start()
|
||||
|
||||
assertTrue(store.state.searchTermPartition?.tabGroups?.isNotEmpty() == true)
|
||||
|
||||
assertEquals(emptyList<TabGroup>(), captured)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN non-group tabs are updated THEN do not notify the adapter`() {
|
||||
assertEquals(store.state.searchTermPartition?.tabGroups, null)
|
||||
|
||||
store.dispatch(TabsTrayAction.UpdatePrivateTabs(listOf(createTab("https://mozilla.org")))).joinBlocking()
|
||||
|
||||
binding.start()
|
||||
|
||||
assertNull(store.state.searchTermPartition?.tabGroups)
|
||||
|
||||
assertEquals(emptyList<TabGroup>(), captured)
|
||||
}
|
||||
}
|
|
@ -6,14 +6,11 @@ package org.mozilla.fenix.tabstray.browser
|
|||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import mozilla.components.browser.state.state.TabGroup
|
||||
import mozilla.components.browser.state.state.TabPartition
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.tabstray.SEARCH_TERM_TAB_GROUPS
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
|
@ -29,7 +26,7 @@ class TabSorterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN updated with one normal tab THEN adapter have only one normal tab and no header`() {
|
||||
fun `WHEN updated with one normal tab THEN adapter have only one normal tab`() {
|
||||
val tabSorter = TabSorter(settings, tabsTrayStore)
|
||||
|
||||
tabSorter.updateTabs(
|
||||
|
@ -43,52 +40,25 @@ class TabSorterTest {
|
|||
tabsTrayStore.waitUntilIdle()
|
||||
|
||||
assertEquals(tabsTrayStore.state.inactiveTabs.size, 0)
|
||||
assertEquals(tabsTrayStore.state.searchTermPartition?.tabGroups?.size, null)
|
||||
assertEquals(tabsTrayStore.state.normalTabs.size, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN updated with one normal tab and two search term tab THEN adapter have normal tab and a search group`() {
|
||||
fun `WHEN updated with one normal tab and one inactive tab THEN adapter have normal tab and inactive tab`() {
|
||||
val tabSorter = TabSorter(settings, tabsTrayStore)
|
||||
val searchTab1 = createTab(url = "url", id = "tab2", lastAccess = System.currentTimeMillis(), searchTerms = "mozilla")
|
||||
val searchTab2 = createTab(url = "url", id = "tab3", lastAccess = System.currentTimeMillis(), searchTerms = "mozilla")
|
||||
|
||||
tabSorter.updateTabs(
|
||||
listOf(
|
||||
createTab(url = "url", id = "tab1", lastAccess = System.currentTimeMillis()),
|
||||
searchTab1, searchTab2
|
||||
),
|
||||
TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(TabGroup("mozilla", "", listOf(searchTab1.id, searchTab2.id)))),
|
||||
selectedTabId = "tab1"
|
||||
)
|
||||
|
||||
tabsTrayStore.waitUntilIdle()
|
||||
|
||||
assertEquals(tabsTrayStore.state.inactiveTabs.size, 0)
|
||||
assertEquals(tabsTrayStore.state.searchTermPartition?.tabGroups?.size, 1)
|
||||
assertEquals(tabsTrayStore.state.normalTabs.size, 1)
|
||||
}
|
||||
|
||||
@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 tabSorter = TabSorter(settings, tabsTrayStore)
|
||||
val searchTab1 = createTab(url = "url", id = "tab3", lastAccess = System.currentTimeMillis(), searchTerms = "mozilla")
|
||||
val searchTab2 = createTab(url = "url", id = "tab4", lastAccess = System.currentTimeMillis(), searchTerms = "mozilla")
|
||||
|
||||
tabSorter.updateTabs(
|
||||
listOf(
|
||||
createTab(url = "url", id = "tab1", lastAccess = System.currentTimeMillis()),
|
||||
createTab(url = "url", id = "tab2", lastAccess = inactiveTimestamp, createdAt = inactiveTimestamp),
|
||||
searchTab1, searchTab2
|
||||
),
|
||||
TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(TabGroup("mozilla", "", listOf(searchTab1.id, searchTab2.id)))),
|
||||
tabPartition = null,
|
||||
selectedTabId = "tab1"
|
||||
)
|
||||
|
||||
tabsTrayStore.waitUntilIdle()
|
||||
|
||||
assertEquals(tabsTrayStore.state.inactiveTabs.size, 1)
|
||||
assertEquals(tabsTrayStore.state.searchTermPartition?.tabGroups?.size, 1)
|
||||
assertEquals(tabsTrayStore.state.normalTabs.size, 1)
|
||||
}
|
||||
|
||||
|
@ -106,70 +76,19 @@ class TabSorterTest {
|
|||
lastAccess = inactiveTimestamp,
|
||||
createdAt = inactiveTimestamp
|
||||
),
|
||||
createTab(
|
||||
url = "url",
|
||||
id = "tab3",
|
||||
lastAccess = System.currentTimeMillis(),
|
||||
searchTerms = "mozilla"
|
||||
),
|
||||
createTab(
|
||||
url = "url",
|
||||
id = "tab4",
|
||||
lastAccess = System.currentTimeMillis(),
|
||||
searchTerms = "mozilla"
|
||||
)
|
||||
),
|
||||
TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(TabGroup("mozilla", "", listOf("tab3", "tab4")))),
|
||||
tabPartition = null,
|
||||
selectedTabId = "tab1"
|
||||
)
|
||||
|
||||
tabsTrayStore.waitUntilIdle()
|
||||
|
||||
assertEquals(tabsTrayStore.state.inactiveTabs.size, 0)
|
||||
assertEquals(tabsTrayStore.state.searchTermPartition?.tabGroups?.size, 1)
|
||||
assertEquals(tabsTrayStore.state.normalTabs.size, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN search term tabs is off THEN adapter have no search term group`() {
|
||||
every { settings.searchTermTabGroupsAreEnabled }.answers { false }
|
||||
val tabSorter = TabSorter(settings, tabsTrayStore)
|
||||
|
||||
tabSorter.updateTabs(
|
||||
listOf(
|
||||
createTab(url = "url", id = "tab1", lastAccess = System.currentTimeMillis()),
|
||||
createTab(
|
||||
url = "url",
|
||||
id = "tab2",
|
||||
lastAccess = inactiveTimestamp,
|
||||
createdAt = inactiveTimestamp
|
||||
),
|
||||
createTab(
|
||||
url = "url",
|
||||
id = "tab3",
|
||||
lastAccess = System.currentTimeMillis(),
|
||||
searchTerms = "mozilla"
|
||||
),
|
||||
createTab(
|
||||
url = "url",
|
||||
id = "tab4",
|
||||
lastAccess = System.currentTimeMillis(),
|
||||
searchTerms = "mozilla"
|
||||
)
|
||||
),
|
||||
TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(TabGroup("mozilla", "", listOf("tab3", "tab4")))),
|
||||
selectedTabId = "tab1"
|
||||
)
|
||||
|
||||
tabsTrayStore.waitUntilIdle()
|
||||
|
||||
assertEquals(tabsTrayStore.state.inactiveTabs.size, 1)
|
||||
assertEquals(tabsTrayStore.state.searchTermPartition?.tabGroups?.size, 0)
|
||||
assertEquals(tabsTrayStore.state.normalTabs.size, 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN both inactive tabs and search term tabs are off THEN adapter have only normal tabs`() {
|
||||
fun `WHEN inactive tabs are disabled THEN adapter have only normal tabs`() {
|
||||
every { settings.inactiveTabsAreEnabled }.answers { false }
|
||||
every { settings.searchTermTabGroupsAreEnabled }.answers { false }
|
||||
val tabSorter = TabSorter(settings, tabsTrayStore)
|
||||
|
@ -195,67 +114,13 @@ class TabSorterTest {
|
|||
searchTerms = "mozilla"
|
||||
)
|
||||
),
|
||||
TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(TabGroup("mozilla", "", mockk()), TabGroup("mozilla", "", mockk()))),
|
||||
tabPartition = null,
|
||||
selectedTabId = "tab1"
|
||||
)
|
||||
|
||||
tabsTrayStore.waitUntilIdle()
|
||||
|
||||
assertEquals(tabsTrayStore.state.inactiveTabs.size, 0)
|
||||
assertEquals(tabsTrayStore.state.searchTermPartition?.tabGroups?.size, 0)
|
||||
assertEquals(tabsTrayStore.state.normalTabs.size, 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN only one search term tab THEN there is no search group`() {
|
||||
val tabSorter = TabSorter(settings, tabsTrayStore)
|
||||
val tab1 =
|
||||
createTab(
|
||||
url = "url", id = "tab1", lastAccess = System.currentTimeMillis(),
|
||||
searchTerms = "mozilla"
|
||||
)
|
||||
|
||||
tabSorter.updateTabs(
|
||||
listOf(tab1),
|
||||
TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(TabGroup("mozilla", "", listOf(tab1.id)))),
|
||||
selectedTabId = "tab1"
|
||||
)
|
||||
|
||||
tabsTrayStore.waitUntilIdle()
|
||||
|
||||
assertEquals(tabsTrayStore.state.inactiveTabs.size, 0)
|
||||
assertEquals(tabsTrayStore.state.searchTermPartition?.tabGroups?.size, 0)
|
||||
assertEquals(tabsTrayStore.state.normalTabs.size, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN remove second last one search term tab THEN search group is kept even if there's only one tab`() {
|
||||
val tabSorter = TabSorter(settings, tabsTrayStore)
|
||||
val tab1 = createTab(url = "url", id = "tab1", lastAccess = System.currentTimeMillis(), searchTerms = "mozilla")
|
||||
val tab2 = createTab(url = "url", id = "tab2", lastAccess = System.currentTimeMillis(), searchTerms = "mozilla")
|
||||
|
||||
tabSorter.updateTabs(
|
||||
listOf(tab1, tab2),
|
||||
TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(TabGroup("mozilla", "", listOf(tab1.id, tab2.id)))),
|
||||
selectedTabId = "tab1"
|
||||
)
|
||||
|
||||
tabsTrayStore.waitUntilIdle()
|
||||
|
||||
assertEquals(tabsTrayStore.state.inactiveTabs.size, 0)
|
||||
assertEquals(tabsTrayStore.state.searchTermPartition?.tabGroups?.size, 1)
|
||||
assertEquals(tabsTrayStore.state.normalTabs.size, 0)
|
||||
|
||||
tabSorter.updateTabs(
|
||||
listOf(tab1),
|
||||
TabPartition(SEARCH_TERM_TAB_GROUPS, listOf(TabGroup("mozilla", "", listOf(tab1.id)))),
|
||||
selectedTabId = "tab1"
|
||||
)
|
||||
|
||||
tabsTrayStore.waitUntilIdle()
|
||||
|
||||
assertEquals(tabsTrayStore.state.inactiveTabs.size, 0)
|
||||
assertEquals(tabsTrayStore.state.searchTermPartition?.tabGroups?.size, 1)
|
||||
assertEquals(tabsTrayStore.state.normalTabs.size, 0)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue