diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt index c96675240c..470153c79c 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.tabstray +import android.content.res.Configuration import android.view.View import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintLayout @@ -14,29 +15,52 @@ 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 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. */ internal class TabSheetBehaviorManager( - behavior: BottomSheetBehavior, - isLandscape: Boolean, - maxNumberOfTabs: Int, - numberForExpandingTray: Int, + private val behavior: BottomSheetBehavior, + orientation: Int, + private val maxNumberOfTabs: Int, + private val numberForExpandingTray: Int, navigationInteractor: NavigationInteractor ) { + @VisibleForTesting + internal var currentOrientation = orientation + init { behavior.addBottomSheetCallback( TraySheetBehaviorCallback(behavior, navigationInteractor) ) + updateBehaviorState(isLandscape(orientation)) + } + + /** + * Update how the tray looks depending on whether it is shown in landscape or portrait. + */ + internal fun updateDependingOnOrientation(newOrientation: Int) { + if (currentOrientation != newOrientation) { + currentOrientation = newOrientation + + val isInLandscape = isLandscape(newOrientation) + updateBehaviorState(isInLandscape) + } + } + + @VisibleForTesting + internal fun updateBehaviorState(isLandscape: Boolean) { behavior.state = if (isLandscape || maxNumberOfTabs >= numberForExpandingTray) { BottomSheetBehavior.STATE_EXPANDED } else { BottomSheetBehavior.STATE_COLLAPSED } } + + @VisibleForTesting + internal fun isLandscape(orientation: Int) = Configuration.ORIENTATION_LANDSCAPE == orientation } @VisibleForTesting diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt index bf3a69fbb7..a56b52689e 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -60,7 +60,7 @@ class TabsTrayFragment : AppCompatDialogFragment() { private lateinit var browserTrayInteractor: BrowserTrayInteractor private lateinit var tabsTrayInteractor: TabsTrayInteractor private lateinit var tabsTrayController: DefaultTabsTrayController - private lateinit var trayBehaviorManager: TabSheetBehaviorManager + @VisibleForTesting internal lateinit var trayBehaviorManager: TabSheetBehaviorManager private val tabLayoutMediator = ViewBoundFeatureWrapper() private val tabCounterBinding = ViewBoundFeatureWrapper() @@ -160,7 +160,7 @@ class TabsTrayFragment : AppCompatDialogFragment() { trayBehaviorManager = TabSheetBehaviorManager( behavior = BottomSheetBehavior.from(view.tab_wrapper), - isLandscape = requireContext().resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE, + orientation = resources.configuration.orientation, maxNumberOfTabs = max( requireContext().components.core.store.state.normalTabs.size, requireContext().components.core.store.state.privateTabs.size @@ -262,6 +262,12 @@ class TabsTrayFragment : AppCompatDialogFragment() { ) } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + + trayBehaviorManager.updateDependingOnOrientation(newConfig.orientation) + } + @VisibleForTesting internal fun showUndoSnackbarForTab(isPrivate: Boolean) { val snackbarMessage = diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt index de1614ca2f..dce57f4ffc 100644 --- a/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt @@ -4,7 +4,9 @@ package org.mozilla.fenix.tabstray +import android.content.res.Configuration import androidx.constraintlayout.widget.ConstraintLayout +import androidx.paging.Config 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 @@ -15,9 +17,13 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_SETTLIN import io.mockk.Called import io.mockk.mockk import io.mockk.slot +import io.mockk.spyk import io.mockk.verify +import kotlinx.coroutines.currentCoroutineContext import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue import org.junit.Test class TabSheetBehaviorManagerTest { @@ -63,18 +69,30 @@ class TabSheetBehaviorManagerTest { val navigationInteractor: NavigationInteractor = mockk() val callbackCaptor = slot() - TabSheetBehaviorManager(behavior, true, 2, 2, navigationInteractor) + TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 2, 2, navigationInteractor) verify { behavior.addBottomSheetCallback(capture(callbackCaptor)) } assertSame(behavior, callbackCaptor.captured.behavior) assertSame(navigationInteractor, callbackCaptor.captured.trayInteractor) } + @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()) + assertEquals(Configuration.ORIENTATION_UNDEFINED, manager0.currentOrientation) + + val manager1 = TabSheetBehaviorManager(mockk(relaxed = true), Configuration.ORIENTATION_PORTRAIT, 5, 4, mockk(relaxed = true)) + assertEquals(Configuration.ORIENTATION_PORTRAIT, manager1.currentOrientation) + + val manager2 = TabSheetBehaviorManager(mockk(relaxed = true), Configuration.ORIENTATION_LANDSCAPE, 5, 4, mockk()) + assertEquals(Configuration.ORIENTATION_LANDSCAPE, manager2.currentOrientation) + } + @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() - TabSheetBehaviorManager(behavior, false, 5, 4, mockk()) + TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_PORTRAIT, 5, 4, mockk()) assertEquals(STATE_EXPANDED, behavior.state) } @@ -83,7 +101,7 @@ 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, false, 5, 5, mockk()) + TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_PORTRAIT, 5, 5, mockk()) assertEquals(STATE_EXPANDED, behavior.state) } @@ -92,7 +110,34 @@ 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, false, 4, 5, mockk()) + TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_PORTRAIT, 4, 5, mockk()) + + assertEquals(STATE_COLLAPSED, behavior.state) + } + + @Test + 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()) + + assertEquals(STATE_EXPANDED, behavior.state) + } + + @Test + 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()) + + assertEquals(STATE_EXPANDED, behavior.state) + } + + @Test + 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()) assertEquals(STATE_COLLAPSED, behavior.state) } @@ -101,7 +146,7 @@ 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, true, 5, 4, mockk()) + TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 5, 4, mockk()) assertEquals(STATE_EXPANDED, behavior.state) } @@ -110,7 +155,7 @@ 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, true, 5, 5, mockk()) + TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 5, 5, mockk()) assertEquals(STATE_EXPANDED, behavior.state) } @@ -119,8 +164,112 @@ 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, true, 4, 5, mockk()) + TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 4, 5, mockk()) + + assertEquals(STATE_EXPANDED, behavior.state) + } + + @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()) + + manager.updateBehaviorState(false) + + assertEquals(STATE_EXPANDED, behavior.state) + } + + @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()) + + manager.updateBehaviorState(false) + + assertEquals(STATE_EXPANDED, behavior.state) + } + + @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()) + + manager.updateBehaviorState(false) + + assertEquals(STATE_COLLAPSED, behavior.state) + } + + @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()) + + manager.updateBehaviorState(true) + + assertEquals(STATE_EXPANDED, behavior.state) + } + + @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()) + + manager.updateBehaviorState(true) + + assertEquals(STATE_EXPANDED, behavior.state) + } + + @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()) + + manager.updateBehaviorState(true) assertEquals(STATE_EXPANDED, behavior.state) } + + @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())) + + manager.updateDependingOnOrientation(Configuration.ORIENTATION_PORTRAIT) + + verify(exactly = 0) { manager.currentOrientation = any() } + verify(exactly = 0) { manager.updateBehaviorState(any()) } + } + + @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())) + + manager.updateDependingOnOrientation(Configuration.ORIENTATION_UNDEFINED) + assertEquals(Configuration.ORIENTATION_UNDEFINED, manager.currentOrientation) + verify { manager.updateBehaviorState(any()) } + + manager.updateDependingOnOrientation(Configuration.ORIENTATION_LANDSCAPE) + assertEquals(Configuration.ORIENTATION_LANDSCAPE, manager.currentOrientation) + verify(exactly = 2) { manager.updateBehaviorState(any()) } + } + + @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())) + + 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())) + + 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())) + + assertFalse(manager.isLandscape(Configuration.ORIENTATION_UNDEFINED)) + } } diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/TabsTrayFragmentTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/TabsTrayFragmentTest.kt index bbdb915bd2..72432128f8 100644 --- a/app/src/test/java/org/mozilla/fenix/tabstray/TabsTrayFragmentTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabstray/TabsTrayFragmentTest.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.tabstray import android.content.Context +import android.content.res.Configuration import android.view.View import android.widget.Button import android.widget.ImageButton @@ -344,4 +345,15 @@ class TabsTrayFragmentTest { verify { fragment.dismissAllowingStateLoss() } } + + @Test + fun `WHEN onConfigurationChanged is called THEN it delegates the tray behavior manager to update the tray`() { + val trayBehaviorManager: TabSheetBehaviorManager = mockk(relaxed = true) + fragment.trayBehaviorManager = trayBehaviorManager + val newConfiguration = Configuration() + + fragment.onConfigurationChanged(newConfiguration) + + verify { trayBehaviorManager.updateDependingOnOrientation(newConfiguration.orientation) } + } }