diff --git a/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt b/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt index 74264d157310..05acedbb5ce0 100644 --- a/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt +++ b/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt @@ -8,14 +8,25 @@ import android.content.res.Configuration import android.util.DisplayMetrics import android.view.View import androidx.annotation.VisibleForTesting +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN import mozilla.components.support.ktx.android.util.dpToPx @VisibleForTesting internal const val EXPANDED_OFFSET_IN_LANDSCAPE_DP = 0 @VisibleForTesting internal const val EXPANDED_OFFSET_IN_PORTRAIT_DP = 40 +/** + * The default max dim value of the [TabsTrayDialog]. + */ +private const val DEFAULT_MAX_DIM = 0.6f + +/** + * The dim amount is 0.0 - 1.0 inclusive. We use this to convert the view element to the dim scale. + */ +private const val DIM_CONVERSION = 1000f + /** * Helper class for updating how the tray looks and behaves depending on app state / internal tray state. * @@ -23,7 +34,6 @@ import mozilla.components.support.ktx.android.util.dpToPx * @param orientation current Configuration.ORIENTATION_* of the device. * @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. * @param displayMetrics [DisplayMetrics] used for adapting resources to the current display. */ internal class TabSheetBehaviorManager( @@ -31,18 +41,12 @@ internal class TabSheetBehaviorManager( orientation: Int, private val maxNumberOfTabs: Int, private val numberForExpandingTray: Int, - navigationInteractor: NavigationInteractor, private val displayMetrics: DisplayMetrics, ) { @VisibleForTesting internal var currentOrientation = orientation init { - behavior.skipCollapsed = true - behavior.addBottomSheetCallback( - TraySheetBehaviorCallback(behavior, navigationInteractor), - ) - val isInLandscape = isLandscape(orientation) updateBehaviorExpandedOffset(isInLandscape) updateBehaviorState(isInLandscape) @@ -83,21 +87,78 @@ internal class TabSheetBehaviorManager( internal fun isLandscape(orientation: Int) = Configuration.ORIENTATION_LANDSCAPE == orientation } -@VisibleForTesting internal class TraySheetBehaviorCallback( @get:VisibleForTesting internal val behavior: BottomSheetBehavior, @get:VisibleForTesting internal val trayInteractor: NavigationInteractor, + private val tabsTrayDialog: TabsTrayDialog, + private var newTabFab: View, ) : BottomSheetBehavior.BottomSheetCallback() { + @VisibleForTesting + var draggedLowestSheetTop: Int? = null + override fun onStateChanged(bottomSheet: View, newState: Int) { - if (newState == STATE_HIDDEN) { - trayInteractor.onTabTrayDismissed() - } else if (newState == BottomSheetBehavior.STATE_HALF_EXPANDED) { + when (newState) { + BottomSheetBehavior.STATE_HIDDEN -> trayInteractor.onTabTrayDismissed() + // We only support expanded and collapsed states. // Otherwise the tray may be left in an unusable state. See #14980. - behavior.state = STATE_HIDDEN + BottomSheetBehavior.STATE_HALF_EXPANDED -> + behavior.state = BottomSheetBehavior.STATE_HIDDEN + + // Reset the dragged lowest top value + BottomSheetBehavior.STATE_EXPANDED, BottomSheetBehavior.STATE_COLLAPSED -> { + draggedLowestSheetTop = null + } + + BottomSheetBehavior.STATE_DRAGGING, BottomSheetBehavior.STATE_SETTLING -> { + // Do nothing. Both cases are handled in the onSlide function. + } + } + } + + override fun onSlide(bottomSheet: View, slideOffset: Float) { + setTabsTrayDialogDimAmount(bottomSheet.top) + setFabY(bottomSheet.top) + } + + private fun setTabsTrayDialogDimAmount(bottomSheetTop: Int) { + // Get any displayed bottom system bar. + val bottomSystemBarHeight = + ViewCompat.getRootWindowInsets(newTabFab) + ?.getInsets(WindowInsetsCompat.Type.systemBars())?.bottom ?: 0 + + // Calculate and convert delta to dim amount. + val appVisibleBottom = newTabFab.rootView.bottom - bottomSystemBarHeight + val trayTopAppBottomDelta = appVisibleBottom - bottomSheetTop + val convertedDimValue = trayTopAppBottomDelta / DIM_CONVERSION + + if (convertedDimValue < DEFAULT_MAX_DIM) { + tabsTrayDialog.window?.setDimAmount(convertedDimValue) + } + } + + private fun setFabY(bottomSheetTop: Int) { + if (behavior.state == BottomSheetBehavior.STATE_DRAGGING) { + draggedLowestSheetTop = getDraggedLowestSheetTop(bottomSheetTop) + + val dynamicSheetButtonDelta = newTabFab.top - draggedLowestSheetTop!! + newTabFab.y = getUpdatedFabY(bottomSheetTop, dynamicSheetButtonDelta) + } + + if (behavior.state == BottomSheetBehavior.STATE_SETTLING) { + val dynamicSheetButtonDelta = newTabFab.top - getDraggedLowestSheetTop(bottomSheetTop) + newTabFab.y = getUpdatedFabY(bottomSheetTop, dynamicSheetButtonDelta) } } - override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit + private fun getDraggedLowestSheetTop(currentBottomSheetTop: Int) = + if (draggedLowestSheetTop == null || currentBottomSheetTop < draggedLowestSheetTop!!) { + currentBottomSheetTop + } else { + draggedLowestSheetTop!! + } + + private fun getUpdatedFabY(bottomSheetTop: Int, dynamicSheetButtonDelta: Int) = + (bottomSheetTop + dynamicSheetButtonDelta).toFloat() } diff --git a/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt index a4b30dd7383d..b5b93967ddeb 100644 --- a/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.tabstray +import android.app.Dialog import android.content.Context import android.content.res.Configuration import android.os.Build @@ -80,6 +81,7 @@ enum class TabsTrayAccessPoint { class TabsTrayFragment : AppCompatDialogFragment() { @VisibleForTesting internal lateinit var tabsTrayStore: TabsTrayStore + private lateinit var tabsTrayDialog: TabsTrayDialog private lateinit var tabsTrayInteractor: TabsTrayInteractor private lateinit var tabsTrayController: DefaultTabsTrayController private lateinit var navigationInteractor: DefaultNavigationInteractor @@ -126,15 +128,7 @@ class TabsTrayFragment : AppCompatDialogFragment() { setStyle(STYLE_NO_TITLE, R.style.TabTrayDialogStyle) } - override fun onCreateDialog(savedInstanceState: Bundle?) = - TabsTrayDialog(requireContext(), theme) { tabsTrayInteractor } - - @Suppress("LongMethod") - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val args by navArgs() args.accessPoint.takeIf { it != TabsTrayAccessPoint.None }?.let { TabsTray.accessPoint[it.name.lowercase()].add() @@ -196,6 +190,16 @@ class TabsTrayFragment : AppCompatDialogFragment() { controller = tabsTrayController, ) + tabsTrayDialog = TabsTrayDialog(requireContext(), theme) { tabsTrayInteractor } + return tabsTrayDialog + } + + @Suppress("LongMethod") + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { _tabsTrayDialogBinding = FragmentTabTrayDialogBinding.inflate( inflater, container, @@ -275,7 +279,7 @@ class TabsTrayFragment : AppCompatDialogFragment() { true, ) _fabButtonBinding = ComponentTabstrayFabBinding.inflate( - LayoutInflater.from(tabsTrayDialogBinding.root.context), + inflater, tabsTrayDialogBinding.root, true, ) @@ -310,8 +314,27 @@ class TabsTrayFragment : AppCompatDialogFragment() { } else { tabsTrayBinding.tabWrapper } + + val newTabFab = if (requireContext().settings().enableTabsTrayToCompose) { + fabButtonComposeBinding.root + } else { + fabButtonBinding.newTabButton + } + + val behavior = BottomSheetBehavior.from(rootView).apply { + addBottomSheetCallback( + TraySheetBehaviorCallback( + this, + navigationInteractor, + tabsTrayDialog, + newTabFab, + ), + ) + skipCollapsed = true + } + trayBehaviorManager = TabSheetBehaviorManager( - behavior = BottomSheetBehavior.from(rootView), + behavior = behavior, orientation = resources.configuration.orientation, maxNumberOfTabs = max( requireContext().components.core.store.state.normalTabs.size, @@ -322,7 +345,6 @@ class TabsTrayFragment : AppCompatDialogFragment() { } else { EXPAND_AT_LIST_SIZE }, - navigationInteractor = navigationInteractor, displayMetrics = requireContext().resources.displayMetrics, ) diff --git a/fenix/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt b/fenix/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt index 9ee9330f8a3c..73aeae703818 100644 --- a/fenix/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt +++ b/fenix/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt @@ -6,6 +6,7 @@ package org.mozilla.fenix.tabstray import android.content.res.Configuration import android.util.DisplayMetrics +import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED @@ -14,18 +15,18 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDE 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 com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import io.mockk.Called import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic -import io.mockk.slot import io.mockk.spyk import io.mockk.unmockkStatic import io.mockk.verify import mozilla.components.support.ktx.android.util.dpToPx import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse -import org.junit.Assert.assertSame +import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test @@ -34,7 +35,7 @@ class TabSheetBehaviorManagerTest { @Test fun `WHEN state is hidden THEN invoke interactor`() { val interactor = mockk(relaxed = true) - val callback = TraySheetBehaviorCallback(mockk(), interactor) + val callback = TraySheetBehaviorCallback(mockk(), interactor, mockk(), mockk()) callback.onStateChanged(mockk(), STATE_HIDDEN) @@ -44,7 +45,7 @@ class TabSheetBehaviorManagerTest { @Test fun `WHEN state is half-expanded THEN close the tray`() { val behavior = mockk>(relaxed = true) - val callback = TraySheetBehaviorCallback(behavior, mockk()) + val callback = TraySheetBehaviorCallback(behavior, mockk(), mockk(), mockk()) callback.onStateChanged(mockk(), STATE_HALF_EXPANDED) @@ -55,7 +56,7 @@ class TabSheetBehaviorManagerTest { fun `WHEN other states are invoked THEN do nothing`() { val behavior = mockk>(relaxed = true) val interactor = mockk(relaxed = true) - val callback = TraySheetBehaviorCallback(behavior, interactor) + val callback = TraySheetBehaviorCallback(behavior, interactor, mockk(), mockk()) callback.onStateChanged(mockk(), STATE_COLLAPSED) callback.onStateChanged(mockk(), STATE_DRAGGING) @@ -67,27 +68,359 @@ class TabSheetBehaviorManagerTest { } @Test - fun `GIVEN a behavior WHEN TabSheetBehaviorManager is initialized THEN it sets a TraySheetBehaviorCallback on that behavior`() { - val behavior: BottomSheetBehavior = mockk(relaxed = true) - val navigationInteractor: NavigationInteractor = mockk() - val callbackCaptor = slot() + fun `GIVEN converted dim value is more than max dim WHEN onSlide is called THEN it does not set the dialog dim amount`() { + val behavior = mockk>(relaxed = true) + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + val bottomSheet = mockk(relaxed = true) + every { bottomSheet.top } returns 300 // (1000 - 300) / 1000 = 0.7 + + callback.onSlide(bottomSheet, 1f) + + verify(exactly = 0) { tabsTrayDialog.window?.setDimAmount(any()) } + } + + @Test + fun `GIVEN converted dim value is at max dim WHEN onSlide is called THEN it does not set the dialog dim amount`() { + val behavior = mockk>(relaxed = true) + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + val bottomSheet = mockk(relaxed = true) + every { bottomSheet.top } returns 400 // (1000 - 400) / 1000 = 0.6 + + callback.onSlide(bottomSheet, 1f) + + verify(exactly = 0) { tabsTrayDialog.window?.setDimAmount(any()) } + } + + @Test + fun `GIVEN converted dim value is less than max dim WHEN onSlide is called THEN it sets the dialog dim amount`() { + val behavior = mockk>(relaxed = true) + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + val bottomSheet = mockk(relaxed = true) + every { bottomSheet.top } returns 500 // (1000 - 500) / 1000 = 0.5 + + callback.onSlide(bottomSheet, 1f) + + verify(exactly = 1) { tabsTrayDialog.window?.setDimAmount(any()) } + } + + @Test + fun `GIVEN behaviour state is 'dragging' & draggedLowestSheetTop is null WHEN onSlide is called THEN draggedLowestSheetTop is set to currentBottomSheetTop`() { + val behavior = mockk>(relaxed = true) + every { behavior.state } returns STATE_DRAGGING + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + every { fab.top } returns 900 + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + val bottomSheetTop = 10 + + // draggedLowestSheetTop is null + val bottomSheet = mockk(relaxed = true) + every { bottomSheet.top } returns bottomSheetTop + callback.onSlide(bottomSheet, 1f) + verify { fab.y = 900f } + + assertEquals(bottomSheetTop, callback.draggedLowestSheetTop) + } + + @Test + fun `GIVEN behaviour state is 'dragging' & currentBottomSheetTop is same as draggedLowestSheetTop WHEN onSlide is called THEN draggedLowestSheetTop is same value`() { + val behavior = mockk>(relaxed = true) + every { behavior.state } returns STATE_DRAGGING + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + every { fab.top } returns 900 + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + val bottomSheetTop = 10 + + // draggedLowestSheetTop is null + val bottomSheet1 = mockk(relaxed = true) + every { bottomSheet1.top } returns bottomSheetTop + callback.onSlide(bottomSheet1, 1f) + verify { fab.y = 900f } + + // currentBottomSheetTop is same as draggedLowestSheetTop + val bottomSheet2 = mockk(relaxed = true) + every { bottomSheet2.top } returns bottomSheetTop + callback.onSlide(bottomSheet2, 1f) + verify { fab.y = 900f } + + assertEquals(bottomSheetTop, callback.draggedLowestSheetTop) + } + + @Test + fun `GIVEN behaviour state is 'dragging' & currentBottomSheetTop is more than draggedLowestSheetTop WHEN onSlide is called THEN draggedLowestSheetTop is same value`() { + val behavior = mockk>(relaxed = true) + every { behavior.state } returns STATE_DRAGGING + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + every { fab.top } returns 900 + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + val originalBottomSheetTop = 10 + + // draggedLowestSheetTop is null + val bottomSheet1 = mockk(relaxed = true) + every { bottomSheet1.top } returns originalBottomSheetTop + callback.onSlide(bottomSheet1, 1f) + verify { fab.y = 900f } + + // currentBottomSheetTop is same as draggedLowestSheetTop + val newBottomSheetTop = originalBottomSheetTop + 1 + val bottomSheet2 = mockk(relaxed = true) + every { bottomSheet2.top } returns newBottomSheetTop + callback.onSlide(bottomSheet2, 1f) + verify { fab.y = 901f } + + assertEquals(originalBottomSheetTop, callback.draggedLowestSheetTop) + } + + @Test + fun `GIVEN behaviour state is 'dragging' & currentBottomSheetTop less than draggedLowestSheetTop WHEN onSlide is called THEN draggedLowestSheetTop is set to currentBottomSheetTop`() { + val behavior = mockk>(relaxed = true) + every { behavior.state } returns STATE_DRAGGING + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + every { fab.top } returns 900 + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + // draggedLowestSheetTop is null + val bottomSheet1 = mockk(relaxed = true) + every { bottomSheet1.top } returns 10 + callback.onSlide(bottomSheet1, 1f) + verify { fab.y = 900f } + + // currentBottomSheetTop is less than draggedLowestSheetTop + val newBottomSheetTop = 9 + val bottomSheet2 = mockk(relaxed = true) + every { bottomSheet2.top } returns newBottomSheetTop + callback.onSlide(bottomSheet2, 1f) + verify { fab.y = 900f } + + assertEquals(newBottomSheetTop, callback.draggedLowestSheetTop) + } + + @Test + fun `GIVEN behaviour state is 'settling' & draggedLowestSheetTop is null WHEN onSlide is called THEN draggedLowestSheetTop is set to fab y is set`() { + val behavior = mockk>(relaxed = true) + every { behavior.state } returns STATE_SETTLING + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + every { fab.top } returns 900 + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + // draggedLowestSheetTop is null + val bottomSheet = mockk(relaxed = true) + every { bottomSheet.top } returns 10 + callback.onSlide(bottomSheet, 1f) + verify { fab.y = 900f } + + assertNull(callback.draggedLowestSheetTop) + } + + @Test + fun `GIVEN behaviour state is 'settling' & currentBottomSheetTop is same as draggedLowestSheetTop WHEN onSlide is called THEN fab y is set`() { + val behavior = mockk>(relaxed = true) + every { behavior.state } returns STATE_SETTLING + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + every { fab.top } returns 900 + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + val bottomSheetTop = 10 + + // draggedLowestSheetTop is null + val bottomSheet1 = mockk(relaxed = true) + every { bottomSheet1.top } returns bottomSheetTop + callback.onSlide(bottomSheet1, 1f) + verify { fab.y = 900f } + + // currentBottomSheetTop is same as draggedLowestSheetTop + val bottomSheet2 = mockk(relaxed = true) + every { bottomSheet2.top } returns bottomSheetTop + callback.onSlide(bottomSheet2, 1f) + verify { fab.y = 900f } + + assertNull(callback.draggedLowestSheetTop) + } + + @Test + fun `GIVEN behaviour state is 'settling' & currentBottomSheetTop is more than draggedLowestSheetTop WHEN onSlide is called THEN fab y is set`() { + val behavior = mockk>(relaxed = true) + every { behavior.state } returns STATE_SETTLING + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + every { fab.top } returns 900 + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + // draggedLowestSheetTop is null + val bottomSheet1 = mockk(relaxed = true) + every { bottomSheet1.top } returns 10 + callback.onSlide(bottomSheet1, 1f) + verify { fab.y = 900f } + + // currentBottomSheetTop is same as draggedLowestSheetTop + val bottomSheet2 = mockk(relaxed = true) + every { bottomSheet2.top } returns 11 + callback.onSlide(bottomSheet2, 1f) + verify { fab.y = 900f } + + assertNull(callback.draggedLowestSheetTop) + } + + @Test + fun `GIVEN behaviour state is 'settling' & currentBottomSheetTop less than draggedLowestSheetTop WHEN onSlide is called THEN fab y is set`() { + val behavior = mockk>(relaxed = true) + every { behavior.state } returns STATE_SETTLING + + val tabsTrayDialog = mockk(relaxed = true) + + val rootView = mockk(relaxed = true) + every { rootView.bottom } returns 1000 + val fab = mockk(relaxed = true) + every { fab.rootView } returns rootView + every { fab.top } returns 900 + + val callback = TraySheetBehaviorCallback(behavior, mockk(), tabsTrayDialog, fab) + + // draggedLowestSheetTop is null + val bottomSheet1 = mockk(relaxed = true) + every { bottomSheet1.top } returns 10 + callback.onSlide(bottomSheet1, 1f) + verify { fab.y = 900f } + + // currentBottomSheetTop is less than draggedLowestSheetTop + val bottomSheet2 = mockk(relaxed = true) + every { bottomSheet2.top } returns 9 + callback.onSlide(bottomSheet2, 1f) + verify { fab.y = 900f } + + assertNull(callback.draggedLowestSheetTop) + } + + @Test + fun `GIVEN behaviour state is 'state expanded' WHEN onStateChanged is called THEN draggedLowestSheetTop is set to null`() { + val bottomSheet = mockk(relaxed = true) + val callback = TraySheetBehaviorCallback(mockk(), mockk(), mockk(), mockk()) + + callback.draggedLowestSheetTop = 1 + assertEquals(1, callback.draggedLowestSheetTop) + + callback.onStateChanged(bottomSheet, STATE_EXPANDED) + + assertNull(callback.draggedLowestSheetTop) + } + + @Test + fun `GIVEN behaviour state is 'state collapsed' WHEN onStateChanged is called THEN draggedLowestSheetTop is set to null`() { + val bottomSheet = mockk(relaxed = true) + val callback = TraySheetBehaviorCallback(mockk(), mockk(), mockk(), mockk()) + + callback.draggedLowestSheetTop = 1 + assertEquals(1, callback.draggedLowestSheetTop) - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 2, 2, navigationInteractor, mockk()) + callback.onStateChanged(bottomSheet, STATE_COLLAPSED) - verify { behavior.addBottomSheetCallback(capture(callbackCaptor)) } - assertSame(behavior, callbackCaptor.captured.behavior) - assertSame(navigationInteractor, callbackCaptor.captured.trayInteractor) + assertNull(callback.draggedLowestSheetTop) } @Test fun `WHEN TabSheetBehaviorManager is initialized THEN it caches the orientation parameter value`() { - val manager0 = TabSheetBehaviorManager(mockk(relaxed = true), Configuration.ORIENTATION_UNDEFINED, 5, 4, mockk(), mockk()) + val manager0 = TabSheetBehaviorManager( + mockk(relaxed = true), + Configuration.ORIENTATION_UNDEFINED, + 5, + 4, + mockk(), + ) assertEquals(Configuration.ORIENTATION_UNDEFINED, manager0.currentOrientation) - val manager1 = TabSheetBehaviorManager(mockk(relaxed = true), Configuration.ORIENTATION_PORTRAIT, 5, 4, mockk(relaxed = true), mockk()) + val manager1 = TabSheetBehaviorManager( + mockk(relaxed = true), + Configuration.ORIENTATION_PORTRAIT, + 5, + 4, + mockk(), + ) assertEquals(Configuration.ORIENTATION_PORTRAIT, manager1.currentOrientation) - val manager2 = TabSheetBehaviorManager(mockk(relaxed = true), Configuration.ORIENTATION_LANDSCAPE, 5, 4, mockk(), mockk()) + val manager2 = TabSheetBehaviorManager( + mockk(relaxed = true), + Configuration.ORIENTATION_LANDSCAPE, + 5, + 4, + mockk(), + ) assertEquals(Configuration.ORIENTATION_LANDSCAPE, manager2.currentOrientation) } @@ -95,7 +428,13 @@ class TabSheetBehaviorManagerTest { 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() - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_PORTRAIT, 5, 4, mockk(), mockk()) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_PORTRAIT, + 5, + 4, + mockk(), + ) assertEquals(STATE_EXPANDED, behavior.state) } @@ -104,7 +443,13 @@ class TabSheetBehaviorManagerTest { 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() - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_PORTRAIT, 5, 5, mockk(), mockk()) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_PORTRAIT, + 5, + 5, + mockk(), + ) assertEquals(STATE_EXPANDED, behavior.state) } @@ -113,7 +458,13 @@ class TabSheetBehaviorManagerTest { 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() - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_PORTRAIT, 4, 5, mockk(), mockk()) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_PORTRAIT, + 4, + 5, + mockk(), + ) assertEquals(STATE_COLLAPSED, behavior.state) } @@ -122,7 +473,13 @@ class TabSheetBehaviorManagerTest { fun `GIVEN more tabs opened than the expanding limit and undefined orientation WHEN TabSheetBehaviorManager is initialized THEN the behavior is set as expanded`() { val behavior = BottomSheetBehavior() - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 5, 4, mockk(), mockk()) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 5, + 4, + mockk(), + ) assertEquals(STATE_EXPANDED, behavior.state) } @@ -131,7 +488,13 @@ class TabSheetBehaviorManagerTest { fun `GIVEN the number of tabs opened is exactly the expanding limit and undefined orientation WHEN TabSheetBehaviorManager is initialized THEN the behavior is set as expanded`() { val behavior = BottomSheetBehavior() - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 5, 5, mockk(), mockk()) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 5, + 5, + mockk(), + ) assertEquals(STATE_EXPANDED, behavior.state) } @@ -140,7 +503,13 @@ class TabSheetBehaviorManagerTest { fun `GIVEN fewer tabs opened than the expanding limit and undefined orientation WHEN TabSheetBehaviorManager is initialized THEN the behavior is set as collapsed`() { val behavior = BottomSheetBehavior() - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 4, 5, mockk(), mockk()) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 4, + 5, + mockk(), + ) assertEquals(STATE_COLLAPSED, behavior.state) } @@ -149,7 +518,13 @@ class TabSheetBehaviorManagerTest { 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() - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 5, 4, mockk(), mockk()) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_LANDSCAPE, + 5, + 4, + mockk(), + ) assertEquals(STATE_EXPANDED, behavior.state) } @@ -158,7 +533,13 @@ class TabSheetBehaviorManagerTest { 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() - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 5, 5, mockk(), mockk()) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_LANDSCAPE, + 5, + 5, + mockk(), + ) assertEquals(STATE_EXPANDED, behavior.state) } @@ -167,7 +548,13 @@ class TabSheetBehaviorManagerTest { 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() - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 4, 5, mockk(), mockk()) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_LANDSCAPE, + 4, + 5, + mockk(), + ) assertEquals(STATE_EXPANDED, behavior.state) } @@ -175,7 +562,13 @@ class TabSheetBehaviorManagerTest { @Test fun `GIVEN more tabs opened than the expanding limit and not landscape orientation WHEN updateBehaviorState is called THEN the behavior is set as expanded`() { val behavior = BottomSheetBehavior() - val manager = TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 5, 4, mockk(), mockk()) + val manager = TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 5, + 4, + mockk(), + ) manager.updateBehaviorState(false) @@ -185,7 +578,13 @@ class TabSheetBehaviorManagerTest { @Test fun `GIVEN the number of tabs opened is exactly the expanding limit and portrait orientation WHEN updateBehaviorState is called THEN the behavior is set as expanded`() { val behavior = BottomSheetBehavior() - val manager = TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 5, 5, mockk(), mockk()) + val manager = TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 5, + 5, + mockk(), + ) manager.updateBehaviorState(false) @@ -195,7 +594,13 @@ class TabSheetBehaviorManagerTest { @Test fun `GIVEN fewer tabs opened than the expanding limit and portrait orientation WHEN updateBehaviorState is called THEN the behavior is set as collapsed`() { val behavior = BottomSheetBehavior() - val manager = TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 4, 5, mockk(), mockk()) + val manager = TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 4, + 5, + mockk(), + ) manager.updateBehaviorState(false) @@ -205,7 +610,13 @@ class TabSheetBehaviorManagerTest { @Test fun `GIVEN more tabs opened than the expanding limit and landscape orientation WHEN updateBehaviorState is called THEN the behavior is set as expanded`() { val behavior = BottomSheetBehavior() - val manager = TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 5, 4, mockk(), mockk()) + val manager = TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 5, + 4, + mockk(), + ) manager.updateBehaviorState(true) @@ -215,7 +626,13 @@ class TabSheetBehaviorManagerTest { @Test fun `GIVEN the number of tabs opened is exactly the expanding limit and landscape orientation WHEN updateBehaviorState is called THEN the behavior is set as expanded`() { val behavior = BottomSheetBehavior() - val manager = TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 5, 5, mockk(), mockk()) + val manager = TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 5, + 5, + mockk(), + ) manager.updateBehaviorState(true) @@ -225,7 +642,13 @@ class TabSheetBehaviorManagerTest { @Test fun `GIVEN fewer tabs opened than the expanding limit and landscape orientation WHEN updateBehaviorState is called THEN the behavior is set as expanded`() { val behavior = BottomSheetBehavior() - val manager = TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 4, 5, mockk(), mockk()) + val manager = TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 4, + 5, + mockk(), + ) manager.updateBehaviorState(true) @@ -234,7 +657,15 @@ class TabSheetBehaviorManagerTest { @Test fun `WHEN updateDependingOnOrientation is called with the same orientation as the current one THEN nothing happens`() { - val manager = spyk(TabSheetBehaviorManager(mockk(relaxed = true), Configuration.ORIENTATION_PORTRAIT, 4, 5, mockk(), mockk())) + val manager = spyk( + TabSheetBehaviorManager( + mockk(relaxed = true), + Configuration.ORIENTATION_PORTRAIT, + 4, + 5, + mockk(), + ), + ) manager.updateDependingOnOrientation(Configuration.ORIENTATION_PORTRAIT) @@ -245,7 +676,15 @@ class TabSheetBehaviorManagerTest { @Test fun `WHEN updateDependingOnOrientation is called with a new orientation THEN this is cached and updateBehaviorState is called`() { - val manager = spyk(TabSheetBehaviorManager(mockk(relaxed = true), Configuration.ORIENTATION_PORTRAIT, 4, 5, mockk(), mockk())) + val manager = spyk( + TabSheetBehaviorManager( + mockk(relaxed = true), + Configuration.ORIENTATION_PORTRAIT, + 4, + 5, + mockk(), + ), + ) manager.updateDependingOnOrientation(Configuration.ORIENTATION_UNDEFINED) assertEquals(Configuration.ORIENTATION_UNDEFINED, manager.currentOrientation) @@ -260,21 +699,45 @@ class TabSheetBehaviorManagerTest { @Test fun `WHEN isLandscape is called with Configuration#ORIENTATION_LANDSCAPE THEN it returns true`() { - val manager = spyk(TabSheetBehaviorManager(mockk(relaxed = true), Configuration.ORIENTATION_PORTRAIT, 4, 5, mockk(), mockk())) + val manager = spyk( + TabSheetBehaviorManager( + mockk(relaxed = true), + Configuration.ORIENTATION_PORTRAIT, + 4, + 5, + mockk(), + ), + ) assertTrue(manager.isLandscape(Configuration.ORIENTATION_LANDSCAPE)) } @Test fun `WHEN isLandscape is called with Configuration#ORIENTATION_PORTRAIT THEN it returns false`() { - val manager = spyk(TabSheetBehaviorManager(mockk(relaxed = true), Configuration.ORIENTATION_PORTRAIT, 4, 5, mockk(), mockk())) + val manager = spyk( + TabSheetBehaviorManager( + mockk(relaxed = true), + Configuration.ORIENTATION_PORTRAIT, + 4, + 5, + mockk(), + ), + ) assertFalse(manager.isLandscape(Configuration.ORIENTATION_PORTRAIT)) } @Test fun `WHEN isLandscape is called with Configuration#ORIENTATION_UNDEFINED THEN it returns false`() { - val manager = spyk(TabSheetBehaviorManager(mockk(relaxed = true), Configuration.ORIENTATION_PORTRAIT, 4, 5, mockk(), mockk())) + val manager = spyk( + TabSheetBehaviorManager( + mockk(relaxed = true), + Configuration.ORIENTATION_PORTRAIT, + 4, + 5, + mockk(), + ), + ) assertFalse(manager.isLandscape(Configuration.ORIENTATION_UNDEFINED)) } @@ -290,7 +753,13 @@ class TabSheetBehaviorManagerTest { mockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt") every { EXPANDED_OFFSET_IN_LANDSCAPE_DP.dpToPx(displayMetrics) } returns EXPANDED_OFFSET_IN_LANDSCAPE_DP - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 5, 4, mockk(), displayMetrics) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_LANDSCAPE, + 5, + 4, + displayMetrics, + ) } finally { unmockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt") } @@ -309,7 +778,13 @@ class TabSheetBehaviorManagerTest { mockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt") every { EXPANDED_OFFSET_IN_PORTRAIT_DP.dpToPx(displayMetrics) } returns EXPANDED_OFFSET_IN_PORTRAIT_DP - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_PORTRAIT, 5, 4, mockk(), displayMetrics) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_PORTRAIT, + 5, + 4, + displayMetrics, + ) } finally { unmockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt") } @@ -328,7 +803,13 @@ class TabSheetBehaviorManagerTest { mockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt") every { EXPANDED_OFFSET_IN_PORTRAIT_DP.dpToPx(displayMetrics) } returns EXPANDED_OFFSET_IN_PORTRAIT_DP - TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 5, 4, mockk(), displayMetrics) + TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 5, + 4, + displayMetrics, + ) } finally { unmockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt") } @@ -346,7 +827,13 @@ class TabSheetBehaviorManagerTest { try { mockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt") every { EXPANDED_OFFSET_IN_PORTRAIT_DP.dpToPx(displayMetrics) } returns EXPANDED_OFFSET_IN_PORTRAIT_DP - val manager = TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 5, 4, mockk(), displayMetrics) + val manager = TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_LANDSCAPE, + 5, + 4, + displayMetrics, + ) manager.updateDependingOnOrientation(Configuration.ORIENTATION_PORTRAIT) } finally { @@ -366,7 +853,13 @@ class TabSheetBehaviorManagerTest { try { mockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt") every { EXPANDED_OFFSET_IN_PORTRAIT_DP.dpToPx(displayMetrics) } returns EXPANDED_OFFSET_IN_PORTRAIT_DP - val manager = TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 5, 4, mockk(), displayMetrics) + val manager = TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_LANDSCAPE, + 5, + 4, + displayMetrics, + ) manager.updateDependingOnOrientation(Configuration.ORIENTATION_UNDEFINED) } finally { @@ -386,7 +879,13 @@ class TabSheetBehaviorManagerTest { try { mockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt") every { EXPANDED_OFFSET_IN_LANDSCAPE_DP.dpToPx(displayMetrics) } returns EXPANDED_OFFSET_IN_LANDSCAPE_DP - val manager = TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 5, 4, mockk(), displayMetrics) + val manager = TabSheetBehaviorManager( + behavior, + Configuration.ORIENTATION_UNDEFINED, + 5, + 4, + displayMetrics, + ) manager.updateDependingOnOrientation(Configuration.ORIENTATION_LANDSCAPE) } finally {