1
0
Fork 0

FNX-14513 ⁃ For #12862: Use concept-menu in library (#13332)

master
Tiger Oakes 2020-08-14 16:44:09 -07:00 committed by GitHub
parent 2e61425f2b
commit a04b91ee3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 307 additions and 91 deletions

View File

@ -13,8 +13,8 @@ import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.library_site_item.view.*
import mozilla.components.browser.menu.BrowserMenu
import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.concept.menu.MenuController
import mozilla.components.concept.menu.Orientation
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.increaseTapArea
@ -48,10 +48,6 @@ interface SelectionHolder<T> {
val selectedItems: Set<T>
}
interface LibraryItemMenu {
val menuBuilder: BrowserMenuBuilder
}
class LibrarySiteItemView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@ -102,11 +98,11 @@ class LibrarySiteItemView @JvmOverloads constructor(
context.components.core.icons.loadIntoView(favicon, url)
}
fun attachMenu(menu: LibraryItemMenu) {
fun attachMenu(menuController: MenuController) {
overflow_menu.setOnClickListener {
menu.menuBuilder.build(context).show(
menuController.show(
anchor = it,
orientation = BrowserMenu.Orientation.DOWN
orientation = Orientation.DOWN
)
}
}

View File

@ -5,65 +5,89 @@
package org.mozilla.fenix.library.bookmarks
import android.content.Context
import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
import mozilla.components.concept.storage.BookmarkNode
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.concept.storage.BookmarkNodeType
import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.R
import org.mozilla.fenix.library.LibraryItemMenu
import org.mozilla.fenix.theme.ThemeManager
class BookmarkItemMenu(
private val context: Context,
private val item: BookmarkNode,
private val onItemTapped: (BookmarkItemMenu.Item) -> Unit = {}
) : LibraryItemMenu {
private val onItemTapped: (Item) -> Unit
) {
sealed class Item {
object Edit : Item()
object Select : Item()
object Copy : Item()
object Share : Item()
object OpenInNewTab : Item()
object OpenInPrivateTab : Item()
object Delete : Item()
enum class Item {
Edit,
Copy,
Share,
OpenInNewTab,
OpenInPrivateTab,
Delete;
}
override val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
val menuController: MenuController by lazy { BrowserMenuController() }
private val menuItems by lazy {
listOfNotNull(
if (item.type in listOf(BookmarkNodeType.ITEM, BookmarkNodeType.FOLDER)) {
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_edit_button)) {
onItemTapped.invoke(BookmarkItemMenu.Item.Edit)
@VisibleForTesting
internal fun menuItems(itemType: BookmarkNodeType): List<TextMenuCandidate> {
return listOfNotNull(
if (itemType != BookmarkNodeType.SEPARATOR) {
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_edit_button)
) {
onItemTapped.invoke(Item.Edit)
}
} else null,
if (item.type == BookmarkNodeType.ITEM) {
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_copy_button)) {
onItemTapped.invoke(BookmarkItemMenu.Item.Copy)
} else {
null
},
if (itemType == BookmarkNodeType.ITEM) {
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_copy_button)
) {
onItemTapped.invoke(Item.Copy)
}
} else null,
if (item.type == BookmarkNodeType.ITEM) {
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_share_button)) {
onItemTapped.invoke(BookmarkItemMenu.Item.Share)
} else {
null
},
if (itemType == BookmarkNodeType.ITEM) {
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_share_button)
) {
onItemTapped.invoke(Item.Share)
}
} else null,
if (item.type == BookmarkNodeType.ITEM) {
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_open_in_new_tab_button)) {
onItemTapped.invoke(BookmarkItemMenu.Item.OpenInNewTab)
} else {
null
},
if (itemType == BookmarkNodeType.ITEM) {
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_open_in_new_tab_button)
) {
onItemTapped.invoke(Item.OpenInNewTab)
}
} else null,
if (item.type == BookmarkNodeType.ITEM) {
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_open_in_private_tab_button)) {
onItemTapped.invoke(BookmarkItemMenu.Item.OpenInPrivateTab)
} else {
null
},
if (itemType == BookmarkNodeType.ITEM) {
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_open_in_private_tab_button)
) {
onItemTapped.invoke(Item.OpenInPrivateTab)
}
} else null,
SimpleBrowserMenuItem(
context.getString(R.string.bookmark_menu_delete_button),
textColorResource = ThemeManager.resolveAttribute(R.attr.destructive, context)
} else {
null
},
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_delete_button),
textStyle = TextStyle(color = context.getColorFromAttr(R.attr.destructive))
) {
onItemTapped.invoke(BookmarkItemMenu.Item.Delete)
onItemTapped.invoke(Item.Delete)
}
)
}
fun updateMenu(itemType: BookmarkNodeType) {
menuController.submitList(menuItems(itemType))
}
}

