From cb1fb95a3a11a73a03d2adfa744c4f4cf16442cc Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Sun, 28 Mar 2021 21:49:54 -0400 Subject: [PATCH] Issue #18521: Focus on page corresponding to selected tab This first part of #18521 is to focus on the correct pager item first before we land a patch for `scrollToPosition` of the selected tab. --- .../fenix/tabstray/TabLayoutMediator.kt | 59 +++++++++++++++ .../fenix/tabstray/TabsTrayFragment.kt | 23 ++---- .../fenix/tabstray/TabLayoutMediatorTest.kt | 73 +++++++++++++++++++ 3 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/TabLayoutMediator.kt create mode 100644 app/src/test/java/org/mozilla/fenix/tabstray/TabLayoutMediatorTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabLayoutMediator.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabLayoutMediator.kt new file mode 100644 index 000000000..3726636a0 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabLayoutMediator.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 androidx.annotation.VisibleForTesting +import com.google.android.material.tabs.TabLayout +import mozilla.components.browser.state.selector.selectedTab +import mozilla.components.browser.state.store.BrowserStore +import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.POSITION_NORMAL_TABS +import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.POSITION_PRIVATE_TABS + +/** + * Selected the selected pager depending on the [BrowserStore] state and synchronizes user actions + * with the pager position. + */ +class TabLayoutMediator( + private val tabLayout: TabLayout, + private val interactor: TabsTrayInteractor, + private val store: BrowserStore +) { + + /** + * Start observing the [TabLayout] and select the current tab for initial state. + */ + fun attach() { + tabLayout.addOnTabSelectedListener(TabLayoutObserver(interactor)) + + selectActivePage() + } + + @VisibleForTesting + internal fun selectActivePage() { + val selectedTab = store.state.selectedTab ?: return + + val selectedPagerPosition = if (selectedTab.content.private) { + POSITION_PRIVATE_TABS + } else { + POSITION_NORMAL_TABS + } + + tabLayout.getTabAt(selectedPagerPosition)?.select() + } +} + +/** + * An observer for the [TabLayout] used for the Tabs Tray. + */ +internal class TabLayoutObserver( + private val interactor: TabsTrayInteractor +) : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + interactor.setCurrentTrayPosition(tab.position) + } + + override fun onTabUnselected(tab: TabLayout.Tab) = Unit + override fun onTabReselected(tab: TabLayout.Tab) = 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 2c9e6c51f..98da16122 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -13,7 +13,6 @@ import androidx.appcompat.app.AppCompatDialogFragment import androidx.constraintlayout.widget.ConstraintLayout import androidx.navigation.fragment.findNavController import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.tabs.TabLayout import kotlinx.android.synthetic.main.component_tabstray2.* import kotlinx.android.synthetic.main.component_tabstray2.view.* import org.mozilla.fenix.R @@ -72,6 +71,12 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { ) setupPager(view.context, this, browserTrayInteractor) + + TabLayoutMediator( + tabLayout = tab_layout, + interactor = this, + store = requireComponents.core.store + ).attach() } override fun setCurrentTrayPosition(position: Int) { @@ -110,21 +115,5 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { adapter = TrayPagerAdapter(context, trayInteractor, browserInteractor) isUserInputEnabled = false } - - tab_layout.addOnTabSelectedListener(TabLayoutObserver(trayInteractor)) - } -} - -/** - * An observer for the [TabLayout] used for the Tabs Tray. - */ -internal class TabLayoutObserver( - private val interactor: TabsTrayInteractor -) : TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab) { - interactor.setCurrentTrayPosition(tab.position) } - - override fun onTabUnselected(tab: TabLayout.Tab) = Unit - override fun onTabReselected(tab: TabLayout.Tab) = Unit } diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/TabLayoutMediatorTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/TabLayoutMediatorTest.kt new file mode 100644 index 000000000..67b3ad78d --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/TabLayoutMediatorTest.kt @@ -0,0 +1,73 @@ +/* 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 com.google.android.material.tabs.TabLayout +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.ContentState +import mozilla.components.browser.state.state.TabSessionState +import mozilla.components.browser.state.store.BrowserStore +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.POSITION_NORMAL_TABS +import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.POSITION_PRIVATE_TABS + +@RunWith(FenixRobolectricTestRunner::class) +class TabLayoutMediatorTest { + + @Test + fun `page to normal tab position when selected tab is also normal`() { + val store = createState("123") + val tabLayout: TabLayout = mockk(relaxed = true) + val tab: TabLayout.Tab = mockk(relaxed = true) + val mediator = TabLayoutMediator(tabLayout, mockk(relaxed = true), store) + + every { tabLayout.getTabAt(POSITION_NORMAL_TABS) }.answers { tab } + + mediator.selectActivePage() + + verify { tab.select() } + } + + @Test + fun `page to private tab position when selected tab is also private`() { + val store = createState("456") + val tabLayout: TabLayout = mockk(relaxed = true) + val tab: TabLayout.Tab = mockk(relaxed = true) + val mediator = TabLayoutMediator(tabLayout, mockk(relaxed = true), store) + + every { tabLayout.getTabAt(POSITION_PRIVATE_TABS) }.answers { tab } + + mediator.selectActivePage() + + verify { tab.select() } + } + + private fun createState(selectedId: String) = BrowserStore( + initialState = BrowserState( + tabs = listOf( + TabSessionState( + id = "123", + content = ContentState( + private = false, + url = "https://firefox.com" + ) + ), + TabSessionState( + id = "456", + content = ContentState( + private = true, + url = "https://mozilla.org" + ) + ) + ), + selectedTabId = selectedId + ) + ) +}