For #20893 - Search term groups in history
parent
391ff6b5fd
commit
2ae7d5d593
@ -1,68 +0,0 @@
|
|||||||
/* 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 android.content.Context
|
|
||||||
import androidx.annotation.VisibleForTesting
|
|
||||||
import mozilla.components.browser.menu2.BrowserMenuController
|
|
||||||
import mozilla.components.concept.menu.MenuController
|
|
||||||
import mozilla.components.concept.menu.candidate.TextMenuCandidate
|
|
||||||
import mozilla.components.concept.menu.candidate.TextStyle
|
|
||||||
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
|
||||||
import org.mozilla.fenix.R
|
|
||||||
|
|
||||||
class HistoryItemMenu(
|
|
||||||
private val context: Context,
|
|
||||||
private val onItemTapped: (Item) -> Unit
|
|
||||||
) {
|
|
||||||
|
|
||||||
enum class Item {
|
|
||||||
Copy,
|
|
||||||
Share,
|
|
||||||
OpenInNewTab,
|
|
||||||
OpenInPrivateTab,
|
|
||||||
Delete;
|
|
||||||
}
|
|
||||||
|
|
||||||
val menuController: MenuController by lazy {
|
|
||||||
BrowserMenuController().apply {
|
|
||||||
submitList(menuItems())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
internal fun menuItems(): List<TextMenuCandidate> {
|
|
||||||
return listOf(
|
|
||||||
TextMenuCandidate(
|
|
||||||
text = context.getString(R.string.history_menu_copy_button)
|
|
||||||
) {
|
|
||||||
onItemTapped.invoke(Item.Copy)
|
|
||||||
},
|
|
||||||
TextMenuCandidate(
|
|
||||||
text = context.getString(R.string.history_menu_share_button)
|
|
||||||
) {
|
|
||||||
onItemTapped.invoke(Item.Share)
|
|
||||||
},
|
|
||||||
TextMenuCandidate(
|
|
||||||
text = context.getString(R.string.history_menu_open_in_new_tab_button)
|
|
||||||
) {
|
|
||||||
onItemTapped.invoke(Item.OpenInNewTab)
|
|
||||||
},
|
|
||||||
TextMenuCandidate(
|
|
||||||
text = context.getString(R.string.history_menu_open_in_private_tab_button)
|
|
||||||
) {
|
|
||||||
onItemTapped.invoke(Item.OpenInPrivateTab)
|
|
||||||
},
|
|
||||||
TextMenuCandidate(
|
|
||||||
text = context.getString(R.string.history_delete_item),
|
|
||||||
textStyle = TextStyle(
|
|
||||||
color = context.getColorFromAttr(R.attr.destructive)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
onItemTapped.invoke(Item.Delete)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,168 @@
|
|||||||
|
/* 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.historymetadata
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.SpannableString
|
||||||
|
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 androidx.navigation.fragment.navArgs
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import mozilla.components.lib.state.ext.consumeFrom
|
||||||
|
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||||
|
import org.mozilla.fenix.components.StoreProvider
|
||||||
|
import org.mozilla.fenix.databinding.FragmentHistoryMetadataGroupBinding
|
||||||
|
import org.mozilla.fenix.ext.nav
|
||||||
|
import org.mozilla.fenix.ext.setTextColor
|
||||||
|
import org.mozilla.fenix.ext.showToolbar
|
||||||
|
import org.mozilla.fenix.library.LibraryPageFragment
|
||||||
|
import org.mozilla.fenix.library.history.History
|
||||||
|
import org.mozilla.fenix.library.historymetadata.controller.DefaultHistoryMetadataGroupController
|
||||||
|
import org.mozilla.fenix.library.historymetadata.interactor.DefaultHistoryMetadataGroupInteractor
|
||||||
|
import org.mozilla.fenix.library.historymetadata.interactor.HistoryMetadataGroupInteractor
|
||||||
|
import org.mozilla.fenix.library.historymetadata.view.HistoryMetadataGroupView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a list of history metadata items for a history metadata search group.
|
||||||
|
*/
|
||||||
|
class HistoryMetadataGroupFragment : LibraryPageFragment<History.Metadata>(), UserInteractionHandler {
|
||||||
|
|
||||||
|
private lateinit var historyMetadataGroupStore: HistoryMetadataGroupFragmentStore
|
||||||
|
private lateinit var interactor: HistoryMetadataGroupInteractor
|
||||||
|
|
||||||
|
private var _historyMetadataGroupView: HistoryMetadataGroupView? = null
|
||||||
|
private val historyMetadataGroupView: HistoryMetadataGroupView
|
||||||
|
get() = _historyMetadataGroupView!!
|
||||||
|
|
||||||
|
private val args by navArgs<HistoryMetadataGroupFragmentArgs>()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val binding = FragmentHistoryMetadataGroupBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
historyMetadataGroupStore = StoreProvider.get(this) {
|
||||||
|
HistoryMetadataGroupFragmentStore(
|
||||||
|
HistoryMetadataGroupFragmentState(
|
||||||
|
items = args.historyMetadataItems.filterIsInstance<History.Metadata>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interactor = DefaultHistoryMetadataGroupInteractor(
|
||||||
|
controller = DefaultHistoryMetadataGroupController(
|
||||||
|
activity = activity as HomeActivity,
|
||||||
|
store = historyMetadataGroupStore,
|
||||||
|
navController = findNavController()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
_historyMetadataGroupView = HistoryMetadataGroupView(
|
||||||
|
container = binding.historyMetadataGroupLayout,
|
||||||
|
interactor = interactor,
|
||||||
|
title = args.title
|
||||||
|
)
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
consumeFrom(historyMetadataGroupStore) { state ->
|
||||||
|
historyMetadataGroupView.update(state)
|
||||||
|
activity?.invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
showToolbar(args.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
if (selectedItems.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.history_menu, menu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
return when (item.itemId) {
|
||||||
|
R.id.share_history_multi_select -> {
|
||||||
|
interactor.onShareMenuItem(selectedItems)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.delete_history_multi_select -> {
|
||||||
|
interactor.onDeleteMenuItem(selectedItems)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.open_history_in_new_tabs_multi_select -> {
|
||||||
|
openItemsInNewTab { selectedItem ->
|
||||||
|
selectedItem.url
|
||||||
|
}
|
||||||
|
|
||||||
|
showTabTray()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.open_history_in_private_tabs_multi_select -> {
|
||||||
|
openItemsInNewTab(private = true) { selectedItem ->
|
||||||
|
selectedItem.url
|
||||||
|
}
|
||||||
|
|
||||||
|
(activity as HomeActivity).apply {
|
||||||
|
browsingModeManager.mode = BrowsingMode.Private
|
||||||
|
supportActionBar?.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
showTabTray()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.history_delete_all -> {
|
||||||
|
interactor.onDeleteAllMenuItem()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_historyMetadataGroupView = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override val selectedItems: Set<History.Metadata> get() =
|
||||||
|
historyMetadataGroupStore.state.items.filter { it.selected }.toSet()
|
||||||
|
|
||||||
|
override fun onBackPressed(): Boolean = interactor.onBackPressed(selectedItems)
|
||||||
|
|
||||||
|
private fun showTabTray() {
|
||||||
|
findNavController().nav(
|
||||||
|
R.id.historyMetadataGroupFragment,
|
||||||
|
HistoryMetadataGroupFragmentDirections.actionGlobalTabsTrayFragment()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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.historymetadata
|
||||||
|
|
||||||
|
import mozilla.components.lib.state.Action
|
||||||
|
import mozilla.components.lib.state.State
|
||||||
|
import mozilla.components.lib.state.Store
|
||||||
|
import org.mozilla.fenix.library.history.History
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Store] for holding the [HistoryMetadataGroupFragmentState] and applying
|
||||||
|
* [HistoryMetadataGroupFragmentAction]s.
|
||||||
|
*/
|
||||||
|
class HistoryMetadataGroupFragmentStore(initialState: HistoryMetadataGroupFragmentState) :
|
||||||
|
Store<HistoryMetadataGroupFragmentState, HistoryMetadataGroupFragmentAction>(
|
||||||
|
initialState,
|
||||||
|
::historyStateReducer
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to dispatch through the [HistoryMetadataGroupFragmentStore to modify the
|
||||||
|
* [HistoryMetadataGroupFragmentState] through the [historyStateReducer].
|
||||||
|
*/
|
||||||
|
sealed class HistoryMetadataGroupFragmentAction : Action {
|
||||||
|
data class UpdateHistoryItems(val items: List<History.Metadata>) :
|
||||||
|
HistoryMetadataGroupFragmentAction()
|
||||||
|
data class Select(val item: History.Metadata) : HistoryMetadataGroupFragmentAction()
|
||||||
|
data class Deselect(val item: History.Metadata) : HistoryMetadataGroupFragmentAction()
|
||||||
|
object DeselectAll : HistoryMetadataGroupFragmentAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state for [HistoryMetadataGroupFragment].
|
||||||
|
*
|
||||||
|
* @property items The list of [History.Metadata] to display.
|
||||||
|
*/
|
||||||
|
data class HistoryMetadataGroupFragmentState(
|
||||||
|
val items: List<History.Metadata> = emptyList()
|
||||||
|
) : State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces the history metadata state from the current state with the provided [action] to be
|
||||||
|
* performed.
|
||||||
|
*
|
||||||
|
* @param state The current history metadata state.
|
||||||
|
* @param action The action to be performed on the state.
|
||||||
|
* @return the new [HistoryMetadataGroupFragmentState] with the [action] executed.
|
||||||
|
*/
|
||||||
|
private fun historyStateReducer(
|
||||||
|
state: HistoryMetadataGroupFragmentState,
|
||||||
|
action: HistoryMetadataGroupFragmentAction
|
||||||
|
): HistoryMetadataGroupFragmentState {
|
||||||
|
return when (action) {
|
||||||
|
is HistoryMetadataGroupFragmentAction.UpdateHistoryItems ->
|
||||||
|
state.copy(items = action.items)
|
||||||
|
is HistoryMetadataGroupFragmentAction.Select ->
|
||||||
|
state.copy(
|
||||||
|
items = state.items.toMutableList()
|
||||||
|
.map {
|
||||||
|
if (it == action.item) {
|
||||||
|
it.copy(selected = true)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is HistoryMetadataGroupFragmentAction.Deselect ->
|
||||||
|
state.copy(
|
||||||
|
items = state.items.toMutableList()
|
||||||
|
.map {
|
||||||
|
if (it == action.item) {
|
||||||
|
it.copy(selected = false)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is HistoryMetadataGroupFragmentAction.DeselectAll ->
|
||||||
|
state.copy(
|
||||||
|
items = state.items.toMutableList()
|
||||||
|
.map { it.copy(selected = false) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
/* 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.historymetadata.controller
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import mozilla.components.concept.engine.prompt.ShareData
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.library.history.History
|
||||||
|
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentAction
|
||||||
|
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentDirections
|
||||||
|
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentStore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface that handles the view manipulation of the history metadata group in the History
|
||||||
|
* metadata group screen.
|
||||||
|
*/
|
||||||
|
interface HistoryMetadataGroupController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the given history [item] in a new tab.
|
||||||
|
*
|
||||||
|
* @param item The [History] to open in a new tab.
|
||||||
|
*/
|
||||||
|
fun handleOpen(item: History.Metadata)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the given history [item] to be selected in multi-select mode.
|
||||||
|
*
|
||||||
|
* @param item The [History] to select.
|
||||||
|
*/
|
||||||
|
fun handleSelect(item: History.Metadata)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the given history [item] to be deselected in multi-select mode.
|
||||||
|
*
|
||||||
|
* @param item The [History] to deselect.
|
||||||
|
*/
|
||||||
|
fun handleDeselect(item: History.Metadata)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on backpressed to deselect all the given [items].
|
||||||
|
*
|
||||||
|
* @param items The set of [History]s to deselect.
|
||||||
|
*/
|
||||||
|
fun handleBackPressed(items: Set<History.Metadata>): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the share sheet for a set of history [items].
|
||||||
|
*
|
||||||
|
* @param items The set of [History]s to share.
|
||||||
|
*/
|
||||||
|
fun handleShare(items: Set<History.Metadata>)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of [HistoryMetadataGroupController].
|
||||||
|
*/
|
||||||
|
class DefaultHistoryMetadataGroupController(
|
||||||
|
private val activity: HomeActivity,
|
||||||
|
private val store: HistoryMetadataGroupFragmentStore,
|
||||||
|
private val navController: NavController,
|
||||||
|
) : HistoryMetadataGroupController {
|
||||||
|
|
||||||
|
override fun handleOpen(item: History.Metadata) {
|
||||||
|
activity.openToBrowserAndLoad(
|
||||||
|
searchTermOrURL = item.url,
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromHistoryMetadataGroup
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleSelect(item: History.Metadata) {
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.Select(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleDeselect(item: History.Metadata) {
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.Deselect(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleBackPressed(items: Set<History.Metadata>): Boolean {
|
||||||
|
return if (items.isNotEmpty()) {
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.DeselectAll)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleShare(items: Set<History.Metadata>) {
|
||||||
|
navController.navigate(
|
||||||
|
HistoryMetadataGroupFragmentDirections.actionGlobalShareFragment(
|
||||||
|
data = items.map { ShareData(url = it.url, title = it.title) }.toTypedArray()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
/* 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.historymetadata.interactor
|
||||||
|
|
||||||
|
import org.mozilla.fenix.library.history.History
|
||||||
|
import org.mozilla.fenix.library.historymetadata.controller.HistoryMetadataGroupController
|
||||||
|
import org.mozilla.fenix.selection.SelectionInteractor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for history metadata group related actions in the History view.
|
||||||
|
*/
|
||||||
|
interface HistoryMetadataGroupInteractor : SelectionInteractor<History.Metadata> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on backpressed to deselect all the given [items].
|
||||||
|
*
|
||||||
|
* @param items The set of [History]s to deselect.
|
||||||
|
*/
|
||||||
|
fun onBackPressed(items: Set<History.Metadata>): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given set of history [items] that are selected. Called when a user clicks on the
|
||||||
|
* "Delete" menu item.
|
||||||
|
*
|
||||||
|
* @param items The set of [History]s to delete.
|
||||||
|
*/
|
||||||
|
fun onDeleteMenuItem(items: Set<History.Metadata>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the all the history items in the history metadata group. Called when a user clicks
|
||||||
|
* on the "Delete history" menu item.
|
||||||
|
*/
|
||||||
|
fun onDeleteAllMenuItem()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the share sheet for a set of history [items]. Called when a user clicks on the
|
||||||
|
* "Share" menu item.
|
||||||
|
*
|
||||||
|
* @param items The set of [History]s to share.
|
||||||
|
*/
|
||||||
|
fun onShareMenuItem(items: Set<History.Metadata>)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of [HistoryMetadataGroupInteractor].
|
||||||
|
*/
|
||||||
|
class DefaultHistoryMetadataGroupInteractor(
|
||||||
|
private val controller: HistoryMetadataGroupController
|
||||||
|
) : HistoryMetadataGroupInteractor {
|
||||||
|
|
||||||
|
override fun open(item: History.Metadata) {
|
||||||
|
controller.handleOpen(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun select(item: History.Metadata) {
|
||||||
|
controller.handleSelect(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deselect(item: History.Metadata) {
|
||||||
|
controller.handleDeselect(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(items: Set<History.Metadata>): Boolean {
|
||||||
|
return controller.handleBackPressed(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleteMenuItem(items: Set<History.Metadata>) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleteAllMenuItem() {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShareMenuItem(items: Set<History.Metadata>) {
|
||||||
|
controller.handleShare(items)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/* 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.historymetadata.view
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import org.mozilla.fenix.library.history.History
|
||||||
|
import org.mozilla.fenix.library.historymetadata.interactor.HistoryMetadataGroupInteractor
|
||||||
|
import org.mozilla.fenix.selection.SelectionHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter for a list of history metadata items to be displayed.
|
||||||
|
*/
|
||||||
|
class HistoryMetadataGroupAdapter(
|
||||||
|
private val interactor: HistoryMetadataGroupInteractor
|
||||||
|
) : ListAdapter<History.Metadata, HistoryMetadataGroupItemViewHolder>(DiffCallback),
|
||||||
|
SelectionHolder<History.Metadata> {
|
||||||
|
|
||||||
|
private var selectedHistoryItems: Set<History.Metadata> = emptySet()
|
||||||
|
|
||||||
|
override val selectedItems: Set<History.Metadata>
|
||||||
|
get() = selectedHistoryItems
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int
|
||||||
|
): HistoryMetadataGroupItemViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(HistoryMetadataGroupItemViewHolder.LAYOUT_ID, parent, false)
|
||||||
|
return HistoryMetadataGroupItemViewHolder(view, interactor, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: HistoryMetadataGroupItemViewHolder, position: Int) {
|
||||||
|
holder.bind(getItem(position))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateData(items: List<History.Metadata>) {
|
||||||
|
this.selectedHistoryItems = items.filter { it.selected }.toSet()
|
||||||
|
notifyItemRangeChanged(0, items.size)
|
||||||
|
submitList(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object DiffCallback : DiffUtil.ItemCallback<History.Metadata>() {
|
||||||
|
override fun areContentsTheSame(oldItem: History.Metadata, newItem: History.Metadata): Boolean =
|
||||||
|
oldItem.id == newItem.id
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItem: History.Metadata, newItem: History.Metadata): Boolean =
|
||||||
|
oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/* 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.historymetadata.view
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.databinding.HistoryMetadataGroupListItemBinding
|
||||||
|
import org.mozilla.fenix.ext.hideAndDisable
|
||||||
|
import org.mozilla.fenix.ext.showAndEnable
|
||||||
|
import org.mozilla.fenix.library.history.History
|
||||||
|
import org.mozilla.fenix.library.historymetadata.interactor.HistoryMetadataGroupInteractor
|
||||||
|
import org.mozilla.fenix.selection.SelectionHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View holder for a history metadata list item.
|
||||||
|
*/
|
||||||
|
class HistoryMetadataGroupItemViewHolder(
|
||||||
|
view: View,
|
||||||
|
private val interactor: HistoryMetadataGroupInteractor,
|
||||||
|
private val selectionHolder: SelectionHolder<History.Metadata>
|
||||||
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
|
||||||
|
private val binding = HistoryMetadataGroupListItemBinding.bind(view)
|
||||||
|
|
||||||
|
private var item: History.Metadata? = null
|
||||||
|
|
||||||
|
fun bind(item: History.Metadata) {
|
||||||
|
binding.historyLayout.titleView.text = item.title
|
||||||
|
binding.historyLayout.urlView.text = item.url
|
||||||
|
|
||||||
|
binding.historyLayout.setSelectionInteractor(item, selectionHolder, interactor)
|
||||||
|
binding.historyLayout.changeSelected(item in selectionHolder.selectedItems)
|
||||||
|
|
||||||
|
if (this.item?.url != item.url) {
|
||||||
|
binding.historyLayout.loadFavicon(item.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.historyLayout.overflowView.setImageResource(R.drawable.ic_close)
|
||||||
|
|
||||||
|
if (selectionHolder.selectedItems.isEmpty()) {
|
||||||
|
binding.historyLayout.overflowView.showAndEnable()
|
||||||
|
} else {
|
||||||
|
binding.historyLayout.overflowView.hideAndDisable()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.item = item
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.history_metadata_group_list_item
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/* 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.historymetadata.view
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.databinding.ComponentHistoryMetadataGroupBinding
|
||||||
|
import org.mozilla.fenix.library.LibraryPageView
|
||||||
|
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentState
|
||||||
|
import org.mozilla.fenix.library.historymetadata.interactor.HistoryMetadataGroupInteractor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a list of history metadata items.
|
||||||
|
*/
|
||||||
|
class HistoryMetadataGroupView(
|
||||||
|
container: ViewGroup,
|
||||||
|
val interactor: HistoryMetadataGroupInteractor,
|
||||||
|
val title: String
|
||||||
|
) : LibraryPageView(container) {
|
||||||
|
|
||||||
|
private val binding = ComponentHistoryMetadataGroupBinding.inflate(
|
||||||
|
LayoutInflater.from(container.context), container, true
|
||||||
|
)
|
||||||
|
|
||||||
|
private val historyMetadataGroupAdapter = HistoryMetadataGroupAdapter(interactor)
|
||||||
|
|
||||||
|
init {
|
||||||
|
binding.historyMetadataGroupList.apply {
|
||||||
|
layoutManager = LinearLayoutManager(containerView.context)
|
||||||
|
adapter = historyMetadataGroupAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the display of the history metadata items based on the given
|
||||||
|
* [HistoryMetadataGroupFragmentState].
|
||||||
|
*/
|
||||||
|
fun update(state: HistoryMetadataGroupFragmentState) {
|
||||||
|
binding.historyMetadataGroupList.isVisible = state.items.isNotEmpty()
|
||||||
|
binding.historyMetadataGroupEmptyView.isVisible = state.items.isEmpty()
|
||||||
|
|
||||||
|
historyMetadataGroupAdapter.updateData(state.items)
|
||||||
|
|
||||||
|
val selectedItems = state.items.filter { it.selected }
|
||||||
|
|
||||||
|
if (selectedItems.isEmpty()) {
|
||||||
|
setUiForNormalMode(title)
|
||||||
|
} else {
|
||||||
|
setUiForSelectingMode(
|
||||||
|
context.getString(R.string.history_multi_select_title, selectedItems.size)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
<?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:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/history_metadata_group_empty_view"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/history_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.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/history_metadata_group_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
tools:listitem="@layout/history_list_item" />
|
||||||
|
|
||||||
|
</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/historyMetadataGroupLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical" />
|
@ -0,0 +1,16 @@
|
|||||||
|
<?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:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<org.mozilla.fenix.library.LibrarySiteItemView
|
||||||
|
android:id="@+id/history_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="@dimen/library_item_height" />
|
||||||
|
</LinearLayout>
|
@ -1,76 +0,0 @@
|
|||||||
/* 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 android.content.Context
|
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
|
||||||
import mozilla.components.concept.menu.candidate.TextStyle
|
|
||||||
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
|
||||||
import mozilla.components.support.test.robolectric.testContext
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.mozilla.fenix.R
|
|
||||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
|
||||||
import org.mozilla.fenix.library.history.HistoryItemMenu.Item
|
|
||||||
|
|
||||||
@RunWith(FenixRobolectricTestRunner::class)
|
|
||||||
class HistoryItemMenuTest {
|
|
||||||
|
|
||||||
private lateinit var context: Context
|
|
||||||
private lateinit var menu: HistoryItemMenu
|
|
||||||
private var onItemTappedCaptured: Item? = null
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
context = ContextThemeWrapper(testContext, R.style.NormalTheme)
|
|
||||||
onItemTappedCaptured = null
|
|
||||||
menu = HistoryItemMenu(context) {
|
|
||||||
onItemTappedCaptured = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `delete item has special styling`() {
|
|
||||||
val deleteItem = menu.menuItems().last()
|
|
||||||
assertEquals("Delete", deleteItem.text)
|
|
||||||
assertEquals(
|
|
||||||
TextStyle(color = context.getColorFromAttr(R.attr.destructive)),
|
|
||||||
deleteItem.textStyle
|
|
||||||
)
|
|
||||||
|
|
||||||
deleteItem.onClick()
|
|
||||||
assertEquals(Item.Delete, onItemTappedCaptured)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `builds menu items`() {
|
|
||||||
val items = menu.menuItems()
|
|
||||||
assertEquals(5, items.size)
|
|
||||||
val (copy, share, openInNewTab, openInPrivateTab, delete) = items
|
|
||||||
|
|
||||||
assertEquals("Copy", copy.text)
|
|
||||||
assertEquals("Share", share.text)
|
|
||||||
assertEquals("Open in new tab", openInNewTab.text)
|
|
||||||
assertEquals("Open in private tab", openInPrivateTab.text)
|
|
||||||
assertEquals("Delete", delete.text)
|
|
||||||
|
|
||||||
copy.onClick()
|
|
||||||
assertEquals(Item.Copy, onItemTappedCaptured)
|
|
||||||
|
|
||||||
share.onClick()
|
|
||||||
assertEquals(Item.Share, onItemTappedCaptured)
|
|
||||||
|
|
||||||
openInNewTab.onClick()
|
|
||||||
assertEquals(Item.OpenInNewTab, onItemTappedCaptured)
|
|
||||||
|
|
||||||
openInPrivateTab.onClick()
|
|
||||||
assertEquals(Item.OpenInPrivateTab, onItemTappedCaptured)
|
|
||||||
|
|
||||||
delete.onClick()
|
|
||||||
assertEquals(Item.Delete, onItemTappedCaptured)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,82 @@
|
|||||||
|
/* 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.historymetadata
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.library.history.History
|
||||||
|
|
||||||
|
class HistoryMetadataGroupFragmentStoreTest {
|
||||||
|
|
||||||
|
private lateinit var state: HistoryMetadataGroupFragmentState
|
||||||
|
private lateinit var store: HistoryMetadataGroupFragmentStore
|
||||||
|
|
||||||
|
private val mozillaHistoryMetadataItem = History.Metadata(
|
||||||
|
id = 0,
|
||||||
|
title = "Mozilla",
|
||||||
|
url = "mozilla.org",
|
||||||
|
visitedAt = 0,
|
||||||
|
totalViewTime = 0
|
||||||
|
)
|
||||||
|
private val firefoxHistoryMetadataItem = History.Metadata(
|
||||||
|
id = 0,
|
||||||
|
title = "Firefox",
|
||||||
|
url = "firefox.com",
|
||||||
|
visitedAt = 0,
|
||||||
|
totalViewTime = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
state = HistoryMetadataGroupFragmentState()
|
||||||
|
store = HistoryMetadataGroupFragmentStore(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Test updating the items in HistoryMetadataGroupFragmentStore`() = runBlocking {
|
||||||
|
assertEquals(0, store.state.items.size)
|
||||||
|
|
||||||
|
val items = listOf(mozillaHistoryMetadataItem, firefoxHistoryMetadataItem)
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.UpdateHistoryItems(items)).join()
|
||||||
|
|
||||||
|
assertEquals(items, store.state.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Test selecting and deselecting an item in HistoryMetadataGroupFragmentStore`() = runBlocking {
|
||||||
|
val items = listOf(mozillaHistoryMetadataItem, firefoxHistoryMetadataItem)
|
||||||
|
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.UpdateHistoryItems(items)).join()
|
||||||
|
|
||||||
|
assertFalse(store.state.items[0].selected)
|
||||||
|
assertFalse(store.state.items[1].selected)
|
||||||
|
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.Select(mozillaHistoryMetadataItem)).join()
|
||||||
|
|
||||||
|
assertTrue(store.state.items[0].selected)
|
||||||
|
assertFalse(store.state.items[1].selected)
|
||||||
|
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.Deselect(store.state.items[0])).join()
|
||||||
|
|
||||||
|
assertFalse(store.state.items[0].selected)
|
||||||
|
assertFalse(store.state.items[1].selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Test deselecting all items in HistoryMetadataGroupFragmentStore`() = runBlocking {
|
||||||
|
val items = listOf(mozillaHistoryMetadataItem, firefoxHistoryMetadataItem)
|
||||||
|
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.UpdateHistoryItems(items)).join()
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.Select(mozillaHistoryMetadataItem)).join()
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.DeselectAll).join()
|
||||||
|
|
||||||
|
assertFalse(store.state.items[0].selected)
|
||||||
|
assertFalse(store.state.items[1].selected)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
/* 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.historymetadata.controller
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||||
|
import mozilla.components.concept.engine.prompt.ShareData
|
||||||
|
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.ext.directionsEq
|
||||||
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
import org.mozilla.fenix.library.history.History
|
||||||
|
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentAction
|
||||||
|
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentDirections
|
||||||
|
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentStore
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
|
class HistoryMetadataGroupControllerTest {
|
||||||
|
|
||||||
|
private val testDispatcher = TestCoroutineDispatcher()
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val coroutinesTestRule = MainCoroutineRule(testDispatcher)
|
||||||
|
|
||||||
|
private val activity: HomeActivity = mockk(relaxed = true)
|
||||||
|
private val store: HistoryMetadataGroupFragmentStore = mockk(relaxed = true)
|
||||||
|
private val navController: NavController = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private val mozillaHistoryMetadataItem = History.Metadata(
|
||||||
|
id = 0,
|
||||||
|
title = "Mozilla",
|
||||||
|
url = "mozilla.org",
|
||||||
|
visitedAt = 0,
|
||||||
|
totalViewTime = 1
|
||||||
|
)
|
||||||
|
private val firefoxHistoryMetadataItem = History.Metadata(
|
||||||
|
id = 0,
|
||||||
|
title = "Firefox",
|
||||||
|
url = "firefox.com",
|
||||||
|
visitedAt = 0,
|
||||||
|
totalViewTime = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
private lateinit var controller: DefaultHistoryMetadataGroupController
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
controller = DefaultHistoryMetadataGroupController(
|
||||||
|
activity = activity,
|
||||||
|
store = store,
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleOpen() {
|
||||||
|
controller.handleOpen(mozillaHistoryMetadataItem)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
activity.openToBrowserAndLoad(
|
||||||
|
searchTermOrURL = mozillaHistoryMetadataItem.url,
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromHistoryMetadataGroup,
|
||||||
|
historyMetadata = mozillaHistoryMetadataItem.historyMetadataKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleSelect() {
|
||||||
|
controller.handleSelect(mozillaHistoryMetadataItem)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.Select(mozillaHistoryMetadataItem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleDeselect() {
|
||||||
|
controller.handleDeselect(mozillaHistoryMetadataItem)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.Deselect(mozillaHistoryMetadataItem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleBackPressed() {
|
||||||
|
assertTrue(controller.handleBackPressed(setOf(mozillaHistoryMetadataItem)))
|
||||||
|
|
||||||
|
verify {
|
||||||
|
store.dispatch(HistoryMetadataGroupFragmentAction.DeselectAll)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFalse(controller.handleBackPressed(emptySet()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleShare() {
|
||||||
|
controller.handleShare(setOf(mozillaHistoryMetadataItem, firefoxHistoryMetadataItem))
|
||||||
|
|
||||||
|
val data = arrayOf(
|
||||||
|
ShareData(
|
||||||
|
title = mozillaHistoryMetadataItem.title,
|
||||||
|
url = mozillaHistoryMetadataItem.url
|
||||||
|
),
|
||||||
|
ShareData(
|
||||||
|
title = firefoxHistoryMetadataItem.title,
|
||||||
|
url = firefoxHistoryMetadataItem.url
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
navController.navigate(
|
||||||
|
directionsEq(HistoryMetadataGroupFragmentDirections.actionGlobalShareFragment(data))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
/* 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.historymetadata.view
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import androidx.navigation.Navigation
|
||||||
|
import io.mockk.mockk
|
||||||
|
import mozilla.components.support.test.robolectric.testContext
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.databinding.HistoryMetadataGroupListItemBinding
|
||||||
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
import org.mozilla.fenix.library.history.History
|
||||||
|
import org.mozilla.fenix.library.historymetadata.interactor.HistoryMetadataGroupInteractor
|
||||||
|
import org.mozilla.fenix.selection.SelectionHolder
|
||||||
|
|
||||||
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
|
class HistoryMetadataGroupItemViewHolderTest {
|
||||||
|
|
||||||
|
private lateinit var binding: HistoryMetadataGroupListItemBinding
|
||||||
|
private lateinit var interactor: HistoryMetadataGroupInteractor
|
||||||
|
private lateinit var selectionHolder: SelectionHolder<History.Metadata>
|
||||||
|
|
||||||
|
private val item = History.Metadata(
|
||||||
|
id = 0,
|
||||||
|
title = "Mozilla",
|
||||||
|
url = "mozilla.org",
|
||||||
|
visitedAt = 0,
|
||||||
|
totalViewTime = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
binding = HistoryMetadataGroupListItemBinding.inflate(LayoutInflater.from(testContext))
|
||||||
|
Navigation.setViewNavController(binding.root, mockk(relaxed = true))
|
||||||
|
interactor = mockk(relaxed = true)
|
||||||
|
selectionHolder = mockk(relaxed = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN a history metadata item on bind THEN set the title and url text`() {
|
||||||
|
HistoryMetadataGroupItemViewHolder(binding.root, interactor, selectionHolder).bind(item)
|
||||||
|
|
||||||
|
assertEquals(item.title, binding.historyLayout.titleView.text)
|
||||||
|
assertEquals(item.url, binding.historyLayout.urlView.text)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue