For #12793: Improve snackbars for tabs tray
parent
6c58098fef
commit
9c56e1905b
|
@ -393,12 +393,62 @@ class HomeFragment : Fragment() {
|
|||
}
|
||||
|
||||
bundleArgs.getString(SESSION_TO_DELETE)?.also {
|
||||
sessionManager.findSessionById(it)?.let { session ->
|
||||
if (it == ALL_NORMAL_TABS || it == ALL_PRIVATE_TABS) {
|
||||
removeAllTabsAndShowSnackbar(it)
|
||||
} else {
|
||||
removeTabAndShowSnackbar(it)
|
||||
}
|
||||
}
|
||||
|
||||
updateTabCounter(requireComponents.core.store.state)
|
||||
}
|
||||
|
||||
private fun removeAllTabsAndShowSnackbar(sessionCode: String) {
|
||||
val tabs = sessionManager.sessionsOfType(private = sessionCode == ALL_PRIVATE_TABS).toList()
|
||||
val selectedIndex = sessionManager
|
||||
.selectedSession?.let { sessionManager.sessions.indexOf(it) } ?: 0
|
||||
|
||||
val snapshot = tabs
|
||||
.map(sessionManager::createSessionSnapshot)
|
||||
.map {
|
||||
it.copy(
|
||||
engineSession = null,
|
||||
engineSessionState = it.engineSession?.saveState()
|
||||
)
|
||||
}
|
||||
.let { SessionManager.Snapshot(it, selectedIndex) }
|
||||
|
||||
tabs.forEach {
|
||||
sessionManager.remove(it)
|
||||
}
|
||||
|
||||
val snackbarMessage = if (sessionCode == ALL_PRIVATE_TABS) {
|
||||
getString(R.string.snackbar_private_tabs_closed)
|
||||
} else {
|
||||
getString(R.string.snackbar_tabs_closed)
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.allowUndo(
|
||||
requireView(),
|
||||
snackbarMessage,
|
||||
requireContext().getString(R.string.snackbar_deleted_undo),
|
||||
{
|
||||
sessionManager.restore(snapshot)
|
||||
},
|
||||
operation = { },
|
||||
anchorView = snackbarAnchorView
|
||||
)
|
||||
}
|
||||
|
||||
private fun removeTabAndShowSnackbar(sessionId: String) {
|
||||
sessionManager.findSessionById(sessionId)?.let { session ->
|
||||
val snapshot = sessionManager.createSessionSnapshot(session)
|
||||
val state = snapshot.engineSession?.saveState()
|
||||
val isSelected =
|
||||
session.id == requireComponents.core.store.state.selectedTabId ?: false
|
||||
|
||||
sessionManager.remove(session)
|
||||
|
||||
val snackbarMessage = if (snapshot.session.private) {
|
||||
requireContext().getString(R.string.snackbar_private_tab_closed)
|
||||
} else {
|
||||
|
@ -420,13 +470,9 @@ class HomeFragment : Fragment() {
|
|||
operation = { },
|
||||
anchorView = snackbarAnchorView
|
||||
)
|
||||
requireComponents.useCases.tabsUseCases.removeTab.invoke(session)
|
||||
}
|
||||
}
|
||||
|
||||
updateTabCounter(requireComponents.core.store.state)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_sessionControlInteractor = null
|
||||
|
@ -888,6 +934,9 @@ class HomeFragment : Fragment() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val ALL_NORMAL_TABS = "all_normal"
|
||||
const val ALL_PRIVATE_TABS = "all_private"
|
||||
|
||||
private const val FOCUS_ON_ADDRESS_BAR = "focusOnAddressBar"
|
||||
private const val SESSION_TO_DELETE = "session_to_delete"
|
||||
private const val ANIMATION_DELAY = 100L
|
||||
|
|
|
@ -6,8 +6,8 @@ package org.mozilla.fenix.tabtray
|
|||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.navigation.NavController
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import mozilla.components.concept.engine.prompt.ShareData
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
|
@ -15,6 +15,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
|||
import org.mozilla.fenix.collections.SaveCollectionStep
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.sessionsOfType
|
||||
import org.mozilla.fenix.home.HomeFragment
|
||||
|
||||
/**
|
||||
* [TabTrayDialogFragment] controller.
|
||||
|
@ -34,7 +35,7 @@ class DefaultTabTrayController(
|
|||
private val activity: HomeActivity,
|
||||
private val navController: NavController,
|
||||
private val dismissTabTray: () -> Unit,
|
||||
private val showUndoSnackbar: (String, SessionManager.Snapshot) -> Unit,
|
||||
private val dismissTabTrayAndNavigateHome: (String) -> Unit,
|
||||
private val registerCollectionStorageObserver: () -> Unit
|
||||
) : TabTrayController {
|
||||
override fun onNewTabTapped(private: Boolean) {
|
||||
|
@ -89,35 +90,15 @@ class DefaultTabTrayController(
|
|||
navController.navigate(directions)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun onCloseAllTabsClicked(private: Boolean) {
|
||||
val sessionManager = activity.components.core.sessionManager
|
||||
val tabs = getListOfSessions(private)
|
||||
|
||||
val selectedIndex = sessionManager
|
||||
.selectedSession?.let { sessionManager.sessions.indexOf(it) } ?: 0
|
||||
|
||||
val snapshot = tabs
|
||||
.map(sessionManager::createSessionSnapshot)
|
||||
.map {
|
||||
it.copy(
|
||||
engineSession = null,
|
||||
engineSessionState = it.engineSession?.saveState()
|
||||
)
|
||||
}
|
||||
.let { SessionManager.Snapshot(it, selectedIndex) }
|
||||
|
||||
tabs.forEach {
|
||||
sessionManager.remove(it)
|
||||
}
|
||||
|
||||
val snackbarMessage = if (private) {
|
||||
activity.getString(R.string.snackbar_private_tabs_closed)
|
||||
val sessionsToClose = if (private) {
|
||||
HomeFragment.ALL_PRIVATE_TABS
|
||||
} else {
|
||||
activity.getString(R.string.snackbar_tabs_closed)
|
||||
HomeFragment.ALL_NORMAL_TABS
|
||||
}
|
||||
|
||||
showUndoSnackbar(snackbarMessage, snapshot)
|
||||
dismissTabTray()
|
||||
dismissTabTrayAndNavigateHome(sessionsToClose)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
|
|
|
@ -22,10 +22,6 @@ import kotlinx.android.synthetic.main.fragment_tab_tray_dialog.*
|
|||
import kotlinx.android.synthetic.main.fragment_tab_tray_dialog.view.*
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import mozilla.components.browser.state.selector.normalTabs
|
||||
import mozilla.components.browser.state.selector.privateTabs
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.feature.tab.collections.TabCollection
|
||||
import mozilla.components.feature.tabs.TabsUseCases
|
||||
|
@ -34,11 +30,11 @@ import mozilla.components.lib.state.ext.consumeFrom
|
|||
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.browser.BrowserFragmentDirections
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.components.TabCollectionStorage
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.getRootView
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.utils.allowUndo
|
||||
|
@ -82,13 +78,24 @@ class TabTrayDialogFragment : AppCompatDialogFragment() {
|
|||
override fun invoke(sessionId: String) {
|
||||
requireContext().components.analytics.metrics.track(Event.ClosedExistingTab)
|
||||
showUndoSnackbarForTab(sessionId)
|
||||
requireComponents.useCases.tabsUseCases.removeTab(sessionId)
|
||||
removeIfNotLastTab(sessionId)
|
||||
}
|
||||
|
||||
override fun invoke(session: Session) {
|
||||
requireContext().components.analytics.metrics.track(Event.ClosedExistingTab)
|
||||
showUndoSnackbarForTab(session.id)
|
||||
requireComponents.useCases.tabsUseCases.removeTab(session)
|
||||
removeIfNotLastTab(session.id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeIfNotLastTab(sessionId: String) {
|
||||
// We only want to *immediately* remove a tab if there are more than one in the tab tray
|
||||
// If there is only one, the HomeFragment handles deleting the tab (to better support snackbars)
|
||||
val sessionManager = view?.context?.components?.core?.sessionManager
|
||||
val sessionToRemove = sessionManager?.findSessionById(sessionId)
|
||||
|
||||
if (sessionManager?.sessions?.filter { sessionToRemove?.private == it.private }?.size != 1) {
|
||||
requireComponents.useCases.tabsUseCases.removeTab(sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,7 +134,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment() {
|
|||
activity = (activity as HomeActivity),
|
||||
navController = findNavController(),
|
||||
dismissTabTray = ::dismissAllowingStateLoss,
|
||||
showUndoSnackbar = ::showUndoSnackbar,
|
||||
dismissTabTrayAndNavigateHome = ::dismissTabTrayAndNavigateHome,
|
||||
registerCollectionStorageObserver = ::registerCollectionStorageObserver
|
||||
)
|
||||
),
|
||||
|
@ -177,7 +184,6 @@ class TabTrayDialogFragment : AppCompatDialogFragment() {
|
|||
|
||||
consumeFrom(requireComponents.core.store) {
|
||||
tabTrayView.updateState(it)
|
||||
navigateHomeIfNeeded(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,11 +197,20 @@ class TabTrayDialogFragment : AppCompatDialogFragment() {
|
|||
|
||||
private fun showUndoSnackbarForTab(sessionId: String) {
|
||||
val sessionManager = view?.context?.components?.core?.sessionManager
|
||||
|
||||
val snapshot = sessionManager
|
||||
?.findSessionById(sessionId)?.let {
|
||||
sessionManager.createSessionSnapshot(it)
|
||||
} ?: return
|
||||
|
||||
// Check if this is the last tab of this session type
|
||||
val isLastOpenTab = sessionManager.sessions.filter { snapshot.session.private == it.private }.size == 1
|
||||
|
||||
if (isLastOpenTab) {
|
||||
dismissTabTrayAndNavigateHome(sessionId)
|
||||
return
|
||||
}
|
||||
|
||||
val state = snapshot.engineSession?.saveState()
|
||||
val isSelected = sessionId == requireComponents.core.store.state.selectedTabId ?: false
|
||||
|
||||
|
@ -205,13 +220,8 @@ class TabTrayDialogFragment : AppCompatDialogFragment() {
|
|||
getString(R.string.snackbar_tab_closed)
|
||||
}
|
||||
|
||||
// Check if this is the last tab of this session type
|
||||
val isLastOpenTab = sessionManager.sessions.filter { snapshot.session.private == it.private }.size == 1
|
||||
val rootView = if (isLastOpenTab) { requireActivity().getRootView()!! } else { requireView().tabLayout }
|
||||
val anchorView = if (isLastOpenTab) { null } else { snackbarAnchor }
|
||||
|
||||
requireActivity().lifecycleScope.allowUndo(
|
||||
rootView,
|
||||
lifecycleScope.allowUndo(
|
||||
requireView().tabLayout,
|
||||
snackbarMessage,
|
||||
getString(R.string.snackbar_deleted_undo),
|
||||
{
|
||||
|
@ -220,19 +230,15 @@ class TabTrayDialogFragment : AppCompatDialogFragment() {
|
|||
},
|
||||
operation = { },
|
||||
elevation = ELEVATION,
|
||||
paddedForBottomToolbar = isLastOpenTab,
|
||||
anchorView = anchorView
|
||||
anchorView = snackbarAnchor
|
||||
)
|
||||
|
||||
dismissTabTrayIfNecessary()
|
||||
}
|
||||
|
||||
private fun dismissTabTrayIfNecessary() {
|
||||
if (requireComponents.core.sessionManager.sessions.size == 1) {
|
||||
findNavController().popBackStack(R.id.homeFragment, false)
|
||||
private fun dismissTabTrayAndNavigateHome(sessionId: String) {
|
||||
val directions = BrowserFragmentDirections.actionGlobalHome(sessionToDelete = sessionId)
|
||||
findNavController().navigate(directions)
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
_tabTrayView = null
|
||||
|
@ -247,39 +253,10 @@ class TabTrayDialogFragment : AppCompatDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun navigateHomeIfNeeded(state: BrowserState) {
|
||||
val shouldPop = if (tabTrayView.isPrivateModeSelected) {
|
||||
state.privateTabs.isEmpty()
|
||||
} else {
|
||||
state.normalTabs.isEmpty()
|
||||
}
|
||||
|
||||
if (shouldPop) {
|
||||
findNavController().popBackStack(R.id.homeFragment, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerCollectionStorageObserver() {
|
||||
requireComponents.core.tabCollectionStorage.register(collectionStorageObserver, this)
|
||||
}
|
||||
|
||||
private fun showUndoSnackbar(snackbarMessage: String, snapshot: SessionManager.Snapshot) {
|
||||
// Warning: removing this definition and using it directly in the onCancel block will fail silently.
|
||||
val sessionManager = view?.context?.components?.core?.sessionManager
|
||||
|
||||
requireActivity().lifecycleScope.allowUndo(
|
||||
requireActivity().getRootView()!!,
|
||||
snackbarMessage,
|
||||
getString(R.string.snackbar_deleted_undo),
|
||||
{
|
||||
sessionManager?.restore(snapshot)
|
||||
},
|
||||
operation = { },
|
||||
elevation = ELEVATION,
|
||||
paddedForBottomToolbar = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun showCollectionSnackbar(tabSize: Int, isNewCollection: Boolean = false) {
|
||||
view.let {
|
||||
val messageStringRes = when {
|
||||
|
|
|
@ -35,6 +35,7 @@ class DefaultTabTrayControllerTest {
|
|||
private val navController: NavController = mockk()
|
||||
private val sessionManager: SessionManager = mockk(relaxed = true)
|
||||
private val dismissTabTray: (() -> Unit) = mockk(relaxed = true)
|
||||
private val dismissTabTrayAndNavigateHome: ((String) -> Unit) = mockk(relaxed = true)
|
||||
private val showUndoSnackbar: ((String, SessionManager.Snapshot) -> Unit) =
|
||||
mockk(relaxed = true)
|
||||
private val registerCollectionStorageObserver: (() -> Unit) = mockk(relaxed = true)
|
||||
|
@ -78,7 +79,7 @@ class DefaultTabTrayControllerTest {
|
|||
activity = activity,
|
||||
navController = navController,
|
||||
dismissTabTray = dismissTabTray,
|
||||
showUndoSnackbar = showUndoSnackbar,
|
||||
dismissTabTrayAndNavigateHome = dismissTabTrayAndNavigateHome,
|
||||
registerCollectionStorageObserver = registerCollectionStorageObserver
|
||||
)
|
||||
}
|
||||
|
@ -155,12 +156,9 @@ class DefaultTabTrayControllerTest {
|
|||
@Test
|
||||
fun onCloseAllTabsClicked() {
|
||||
controller.onCloseAllTabsClicked(private = false)
|
||||
val snackbarMessage = activity.getString(R.string.snackbar_tabs_closed)
|
||||
|
||||
verify {
|
||||
sessionManager.createSessionSnapshot(nonPrivateSession)
|
||||
sessionManager.remove(nonPrivateSession)
|
||||
showUndoSnackbar(snackbarMessage, any())
|
||||
dismissTabTrayAndNavigateHome(any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue