[fenix] For https://github.com/mozilla-mobile/fenix/issues/19135 - Expand the tabs tray in landscape

Track the current orientation and collapse / expand it the tabs tray depending
on the orientation and the number of opened tabs.
pull/600/head
Mugurell 3 years ago
parent 515ae82e9b
commit 74a14fe91c

@ -4,6 +4,7 @@
package org.mozilla.fenix.tabstray package org.mozilla.fenix.tabstray
import android.content.res.Configuration
import android.view.View import android.view.View
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout 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. * 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 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 maxNumberOfTabs highest number of tabs in each tray page.
* @param numberForExpandingTray limit depending on which the tray should be collapsed or expanded. * @param numberForExpandingTray limit depending on which the tray should be collapsed or expanded.
* @param navigationInteractor [NavigationInteractor] used for tray updates / navigation. * @param navigationInteractor [NavigationInteractor] used for tray updates / navigation.
*/ */
internal class TabSheetBehaviorManager( internal class TabSheetBehaviorManager(
behavior: BottomSheetBehavior<ConstraintLayout>, private val behavior: BottomSheetBehavior<ConstraintLayout>,
isLandscape: Boolean, orientation: Int,
maxNumberOfTabs: Int, private val maxNumberOfTabs: Int,
numberForExpandingTray: Int, private val numberForExpandingTray: Int,
navigationInteractor: NavigationInteractor navigationInteractor: NavigationInteractor
) { ) {
@VisibleForTesting
internal var currentOrientation = orientation
init { init {
behavior.addBottomSheetCallback( behavior.addBottomSheetCallback(
TraySheetBehaviorCallback(behavior, navigationInteractor) 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) { behavior.state = if (isLandscape || maxNumberOfTabs >= numberForExpandingTray) {
BottomSheetBehavior.STATE_EXPANDED BottomSheetBehavior.STATE_EXPANDED
} else { } else {
BottomSheetBehavior.STATE_COLLAPSED BottomSheetBehavior.STATE_COLLAPSED
} }
} }
@VisibleForTesting
internal fun isLandscape(orientation: Int) = Configuration.ORIENTATION_LANDSCAPE == orientation
} }
@VisibleForTesting @VisibleForTesting

@ -60,7 +60,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
private lateinit var browserTrayInteractor: BrowserTrayInteractor private lateinit var browserTrayInteractor: BrowserTrayInteractor
private lateinit var tabsTrayInteractor: TabsTrayInteractor private lateinit var tabsTrayInteractor: TabsTrayInteractor
private lateinit var tabsTrayController: DefaultTabsTrayController private lateinit var tabsTrayController: DefaultTabsTrayController
private lateinit var trayBehaviorManager: TabSheetBehaviorManager @VisibleForTesting internal lateinit var trayBehaviorManager: TabSheetBehaviorManager
private val tabLayoutMediator = ViewBoundFeatureWrapper<TabLayoutMediator>() private val tabLayoutMediator = ViewBoundFeatureWrapper<TabLayoutMediator>()
private val tabCounterBinding = ViewBoundFeatureWrapper<TabCounterBinding>() private val tabCounterBinding = ViewBoundFeatureWrapper<TabCounterBinding>()
@ -160,7 +160,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
trayBehaviorManager = TabSheetBehaviorManager( trayBehaviorManager = TabSheetBehaviorManager(
behavior = BottomSheetBehavior.from(view.tab_wrapper), behavior = BottomSheetBehavior.from(view.tab_wrapper),
isLandscape = requireContext().resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE, orientation = resources.configuration.orientation,
maxNumberOfTabs = max( maxNumberOfTabs = max(
requireContext().components.core.store.state.normalTabs.size, requireContext().components.core.store.state.normalTabs.size,
requireContext().components.core.store.state.privateTabs.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 @VisibleForTesting
internal fun showUndoSnackbarForTab(isPrivate: Boolean) { internal fun showUndoSnackbarForTab(isPrivate: Boolean) {
val snackbarMessage = val snackbarMessage =

@ -4,7 +4,9 @@
package org.mozilla.fenix.tabstray package org.mozilla.fenix.tabstray
import android.content.res.Configuration
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.paging.Config
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED 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_DRAGGING
@ -15,9 +17,13 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_SETTLIN
import io.mockk.Called import io.mockk.Called
import io.mockk.mockk import io.mockk.mockk
import io.mockk.slot import io.mockk.slot
import io.mockk.spyk
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.currentCoroutineContext
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.Assert.assertTrue
import org.junit.Test import org.junit.Test
class TabSheetBehaviorManagerTest { class TabSheetBehaviorManagerTest {
@ -63,18 +69,30 @@ class TabSheetBehaviorManagerTest {
val navigationInteractor: NavigationInteractor = mockk() val navigationInteractor: NavigationInteractor = mockk()
val callbackCaptor = slot<TraySheetBehaviorCallback>() val callbackCaptor = slot<TraySheetBehaviorCallback>()
TabSheetBehaviorManager(behavior, true, 2, 2, navigationInteractor) TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 2, 2, navigationInteractor)
verify { behavior.addBottomSheetCallback(capture(callbackCaptor)) } verify { behavior.addBottomSheetCallback(capture(callbackCaptor)) }
assertSame(behavior, callbackCaptor.captured.behavior) assertSame(behavior, callbackCaptor.captured.behavior)
assertSame(navigationInteractor, callbackCaptor.captured.trayInteractor) 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 @Test
fun `GIVEN more tabs opened than the expanding limit and portrait orientation WHEN TabSheetBehaviorManager is initialized THEN the behavior is set as expanded`() { 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>() val behavior = BottomSheetBehavior<ConstraintLayout>()
TabSheetBehaviorManager(behavior, false, 5, 4, mockk()) TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_PORTRAIT, 5, 4, mockk())
assertEquals(STATE_EXPANDED, behavior.state) 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`() { 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>() val behavior = BottomSheetBehavior<ConstraintLayout>()
TabSheetBehaviorManager(behavior, false, 5, 5, mockk()) TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_PORTRAIT, 5, 5, mockk())
assertEquals(STATE_EXPANDED, behavior.state) 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`() { 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>() val behavior = BottomSheetBehavior<ConstraintLayout>()
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<ConstraintLayout>()
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<ConstraintLayout>()
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<ConstraintLayout>()
TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 4, 5, mockk())
assertEquals(STATE_COLLAPSED, behavior.state) 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`() { 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>() val behavior = BottomSheetBehavior<ConstraintLayout>()
TabSheetBehaviorManager(behavior, true, 5, 4, mockk()) TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 5, 4, mockk())
assertEquals(STATE_EXPANDED, behavior.state) 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`() { 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>() val behavior = BottomSheetBehavior<ConstraintLayout>()
TabSheetBehaviorManager(behavior, true, 5, 5, mockk()) TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_LANDSCAPE, 5, 5, mockk())
assertEquals(STATE_EXPANDED, behavior.state) 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`() { 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>() val behavior = BottomSheetBehavior<ConstraintLayout>()
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<ConstraintLayout>()
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<ConstraintLayout>()
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<ConstraintLayout>()
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<ConstraintLayout>()
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<ConstraintLayout>()
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<ConstraintLayout>()
val manager = TabSheetBehaviorManager(behavior, Configuration.ORIENTATION_UNDEFINED, 4, 5, mockk())
manager.updateBehaviorState(true)
assertEquals(STATE_EXPANDED, behavior.state) 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))
}
} }

@ -5,6 +5,7 @@
package org.mozilla.fenix.tabstray package org.mozilla.fenix.tabstray
import android.content.Context import android.content.Context
import android.content.res.Configuration
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.ImageButton import android.widget.ImageButton
@ -344,4 +345,15 @@ class TabsTrayFragmentTest {
verify { fragment.dismissAllowingStateLoss() } 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) }
}
} }

Loading…
Cancel
Save