View File

@ -44,7 +44,7 @@ class BookmarkFolderViewHolder(
setSelectionListeners(item, mode)
if (!item.inRoots()) {
setupMenu(item)
updateMenu(item.type)
if (payload.modeChanged) {
if (mode is BookmarkFragmentState.Mode.Selecting) {
containerView.overflowView.hideAndDisable()

View File

@ -37,7 +37,7 @@ class BookmarkItemViewHolder(
override fun bind(item: BookmarkNode, mode: BookmarkFragmentState.Mode, payload: BookmarkPayload) {
this.item = item
setupMenu(item)
updateMenu(item.type)
if (payload.modeChanged) {
if (mode is BookmarkFragmentState.Mode.Selecting) {

View File

@ -5,29 +5,32 @@
package org.mozilla.fenix.library.bookmarks.viewholders
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.SelectionHolder
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
import org.mozilla.fenix.library.bookmarks.BookmarkItemMenu
import org.mozilla.fenix.library.bookmarks.BookmarkPayload
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
import org.mozilla.fenix.utils.Do
/**
* Base class for bookmark node view holders.
*/
abstract class BookmarkNodeViewHolder(
override val containerView: LibrarySiteItemView,
protected val containerView: LibrarySiteItemView,
private val interactor: BookmarkViewInteractor
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
) : RecyclerView.ViewHolder(containerView) {
abstract var item: BookmarkNode?
private lateinit var menu: BookmarkItemMenu
abstract fun bind(
item: BookmarkNode,
mode: BookmarkFragmentState.Mode
)
init {
setupMenu()
}
abstract fun bind(item: BookmarkNode, mode: BookmarkFragmentState.Mode)
abstract fun bind(
item: BookmarkNode,
@ -39,11 +42,11 @@ abstract class BookmarkNodeViewHolder(
containerView.setSelectionInteractor(item, selectionHolder, interactor)
}
protected fun setupMenu(item: BookmarkNode) {
val bookmarkItemMenu = BookmarkItemMenu(containerView.context, item) {
when (it) {
private fun setupMenu() {
menu = BookmarkItemMenu(containerView.context) { menuItem ->
val item = this.item ?: return@BookmarkItemMenu
Do exhaustive when (menuItem) {
BookmarkItemMenu.Item.Edit -> interactor.onEditPressed(item)
BookmarkItemMenu.Item.Select -> interactor.select(item)
BookmarkItemMenu.Item.Copy -> interactor.onCopyPressed(item)
BookmarkItemMenu.Item.Share -> interactor.onSharePressed(item)
BookmarkItemMenu.Item.OpenInNewTab -> interactor.onOpenInNormalTab(item)
@ -52,6 +55,8 @@ abstract class BookmarkNodeViewHolder(
}
}
containerView.attachMenu(bookmarkItemMenu)
containerView.attachMenu(menu.menuController)
}
protected fun updateMenu(itemType: BookmarkNodeType) = menu.updateMenu(itemType)
}

View File

@ -26,7 +26,7 @@ class BookmarkSeparatorViewHolder(
) {
this.item = item
containerView.displayAs(LibrarySiteItemView.ItemType.SEPARATOR)
setupMenu(item)
updateMenu(item.type)
}
override fun bind(

View File

@ -5,43 +5,61 @@
package org.mozilla.fenix.library.history
import android.content.Context
import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
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
import org.mozilla.fenix.library.LibraryItemMenu
import org.mozilla.fenix.theme.ThemeManager
class HistoryItemMenu(
private val context: Context,
private val onItemTapped: (Item) -> Unit = {}
) : LibraryItemMenu {
sealed class Item {
object Copy : Item()
object Share : Item()
object OpenInNewTab : Item()
object OpenInPrivateTab : Item()
object Delete : Item()
private val onItemTapped: (Item) -> Unit
) {
enum class Item {
Copy,
Share,
OpenInNewTab,
OpenInPrivateTab,
Delete;
}
override val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
val menuController: MenuController by lazy {
BrowserMenuController().apply {
submitList(menuItems())
}
}
private val menuItems by lazy {
listOfNotNull(
SimpleBrowserMenuItem(context.getString(R.string.history_menu_copy_button)) {
@VisibleForTesting
internal fun menuItems(): List<TextMenuCandidate> {
return listOf(
TextMenuCandidate(
text = context.getString(R.string.history_menu_copy_button)
) {
onItemTapped.invoke(Item.Copy)
},
SimpleBrowserMenuItem(context.getString(R.string.history_menu_share_button)) {
TextMenuCandidate(
text = context.getString(R.string.history_menu_share_button)
) {
onItemTapped.invoke(Item.Share)
},
SimpleBrowserMenuItem(context.getString(R.string.history_menu_open_in_new_tab_button)) {
TextMenuCandidate(
text = context.getString(R.string.history_menu_open_in_new_tab_button)
) {
onItemTapped.invoke(Item.OpenInNewTab)
},
SimpleBrowserMenuItem(context.getString(R.string.history_menu_open_in_private_tab_button)) {
TextMenuCandidate(
text = context.getString(R.string.history_menu_open_in_private_tab_button)
) {
onItemTapped.invoke(Item.OpenInPrivateTab)
},
SimpleBrowserMenuItem(
context.getString(R.string.history_delete_item),
textColorResource = ThemeManager.resolveAttribute(R.attr.destructive, context)
TextMenuCandidate(
text = context.getString(R.string.history_delete_item),
textStyle = TextStyle(
color = context.getColorFromAttr(R.attr.destructive)
)
) {
onItemTapped.invoke(Item.Delete)
}

View File

@ -110,7 +110,6 @@ class HistoryListItemViewHolder(
private fun setupMenu() {
val historyMenu = HistoryItemMenu(itemView.context) {
val item = this.item ?: return@HistoryItemMenu
Do exhaustive when (it) {
HistoryItemMenu.Item.Copy -> historyInteractor.onCopyPressed(item)
HistoryItemMenu.Item.Share -> historyInteractor.onSharePressed(item)
@ -120,7 +119,7 @@ class HistoryListItemViewHolder(
}
}
itemView.history_layout.attachMenu(historyMenu)
itemView.history_layout.attachMenu(historyMenu.menuController)
}
companion object {

View File

@ -0,0 +1,98 @@
/* 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.bookmarks
import android.content.Context
import androidx.appcompat.view.ContextThemeWrapper
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.concept.menu.candidate.TextStyle
import mozilla.components.concept.storage.BookmarkNodeType
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.bookmarks.BookmarkItemMenu.Item
@RunWith(FenixRobolectricTestRunner::class)
class BookmarkItemMenuTest {
private lateinit var context: Context
private lateinit var onItemTapped: (Item) -> Unit
private lateinit var menu: BookmarkItemMenu
@Before
fun setup() {
context = ContextThemeWrapper(testContext, R.style.NormalTheme)
onItemTapped = mockk(relaxed = true)
menu = BookmarkItemMenu(context, onItemTapped)
}
@Test
fun `delete item has special styling`() {
val deleteItem = menu.menuItems(BookmarkNodeType.SEPARATOR).last()
assertEquals("Delete", deleteItem.text)
assertEquals(
TextStyle(color = context.getColorFromAttr(R.attr.destructive)),
deleteItem.textStyle
)
deleteItem.onClick()
verify { onItemTapped(Item.Delete) }
}
@Test
fun `edit item appears for folders`() {
val folderItems = menu.menuItems(BookmarkNodeType.FOLDER)
assertEquals(2, folderItems.size)
val (edit, delete) = folderItems
assertEquals("Edit", edit.text)
edit.onClick()
verify { onItemTapped(Item.Edit) }
assertEquals("Delete", delete.text)
}
@Test
fun `all item appears for sites`() {
val siteItems = menu.menuItems(BookmarkNodeType.ITEM)
assertEquals(6, siteItems.size)
val (edit, copy, share, openInNewTab, openInPrivateTab, delete) = siteItems
assertEquals("Edit", edit.text)
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)
edit.onClick()
verify { onItemTapped(Item.Edit) }
copy.onClick()
verify { onItemTapped(Item.Copy) }
share.onClick()
verify { onItemTapped(Item.Share) }
openInNewTab.onClick()
verify { onItemTapped(Item.OpenInNewTab) }
openInPrivateTab.onClick()
verify { onItemTapped(Item.OpenInPrivateTab) }
delete.onClick()
verify { onItemTapped(Item.Delete) }
}
private operator fun <T> List<T>.component6(): T {
return get(5)
}
}

View File

@ -0,0 +1,76 @@
/* 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 io.mockk.mockk
import io.mockk.verify
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 onItemTapped: (Item) -> Unit
private lateinit var menu: HistoryItemMenu
@Before
fun setup() {
context = ContextThemeWrapper(testContext, R.style.NormalTheme)
onItemTapped = mockk(relaxed = true)
menu = HistoryItemMenu(context, onItemTapped)
}
@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()
verify { onItemTapped(Item.Delete) }
}
@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()
verify { onItemTapped(Item.Copy) }
share.onClick()
verify { onItemTapped(Item.Share) }
openInNewTab.onClick()
verify { onItemTapped(Item.OpenInNewTab) }
openInPrivateTab.onClick()
verify { onItemTapped(Item.OpenInPrivateTab) }
delete.onClick()
verify { onItemTapped(Item.Delete) }
}
}