[fenix] Bug 1814241 - Create interactor/controller for the Tabs Tray FAB

pull/600/head
Noah Bond 1 year ago committed by mergify[bot]
parent a05e5f4488
commit ea21c39d8a

@ -11,15 +11,16 @@ import kotlinx.coroutines.flow.map
import mozilla.components.lib.state.helpers.AbstractBinding
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.TabsTrayFabInteractor
/**
* A binding that show a FAB in tab tray used to open a new tab.
*/
@OptIn(ExperimentalCoroutinesApi::class)
class FloatingActionButtonBinding(
private val store: TabsTrayStore,
store: TabsTrayStore,
private val actionButton: ExtendedFloatingActionButton,
private val interactor: TabsTrayInteractor,
private val interactor: TabsTrayFabInteractor,
) : AbstractBinding<TabsTrayState>(store) {
override suspend fun onState(flow: Flow<TabsTrayState>) {
@ -44,7 +45,7 @@ class FloatingActionButtonBinding(
contentDescription = context.getString(R.string.add_tab)
setIconResource(R.drawable.ic_new)
setOnClickListener {
interactor.onFabClicked(false)
interactor.onNormalTabsFabClicked()
}
}
}
@ -56,7 +57,7 @@ class FloatingActionButtonBinding(
contentDescription = context.getString(R.string.add_private_tab)
setIconResource(R.drawable.ic_new)
setOnClickListener {
interactor.onFabClicked(true)
interactor.onPrivateTabsFabClicked()
}
}
}
@ -73,11 +74,7 @@ class FloatingActionButtonBinding(
show()
setIconResource(R.drawable.ic_fab_sync)
setOnClickListener {
// Notify the store observers (one of which is the SyncedTabsFeature), that
// a sync was requested.
if (!syncing) {
store.dispatch(TabsTrayAction.SyncNow)
}
interactor.onSyncedTabsFabClicked()
}
}
}

