parent
005f53965f
commit
e1cdeffe8c
|
@ -67,7 +67,7 @@ sealed class BookmarkAction : Action {
|
|||
data class Deselect(val item: BookmarkNode) : BookmarkAction()
|
||||
data class Delete(val item: BookmarkNode) : BookmarkAction()
|
||||
object BackPressed : BookmarkAction()
|
||||
object ModeChanged : BookmarkAction()
|
||||
object SwitchMode : BookmarkAction()
|
||||
}
|
||||
|
||||
sealed class BookmarkChange : Change {
|
||||
|
|
|
@ -231,7 +231,7 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
|
|||
refreshBookmarks(components)
|
||||
}
|
||||
}
|
||||
is BookmarkAction.ModeChanged -> activity?.invalidateOptionsMenu()
|
||||
is BookmarkAction.SwitchMode -> activity?.invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ class BookmarkUIView(
|
|||
}
|
||||
if (it.mode != mode) {
|
||||
mode = it.mode
|
||||
actionEmitter.onNext(BookmarkAction.ModeChanged)
|
||||
actionEmitter.onNext(BookmarkAction.SwitchMode)
|
||||
}
|
||||
bookmarkAdapter.updateData(it.tree, it.mode)
|
||||
when (val modeCopy = mode) {
|
||||
|
@ -78,7 +78,7 @@ class BookmarkUIView(
|
|||
mode = BookmarkState.Mode.Normal
|
||||
bookmarkAdapter.updateData(tree, mode)
|
||||
setUIForNormalMode(tree)
|
||||
actionEmitter.onNext(BookmarkAction.ModeChanged)
|
||||
actionEmitter.onNext(BookmarkAction.SwitchMode)
|
||||
true
|
||||
}
|
||||
canGoBack -> {
|
||||
|
@ -113,7 +113,7 @@ class BookmarkUIView(
|
|||
context.getString(R.string.bookmarks_multi_select_title, mode.selectedItems.size)
|
||||
setToolbarColors(
|
||||
R.color.white_color,
|
||||
R.attr.accentBright.getColorIntFromAttr(context!!)
|
||||
R.attr.accentHighContrast.getColorIntFromAttr(context!!)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -98,10 +98,13 @@ class HistoryAdapter(
|
|||
private var historyList: HistoryList = HistoryList(emptyList())
|
||||
private var mode: HistoryState.Mode = HistoryState.Mode.Normal
|
||||
private lateinit var job: Job
|
||||
var selected = listOf<HistoryItem>()
|
||||
|
||||
fun updateData(items: List<HistoryItem>, mode: HistoryState.Mode) {
|
||||
this.historyList = HistoryList(items)
|
||||
this.mode = mode
|
||||
this.selected = if (mode is HistoryState.Mode.Editing) mode.selectedItems else listOf()
|
||||
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ class HistoryComponent(
|
|||
bus.getManagedEmitter(HistoryAction::class.java),
|
||||
bus.getSafeManagedObservable(HistoryChange::class.java)
|
||||
) {
|
||||
|
||||
override fun initView() = HistoryUIView(container, actionEmitter, changesObservable)
|
||||
|
||||
override fun render(): Observable<HistoryState> =
|
||||
|
@ -51,11 +52,12 @@ data class HistoryState(val items: List<HistoryItem>, val mode: Mode) : ViewStat
|
|||
}
|
||||
|
||||
sealed class HistoryAction : Action {
|
||||
data class Select(val item: HistoryItem) : HistoryAction()
|
||||
data class Open(val item: HistoryItem) : HistoryAction()
|
||||
data class EnterEditMode(val item: HistoryItem) : HistoryAction()
|
||||
object BackPressed : HistoryAction()
|
||||
data class AddItemForRemoval(val item: HistoryItem) : HistoryAction()
|
||||
data class RemoveItemForRemoval(val item: HistoryItem) : HistoryAction()
|
||||
object SwitchMode : HistoryAction()
|
||||
|
||||
sealed class Delete : HistoryAction() {
|
||||
object All : Delete()
|
||||
|
@ -99,10 +101,13 @@ class HistoryViewModel(initialState: HistoryState, changesObservable: Observable
|
|||
}
|
||||
}
|
||||
is HistoryChange.RemoveItemForRemoval -> {
|
||||
val mode = state.mode
|
||||
var mode = state.mode
|
||||
|
||||
if (mode is HistoryState.Mode.Editing) {
|
||||
val items = mode.selectedItems.filter { it.id != change.item.id }
|
||||
state.copy(mode = mode.copy(selectedItems = items))
|
||||
mode = if (items.isEmpty()) HistoryState.Mode.Normal else HistoryState.Mode.Editing(items)
|
||||
|
||||
state.copy(mode = mode)
|
||||
} else {
|
||||
state
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package org.mozilla.fenix.library.history
|
||||
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
|
@ -13,23 +15,30 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.Navigation
|
||||
import kotlinx.android.synthetic.main.fragment_history.view.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import mozilla.components.concept.storage.VisitType
|
||||
import mozilla.components.support.base.feature.BackHandler
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.BrowsingModeManager
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.Components
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.share
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||
import org.mozilla.fenix.mvi.getManagedEmitter
|
||||
import org.mozilla.fenix.utils.ItsNotBrokenSnack
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
@ -39,6 +48,7 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
|
|||
|
||||
private lateinit var job: Job
|
||||
private lateinit var historyComponent: HistoryComponent
|
||||
private val navigation by lazy { Navigation.findNavController(requireView()) }
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.Main + job
|
||||
|
@ -66,7 +76,7 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
|
|||
(activity as AppCompatActivity).supportActionBar?.show()
|
||||
}
|
||||
|
||||
private fun selectItem(item: HistoryItem) {
|
||||
private fun openItem(item: HistoryItem) {
|
||||
(activity as HomeActivity).openToBrowserAndLoad(
|
||||
searchTermOrURL = item.url,
|
||||
newTab = false,
|
||||
|
@ -79,7 +89,19 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
|
|||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.library_menu, menu)
|
||||
when (val mode = (historyComponent.uiView as HistoryUIView).mode) {
|
||||
HistoryState.Mode.Normal -> inflater.inflate(R.menu.library_menu, menu)
|
||||
is HistoryState.Mode.Editing -> {
|
||||
inflater.inflate(R.menu.history_select_multi, menu)
|
||||
menu.findItem(R.id.share_history_multi_select)?.run {
|
||||
isVisible = mode.selectedItems.isNotEmpty()
|
||||
icon.colorFilter = PorterDuffColorFilter(
|
||||
ContextCompat.getColor(context!!, R.color.white_color),
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -96,7 +118,7 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
|
|||
getAutoDisposeObservable<HistoryAction>()
|
||||
.subscribe {
|
||||
when (it) {
|
||||
is HistoryAction.Select -> selectItem(it.item)
|
||||
is HistoryAction.Open -> openItem(it.item)
|
||||
is HistoryAction.EnterEditMode -> getManagedEmitter<HistoryChange>()
|
||||
.onNext(HistoryChange.EnterEditMode(it.item))
|
||||
is HistoryAction.AddItemForRemoval -> getManagedEmitter<HistoryChange>()
|
||||
|
@ -119,17 +141,59 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
|
|||
}
|
||||
reloadData()
|
||||
}
|
||||
is HistoryAction.SwitchMode -> activity?.invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.share_history_multi_select -> {
|
||||
val selectedHistory = (historyComponent.uiView as HistoryUIView).getSelected()
|
||||
when {
|
||||
selectedHistory.size == 1 -> context?.share(selectedHistory.first().url)
|
||||
selectedHistory.size > 1 -> ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "2377")
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.libraryClose -> {
|
||||
Navigation.findNavController(requireActivity(), R.id.container)
|
||||
.popBackStack(R.id.libraryFragment, true)
|
||||
true
|
||||
}
|
||||
R.id.delete_history_multi_select -> {
|
||||
val components = context?.applicationContext?.components!!
|
||||
val selectedHistory = (historyComponent.uiView as HistoryUIView).getSelected()
|
||||
|
||||
CoroutineScope(Main).launch {
|
||||
deleteSelectedHistory(selectedHistory, components)
|
||||
reloadData()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.open_history_in_new_tabs_multi_select -> {
|
||||
val selectedHistory = (historyComponent.uiView as HistoryUIView).getSelected()
|
||||
selectedHistory.forEach {
|
||||
requireComponents.useCases.tabsUseCases.addTab.invoke(it.url)
|
||||
}
|
||||
|
||||
(activity as HomeActivity).browsingModeManager.mode = BrowsingModeManager.Mode.Normal
|
||||
(activity as HomeActivity).supportActionBar?.hide()
|
||||
navigation.navigate(HistoryFragmentDirections.actionHistoryFragmentToHomeFragment())
|
||||
true
|
||||
}
|
||||
R.id.open_history_in_private_tabs_multi_select -> {
|
||||
val selectedHistory = (historyComponent.uiView as HistoryUIView).getSelected()
|
||||
selectedHistory.forEach {
|
||||
requireComponents.useCases.tabsUseCases.addPrivateTab.invoke(it.url)
|
||||
}
|
||||
|
||||
(activity as HomeActivity).browsingModeManager.mode = BrowsingModeManager.Mode.Private
|
||||
(activity as HomeActivity).supportActionBar?.hide()
|
||||
navigation.navigate(HistoryFragmentDirections.actionHistoryFragmentToHomeFragment())
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
@ -171,4 +235,13 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun deleteSelectedHistory(
|
||||
selected: List<HistoryItem>,
|
||||
components: Components = requireComponents
|
||||
) {
|
||||
selected.forEach {
|
||||
components.core.historyStorage.deleteVisit(it.url, it.visitedAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,15 @@
|
|||
|
||||
package org.mozilla.fenix.library.history
|
||||
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Observer
|
||||
|
@ -14,6 +20,8 @@ import io.reactivex.functions.Consumer
|
|||
import kotlinx.android.synthetic.main.component_history.view.*
|
||||
import mozilla.components.support.base.feature.BackHandler
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.asActivity
|
||||
import org.mozilla.fenix.ext.getColorIntFromAttr
|
||||
import org.mozilla.fenix.mvi.UIView
|
||||
|
||||
class HistoryUIView(
|
||||
|
@ -27,28 +35,103 @@ class HistoryUIView(
|
|||
var mode: HistoryState.Mode = HistoryState.Mode.Normal
|
||||
private set
|
||||
|
||||
private val historyAdapter: HistoryAdapter
|
||||
private var items: List<HistoryItem> = listOf()
|
||||
private val context = container.context
|
||||
private val activity = context?.asActivity()
|
||||
|
||||
fun getSelected(): List<HistoryItem> = historyAdapter.selected
|
||||
|
||||
override val view: LinearLayout = LayoutInflater.from(container.context)
|
||||
.inflate(R.layout.component_history, container, true)
|
||||
.findViewById(R.id.history_wrapper)
|
||||
|
||||
init {
|
||||
view.history_list.apply {
|
||||
adapter = HistoryAdapter(actionEmitter)
|
||||
historyAdapter = HistoryAdapter(actionEmitter)
|
||||
adapter = historyAdapter
|
||||
layoutManager = LinearLayoutManager(container.context)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateView() = Consumer<HistoryState> {
|
||||
mode = it.mode
|
||||
if (it.mode != mode) {
|
||||
mode = it.mode
|
||||
actionEmitter.onNext(HistoryAction.SwitchMode)
|
||||
}
|
||||
(view.history_list.adapter as HistoryAdapter).updateData(it.items, it.mode)
|
||||
|
||||
items = it.items
|
||||
when (val modeCopy = mode) {
|
||||
is HistoryState.Mode.Normal -> setUIForNormalMode()
|
||||
is HistoryState.Mode.Editing -> setUIForSelectingMode(modeCopy)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUIForSelectingMode(
|
||||
mode: HistoryState.Mode.Editing
|
||||
) {
|
||||
(activity as? AppCompatActivity)?.title =
|
||||
context.getString(R.string.history_multi_select_title, mode.selectedItems.size)
|
||||
setToolbarColors(
|
||||
R.color.white_color,
|
||||
R.attr.accentHighContrast.getColorIntFromAttr(context!!)
|
||||
)
|
||||
}
|
||||
|
||||
private fun setUIForNormalMode() {
|
||||
(activity as? AppCompatActivity)?.title = context.getString(R.string.library_history)
|
||||
setToolbarColors(
|
||||
R.attr.primaryText.getColorIntFromAttr(context!!),
|
||||
R.attr.foundation.getColorIntFromAttr(context)
|
||||
)
|
||||
}
|
||||
|
||||
private fun setToolbarColors(foreground: Int, background: Int) {
|
||||
val toolbar = (activity as AppCompatActivity).findViewById<Toolbar>(R.id.navigationToolbar)
|
||||
val colorFilter = PorterDuffColorFilter(
|
||||
ContextCompat.getColor(context, foreground), PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
toolbar.setBackgroundColor(ContextCompat.getColor(context, background))
|
||||
toolbar.setTitleTextColor(ContextCompat.getColor(context, foreground))
|
||||
|
||||
themeToolbar(
|
||||
toolbar, foreground,
|
||||
background, colorFilter
|
||||
)
|
||||
}
|
||||
|
||||
private fun themeToolbar(
|
||||
toolbar: androidx.appcompat.widget.Toolbar,
|
||||
textColor: Int,
|
||||
backgroundColor: Int,
|
||||
colorFilter: PorterDuffColorFilter? = null
|
||||
) {
|
||||
toolbar.setTitleTextColor(ContextCompat.getColor(context!!, textColor))
|
||||
toolbar.setBackgroundColor(ContextCompat.getColor(context, backgroundColor))
|
||||
|
||||
if (colorFilter == null) {
|
||||
return
|
||||
}
|
||||
|
||||
toolbar.overflowIcon?.colorFilter = colorFilter
|
||||
(0 until toolbar.childCount).forEach {
|
||||
when (val item = toolbar.getChildAt(it)) {
|
||||
is ImageButton -> item.drawable.colorFilter = colorFilter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
if (mode is HistoryState.Mode.Editing) {
|
||||
actionEmitter.onNext(HistoryAction.BackPressed)
|
||||
return true
|
||||
return when {
|
||||
mode is HistoryState.Mode.Editing -> {
|
||||
mode = HistoryState.Mode.Normal
|
||||
historyAdapter.updateData(items, mode)
|
||||
setUIForNormalMode()
|
||||
actionEmitter.onNext(HistoryAction.SwitchMode)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,20 +35,19 @@ class HistoryDeleteButtonViewHolder(
|
|||
fun bind(mode: HistoryState.Mode) {
|
||||
this.mode = mode
|
||||
|
||||
val text = if (mode is HistoryState.Mode.Editing && mode.selectedItems.isNotEmpty()) {
|
||||
textView.context.resources.getString(
|
||||
R.string.history_delete_some,
|
||||
mode.selectedItems.size
|
||||
)
|
||||
} else {
|
||||
textView.context.resources.getString(R.string.history_delete_all)
|
||||
buttonView.run {
|
||||
if (mode is HistoryState.Mode.Editing && mode.selectedItems.isNotEmpty()) {
|
||||
isEnabled = false
|
||||
alpha = DISABLED_ALPHA
|
||||
} else {
|
||||
isEnabled = true
|
||||
alpha = 1f
|
||||
}
|
||||
}
|
||||
|
||||
buttonView.contentDescription = text
|
||||
textView.text = text
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DISABLED_ALPHA = 0.4f
|
||||
const val LAYOUT_ID = R.layout.delete_history_button
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package org.mozilla.fenix.library.history.viewholders
|
|||
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.Observer
|
||||
import kotlinx.android.synthetic.main.history_list_item.view.*
|
||||
|
@ -15,6 +16,7 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.icons.IconRequest
|
||||
import mozilla.components.browser.menu.BrowserMenu
|
||||
import org.mozilla.fenix.DefaultThemeManager
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.library.history.HistoryAction
|
||||
|
@ -32,7 +34,6 @@ class HistoryListItemViewHolder(
|
|||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.IO + job
|
||||
|
||||
private val checkbox = view.should_remove_checkbox
|
||||
private val favicon = view.history_favicon
|
||||
private val title = view.history_title
|
||||
private val url = view.history_url
|
||||
|
@ -60,17 +61,6 @@ class HistoryListItemViewHolder(
|
|||
init {
|
||||
setupMenu()
|
||||
|
||||
view.setOnClickListener {
|
||||
if (mode is HistoryState.Mode.Editing) {
|
||||
checkbox.isChecked = !checkbox.isChecked
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
item?.apply {
|
||||
actionEmitter.onNext(HistoryAction.Select(this))
|
||||
}
|
||||
}
|
||||
|
||||
view.setOnLongClickListener {
|
||||
item?.apply {
|
||||
actionEmitter.onNext(HistoryAction.EnterEditMode(this))
|
||||
|
@ -84,8 +74,6 @@ class HistoryListItemViewHolder(
|
|||
anchor = it,
|
||||
orientation = BrowserMenu.Orientation.DOWN)
|
||||
}
|
||||
|
||||
checkbox.setOnCheckedChangeListener(checkListener)
|
||||
}
|
||||
|
||||
fun bind(item: HistoryItem, mode: HistoryState.Mode) {
|
||||
|
@ -95,23 +83,31 @@ class HistoryListItemViewHolder(
|
|||
title.text = item.title
|
||||
url.text = item.url
|
||||
|
||||
val isEditing = mode is HistoryState.Mode.Editing
|
||||
checkbox.visibility = if (isEditing) View.VISIBLE else View.GONE
|
||||
favicon.visibility = if (isEditing) View.INVISIBLE else View.VISIBLE
|
||||
|
||||
if (mode is HistoryState.Mode.Editing) {
|
||||
checkbox.setOnCheckedChangeListener(null)
|
||||
|
||||
// Don't set the checkbox if it already contains the right value.
|
||||
// This prevent us from cutting off the animation
|
||||
val shouldCheck = mode.selectedItems.contains(item)
|
||||
if (checkbox.isChecked != shouldCheck) {
|
||||
checkbox.isChecked = shouldCheck
|
||||
}
|
||||
checkbox.setOnCheckedChangeListener(checkListener)
|
||||
val selected = when (mode) {
|
||||
is HistoryState.Mode.Editing -> mode.selectedItems.contains(item)
|
||||
HistoryState.Mode.Normal -> false
|
||||
}
|
||||
|
||||
updateFavIcon(item.url)
|
||||
setClickListeners(item, selected)
|
||||
|
||||
if (mode is HistoryState.Mode.Editing) {
|
||||
val backgroundTint =
|
||||
if (selected) {
|
||||
DefaultThemeManager.resolveAttribute(R.attr.accentHighContrast, itemView.context)
|
||||
} else {
|
||||
DefaultThemeManager.resolveAttribute(R.attr.neutral, itemView.context)
|
||||
}
|
||||
val backgroundTintList = ContextCompat.getColorStateList(itemView.context, backgroundTint)
|
||||
favicon.backgroundTintList = backgroundTintList
|
||||
|
||||
if (selected) {
|
||||
favicon.setImageResource(R.drawable.mozac_ic_check)
|
||||
} else {
|
||||
favicon.setImageResource(0)
|
||||
}
|
||||
} else {
|
||||
updateFavIcon(item.url)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMenu() {
|
||||
|
@ -134,6 +130,21 @@ class HistoryListItemViewHolder(
|
|||
}
|
||||
}
|
||||
|
||||
private fun setClickListeners(
|
||||
item: HistoryItem,
|
||||
selected: Boolean
|
||||
) {
|
||||
itemView.history_layout.setOnClickListener {
|
||||
if (mode == HistoryState.Mode.Normal) {
|
||||
actionEmitter.onNext(HistoryAction.Open(item))
|
||||
} else {
|
||||
if (selected) actionEmitter.onNext(HistoryAction.RemoveItemForRemoval(item)) else actionEmitter.onNext(
|
||||
HistoryAction.AddItemForRemoval(item)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.history_list_item
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="4dp"/>
|
||||
<solid android:color="@color/foundation_normal_theme" />
|
||||
<solid android:color="?inset" />
|
||||
</shape>
|
|
@ -16,14 +16,18 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/delete_history_button_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/history_delete_all"
|
||||
android:textColor="?destructive"
|
||||
android:drawablePadding="8dp"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center"
|
||||
android:textColor="?primaryText"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:layout_gravity="center" />
|
||||
android:drawableTint="?primaryText"
|
||||
android:drawableStart="@drawable/ic_delete"
|
||||
android:drawablePadding="12dp"/>
|
||||
|
||||
</FrameLayout>
|
|
@ -4,6 +4,7 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/history_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
|
@ -12,15 +13,6 @@
|
|||
android:padding="4dp"
|
||||
android:paddingStart="20dp">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/should_remove_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/history_item_overflow"
|
||||
android:layout_width="@dimen/glyph_button_width"
|
||||
|
|
36
app/src/main/res/menu/history_select_multi.xml
Normal file
36
app/src/main/res/menu/history_select_multi.xml
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?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/. -->
|
||||
<menu 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">
|
||||
<item
|
||||
android:id="@+id/share_history_multi_select"
|
||||
android:icon="@drawable/ic_hollow_share"
|
||||
android:iconTint="?primaryText"
|
||||
android:title="@string/browser_menu_share"
|
||||
app:showAsAction="ifRoom"
|
||||
tools:targetApi="o" />
|
||||
<item
|
||||
android:id="@+id/open_history_in_new_tabs_multi_select"
|
||||
android:icon="@drawable/ic_new"
|
||||
android:iconTint="?primaryText"
|
||||
android:title="@string/bookmark_menu_open_in_new_tab_button"
|
||||
app:showAsAction="never"
|
||||
tools:targetApi="o" />
|
||||
<item
|
||||
android:id="@+id/open_history_in_private_tabs_multi_select"
|
||||
android:icon="@drawable/ic_new"
|
||||
android:iconTint="?primaryText"
|
||||
android:title="@string/bookmark_menu_open_in_private_tab_button"
|
||||
app:showAsAction="never"
|
||||
tools:targetApi="o" />
|
||||
<item
|
||||
android:id="@+id/delete_history_multi_select"
|
||||
android:icon="@drawable/ic_new"
|
||||
android:iconTint="?primaryText"
|
||||
android:title="@string/bookmark_menu_delete_button"
|
||||
app:showAsAction="never"
|
||||
tools:targetApi="o" />
|
||||
</menu>
|
|
@ -137,6 +137,7 @@
|
|||
android:label="@string/library_history"
|
||||
tools:layout="@layout/fragment_history" >
|
||||
<action android:id="@+id/action_historyFragment_to_browserFragment" app:destination="@id/browserFragment"/>
|
||||
<action android:id="@+id/action_historyFragment_to_homeFragment" app:destination="@id/homeFragment"/>
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
<string name="content_description_disable_private_browsing_button">Disable private browsing</string>
|
||||
<!-- Placeholder text shown in the search bar before a user enters text -->
|
||||
<string name="search_hint">Search or enter address</string>
|
||||
<!-- No Open Tabs Message Header -->
|
||||
<string name="no_open_tabs_header">No tabs opened</string>
|
||||
<!-- No Open Tabs Message Description -->
|
||||
<string name="no_open_tabs_description">Your open tabs will be shown here.</string>
|
||||
|
||||
<!-- Private Browsing -->
|
||||
<!-- Title for private session option -->
|
||||
|
@ -77,11 +81,9 @@
|
|||
<!-- Button in the search view that lets a user navigate to the site in their clipboard -->
|
||||
<string name="awesomebar_clipboard_title">Fill link from clipboard</string>
|
||||
|
||||
<!-- Settings Fragment -->
|
||||
<!-- Preferences -->
|
||||
<!-- Title for the settings page-->
|
||||
<string name="settings">Settings</string>
|
||||
|
||||
<!-- Preferences -->
|
||||
<!-- Preference category for basic settings -->
|
||||
<string name="preferences_category_basics">Basics</string>
|
||||
<!-- Preference category for all links about Fenix -->
|
||||
|
@ -281,16 +283,15 @@
|
|||
The first parameter is a digit that shows the cardinal number of how many additional tabs the session has. -->
|
||||
<string name="session_items_more">%1$d sites…</string>
|
||||
|
||||
<!-- No Open Tabs Message Header -->
|
||||
<string name="no_open_tabs_header">No tabs opened</string>
|
||||
<!-- No Open Tabs Message Description -->
|
||||
<string name="no_open_tabs_description">Your open tabs will be shown here.</string>
|
||||
|
||||
<!-- History -->
|
||||
<!-- Text for the button to clear all history -->
|
||||
<string name="history_delete_all">Delete history</string>
|
||||
<!-- Text for the button to delete a single history item -->
|
||||
<string name="history_delete_item">Delete</string>
|
||||
<!-- History multi select title in app bar
|
||||
The first parameter is the number of bookmarks selected -->
|
||||
<string name="history_multi_select_title">%1$d selected</string>
|
||||
<!-- Text for the button to clear selected history items. The first parameter
|
||||
is a digit showing the number of items you have selected -->
|
||||
<string name="history_delete_some">Delete %1$d items</string>
|
||||
|
@ -302,13 +303,10 @@
|
|||
<string name="history_this_month">This month</string>
|
||||
<!-- Text for the header that groups the history older than the last month -->
|
||||
<string name="history_older">Older</string>
|
||||
<!-- Text displayed in a notification when the user enters full screen mode -->
|
||||
<string name="full_screen_notification">Entering full screen mode</string>
|
||||
|
||||
<!-- Crashes -->
|
||||
<!-- Title text displayed on the tab crash page. This first parameter is the name of the application (For example: Fenix) -->
|
||||
<string name="tab_crash_title_2">Sorry. %1$s can’t load that page.</string>
|
||||
|
||||
<!-- Description text displayed on the tab crash page -->
|
||||
<string name="tab_crash_description">You can attempt to restore or close this tab below.</string>
|
||||
<!-- Send crash report checkbox text on the tab crash page -->
|
||||
|
@ -324,6 +322,7 @@
|
|||
<!-- Content Description for session item share button -->
|
||||
<string name="content_description_session_share">Share session</string>
|
||||
|
||||
<!-- Bookmarks -->
|
||||
<!-- Content description for bookmarks library menu -->
|
||||
<string name="bookmark_menu_content_description">Bookmark menu</string>
|
||||
<!-- Screen title for editing bookmarks -->
|
||||
|
@ -338,7 +337,6 @@
|
|||
<string name="bookmark_saved_snackbar">Bookmark saved!</string>
|
||||
<!-- Snackbar edit button shown after a bookmark has been created. -->
|
||||
<string name="edit_bookmark_snackbar_action">EDIT</string>
|
||||
|
||||
<!-- Bookmark overflow menu edit button -->
|
||||
<string name="bookmark_menu_edit_button">Edit</string>
|
||||
<!-- Bookmark overflow menu select button -->
|
||||
|
@ -384,9 +382,6 @@
|
|||
<!-- Bookmark undo button for deletion snackbar action -->
|
||||
<string name="bookmark_undo_deletion">UNDO</string>
|
||||
|
||||
<!-- Message for copying the URL via long press on the toolbar -->
|
||||
<string name="url_copied">URL copied</string>
|
||||
|
||||
<!-- Site Permissions -->
|
||||
<!-- Button label that take the user to the Android App setting -->
|
||||
<string name="phone_feature_go_to_settings">Go to Settings</string>
|
||||
|
@ -434,58 +429,48 @@
|
|||
<string name="no_collections_header">No collections</string>
|
||||
<!-- No Open Tabs Message Description -->
|
||||
<string name="no_collections_description">Your collections will be shown here.</string>
|
||||
|
||||
<!-- Title for the "select tabs" step of the collection creator -->
|
||||
<string name="create_collection_select_tabs">Select Tabs</string>
|
||||
|
||||
<!-- Title for the "select collection" step of the collection creator -->
|
||||
<string name="create_collection_select_collection">Select collection</string>
|
||||
|
||||
<!-- Title for the "name collection" step of the collection creator -->
|
||||
<string name="create_collection_name_collection">Name collection</string>
|
||||
|
||||
<!-- Button to add new collection for the "select collection" step of the collection creator -->
|
||||
<string name="create_collection_add_new_collection">Add new collection</string>
|
||||
|
||||
<!-- Button to select all tabs in the "select tabs" step of the collection creator -->
|
||||
<string name="create_collection_select_all">Select All</string>
|
||||
<!-- Text to prompt users to select the tabs to save in the "select tabs" step of the collection creator -->
|
||||
<string name="create_collection_save_to_collection_empty">Select tabs to save</string>
|
||||
|
||||
<!-- Text to show users how many tabs they have selected in the "select tabs" step of the collection creator.
|
||||
%d is a placeholder for the number of tabs selected. -->
|
||||
<string name="create_collection_save_to_collection_tabs_selected">%d tabs selected</string>
|
||||
|
||||
<!-- Text to show users they have one tab selected in the "select tabs" step of the collection creator.
|
||||
%d is a placeholder for the number of tabs selected. -->
|
||||
<string name="create_collection_save_to_collection_tab_selected">%d tab selected</string>
|
||||
|
||||
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
|
||||
<string name="create_collection_tabs_saved">Tabs saved!</string>
|
||||
|
||||
<!-- Text shown in snackbar when one tab has been saved in a collection -->
|
||||
<string name="create_collection_tab_saved">Tab saved!</string>
|
||||
|
||||
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
|
||||
<string name="create_collection_close">Close</string>
|
||||
|
||||
<!-- Button to save currently selected tabs in the "select tabs" step of the collection creator-->
|
||||
<string name="create_collection_save">Save</string>
|
||||
|
||||
<!-- Default name for a new collection in "name new collection" step of the collection creator. %d is a placeholder for the number of collections-->
|
||||
<string name="create_collection_default_name">Collection %d</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<!-- Text shown in snackbar when user deletes a collection -->
|
||||
<string name="snackbar_collection_deleted">Collection deleted</string>
|
||||
|
||||
<!-- Text shown in snackbar when user deletes a tab -->
|
||||
<string name="snackbar_tab_deleted">Tab deleted</string>
|
||||
|
||||
<!-- Text for action to undo deleting a tab or collection shown in snackbar -->
|
||||
<string name="snackbar_deleted_undo">UNDO</string>
|
||||
|
||||
<!-- QR code scanner prompt which appears after scanning a code, but before navigating to it
|
||||
First parameter is the name of the app, second parameter is the URL or text scanned-->
|
||||
<string name="qr_scanner_confirmation_dialog_message">Allow %1$s to open %2$s</string>
|
||||
|
||||
<!-- Text displayed in a notification when the user enters full screen mode -->
|
||||
<string name="full_screen_notification">Entering full screen mode</string>
|
||||
<!-- Message for copying the URL via long press on the toolbar -->
|
||||
<string name="url_copied">URL copied</string>
|
||||
</resources>
|
||||
|
|
|
@ -39,22 +39,40 @@ class HistoryComponentTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `add and remove one history item normally`() {
|
||||
fun `select two items for removal, then deselect one, then select it again`() {
|
||||
val historyItem = HistoryItem(1, "Mozilla", "http://mozilla.org", 0)
|
||||
val historyItem2 = HistoryItem(2, "Mozilla", "http://mozilla.org", 0)
|
||||
|
||||
emitter.onNext(HistoryChange.Change(listOf(historyItem, historyItem2)))
|
||||
emitter.onNext(HistoryChange.EnterEditMode(historyItem))
|
||||
emitter.onNext(HistoryChange.AddItemForRemoval(historyItem2))
|
||||
emitter.onNext(HistoryChange.RemoveItemForRemoval(historyItem))
|
||||
emitter.onNext(HistoryChange.AddItemForRemoval(historyItem))
|
||||
emitter.onNext(HistoryChange.ExitEditMode)
|
||||
|
||||
historyObserver.assertSubscribed().awaitCount(7).assertNoErrors()
|
||||
.assertValues(
|
||||
HistoryState(listOf(), HistoryState.Mode.Normal),
|
||||
HistoryState(listOf(historyItem, historyItem2), HistoryState.Mode.Normal),
|
||||
HistoryState(listOf(historyItem, historyItem2), HistoryState.Mode.Editing(listOf(historyItem))),
|
||||
HistoryState(listOf(historyItem, historyItem2), HistoryState.Mode.Editing(listOf(historyItem, historyItem2))),
|
||||
HistoryState(listOf(historyItem, historyItem2), HistoryState.Mode.Editing(listOf(historyItem2))),
|
||||
HistoryState(listOf(historyItem, historyItem2), HistoryState.Mode.Editing(listOf(historyItem2, historyItem))),
|
||||
HistoryState(listOf(historyItem, historyItem2), HistoryState.Mode.Normal)
|
||||
)
|
||||
}
|
||||
@Test
|
||||
fun `deselecting all items triggers normal mode`() {
|
||||
val historyItem = HistoryItem(123, "Mozilla", "http://mozilla.org", 0)
|
||||
|
||||
emitter.onNext(HistoryChange.Change(listOf(historyItem)))
|
||||
emitter.onNext(HistoryChange.EnterEditMode(historyItem))
|
||||
emitter.onNext(HistoryChange.RemoveItemForRemoval(historyItem))
|
||||
emitter.onNext(HistoryChange.AddItemForRemoval(historyItem))
|
||||
emitter.onNext(HistoryChange.ExitEditMode)
|
||||
|
||||
historyObserver.assertSubscribed().awaitCount(6).assertNoErrors()
|
||||
.assertValues(
|
||||
HistoryState(listOf(), HistoryState.Mode.Normal),
|
||||
HistoryState(listOf(historyItem), HistoryState.Mode.Normal),
|
||||
HistoryState(listOf(historyItem), HistoryState.Mode.Editing(listOf(historyItem))),
|
||||
HistoryState(listOf(historyItem), HistoryState.Mode.Editing(listOf())),
|
||||
HistoryState(listOf(historyItem), HistoryState.Mode.Editing(listOf(historyItem))),
|
||||
HistoryState(listOf(historyItem), HistoryState.Mode.Normal)
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user