For #19135 - Refactor the behavior code to support more functionality later

Setup a new TabSheetBehaviorManager with all the dependencies it needs to
set the initial tray's behavior.
This same manager will later be called to update behavior's properties.
upstream-sync
Mugurell 3 years ago
parent c3001fff41
commit d7544337b8

@ -0,0 +1,59 @@
/* 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
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
/**
* Helper class for updating how the tray looks and behaves depending on app state / internal tray state.
*
* @param behavior [BottomSheetBehavior] that will actually control the tray.
* @param isLandscape whether the device is currently is portrait or landscape.
* @param maxNumberOfTabs highest number of tabs in each tray page.
* @param numberForExpandingTray limit depending on which the tray should be collapsed or expanded.
* @param navigationInteractor [NavigationInteractor] used for tray updates / navigation.
*/
internal class TabSheetBehaviorManager(
behavior: BottomSheetBehavior<ConstraintLayout>,
isLandscape: Boolean,
maxNumberOfTabs: Int,
numberForExpandingTray: Int,
navigationInteractor: NavigationInteractor
) {
init {
behavior.addBottomSheetCallback(
TraySheetBehaviorCallback(behavior, navigationInteractor)
)
behavior.state = if (isLandscape || maxNumberOfTabs >= numberForExpandingTray) {
BottomSheetBehavior.STATE_EXPANDED
} else {
BottomSheetBehavior.STATE_COLLAPSED
}
}
}
@VisibleForTesting
internal class TraySheetBehaviorCallback(
@VisibleForTesting internal val behavior: BottomSheetBehavior<ConstraintLayout>,
@VisibleForTesting internal val trayInteractor: NavigationInteractor
) : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == STATE_HIDDEN) {
trayInteractor.onTabTrayDismissed()
} else if (newState == BottomSheetBehavior.STATE_HALF_EXPANDED) {
// We only support expanded and collapsed states.
// But why??
behavior.state = STATE_HIDDEN
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
}