@ -36,6 +36,7 @@ import org.mozilla.fenix.ext.potentialInactiveTabs
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.tabstray.browser.InactiveTabsController
import org.mozilla.fenix.tabstray.browser.TabsTrayFabController
import org.mozilla.fenix.tabstray.ext.isActiveDownload
import org.mozilla.fenix.tabstray.ext.isSelect
import org.mozilla.fenix.utils.Settings
@ -45,12 +46,7 @@ import org.mozilla.fenix.GleanMetrics.Tab as GleanTab
/**
* Controller for handling any actions in the tabs tray.
*/
interface TabsTrayController : SyncedTabsController, InactiveTabsController {
/**
* Called to open a new tab.
*/
fun handleOpeningNewTab(isPrivate: Boolean)
interface TabsTrayController : SyncedTabsController, InactiveTabsController, TabsTrayFabController {
/**
* Set the current tray item to the clamped [position].
@ -207,7 +203,26 @@ class DefaultTabsTrayController(
internal val showCancelledDownloadWarning: (downloadCount: Int, tabId: String?, source: String?) -> Unit,
) : TabsTrayController {
override fun handleOpeningNewTab(isPrivate: Boolean) {
override fun handleNormalTabsFabClick() {
openNewTab(isPrivate = false)
}
override fun handlePrivateTabsFabClick() {
openNewTab(isPrivate = true)
}
override fun handleSyncedTabsFabClick() {
if (!tabsTrayStore.state.syncing) {
tabsTrayStore.dispatch(TabsTrayAction.SyncNow)
}
}
/**
* Opens a new tab.
*
* @param isPrivate [Boolean] indicating whether the new tab is private.
*/
private fun openNewTab(isPrivate: Boolean) {
val startTime = profiler?.getProfilerTime()
browsingModeManager.mode = BrowsingMode.fromBoolean(isPrivate)
navController.navigate(

@ -9,11 +9,17 @@ import mozilla.components.browser.storage.sync.Tab
import mozilla.components.browser.tabstray.TabsTray
import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.tabstray.browser.InactiveTabsInteractor
import org.mozilla.fenix.tabstray.browser.TabsTrayFabInteractor
/**
* Interactor for responding to all user actions in the tabs tray.
*/
interface TabsTrayInteractor : SyncedTabsInteractor, TabsTray.Delegate, InactiveTabsInteractor {
interface TabsTrayInteractor :
SyncedTabsInteractor,
TabsTray.Delegate,
InactiveTabsInteractor,
TabsTrayFabInteractor {
/**
* Invoked when a page in the tabs tray is selected.
*
@ -56,13 +62,6 @@ interface TabsTrayInteractor : SyncedTabsInteractor, TabsTray.Delegate, Inactive
placeAfter: Boolean,
)
/**
* Invoked when the TabTray's Floating Action Button is clicked.
*
* @param isPrivate [Boolean] indicating whether the FAB was clicked in the private page of the tabs tray.
*/
fun onFabClicked(isPrivate: Boolean)
/**
* Invoked when the recently closed item is clicked.
*/
@ -161,8 +160,16 @@ class DefaultTabsTrayInteractor(
controller.handleTabSelected(tab, source)
}
override fun onFabClicked(isPrivate: Boolean) {
controller.handleOpeningNewTab(isPrivate)
override fun onNormalTabsFabClicked() {
controller.handleNormalTabsFabClick()
}
override fun onPrivateTabsFabClicked() {
controller.handlePrivateTabsFabClick()
}
override fun onSyncedTabsFabClicked() {
controller.handleSyncedTabsFabClick()
}
override fun onRecentlyClosedClicked() {

@ -0,0 +1,25 @@
/* 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.browser
/**
* Contract for handling all user interactions with the Tabs Tray floating action button.
*/
interface TabsTrayFabController {
/**
* Opens a new normal tab.
*/
fun handleNormalTabsFabClick()
/**
* Opens a new private tab.
*/
fun handlePrivateTabsFabClick()
/**
* Starts a re-sync of synced content if a sync isn't already underway.
*/
fun handleSyncedTabsFabClick()
}

@ -0,0 +1,27 @@
/* 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.browser
import org.mozilla.fenix.tabstray.Page
/**
* Interactor for all things related to the floating action button in the tabs tray.
*/
interface TabsTrayFabInteractor {
/**
* Invoked when the fab is clicked in [Page.NormalTabs].
*/
fun onNormalTabsFabClicked()
/**
* Invoked when the fab is clicked in [Page.PrivateTabs].
*/
fun onPrivateTabsFabClicked()
/**
* Invoked when the fab is clicked in [Page.SyncedTabs].
*/
fun onSyncedTabsFabClicked()
}

@ -95,14 +95,14 @@ class DefaultTabsTrayControllerTest {
}
@Test
fun `GIVEN private mode WHEN handleOpeningNewTab is called THEN a profile marker is added for the operations executed`() {
fun `GIVEN private mode WHEN the fab is clicked THEN a profile marker is added for the operations executed`() {
profiler = spyk(profiler) {
every { getProfilerTime() } returns Double.MAX_VALUE
}
assertNull(TabsTray.newPrivateTabTapped.testGetValue())
createController().handleOpeningNewTab(true)
createController().handlePrivateTabsFabClick()
assertNotNull(TabsTray.newPrivateTabTapped.testGetValue())
@ -120,12 +120,12 @@ class DefaultTabsTrayControllerTest {
}
@Test
fun `GIVEN normal mode WHEN handleOpeningNewTab is called THEN a profile marker is added for the operations executed`() {
fun `GIVEN normal mode WHEN the fab is clicked THEN a profile marker is added for the operations executed`() {
profiler = spyk(profiler) {
every { getProfilerTime() } returns Double.MAX_VALUE
}
createController().handleOpeningNewTab(false)
createController().handleNormalTabsFabClick()
verifyOrder {
profiler.getProfilerTime()
@ -141,23 +141,41 @@ class DefaultTabsTrayControllerTest {
}
@Test
fun `GIVEN private mode WHEN handleOpeningNewTab is called THEN Event#NewPrivateTabTapped is added to telemetry`() {
fun `GIVEN private mode WHEN the fab is clicked THEN Event#NewPrivateTabTapped is added to telemetry`() {
assertNull(TabsTray.newPrivateTabTapped.testGetValue())
createController().handleOpeningNewTab(true)
createController().handlePrivateTabsFabClick()
assertNotNull(TabsTray.newPrivateTabTapped.testGetValue())
}
@Test
fun `GIVEN private mode WHEN handleOpeningNewTab is called THEN Event#NewTabTapped is added to telemetry`() {
fun `GIVEN normal mode WHEN the fab is clicked THEN Event#NewTabTapped is added to telemetry`() {
assertNull(TabsTray.newTabTapped.testGetValue())
createController().handleOpeningNewTab(false)
createController().handleNormalTabsFabClick()
assertNotNull(TabsTray.newTabTapped.testGetValue())
}
@Test
fun `GIVEN the user is on the synced tabs page WHEN the fab is clicked THEN fire off a sync action`() {
every { trayStore.state.syncing } returns false
createController().handleSyncedTabsFabClick()
verify { trayStore.dispatch(TabsTrayAction.SyncNow) }
}
@Test
fun `GIVEN the user is on the synced tabs page and there is already an active sync WHEN the fab is clicked THEN no action should be taken`() {
every { trayStore.state.syncing } returns true
createController().handleSyncedTabsFabClick()
verify(exactly = 0) { trayStore.dispatch(TabsTrayAction.SyncNow) }
}
@Test
fun `WHEN handleTabDeletion is called THEN Event#ClosedExistingTab is added to telemetry`() {
val tab: TabSessionState = mockk { every { content.private } returns true }

@ -113,4 +113,25 @@ class DefaultTabsTrayInteractorTest {
verify { controller.handleDeleteAllInactiveTabsClicked() }
}
@Test
fun `GIVEN the user is viewing normal tabs WHEN the user clicks on the FAB THEN the Interactor delegates to the controller`() {
interactor.onNormalTabsFabClicked()
verifySequence { controller.handleNormalTabsFabClick() }
}
@Test
fun `GIVEN the user is viewing private tabs WHEN the user clicks on the FAB THEN the Interactor delegates to the controller`() {
interactor.onPrivateTabsFabClicked()
verifySequence { controller.handlePrivateTabsFabClick() }
}
@Test
fun `GIVEN the user is viewing synced tabs WHEN the user clicks on the FAB THEN the Interactor delegates to the controller`() {
interactor.onSyncedTabsFabClicked()
verifySequence { controller.handleSyncedTabsFabClick() }
}
}

@ -19,6 +19,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.TabsTrayFabInteractor
class FloatingActionButtonBindingTest {
@ -27,7 +28,7 @@ class FloatingActionButtonBindingTest {
val coroutinesTestRule = MainCoroutineRule()
private val actionButton: ExtendedFloatingActionButton = mockk(relaxed = true)
private val interactor: TabsTrayInteractor = mockk(relaxed = true)
private val interactor: TabsTrayFabInteractor = mockk(relaxed = true)
@Before
fun setup() {

Loading…
Cancel
Save