Issue #18538: Add BrowserTabsAdapter for tabs tray

upstream-sync
Jonathan Almeida 3 years ago committed by Jonathan Almeida
parent 3b11b9a700
commit 2c23941823

@ -17,11 +17,32 @@ import com.google.android.material.tabs.TabLayout
import kotlinx.android.synthetic.main.component_tabstray2.*
import kotlinx.android.synthetic.main.component_tabstray2.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.tabstray.browser.DefaultBrowserTrayInteractor
import org.mozilla.fenix.tabstray.browser.RemoveTabUseCaseWrapper
import org.mozilla.fenix.tabstray.browser.SelectTabUseCaseWrapper
class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
lateinit var behavior: BottomSheetBehavior<ConstraintLayout>
private val selectTabUseCase by lazy {
SelectTabUseCaseWrapper(
requireComponents.analytics.metrics,
requireComponents.useCases.tabsUseCases.selectTab
) {
navigateToBrowser()
}
}
private val removeUseCases by lazy {
RemoveTabUseCaseWrapper(requireComponents.analytics.metrics
) {
tabRemoved(it)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.TabTrayDialogStyle)
@ -44,7 +65,13 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupPager(view.context, this)
val browserTrayInteractor = DefaultBrowserTrayInteractor(
this,
selectTabUseCase,
removeUseCases
)
setupPager(view.context, this, browserTrayInteractor)
}
override fun setCurrentTrayPosition(position: Int) {
@ -65,19 +92,26 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
}
}
override fun tabRemoved(sessionId: String) {
override fun tabRemoved(tabId: String) {
// TODO re-implement these methods
// showUndoSnackbarForTab(sessionId)
// removeIfNotLastTab(sessionId)
// Temporary
requireComponents.useCases.tabsUseCases.removeTab(tabId)
}
private fun setupPager(context: Context, interactor: TabsTrayInteractor) {
private fun setupPager(
context: Context,
trayInteractor: TabsTrayInteractor,
browserInteractor: BrowserTrayInteractor
) {
tabsTray.apply {
adapter = TrayPagerAdapter(context, interactor)
adapter = TrayPagerAdapter(context, trayInteractor, browserInteractor)
isUserInputEnabled = false
}
tab_layout.addOnTabSelectedListener(TabLayoutObserver(interactor))
tab_layout.addOnTabSelectedListener(TabLayoutObserver(trayInteractor))
}
}

@ -16,7 +16,7 @@ interface TabsTrayInteractor {
fun navigateToBrowser()
/**
* Invoked when a tab is removed from the tabs tray with the given [sessionId].
* Invoked when a tab is removed from the tabs tray with the given [tabId].
*/
fun tabRemoved(sessionId: String)
fun tabRemoved(tabId: String)
}

@ -7,18 +7,21 @@ package org.mozilla.fenix.tabstray
import android.content.Context
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.tabtray.FenixTabsAdapter
import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
class TrayPagerAdapter(
context: Context,
val interactor: TabsTrayInteractor
val context: Context,
val interactor: TabsTrayInteractor,
val browserInteractor: BrowserTrayInteractor
) : RecyclerView.Adapter<TrayViewHolder>() {
private val normalAdapter by lazy { FenixTabsAdapter(context) }
private val privateAdapter by lazy { FenixTabsAdapter(context) }
private val normalAdapter by lazy { BrowserTabsAdapter(context, browserInteractor) }
private val privateAdapter by lazy { BrowserTabsAdapter(context, browserInteractor) }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrayViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
@ -37,7 +40,7 @@ class TrayPagerAdapter(
else -> throw IllegalStateException("View type does not exist.")
}
viewHolder.bind(adapter)
viewHolder.bind(adapter, GridLayoutManager(context, 1))
}
override fun getItemViewType(position: Int): Int {

@ -5,17 +5,22 @@
package org.mozilla.fenix.tabstray
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList
/**
* Base [RecyclerView.ViewHolder] for [TrayPagerAdapter] items.
*/
sealed class TrayViewHolder constructor(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
abstract fun bind(adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>)
abstract fun bind(
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>,
layoutManager: RecyclerView.LayoutManager
)
}
class BrowserTabViewHolder(
@ -29,8 +34,11 @@ class BrowserTabViewHolder(
trayList.interactor = interactor
}
override fun bind(adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>) {
trayList.layoutManager = LinearLayoutManager(itemView.context)
override fun bind(
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>,
layoutManager: RecyclerView.LayoutManager
) {
trayList.layoutManager = layoutManager
trayList.adapter = adapter
}

@ -7,12 +7,8 @@ package org.mozilla.fenix.tabstray.browser
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.browser.tabstray.TabsAdapter
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.feature.tabs.tabstray.TabsFeature
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.tabstray.TabsTrayInteractor
import org.mozilla.fenix.tabstray.TrayItem
@ -25,7 +21,14 @@ abstract class BaseBrowserTrayList @JvmOverloads constructor(
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr), TrayItem {
/**
* The browser tab types we would want to show.
*/
enum class BrowserTabType { NORMAL, PRIVATE }
/**
* A configuration for classes that extend [BaseBrowserTrayList].
*/
data class Configuration(val browserTabType: BrowserTabType)
abstract val configuration: Configuration
@ -71,25 +74,3 @@ abstract class BaseBrowserTrayList @JvmOverloads constructor(
tabsFeature
}
}
internal class SelectTabUseCaseWrapper(
private val metrics: MetricController,
private val selectTab: TabsUseCases.SelectTabUseCase,
private val onSelect: (String) -> Unit
) : TabsUseCases.SelectTabUseCase {
override fun invoke(tabId: String) {
metrics.track(Event.OpenedExistingTab)
selectTab(tabId)
onSelect(tabId)
}
}
internal class RemoveTabUseCaseWrapper(
private val metrics: MetricController,
private val onRemove: (String) -> Unit
) : TabsUseCases.RemoveTabUseCase {
override fun invoke(sessionId: String) {
metrics.track(Event.ClosedExistingTab)
onRemove(sessionId)
}
}

@ -0,0 +1,84 @@
/* 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.RecyclerView
import androidx.recyclerview.widget.GridLayoutManager
import kotlinx.android.synthetic.main.tab_tray_item.view.*
import mozilla.components.browser.tabstray.TabViewHolder
import mozilla.components.browser.thumbnails.loader.ThumbnailLoader
import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.tabtray.TabTrayViewHolder
/**
* A [RecyclerView.Adapter] for browser tabs.
*/
class BrowserTabsAdapter(
private val context: Context,
private val interactor: BrowserTrayInteractor,
private val layoutManager: (() -> GridLayoutManager)? = null,
delegate: Observable<TabsTray.Observer> = ObserverRegistry()
) : TabsAdapter(delegate) {
/**
* The layout types for the tabs.
*/
enum class ViewType {
LIST,
GRID
}
private val imageLoader = ThumbnailLoader(context.components.core.thumbnailStorage)
override fun getItemViewType(position: Int): Int {
return if (context.settings().gridTabView) {
// ViewType.GRID.ordinal
R.layout.tab_tray_grid_item
} else {
// ViewType.LIST.ordinal
R.layout.tab_tray_item
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabViewHolder {
// TODO make this into separate view holders for each layout
// For this, we need to separate the TabTrayViewHolder as well.
// See https://github.com/mozilla-mobile/fenix/issues/18535
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return TabTrayViewHolder(view, imageLoader)
}
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)
}
holder.itemView.mozac_browser_tabstray_close.setOnClickListener {
interactor.onCloseTab(tab)
}
}
}
}

@ -0,0 +1,62 @@
/* 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.concept.tabstray.Tab
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.
*/
interface BrowserTrayInteractor {
/**
* Select the tab.
*/
fun onOpenTab(tab: Tab)
/**
* Close the tab.
*/
fun onCloseTab(tab: Tab)
/**
* Enable or disable multi-select mode.
*/
fun onMultiSelect(enabled: Boolean)
}
/**
* A default implementation of [BrowserTrayInteractor].
*/
class DefaultBrowserTrayInteractor(
private val trayInteractor: TabsTrayInteractor,
private val selectTabUseCase: TabsUseCases.SelectTabUseCase,
private val removeUseCases: TabsUseCases.RemoveTabUseCase
) : BrowserTrayInteractor {
/**
* See [BrowserTrayInteractor.onOpenTab].
*/
override fun onOpenTab(tab: Tab) {
selectTabUseCase.invoke(tab.id)
trayInteractor.navigateToBrowser()
}
/**
* See [BrowserTrayInteractor.onCloseTab].
*/
override fun onCloseTab(tab: Tab) {
removeUseCases.invoke(tab.id)
}
/**
* See [BrowserTrayInteractor.onMultiSelect].
*/
override fun onMultiSelect(enabled: Boolean) {
// TODO https://github.com/mozilla-mobile/fenix/issues/18443
}
}

@ -0,0 +1,58 @@
/* 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.annotation.CallSuper
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.browser.tabstray.TabViewHolder
import mozilla.components.browser.tabstray.TabsTrayStyling
import mozilla.components.concept.tabstray.Tabs
import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry
// The previous tabs adapter was very restrictive and required Fenix to jump through
// may hoops to access and update certain methods. An abstract adapter is easier to manage
// for Android UI APIs.
//
// TODO Let's upstream this to AC with tests.
abstract class TabsAdapter(
delegate: Observable<TabsTray.Observer> = ObserverRegistry()
) : RecyclerView.Adapter<TabViewHolder>(), TabsTray, Observable<TabsTray.Observer> by delegate {
private var tabs: Tabs? = null
var styling: TabsTrayStyling = TabsTrayStyling()
override fun getItemCount(): Int = tabs?.list?.size ?: 0
@CallSuper
override fun updateTabs(tabs: Tabs) {
this.tabs = tabs
notifyObservers { onTabsUpdated() }
}
@CallSuper
override fun onBindViewHolder(holder: TabViewHolder, position: Int) {
val tabs = tabs ?: return
holder.bind(tabs.list[position], isTabSelected(tabs, position), styling, this)
}
final override fun isTabSelected(tabs: Tabs, position: Int): Boolean =
tabs.selectedIndex == position
final override fun onTabsChanged(position: Int, count: Int) =
notifyItemRangeChanged(position, count)
final override fun onTabsInserted(position: Int, count: Int) =
notifyItemRangeInserted(position, count)
final override fun onTabsMoved(fromPosition: Int, toPosition: Int) =
notifyItemMoved(fromPosition, toPosition)
final override fun onTabsRemoved(position: Int, count: Int) =
notifyItemRangeRemoved(position, count)
}

@ -0,0 +1,31 @@
/* 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.feature.tabs.TabsUseCases
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
class SelectTabUseCaseWrapper(
private val metrics: MetricController,
private val selectTab: TabsUseCases.SelectTabUseCase,
private val onSelect: (String) -> Unit
) : TabsUseCases.SelectTabUseCase {
override fun invoke(tabId: String) {
metrics.track(Event.OpenedExistingTab)
selectTab(tabId)
onSelect(tabId)
}
}
class RemoveTabUseCaseWrapper(
private val metrics: MetricController,
private val onRemove: (String) -> Unit
) : TabsUseCases.RemoveTabUseCase {
override fun invoke(sessionId: String) {
metrics.track(Event.ClosedExistingTab)
onRemove(sessionId)
}
}
Loading…
Cancel
Save