mirror of
https://github.com/fork-maintainers/iceraven-browser
synced 2024-11-03 23:15:31 +00:00
[fenix] Close https://github.com/mozilla-mobile/fenix/issues/18443: Use recyclerview-selection for multi-select mode in tray
Add multi-select mode to the BrowserTabsAdapter. It has the functionality to: - Enable multi-select mode on long-press. - Enable multi-select mode when changed by an external function. - Only works for normal tabs (as we currently have it). Co-authored-by: "codrut.topliceanu" <codrut.topliceanu@softvision.ro>
This commit is contained in:
parent
83e350ed4c
commit
0e3def9e83
@ -533,6 +533,7 @@ dependencies {
|
||||
implementation Deps.androidx_navigation_fragment
|
||||
implementation Deps.androidx_navigation_ui
|
||||
implementation Deps.androidx_recyclerview
|
||||
implementation Deps.androidx_recyclerview_selection
|
||||
implementation Deps.androidx_lifecycle_livedata
|
||||
implementation Deps.androidx_lifecycle_runtime
|
||||
implementation Deps.androidx_lifecycle_viewmodel
|
||||
|
@ -9,8 +9,6 @@ import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.mozilla.fenix.tabstray.BrowserTabViewHolder.Companion.LAYOUT_ID_NORMAL_TAB
|
||||
import org.mozilla.fenix.tabstray.BrowserTabViewHolder.Companion.LAYOUT_ID_PRIVATE_TAB
|
||||
import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter
|
||||
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
|
||||
|
||||
@ -27,8 +25,14 @@ class TrayPagerAdapter(
|
||||
val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||
|
||||
return when (viewType) {
|
||||
LAYOUT_ID_NORMAL_TAB -> BrowserTabViewHolder(itemView, interactor)
|
||||
LAYOUT_ID_PRIVATE_TAB -> BrowserTabViewHolder(itemView, interactor)
|
||||
NormalBrowserTabViewHolder.LAYOUT_ID -> NormalBrowserTabViewHolder(
|
||||
itemView,
|
||||
interactor
|
||||
)
|
||||
PrivateBrowserTabViewHolder.LAYOUT_ID -> PrivateBrowserTabViewHolder(
|
||||
itemView,
|
||||
interactor
|
||||
)
|
||||
else -> throw IllegalStateException("Unknown viewType.")
|
||||
}
|
||||
}
|
||||
@ -45,8 +49,8 @@ class TrayPagerAdapter(
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (position) {
|
||||
POSITION_NORMAL_TABS -> LAYOUT_ID_NORMAL_TAB
|
||||
POSITION_PRIVATE_TABS -> LAYOUT_ID_PRIVATE_TAB
|
||||
POSITION_NORMAL_TABS -> NormalBrowserTabViewHolder.LAYOUT_ID
|
||||
POSITION_PRIVATE_TABS -> PrivateBrowserTabViewHolder.LAYOUT_ID
|
||||
else -> throw IllegalStateException("Unknown position.")
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,22 @@
|
||||
package org.mozilla.fenix.tabstray
|
||||
|
||||
import android.view.View
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.recyclerview.selection.SelectionPredicates
|
||||
import androidx.recyclerview.selection.SelectionTracker
|
||||
import androidx.recyclerview.selection.StableIdKeyProvider
|
||||
import androidx.recyclerview.selection.StorageStrategy
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList
|
||||
import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter
|
||||
import org.mozilla.fenix.tabstray.browser.TabsDetailsLookup
|
||||
|
||||
/**
|
||||
* Base [RecyclerView.ViewHolder] for [TrayPagerAdapter] items.
|
||||
*/
|
||||
sealed class TrayViewHolder constructor(
|
||||
abstract class TrayViewHolder constructor(
|
||||
override val containerView: View
|
||||
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
|
||||
|
||||
@ -23,17 +30,18 @@ sealed class TrayViewHolder constructor(
|
||||
)
|
||||
}
|
||||
|
||||
class BrowserTabViewHolder(
|
||||
abstract class BrowserTabViewHolder(
|
||||
containerView: View,
|
||||
interactor: TabsTrayInteractor
|
||||
) : TrayViewHolder(containerView) {
|
||||
|
||||
private val trayList: BaseBrowserTrayList = itemView.findViewById(R.id.tray_list_item)
|
||||
protected val trayList: BaseBrowserTrayList = itemView.findViewById(R.id.tray_list_item)
|
||||
|
||||
init {
|
||||
trayList.interactor = interactor
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun bind(
|
||||
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>,
|
||||
layoutManager: RecyclerView.LayoutManager
|
||||
@ -41,9 +49,50 @@ class BrowserTabViewHolder(
|
||||
trayList.layoutManager = layoutManager
|
||||
trayList.adapter = adapter
|
||||
}
|
||||
}
|
||||
|
||||
class NormalBrowserTabViewHolder(
|
||||
containerView: View,
|
||||
interactor: TabsTrayInteractor
|
||||
) : BrowserTabViewHolder(containerView, interactor) {
|
||||
|
||||
private lateinit var selectionTracker: SelectionTracker<Long>
|
||||
|
||||
override fun bind(
|
||||
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>,
|
||||
layoutManager: RecyclerView.LayoutManager
|
||||
) {
|
||||
super.bind(adapter, layoutManager)
|
||||
|
||||
selectionTracker = SelectionTracker.Builder(
|
||||
"mySelection",
|
||||
trayList,
|
||||
StableIdKeyProvider(trayList),
|
||||
TabsDetailsLookup(trayList),
|
||||
StorageStrategy.createLongStorage()
|
||||
).withSelectionPredicate(
|
||||
SelectionPredicates.createSelectAnything()
|
||||
).build()
|
||||
|
||||
(adapter as BrowserTabsAdapter).tracker = selectionTracker
|
||||
|
||||
selectionTracker.addObserver(object : SelectionTracker.SelectionObserver<Long>() {
|
||||
override fun onItemStateChanged(key: Long, selected: Boolean) {
|
||||
// TODO Do nothing for now; remove in a future patch if needed.
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID_NORMAL_TAB = R.layout.normal_browser_tray_list
|
||||
const val LAYOUT_ID_PRIVATE_TAB = R.layout.private_browser_tray_list
|
||||
const val LAYOUT_ID = R.layout.normal_browser_tray_list
|
||||
}
|
||||
}
|
||||
|
||||
class PrivateBrowserTabViewHolder(
|
||||
containerView: View,
|
||||
interactor: TabsTrayInteractor
|
||||
) : BrowserTabViewHolder(containerView, interactor) {
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.private_browser_tray_list
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ abstract class BaseBrowserTrayList @JvmOverloads constructor(
|
||||
private val tabsFeature by lazy {
|
||||
ViewBoundFeatureWrapper(
|
||||
feature = TabsFeature(
|
||||
adapter as TabsAdapter,
|
||||
adapter as BrowserTabsAdapter,
|
||||
context.components.core.store,
|
||||
selectTabUseCase,
|
||||
removeTabUseCase,
|
||||
|
@ -6,6 +6,7 @@ package org.mozilla.fenix.tabstray.browser
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.selection.SelectionTracker
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import kotlinx.android.synthetic.main.tab_tray_item.view.*
|
||||
@ -27,7 +28,7 @@ class BrowserTabsAdapter(
|
||||
private val interactor: BrowserTrayInteractor,
|
||||
private val layoutManager: (() -> GridLayoutManager)? = null,
|
||||
delegate: Observable<TabsTray.Observer> = ObserverRegistry()
|
||||
) : TabsAdapter(delegate) {
|
||||
) : TabsAdapter<TabViewHolder>(delegate) {
|
||||
|
||||
/**
|
||||
* The layout types for the tabs.
|
||||
@ -37,8 +38,17 @@ class BrowserTabsAdapter(
|
||||
GRID
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the selected tabs in multi-select mode.
|
||||
*/
|
||||
var tracker: SelectionTracker<Long>? = null
|
||||
|
||||
private val imageLoader = ThumbnailLoader(context.components.core.thumbnailStorage)
|
||||
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (context.settings().gridTabView) {
|
||||
ViewType.GRID.ordinal
|
||||
@ -54,19 +64,12 @@ class BrowserTabsAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int) = position.toLong()
|
||||
|
||||
override fun onBindViewHolder(holder: TabViewHolder, position: Int) {
|
||||
super.onBindViewHolder(holder, position)
|
||||
|
||||
holder.tab?.let { tab ->
|
||||
if (!tab.private) {
|
||||
holder.itemView.setOnLongClickListener {
|
||||
interactor.onMultiSelect(true)
|
||||
true
|
||||
}
|
||||
} else {
|
||||
holder.itemView.setOnLongClickListener(null)
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
interactor.onOpenTab(tab)
|
||||
}
|
||||
@ -74,6 +77,10 @@ class BrowserTabsAdapter(
|
||||
holder.itemView.mozac_browser_tabstray_close.setOnClickListener {
|
||||
interactor.onCloseTab(tab)
|
||||
}
|
||||
|
||||
tracker?.let {
|
||||
holder.showTabIsMultiSelectEnabled(it.isSelected(position.toLong()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ import mozilla.components.feature.tabs.TabsUseCases
|
||||
import org.mozilla.fenix.tabstray.TabsTrayInteractor
|
||||
|
||||
/**
|
||||
* For interacting with UI that extends from [BaseBrowserTrayList] and other browser tab tray views.
|
||||
* For interacting with UI that is specifically for [BaseBrowserTrayList] and other browser
|
||||
* tab tray views.
|
||||
*/
|
||||
interface BrowserTrayInteractor {
|
||||
|
||||
@ -24,9 +25,9 @@ interface BrowserTrayInteractor {
|
||||
fun onCloseTab(tab: Tab)
|
||||
|
||||
/**
|
||||
* Enable or disable multi-select mode.
|
||||
* If multi-select mode is enabled or disabled.
|
||||
*/
|
||||
fun onMultiSelect(enabled: Boolean)
|
||||
fun isMultiSelectMode(): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,9 +55,10 @@ class DefaultBrowserTrayInteractor(
|
||||
}
|
||||
|
||||
/**
|
||||
* See [BrowserTrayInteractor.onMultiSelect].
|
||||
* See [BrowserTrayInteractor.isMultiSelectMode].
|
||||
*/
|
||||
override fun onMultiSelect(enabled: Boolean) {
|
||||
// TODO https://github.com/mozilla-mobile/fenix/issues/18443
|
||||
override fun isMultiSelectMode(): Boolean {
|
||||
// Needs https://github.com/mozilla-mobile/fenix/issues/18513 to change this value
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,12 @@ import mozilla.components.support.base.observer.ObserverRegistry
|
||||
// for Android UI APIs.
|
||||
//
|
||||
// TODO Let's upstream this to AC with tests.
|
||||
abstract class TabsAdapter(
|
||||
abstract class TabsAdapter<T : TabViewHolder>(
|
||||
delegate: Observable<TabsTray.Observer> = ObserverRegistry()
|
||||
) : RecyclerView.Adapter<TabViewHolder>(), TabsTray, Observable<TabsTray.Observer> by delegate {
|
||||
private var tabs: Tabs? = null
|
||||
) : RecyclerView.Adapter<T>(), TabsTray, Observable<TabsTray.Observer> by delegate {
|
||||
|
||||
var styling: TabsTrayStyling = TabsTrayStyling()
|
||||
|
||||
override fun getItemCount(): Int = tabs?.list?.size ?: 0
|
||||
protected var tabs: Tabs? = null
|
||||
protected var styling: TabsTrayStyling = TabsTrayStyling()
|
||||
|
||||
@CallSuper
|
||||
override fun updateTabs(tabs: Tabs) {
|
||||
@ -35,12 +33,14 @@ abstract class TabsAdapter(
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onBindViewHolder(holder: TabViewHolder, position: Int) {
|
||||
override fun onBindViewHolder(holder: T, position: Int) {
|
||||
val tabs = tabs ?: return
|
||||
|
||||
holder.bind(tabs.list[position], isTabSelected(tabs, position), styling, this)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = tabs?.list?.size ?: 0
|
||||
|
||||
final override fun isTabSelected(tabs: Tabs, position: Int): Boolean =
|
||||
tabs.selectedIndex == position
|
||||
|
||||
|
@ -0,0 +1,28 @@
|
||||
/* 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.MotionEvent
|
||||
import androidx.recyclerview.selection.ItemDetailsLookup
|
||||
import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.mozilla.fenix.tabtray.TabTrayViewHolder
|
||||
|
||||
/**
|
||||
* An [ItemDetailsLookup] for retrieving the [ItemDetails] of a [TabTrayViewHolder].
|
||||
*/
|
||||
class TabsDetailsLookup(
|
||||
private val recyclerView: RecyclerView
|
||||
) : ItemDetailsLookup<Long>() {
|
||||
|
||||
override fun getItemDetails(event: MotionEvent): ItemDetails<Long>? {
|
||||
val view = recyclerView.findChildViewUnder(event.x, event.y)
|
||||
if (view != null) {
|
||||
val viewHolder = recyclerView.getChildViewHolder(view) as TabTrayViewHolder
|
||||
return viewHolder.getItemDetails()
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
package org.mozilla.fenix.tabtray
|
||||
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
@ -12,7 +13,12 @@ import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.appcompat.widget.AppCompatImageButton
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.selection.ItemDetailsLookup
|
||||
import kotlinx.android.synthetic.main.checkbox_item.view.*
|
||||
import kotlinx.android.synthetic.main.tab_tray_grid_item.view.*
|
||||
import kotlinx.android.synthetic.main.tab_tray_grid_item.view.mozac_browser_tabstray_close
|
||||
import kotlinx.android.synthetic.main.tab_tray_item.view.*
|
||||
import mozilla.components.browser.state.selector.findTabOrCustomTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.browser.tabstray.TabViewHolder
|
||||
@ -35,6 +41,7 @@ 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.tabstray.browser.BrowserTrayInteractor
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
@ -44,7 +51,8 @@ class TabTrayViewHolder(
|
||||
itemView: View,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val store: BrowserStore = itemView.context.components.core.store,
|
||||
private val metrics: MetricController = itemView.context.components.analytics.metrics
|
||||
private val metrics: MetricController = itemView.context.components.analytics.metrics,
|
||||
private val browserTrayInteractor: BrowserTrayInteractor? = null
|
||||
) : TabViewHolder(itemView) {
|
||||
|
||||
private val faviconView: ImageView? =
|
||||
@ -196,6 +204,20 @@ class TabTrayViewHolder(
|
||||
)
|
||||
}
|
||||
|
||||
fun getItemDetails() = object : ItemDetailsLookup.ItemDetails<Long>() {
|
||||
override fun getPosition(): Int = bindingAdapterPosition
|
||||
override fun getSelectionKey(): Long = itemId
|
||||
override fun inSelectionHotspot(e: MotionEvent): Boolean {
|
||||
return browserTrayInteractor?.isMultiSelectMode() == true
|
||||
}
|
||||
}
|
||||
|
||||
fun showTabIsMultiSelectEnabled(isSelected: Boolean) {
|
||||
itemView.selected_mask.isVisible = isSelected
|
||||
itemView.mozac_browser_tabstray_close.isVisible =
|
||||
browserTrayInteractor?.isMultiSelectMode() == false
|
||||
}
|
||||
|
||||
private fun updateCloseButtonDescription(title: String) {
|
||||
closeView.contentDescription =
|
||||
closeView.context.getString(R.string.close_tab_title, title)
|
||||
|
@ -29,6 +29,7 @@ object Versions {
|
||||
const val androidx_fragment = "1.2.5"
|
||||
const val androidx_navigation = "2.3.3"
|
||||
const val androidx_recyclerview = "1.2.0-beta01"
|
||||
const val androidx_recyclerview_selection = "1.0.0"
|
||||
const val androidx_core = "1.3.2"
|
||||
const val androidx_paging = "2.1.2"
|
||||
const val androidx_transition = "1.4.0"
|
||||
@ -185,6 +186,7 @@ object Deps {
|
||||
const val androidx_navigation_fragment = "androidx.navigation:navigation-fragment-ktx:${Versions.androidx_navigation}"
|
||||
const val androidx_navigation_ui = "androidx.navigation:navigation-ui:${Versions.androidx_navigation}"
|
||||
const val androidx_recyclerview = "androidx.recyclerview:recyclerview:${Versions.androidx_recyclerview}"
|
||||
const val androidx_recyclerview_selection = "androidx.recyclerview:recyclerview-selection:${Versions.androidx_recyclerview_selection}"
|
||||
const val androidx_core = "androidx.core:core:${Versions.androidx_core}"
|
||||
const val androidx_core_ktx = "androidx.core:core-ktx:${Versions.androidx_core}"
|
||||
const val androidx_transition = "androidx.transition:transition:${Versions.androidx_transition}"
|
||||
|
Loading…
Reference in New Issue
Block a user