1
0
Fork 0

For #563: Restyles history management (#2378)

master
Sawyer Blatz 2019-05-10 09:58:54 -07:00 committed by GitHub
parent 005f53965f
commit e1cdeffe8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 317 additions and 107 deletions

View File

@ -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 {

View File

@ -231,7 +231,7 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
refreshBookmarks(components)
}
}
is BookmarkAction.ModeChanged -> activity?.invalidateOptionsMenu()
is BookmarkAction.SwitchMode -> activity?.invalidateOptionsMenu()
}
}

View File

@ -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!!)
)
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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>

View File

@ -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>

View File

@ -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"

View 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>

View File

@ -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

View File

@ -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 cant 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>

View File

@ -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)
)
}