From 515ae82e9b7629b3cba1193c05620176be81d15d Mon Sep 17 00:00:00 2001 From: Mugurell Date: Fri, 11 Jun 2021 11:30:15 +0300 Subject: [PATCH] [fenix] For https://github.com/mozilla-mobile/fenix/issues/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. --- .../fenix/tabstray/TabSheetBehaviorManager.kt | 59 ++++++++ .../fenix/tabstray/TabsTrayFragment.kt | 11 +- .../tabstray/TraySheetBehaviorCallback.kt | 44 ------ .../tabstray/TabSheetBehaviorManagerTest.kt | 126 ++++++++++++++++++ .../tabstray/TraySheetBehaviorCallbackTest.kt | 93 ------------- 5 files changed, 189 insertions(+), 144 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallback.kt create mode 100644 app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt delete mode 100644 app/src/test/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallbackTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt new file mode 100644 index 0000000000..c96675240c --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManager.kt @@ -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, + 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, + @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 +} 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 c0f90708f0..bf3a69fbb7 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -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 + private lateinit var trayBehaviorManager: TabSheetBehaviorManager private val tabLayoutMediator = ViewBoundFeatureWrapper() private val tabCounterBinding = ViewBoundFeatureWrapper() @@ -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, diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallback.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallback.kt deleted file mode 100644 index 5c92df303b..0000000000 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallback.kt +++ /dev/null @@ -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, - 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.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 - } -} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt new file mode 100644 index 0000000000..de1614ca2f --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/TabSheetBehaviorManagerTest.kt @@ -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(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>(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>(relaxed = true) + val interactor = mockk(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 = mockk(relaxed = true) + val navigationInteractor: NavigationInteractor = mockk() + val callbackCaptor = slot() + + 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() + + 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() + + 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() + + 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() + + 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() + + 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() + + TabSheetBehaviorManager(behavior, true, 4, 5, mockk()) + + assertEquals(STATE_EXPANDED, behavior.state) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallbackTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallbackTest.kt deleted file mode 100644 index d388290a21..0000000000 --- a/app/src/test/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallbackTest.kt +++ /dev/null @@ -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(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>(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>(relaxed = true) - val interactor = mockk(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()) - val interactor = mockk(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()) - val interactor = mockk(relaxed = true) - - // when - behavior.setUpTrayBehavior( - isLandscape = false, - maxNumberOfTabs = 2, - numberForExpandingTray = TabsTrayFragment.EXPAND_AT_LIST_SIZE, - navigationInteractor = interactor - ) - - // then - assert(behavior.state == STATE_COLLAPSED) - } -}