For #[15083]: Add multi select to recently closed tabs
This commit is contained in:
parent
7d5582a5bf
commit
e4fa71fde7
|
@ -9,28 +9,42 @@ import android.view.ViewGroup
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import mozilla.components.browser.state.state.recover.RecoverableTab
|
||||
import org.mozilla.fenix.selection.SelectionHolder
|
||||
|
||||
class RecentlyClosedAdapter(
|
||||
private val interactor: RecentlyClosedFragmentInteractor
|
||||
) : ListAdapter<RecoverableTab, RecentlyClosedItemViewHolder>(DiffCallback) {
|
||||
) : ListAdapter<RecoverableTab, RecentlyClosedItemViewHolder>(DiffCallback),
|
||||
SelectionHolder<RecoverableTab> {
|
||||
|
||||
private var selectedTabs: Set<RecoverableTab> = emptySet()
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): RecentlyClosedItemViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(RecentlyClosedItemViewHolder.LAYOUT_ID, parent, false)
|
||||
return RecentlyClosedItemViewHolder(view, interactor)
|
||||
return RecentlyClosedItemViewHolder(view, interactor, this)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecentlyClosedItemViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
override val selectedItems: Set<RecoverableTab>
|
||||
get() = selectedTabs
|
||||
|
||||
fun updateData(tabs: List<RecoverableTab>, selectedTabs: Set<RecoverableTab>) {
|
||||
this.selectedTabs = selectedTabs
|
||||
notifyItemRangeChanged(0, tabs.size)
|
||||
submitList(tabs)
|
||||
}
|
||||
|
||||
private object DiffCallback : DiffUtil.ItemCallback<RecoverableTab>() {
|
||||
override fun areItemsTheSame(oldItem: RecoverableTab, newItem: RecoverableTab) =
|
||||
oldItem.id == newItem.id
|
||||
|
||||
override fun areContentsTheSame(oldItem: RecoverableTab, newItem: RecoverableTab) =
|
||||
oldItem.id == newItem.id
|
||||
oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,18 +21,27 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
|||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface RecentlyClosedController {
|
||||
fun handleOpen(item: RecoverableTab, mode: BrowsingMode? = null)
|
||||
fun handleDeleteOne(tab: RecoverableTab)
|
||||
fun handleOpen(tab: RecoverableTab, mode: BrowsingMode? = null)
|
||||
fun handleOpen(tabs: Set<RecoverableTab>, mode: BrowsingMode? = null)
|
||||
fun handleDelete(tab: RecoverableTab)
|
||||
fun handleDelete(tabs: Set<RecoverableTab>)
|
||||
fun handleCopyUrl(item: RecoverableTab)
|
||||
fun handleShare(item: RecoverableTab)
|
||||
fun handleShare(tab: RecoverableTab)
|
||||
fun handleShare(tabs: Set<RecoverableTab>)
|
||||
fun handleNavigateToHistory()
|
||||
fun handleRestore(item: RecoverableTab)
|
||||
fun handleSelect(tab: RecoverableTab)
|
||||
fun handleDeselect(tab: RecoverableTab)
|
||||
fun handleBackPressed(): Boolean
|
||||
}
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class DefaultRecentlyClosedController(
|
||||
private val navController: NavController,
|
||||
private val store: BrowserStore,
|
||||
private val browserStore: BrowserStore,
|
||||
private val recentlyClosedStore: RecentlyClosedFragmentStore,
|
||||
private val tabsUseCases: TabsUseCases,
|
||||
private val resources: Resources,
|
||||
private val snackbar: FenixSnackbar,
|
||||
|
@ -40,12 +49,30 @@ class DefaultRecentlyClosedController(
|
|||
private val activity: HomeActivity,
|
||||
private val openToBrowser: (item: RecoverableTab, mode: BrowsingMode?) -> Unit
|
||||
) : RecentlyClosedController {
|
||||
override fun handleOpen(item: RecoverableTab, mode: BrowsingMode?) {
|
||||
openToBrowser(item, mode)
|
||||
override fun handleOpen(tab: RecoverableTab, mode: BrowsingMode?) {
|
||||
openToBrowser(tab, mode)
|
||||
}
|
||||
|
||||
override fun handleDeleteOne(tab: RecoverableTab) {
|
||||
store.dispatch(RecentlyClosedAction.RemoveClosedTabAction(tab))
|
||||
override fun handleOpen(tabs: Set<RecoverableTab>, mode: BrowsingMode?) {
|
||||
recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.DeselectAll)
|
||||
tabs.forEach { tab -> handleOpen(tab, mode) }
|
||||
}
|
||||
|
||||
override fun handleSelect(tab: RecoverableTab) {
|
||||
recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.Select(tab))
|
||||
}
|
||||
|
||||
override fun handleDeselect(tab: RecoverableTab) {
|
||||
recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.Deselect(tab))
|
||||
}
|
||||
|
||||
override fun handleDelete(tab: RecoverableTab) {
|
||||
browserStore.dispatch(RecentlyClosedAction.RemoveClosedTabAction(tab))
|
||||
}
|
||||
|
||||
override fun handleDelete(tabs: Set<RecoverableTab>) {
|
||||
recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.DeselectAll)
|
||||
tabs.forEach { tab -> handleDelete(tab) }
|
||||
}
|
||||
|
||||
override fun handleNavigateToHistory() {
|
||||
|
@ -64,10 +91,13 @@ class DefaultRecentlyClosedController(
|
|||
}
|
||||
}
|
||||
|
||||
override fun handleShare(item: RecoverableTab) {
|
||||
override fun handleShare(tab: RecoverableTab) = handleShare(setOf(tab))
|
||||
|
||||
override fun handleShare(tabs: Set<RecoverableTab>) {
|
||||
val shareData = tabs.map { ShareData(url = it.url, title = it.title) }
|
||||
navController.navigateBlockingForAsyncNavGraph(
|
||||
RecentlyClosedFragmentDirections.actionGlobalShareFragment(
|
||||
data = arrayOf(ShareData(url = item.url, title = item.title))
|
||||
data = shareData.toTypedArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -75,7 +105,7 @@ class DefaultRecentlyClosedController(
|
|||
override fun handleRestore(item: RecoverableTab) {
|
||||
tabsUseCases.restore(item)
|
||||
|
||||
store.dispatch(
|
||||
browserStore.dispatch(
|
||||
RecentlyClosedAction.RemoveClosedTabAction(item)
|
||||
)
|
||||
|
||||
|
@ -83,4 +113,13 @@ class DefaultRecentlyClosedController(
|
|||
from = BrowserDirection.FromRecentlyClosed
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleBackPressed(): Boolean {
|
||||
return if (recentlyClosedStore.state.selectedTabs.isNotEmpty()) {
|
||||
recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.DeselectAll)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.fenix.library.recentlyclosed
|
|||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableString
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
|
@ -21,6 +22,7 @@ import kotlinx.coroutines.flow.map
|
|||
import mozilla.components.browser.state.state.recover.RecoverableTab
|
||||
import mozilla.components.lib.state.ext.consumeFrom
|
||||
import mozilla.components.lib.state.ext.flowScoped
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
|
@ -30,17 +32,19 @@ import org.mozilla.fenix.components.FenixSnackbar
|
|||
import org.mozilla.fenix.components.StoreProvider
|
||||
import org.mozilla.fenix.ext.getRootView
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.setTextColor
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
import org.mozilla.fenix.library.LibraryPageFragment
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class RecentlyClosedFragment : LibraryPageFragment<RecoverableTab>() {
|
||||
class RecentlyClosedFragment : LibraryPageFragment<RecoverableTab>(), UserInteractionHandler {
|
||||
private lateinit var recentlyClosedFragmentStore: RecentlyClosedFragmentStore
|
||||
private var _recentlyClosedFragmentView: RecentlyClosedFragmentView? = null
|
||||
protected val recentlyClosedFragmentView: RecentlyClosedFragmentView
|
||||
get() = _recentlyClosedFragmentView!!
|
||||
|
||||
private lateinit var recentlyClosedInteractor: RecentlyClosedFragmentInteractor
|
||||
private lateinit var recentlyClosedController: RecentlyClosedController
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
@ -48,15 +52,43 @@ class RecentlyClosedFragment : LibraryPageFragment<RecoverableTab>() {
|
|||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.library_menu, menu)
|
||||
if (recentlyClosedFragmentStore.state.selectedTabs.isNotEmpty()) {
|
||||
inflater.inflate(R.menu.history_select_multi, menu)
|
||||
menu.findItem(R.id.delete_history_multi_select)?.let { deleteItem ->
|
||||
deleteItem.title = SpannableString(deleteItem.title)
|
||||
.apply { setTextColor(requireContext(), R.attr.destructive) }
|
||||
}
|
||||
} else {
|
||||
inflater.inflate(R.menu.library_menu, menu)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
||||
R.id.close_history -> {
|
||||
close()
|
||||
true
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val selectedTabs = recentlyClosedFragmentStore.state.selectedTabs
|
||||
|
||||
return when (item.itemId) {
|
||||
R.id.close_history -> {
|
||||
close()
|
||||
true
|
||||
}
|
||||
R.id.share_history_multi_select -> {
|
||||
recentlyClosedController.handleShare(selectedTabs)
|
||||
true
|
||||
}
|
||||
R.id.delete_history_multi_select -> {
|
||||
recentlyClosedController.handleDelete(selectedTabs)
|
||||
true
|
||||
}
|
||||
R.id.open_history_in_new_tabs_multi_select -> {
|
||||
recentlyClosedController.handleOpen(selectedTabs, BrowsingMode.Normal)
|
||||
true
|
||||
}
|
||||
R.id.open_history_in_private_tabs_multi_select -> {
|
||||
recentlyClosedController.handleOpen(selectedTabs, BrowsingMode.Private)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -73,25 +105,26 @@ class RecentlyClosedFragment : LibraryPageFragment<RecoverableTab>() {
|
|||
recentlyClosedFragmentStore = StoreProvider.get(this) {
|
||||
RecentlyClosedFragmentStore(
|
||||
RecentlyClosedFragmentState(
|
||||
items = listOf()
|
||||
items = listOf(),
|
||||
selectedTabs = emptySet()
|
||||
)
|
||||
)
|
||||
}
|
||||
recentlyClosedInteractor = RecentlyClosedFragmentInteractor(
|
||||
recentlyClosedController = DefaultRecentlyClosedController(
|
||||
navController = findNavController(),
|
||||
store = requireComponents.core.store,
|
||||
activity = activity as HomeActivity,
|
||||
tabsUseCases = requireComponents.useCases.tabsUseCases,
|
||||
resources = requireContext().resources,
|
||||
snackbar = FenixSnackbar.make(
|
||||
view = requireActivity().getRootView()!!,
|
||||
isDisplayedWithBrowserToolbar = true
|
||||
),
|
||||
clipboardManager = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager,
|
||||
openToBrowser = ::openItem
|
||||
)
|
||||
recentlyClosedController = DefaultRecentlyClosedController(
|
||||
navController = findNavController(),
|
||||
browserStore = requireComponents.core.store,
|
||||
recentlyClosedStore = recentlyClosedFragmentStore,
|
||||
activity = activity as HomeActivity,
|
||||
tabsUseCases = requireComponents.useCases.tabsUseCases,
|
||||
resources = requireContext().resources,
|
||||
snackbar = FenixSnackbar.make(
|
||||
view = requireActivity().getRootView()!!,
|
||||
isDisplayedWithBrowserToolbar = true
|
||||
),
|
||||
clipboardManager = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager,
|
||||
openToBrowser = ::openItem
|
||||
)
|
||||
recentlyClosedInteractor = RecentlyClosedFragmentInteractor(recentlyClosedController)
|
||||
_recentlyClosedFragmentView = RecentlyClosedFragmentView(
|
||||
view.recentlyClosedLayout,
|
||||
recentlyClosedInteractor
|
||||
|
@ -116,8 +149,9 @@ class RecentlyClosedFragment : LibraryPageFragment<RecoverableTab>() {
|
|||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
consumeFrom(recentlyClosedFragmentStore) {
|
||||
recentlyClosedFragmentView.update(it.items)
|
||||
consumeFrom(recentlyClosedFragmentStore) { state ->
|
||||
recentlyClosedFragmentView.update(state)
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
requireComponents.core.store.flowScoped(viewLifecycleOwner) { flow ->
|
||||
|
@ -132,4 +166,8 @@ class RecentlyClosedFragment : LibraryPageFragment<RecoverableTab>() {
|
|||
}
|
||||
|
||||
override val selectedItems: Set<RecoverableTab> = setOf()
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
return recentlyClosedController.handleBackPressed()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,11 +34,23 @@ class RecentlyClosedFragmentInteractor(
|
|||
recentlyClosedController.handleOpen(item, BrowsingMode.Private)
|
||||
}
|
||||
|
||||
override fun onDeleteOne(tab: RecoverableTab) {
|
||||
recentlyClosedController.handleDeleteOne(tab)
|
||||
override fun onDelete(tab: RecoverableTab) {
|
||||
recentlyClosedController.handleDelete(tab)
|
||||
}
|
||||
|
||||
override fun onNavigateToHistory() {
|
||||
recentlyClosedController.handleNavigateToHistory()
|
||||
}
|
||||
|
||||
override fun open(item: RecoverableTab) {
|
||||
recentlyClosedController.handleRestore(item)
|
||||
}
|
||||
|
||||
override fun select(item: RecoverableTab) {
|
||||
recentlyClosedController.handleSelect(item)
|
||||
}
|
||||
|
||||
override fun deselect(item: RecoverableTab) {
|
||||
recentlyClosedController.handleDeselect(item)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,13 +24,19 @@ class RecentlyClosedFragmentStore(initialState: RecentlyClosedFragmentState) :
|
|||
*/
|
||||
sealed class RecentlyClosedFragmentAction : Action {
|
||||
data class Change(val list: List<RecoverableTab>) : RecentlyClosedFragmentAction()
|
||||
data class Select(val tab: RecoverableTab) : RecentlyClosedFragmentAction()
|
||||
data class Deselect(val tab: RecoverableTab) : RecentlyClosedFragmentAction()
|
||||
object DeselectAll : RecentlyClosedFragmentAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* The state for the Recently Closed Screen
|
||||
* @property items List of recently closed tabs to display
|
||||
*/
|
||||
data class RecentlyClosedFragmentState(val items: List<RecoverableTab> = emptyList()) : State
|
||||
data class RecentlyClosedFragmentState(
|
||||
val items: List<RecoverableTab> = emptyList(),
|
||||
val selectedTabs: Set<RecoverableTab>
|
||||
) : State
|
||||
|
||||
/**
|
||||
* The RecentlyClosedFragmentState Reducer.
|
||||
|
@ -41,5 +47,12 @@ private fun recentlyClosedStateReducer(
|
|||
): RecentlyClosedFragmentState {
|
||||
return when (action) {
|
||||
is RecentlyClosedFragmentAction.Change -> state.copy(items = action.list)
|
||||
is RecentlyClosedFragmentAction.Select -> {
|
||||
state.copy(selectedTabs = state.selectedTabs + action.tab)
|
||||
}
|
||||
is RecentlyClosedFragmentAction.Deselect -> {
|
||||
state.copy(selectedTabs = state.selectedTabs - action.tab)
|
||||
}
|
||||
RecentlyClosedFragmentAction.DeselectAll -> state.copy(selectedTabs = emptySet())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,17 +5,19 @@
|
|||
package org.mozilla.fenix.library.recentlyclosed
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import kotlinx.android.synthetic.main.component_recently_closed.*
|
||||
import mozilla.components.browser.state.state.recover.RecoverableTab
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.library.LibraryPageView
|
||||
import org.mozilla.fenix.selection.SelectionInteractor
|
||||
|
||||
interface RecentlyClosedInteractor {
|
||||
interface RecentlyClosedInteractor : SelectionInteractor<RecoverableTab> {
|
||||
/**
|
||||
* Called when an item is tapped to restore it.
|
||||
*
|
||||
|
@ -57,11 +59,11 @@ interface RecentlyClosedInteractor {
|
|||
fun onOpenInPrivateTab(item: RecoverableTab)
|
||||
|
||||
/**
|
||||
* Deletes one recently closed tab item.
|
||||
* Called when recently closed tab is selected for deletion.
|
||||
*
|
||||
* @param tab the recently closed tab item to delete.
|
||||
* @param tab the recently closed tab to delete.
|
||||
*/
|
||||
fun onDeleteOne(tab: RecoverableTab)
|
||||
fun onDelete(tab: RecoverableTab)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,11 +72,10 @@ interface RecentlyClosedInteractor {
|
|||
class RecentlyClosedFragmentView(
|
||||
container: ViewGroup,
|
||||
private val interactor: RecentlyClosedFragmentInteractor
|
||||
) : LayoutContainer {
|
||||
) : LibraryPageView(container) {
|
||||
|
||||
override val containerView: ConstraintLayout = LayoutInflater.from(container.context)
|
||||
val view: View = LayoutInflater.from(container.context)
|
||||
.inflate(R.layout.component_recently_closed, container, true)
|
||||
.findViewById(R.id.recently_closed_wrapper)
|
||||
|
||||
private val recentlyClosedAdapter: RecentlyClosedAdapter = RecentlyClosedAdapter(interactor)
|
||||
|
||||
|
@ -82,6 +83,7 @@ class RecentlyClosedFragmentView(
|
|||
recently_closed_list.apply {
|
||||
layoutManager = LinearLayoutManager(containerView.context)
|
||||
adapter = recentlyClosedAdapter
|
||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
}
|
||||
|
||||
view_more_history.apply {
|
||||
|
@ -102,9 +104,20 @@ class RecentlyClosedFragmentView(
|
|||
}
|
||||
}
|
||||
|
||||
fun update(items: List<RecoverableTab>) {
|
||||
recently_closed_empty_view.isVisible = items.isEmpty()
|
||||
recently_closed_list.isVisible = items.isNotEmpty()
|
||||
recentlyClosedAdapter.submitList(items)
|
||||
fun update(state: RecentlyClosedFragmentState) {
|
||||
state.apply {
|
||||
recently_closed_empty_view.isVisible = items.isEmpty()
|
||||
recently_closed_list.isVisible = items.isNotEmpty()
|
||||
|
||||
recentlyClosedAdapter.updateData(items, selectedTabs)
|
||||
|
||||
if (selectedTabs.isEmpty()) {
|
||||
setUiForNormalMode(context.getString(R.string.library_recently_closed_tabs))
|
||||
} else {
|
||||
setUiForSelectingMode(
|
||||
context.getString(R.string.history_multi_select_title, selectedTabs.size)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,14 +7,19 @@ package org.mozilla.fenix.library.recentlyclosed
|
|||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.history_list_item.view.*
|
||||
import kotlinx.android.synthetic.main.library_site_item.view.*
|
||||
import mozilla.components.browser.state.state.recover.RecoverableTab
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.hideAndDisable
|
||||
import org.mozilla.fenix.ext.showAndEnable
|
||||
import org.mozilla.fenix.selection.SelectionHolder
|
||||
import org.mozilla.fenix.library.history.HistoryItemMenu
|
||||
import org.mozilla.fenix.utils.Do
|
||||
|
||||
class RecentlyClosedItemViewHolder(
|
||||
view: View,
|
||||
private val recentlyClosedFragmentInteractor: RecentlyClosedFragmentInteractor
|
||||
private val recentlyClosedFragmentInteractor: RecentlyClosedFragmentInteractor,
|
||||
private val selectionHolder: SelectionHolder<RecoverableTab>
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
private var item: RecoverableTab? = null
|
||||
|
@ -30,12 +35,17 @@ class RecentlyClosedItemViewHolder(
|
|||
if (item.title.isNotEmpty()) item.title else item.url
|
||||
itemView.history_layout.urlView.text = item.url
|
||||
|
||||
itemView.history_layout.setSelectionInteractor(item, selectionHolder, recentlyClosedFragmentInteractor)
|
||||
itemView.history_layout.changeSelected(item in selectionHolder.selectedItems)
|
||||
|
||||
if (this.item?.url != item.url) {
|
||||
itemView.history_layout.loadFavicon(item.url)
|
||||
}
|
||||
|
||||
itemView.setOnClickListener {
|
||||
recentlyClosedFragmentInteractor.restore(item)
|
||||
if (selectionHolder.selectedItems.isEmpty()) {
|
||||
itemView.overflow_menu.showAndEnable()
|
||||
} else {
|
||||
itemView.overflow_menu.hideAndDisable()
|
||||
}
|
||||
|
||||
this.item = item
|
||||
|
@ -53,9 +63,7 @@ class RecentlyClosedItemViewHolder(
|
|||
HistoryItemMenu.Item.OpenInPrivateTab -> recentlyClosedFragmentInteractor.onOpenInPrivateTab(
|
||||
item
|
||||
)
|
||||
HistoryItemMenu.Item.Delete -> recentlyClosedFragmentInteractor.onDeleteOne(
|
||||
item
|
||||
)
|
||||
HistoryItemMenu.Item.Delete -> recentlyClosedFragmentInteractor.onDelete(item)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recently_closed_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/view_more_history"
|
||||
tools:listitem="@layout/history_list_item" />
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.mozilla.fenix.R
|
|||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.ext.directionsEq
|
||||
import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
|
||||
import org.mozilla.fenix.ext.optionsEq
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
|
@ -46,13 +47,15 @@ class DefaultRecentlyClosedControllerTest {
|
|||
private val clipboardManager: ClipboardManager = mockk(relaxed = true)
|
||||
private val openToBrowser: (RecoverableTab, BrowsingMode?) -> Unit = mockk(relaxed = true)
|
||||
private val activity: HomeActivity = mockk(relaxed = true)
|
||||
private val store: BrowserStore = mockk(relaxed = true)
|
||||
private val browserStore: BrowserStore = mockk(relaxed = true)
|
||||
private val recentlyClosedStore: RecentlyClosedFragmentStore = mockk(relaxed = true)
|
||||
private val tabsUseCases: TabsUseCases = mockk(relaxed = true)
|
||||
val mockedTab: RecoverableTab = mockk(relaxed = true)
|
||||
|
||||
private val controller = DefaultRecentlyClosedController(
|
||||
navController,
|
||||
store,
|
||||
browserStore,
|
||||
recentlyClosedStore,
|
||||
tabsUseCases,
|
||||
resources,
|
||||
snackbar,
|
||||
|
@ -89,13 +92,62 @@ class DefaultRecentlyClosedControllerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun handleDeleteOne() {
|
||||
val item: RecoverableTab = mockk(relaxed = true)
|
||||
fun `open multiple tabs`() {
|
||||
val tabs = createFakeTabList(2)
|
||||
|
||||
controller.handleDeleteOne(item)
|
||||
controller.handleOpen(tabs.toSet(), BrowsingMode.Normal)
|
||||
|
||||
verify {
|
||||
store.dispatch(RecentlyClosedAction.RemoveClosedTabAction(item))
|
||||
openToBrowser(tabs[0], BrowsingMode.Normal)
|
||||
openToBrowser(tabs[1], BrowsingMode.Normal)
|
||||
}
|
||||
|
||||
controller.handleOpen(tabs.toSet(), BrowsingMode.Private)
|
||||
|
||||
verify {
|
||||
openToBrowser(tabs[0], BrowsingMode.Private)
|
||||
openToBrowser(tabs[1], BrowsingMode.Private)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handle select tab`() {
|
||||
val selectedTab = createFakeTab()
|
||||
|
||||
controller.handleSelect(selectedTab)
|
||||
|
||||
verify { recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.Select(selectedTab)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handle deselect tab`() {
|
||||
val deselectedTab = createFakeTab()
|
||||
|
||||
controller.handleDeselect(deselectedTab)
|
||||
|
||||
verify { recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.Deselect(deselectedTab)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleDelete() {
|
||||
val item: RecoverableTab = mockk(relaxed = true)
|
||||
|
||||
controller.handleDelete(item)
|
||||
|
||||
verify {
|
||||
browserStore.dispatch(RecentlyClosedAction.RemoveClosedTabAction(item))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete multiple tabs`() {
|
||||
val tabs = createFakeTabList(2)
|
||||
|
||||
controller.handleDelete(tabs.toSet())
|
||||
|
||||
verify {
|
||||
browserStore.dispatch(RecentlyClosedAction.RemoveClosedTabAction(tabs[0]))
|
||||
browserStore.dispatch(RecentlyClosedAction.RemoveClosedTabAction(tabs[1]))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +156,7 @@ class DefaultRecentlyClosedControllerTest {
|
|||
controller.handleNavigateToHistory()
|
||||
|
||||
verify {
|
||||
navController.navigate(
|
||||
navController.navigateBlockingForAsyncNavGraph(
|
||||
directionsEq(
|
||||
RecentlyClosedFragmentDirections.actionGlobalHistoryFragment()
|
||||
),
|
||||
|
@ -139,7 +191,7 @@ class DefaultRecentlyClosedControllerTest {
|
|||
controller.handleShare(item)
|
||||
|
||||
verify {
|
||||
navController.navigate(
|
||||
navController.navigateBlockingForAsyncNavGraph(
|
||||
directionsEq(
|
||||
RecentlyClosedFragmentDirections.actionGlobalShareFragment(
|
||||
data = arrayOf(ShareData(url = item.url, title = item.title))
|
||||
|
@ -149,6 +201,23 @@ class DefaultRecentlyClosedControllerTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `share multiple tabs`() {
|
||||
val tabs = createFakeTabList(2)
|
||||
|
||||
controller.handleShare(tabs.toSet())
|
||||
|
||||
verify {
|
||||
val data = arrayOf(
|
||||
ShareData(title = tabs[0].title, url = tabs[0].url),
|
||||
ShareData(title = tabs[1].title, url = tabs[1].url)
|
||||
)
|
||||
navController.navigateBlockingForAsyncNavGraph(
|
||||
directionsEq(RecentlyClosedFragmentDirections.actionGlobalShareFragment(data))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleRestore() {
|
||||
controller.handleRestore(mockedTab)
|
||||
|
@ -157,4 +226,25 @@ class DefaultRecentlyClosedControllerTest {
|
|||
|
||||
verify { tabsUseCases.restore.invoke(mockedTab, true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exist multi-select mode when back is pressed`() {
|
||||
every { recentlyClosedStore.state.selectedTabs } returns createFakeTabList(3).toSet()
|
||||
|
||||
controller.handleBackPressed()
|
||||
|
||||
verify { recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.DeselectAll) }
|
||||
}
|
||||
|
||||
private fun createFakeTab(id: String = "FakeId", url: String = "www.fake.com"): RecoverableTab =
|
||||
RecoverableTab(id, url)
|
||||
|
||||
private fun createFakeTabList(size: Int): List<RecoverableTab> {
|
||||
val fakeTabs = mutableListOf<RecoverableTab>()
|
||||
for (i in 0 until size) {
|
||||
fakeTabs.add(createFakeTab(id = "FakeId$i"))
|
||||
}
|
||||
|
||||
return fakeTabs
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,12 +76,12 @@ class RecentlyClosedFragmentInteractorTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun onDeleteOne() {
|
||||
fun onDelete() {
|
||||
val tab = RecoverableTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", lastAccess = 1L)
|
||||
interactor.onDeleteOne(tab)
|
||||
interactor.onDelete(tab)
|
||||
|
||||
verify {
|
||||
defaultRecentlyClosedController.handleDeleteOne(tab)
|
||||
defaultRecentlyClosedController.handleDelete(tab)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user