For #17917: Use View binding in tabs tray

upstream-sync
codrut.topliceanu 3 years ago committed by mergify[bot]
parent 69fa9abdd2
commit bf5b4a5655

@ -9,15 +9,16 @@ import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.animation.Animation import android.view.animation.Animation
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.sync_tabs_error_row.view.*
import kotlinx.android.synthetic.main.sync_tabs_list_item.view.*
import kotlinx.android.synthetic.main.view_synced_tabs_group.view.*
import kotlinx.android.synthetic.main.view_synced_tabs_title.view.*
import mozilla.components.browser.toolbar.MAX_URI_LENGTH import mozilla.components.browser.toolbar.MAX_URI_LENGTH
import mozilla.components.feature.syncedtabs.view.SyncedTabsView import mozilla.components.feature.syncedtabs.view.SyncedTabsView
import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.SyncTabsErrorRowBinding
import org.mozilla.fenix.databinding.SyncTabsListItemBinding
import org.mozilla.fenix.databinding.ViewSyncedTabsGroupBinding
import org.mozilla.fenix.databinding.ViewSyncedTabsTitleBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.sync.SyncedTabsAdapter.AdapterItem import org.mozilla.fenix.sync.SyncedTabsAdapter.AdapterItem
@ -42,8 +43,9 @@ sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(item
private fun bindTab(tab: AdapterItem.Tab) { private fun bindTab(tab: AdapterItem.Tab) {
val active = tab.tab.active() val active = tab.tab.active()
itemView.synced_tab_item_title.text = active.title val binding = SyncTabsListItemBinding.bind(itemView)
itemView.synced_tab_item_url.text = active.url binding.syncedTabItemTitle.text = active.title
binding.syncedTabItemUrl.text = active.url
.toShortUrl(itemView.context.components.publicSuffixList) .toShortUrl(itemView.context.components.publicSuffixList)
.take(MAX_URI_LENGTH) .take(MAX_URI_LENGTH)
} }
@ -57,14 +59,15 @@ sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(item
override fun <T : AdapterItem> bind(item: T, interactor: SyncedTabsView.Listener) { override fun <T : AdapterItem> bind(item: T, interactor: SyncedTabsView.Listener) {
val errorItem = item as AdapterItem.Error val errorItem = item as AdapterItem.Error
val binding = SyncTabsErrorRowBinding.bind(itemView)
itemView.sync_tabs_error_description.text = binding.syncTabsErrorDescription.text =
itemView.context.getString(errorItem.descriptionResId) itemView.context.getString(errorItem.descriptionResId)
itemView.sync_tabs_error_cta_button.visibility = GONE binding.syncTabsErrorCtaButton.visibility = GONE
errorItem.navController?.let { navController -> errorItem.navController?.let { navController ->
itemView.sync_tabs_error_cta_button.visibility = VISIBLE binding.syncTabsErrorCtaButton.visibility = VISIBLE
itemView.sync_tabs_error_cta_button.setOnClickListener { binding.syncTabsErrorCtaButton.setOnClickListener {
navController.navigate(NavGraphDirections.actionGlobalTurnOnSync()) navController.navigate(NavGraphDirections.actionGlobalTurnOnSync())
} }
} }
@ -77,12 +80,15 @@ sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(item
class DeviceViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) { class DeviceViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
@VisibleForTesting
internal val binding = ViewSyncedTabsGroupBinding.bind(itemView)
override fun <T : AdapterItem> bind(item: T, interactor: SyncedTabsView.Listener) { override fun <T : AdapterItem> bind(item: T, interactor: SyncedTabsView.Listener) {
bindHeader(item as AdapterItem.Device) bindHeader(item as AdapterItem.Device)
} }
private fun bindHeader(device: AdapterItem.Device) { private fun bindHeader(device: AdapterItem.Device) {
itemView.synced_tabs_group_name.text = device.device.displayName binding.syncedTabsGroupName.text = device.device.displayName
} }
companion object { companion object {
@ -101,7 +107,8 @@ sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(item
class TitleViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) { class TitleViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
override fun <T : AdapterItem> bind(item: T, interactor: SyncedTabsView.Listener) { override fun <T : AdapterItem> bind(item: T, interactor: SyncedTabsView.Listener) {
itemView.refresh_icon.setOnClickListener { v -> val binding = ViewSyncedTabsTitleBinding.bind(itemView)
binding.refreshIcon.setOnClickListener { v ->
val rotation = AnimationUtils.loadAnimation( val rotation = AnimationUtils.loadAnimation(
itemView.context, itemView.context,
R.anim.full_rotation R.anim.full_rotation

@ -21,12 +21,6 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import kotlinx.android.synthetic.main.component_tabstray2.*
import kotlinx.android.synthetic.main.component_tabstray2.view.*
import kotlinx.android.synthetic.main.component_tabstray_fab.*
import kotlinx.android.synthetic.main.fragment_tab_tray_dialog.*
import kotlinx.android.synthetic.main.tabs_tray_tab_counter2.*
import kotlinx.android.synthetic.main.tabstray_multiselect_items.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.normalTabs
@ -40,6 +34,11 @@ import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.share.ShareFragment import org.mozilla.fenix.share.ShareFragment
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.databinding.ComponentTabstray2Binding
import org.mozilla.fenix.databinding.ComponentTabstrayFabBinding
import org.mozilla.fenix.databinding.FragmentTabTrayDialogBinding
import org.mozilla.fenix.databinding.TabsTrayTabCounter2Binding
import org.mozilla.fenix.databinding.TabstrayMultiselectItemsBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
@ -60,7 +59,6 @@ import kotlin.math.max
@Suppress("TooManyFunctions", "LargeClass") @Suppress("TooManyFunctions", "LargeClass")
class TabsTrayFragment : AppCompatDialogFragment() { class TabsTrayFragment : AppCompatDialogFragment() {
private var fabView: View? = null
@VisibleForTesting internal lateinit var tabsTrayStore: TabsTrayStore @VisibleForTesting internal lateinit var tabsTrayStore: TabsTrayStore
private lateinit var browserTrayInteractor: BrowserTrayInteractor private lateinit var browserTrayInteractor: BrowserTrayInteractor
private lateinit var tabsTrayInteractor: TabsTrayInteractor private lateinit var tabsTrayInteractor: TabsTrayInteractor
@ -75,6 +73,16 @@ class TabsTrayFragment : AppCompatDialogFragment() {
private val tabsTrayCtaBinding = ViewBoundFeatureWrapper<TabsTrayInfoBannerBinding>() private val tabsTrayCtaBinding = ViewBoundFeatureWrapper<TabsTrayInfoBannerBinding>()
private val secureTabsTrayBinding = ViewBoundFeatureWrapper<SecureTabsTrayBinding>() private val secureTabsTrayBinding = ViewBoundFeatureWrapper<SecureTabsTrayBinding>()
@VisibleForTesting @Suppress("VariableNaming")
internal var _tabsTrayBinding: ComponentTabstray2Binding? = null
private val tabsTrayBinding get() = _tabsTrayBinding!!
@VisibleForTesting @Suppress("VariableNaming")
internal var _tabsTrayDialogBinding: FragmentTabTrayDialogBinding? = null
private val tabsTrayDialogBinding get() = _tabsTrayDialogBinding!!
@VisibleForTesting @Suppress("VariableNaming")
internal var _fabButtonBinding: ComponentTabstrayFabBinding? = null
private val fabButtonBinding get() = _fabButtonBinding!!
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.TabTrayDialogStyle) setStyle(STYLE_NO_TITLE, R.style.TabTrayDialogStyle)
@ -88,8 +96,21 @@ class TabsTrayFragment : AppCompatDialogFragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
val containerView = inflater.inflate(R.layout.fragment_tab_tray_dialog, container, false) _tabsTrayDialogBinding = FragmentTabTrayDialogBinding.inflate(
inflater.inflate(R.layout.component_tabstray2, containerView as ViewGroup, true) inflater,
container,
false
)
_tabsTrayBinding = ComponentTabstray2Binding.inflate(
inflater,
tabsTrayDialogBinding.root,
true
)
_fabButtonBinding = ComponentTabstrayFabBinding.inflate(
LayoutInflater.from(tabsTrayDialogBinding.root.context),
tabsTrayDialogBinding.root,
true
)
val args by navArgs<TabsTrayFragmentArgs>() val args by navArgs<TabsTrayFragmentArgs>()
val initialMode = if (args.enterMultiselect) { val initialMode = if (args.enterMultiselect) {
@ -106,10 +127,14 @@ class TabsTrayFragment : AppCompatDialogFragment() {
) )
} }
fabView = LayoutInflater.from(containerView.context) return tabsTrayDialogBinding.root
.inflate(R.layout.component_tabstray_fab, containerView, true) }
return containerView override fun onDestroyView() {
super.onDestroyView()
_tabsTrayBinding = null
_tabsTrayDialogBinding = null
_fabButtonBinding = null
} }
@Suppress("LongMethod") @Suppress("LongMethod")
@ -118,7 +143,8 @@ class TabsTrayFragment : AppCompatDialogFragment() {
val activity = activity as HomeActivity val activity = activity as HomeActivity
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
new_tab_button.accessibilityTraversalAfter = tab_layout.id fabButtonBinding.newTabButton.accessibilityTraversalAfter =
tabsTrayBinding.tabLayout.id
} }
requireComponents.analytics.metrics.track(Event.TabsTrayOpened) requireComponents.analytics.metrics.track(Event.TabsTrayOpened)
@ -165,7 +191,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
requireComponents.analytics.metrics requireComponents.analytics.metrics
) )
setupMenu(view, navigationInteractor) setupMenu(navigationInteractor)
setupPager( setupPager(
view.context, view.context,
tabsTrayStore, tabsTrayStore,
@ -180,7 +206,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
} }
trayBehaviorManager = TabSheetBehaviorManager( trayBehaviorManager = TabSheetBehaviorManager(
behavior = BottomSheetBehavior.from(view.tab_wrapper), behavior = BottomSheetBehavior.from(tabsTrayBinding.tabWrapper),
orientation = resources.configuration.orientation, orientation = resources.configuration.orientation,
maxNumberOfTabs = max( maxNumberOfTabs = max(
requireContext().components.core.store.state.normalTabs.size, requireContext().components.core.store.state.normalTabs.size,
@ -199,7 +225,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
feature = TabsTrayInfoBannerBinding( feature = TabsTrayInfoBannerBinding(
context = view.context, context = view.context,
store = requireComponents.core.store, store = requireComponents.core.store,
infoBannerView = view.info_banner, infoBannerView = tabsTrayBinding.infoBanner,
settings = requireComponents.settings, settings = requireComponents.settings,
navigationInteractor = navigationInteractor navigationInteractor = navigationInteractor
), ),
@ -209,7 +235,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
tabLayoutMediator.set( tabLayoutMediator.set(
feature = TabLayoutMediator( feature = TabLayoutMediator(
tabLayout = tab_layout, tabLayout = tabsTrayBinding.tabLayout,
interactor = tabsTrayInteractor, interactor = tabsTrayInteractor,
browsingModeManager = activity.browsingModeManager, browsingModeManager = activity.browsingModeManager,
tabsTrayStore = tabsTrayStore, tabsTrayStore = tabsTrayStore,
@ -219,10 +245,14 @@ class TabsTrayFragment : AppCompatDialogFragment() {
view = view view = view
) )
val tabsTrayTabCounter2Binding = TabsTrayTabCounter2Binding.bind(
tabsTrayBinding.tabLayout
)
tabCounterBinding.set( tabCounterBinding.set(
feature = TabCounterBinding( feature = TabCounterBinding(
store = requireComponents.core.store, store = requireComponents.core.store,
counter = tab_counter counter = tabsTrayTabCounter2Binding.tabCounter
), ),
owner = this, owner = this,
view = view view = view
@ -231,32 +261,37 @@ class TabsTrayFragment : AppCompatDialogFragment() {
floatingActionButtonBinding.set( floatingActionButtonBinding.set(
feature = FloatingActionButtonBinding( feature = FloatingActionButtonBinding(
store = tabsTrayStore, store = tabsTrayStore,
actionButton = new_tab_button, actionButton = fabButtonBinding.newTabButton,
browserTrayInteractor = browserTrayInteractor browserTrayInteractor = browserTrayInteractor
), ),
owner = this, owner = this,
view = view view = view
) )
val tabsTrayMultiselectItemsBinding = TabstrayMultiselectItemsBinding.bind(
tabsTrayBinding.root
)
selectionBannerBinding.set( selectionBannerBinding.set(
feature = SelectionBannerBinding( feature = SelectionBannerBinding(
context = requireContext(), context = requireContext(),
binding = tabsTrayBinding,
store = tabsTrayStore, store = tabsTrayStore,
navInteractor = navigationInteractor, navInteractor = navigationInteractor,
tabsTrayInteractor = tabsTrayInteractor, tabsTrayInteractor = tabsTrayInteractor,
containerView = view, containerView = view,
backgroundView = topBar, backgroundView = tabsTrayBinding.topBar,
showOnSelectViews = VisibilityModifier( showOnSelectViews = VisibilityModifier(
collect_multi_select, tabsTrayMultiselectItemsBinding.collectMultiSelect,
share_multi_select, tabsTrayMultiselectItemsBinding.shareMultiSelect,
menu_multi_select, tabsTrayMultiselectItemsBinding.menuMultiSelect,
multiselect_title, tabsTrayBinding.multiselectTitle,
exit_multi_select tabsTrayBinding.exitMultiSelect
), ),
showOnNormalViews = VisibilityModifier( showOnNormalViews = VisibilityModifier(
tab_layout, tabsTrayBinding.tabLayout,
tab_tray_overflow, tabsTrayBinding.tabTrayOverflow,
new_tab_button fabButtonBinding.newTabButton
) )
), ),
owner = this, owner = this,
@ -266,8 +301,8 @@ class TabsTrayFragment : AppCompatDialogFragment() {
selectionHandleBinding.set( selectionHandleBinding.set(
feature = SelectionHandleBinding( feature = SelectionHandleBinding(
store = tabsTrayStore, store = tabsTrayStore,
handle = handle, handle = tabsTrayBinding.handle,
containerLayout = tab_wrapper containerLayout = tabsTrayBinding.tabWrapper
), ),
owner = this, owner = this,
view = view view = view
@ -295,7 +330,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
trayBehaviorManager.updateDependingOnOrientation(newConfig.orientation) trayBehaviorManager.updateDependingOnOrientation(newConfig.orientation)
if (requireContext().settings().gridTabView) { if (requireContext().settings().gridTabView) {
tabsTray.adapter?.notifyDataSetChanged() tabsTrayBinding.tabsTray.adapter?.notifyDataSetChanged()
} }
} }
@ -321,7 +356,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
}, },
operation = { }, operation = { },
elevation = ELEVATION, elevation = ELEVATION,
anchorView = if (new_tab_button.isVisible) new_tab_button else null anchorView = if (fabButtonBinding.newTabButton.isVisible) fabButtonBinding.newTabButton else null
) )
} }
@ -333,7 +368,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
browserInteractor: BrowserTrayInteractor, browserInteractor: BrowserTrayInteractor,
navigationInteractor: NavigationInteractor navigationInteractor: NavigationInteractor
) { ) {
tabsTray.apply { tabsTrayBinding.tabsTray.apply {
adapter = TrayPagerAdapter( adapter = TrayPagerAdapter(
context, context,
store, store,
@ -347,8 +382,8 @@ class TabsTrayFragment : AppCompatDialogFragment() {
} }
@VisibleForTesting @VisibleForTesting
internal fun setupMenu(view: View, navigationInteractor: NavigationInteractor) { internal fun setupMenu(navigationInteractor: NavigationInteractor) {
view.tab_tray_overflow.setOnClickListener { anchor -> tabsTrayBinding.tabTrayOverflow.setOnClickListener { anchor ->
requireComponents.analytics.metrics.track(Event.TabsTrayMenuOpened) requireComponents.analytics.metrics.track(Event.TabsTrayMenuOpened)
@ -356,7 +391,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
context = requireContext(), context = requireContext(),
browserStore = requireComponents.core.store, browserStore = requireComponents.core.store,
tabsTrayStore = tabsTrayStore, tabsTrayStore = tabsTrayStore,
tabLayout = tab_layout, tabLayout = tabsTrayBinding.tabLayout,
navigationInteractor = navigationInteractor navigationInteractor = navigationInteractor
).build() ).build()
@ -375,8 +410,8 @@ class TabsTrayFragment : AppCompatDialogFragment() {
@VisibleForTesting @VisibleForTesting
internal fun setupBackgroundDismissalListener(block: (View) -> Unit) { internal fun setupBackgroundDismissalListener(block: (View) -> Unit) {
tabLayout.setOnClickListener(block) tabsTrayDialogBinding.tabLayout.setOnClickListener(block)
handle.setOnClickListener(block) tabsTrayBinding.handle.setOnClickListener(block)
} }
@VisibleForTesting @VisibleForTesting
@ -396,8 +431,8 @@ class TabsTrayFragment : AppCompatDialogFragment() {
@VisibleForTesting @VisibleForTesting
internal fun selectTabPosition(position: Int, smoothScroll: Boolean) { internal fun selectTabPosition(position: Int, smoothScroll: Boolean) {
tabsTray.setCurrentItem(position, smoothScroll) tabsTrayBinding.tabsTray.setCurrentItem(position, smoothScroll)
tab_layout.getTabAt(position)?.select() tabsTrayBinding.tabLayout.getTabAt(position)?.select()
} }
@VisibleForTesting @VisibleForTesting
@ -447,7 +482,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
return if (requireComponents.settings.accessibilityServicesEnabled) { return if (requireComponents.settings.accessibilityServicesEnabled) {
null null
} else { } else {
new_tab_button fabButtonBinding.newTabButton
} }
} }

@ -13,7 +13,6 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.AppCompatImageButton import androidx.appcompat.widget.AppCompatImageButton
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.checkbox_item.view.*
import mozilla.components.browser.state.selector.findTabOrCustomTab import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.tabstray.TabViewHolder import mozilla.components.browser.tabstray.TabViewHolder
@ -102,8 +101,8 @@ abstract class AbstractBrowserTabViewHolder(
} }
} }
fun showTabIsMultiSelectEnabled(isSelected: Boolean) { fun showTabIsMultiSelectEnabled(selectedMaskView: View?, isSelected: Boolean) {
itemView.selected_mask.isVisible = isSelected selectedMaskView?.isVisible = isSelected
closeView.isInvisible = trayStore.state.mode is TabsTrayState.Mode.Select closeView.isInvisible = trayStore.state.mode is TabsTrayState.Mode.Select
} }

@ -13,9 +13,9 @@ import mozilla.components.concept.tabstray.Tab
import mozilla.components.concept.tabstray.TabsTray import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.support.base.observer.Observable import mozilla.components.support.base.observer.Observable
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.TabTrayGridItemBinding
import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ext.increaseTapArea
import kotlin.math.max import kotlin.math.max
import kotlinx.android.synthetic.main.tab_tray_grid_item.view.tab_tray_grid_item
import org.mozilla.fenix.selection.SelectionHolder import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.tabstray.TabsTrayStore import org.mozilla.fenix.tabstray.TabsTrayStore
@ -39,7 +39,8 @@ class BrowserTabGridViewHolder(
) )
override fun updateSelectedTabIndicator(showAsSelected: Boolean) { override fun updateSelectedTabIndicator(showAsSelected: Boolean) {
itemView.tab_tray_grid_item.background = if (showAsSelected) { val binding = TabTrayGridItemBinding.bind(itemView)
binding.tabTrayGridItem.background = if (showAsSelected) {
AppCompatResources.getDrawable(itemView.context, R.drawable.tab_tray_grid_item_selected_border) AppCompatResources.getDrawable(itemView.context, R.drawable.tab_tray_grid_item_selected_border)
} else { } else {
null null

@ -6,9 +6,9 @@ package org.mozilla.fenix.tabstray.browser
import android.content.Context import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.tab_tray_item.view.*
import mozilla.components.browser.tabstray.TabsAdapter.Companion.PAYLOAD_DONT_HIGHLIGHT_SELECTED_ITEM 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.TabsAdapter.Companion.PAYLOAD_HIGHLIGHT_SELECTED_ITEM
import mozilla.components.browser.thumbnails.loader.ThumbnailLoader import mozilla.components.browser.thumbnails.loader.ThumbnailLoader
@ -17,6 +17,8 @@ import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.support.base.observer.Observable import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry import mozilla.components.support.base.observer.ObserverRegistry
import org.mozilla.fenix.R 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.ext.components
import org.mozilla.fenix.selection.SelectionHolder import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.tabstray.TabsTrayStore import org.mozilla.fenix.tabstray.TabsTrayStore
@ -68,14 +70,23 @@ class BrowserTabsAdapter(
override fun onBindViewHolder(holder: AbstractBrowserTabViewHolder, position: Int) { override fun onBindViewHolder(holder: AbstractBrowserTabViewHolder, position: Int) {
super.onBindViewHolder(holder, position) super.onBindViewHolder(holder, position)
var selectedMaskView: View? = null
holder.tab?.let { tab -> holder.tab?.let { tab ->
holder.itemView.mozac_browser_tabstray_close.setOnClickListener { when (getItemViewType(position)) {
interactor.close(tab) ViewType.GRID.layoutRes -> {
val gridBinding = TabTrayGridItemBinding.bind(holder.itemView)
selectedMaskView = gridBinding.checkboxInclude.selectedMask
gridBinding.mozacBrowserTabstrayClose.setOnClickListener { interactor.close(tab) }
}
ViewType.LIST.layoutRes -> {
val listBinding = TabTrayItemBinding.bind(holder.itemView)
selectedMaskView = listBinding.checkboxInclude.selectedMask
listBinding.mozacBrowserTabstrayClose.setOnClickListener { interactor.close(tab) }
}
} }
selectionHolder?.let { selectionHolder?.let {
holder.showTabIsMultiSelectEnabled(it.selectedItems.contains(tab)) holder.showTabIsMultiSelectEnabled(selectedMaskView, it.selectedItems.contains(tab))
} }
} }
} }
@ -103,7 +114,18 @@ class BrowserTabsAdapter(
} }
selectionHolder?.let { selectionHolder?.let {
holder.showTabIsMultiSelectEnabled(it.selectedItems.contains(holder.tab)) var selectedMaskView: View? = null
when (getItemViewType(position)) {
ViewType.GRID.layoutRes -> {
val gridBinding = TabTrayGridItemBinding.bind(holder.itemView)
selectedMaskView = gridBinding.checkboxInclude.selectedMask
}
ViewType.LIST.layoutRes -> {
val listBinding = TabTrayItemBinding.bind(holder.itemView)
selectedMaskView = listBinding.checkboxInclude.selectedMask
}
}
holder.showTabIsMultiSelectEnabled(selectedMaskView, it.selectedItems.contains(holder.tab))
} }
} }

@ -9,9 +9,6 @@ import android.view.View
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.component_tabstray2.view.exit_multi_select
import kotlinx.android.synthetic.main.component_tabstray2.view.multiselect_title
import kotlinx.android.synthetic.main.tabstray_multiselect_items.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@ -19,6 +16,8 @@ import kotlinx.coroutines.flow.map
import mozilla.components.lib.state.helpers.AbstractBinding import mozilla.components.lib.state.helpers.AbstractBinding
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.ComponentTabstray2Binding
import org.mozilla.fenix.databinding.TabstrayMultiselectItemsBinding
import org.mozilla.fenix.tabstray.NavigationInteractor import org.mozilla.fenix.tabstray.NavigationInteractor
import org.mozilla.fenix.tabstray.TabsTrayInteractor import org.mozilla.fenix.tabstray.TabsTrayInteractor
import org.mozilla.fenix.tabstray.TabsTrayState import org.mozilla.fenix.tabstray.TabsTrayState
@ -45,6 +44,7 @@ import org.mozilla.fenix.tabstray.ext.showWithTheme
@Suppress("LongParameterList") @Suppress("LongParameterList")
class SelectionBannerBinding( class SelectionBannerBinding(
private val context: Context, private val context: Context,
private val binding: ComponentTabstray2Binding,
private val store: TabsTrayStore, private val store: TabsTrayStore,
private val navInteractor: NavigationInteractor, private val navInteractor: NavigationInteractor,
private val tabsTrayInteractor: TabsTrayInteractor, private val tabsTrayInteractor: TabsTrayInteractor,
@ -64,7 +64,7 @@ class SelectionBannerBinding(
override fun start() { override fun start() {
super.start() super.start()
initListeners(containerView) initListeners()
} }
override suspend fun onState(flow: Flow<TabsTrayState>) { override suspend fun onState(flow: Flow<TabsTrayState>) {
@ -89,20 +89,22 @@ class SelectionBannerBinding(
} }
} }
private fun initListeners(containerView: View) { private fun initListeners() {
containerView.share_multi_select.setOnClickListener { val tabsTrayMultiselectItemsBinding = TabstrayMultiselectItemsBinding.bind(binding.root)
tabsTrayMultiselectItemsBinding.shareMultiSelect.setOnClickListener {
navInteractor.onShareTabs(store.state.mode.selectedTabs) navInteractor.onShareTabs(store.state.mode.selectedTabs)
} }
containerView.collect_multi_select.setOnClickListener { tabsTrayMultiselectItemsBinding.collectMultiSelect.setOnClickListener {
navInteractor.onSaveToCollections(store.state.mode.selectedTabs) navInteractor.onSaveToCollections(store.state.mode.selectedTabs)
} }
containerView.exit_multi_select.setOnClickListener { binding.exitMultiSelect.setOnClickListener {
store.dispatch(ExitSelectMode) store.dispatch(ExitSelectMode)
} }
containerView.menu_multi_select.setOnClickListener { anchor -> tabsTrayMultiselectItemsBinding.menuMultiSelect.setOnClickListener { anchor ->
val menu = SelectionMenuIntegration( val menu = SelectionMenuIntegration(
context, context,
store, store,
@ -133,9 +135,9 @@ class SelectionBannerBinding(
@VisibleForTesting @VisibleForTesting
private fun updateSelectTitle(selectedMode: Boolean, tabCount: Int) { private fun updateSelectTitle(selectedMode: Boolean, tabCount: Int) {
if (selectedMode) { if (selectedMode) {
containerView.multiselect_title.text = binding.multiselectTitle.text =
context.getString(R.string.tab_tray_multi_select_title, tabCount) context.getString(R.string.tab_tray_multi_select_title, tabCount)
containerView.multiselect_title.importantForAccessibility = binding.multiselectTitle.importantForAccessibility =
View.IMPORTANT_FOR_ACCESSIBILITY_YES View.IMPORTANT_FOR_ACCESSIBILITY_YES
} }
} }

@ -10,7 +10,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.findFragment import androidx.fragment.app.findFragment
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.component_sync_tabs_tray_layout.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
@ -20,6 +19,7 @@ import mozilla.components.feature.syncedtabs.SyncedTabsFeature
import mozilla.components.feature.syncedtabs.view.SyncedTabsView import mozilla.components.feature.syncedtabs.view.SyncedTabsView
import mozilla.components.support.base.observer.Observable import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry import mozilla.components.support.base.observer.ObserverRegistry
import org.mozilla.fenix.databinding.ComponentSyncTabsTrayLayoutBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.sync.SyncedTabsAdapter import org.mozilla.fenix.sync.SyncedTabsAdapter
import org.mozilla.fenix.sync.SyncedTabsTitleDecoration import org.mozilla.fenix.sync.SyncedTabsTitleDecoration
@ -40,6 +40,8 @@ class SyncedTabsTrayLayout @JvmOverloads constructor(
private val lifecycleProvider = LifecycleViewProvider(this) private val lifecycleProvider = LifecycleViewProvider(this)
private val coroutineScope = CoroutineScope(Dispatchers.Main) private val coroutineScope = CoroutineScope(Dispatchers.Main)
private var _binding: ComponentSyncTabsTrayLayoutBinding? = null
private val binding get() = _binding!!
private val syncedTabsFeature by lazy { private val syncedTabsFeature by lazy {
SyncedTabsFeature( SyncedTabsFeature(
@ -67,14 +69,15 @@ class SyncedTabsTrayLayout @JvmOverloads constructor(
override var listener: SyncedTabsView.Listener? = null override var listener: SyncedTabsView.Listener? = null
override fun onFinishInflate() { override fun onFinishInflate() {
synced_tabs_list.addItemDecoration(SyncedTabsTitleDecoration(context)) _binding = ComponentSyncTabsTrayLayoutBinding.bind(this)
binding.syncedTabsList.addItemDecoration(SyncedTabsTitleDecoration(context))
super.onFinishInflate() super.onFinishInflate()
} }
override fun displaySyncedTabs(syncedTabs: List<SyncedDeviceTabs>) { override fun displaySyncedTabs(syncedTabs: List<SyncedDeviceTabs>) {
coroutineScope.launch { coroutineScope.launch {
(synced_tabs_list.adapter as SyncedTabsAdapter).updateData(syncedTabs) (binding.syncedTabsList.adapter as SyncedTabsAdapter).updateData(syncedTabs)
} }
} }
@ -93,7 +96,7 @@ class SyncedTabsTrayLayout @JvmOverloads constructor(
val errorItem = error.toAdapterItem(descriptionResId, navController) val errorItem = error.toAdapterItem(descriptionResId, navController)
val errorList: List<SyncedTabsAdapter.AdapterItem> = listOf(errorItem) val errorList: List<SyncedTabsAdapter.AdapterItem> = listOf(errorItem)
(synced_tabs_list.adapter as SyncedTabsAdapter).submitList(errorList) (binding.syncedTabsList.adapter as SyncedTabsAdapter).submitList(errorList)
} }
} }
@ -106,6 +109,7 @@ class SyncedTabsTrayLayout @JvmOverloads constructor(
override fun onDetachedFromWindow() { override fun onDetachedFromWindow() {
super.onDetachedFromWindow() super.onDetachedFromWindow()
_binding = null
syncedTabsFeature.stop() syncedTabsFeature.stop()
syncButtonBinding.stop() syncButtonBinding.stop()

@ -89,21 +89,18 @@
app:tabRippleColor="@android:color/transparent"> app:tabRippleColor="@android:color/transparent">
<com.google.android.material.tabs.TabItem <com.google.android.material.tabs.TabItem
android:id="@+id/default_tab_item"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:contentDescription="@string/tab_header_label" android:contentDescription="@string/tab_header_label"
android:layout="@layout/tabs_tray_tab_counter2" /> android:layout="@layout/tabs_tray_tab_counter2" />
<com.google.android.material.tabs.TabItem <com.google.android.material.tabs.TabItem
android:id="@+id/private_tab_item"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:contentDescription="@string/tabs_header_private_tabs_title" android:contentDescription="@string/tabs_header_private_tabs_title"
android:icon="@drawable/ic_private_browsing" /> android:icon="@drawable/ic_private_browsing" />
<com.google.android.material.tabs.TabItem <com.google.android.material.tabs.TabItem
android:id="@+id/synced_tab_item"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:contentDescription="@string/tabs_header_synced_tabs_title" android:contentDescription="@string/tabs_header_synced_tabs_title"

@ -112,7 +112,9 @@ A FrameLayout here is an efficient way of having a views stack while allowing:
android:layout_height="match_parent" android:layout_height="match_parent"
android:contentDescription="@string/mozac_browser_tabstray_open_tab" /> android:contentDescription="@string/mozac_browser_tabstray_open_tab" />
<include layout="@layout/checkbox_item" /> <include
android:id="@+id/checkbox_include"
layout="@layout/checkbox_item" />
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

@ -53,7 +53,9 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:contentDescription="@string/mozac_browser_tabstray_open_tab" /> android:contentDescription="@string/mozac_browser_tabstray_open_tab" />
<include layout="@layout/checkbox_item" /> <include
android:id="@+id/checkbox_include"
layout="@layout/checkbox_item" />
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

@ -10,9 +10,8 @@ import android.widget.TextView
import io.mockk.Called import io.mockk.Called
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify import io.mockk.verify
import kotlinx.android.synthetic.main.sync_tabs_list_item.view.*
import kotlinx.android.synthetic.main.view_synced_tabs_group.view.*
import mozilla.components.browser.storage.sync.Tab import mozilla.components.browser.storage.sync.Tab
import mozilla.components.browser.storage.sync.TabEntry import mozilla.components.browser.storage.sync.TabEntry
import mozilla.components.concept.sync.Device import mozilla.components.concept.sync.Device
@ -24,6 +23,8 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.SyncTabsListItemBinding
import org.mozilla.fenix.databinding.ViewSyncedTabsGroupBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
@ -39,6 +40,8 @@ class SyncedTabsViewHolderTest {
private lateinit var noTabsView: View private lateinit var noTabsView: View
private lateinit var noTabsViewHolder: SyncedTabsViewHolder.NoTabsViewHolder private lateinit var noTabsViewHolder: SyncedTabsViewHolder.NoTabsViewHolder
private lateinit var syncTabsListItemBinding: SyncTabsListItemBinding
private val tab = Tab( private val tab = Tab(
history = listOf( history = listOf(
mockk(), mockk(),
@ -60,11 +63,13 @@ class SyncedTabsViewHolderTest {
tabView = inflater.inflate(SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID, null) tabView = inflater.inflate(SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID, null)
tabViewHolder = SyncedTabsViewHolder.TabViewHolder(tabView) tabViewHolder = SyncedTabsViewHolder.TabViewHolder(tabView)
deviceViewGroupName = mockk(relaxUnitFun = true) syncTabsListItemBinding = SyncTabsListItemBinding.bind(tabView)
deviceView = mockk { val viewSyncedTabsGroupBinding = ViewSyncedTabsGroupBinding.inflate(inflater)
every { synced_tabs_group_name } returns deviceViewGroupName deviceView = mockk()
}
deviceViewHolder = SyncedTabsViewHolder.DeviceViewHolder(deviceView) deviceViewHolder = SyncedTabsViewHolder.DeviceViewHolder(spyk(viewSyncedTabsGroupBinding.root))
deviceViewGroupName = spyk(viewSyncedTabsGroupBinding.syncedTabsGroupName)
titleView = inflater.inflate(SyncedTabsViewHolder.TitleViewHolder.LAYOUT_ID, null) titleView = inflater.inflate(SyncedTabsViewHolder.TitleViewHolder.LAYOUT_ID, null)
titleViewHolder = SyncedTabsViewHolder.TitleViewHolder(titleView) titleViewHolder = SyncedTabsViewHolder.TitleViewHolder(titleView)
@ -77,8 +82,8 @@ class SyncedTabsViewHolderTest {
fun `TabViewHolder binds active tab`() { fun `TabViewHolder binds active tab`() {
tabViewHolder.bind(SyncedTabsAdapter.AdapterItem.Tab(tab), mockk()) tabViewHolder.bind(SyncedTabsAdapter.AdapterItem.Tab(tab), mockk())
assertEquals("Firefox", tabView.synced_tab_item_title.text) assertEquals("Firefox", syncTabsListItemBinding.syncedTabItemTitle.text)
assertEquals("mozilla.org", tabView.synced_tab_item_url.text) assertEquals("mozilla.org", syncTabsListItemBinding.syncedTabItemUrl.text)
} }
@Test @Test
@ -98,7 +103,7 @@ class SyncedTabsViewHolderTest {
} }
deviceViewHolder.bind(SyncedTabsAdapter.AdapterItem.Device(device), mockk()) deviceViewHolder.bind(SyncedTabsAdapter.AdapterItem.Device(device), mockk())
verify { deviceViewGroupName.text = "Charcoal" } assertEquals("Charcoal", deviceViewHolder.binding.syncedTabsGroupName.text)
} }
@Test @Test
@ -109,7 +114,7 @@ class SyncedTabsViewHolderTest {
} }
deviceViewHolder.bind(SyncedTabsAdapter.AdapterItem.Device(device), mockk()) deviceViewHolder.bind(SyncedTabsAdapter.AdapterItem.Device(device), mockk())
verify { deviceViewGroupName.text = "Emerald" } assertEquals("Emerald", deviceViewHolder.binding.syncedTabsGroupName.text)
} }
@Test @Test

@ -8,37 +8,33 @@ import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.Button import android.view.ViewGroup
import android.widget.ImageButton
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.viewbinding.ViewBindings
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import io.mockk.Runs import io.mockk.Runs
import io.mockk.every import io.mockk.every
import io.mockk.just import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.spyk import io.mockk.spyk
import io.mockk.unmockkStatic import io.mockk.unmockkStatic
import io.mockk.verify import io.mockk.verify
import kotlinx.android.synthetic.main.component_tabstray2.*
import kotlinx.android.synthetic.main.component_tabstray2.view.*
import kotlinx.android.synthetic.main.component_tabstray_fab.*
import kotlinx.android.synthetic.main.fragment_tab_tray_dialog.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import mozilla.components.browser.menu.BrowserMenu import mozilla.components.browser.menu.BrowserMenu
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert import org.junit.Assert
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertSame import org.junit.Assert.assertSame
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -47,6 +43,9 @@ import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.databinding.ComponentTabstray2Binding
import org.mozilla.fenix.databinding.ComponentTabstrayFabBinding
import org.mozilla.fenix.databinding.FragmentTabTrayDialogBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@ -58,15 +57,25 @@ import org.mozilla.fenix.utils.allowUndo
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
class TabsTrayFragmentTest { class TabsTrayFragmentTest {
private lateinit var context: Context private lateinit var context: Context
private lateinit var view: View private lateinit var view: ViewGroup
private lateinit var fragment: TabsTrayFragment private lateinit var fragment: TabsTrayFragment
private lateinit var tabsTrayBinding: ComponentTabstray2Binding
private lateinit var tabsTrayDialogBinding: FragmentTabTrayDialogBinding
private lateinit var fabButtonBinding: ComponentTabstrayFabBinding
@Before @Before
fun setup() { fun setup() {
context = mockk(relaxed = true) context = mockk(relaxed = true)
view = mockk(relaxed = true) view = mockk(relaxed = true)
val inflater = LayoutInflater.from(testContext)
tabsTrayDialogBinding = FragmentTabTrayDialogBinding.inflate(inflater)
tabsTrayBinding = ComponentTabstray2Binding.inflate(inflater)
fabButtonBinding = ComponentTabstrayFabBinding.inflate(inflater)
fragment = spyk(TabsTrayFragment()) fragment = spyk(TabsTrayFragment())
fragment._tabsTrayBinding = tabsTrayBinding
fragment._tabsTrayDialogBinding = tabsTrayDialogBinding
fragment._fabButtonBinding = fabButtonBinding
every { fragment.context } returns context every { fragment.context } returns context
every { fragment.view } returns view every { fragment.view } returns view
} }
@ -78,10 +87,7 @@ class TabsTrayFragmentTest {
mockkStatic("androidx.lifecycle.LifecycleOwnerKt") mockkStatic("androidx.lifecycle.LifecycleOwnerKt")
val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true) val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true)
every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope
val newTabButton: ExtendedFloatingActionButton = mockk { fabButtonBinding.newTabButton.isVisible = true
every { visibility } returns View.VISIBLE
}
every { fragment.new_tab_button } returns newTabButton
every { fragment.context } returns testContext // needed for getString() every { fragment.context } returns testContext // needed for getString()
every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs
@ -89,12 +95,12 @@ class TabsTrayFragmentTest {
verify { verify {
lifecycleScope.allowUndo( lifecycleScope.allowUndo(
fragment.view!!, view,
testContext.getString(R.string.snackbar_private_tab_closed), testContext.getString(R.string.snackbar_private_tab_closed),
testContext.getString(R.string.snackbar_deleted_undo), testContext.getString(R.string.snackbar_deleted_undo),
any(), any(),
any(), any(),
newTabButton, fabButtonBinding.newTabButton,
TabsTrayFragment.ELEVATION, TabsTrayFragment.ELEVATION,
false false
) )
@ -112,10 +118,6 @@ class TabsTrayFragmentTest {
mockkStatic("androidx.lifecycle.LifecycleOwnerKt") mockkStatic("androidx.lifecycle.LifecycleOwnerKt")
val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true) val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true)
every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope
val newTabButton: ExtendedFloatingActionButton = mockk {
every { visibility } returns View.GONE
}
every { fragment.new_tab_button } returns newTabButton
every { fragment.context } returns testContext // needed for getString() every { fragment.context } returns testContext // needed for getString()
every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs
@ -123,7 +125,7 @@ class TabsTrayFragmentTest {
verify { verify {
lifecycleScope.allowUndo( lifecycleScope.allowUndo(
fragment.view!!, view,
testContext.getString(R.string.snackbar_private_tab_closed), testContext.getString(R.string.snackbar_private_tab_closed),
testContext.getString(R.string.snackbar_deleted_undo), testContext.getString(R.string.snackbar_deleted_undo),
any(), any(),
@ -146,10 +148,7 @@ class TabsTrayFragmentTest {
mockkStatic("androidx.lifecycle.LifecycleOwnerKt") mockkStatic("androidx.lifecycle.LifecycleOwnerKt")
val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true) val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true)
every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope
val newTabButton: ExtendedFloatingActionButton = mockk { fabButtonBinding.newTabButton.isVisible = true
every { visibility } returns View.VISIBLE
}
every { fragment.new_tab_button } returns newTabButton
every { fragment.context } returns testContext // needed for getString() every { fragment.context } returns testContext // needed for getString()
every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs
@ -157,12 +156,12 @@ class TabsTrayFragmentTest {
verify { verify {
lifecycleScope.allowUndo( lifecycleScope.allowUndo(
fragment.view!!, view,
testContext.getString(R.string.snackbar_tab_closed), testContext.getString(R.string.snackbar_tab_closed),
testContext.getString(R.string.snackbar_deleted_undo), testContext.getString(R.string.snackbar_deleted_undo),
any(), any(),
any(), any(),
newTabButton, fabButtonBinding.newTabButton,
TabsTrayFragment.ELEVATION, TabsTrayFragment.ELEVATION,
false false
) )
@ -180,10 +179,6 @@ class TabsTrayFragmentTest {
mockkStatic("androidx.lifecycle.LifecycleOwnerKt") mockkStatic("androidx.lifecycle.LifecycleOwnerKt")
val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true) val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true)
every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope
val newTabButton: ExtendedFloatingActionButton = mockk {
every { visibility } returns View.GONE
}
every { fragment.new_tab_button } returns newTabButton
every { fragment.context } returns testContext // needed for getString() every { fragment.context } returns testContext // needed for getString()
every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs
@ -191,7 +186,7 @@ class TabsTrayFragmentTest {
verify { verify {
lifecycleScope.allowUndo( lifecycleScope.allowUndo(
fragment.view!!, view,
testContext.getString(R.string.snackbar_tab_closed), testContext.getString(R.string.snackbar_tab_closed),
testContext.getString(R.string.snackbar_deleted_undo), testContext.getString(R.string.snackbar_deleted_undo),
any(), any(),
@ -209,28 +204,25 @@ class TabsTrayFragmentTest {
@Test @Test
fun `WHEN setupPager is called THEN it sets the tray adapter and disables user initiated scrolling`() { fun `WHEN setupPager is called THEN it sets the tray adapter and disables user initiated scrolling`() {
val tray: ViewPager2 = mockk(relaxed = true)
val store: TabsTrayStore = mockk() val store: TabsTrayStore = mockk()
val trayInteractor: TabsTrayInteractor = mockk() val trayInteractor: TabsTrayInteractor = mockk()
val browserInteractor: BrowserTrayInteractor = mockk() val browserInteractor: BrowserTrayInteractor = mockk()
val navigationInteractor: NavigationInteractor = mockk() val navigationInteractor: NavigationInteractor = mockk()
val browserStore: BrowserStore = mockk() val browserStore: BrowserStore = mockk()
every { fragment.tabsTray } returns tray
every { context.components.core.store } returns browserStore every { context.components.core.store } returns browserStore
val adapterSlot = slot<TrayPagerAdapter>()
fragment.setupPager( fragment.setupPager(
context, store, trayInteractor, browserInteractor, navigationInteractor context, store, trayInteractor, browserInteractor, navigationInteractor
) )
verify { tray.adapter = capture(adapterSlot) } val adapter = (tabsTrayBinding.tabsTray.adapter as TrayPagerAdapter)
assertSame(context, adapterSlot.captured.context) assertSame(context, adapter.context)
assertSame(store, adapterSlot.captured.store) assertSame(store, adapter.store)
assertSame(trayInteractor, adapterSlot.captured.interactor) assertSame(trayInteractor, adapter.interactor)
assertSame(browserInteractor, adapterSlot.captured.browserInteractor) assertSame(browserInteractor, adapter.browserInteractor)
assertSame(navigationInteractor, adapterSlot.captured.navInteractor) assertSame(navigationInteractor, adapter.navInteractor)
assertSame(browserStore, adapterSlot.captured.browserStore) assertSame(browserStore, adapter.browserStore)
verify { tray.isUserInputEnabled = false } assertFalse(tabsTrayBinding.tabsTray.isUserInputEnabled)
} }
@Test @Test
@ -238,13 +230,10 @@ class TabsTrayFragmentTest {
try { try {
mockkStatic("org.mozilla.fenix.tabstray.ext.BrowserMenuKt") mockkStatic("org.mozilla.fenix.tabstray.ext.BrowserMenuKt")
val navigationInteractor: NavigationInteractor = mockk() val navigationInteractor: NavigationInteractor = mockk()
val threeDotMenu = ImageButton(testContext)
every { view.tab_tray_overflow } returns threeDotMenu
val metrics: MetricController = mockk(relaxed = true) val metrics: MetricController = mockk(relaxed = true)
every { context.components.analytics.metrics } returns metrics every { context.components.analytics.metrics } returns metrics
every { context.components.core.store } returns mockk() every { context.components.core.store } returns mockk()
every { fragment.tabsTrayStore } returns mockk() every { fragment.tabsTrayStore } returns mockk()
every { fragment.tab_layout } returns mockk<TabLayout>()
val menu: BrowserMenu = mockk { val menu: BrowserMenu = mockk {
every { showWithTheme(any()) } just Runs every { showWithTheme(any()) } just Runs
} }
@ -253,12 +242,12 @@ class TabsTrayFragmentTest {
} }
every { fragment.getTrayMenu(any(), any(), any(), any(), any()) } returns menuBuilder every { fragment.getTrayMenu(any(), any(), any(), any(), any()) } returns menuBuilder
fragment.setupMenu(view, navigationInteractor) fragment.setupMenu(navigationInteractor)
threeDotMenu.performClick() tabsTrayBinding.tabTrayOverflow.performClick()
verify { metrics.track(Event.TabsTrayMenuOpened) } verify { metrics.track(Event.TabsTrayMenuOpened) }
verify { menuBuilder.build() } verify { menuBuilder.build() }
verify { menu.showWithTheme(threeDotMenu) } verify { menu.showWithTheme(tabsTrayBinding.tabTrayOverflow) }
} finally { } finally {
unmockkStatic("org.mozilla.fenix.tabstray.ext.BrowserMenuKt") unmockkStatic("org.mozilla.fenix.tabstray.ext.BrowserMenuKt")
} }
@ -284,16 +273,12 @@ class TabsTrayFragmentTest {
fun `WHEN setupBackgroundDismissalListener is called THEN it sets a click listener for tray's tabLayout and handle`() { fun `WHEN setupBackgroundDismissalListener is called THEN it sets a click listener for tray's tabLayout and handle`() {
var clickCount = 0 var clickCount = 0
val callback: (View) -> Unit = { clickCount++ } val callback: (View) -> Unit = { clickCount++ }
val tabLayout = CoordinatorLayout(testContext)
val handle = Button(testContext)
every { fragment.tabLayout } returns tabLayout
every { fragment.handle } returns handle
fragment.setupBackgroundDismissalListener(callback) fragment.setupBackgroundDismissalListener(callback)
tabLayout.performClick() tabsTrayDialogBinding.tabLayout.performClick()
assertEquals(1, clickCount) assertEquals(1, clickCount)
handle.performClick() tabsTrayBinding.handle.performClick()
assertEquals(2, clickCount) assertEquals(2, clickCount)
} }
@ -337,13 +322,19 @@ class TabsTrayFragmentTest {
val tabLayout: TabLayout = mockk { val tabLayout: TabLayout = mockk {
every { getTabAt(any()) } returns tab every { getTabAt(any()) } returns tab
} }
every { fragment.tab_layout } returns tabLayout
every { fragment.tabsTray } returns tabsTray
fragment.selectTabPosition(2, true) mockkStatic(ViewBindings::class) {
every { ViewBindings.findChildViewById<View>(tabsTrayBinding.root, tabsTrayBinding.tabsTray.id) } returns tabsTray
every { ViewBindings.findChildViewById<View>(tabsTrayBinding.root, tabsTrayBinding.tabLayout.id) } returns tabLayout
tabsTrayBinding = ComponentTabstray2Binding.bind(tabsTrayBinding.root)
fragment._tabsTrayBinding = tabsTrayBinding
verify { tabsTray.setCurrentItem(2, true) } fragment.selectTabPosition(2, true)
verify { tab.select() }
verify { tabsTray.setCurrentItem(2, true) }
verify { tab.select() }
}
} }
@Test @Test
@ -371,9 +362,10 @@ class TabsTrayFragmentTest {
@Test @Test
fun `WHEN the tabs tray is declared in XML THEN certain options are set for the behavior`() { fun `WHEN the tabs tray is declared in XML THEN certain options are set for the behavior`() {
val view: View = LayoutInflater.from(testContext) tabsTrayBinding = ComponentTabstray2Binding.inflate(
.inflate(R.layout.component_tabstray2, CoordinatorLayout(testContext), true) LayoutInflater.from(testContext), CoordinatorLayout(testContext), true
val behavior = BottomSheetBehavior.from(view.tab_wrapper) )
val behavior = BottomSheetBehavior.from(tabsTrayBinding.tabWrapper)
Assert.assertFalse(behavior.isFitToContents) Assert.assertFalse(behavior.isFitToContents)
Assert.assertFalse(behavior.skipCollapsed) Assert.assertFalse(behavior.skipCollapsed)
@ -382,55 +374,31 @@ class TabsTrayFragmentTest {
@Test @Test
fun `GIVEN a grid TabView WHEN onConfigurationChanged is called THEN the adapter structure is updated`() { fun `GIVEN a grid TabView WHEN onConfigurationChanged is called THEN the adapter structure is updated`() {
val tray: ViewPager2 = mockk(relaxed = true)
val store: TabsTrayStore = mockk()
val trayInteractor: TabsTrayInteractor = mockk()
val browserInteractor: BrowserTrayInteractor = mockk()
val navigationInteractor: NavigationInteractor = mockk()
val browserStore: BrowserStore = mockk()
every { fragment.tabsTray } returns tray
every { context.components.core.store } returns browserStore
every { context.settings().gridTabView } returns true every { context.settings().gridTabView } returns true
val adapter = mockk<TrayPagerAdapter>(relaxed = true)
fragment.setupPager( tabsTrayBinding.tabsTray.adapter = adapter
context, store, trayInteractor, browserInteractor, navigationInteractor fragment._tabsTrayBinding = tabsTrayBinding
)
val trayBehaviorManager: TabSheetBehaviorManager = mockk(relaxed = true) val trayBehaviorManager: TabSheetBehaviorManager = mockk(relaxed = true)
fragment.trayBehaviorManager = trayBehaviorManager fragment.trayBehaviorManager = trayBehaviorManager
val newConfiguration = Configuration() val newConfiguration = Configuration()
fragment.onConfigurationChanged(newConfiguration) fragment.onConfigurationChanged(newConfiguration)
verify { tray.adapter?.notifyDataSetChanged() } verify { adapter.notifyDataSetChanged() }
} }
@Test @Test
fun `GIVEN a list TabView WHEN onConfigurationChanged is called THEN the adapter structure is NOT updated`() { fun `GIVEN a list TabView WHEN onConfigurationChanged is called THEN the adapter structure is NOT updated`() {
val tray: ViewPager2 = mockk(relaxed = true)
val store: TabsTrayStore = mockk()
val trayInteractor: TabsTrayInteractor = mockk()
val browserInteractor: BrowserTrayInteractor = mockk()
val navigationInteractor: NavigationInteractor = mockk()
val browserStore: BrowserStore = mockk()
every { fragment.tabsTray } returns tray
every { context.components.core.store } returns browserStore
every { context.settings().gridTabView } returns false every { context.settings().gridTabView } returns false
val adapter = mockk<TrayPagerAdapter>(relaxed = true)
fragment.setupPager( tabsTrayBinding.tabsTray.adapter = adapter
context, store, trayInteractor, browserInteractor, navigationInteractor fragment._tabsTrayBinding = tabsTrayBinding
)
val trayBehaviorManager: TabSheetBehaviorManager = mockk(relaxed = true) val trayBehaviorManager: TabSheetBehaviorManager = mockk(relaxed = true)
fragment.trayBehaviorManager = trayBehaviorManager fragment.trayBehaviorManager = trayBehaviorManager
val newConfiguration = Configuration() val newConfiguration = Configuration()
fragment.onConfigurationChanged(newConfiguration) fragment.onConfigurationChanged(newConfiguration)
verify(exactly = 0) { tray.adapter?.notifyDataSetChanged() } verify(exactly = 0) { adapter.notifyDataSetChanged() }
} }
} }

@ -4,8 +4,10 @@
package org.mozilla.fenix.tabstray.browser package org.mozilla.fenix.tabstray.browser
import android.view.LayoutInflater
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify import io.mockk.verify
import mozilla.components.browser.tabstray.TabsAdapter.Companion.PAYLOAD_DONT_HIGHLIGHT_SELECTED_ITEM 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.TabsAdapter.Companion.PAYLOAD_HIGHLIGHT_SELECTED_ITEM
@ -14,6 +16,7 @@ import mozilla.components.concept.tabstray.Tabs
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.databinding.TabTrayItemBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.selection.SelectionHolder import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.tabstray.TabsTrayStore import org.mozilla.fenix.tabstray.TabsTrayStore
@ -51,10 +54,20 @@ class BrowserTabsAdapterTest {
@Test @Test
fun `WHEN the selection holder is set THEN update the selected tab`() { fun `WHEN the selection holder is set THEN update the selected tab`() {
val adapter = BrowserTabsAdapter(context, interactor, store) val adapter = BrowserTabsAdapter(context, interactor, store)
val holder = mockk<AbstractBrowserTabViewHolder>(relaxed = true) val binding = TabTrayItemBinding.inflate(LayoutInflater.from(testContext))
val holder = spyk(
BrowserTabListViewHolder(
imageLoader = mockk(),
browserTrayInteractor = interactor,
store = store,
selectionHolder = null,
itemView = binding.root
)
)
val tab = createTab("tab1") val tab = createTab("tab1")
every { holder.tab }.answers { tab } every { holder.tab }.answers { tab }
testSelectionHolder.internalState.add(tab) testSelectionHolder.internalState.add(tab)
adapter.selectionHolder = testSelectionHolder adapter.selectionHolder = testSelectionHolder
@ -69,7 +82,7 @@ class BrowserTabsAdapterTest {
adapter.onBindViewHolder(holder, 0, listOf(PAYLOAD_DONT_HIGHLIGHT_SELECTED_ITEM)) adapter.onBindViewHolder(holder, 0, listOf(PAYLOAD_DONT_HIGHLIGHT_SELECTED_ITEM))
verify { holder.showTabIsMultiSelectEnabled(true) } verify { holder.showTabIsMultiSelectEnabled(any(), true) }
} }
private val testSelectionHolder = object : SelectionHolder<Tab> { private val testSelectionHolder = object : SelectionHolder<Tab> {

Loading…
Cancel
Save