@ -12,7 +12,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
@ -61,7 +60,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
private lateinit var browserTrayInteractor: BrowserTrayInteractor
private lateinit var tabsTrayInteractor: TabsTrayInteractor
private lateinit var tabsTrayController: DefaultTabsTrayController
private lateinit var behavior: BottomSheetBehavior<ConstraintLayout>
private lateinit var trayBehaviorManager: TabSheetBehaviorManager
private val tabLayoutMediator = ViewBoundFeatureWrapper<TabLayoutMediator>()
private val tabCounterBinding = ViewBoundFeatureWrapper<TabCounterBinding>()
@ -85,10 +84,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
savedInstanceState: Bundle?
): View {
val containerView = inflater.inflate(R.layout.fragment_tab_tray_dialog, container, false)
val view: View = LayoutInflater.from(containerView.context)
.inflate(R.layout.component_tabstray2, containerView as ViewGroup, true)
behavior = BottomSheetBehavior.from(view.tab_wrapper)
inflater.inflate(R.layout.component_tabstray2, containerView as ViewGroup, true)
tabsTrayStore = StoreProvider.get(this) { TabsTrayStore() }
@ -162,7 +158,8 @@ class TabsTrayFragment : AppCompatDialogFragment() {
dismissAllowingStateLoss()
}
behavior.setUpTrayBehavior(
trayBehaviorManager = TabSheetBehaviorManager(
behavior = BottomSheetBehavior.from(view.tab_wrapper),
isLandscape = requireContext().resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE,
maxNumberOfTabs = max(
requireContext().components.core.store.state.normalTabs.size,

@ -1,44 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.tabstray
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
class TraySheetBehaviorCallback(
private val behavior: BottomSheetBehavior<ConstraintLayout>,
private val trayInteractor: NavigationInteractor
) : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == STATE_HIDDEN) {
trayInteractor.onTabTrayDismissed()
} else if (newState == BottomSheetBehavior.STATE_HALF_EXPANDED) {
// We only support expanded and collapsed states.
// But why??
behavior.state = STATE_HIDDEN
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
}
fun BottomSheetBehavior<ConstraintLayout>.setUpTrayBehavior(
isLandscape: Boolean,
maxNumberOfTabs: Int,
numberForExpandingTray: Int,
navigationInteractor: DefaultNavigationInteractor
) {
addBottomSheetCallback(
TraySheetBehaviorCallback(this, navigationInteractor)
)
state = if (isLandscape || maxNumberOfTabs >= numberForExpandingTray) {
BottomSheetBehavior.STATE_EXPANDED
} else {
BottomSheetBehavior.STATE_COLLAPSED
}
}

@ -0,0 +1,126 @@
/* 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
import androidx.constraintlayout.widget.ConstraintLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_DRAGGING
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_SETTLING
import io.mockk.Called
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Test
class TabSheetBehaviorManagerTest {
@Test
fun `WHEN state is hidden THEN invoke interactor`() {
val interactor = mockk<NavigationInteractor>(relaxed = true)
val callback = TraySheetBehaviorCallback(mockk(), interactor)
callback.onStateChanged(mockk(), STATE_HIDDEN)
verify { interactor.onTabTrayDismissed() }
}
@Test
fun `WHEN state is half-expanded THEN close the tray`() {
val behavior = mockk<BottomSheetBehavior<ConstraintLayout>>(relaxed = true)
val callback = TraySheetBehaviorCallback(behavior, mockk())
callback.onStateChanged(mockk(), STATE_HALF_EXPANDED)
verify { behavior.state = STATE_HIDDEN }
}
@Test
fun `WHEN other states are invoked THEN do nothing`() {
val behavior = mockk<BottomSheetBehavior<ConstraintLayout>>(relaxed = true)
val interactor = mockk<NavigationInteractor>(relaxed = true)
val callback = TraySheetBehaviorCallback(behavior, interactor)
callback.onStateChanged(mockk(), STATE_COLLAPSED)
callback.onStateChanged(mockk(), STATE_DRAGGING)
callback.onStateChanged(mockk(), STATE_SETTLING)
callback.onStateChanged(mockk(), STATE_EXPANDED)
verify { behavior wasNot Called }
verify { interactor wasNot Called }
}
@Test
fun `GIVEN a behavior WHEN TabSheetBehaviorManager is initialized THEN it sets a TraySheetBehaviorCallback on that behavior`() {
val behavior: BottomSheetBehavior<ConstraintLayout> = mockk(relaxed = true)
val navigationInteractor: NavigationInteractor = mockk()
val callbackCaptor = slot<TraySheetBehaviorCallback>()
TabSheetBehaviorManager(behavior, true, 2, 2, navigationInteractor)
verify { behavior.addBottomSheetCallback(capture(callbackCaptor)) }
assertSame(behavior, callbackCaptor.captured.behavior)
assertSame(navigationInteractor, callbackCaptor.captured.trayInteractor)
}
@Test
fun `GIVEN more tabs opened than the expanding limit and portrait orientation WHEN TabSheetBehaviorManager is initialized THEN the behavior is set as expanded`() {
val behavior = BottomSheetBehavior<ConstraintLayout>()
TabSheetBehaviorManager(behavior, false, 5, 4, mockk())
assertEquals(STATE_EXPANDED, behavior.state)
}
@Test
fun `GIVEN the number of tabs opened is exactly the expanding limit and portrait orientation WHEN TabSheetBehaviorManager is initialized THEN the behavior is set as expanded`() {
val behavior = BottomSheetBehavior<ConstraintLayout>()
TabSheetBehaviorManager(behavior, false, 5, 5, mockk())
assertEquals(STATE_EXPANDED, behavior.state)
}
@Test
fun `GIVEN fewer tabs opened than the expanding limit and portrait orientation WHEN TabSheetBehaviorManager is initialized THEN the behavior is set as collapsed`() {
val behavior = BottomSheetBehavior<ConstraintLayout>()
TabSheetBehaviorManager(behavior, false, 4, 5, mockk())
assertEquals(STATE_COLLAPSED, behavior.state)
}
@Test
fun `GIVEN more tabs opened than the expanding limit and landscape orientation WHEN TabSheetBehaviorManager is initialized THEN the behavior is set as expanded`() {
val behavior = BottomSheetBehavior<ConstraintLayout>()
TabSheetBehaviorManager(behavior, true, 5, 4, mockk())
assertEquals(STATE_EXPANDED, behavior.state)
}
@Test
fun `GIVEN the number of tabs opened is exactly the expanding limit and landscape orientation WHEN TabSheetBehaviorManager is initialized THEN the behavior is set as expanded`() {
val behavior = BottomSheetBehavior<ConstraintLayout>()
TabSheetBehaviorManager(behavior, true, 5, 5, mockk())
assertEquals(STATE_EXPANDED, behavior.state)
}
@Test
fun `GIVEN fewer tabs opened than the expanding limit and landscape orientation WHEN TabSheetBehaviorManager is initialized THEN the behavior is set as expanded`() {
val behavior = BottomSheetBehavior<ConstraintLayout>()
TabSheetBehaviorManager(behavior, true, 4, 5, mockk())
assertEquals(STATE_EXPANDED, behavior.state)
}
}

@ -1,93 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.tabstray
import androidx.constraintlayout.widget.ConstraintLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_DRAGGING
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_SETTLING
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import io.mockk.Called
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import org.junit.Test
class TraySheetBehaviorCallbackTest {
@Test
fun `WHEN state is hidden THEN invoke interactor`() {
val interactor = mockk<NavigationInteractor>(relaxed = true)
val callback = TraySheetBehaviorCallback(mockk(), interactor)
callback.onStateChanged(mockk(), STATE_HIDDEN)
verify { interactor.onTabTrayDismissed() }
}
@Test
fun `WHEN state is half-expanded THEN close the tray`() {
val behavior = mockk<BottomSheetBehavior<ConstraintLayout>>(relaxed = true)
val callback = TraySheetBehaviorCallback(behavior, mockk())
callback.onStateChanged(mockk(), STATE_HALF_EXPANDED)
verify { behavior.state = STATE_HIDDEN }
}
@Test
fun `WHEN other states are invoked THEN do nothing`() {
val behavior = mockk<BottomSheetBehavior<ConstraintLayout>>(relaxed = true)
val interactor = mockk<NavigationInteractor>(relaxed = true)
val callback = TraySheetBehaviorCallback(behavior, interactor)
callback.onStateChanged(mockk(), STATE_COLLAPSED)
callback.onStateChanged(mockk(), STATE_DRAGGING)
callback.onStateChanged(mockk(), STATE_SETTLING)
callback.onStateChanged(mockk(), STATE_EXPANDED)
verify { behavior wasNot Called }
verify { interactor wasNot Called }
}
@Test
fun `GIVEN portraitMode and 5 tabs WHEN setUpTrayBehavior THEN add TraySheetBehaviorCallback and STATE_COLLAPSED`() {
// given
val behavior = spyk(BottomSheetBehavior<ConstraintLayout>())
val interactor = mockk<DefaultNavigationInteractor>(relaxed = true)
// when
behavior.setUpTrayBehavior(
isLandscape = false,
maxNumberOfTabs = 5,
numberForExpandingTray = TabsTrayFragment.EXPAND_AT_LIST_SIZE,
navigationInteractor = interactor
)
// then
assert(behavior.state == STATE_EXPANDED)
}
@Test
fun `GIVEN portraitMode and 2 tabs WHEN setUpTrayBehavior THEN add TraySheetBehaviorCallback and STATE_COLLAPSED`() {
// given
val behavior = spyk(BottomSheetBehavior<ConstraintLayout>())
val interactor = mockk<DefaultNavigationInteractor>(relaxed = true)
// when
behavior.setUpTrayBehavior(
isLandscape = false,
maxNumberOfTabs = 2,
numberForExpandingTray = TabsTrayFragment.EXPAND_AT_LIST_SIZE,
navigationInteractor = interactor
)
// then
assert(behavior.state == STATE_COLLAPSED)
}
}
Loading…
Cancel
Save