Close #25954: add a new delete time range confirmation dialog for history screen
This commit is contained in:
parent
bfd0eb7306
commit
7982c6b79f
|
@ -71,7 +71,7 @@ class HistoryRobot {
|
|||
deleteButton(item).click()
|
||||
}
|
||||
|
||||
fun clickDeleteAllHistoryButton() = deleteAllButton().click()
|
||||
fun clickDeleteAllHistoryButton() = deleteButton().click()
|
||||
|
||||
fun confirmDeleteAllHistory() {
|
||||
onView(withText("Delete"))
|
||||
|
@ -104,7 +104,7 @@ private fun pageUrl() = onView(withId(R.id.url))
|
|||
private fun deleteButton(title: String) =
|
||||
onView(allOf(withId(R.id.overflow_menu), hasSibling(withText(title))))
|
||||
|
||||
private fun deleteAllButton() = onView(withId(R.id.history_delete_all))
|
||||
private fun deleteButton() = onView(withId(R.id.history_delete))
|
||||
|
||||
private fun snackBarText() = onView(withId(R.id.snackbar_text))
|
||||
|
||||
|
@ -147,7 +147,7 @@ private fun assertPageUrl(expectedUrl: Uri) = pageUrl()
|
|||
.check(matches(withText(Matchers.containsString(expectedUrl.toString()))))
|
||||
|
||||
private fun assertDeleteConfirmationMessage() =
|
||||
onView(withText("This will delete all of your browsing data."))
|
||||
onView(withText("Removes history (including history synced from other devices), cookies and other browsing data."))
|
||||
.inRoot(isDialog())
|
||||
.check(matches(isDisplayed()))
|
||||
|
||||
|
|
|
@ -10,7 +10,11 @@ import androidx.navigation.NavOptions
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.state.action.EngineAction
|
||||
import mozilla.components.browser.state.action.HistoryMetadataAction
|
||||
import mozilla.components.browser.state.action.RecentlyClosedAction
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
||||
import mozilla.components.service.glean.private.NoExtras
|
||||
import org.mozilla.fenix.GleanMetrics.Events
|
||||
import org.mozilla.fenix.R
|
||||
|
@ -19,6 +23,7 @@ import org.mozilla.fenix.components.appstate.AppAction
|
|||
import org.mozilla.fenix.components.history.DefaultPagedHistoryProvider
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.navigateSafe
|
||||
import org.mozilla.fenix.library.history.HistoryFragment.DeleteConfirmationDialogFragment
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
import org.mozilla.fenix.GleanMetrics.History as GleanHistory
|
||||
|
||||
|
@ -30,10 +35,22 @@ interface HistoryController {
|
|||
fun handleBackPressed(): Boolean
|
||||
fun handleModeSwitched()
|
||||
fun handleSearch()
|
||||
fun handleDeleteAll()
|
||||
|
||||
/**
|
||||
* Displays a [DeleteConfirmationDialogFragment].
|
||||
*/
|
||||
fun handleDeleteTimeRange()
|
||||
fun handleDeleteSome(items: Set<History>)
|
||||
|
||||
/**
|
||||
* Deletes history items inside the time frame.
|
||||
*
|
||||
* @param timeFrame Selected time frame by the user. If `null`, removes all history.
|
||||
*/
|
||||
fun handleDeleteTimeRangeConfirmed(timeFrame: RemoveTimeFrame?)
|
||||
fun handleRequestSync()
|
||||
fun handleEnterRecentlyClosed()
|
||||
|
||||
/**
|
||||
* Navigates to [org.mozilla.fenix.library.syncedhistory.SyncedHistoryFragment]
|
||||
*/
|
||||
|
@ -44,11 +61,14 @@ interface HistoryController {
|
|||
class DefaultHistoryController(
|
||||
private val store: HistoryFragmentStore,
|
||||
private val appStore: AppStore,
|
||||
private val browserStore: BrowserStore,
|
||||
private val historyStorage: PlacesHistoryStorage,
|
||||
private var historyProvider: DefaultPagedHistoryProvider,
|
||||
private val navController: NavController,
|
||||
private val scope: CoroutineScope,
|
||||
private val openToBrowser: (item: History.Regular) -> Unit,
|
||||
private val displayDeleteAll: () -> Unit,
|
||||
private val displayDeleteTimeRange: () -> Unit,
|
||||
private val onTimeFrameDeleted: () -> Unit,
|
||||
private val invalidateOptionsMenu: () -> Unit,
|
||||
private val deleteSnackbar: (
|
||||
items: Set<History>,
|
||||
|
@ -111,8 +131,8 @@ class DefaultHistoryController(
|
|||
navController.navigateSafe(R.id.historyFragment, directions)
|
||||
}
|
||||
|
||||
override fun handleDeleteAll() {
|
||||
displayDeleteAll.invoke()
|
||||
override fun handleDeleteTimeRange() {
|
||||
displayDeleteTimeRange.invoke()
|
||||
}
|
||||
|
||||
override fun handleDeleteSome(items: Set<History>) {
|
||||
|
@ -121,6 +141,31 @@ class DefaultHistoryController(
|
|||
deleteSnackbar.invoke(items, ::undo, ::delete)
|
||||
}
|
||||
|
||||
override fun handleDeleteTimeRangeConfirmed(timeFrame: RemoveTimeFrame?) {
|
||||
scope.launch {
|
||||
store.dispatch(HistoryFragmentAction.EnterDeletionMode)
|
||||
if (timeFrame == null) {
|
||||
GleanHistory.removedAll.record(mozilla.telemetry.glean.private.NoExtras())
|
||||
historyStorage.deleteEverything()
|
||||
} else {
|
||||
historyStorage.deleteVisitsBetween(
|
||||
startTime = timeFrame.toLongRange().first,
|
||||
endTime = timeFrame.toLongRange().last,
|
||||
)
|
||||
}
|
||||
// We introduced more deleting options, but are keeping these actions for all options.
|
||||
// The approach could be improved: https://github.com/mozilla-mobile/fenix/issues/26102
|
||||
browserStore.dispatch(RecentlyClosedAction.RemoveAllClosedTabAction)
|
||||
browserStore.dispatch(EngineAction.PurgeHistoryAction).join()
|
||||
|
||||
store.dispatch(HistoryFragmentAction.ExitDeletionMode)
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
onTimeFrameDeleted.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun undo(items: Set<History>) {
|
||||
val pendingDeletionItems = items.map { it.toPendingDeletionHistory() }.toSet()
|
||||
appStore.dispatch(AppAction.UndoPendingDeletionSet(pendingDeletionItems))
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.mozilla.fenix.library.history
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
|
@ -14,7 +15,9 @@ import android.view.MenuInflater
|
|||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.RadioGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavDirections
|
||||
import androidx.navigation.fragment.findNavController
|
||||
|
@ -23,14 +26,9 @@ import androidx.paging.PagingConfig
|
|||
import androidx.paging.PagingData
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.state.action.EngineAction
|
||||
import mozilla.components.browser.state.action.RecentlyClosedAction
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.prompt.ShareData
|
||||
import mozilla.components.lib.state.ext.consumeFrom
|
||||
import mozilla.components.lib.state.ext.flowScoped
|
||||
|
@ -48,11 +46,12 @@ import org.mozilla.fenix.components.StoreProvider
|
|||
import org.mozilla.fenix.components.history.DefaultPagedHistoryProvider
|
||||
import org.mozilla.fenix.databinding.FragmentHistoryBinding
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.getRootView
|
||||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.runIfFragmentIsAttached
|
||||
import org.mozilla.fenix.ext.setTextColor
|
||||
import org.mozilla.fenix.ext.toShortUrl
|
||||
import org.mozilla.fenix.ext.getRootView
|
||||
import org.mozilla.fenix.library.LibraryPageFragment
|
||||
import org.mozilla.fenix.utils.allowUndo
|
||||
import org.mozilla.fenix.GleanMetrics.History as GleanHistory
|
||||
|
@ -100,13 +99,16 @@ class HistoryFragment : LibraryPageFragment<History>(), UserInteractionHandler {
|
|||
val historyController: HistoryController = DefaultHistoryController(
|
||||
store = historyStore,
|
||||
appStore = requireContext().components.appStore,
|
||||
browserStore = requireComponents.core.store,
|
||||
historyStorage = requireComponents.core.historyStorage,
|
||||
historyProvider = historyProvider,
|
||||
navController = findNavController(),
|
||||
scope = lifecycleScope,
|
||||
openToBrowser = ::openItem,
|
||||
displayDeleteAll = ::displayDeleteAllDialog,
|
||||
displayDeleteTimeRange = ::displayDeleteTimeRange,
|
||||
invalidateOptionsMenu = ::invalidateOptionsMenu,
|
||||
deleteSnackbar = :: deleteSnackbar,
|
||||
deleteSnackbar = ::deleteSnackbar,
|
||||
onTimeFrameDeleted = ::onTimeFrameDeleted,
|
||||
syncHistory = ::syncHistory,
|
||||
settings = requireContext().components.settings,
|
||||
)
|
||||
|
@ -173,6 +175,16 @@ class HistoryFragment : LibraryPageFragment<History>(), UserInteractionHandler {
|
|||
)
|
||||
}
|
||||
|
||||
private fun onTimeFrameDeleted() {
|
||||
runIfFragmentIsAttached {
|
||||
historyView.historyAdapter.refresh()
|
||||
showSnackBar(
|
||||
binding.root,
|
||||
getString(R.string.preferences_delete_browsing_data_snackbar)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
@ -280,8 +292,8 @@ class HistoryFragment : LibraryPageFragment<History>(), UserInteractionHandler {
|
|||
historyInteractor.onSearch()
|
||||
true
|
||||
}
|
||||
R.id.history_delete_all -> {
|
||||
historyInteractor.onDeleteAll()
|
||||
R.id.history_delete -> {
|
||||
historyInteractor.onDeleteTimeRange()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
@ -331,40 +343,10 @@ class HistoryFragment : LibraryPageFragment<History>(), UserInteractionHandler {
|
|||
)
|
||||
}
|
||||
|
||||
private fun displayDeleteAllDialog() {
|
||||
activity?.let { activity ->
|
||||
AlertDialog.Builder(activity).apply {
|
||||
setMessage(R.string.delete_browsing_data_prompt_message)
|
||||
setNegativeButton(R.string.delete_browsing_data_prompt_cancel) { dialog: DialogInterface, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
setPositiveButton(R.string.delete_browsing_data_prompt_allow) { dialog: DialogInterface, _ ->
|
||||
historyStore.dispatch(HistoryFragmentAction.EnterDeletionMode)
|
||||
// Use fragment's lifecycle; the view may be gone by the time dialog is interacted with.
|
||||
lifecycleScope.launch(IO) {
|
||||
GleanHistory.removedAll.record(NoExtras())
|
||||
requireComponents.core.store.dispatch(RecentlyClosedAction.RemoveAllClosedTabAction)
|
||||
requireComponents.core.historyStorage.deleteEverything()
|
||||
deleteOpenTabsEngineHistory(requireComponents.core.store)
|
||||
launch(Main) {
|
||||
historyView.historyAdapter.refresh()
|
||||
historyStore.dispatch(HistoryFragmentAction.ExitDeletionMode)
|
||||
showSnackBar(
|
||||
requireView(),
|
||||
getString(R.string.preferences_delete_browsing_data_snackbar)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
create()
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun deleteOpenTabsEngineHistory(store: BrowserStore) {
|
||||
store.dispatch(EngineAction.PurgeHistoryAction).join()
|
||||
private fun displayDeleteTimeRange() {
|
||||
DeleteConfirmationDialogFragment(
|
||||
historyInteractor = historyInteractor
|
||||
).show(childFragmentManager, null)
|
||||
}
|
||||
|
||||
private fun share(data: List<ShareData>) {
|
||||
|
@ -389,6 +371,33 @@ class HistoryFragment : LibraryPageFragment<History>(), UserInteractionHandler {
|
|||
historyView.historyAdapter.refresh()
|
||||
}
|
||||
|
||||
internal class DeleteConfirmationDialogFragment(
|
||||
private val historyInteractor: HistoryInteractor
|
||||
) : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
|
||||
AlertDialog.Builder(requireContext()).apply {
|
||||
val layout = LayoutInflater.from(context)
|
||||
.inflate(R.layout.delete_history_time_range_dialog, null)
|
||||
val radioGroup = layout.findViewById<RadioGroup>(R.id.radio_group)
|
||||
radioGroup.check(R.id.last_hour_button)
|
||||
setView(layout)
|
||||
|
||||
setNegativeButton(R.string.delete_browsing_data_prompt_cancel) { dialog: DialogInterface, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
setPositiveButton(R.string.delete_browsing_data_prompt_allow) { dialog: DialogInterface, _ ->
|
||||
val selectedTimeFrame = when (radioGroup.checkedRadioButtonId) {
|
||||
R.id.last_hour_button -> RemoveTimeFrame.LastHour
|
||||
R.id.today_and_yesterday_button -> RemoveTimeFrame.TodayAndYesterday
|
||||
R.id.everything_button -> null
|
||||
else -> throw IllegalStateException("Unexpected radioButtonId")
|
||||
}
|
||||
historyInteractor.onDeleteTimeRangeConfirmed(selectedTimeFrame)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
companion object {
|
||||
private const val PAGE_SIZE = 25
|
||||
|
|
|
@ -28,9 +28,9 @@ interface HistoryInteractor : SelectionInteractor<History> {
|
|||
fun onSearch()
|
||||
|
||||
/**
|
||||
* Called when delete all is tapped
|
||||
* Called when the delete menu button is tapped.
|
||||
*/
|
||||
fun onDeleteAll()
|
||||
fun onDeleteTimeRange()
|
||||
|
||||
/**
|
||||
* Called when multiple history items are deleted
|
||||
|
@ -38,6 +38,14 @@ interface HistoryInteractor : SelectionInteractor<History> {
|
|||
*/
|
||||
fun onDeleteSome(items: Set<History>)
|
||||
|
||||
/**
|
||||
* Called when the user has confirmed deletion of a time range.
|
||||
*
|
||||
* @param timeFrame The selected timeframe. `null` means no specific time frame has been
|
||||
* selected; should remove everything.
|
||||
*/
|
||||
fun onDeleteTimeRangeConfirmed(timeFrame: RemoveTimeFrame?)
|
||||
|
||||
/**
|
||||
* Called when the user requests a sync of the history
|
||||
*/
|
||||
|
@ -86,14 +94,18 @@ class DefaultHistoryInteractor(
|
|||
historyController.handleSearch()
|
||||
}
|
||||
|
||||
override fun onDeleteAll() {
|
||||
historyController.handleDeleteAll()
|
||||
override fun onDeleteTimeRange() {
|
||||
historyController.handleDeleteTimeRange()
|
||||
}
|
||||
|
||||
override fun onDeleteSome(items: Set<History>) {
|
||||
historyController.handleDeleteSome(items)
|
||||
}
|
||||
|
||||
override fun onDeleteTimeRangeConfirmed(timeFrame: RemoveTimeFrame?) {
|
||||
historyController.handleDeleteTimeRangeConfirmed(timeFrame)
|
||||
}
|
||||
|
||||
override fun onRequestSync() {
|
||||
historyController.handleRequestSync()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/* 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.library.history
|
||||
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* A helper class that provides starting and ending timestamps for a set time frame. Is used by
|
||||
* [HistoryFragment] to provide timestamps for options inside the delete history dialog.
|
||||
*/
|
||||
enum class RemoveTimeFrame {
|
||||
LastHour,
|
||||
TodayAndYesterday;
|
||||
|
||||
/**
|
||||
* Provides starting and ending timestamps for a set time frame. Each call is calculated at the
|
||||
* moment of execution, which is different from [HistoryItemTimeGroup] implementation.
|
||||
*/
|
||||
fun toLongRange(): LongRange {
|
||||
return when (this) {
|
||||
LastHour -> LongRange(getHourAgo(hoursAgo = 1).time, Long.MAX_VALUE)
|
||||
TodayAndYesterday -> LongRange(getDaysAgo(daysAgo = 1).time, Long.MAX_VALUE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHourAgo(hoursAgo: Int): Date {
|
||||
return Calendar.getInstance().apply {
|
||||
add(Calendar.HOUR_OF_DAY, -hoursAgo)
|
||||
}.time
|
||||
}
|
||||
|
||||
private fun getDaysAgo(daysAgo: Int): Date {
|
||||
return Calendar.getInstance().apply {
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
add(Calendar.DAY_OF_YEAR, -daysAgo)
|
||||
}.time
|
||||
}
|
||||
}
|
|
@ -98,6 +98,7 @@ class HistoryMetadataGroupFragment :
|
|||
store = historyMetadataGroupStore,
|
||||
selectOrAddUseCase = requireComponents.useCases.tabsUseCases.selectOrAddTab,
|
||||
navController = findNavController(),
|
||||
scope = CoroutineScope(Dispatchers.IO),
|
||||
searchTerm = args.title,
|
||||
deleteSnackbar = :: deleteSnackbar,
|
||||
promptDeleteAll = :: promptDeleteAll,
|
||||
|
@ -194,8 +195,8 @@ class HistoryMetadataGroupFragment :
|
|||
showTabTray()
|
||||
true
|
||||
}
|
||||
R.id.history_delete_all -> {
|
||||
interactor.onDeleteAllMenuItem()
|
||||
R.id.history_delete -> {
|
||||
interactor.onDeleteAll()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
@ -218,14 +219,14 @@ class HistoryMetadataGroupFragment :
|
|||
)
|
||||
}
|
||||
|
||||
private fun promptDeleteAll(delete: () -> Unit) {
|
||||
private fun promptDeleteAll() {
|
||||
if (childFragmentManager.findFragmentByTag(DeleteAllConfirmationDialogFragment.TAG)
|
||||
as? DeleteAllConfirmationDialogFragment != null
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
DeleteAllConfirmationDialogFragment(delete).show(
|
||||
DeleteAllConfirmationDialogFragment(interactor, args.title).show(
|
||||
childFragmentManager, DeleteAllConfirmationDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
|
@ -254,15 +255,23 @@ class HistoryMetadataGroupFragment :
|
|||
)
|
||||
}
|
||||
|
||||
internal class DeleteAllConfirmationDialogFragment(private val delete: () -> Unit) : DialogFragment() {
|
||||
internal class DeleteAllConfirmationDialogFragment(
|
||||
private val interactor: HistoryMetadataGroupInteractor,
|
||||
private val groupName: String
|
||||
) : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setMessage(R.string.delete_history_group_prompt_message)
|
||||
.setMessage(
|
||||
String.format(
|
||||
getString(R.string.delete_all_history_group_prompt_message),
|
||||
groupName
|
||||
)
|
||||
)
|
||||
.setNegativeButton(R.string.delete_history_group_prompt_cancel) { dialog: DialogInterface, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
.setPositiveButton(R.string.delete_history_group_prompt_allow) { dialog: DialogInterface, _ ->
|
||||
delete.invoke()
|
||||
interactor.onDeleteAllConfirmed()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.create()
|
||||
|
|
|
@ -7,7 +7,6 @@ package org.mozilla.fenix.library.historymetadata.controller
|
|||
import android.content.Context
|
||||
import androidx.navigation.NavController
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.state.action.HistoryMetadataAction
|
||||
|
@ -22,6 +21,7 @@ import org.mozilla.fenix.ext.components
|
|||
import mozilla.components.service.glean.private.NoExtras
|
||||
import org.mozilla.fenix.library.history.History
|
||||
import org.mozilla.fenix.library.history.toPendingDeletionHistory
|
||||
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragment.DeleteAllConfirmationDialogFragment
|
||||
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentAction
|
||||
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentDirections
|
||||
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentStore
|
||||
|
@ -74,9 +74,14 @@ interface HistoryMetadataGroupController {
|
|||
fun handleDelete(items: Set<History.Metadata>)
|
||||
|
||||
/**
|
||||
* Deletes all the history metadata items in this group.
|
||||
* Displays a [DeleteAllConfirmationDialogFragment] prompt.
|
||||
*/
|
||||
fun handleDeleteAll()
|
||||
|
||||
/**
|
||||
* Deletes history metadata items in this group.
|
||||
*/
|
||||
fun handleDeleteAllConfirmed()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,13 +95,14 @@ class DefaultHistoryMetadataGroupController(
|
|||
private val store: HistoryMetadataGroupFragmentStore,
|
||||
private val selectOrAddUseCase: TabsUseCases.SelectOrAddUseCase,
|
||||
private val navController: NavController,
|
||||
private val scope: CoroutineScope,
|
||||
private val searchTerm: String,
|
||||
private val deleteSnackbar: (
|
||||
items: Set<History.Metadata>,
|
||||
undo: suspend (Set<History.Metadata>) -> Unit,
|
||||
delete: (Set<History.Metadata>) -> suspend (context: Context) -> Unit
|
||||
) -> Unit,
|
||||
private val promptDeleteAll: (() -> Unit) -> Unit,
|
||||
private val promptDeleteAll: () -> Unit,
|
||||
private val allDeletedSnackbar: () -> Unit
|
||||
) : HistoryMetadataGroupController {
|
||||
|
||||
|
@ -144,7 +150,7 @@ class DefaultHistoryMetadataGroupController(
|
|||
|
||||
private fun delete(items: Set<History.Metadata>): suspend (context: Context) -> Unit {
|
||||
return { context ->
|
||||
CoroutineScope(IO).launch {
|
||||
scope.launch {
|
||||
val isDeletingLastItem = items.containsAll(store.state.items)
|
||||
items.forEach {
|
||||
store.dispatch(HistoryMetadataGroupFragmentAction.Delete(it))
|
||||
|
@ -163,11 +169,11 @@ class DefaultHistoryMetadataGroupController(
|
|||
}
|
||||
|
||||
override fun handleDeleteAll() {
|
||||
promptDeleteAll.invoke(::deleteAll)
|
||||
promptDeleteAll.invoke()
|
||||
}
|
||||
|
||||
private fun deleteAll() {
|
||||
CoroutineScope(IO).launch {
|
||||
override fun handleDeleteAllConfirmed() {
|
||||
scope.launch {
|
||||
store.dispatch(HistoryMetadataGroupFragmentAction.DeleteAll)
|
||||
store.state.items.forEach {
|
||||
historyStorage.deleteVisitsFor(it.url)
|
||||
|
|
|
@ -29,10 +29,14 @@ interface HistoryMetadataGroupInteractor : SelectionInteractor<History.Metadata>
|
|||
fun onDelete(items: Set<History.Metadata>)
|
||||
|
||||
/**
|
||||
* Deletes all the history items in the history metadata group. Called when a user clicks
|
||||
* on the "Delete history" menu item.
|
||||
* Called when a user clicks on the "Delete history" menu item.
|
||||
*/
|
||||
fun onDeleteAllMenuItem()
|
||||
fun onDeleteAll()
|
||||
|
||||
/**
|
||||
* Called when a user has confirmed the deletion of the group.
|
||||
*/
|
||||
fun onDeleteAllConfirmed()
|
||||
|
||||
/**
|
||||
* Opens the share sheet for a set of history [items]. Called when a user clicks on the
|
||||
|
@ -70,10 +74,14 @@ class DefaultHistoryMetadataGroupInteractor(
|
|||
controller.handleDelete(items)
|
||||
}
|
||||
|
||||
override fun onDeleteAllMenuItem() {
|
||||
override fun onDeleteAll() {
|
||||
controller.handleDeleteAll()
|
||||
}
|
||||
|
||||
override fun onDeleteAllConfirmed() {
|
||||
controller.handleDeleteAllConfirmed()
|
||||
}
|
||||
|
||||
override fun onShareMenuItem(items: Set<History.Metadata>) {
|
||||
controller.handleShare(items)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:ellipsize="end"
|
||||
android:text="@string/delete_history_prompt_title"
|
||||
android:textColor="?attr/textPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/delete_history_prompt_body"
|
||||
android:textColor="?attr/textPrimary"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/radio_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/body">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/last_hour_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:ellipsize="end"
|
||||
android:buttonTint="?attr/textSecondary"
|
||||
android:layout_marginStart="18dp"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/delete_history_prompt_button_last_hour"
|
||||
android:textColor="?attr/textPrimary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/today_and_yesterday_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:ellipsize="end"
|
||||
android:buttonTint="?attr/textSecondary"
|
||||
android:layout_marginStart="18dp"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/delete_history_prompt_button_today_and_yesterday"
|
||||
android:textColor="?attr/textPrimary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/everything_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:ellipsize="end"
|
||||
android:buttonTint="?attr/textSecondary"
|
||||
android:layout_marginStart="18dp"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/delete_history_prompt_button_everything"
|
||||
android:textColor="?attr/textPrimary"
|
||||
android:textSize="16sp" />
|
||||
</RadioGroup>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -11,7 +11,7 @@
|
|||
app:iconTint="?attr/textPrimary"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/history_delete_all"
|
||||
android:id="@+id/history_delete"
|
||||
android:icon="@drawable/ic_delete"
|
||||
android:title="@string/history_delete_all"
|
||||
app:iconTint="?attr/textPrimary"
|
||||
|
|
|
@ -1118,7 +1118,18 @@
|
|||
<string name="delete_browsing_data_on_quit_action">Quit</string>
|
||||
|
||||
<!-- Dialog message to the user asking to delete browsing data. -->
|
||||
<string name="delete_browsing_data_prompt_message">This will delete all of your browsing data.</string>
|
||||
<string moz:removedIn="105" name="delete_browsing_data_prompt_message" tools:ignore="UnusedResources">This will delete all of your browsing data.</string>
|
||||
<!-- Title text of a delete browsing data dialog. -->
|
||||
<string name="delete_history_prompt_title">Time range to delete</string>
|
||||
<!-- Body text of a delete browsing data dialog. -->
|
||||
<string name="delete_history_prompt_body">Removes history (including history synced from other devices), cookies and other browsing data.</string>
|
||||
<!-- Radio button of a delete browsing data dialog, to delete history items for the last hour. -->
|
||||
<string name="delete_history_prompt_button_last_hour">Last hour</string>
|
||||
<!-- Radio button of a delete browsing data dialog, to delete history items for today and yesterday. -->
|
||||
<string name="delete_history_prompt_button_today_and_yesterday">Today and yesterday</string>
|
||||
<!-- Radio button of a delete browsing data dialog, to delete all history. -->
|
||||
<string name="delete_history_prompt_button_everything">Everything</string>
|
||||
|
||||
<!-- Dialog message to the user asking to delete browsing data. Parameter will be replaced by app name. -->
|
||||
<string name="delete_browsing_data_prompt_message_3">%s will delete the selected browsing data.</string>
|
||||
<!-- Text for the cancel button for the data deletion dialog -->
|
||||
|
@ -1131,7 +1142,7 @@
|
|||
<string name="deleting_browsing_data_in_progress">Deleting browsing data…</string>
|
||||
|
||||
<!-- Dialog message to the user asking to delete all history items inside the opened group. -->
|
||||
<string name="delete_history_group_prompt_message">This will delete all items.</string>
|
||||
<string name="delete_all_history_group_prompt_message">Delete all sites in \"%s\"</string>
|
||||
<!-- Text for the cancel button for the history group deletion dialog -->
|
||||
<string name="delete_history_group_prompt_cancel">Cancel</string>
|
||||
<!-- Text for the allow button for the history group dialog -->
|
||||
|
|
|
@ -9,6 +9,9 @@ import io.mockk.coVerifyOrder
|
|||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import mozilla.components.browser.state.action.RecentlyClosedAction
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
|
@ -40,6 +43,8 @@ class HistoryControllerTest {
|
|||
|
||||
private val store: HistoryFragmentStore = mockk(relaxed = true)
|
||||
private val appStore: AppStore = mockk(relaxed = true)
|
||||
private val browserStore: BrowserStore = mockk(relaxed = true)
|
||||
private val historyStorage: PlacesHistoryStorage = mockk(relaxed = true)
|
||||
private val state: HistoryFragmentState = mockk(relaxed = true)
|
||||
private val navController: NavController = mockk(relaxed = true)
|
||||
private val historyProvider: DefaultPagedHistoryProvider = mockk(relaxed = true)
|
||||
|
@ -139,16 +144,29 @@ class HistoryControllerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun onDeleteAll() {
|
||||
var displayDeleteAllInvoked = false
|
||||
fun onDeleteTimeRange() {
|
||||
var displayDeleteTimeRangeInvoked = false
|
||||
val controller = createController(
|
||||
displayDeleteAll = {
|
||||
displayDeleteAllInvoked = true
|
||||
displayDeleteTimeRange = {
|
||||
displayDeleteTimeRangeInvoked = true
|
||||
}
|
||||
)
|
||||
|
||||
controller.handleDeleteAll()
|
||||
assertTrue(displayDeleteAllInvoked)
|
||||
controller.handleDeleteTimeRange()
|
||||
assertTrue(displayDeleteTimeRangeInvoked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onDeleteTimeRangeConfirmed() {
|
||||
val controller = createController()
|
||||
|
||||
controller.handleDeleteTimeRangeConfirmed(null)
|
||||
coVerifyOrder {
|
||||
store.dispatch(HistoryFragmentAction.EnterDeletionMode)
|
||||
historyStorage.deleteEverything()
|
||||
browserStore.dispatch(RecentlyClosedAction.RemoveAllClosedTabAction)
|
||||
store.dispatch(HistoryFragmentAction.ExitDeletionMode)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -185,7 +203,8 @@ class HistoryControllerTest {
|
|||
@Suppress("LongParameterList")
|
||||
private fun createController(
|
||||
openInBrowser: (History) -> Unit = { _ -> },
|
||||
displayDeleteAll: () -> Unit = {},
|
||||
displayDeleteTimeRange: () -> Unit = {},
|
||||
onTimeFrameDeleted: () -> Unit = {},
|
||||
invalidateOptionsMenu: () -> Unit = {},
|
||||
deleteHistoryItems: (Set<History>) -> Unit = { _ -> },
|
||||
syncHistory: suspend () -> Unit = {}
|
||||
|
@ -193,11 +212,14 @@ class HistoryControllerTest {
|
|||
return DefaultHistoryController(
|
||||
store,
|
||||
appStore,
|
||||
browserStore,
|
||||
historyStorage,
|
||||
historyProvider,
|
||||
navController,
|
||||
scope,
|
||||
openInBrowser,
|
||||
displayDeleteAll,
|
||||
displayDeleteTimeRange,
|
||||
onTimeFrameDeleted,
|
||||
invalidateOptionsMenu,
|
||||
{ items, _, _ -> deleteHistoryItems.invoke(items) },
|
||||
syncHistory,
|
||||
|
|
|
@ -75,11 +75,20 @@ class HistoryInteractorTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun onDeleteAll() {
|
||||
interactor.onDeleteAll()
|
||||
fun onDeleteTimeRange() {
|
||||
interactor.onDeleteTimeRange()
|
||||
|
||||
verifyAll {
|
||||
controller.handleDeleteAll()
|
||||
controller.handleDeleteTimeRange()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onDeleteTimeRangeConfirmed() {
|
||||
interactor.onDeleteTimeRangeConfirmed(null)
|
||||
|
||||
verifyAll {
|
||||
controller.handleDeleteTimeRangeConfirmed(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/* 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.library.history
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.util.Calendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class RemoveTimeFrameTest {
|
||||
|
||||
@Test
|
||||
fun `WHEN LastHour is calculated THEN first timeStamp is one hour ago`() {
|
||||
val lastHourRange = RemoveTimeFrame.LastHour.toLongRange()
|
||||
val nowMillis = Calendar.getInstance().timeInMillis
|
||||
val millisDif = nowMillis - lastHourRange.first
|
||||
val hourDif = TimeUnit.HOURS.convert(millisDif, TimeUnit.MILLISECONDS)
|
||||
Assert.assertEquals(1, hourDif)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN LastHour is calculated THEN second timeStamp is equal or greater than now`() {
|
||||
val lastHourRange = RemoveTimeFrame.LastHour.toLongRange()
|
||||
val nowMillis = Calendar.getInstance().timeInMillis
|
||||
Assert.assertTrue(nowMillis <= lastHourRange.last)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN TodayAndYesterday is calculated THEN first timeStamp is one day ago`() {
|
||||
val lastHourRange = RemoveTimeFrame.TodayAndYesterday.toLongRange()
|
||||
val nowMillis = Calendar.getInstance().timeInMillis
|
||||
val millisDif = nowMillis - lastHourRange.first
|
||||
val daysDif = TimeUnit.DAYS.convert(millisDif, TimeUnit.MILLISECONDS)
|
||||
Assert.assertEquals(1, daysDif)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN TodayAndYesterday is calculated THEN first timeStamp is the start of the previous day`() {
|
||||
val todayAndYesterdayRange = RemoveTimeFrame.TodayAndYesterday.toLongRange()
|
||||
val yesterdayStartMillis = Calendar.getInstance().apply {
|
||||
add(Calendar.DAY_OF_YEAR, -1)
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
Assert.assertEquals(yesterdayStartMillis, todayAndYesterdayRange.first)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN TodayAndYesterday is calculated THEN second timeStamp is equal or greater than now`() {
|
||||
val lastHourRange = RemoveTimeFrame.TodayAndYesterday.toLongRange()
|
||||
val nowMillis = Calendar.getInstance().timeInMillis
|
||||
Assert.assertTrue(nowMillis <= lastHourRange.last)
|
||||
}
|
||||
}
|
|
@ -23,11 +23,10 @@ import mozilla.components.support.test.rule.MainCoroutineRule
|
|||
import mozilla.components.support.test.rule.runTestOnMain
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -90,23 +89,7 @@ class HistoryMetadataGroupControllerTest {
|
|||
|
||||
@Before
|
||||
fun setUp() {
|
||||
controller = DefaultHistoryMetadataGroupController(
|
||||
historyStorage = historyStorage,
|
||||
browserStore = browserStore,
|
||||
appStore = appStore,
|
||||
store = store,
|
||||
selectOrAddUseCase = selectOrAddUseCase,
|
||||
navController = navController,
|
||||
searchTerm = "mozilla",
|
||||
deleteSnackbar = { items, _, delete ->
|
||||
scope.launch {
|
||||
delete(items).invoke(context)
|
||||
}
|
||||
},
|
||||
promptDeleteAll = { deleteAll -> deleteAll.invoke() },
|
||||
allDeletedSnackbar = {}
|
||||
)
|
||||
|
||||
controller = createController()
|
||||
every { activity.components.core.historyStorage } returns historyStorage
|
||||
every { context.components.core.store } returns browserStore
|
||||
every { context.components.core.historyStorage } returns historyStorage
|
||||
|
@ -189,7 +172,6 @@ class HistoryMetadataGroupControllerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Intermittent test: https://github.com/mozilla-mobile/fenix/issues/25167")
|
||||
fun handleDeleteSingle() = runTestOnMain {
|
||||
assertNull(GleanHistory.searchTermGroupRemoveTab.testGetValue())
|
||||
|
||||
|
@ -218,7 +200,6 @@ class HistoryMetadataGroupControllerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Intermittent test: https://github.com/mozilla-mobile/fenix/issues/25167")
|
||||
fun handleDeleteMultiple() = runTestOnMain {
|
||||
assertNull(GleanHistory.searchTermGroupRemoveTab.testGetValue())
|
||||
controller.handleDelete(getMetadataItemsList().toSet())
|
||||
|
@ -244,14 +225,16 @@ class HistoryMetadataGroupControllerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Intermittent test: https://github.com/mozilla-mobile/fenix/issues/25167")
|
||||
fun handleDeleteAbnormal() = runTestOnMain {
|
||||
val abnormalList = listOf(
|
||||
mozillaHistoryMetadataItem,
|
||||
firefoxHistoryMetadataItem,
|
||||
mozillaHistoryMetadataItem.copy(title = "Pocket", url = "https://getpocket.com"),
|
||||
mozillaHistoryMetadataItem.copy(title = "BBC", url = "https://www.bbc.com/"),
|
||||
mozillaHistoryMetadataItem.copy(title = "Stackoverflow", url = "https://stackoverflow.com/")
|
||||
mozillaHistoryMetadataItem.copy(
|
||||
title = "Stackoverflow",
|
||||
url = "https://stackoverflow.com/"
|
||||
)
|
||||
)
|
||||
assertNull(GleanHistory.searchTermGroupRemoveTab.testGetValue())
|
||||
|
||||
|
@ -290,9 +273,21 @@ class HistoryMetadataGroupControllerTest {
|
|||
|
||||
@Test
|
||||
fun handleDeleteAll() = runTestOnMain {
|
||||
var promptDeleteAllInvoked = false
|
||||
val controller = createController(
|
||||
promptDeleteAll = {
|
||||
promptDeleteAllInvoked = true
|
||||
}
|
||||
)
|
||||
controller.handleDeleteAll()
|
||||
assertTrue(promptDeleteAllInvoked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleDeleteAllConfirmed() = runTestOnMain {
|
||||
assertNull(GleanHistory.searchTermGroupRemoveAll.testGetValue())
|
||||
|
||||
controller.handleDeleteAll()
|
||||
controller.handleDeleteAllConfirmed()
|
||||
|
||||
coVerify {
|
||||
store.dispatch(HistoryMetadataGroupFragmentAction.DeleteAll)
|
||||
|
@ -313,4 +308,33 @@ class HistoryMetadataGroupControllerTest {
|
|||
.single().extra
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun createController(
|
||||
deleteSnackbar: (
|
||||
items: Set<History.Metadata>,
|
||||
undo: suspend (Set<History.Metadata>) -> Unit,
|
||||
delete: (Set<History.Metadata>) -> suspend (context: Context) -> Unit
|
||||
) -> Unit = { items, _, delete ->
|
||||
scope.launch {
|
||||
delete(items).invoke(context)
|
||||
}
|
||||
},
|
||||
promptDeleteAll: () -> Unit = {},
|
||||
allDeletedSnackbar: () -> Unit = {}
|
||||
): DefaultHistoryMetadataGroupController {
|
||||
return DefaultHistoryMetadataGroupController(
|
||||
historyStorage = historyStorage,
|
||||
browserStore = browserStore,
|
||||
appStore = appStore,
|
||||
store = store,
|
||||
selectOrAddUseCase = selectOrAddUseCase,
|
||||
navController = navController,
|
||||
scope = scope,
|
||||
searchTerm = searchTerm,
|
||||
deleteSnackbar = deleteSnackbar,
|
||||
promptDeleteAll = promptDeleteAll,
|
||||
allDeletedSnackbar = allDeletedSnackbar
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue