From 3983c509dc248151fc263fa61ece2e8c921c62c7 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 21 Sep 2020 16:24:56 +0200 Subject: [PATCH] Use "undo" implementation from Android Components. This is not the super fancy version yet - since we still need to restore into SessionManager and haven't fully switched to BrowserStore yet. However AC having knowledge about "undo" and whether it was performed or not, will help us with features like "recently closed tabs". And once we can improve "undo", Fenix will get all the nice things automatically. Requires: https://github.com/mozilla-mobile/android-components/pull/8449 --- .../fenix/browser/BaseBrowserFragment.kt | 10 +-- .../java/org/mozilla/fenix/components/Core.kt | 9 ++- .../org/mozilla/fenix/home/HomeFragment.kt | 67 +++++++------------ .../fenix/tabtray/TabTrayDialogFragment.kt | 13 +--- .../main/java/org/mozilla/fenix/utils/Undo.kt | 21 ++++-- 5 files changed, 51 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 72c2cd9d53..352fcc5226 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -260,9 +260,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session ) }, onCloseTab = { closedSession -> - val tab = store.state.findTab(closedSession.id) - ?: return@DefaultBrowserToolbarController - val isSelected = tab.id == context.components.core.store.state.selectedTabId + val tab = store.state.findTab(closedSession.id) ?: return@DefaultBrowserToolbarController val snackbarMessage = if (tab.content.private) { requireContext().getString(R.string.snackbar_private_tab_closed) @@ -275,11 +273,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session snackbarMessage, requireContext().getString(R.string.snackbar_deleted_undo), { - sessionManager.add( - closedSession, - isSelected, - engineSessionState = tab.engineState.engineSessionState - ) + requireComponents.useCases.tabsUseCases.undo.invoke() }, operation = { } ) diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index fbec09a5d7..a2ae6f8881 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -20,6 +20,7 @@ import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.engine.EngineMiddleware import mozilla.components.browser.session.storage.SessionStorage +import mozilla.components.browser.session.undo.UndoMiddleware import mozilla.components.browser.state.action.RecentlyClosedAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.store.BrowserStore @@ -69,6 +70,7 @@ import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.advanced.getSelectedLocale import org.mozilla.fenix.utils.Mockable +import org.mozilla.fenix.utils.getUndoDelay import java.util.concurrent.TimeUnit /** @@ -146,13 +148,18 @@ class Core(private val context: Context, private val crashReporter: CrashReporti MediaMiddleware(context, MediaService::class.java), DownloadMiddleware(context, DownloadService::class.java), ReaderViewMiddleware(), - ThumbnailsMiddleware(thumbnailStorage) + ThumbnailsMiddleware(thumbnailStorage), + UndoMiddleware(::lookupSessionManager, context.getUndoDelay()) ) + EngineMiddleware.create(engine, ::findSessionById) ).also { it.dispatch(RecentlyClosedAction.InitializeRecentlyClosedState) } } + private fun lookupSessionManager(): SessionManager { + return sessionManager + } + private fun findSessionById(tabId: String): Session? { return sessionManager.findSessionById(tabId) } diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 6df4a41318..707c584ee5 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -92,7 +92,6 @@ import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.resetPoliciesAfter -import org.mozilla.fenix.ext.sessionsOfType import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor @@ -469,17 +468,10 @@ class HomeFragment : Fragment() { } private fun removeAllTabsAndShowSnackbar(sessionCode: String) { - val tabs = sessionManager.sessionsOfType(private = sessionCode == ALL_PRIVATE_TABS).toList() - val selectedIndex = sessionManager - .selectedSession?.let { sessionManager.sessions.indexOf(it) } - ?: SessionManager.NO_SELECTION - - val snapshot = tabs - .map(sessionManager::createSessionSnapshot) - .let { SessionManager.Snapshot(it, selectedIndex) } - - tabs.forEach { - requireComponents.useCases.tabsUseCases.removeTab(it) + if (sessionCode == ALL_PRIVATE_TABS) { + sessionManager.removePrivateSessions() + } else { + sessionManager.removeNormalSessions() } val snackbarMessage = if (sessionCode == ALL_PRIVATE_TABS) { @@ -493,7 +485,7 @@ class HomeFragment : Fragment() { snackbarMessage, requireContext().getString(R.string.snackbar_deleted_undo), { - sessionManager.restore(snapshot) + requireComponents.useCases.tabsUseCases.undo.invoke() }, operation = { }, anchorView = snackbarAnchorView @@ -501,38 +493,29 @@ class HomeFragment : Fragment() { } private fun removeTabAndShowSnackbar(sessionId: String) { - sessionManager.findSessionById(sessionId)?.let { session -> - val snapshot = sessionManager.createSessionSnapshot(session) - val state = store.state.findTab(sessionId)?.engineState?.engineSessionState - val isSelected = - session.id == requireComponents.core.store.state.selectedTabId ?: false + val tab = store.state.findTab(sessionId) ?: return - requireComponents.useCases.tabsUseCases.removeTab(sessionId) - - val snackbarMessage = if (snapshot.session.private) { - requireContext().getString(R.string.snackbar_private_tab_closed) - } else { - requireContext().getString(R.string.snackbar_tab_closed) - } + requireComponents.useCases.tabsUseCases.removeTab(sessionId) - viewLifecycleOwner.lifecycleScope.allowUndo( - requireView(), - snackbarMessage, - requireContext().getString(R.string.snackbar_deleted_undo), - { - sessionManager.add( - snapshot.session, - isSelected, - engineSessionState = state - ) - findNavController().navigate( - HomeFragmentDirections.actionGlobalBrowser(null) - ) - }, - operation = { }, - anchorView = snackbarAnchorView - ) + val snackbarMessage = if (tab.content.private) { + requireContext().getString(R.string.snackbar_private_tab_closed) + } else { + requireContext().getString(R.string.snackbar_tab_closed) } + + viewLifecycleOwner.lifecycleScope.allowUndo( + requireView(), + snackbarMessage, + requireContext().getString(R.string.snackbar_deleted_undo), + { + requireComponents.useCases.tabsUseCases.undo.invoke() + findNavController().navigate( + HomeFragmentDirections.actionGlobalBrowser(null) + ) + }, + operation = { }, + anchorView = snackbarAnchorView + ) } override fun onDestroyView() { diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt index f03bd170ca..858be4e763 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt @@ -260,10 +260,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler private fun showUndoSnackbarForTab(sessionId: String) { val store = requireComponents.core.store - val sessionManager = requireComponents.core.sessionManager - val tab = requireComponents.core.store.state.findTab(sessionId) ?: return - val session = sessionManager.findSessionById(sessionId) ?: return // Check if this is the last tab of this session type val isLastOpenTab = @@ -273,8 +270,6 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler return } - val isSelected = sessionId == requireComponents.core.store.state.selectedTabId ?: false - val snackbarMessage = if (tab.content.private) { getString(R.string.snackbar_private_tab_closed) } else { @@ -286,12 +281,8 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler snackbarMessage, getString(R.string.snackbar_deleted_undo), { - sessionManager.add( - session, - isSelected, - engineSessionState = tab.engineState.engineSessionState - ) - _tabTrayView?.scrollToTab(session.id) + requireComponents.useCases.tabsUseCases.undo.invoke() + _tabTrayView?.scrollToTab(tab.id) }, operation = { }, elevation = ELEVATION, diff --git a/app/src/main/java/org/mozilla/fenix/utils/Undo.kt b/app/src/main/java/org/mozilla/fenix/utils/Undo.kt index 26dbf59eec..6dd272f5ad 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Undo.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Undo.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.utils +import android.content.Context import android.view.View import androidx.appcompat.widget.ContentFrameLayout import androidx.core.view.updatePadding @@ -19,6 +20,18 @@ import java.util.concurrent.atomic.AtomicBoolean internal const val UNDO_DELAY = 3000L internal const val ACCESSIBLE_UNDO_DELAY = 15000L +/** + * Get the recommended time an "undo" action should be available until it can automatically be + * dismissed. The delay may be different based on the accessibility settings of the device. + */ +fun Context.getUndoDelay(): Long { + return if (settings().accessibilityServicesEnabled) { + ACCESSIBLE_UNDO_DELAY + } else { + UNDO_DELAY + } +} + /** * Runs [operation] after giving user time (see [UNDO_DELAY]) to cancel it. * In case of cancellation, [onCancel] is executed. @@ -92,13 +105,7 @@ fun CoroutineScope.allowUndo( // Wait a bit, and if user didn't request cancellation, proceed with // requested operation and hide the snackbar. launch { - val lengthToDelay = if (view.context.settings().accessibilityServicesEnabled) { - ACCESSIBLE_UNDO_DELAY - } else { - UNDO_DELAY - } - - delay(lengthToDelay) + delay(view.context.getUndoDelay()) if (!requestedUndo.get()) { snackbar.dismiss()