For #2486 - Adds Recently Closed Tabs
parent
cce58e7d51
commit
09fbb43f80
@ -0,0 +1,36 @@
|
||||
/* 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.recentlyclosed
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import mozilla.components.browser.state.state.ClosedTab
|
||||
|
||||
class RecentlyClosedAdapter(
|
||||
private val interactor: RecentlyClosedFragmentInteractor
|
||||
) : ListAdapter<ClosedTab, RecentlyClosedItemViewHolder>(DiffCallback) {
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): RecentlyClosedItemViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(RecentlyClosedItemViewHolder.LAYOUT_ID, parent, false)
|
||||
return RecentlyClosedItemViewHolder(view, interactor)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecentlyClosedItemViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
private object DiffCallback : DiffUtil.ItemCallback<ClosedTab>() {
|
||||
override fun areItemsTheSame(oldItem: ClosedTab, newItem: ClosedTab) =
|
||||
oldItem.id == newItem.id || oldItem.title == newItem.title || oldItem.url == newItem.url
|
||||
|
||||
override fun areContentsTheSame(oldItem: ClosedTab, newItem: ClosedTab) =
|
||||
oldItem.id == newItem.id || oldItem.title == newItem.title || oldItem.url == newItem.url
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/* 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.recentlyclosed
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.res.Resources
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavOptions
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import mozilla.components.browser.state.action.RecentlyClosedAction
|
||||
import mozilla.components.browser.state.state.ClosedTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.prompt.ShareData
|
||||
import mozilla.components.feature.recentlyclosed.ext.restoreTab
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
|
||||
interface RecentlyClosedController {
|
||||
fun handleOpen(item: ClosedTab, mode: BrowsingMode? = null)
|
||||
fun handleDeleteOne(tab: ClosedTab)
|
||||
fun handleCopyUrl(item: ClosedTab)
|
||||
fun handleShare(item: ClosedTab)
|
||||
fun handleNavigateToHistory()
|
||||
fun handleRestore(item: ClosedTab)
|
||||
}
|
||||
|
||||
class DefaultRecentlyClosedController(
|
||||
private val navController: NavController,
|
||||
private val store: BrowserStore,
|
||||
private val sessionManager: SessionManager,
|
||||
private val resources: Resources,
|
||||
private val snackbar: FenixSnackbar,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val activity: HomeActivity,
|
||||
private val openToBrowser: (item: ClosedTab, mode: BrowsingMode?) -> Unit
|
||||
) : RecentlyClosedController {
|
||||
override fun handleOpen(item: ClosedTab, mode: BrowsingMode?) {
|
||||
openToBrowser(item, mode)
|
||||
}
|
||||
|
||||
override fun handleDeleteOne(tab: ClosedTab) {
|
||||
store.dispatch(RecentlyClosedAction.RemoveClosedTabAction(tab))
|
||||
}
|
||||
|
||||
override fun handleNavigateToHistory() {
|
||||
navController.navigate(
|
||||
RecentlyClosedFragmentDirections.actionGlobalHistoryFragment(),
|
||||
NavOptions.Builder().setPopUpTo(R.id.historyFragment, true).build()
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleCopyUrl(item: ClosedTab) {
|
||||
val urlClipData = ClipData.newPlainText(item.url, item.url)
|
||||
clipboardManager.setPrimaryClip(urlClipData)
|
||||
with(snackbar) {
|
||||
setText(resources.getString(R.string.url_copied))
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleShare(item: ClosedTab) {
|
||||
navController.navigate(
|
||||
RecentlyClosedFragmentDirections.actionGlobalShareFragment(
|
||||
data = arrayOf(ShareData(url = item.url, title = item.title))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleRestore(item: ClosedTab) {
|
||||
item.restoreTab(
|
||||
store,
|
||||
sessionManager,
|
||||
onTabRestored = {
|
||||
activity.openToBrowser(
|
||||
from = BrowserDirection.FromRecentlyClosed
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/* 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.recentlyclosed
|
||||
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import kotlinx.android.synthetic.main.fragment_recently_closed_tabs.view.*
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.map
|
||||
import mozilla.components.browser.state.state.ClosedTab
|
||||
import mozilla.components.lib.state.ext.consumeFrom
|
||||
import mozilla.components.lib.state.ext.flowScoped
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
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.showToolbar
|
||||
import org.mozilla.fenix.library.LibraryPageFragment
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class RecentlyClosedFragment : LibraryPageFragment<ClosedTab>() {
|
||||
private lateinit var recentlyClosedFragmentStore: RecentlyClosedFragmentStore
|
||||
private var _recentlyClosedFragmentView: RecentlyClosedFragmentView? = null
|
||||
protected val recentlyClosedFragmentView: RecentlyClosedFragmentView
|
||||
get() = _recentlyClosedFragmentView!!
|
||||
|
||||
private lateinit var recentlyClosedInteractor: RecentlyClosedFragmentInteractor
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
showToolbar(getString(R.string.library_recently_closed_tabs))
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.library_menu, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
||||
R.id.close_history -> {
|
||||
close()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_recently_closed_tabs, container, false)
|
||||
recentlyClosedFragmentStore = StoreProvider.get(this) {
|
||||
RecentlyClosedFragmentStore(
|
||||
RecentlyClosedFragmentState(
|
||||
items = listOf()
|
||||
)
|
||||
)
|
||||
}
|
||||
recentlyClosedInteractor = RecentlyClosedFragmentInteractor(
|
||||
recentlyClosedController = DefaultRecentlyClosedController(
|
||||
navController = findNavController(),
|
||||
store = requireComponents.core.store,
|
||||
activity = activity as HomeActivity,
|
||||
sessionManager = requireComponents.core.sessionManager,
|
||||
resources = requireContext().resources,
|
||||
snackbar = FenixSnackbar.make(
|
||||
view = requireActivity().getRootView()!!,
|
||||
isDisplayedWithBrowserToolbar = true
|
||||
),
|
||||
clipboardManager = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager,
|
||||
openToBrowser = ::openItem
|
||||
)
|
||||
)
|
||||
_recentlyClosedFragmentView = RecentlyClosedFragmentView(
|
||||
view.recentlyClosedLayout,
|
||||
recentlyClosedInteractor
|
||||
)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_recentlyClosedFragmentView = null
|
||||
}
|
||||
|
||||
private fun openItem(tab: ClosedTab, mode: BrowsingMode? = null) {
|
||||
mode?.let { (activity as HomeActivity).browsingModeManager.mode = it }
|
||||
|
||||
(activity as HomeActivity).openToBrowserAndLoad(
|
||||
searchTermOrURL = tab.url,
|
||||
newTab = true,
|
||||
from = BrowserDirection.FromRecentlyClosed
|
||||
)
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
consumeFrom(recentlyClosedFragmentStore) {
|
||||
recentlyClosedFragmentView.update(it.items)
|
||||
}
|
||||
|
||||
requireComponents.core.store.flowScoped(viewLifecycleOwner) { flow ->
|
||||
flow.map { state -> state.closedTabs }
|
||||
.ifChanged()
|
||||
.collect { tabs ->
|
||||
recentlyClosedFragmentStore.dispatch(
|
||||
RecentlyClosedFragmentAction.Change(tabs)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val selectedItems: Set<ClosedTab> = setOf()
|
||||
}
|
@ -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.recentlyclosed
|
||||
|
||||
import mozilla.components.browser.state.state.ClosedTab
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
|
||||
/**
|
||||
* Interactor for the recently closed screen
|
||||
* Provides implementations for the RecentlyClosedInteractor
|
||||
*/
|
||||
class RecentlyClosedFragmentInteractor(
|
||||
private val recentlyClosedController: RecentlyClosedController
|
||||
) : RecentlyClosedInteractor {
|
||||
override fun restore(item: ClosedTab) {
|
||||
recentlyClosedController.handleRestore(item)
|
||||
}
|
||||
|
||||
override fun onCopyPressed(item: ClosedTab) {
|
||||
recentlyClosedController.handleCopyUrl(item)
|
||||
}
|
||||
|
||||
override fun onSharePressed(item: ClosedTab) {
|
||||
recentlyClosedController.handleShare(item)
|
||||
}
|
||||
|
||||
override fun onOpenInNormalTab(item: ClosedTab) {
|
||||
recentlyClosedController.handleOpen(item, BrowsingMode.Normal)
|
||||
}
|
||||
|
||||
override fun onOpenInPrivateTab(item: ClosedTab) {
|
||||
recentlyClosedController.handleOpen(item, BrowsingMode.Private)
|
||||
}
|
||||
|
||||
override fun onDeleteOne(tab: ClosedTab) {
|
||||
recentlyClosedController.handleDeleteOne(tab)
|
||||
}
|
||||
|
||||
override fun onNavigateToHistory() {
|
||||
recentlyClosedController.handleNavigateToHistory()
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* 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.recentlyclosed
|
||||
|
||||
import mozilla.components.browser.state.state.ClosedTab
|
||||
import mozilla.components.lib.state.Action
|
||||
import mozilla.components.lib.state.State
|
||||
import mozilla.components.lib.state.Store
|
||||
|
||||
/**
|
||||
* The [Store] for holding the [RecentlyClosedFragmentState] and applying [RecentlyClosedFragmentAction]s.
|
||||
*/
|
||||
class RecentlyClosedFragmentStore(initialState: RecentlyClosedFragmentState) :
|
||||
Store<RecentlyClosedFragmentState, RecentlyClosedFragmentAction>(
|
||||
initialState,
|
||||
::recentlyClosedStateReducer
|
||||
)
|
||||
|
||||
/**
|
||||
* Actions to dispatch through the `RecentlyClosedFragmentStore` to modify
|
||||
* `RecentlyClosedFragmentState` through the reducer.
|
||||
*/
|
||||
sealed class RecentlyClosedFragmentAction : Action {
|
||||
data class Change(val list: List<ClosedTab>) : RecentlyClosedFragmentAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* The state for the Recently Closed Screen
|
||||
* @property items List of recently closed tabs to display
|
||||
*/
|
||||
data class RecentlyClosedFragmentState(val items: List<ClosedTab> = emptyList()) : State
|
||||
|
||||
/**
|
||||
* The RecentlyClosedFragmentState Reducer.
|
||||
*/
|
||||
private fun recentlyClosedStateReducer(
|
||||
state: RecentlyClosedFragmentState,
|
||||
action: RecentlyClosedFragmentAction
|
||||
): RecentlyClosedFragmentState {
|
||||
return when (action) {
|
||||
is RecentlyClosedFragmentAction.Change -> state.copy(items = action.list)
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/* 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.recentlyclosed
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.component_recently_closed.*
|
||||
import mozilla.components.browser.state.state.ClosedTab
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
interface RecentlyClosedInteractor {
|
||||
/**
|
||||
* Called when an item is tapped to restore it.
|
||||
*
|
||||
* @param item the tapped item to restore.
|
||||
*/
|
||||
fun restore(item: ClosedTab)
|
||||
|
||||
/**
|
||||
* Called when the view more history option is tapped.
|
||||
*/
|
||||
fun onNavigateToHistory()
|
||||
|
||||
/**
|
||||
* Copies the URL of a recently closed tab item to the copy-paste buffer.
|
||||
*
|
||||
* @param item the recently closed tab item to copy the URL from
|
||||
*/
|
||||
fun onCopyPressed(item: ClosedTab)
|
||||
|
||||
/**
|
||||
* Opens the share sheet for a recently closed tab item.
|
||||
*
|
||||
* @param item the recently closed tab item to share
|
||||
*/
|
||||
fun onSharePressed(item: ClosedTab)
|
||||
|
||||
/**
|
||||
* Opens a recently closed tab item in a new tab.
|
||||
*
|
||||
* @param item the recently closed tab item to open in a new tab
|
||||
*/
|
||||
fun onOpenInNormalTab(item: ClosedTab)
|
||||
|
||||
/**
|
||||
* Opens a recently closed tab item in a private tab.
|
||||
*
|
||||
* @param item the recently closed tab item to open in a private tab
|
||||
*/
|
||||
fun onOpenInPrivateTab(item: ClosedTab)
|
||||
|
||||
/**
|
||||
* Deletes one recently closed tab item.
|
||||
*
|
||||
* @param item the recently closed tab item to delete.
|
||||
*/
|
||||
fun onDeleteOne(tab: ClosedTab)
|
||||
}
|
||||
|
||||
/**
|
||||
* View that contains and configures the Recently Closed List
|
||||
*/
|
||||
class RecentlyClosedFragmentView(
|
||||
container: ViewGroup,
|
||||
private val interactor: RecentlyClosedFragmentInteractor
|
||||
) : LayoutContainer {
|
||||
|
||||
override val containerView: ConstraintLayout = LayoutInflater.from(container.context)
|
||||
.inflate(R.layout.component_recently_closed, container, true)
|
||||
.findViewById(R.id.recently_closed_wrapper)
|
||||
|
||||
private val recentlyClosedAdapter: RecentlyClosedAdapter = RecentlyClosedAdapter(interactor)
|
||||
|
||||
init {
|
||||
recently_closed_list.apply {
|
||||
layoutManager = LinearLayoutManager(containerView.context)
|
||||
adapter = recentlyClosedAdapter
|
||||
}
|
||||
|
||||
view_more_history.apply {
|
||||
titleView.text =
|
||||
containerView.context.getString(R.string.recently_closed_show_full_history)
|
||||
urlView.isVisible = false
|
||||
overflowView.isVisible = false
|
||||
iconView.background = null
|
||||
iconView.setImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
containerView.context,
|
||||
R.drawable.ic_history
|
||||
)
|
||||
)
|
||||
setOnClickListener {
|
||||
interactor.onNavigateToHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun update(items: List<ClosedTab>) {
|
||||
recently_closed_empty_view.isVisible = items.isEmpty()
|
||||
recently_closed_list.isVisible = items.isNotEmpty()
|
||||
recentlyClosedAdapter.submitList(items)
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/* 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.recentlyclosed
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.history_list_item.view.*
|
||||
import mozilla.components.browser.state.state.ClosedTab
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.library.history.HistoryItemMenu
|
||||
import org.mozilla.fenix.utils.Do
|
||||
|
||||
class RecentlyClosedItemViewHolder(
|
||||
view: View,
|
||||
private val recentlyClosedFragmentInteractor: RecentlyClosedFragmentInteractor
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
private var item: ClosedTab? = null
|
||||
|
||||
init {
|
||||
setupMenu()
|
||||
}
|
||||
|
||||
fun bind(
|
||||
item: ClosedTab
|
||||
) {
|
||||
itemView.history_layout.titleView.text =
|
||||
if (item.title.isNotEmpty()) item.title else item.url
|
||||
itemView.history_layout.urlView.text = item.url
|
||||
|
||||
if (this.item?.url != item.url) {
|
||||
itemView.history_layout.loadFavicon(item.url)
|
||||
}
|
||||
|
||||
itemView.setOnClickListener {
|
||||
recentlyClosedFragmentInteractor.restore(item)
|
||||
}
|
||||
|
||||
this.item = item
|
||||
}
|
||||
|
||||
private fun setupMenu() {
|
||||
val historyMenu = HistoryItemMenu(itemView.context) {
|
||||
val item = this.item ?: return@HistoryItemMenu
|
||||
Do exhaustive when (it) {
|
||||
HistoryItemMenu.Item.Copy -> recentlyClosedFragmentInteractor.onCopyPressed(item)
|
||||
HistoryItemMenu.Item.Share -> recentlyClosedFragmentInteractor.onSharePressed(item)
|
||||
HistoryItemMenu.Item.OpenInNewTab -> recentlyClosedFragmentInteractor.onOpenInNormalTab(
|
||||
item
|
||||
)
|
||||
HistoryItemMenu.Item.OpenInPrivateTab -> recentlyClosedFragmentInteractor.onOpenInPrivateTab(
|
||||
item
|
||||
)
|
||||
HistoryItemMenu.Item.Delete -> recentlyClosedFragmentInteractor.onDeleteOne(
|
||||
item
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
itemView.history_layout.attachMenu(historyMenu.menuController)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.history_list_item
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/recently_closed_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.mozilla.fenix.library.LibrarySiteItemView
|
||||
android:id="@+id/view_more_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recently_closed_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/view_more_history"
|
||||
tools:listitem="@layout/history_list_item" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recently_closed_empty_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/recently_closed_empty_message"
|
||||
android:textColor="?secondaryText"
|
||||
android:textSize="16sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,9 @@
|
||||
<?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/. -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/recentlyClosedLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" />
|
@ -0,0 +1,171 @@
|
||||
/* 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.recentlyclosed
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.res.Resources
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavOptions
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.slot
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import mozilla.components.browser.state.action.RecentlyClosedAction
|
||||
import mozilla.components.browser.state.state.ClosedTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.prompt.ShareData
|
||||
import mozilla.components.feature.recentlyclosed.ext.restoreTab
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
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.optionsEq
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
// Robolectric needed for `onShareItem()`
|
||||
@ExperimentalCoroutinesApi
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class DefaultRecentlyClosedControllerTest {
|
||||
private val dispatcher = TestCoroutineDispatcher()
|
||||
private val navController: NavController = mockk(relaxed = true)
|
||||
private val resources: Resources = mockk(relaxed = true)
|
||||
private val snackbar: FenixSnackbar = mockk(relaxed = true)
|
||||
private val clipboardManager: ClipboardManager = mockk(relaxed = true)
|
||||
private val openToBrowser: (ClosedTab, BrowsingMode?) -> Unit = mockk(relaxed = true)
|
||||
private val sessionManager: SessionManager = mockk(relaxed = true)
|
||||
private val activity: HomeActivity = mockk(relaxed = true)
|
||||
private val store: BrowserStore = mockk(relaxed = true)
|
||||
val mockedTab: ClosedTab = mockk(relaxed = true)
|
||||
|
||||
private val controller = DefaultRecentlyClosedController(
|
||||
navController,
|
||||
store,
|
||||
sessionManager,
|
||||
resources,
|
||||
snackbar,
|
||||
clipboardManager,
|
||||
activity,
|
||||
openToBrowser
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockkStatic("mozilla.components.feature.recentlyclosed.ext.ClosedTabKt")
|
||||
every { mockedTab.restoreTab(any(), any(), any()) } just Runs
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
dispatcher.cleanupTestCoroutines()
|
||||
unmockkStatic("mozilla.components.feature.recentlyclosed.ext.ClosedTabKt")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleOpen() {
|
||||
val item: ClosedTab = mockk(relaxed = true)
|
||||
|
||||
controller.handleOpen(item, BrowsingMode.Private)
|
||||
|
||||
verify {
|
||||
openToBrowser(item, BrowsingMode.Private)
|
||||
}
|
||||
|
||||
controller.handleOpen(item, BrowsingMode.Normal)
|
||||
|
||||
verify {
|
||||
openToBrowser(item, BrowsingMode.Normal)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleDeleteOne() {
|
||||
val item: ClosedTab = mockk(relaxed = true)
|
||||
|
||||
controller.handleDeleteOne(item)
|
||||
|
||||
verify {
|
||||
store.dispatch(RecentlyClosedAction.RemoveClosedTabAction(item))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleNavigateToHistory() {
|
||||
controller.handleNavigateToHistory()
|
||||
|
||||
verify {
|
||||
navController.navigate(
|
||||
directionsEq(
|
||||
RecentlyClosedFragmentDirections.actionGlobalHistoryFragment()
|
||||
),
|
||||
optionsEq(NavOptions.Builder().setPopUpTo(R.id.historyFragment, true).build())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleCopyUrl() {
|
||||
val item = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L)
|
||||
|
||||
val clipdata = slot<ClipData>()
|
||||
|
||||
controller.handleCopyUrl(item)
|
||||
|
||||
verify {
|
||||
clipboardManager.setPrimaryClip(capture(clipdata))
|
||||
snackbar.show()
|
||||
}
|
||||
|
||||
assertEquals(1, clipdata.captured.itemCount)
|
||||
assertEquals("mozilla.org", clipdata.captured.description.label)
|
||||
assertEquals("mozilla.org", clipdata.captured.getItemAt(0).text)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun handleShare() {
|
||||
val item = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L)
|
||||
|
||||
controller.handleShare(item)
|
||||
|
||||
verify {
|
||||
navController.navigate(
|
||||
directionsEq(
|
||||
RecentlyClosedFragmentDirections.actionGlobalShareFragment(
|
||||
data = arrayOf(ShareData(url = item.url, title = item.title))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleRestore() {
|
||||
controller.handleRestore(mockedTab)
|
||||
|
||||
dispatcher.advanceUntilIdle()
|
||||
|
||||
verify {
|
||||
mockedTab.restoreTab(
|
||||
store,
|
||||
sessionManager,
|
||||
onTabRestored = any()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/* 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.recentlyclosed
|
||||
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import mozilla.components.browser.state.state.ClosedTab
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
|
||||
class RecentlyClosedFragmentInteractorTest {
|
||||
|
||||
lateinit var interactor: RecentlyClosedFragmentInteractor
|
||||
private val defaultRecentlyClosedController: DefaultRecentlyClosedController =
|
||||
mockk(relaxed = true)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
interactor =
|
||||
RecentlyClosedFragmentInteractor(
|
||||
recentlyClosedController = defaultRecentlyClosedController
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun open() {
|
||||
val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L)
|
||||
interactor.restore(tab)
|
||||
|
||||
verify {
|
||||
defaultRecentlyClosedController.handleRestore(tab)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onCopyPressed() {
|
||||
val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L)
|
||||
interactor.onCopyPressed(tab)
|
||||
|
||||
verify {
|
||||
defaultRecentlyClosedController.handleCopyUrl(tab)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onSharePressed() {
|
||||
val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L)
|
||||
interactor.onSharePressed(tab)
|
||||
|
||||
verify {
|
||||
defaultRecentlyClosedController.handleShare(tab)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onOpenInNormalTab() {
|
||||
val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L)
|
||||
interactor.onOpenInNormalTab(tab)
|
||||
|
||||
verify {
|
||||
defaultRecentlyClosedController.handleOpen(tab, mode = BrowsingMode.Normal)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onOpenInPrivateTab() {
|
||||
val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L)
|
||||
interactor.onOpenInPrivateTab(tab)
|
||||
|
||||
verify {
|
||||
defaultRecentlyClosedController.handleOpen(tab, mode = BrowsingMode.Private)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onDeleteOne() {
|
||||
val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L)
|
||||
interactor.onDeleteOne(tab)
|
||||
|
||||
verify {
|
||||
defaultRecentlyClosedController.handleDeleteOne(tab)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onNavigateToHistory() {
|
||||
interactor.onNavigateToHistory()
|
||||
|
||||
verify {
|
||||
defaultRecentlyClosedController.handleNavigateToHistory()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue