From 6506102e56af60c9482f2750541d9a2ba52c011a Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Tue, 13 Jul 2021 22:26:15 -0400 Subject: [PATCH] Issue #20349: Add inactive tab grouping to tabs tray --- .../java/org/mozilla/fenix/FeatureFlags.kt | 5 + .../fenix/tabstray/TabsTrayController.kt | 26 ++++ .../fenix/tabstray/TabsTrayInteractor.kt | 9 ++ .../fenix/tabstray/TrayPagerAdapter.kt | 11 +- .../fenix/tabstray/browser/BrowserTrayList.kt | 66 +++------- .../fenix/tabstray/browser/ConcatAdapter.kt | 13 ++ .../tabstray/browser/InactiveTabViewHolder.kt | 99 ++++++++++++++ .../tabstray/browser/InactiveTabsAdapter.kt | 124 ++++++++++++++++++ .../tabstray/browser/NormalBrowserTrayList.kt | 102 ++++++++++++++ .../browser/PrivateBrowserTrayList.kt | 60 +++++++++ .../fenix/tabstray/browser/SelectionMenu.kt | 15 ++- .../browser/SelectionMenuIntegration.kt | 6 +- .../fenix/tabstray/browser/TabsAdapter.kt | 16 ++- .../org/mozilla/fenix/tabstray/ext/Context.kt | 23 ++++ .../fenix/tabstray/ext/TabSessionState.kt | 22 ---- .../AbstractBrowserPageViewHolder.kt | 4 +- .../viewholders/AbstractPageViewHolder.kt | 3 +- .../NormalBrowserPageViewHolder.kt | 31 +++-- .../PrivateBrowserPageViewHolder.kt | 17 ++- .../viewholders/SyncedTabsPageViewHolder.kt | 6 +- .../java/org/mozilla/fenix/utils/Settings.kt | 4 +- .../main/res/layout/inactive_footer_item.xml | 37 ++++++ .../main/res/layout/inactive_header_item.xml | 35 +++++ .../res/layout/inactive_tab_list_item.xml | 15 +++ .../res/layout/normal_browser_tray_list.xml | 2 +- .../res/layout/private_browser_tray_list.xml | 2 +- app/src/main/res/values/static_strings.xml | 2 + app/src/main/res/values/strings.xml | 7 + .../tabstray/browser/BrowserTrayListTest.kt | 2 +- .../tabstray/ext/TabSessionStateKtTest.kt | 57 -------- .../AbstractBrowserPageViewHolderTest.kt | 5 +- 31 files changed, 664 insertions(+), 162 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/ConcatAdapter.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabViewHolder.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsAdapter.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalBrowserTrayList.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateBrowserTrayList.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt create mode 100644 app/src/main/res/layout/inactive_footer_item.xml create mode 100644 app/src/main/res/layout/inactive_header_item.xml create mode 100644 app/src/main/res/layout/inactive_tab_list_item.xml delete mode 100644 app/src/test/java/org/mozilla/fenix/tabstray/ext/TabSessionStateKtTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index fcaf3472871c..181a56e415b6 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -53,4 +53,9 @@ object FeatureFlags { * Enables the recently saved bookmarks feature in the home screen. */ val recentBookmarksFeature = Config.channel.isNightlyOrDebug + + /** + * Identifies and separates the tabs list with a secondary section containing least used tabs. + */ + val inactiveTabs = Config.channel.isDebug } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt index f2f86b74634c..5135bf8c9bf7 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt @@ -7,8 +7,10 @@ package org.mozilla.fenix.tabstray import androidx.annotation.VisibleForTesting import androidx.navigation.NavController import kotlinx.coroutines.ExperimentalCoroutinesApi +import mozilla.components.browser.state.action.LastAccessAction import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.selector.getNormalOrPrivateTabs +import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.base.profiler.Profiler import mozilla.components.concept.tabstray.Tab @@ -19,6 +21,9 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.home.HomeFragment +import java.util.concurrent.TimeUnit + +const val DEFAULT_INACTIVE_TEST_DAYS = 5L interface TabsTrayController { @@ -53,6 +58,16 @@ interface TabsTrayController { * @param tabs List of [Tab]s (sessions) to be removed. */ fun handleMultipleTabsDeletion(tabs: Collection) + + /** + * Set the list of [tabs] into the inactive state. + * + * @param tabs List of [Tab]s to be removed. + */ + fun forceTabsAsInactive( + tabs: Collection, + numOfDays: Long = DEFAULT_INACTIVE_TEST_DAYS + ) } class DefaultTabsTrayController( @@ -143,6 +158,17 @@ class DefaultTabsTrayController( showUndoSnackbarForTab(isPrivate) } + /** + * Marks all the [tabs] with the [TabSessionState.lastAccess] to 5 days; enough time to + * have a tab considered as inactive. + */ + override fun forceTabsAsInactive(tabs: Collection, numOfDays: Long) { + tabs.forEach { tab -> + val daysSince = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(numOfDays) + browserStore.dispatch(LastAccessAction.UpdateLastAccessAction(tab.id, daysSince)) + } + } + @VisibleForTesting internal fun sendNewTabEvent(isPrivateModeSelected: Boolean) { val eventToSend = if (isPrivateModeSelected) { diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt index f8223e4b6e42..9923b0eb079d 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt @@ -29,6 +29,11 @@ interface TabsTrayInteractor { * Invoked when [Tab]s need to be deleted. */ fun onDeleteTabs(tabs: Collection) + + /** + * Called when clicking the debug menu option for inactive tabs. + */ + fun onInactiveDebugClicked(tabs: Collection) } /** @@ -54,4 +59,8 @@ class DefaultTabsTrayInteractor( override fun onDeleteTabs(tabs: Collection) { controller.handleMultipleTabsDeletion(tabs) } + + override fun onInactiveDebugClicked(tabs: Collection) { + controller.forceTabsAsInactive(tabs) + } } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt index 43d74280ed80..cbeeb757b2a6 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt @@ -8,6 +8,7 @@ import android.content.Context import android.view.LayoutInflater import android.view.ViewGroup import androidx.annotation.VisibleForTesting +import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.RecyclerView import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.privateTabs @@ -16,6 +17,7 @@ import mozilla.components.browser.state.store.BrowserStore import org.mozilla.fenix.sync.SyncedTabsAdapter 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.syncedtabs.TabClickDelegate import org.mozilla.fenix.tabstray.viewholders.AbstractPageViewHolder import org.mozilla.fenix.tabstray.viewholders.NormalBrowserPageViewHolder @@ -31,7 +33,12 @@ class TrayPagerAdapter( @VisibleForTesting internal val browserStore: BrowserStore ) : RecyclerView.Adapter() { - private val normalAdapter by lazy { BrowserTabsAdapter(context, browserInteractor, store) } + private val normalAdapter by lazy { + ConcatAdapter( + BrowserTabsAdapter(context, browserInteractor, store), + InactiveTabsAdapter(context, browserInteractor) + ) + } private val privateAdapter by lazy { BrowserTabsAdapter(context, browserInteractor, store) } private val syncedTabsAdapter by lazy { SyncedTabsAdapter(TabClickDelegate(navInteractor)) } @@ -74,7 +81,7 @@ class TrayPagerAdapter( POSITION_SYNCED_TABS -> syncedTabsAdapter else -> throw IllegalStateException("View type does not exist.") } - viewHolder.bind(adapter, browserInteractor.getLayoutManagerForPosition(context, position)) + viewHolder.bind(adapter) } override fun getItemViewType(position: Int): Int { diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/BrowserTrayList.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/BrowserTrayList.kt index 39f595a4cb24..69121d6bd883 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/BrowserTrayList.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/BrowserTrayList.kt @@ -12,82 +12,56 @@ import mozilla.components.feature.tabs.tabstray.TabsFeature import org.mozilla.fenix.ext.components import org.mozilla.fenix.tabstray.TabsTrayInteractor import org.mozilla.fenix.tabstray.TabsTrayStore -import org.mozilla.fenix.tabstray.ext.filterFromConfig -class BrowserTrayList @JvmOverloads constructor( +abstract class BrowserTrayList @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : RecyclerView(context, attrs, defStyleAttr) { - /** - * The browser tab types we would want to show. - */ - enum class BrowserTabType { NORMAL, PRIVATE } - - lateinit var browserTabType: BrowserTabType lateinit var interactor: TabsTrayInteractor lateinit var tabsTrayStore: TabsTrayStore - private val tabsFeature by lazy { - // NB: The use cases here are duplicated because there isn't a nicer - // way to share them without a better dependency injection solution. - val selectTabUseCase = SelectTabUseCaseWrapper( - context.components.analytics.metrics, - context.components.useCases.tabsUseCases.selectTab - ) { - interactor.onBrowserTabSelected() - } - - val removeTabUseCase = RemoveTabUseCaseWrapper( - context.components.analytics.metrics - ) { sessionId -> - interactor.onDeleteTab(sessionId) - } - - TabsFeature( - adapter as TabsAdapter, - context.components.core.store, - selectTabUseCase, - removeTabUseCase, - { it.filterFromConfig(browserTabType) }, - { } - ) + /** + * A [TabsFeature] is required for each browser list to ensure one always exists for displaying + * tabs. + */ + abstract val tabsFeature: TabsFeature + + // NB: The use cases here are duplicated because there isn't a nicer + // way to share them without a better dependency injection solution. + protected val selectTabUseCase = SelectTabUseCaseWrapper( + context.components.analytics.metrics, + context.components.useCases.tabsUseCases.selectTab + ) { + interactor.onBrowserTabSelected() } - private val swipeToDelete by lazy { - SwipeToDeleteBinding(tabsTrayStore) + protected val removeTabUseCase = RemoveTabUseCaseWrapper( + context.components.analytics.metrics + ) { sessionId -> + interactor.onDeleteTab(sessionId) } - private val touchHelper by lazy { - TabsTouchHelper( - observable = adapter as TabsAdapter, - onViewHolderTouched = { swipeToDelete.isSwipeable }, - onViewHolderDraw = { context.components.settings.gridTabView.not() } - ) + protected val swipeToDelete by lazy { + SwipeToDeleteBinding(tabsTrayStore) } override fun onAttachedToWindow() { super.onAttachedToWindow() - tabsFeature.start() swipeToDelete.start() adapter?.onAttachedToRecyclerView(this) - - touchHelper.attachToRecyclerView(this) } @VisibleForTesting public override fun onDetachedFromWindow() { super.onDetachedFromWindow() - tabsFeature.stop() swipeToDelete.stop() // Notify the adapter that it is released from the view preemptively. adapter?.onDetachedFromRecyclerView(this) - - touchHelper.attachToRecyclerView(null) } } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/ConcatAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/ConcatAdapter.kt new file mode 100644 index 000000000000..027d63bed94f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/ConcatAdapter.kt @@ -0,0 +1,13 @@ +/* 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 androidx.recyclerview.widget.ConcatAdapter + +val ConcatAdapter.browserAdapter + get() = adapters.find { it is BrowserTabsAdapter } as BrowserTabsAdapter + +val ConcatAdapter.inactiveTabsAdapter + get() = adapters.find { it is InactiveTabsAdapter } as InactiveTabsAdapter diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabViewHolder.kt new file mode 100644 index 000000000000..40469b92a056 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabViewHolder.kt @@ -0,0 +1,99 @@ +/* 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.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView +import mozilla.components.browser.toolbar.MAX_URI_LENGTH +import mozilla.components.concept.tabstray.Tab +import org.mozilla.fenix.R +import org.mozilla.fenix.databinding.InactiveFooterItemBinding +import org.mozilla.fenix.databinding.InactiveTabListItemBinding +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.loadIntoView +import org.mozilla.fenix.ext.toShortUrl +import org.mozilla.fenix.tabstray.browser.AutoCloseInterval.Manual +import org.mozilla.fenix.tabstray.browser.AutoCloseInterval.OneDay +import org.mozilla.fenix.tabstray.browser.AutoCloseInterval.OneMonth +import org.mozilla.fenix.tabstray.browser.AutoCloseInterval.OneWeek + +sealed class InactiveTabViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + class HeaderHolder(itemView: View) : InactiveTabViewHolder(itemView) { + companion object { + const val LAYOUT_ID = R.layout.inactive_header_item + } + } + + class TabViewHolder( + itemView: View, + private val browserTrayInteractor: BrowserTrayInteractor + ) : InactiveTabViewHolder(itemView) { + + private val binding = InactiveTabListItemBinding.bind(itemView) + + fun bind(tab: Tab) { + val components = itemView.context.components + val makePrettyUrl: (String) -> String = { + it.toShortUrl(components.publicSuffixList).take(MAX_URI_LENGTH) + } + + itemView.setOnClickListener { + browserTrayInteractor.open(tab) + } + + binding.siteListItem.apply { + components.core.icons.loadIntoView(iconView, tab.url) + setText(tab.title, makePrettyUrl(tab.url)) + setSecondaryButton( + R.drawable.mozac_ic_close, + R.string.content_description_close_button + ) { + browserTrayInteractor.close(tab) + } + } + } + + companion object { + const val LAYOUT_ID = R.layout.inactive_tab_list_item + } + } + + class FooterHolder(itemView: View) : InactiveTabViewHolder(itemView) { + + val binding = InactiveFooterItemBinding.bind(itemView) + + fun bind(interval: AutoCloseInterval) { + val context = itemView.context + val stringRes = when (interval) { + Manual, OneDay -> { + itemView.visibility = View.GONE + null + } + OneWeek -> { + context.getString(interval.description) + } + OneMonth -> { + context.getString(interval.description) + } + } + if (stringRes != null) { + binding.inactiveDescription.text = + context.getString(R.string.inactive_tabs_description, stringRes) + } + } + + companion object { + const val LAYOUT_ID = R.layout.inactive_footer_item + } + } +} + +enum class AutoCloseInterval(@StringRes val description: Int) { + Manual(0), + OneDay(0), + OneWeek(R.string.inactive_tabs_7_days), + OneMonth(R.string.inactive_tabs_30_days) +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsAdapter.kt new file mode 100644 index 000000000000..934996cf4db8 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsAdapter.kt @@ -0,0 +1,124 @@ +/* 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.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import mozilla.components.concept.tabstray.Tab +import mozilla.components.concept.tabstray.Tabs +import mozilla.components.concept.tabstray.TabsTray +import mozilla.components.support.base.observer.ObserverRegistry +import org.mozilla.fenix.tabstray.browser.InactiveTabViewHolder.FooterHolder +import org.mozilla.fenix.tabstray.browser.InactiveTabViewHolder.HeaderHolder +import org.mozilla.fenix.tabstray.browser.InactiveTabViewHolder.TabViewHolder +import org.mozilla.fenix.tabstray.ext.autoCloseInterval +import mozilla.components.support.base.observer.Observable as ComponentObservable + +/** + * Convenience alias for readability. + */ +typealias Adapter = ListAdapter + +/** + * Convenience alias for readability. + */ +typealias Observable = ComponentObservable + +/** + * The [ListAdapter] for displaying the list of inactive tabs. + */ +class InactiveTabsAdapter( + private val context: Context, + private val browserTrayInteractor: BrowserTrayInteractor, + delegate: Observable = ObserverRegistry() +) : Adapter(DiffCallback), TabsTray, Observable by delegate { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InactiveTabViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(viewType, parent, false) + + return when (viewType) { + HeaderHolder.LAYOUT_ID -> HeaderHolder(view) + TabViewHolder.LAYOUT_ID -> TabViewHolder(view, browserTrayInteractor) + FooterHolder.LAYOUT_ID -> FooterHolder(view) + else -> throw IllegalStateException("Unknown viewType: $viewType") + } + } + + override fun onBindViewHolder(holder: InactiveTabViewHolder, position: Int) { + (holder as? TabViewHolder)?.let { + val item = getItem(position) as Item.InactiveTab + it.bind(item.tab) + } + (holder as? FooterHolder)?.let { + val item = getItem(position) as Item.Footer + it.bind(item.interval) + } + } + + override fun getItemViewType(position: Int): Int { + if (position == 0) { + return HeaderHolder.LAYOUT_ID + } + if (position == itemCount - 1) { + return FooterHolder.LAYOUT_ID + } + return TabViewHolder.LAYOUT_ID + } + + override fun updateTabs(tabs: Tabs) { + if (tabs.list.isEmpty()) { + return + } + + val items = tabs.list.map { Item.InactiveTab(it) } + val footer = Item.Footer(context.autoCloseInterval) + + submitList(listOf(Item.Header) + items + listOf(footer)) + } + + override fun isTabSelected(tabs: Tabs, position: Int): Boolean = false + override fun onTabsChanged(position: Int, count: Int) = Unit + override fun onTabsInserted(position: Int, count: Int) = Unit + override fun onTabsMoved(fromPosition: Int, toPosition: Int) = Unit + override fun onTabsRemoved(position: Int, count: Int) = Unit + + private object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean { + return if (oldItem is Item.InactiveTab && newItem is Item.InactiveTab) { + oldItem.tab.id == newItem.tab.id + } else { + oldItem == newItem + } + } + + override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean { + return oldItem == newItem + } + } + + sealed class Item { + + /** + * A title header for the inactive tab section. This may be seen only + * when at least one inactive tab is present. + */ + object Header : Item() + + /** + * A tab that is now considered inactive. + */ + data class InactiveTab(val tab: Tab) : Item() + + /** + * A footer for the inactive tab section. This may be seen only + * when at least one inactive tab is present. + */ + data class Footer(val interval: AutoCloseInterval) : Item() + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalBrowserTrayList.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalBrowserTrayList.kt new file mode 100644 index 000000000000..aa0bba2c4b3f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalBrowserTrayList.kt @@ -0,0 +1,102 @@ +/* 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.util.AttributeSet +import androidx.recyclerview.widget.ConcatAdapter +import mozilla.components.browser.state.state.TabSessionState +import mozilla.components.browser.tabstray.TabViewHolder +import mozilla.components.feature.tabs.tabstray.TabsFeature +import org.mozilla.fenix.FeatureFlags +import org.mozilla.fenix.ext.components +import java.util.concurrent.TimeUnit + +const val DEFAULT_INACTIVE_DAYS = 4L + +class NormalBrowserTrayList @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : BrowserTrayList(context, attrs, defStyleAttr) { + + /** + * The max configuration time for the inactive tab. + */ + var maxInactiveThing = TimeUnit.DAYS.toMillis(DEFAULT_INACTIVE_DAYS) + + private val concatAdapter by lazy { adapter as ConcatAdapter } + + override val tabsFeature by lazy { + val tabsAdapter = concatAdapter.browserAdapter + + TabsFeature( + tabsAdapter, + context.components.core.store, + selectTabUseCase, + removeTabUseCase, + { state -> + if (!FeatureFlags.inactiveTabs) { + return@TabsFeature !state.content.private + } + state.isActive(maxInactiveThing) + }, + {} + ) + } + + private val inactiveFeature by lazy { + val tabsAdapter = concatAdapter.inactiveTabsAdapter + + TabsFeature( + tabsAdapter, + context.components.core.store, + selectTabUseCase, + removeTabUseCase, + { state -> + if (!FeatureFlags.inactiveTabs) { + return@TabsFeature false + } + state.isInactive(maxInactiveThing) + }, + {} + ) + } + + private val touchHelper by lazy { + TabsTouchHelper( + observable = concatAdapter.browserAdapter, + onViewHolderTouched = { + it is TabViewHolder && swipeToDelete.isSwipeable + }, + onViewHolderDraw = { context.components.settings.gridTabView.not() } + ) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + tabsFeature.start() + inactiveFeature.start() + + touchHelper.attachToRecyclerView(this) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + + tabsFeature.stop() + inactiveFeature.stop() + + touchHelper.attachToRecyclerView(null) + } +} + +fun TabSessionState.isActive(maxInactiveThing: Long): Boolean { + val now = System.currentTimeMillis() + return now - lastAccess <= maxInactiveThing +} + +fun TabSessionState.isInactive(maxInactiveThing: Long) = !isActive(maxInactiveThing) diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateBrowserTrayList.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateBrowserTrayList.kt new file mode 100644 index 000000000000..d2fa0b4cd158 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateBrowserTrayList.kt @@ -0,0 +1,60 @@ +/* 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.util.AttributeSet +import mozilla.components.feature.tabs.tabstray.TabsFeature +import org.mozilla.fenix.ext.components + +class PrivateBrowserTrayList @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : BrowserTrayList(context, attrs, defStyleAttr) { + + override val tabsFeature by lazy { + // NB: The use cases here are duplicated because there isn't a nicer + // way to share them without a better dependency injection solution. + TabsFeature( + adapter as TabsAdapter, + context.components.core.store, + selectTabUseCase, + removeTabUseCase, + { it.content.private }, + { } + ) + } + private val touchHelper by lazy { + TabsTouchHelper( + observable = adapter as TabsAdapter, + onViewHolderTouched = { swipeToDelete.isSwipeable }, + onViewHolderDraw = { context.components.settings.gridTabView.not() } + ) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + tabsFeature.start() + swipeToDelete.start() + + adapter?.onAttachedToRecyclerView(this) + + touchHelper.attachToRecyclerView(this) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + + tabsFeature.stop() + swipeToDelete.stop() + + // Notify the adapter that it is released from the view preemptively. + adapter?.onDetachedFromRecyclerView(this) + + touchHelper.attachToRecyclerView(null) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenu.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenu.kt index 1a789acb67d9..79957a044a34 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenu.kt @@ -8,6 +8,7 @@ import android.content.Context import mozilla.components.browser.menu.BrowserMenuBuilder import mozilla.components.browser.menu.item.SimpleBrowserMenuItem import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components class SelectionMenu( private val context: Context, @@ -16,10 +17,15 @@ class SelectionMenu( sealed class Item { object BookmarkTabs : Item() object DeleteTabs : Item() + object MakeInactive : Item() } val menuBuilder by lazy { BrowserMenuBuilder(menuItems) } + private val shouldShowSecretSettings = { + context.components.settings.showSecretDebugMenuThisSession + } + private val menuItems by lazy { listOf( SimpleBrowserMenuItem( @@ -34,7 +40,14 @@ class SelectionMenu( textColorResource = R.color.primary_text_normal_theme ) { onItemTapped.invoke(Item.DeleteTabs) - } + }, + // This item is only visible for debugging. + SimpleBrowserMenuItem( + context.getString(R.string.inactive_tabs_menu_item), + textColorResource = R.color.primary_text_normal_theme + ) { + onItemTapped.invoke(Item.MakeInactive) + }.apply { visible = shouldShowSecretSettings } ) } } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenuIntegration.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenuIntegration.kt index 40fee99e8da6..f008a85c41a2 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenuIntegration.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenuIntegration.kt @@ -33,12 +33,14 @@ class SelectionMenuIntegration( Do exhaustive when (item) { is SelectionMenu.Item.BookmarkTabs -> { navInteractor.onSaveToBookmarks(store.state.mode.selectedTabs) - store.dispatch(TabsTrayAction.ExitSelectMode) } is SelectionMenu.Item.DeleteTabs -> { trayInteractor.onDeleteTabs(store.state.mode.selectedTabs) - store.dispatch(TabsTrayAction.ExitSelectMode) + } + is SelectionMenu.Item.MakeInactive -> { + trayInteractor.onInactiveDebugClicked(store.state.mode.selectedTabs) } } + store.dispatch(TabsTrayAction.ExitSelectMode) } } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsAdapter.kt index 8e4820cd12ed..77aa6509caf0 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsAdapter.kt @@ -5,9 +5,11 @@ package org.mozilla.fenix.tabstray.browser import androidx.annotation.CallSuper -import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter import mozilla.components.browser.tabstray.TabViewHolder import mozilla.components.browser.tabstray.TabsTrayStyling +import mozilla.components.concept.tabstray.Tab import mozilla.components.concept.tabstray.Tabs import mozilla.components.concept.tabstray.TabsTray import mozilla.components.support.base.observer.Observable @@ -26,7 +28,7 @@ import mozilla.components.support.base.observer.ObserverRegistry */ abstract class TabsAdapter( delegate: Observable = ObserverRegistry() -) : RecyclerView.Adapter(), TabsTray, Observable by delegate { +) : ListAdapter(DiffCallback), TabsTray, Observable by delegate { protected var tabs: Tabs? = null protected var styling: TabsTrayStyling = TabsTrayStyling() @@ -61,4 +63,14 @@ abstract class TabsAdapter( final override fun onTabsRemoved(position: Int, count: Int) = notifyItemRangeRemoved(position, count) + + private object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Tab, newItem: Tab): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Tab, newItem: Tab): Boolean { + return oldItem == newItem + } + } } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/ext/Context.kt b/app/src/main/java/org/mozilla/fenix/tabstray/ext/Context.kt index 1beff2677507..e61fe903a621 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/ext/Context.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/ext/Context.kt @@ -5,6 +5,9 @@ package org.mozilla.fenix.tabstray.ext import android.content.Context +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.tabstray.browser.AutoCloseInterval private const val MIN_COLUMN_WIDTH_DP = 180 @@ -17,3 +20,23 @@ internal val Context.numberOfGridColumns: Int val screenWidthDp = displayMetrics.widthPixels / displayMetrics.density return (screenWidthDp / MIN_COLUMN_WIDTH_DP).toInt().coerceAtLeast(2) } + +val Context.defaultLayoutColumns: Int + get() { + // Normal/Private tabs + return if (settings().gridTabView) { + numberOfGridColumns + } else { + 1 + } + } + +val Context.autoCloseInterval: AutoCloseInterval + get() = with(components.settings) { + when { + closeTabsAfterOneDay -> AutoCloseInterval.OneDay + closeTabsAfterOneWeek -> AutoCloseInterval.OneWeek + closeTabsAfterOneMonth -> AutoCloseInterval.OneMonth + else -> AutoCloseInterval.Manual + } + } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt b/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt deleted file mode 100644 index 663fb6e2167a..000000000000 --- a/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt +++ /dev/null @@ -1,22 +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.ext - -import mozilla.components.browser.state.state.TabSessionState -import org.mozilla.fenix.tabstray.Page -import org.mozilla.fenix.tabstray.browser.BrowserTrayList.BrowserTabType -import org.mozilla.fenix.tabstray.browser.BrowserTrayList.BrowserTabType.PRIVATE - -fun TabSessionState.filterFromConfig(type: BrowserTabType): Boolean { - val isPrivate = type == PRIVATE - - return content.private == isPrivate -} - -fun TabSessionState.getTrayPosition(): Int = - when (content.private) { - true -> Page.PrivateTabs.ordinal - false -> Page.NormalTabs.ordinal - } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractBrowserPageViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractBrowserPageViewHolder.kt index 9e06a3104f40..a4fb5dc9154f 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractBrowserPageViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractBrowserPageViewHolder.kt @@ -25,7 +25,7 @@ abstract class AbstractBrowserPageViewHolder( private val currentTabIndex: Int ) : AbstractPageViewHolder(containerView) { - protected val trayList: BrowserTrayList = itemView.findViewById(R.id.tray_list_item) + private val trayList: BrowserTrayList = itemView.findViewById(R.id.tray_list_item) private val emptyList: TextView = itemView.findViewById(R.id.tab_tray_empty_view) abstract val emptyStringText: String @@ -36,7 +36,7 @@ abstract class AbstractBrowserPageViewHolder( } @CallSuper - override fun bind( + protected fun bind( adapter: RecyclerView.Adapter, layoutManager: RecyclerView.LayoutManager ) { diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractPageViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractPageViewHolder.kt index ff6055d90c08..f1c6c5791746 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractPageViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractPageViewHolder.kt @@ -17,7 +17,6 @@ abstract class AbstractPageViewHolder constructor( ) : RecyclerView.ViewHolder(containerView), LayoutContainer { abstract fun bind( - adapter: RecyclerView.Adapter, - layoutManager: RecyclerView.LayoutManager + adapter: RecyclerView.Adapter ) } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserPageViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserPageViewHolder.kt index 4edb2a9c262d..e5e1a77cfe30 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserPageViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserPageViewHolder.kt @@ -5,14 +5,16 @@ package org.mozilla.fenix.tabstray.viewholders import android.view.View +import androidx.recyclerview.widget.ConcatAdapter +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import mozilla.components.concept.tabstray.Tab import org.mozilla.fenix.R import org.mozilla.fenix.selection.SelectionHolder import org.mozilla.fenix.tabstray.TabsTrayInteractor import org.mozilla.fenix.tabstray.TabsTrayStore -import org.mozilla.fenix.tabstray.browser.BrowserTrayList.BrowserTabType.NORMAL -import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter +import org.mozilla.fenix.tabstray.browser.browserAdapter +import org.mozilla.fenix.tabstray.ext.defaultLayoutColumns /** * View holder for the normal tabs tray list. @@ -29,10 +31,6 @@ class NormalBrowserPageViewHolder( currentTabIndex ), SelectionHolder { - init { - trayList.browserTabType = NORMAL - } - /** * Holds the list of selected tabs. * @@ -46,12 +44,25 @@ class NormalBrowserPageViewHolder( get() = itemView.resources.getString(R.string.no_open_tabs_description) override fun bind( - adapter: RecyclerView.Adapter, - layoutManager: RecyclerView.LayoutManager + adapter: RecyclerView.Adapter ) { - (adapter as BrowserTabsAdapter).selectionHolder = this + val browserAdapter = (adapter as ConcatAdapter).browserAdapter + browserAdapter.selectionHolder = this + + val number = containerView.context.defaultLayoutColumns + val manager = GridLayoutManager(containerView.context, number).apply { + spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (position >= browserAdapter.itemCount) { + number + } else { + 1 + } + } + } + } - super.bind(adapter, layoutManager) + super.bind(adapter, manager) } companion object { diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/PrivateBrowserPageViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/PrivateBrowserPageViewHolder.kt index c213ee5121fe..3f131a758228 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/PrivateBrowserPageViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/PrivateBrowserPageViewHolder.kt @@ -5,10 +5,12 @@ package org.mozilla.fenix.tabstray.viewholders import android.view.View +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView import org.mozilla.fenix.R import org.mozilla.fenix.tabstray.TabsTrayInteractor import org.mozilla.fenix.tabstray.TabsTrayStore -import org.mozilla.fenix.tabstray.browser.BrowserTrayList.BrowserTabType.PRIVATE +import org.mozilla.fenix.tabstray.ext.defaultLayoutColumns /** * View holder for the private tabs tray list. @@ -25,13 +27,18 @@ class PrivateBrowserPageViewHolder( currentTabIndex ) { - init { - trayList.browserTabType = PRIVATE - } - override val emptyStringText: String get() = itemView.resources.getString(R.string.no_private_tabs_description) + override fun bind( + adapter: RecyclerView.Adapter + ) { + val number = containerView.context.defaultLayoutColumns + val manager = GridLayoutManager(containerView.context, number) + + super.bind(adapter, manager) + } + companion object { const val LAYOUT_ID = R.layout.private_browser_tray_list } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabsPageViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabsPageViewHolder.kt index 601f78d210af..58bfac0f747b 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabsPageViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabsPageViewHolder.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.tabstray.viewholders import android.view.View +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.component_sync_tabs_tray_layout.* import org.mozilla.fenix.R @@ -16,10 +17,9 @@ class SyncedTabsPageViewHolder( ) : AbstractPageViewHolder(containerView) { override fun bind( - adapter: RecyclerView.Adapter, - layoutManager: RecyclerView.LayoutManager + adapter: RecyclerView.Adapter ) { - synced_tabs_list.layoutManager = layoutManager + synced_tabs_list.layoutManager = GridLayoutManager(containerView.context, 1) synced_tabs_list.adapter = adapter synced_tabs_tray_layout.tabsTrayStore = tabsTrayStore diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 76d16897ad34..1d1c106a0b83 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -330,7 +330,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { var manuallyCloseTabs by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_close_tabs_manually), - default = true + default = FeatureFlags.inactiveTabs.not() ) var closeTabsAfterOneDay by booleanPreference( @@ -345,7 +345,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { var closeTabsAfterOneMonth by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_close_tabs_after_one_month), - default = false + default = FeatureFlags.inactiveTabs ) var allowThirdPartyRootCerts by booleanPreference( diff --git a/app/src/main/res/layout/inactive_footer_item.xml b/app/src/main/res/layout/inactive_footer_item.xml new file mode 100644 index 000000000000..253b6b6cac4d --- /dev/null +++ b/app/src/main/res/layout/inactive_footer_item.xml @@ -0,0 +1,37 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/inactive_header_item.xml b/app/src/main/res/layout/inactive_header_item.xml new file mode 100644 index 000000000000..f23f5892b2ac --- /dev/null +++ b/app/src/main/res/layout/inactive_header_item.xml @@ -0,0 +1,35 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/inactive_tab_list_item.xml b/app/src/main/res/layout/inactive_tab_list_item.xml new file mode 100644 index 000000000000..6d004fb3ec8c --- /dev/null +++ b/app/src/main/res/layout/inactive_tab_list_item.xml @@ -0,0 +1,15 @@ + + + diff --git a/app/src/main/res/layout/normal_browser_tray_list.xml b/app/src/main/res/layout/normal_browser_tray_list.xml index fd2dde665a73..003e7066227f 100644 --- a/app/src/main/res/layout/normal_browser_tray_list.xml +++ b/app/src/main/res/layout/normal_browser_tray_list.xml @@ -3,7 +3,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - Use Nimbus Preview Collection (requires restart) + Make inactive + Show Synced Tabs in the tabs tray diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a7366c37bd8..5aa86dbd5e53 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1764,6 +1764,13 @@ Cancel + + + Inactive tabs + Tabs are available here for %s. After that time, tabs will be automatically closed. + 30 days + 1 week + Set links from websites, emails, and messages to open automatically in Firefox. diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/BrowserTrayListTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/BrowserTrayListTest.kt index b3422ee58881..ca39bec53118 100644 --- a/app/src/test/java/org/mozilla/fenix/tabstray/browser/BrowserTrayListTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/BrowserTrayListTest.kt @@ -17,7 +17,7 @@ class BrowserTrayListTest { @Test fun `WHEN recyclerview detaches from window THEN notify adapter`() { - val trayList = BrowserTrayList(testContext) + val trayList = NormalBrowserTrayList(testContext) val adapter = mockk(relaxed = true) trayList.adapter = adapter diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/ext/TabSessionStateKtTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/ext/TabSessionStateKtTest.kt deleted file mode 100644 index d8efb4e564f1..000000000000 --- a/app/src/test/java/org/mozilla/fenix/tabstray/ext/TabSessionStateKtTest.kt +++ /dev/null @@ -1,57 +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.ext - -import io.mockk.every -import io.mockk.mockk -import mozilla.components.browser.state.state.ContentState -import mozilla.components.browser.state.state.TabSessionState -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test -import org.mozilla.fenix.tabstray.browser.BrowserTrayList.BrowserTabType.NORMAL -import org.mozilla.fenix.tabstray.browser.BrowserTrayList.BrowserTabType.PRIVATE - -class TabSessionStateKtTest { - - @Test - fun `WHEN configuration is private THEN return true`() { - val contentState = mockk() - val state = TabSessionState(content = contentState) - val config = PRIVATE - - every { contentState.private } returns true - - assertTrue(state.filterFromConfig(config)) - } - - @Test - fun `WHEN configuration is normal THEN return false`() { - val contentState = mockk() - val state = TabSessionState(content = contentState) - val config = NORMAL - - every { contentState.private } returns false - - assertTrue(state.filterFromConfig(config)) - } - - @Test - fun `WHEN configuration does not match THEN return false`() { - val contentState = mockk() - val state = TabSessionState(content = contentState) - val config = NORMAL - - every { contentState.private } returns true - - assertFalse(state.filterFromConfig(config)) - - val config2 = PRIVATE - - every { contentState.private } returns false - - assertFalse(state.filterFromConfig(config2)) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/viewholders/AbstractBrowserPageViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/viewholders/AbstractBrowserPageViewHolderTest.kt index 6598682ce2e6..0ec3363e089c 100644 --- a/app/src/test/java/org/mozilla/fenix/tabstray/viewholders/AbstractBrowserPageViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabstray/viewholders/AbstractBrowserPageViewHolderTest.kt @@ -8,7 +8,6 @@ import android.view.LayoutInflater import android.view.View.GONE import android.view.View.VISIBLE import android.widget.TextView -import androidx.recyclerview.widget.LinearLayoutManager import io.mockk.mockk import mozilla.components.concept.tabstray.Tabs import mozilla.components.support.test.robolectric.testContext @@ -39,7 +38,7 @@ class AbstractBrowserPageViewHolderTest { val trayList: BrowserTrayList = itemView.findViewById(R.id.tray_list_item) val emptyList: TextView = itemView.findViewById(R.id.tab_tray_empty_view) - viewHolder.bind(adapter, LinearLayoutManager(testContext)) + viewHolder.bind(adapter) adapter.updateTabs( Tabs( @@ -63,7 +62,7 @@ class AbstractBrowserPageViewHolderTest { val trayList: BrowserTrayList = itemView.findViewById(R.id.tray_list_item) val emptyList: TextView = itemView.findViewById(R.id.tab_tray_empty_view) - viewHolder.bind(adapter, LinearLayoutManager(testContext)) + viewHolder.bind(adapter) adapter.updateTabs( Tabs(