1
0
Fork 0

Create shared custom view for library items

master
Tiger Oakes 2019-06-26 20:30:13 -04:00 committed by Emily Kager
parent ed60bdf470
commit 2467588c4a
10 changed files with 379 additions and 590 deletions

View File

@ -0,0 +1,69 @@
/* 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
import android.content.Context
import android.view.LayoutInflater
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.library_site_item.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.ext.loadIntoView
class LibrarySiteItemView(
context: Context
) : ConstraintLayout(context) {
val titleView: TextView get() = title
val urlView: TextView get() = url
val iconView: ImageView get() = favicon
val overflowView: ImageButton get() = overflow_menu
init {
LayoutInflater.from(context).inflate(R.layout.library_site_item, this, true)
overflow_menu.increaseTapArea(OVERFLOW_EXTRA_DIPS)
}
/**
* Change visibility of parts of this view based on what type of item is being represented.
*/
fun displayAs(mode: ItemType) {
favicon.isVisible = mode != ItemType.SEPARATOR
title.isVisible = mode != ItemType.SEPARATOR
url.isVisible = mode == ItemType.SITE
overflow_menu.isVisible = mode != ItemType.SEPARATOR
separator.isVisible = mode == ItemType.SEPARATOR
isClickable = mode != ItemType.SEPARATOR
isFocusable = mode != ItemType.SEPARATOR
}
/**
* Changes the icon to show a check mark if [isSelected]
*/
fun changeSelected(isSelected: Boolean) {
icon.displayedChild = if (isSelected) 1 else 0
}
fun loadFavicon(url: String) {
context.components.core.icons.loadIntoView(favicon, url)
}
enum class ItemType {
SITE, FOLDER, SEPARATOR;
}
companion object {
private const val OVERFLOW_EXTRA_DIPS = 16
}
}

View File

@ -4,24 +4,21 @@
package org.mozilla.fenix.library.bookmarks
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.bookmark_row.*
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.menu.BrowserMenu
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import org.jetbrains.anko.image
import org.mozilla.fenix.R
import org.mozilla.fenix.ThemeManager
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.library.LibrarySiteItemView
class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteractor) :
RecyclerView.Adapter<BookmarkAdapter.BookmarkNodeViewHolder>() {
@ -76,29 +73,28 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookmarkNodeViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.bookmark_row, parent, false)
val view = LibrarySiteItemView(parent.context).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
}
return when (viewType) {
BookmarkItemViewHolder.viewType.ordinal -> BookmarkItemViewHolder(
view, interactor
)
BookmarkFolderViewHolder.viewType.ordinal -> BookmarkFolderViewHolder(
view, interactor
)
BookmarkSeparatorViewHolder.viewType.ordinal -> BookmarkSeparatorViewHolder(
view, interactor
)
LibrarySiteItemView.ItemType.SITE.ordinal ->
BookmarkItemViewHolder(view, interactor)
LibrarySiteItemView.ItemType.FOLDER.ordinal ->
BookmarkFolderViewHolder(view, interactor)
LibrarySiteItemView.ItemType.SEPARATOR.ordinal ->
BookmarkSeparatorViewHolder(view, interactor)
else -> throw IllegalStateException("ViewType $viewType does not match to a ViewHolder")
}
}
override fun getItemViewType(position: Int): Int {
return when (tree[position].type) {
BookmarkNodeType.ITEM -> ViewType.ITEM.ordinal
BookmarkNodeType.FOLDER -> ViewType.FOLDER.ordinal
BookmarkNodeType.SEPARATOR -> ViewType.SEPARATOR.ordinal
BookmarkNodeType.ITEM -> LibrarySiteItemView.ItemType.SITE
BookmarkNodeType.FOLDER -> LibrarySiteItemView.ItemType.FOLDER
BookmarkNodeType.SEPARATOR -> LibrarySiteItemView.ItemType.SEPARATOR
else -> throw IllegalStateException("Item $tree[position] does not match to a ViewType")
}
}.ordinal
}
override fun getItemCount(): Int = tree.size
@ -111,270 +107,145 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
)
}
open class BookmarkNodeViewHolder(
view: View,
val interactor: BookmarkViewInteractor,
override val containerView: View? = view
) : RecyclerView.ViewHolder(view), LayoutContainer {
open fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {}
}
class BookmarkItemViewHolder(
view: View,
interactor: BookmarkViewInteractor,
override val containerView: View? = view
abstract class BookmarkNodeViewHolder(
val view: LibrarySiteItemView,
val interactor: BookmarkViewInteractor
) :
BookmarkNodeViewHolder(view, interactor, containerView) {
RecyclerView.ViewHolder(view), LayoutContainer {
@Suppress("ComplexMethod")
override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {
override val containerView get() = view
val shiftTwoDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, TWO_DIGIT_MARGIN, containerView!!.context.resources.displayMetrics
).toInt()
val params = bookmark_title.layoutParams as ViewGroup.MarginLayoutParams
params.topMargin = shiftTwoDp
bookmark_title.layoutParams = params
abstract fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean)
bookmark_favicon.visibility = View.VISIBLE
bookmark_title.visibility = View.VISIBLE
bookmark_url.visibility = View.VISIBLE
bookmark_overflow.visibility = View.VISIBLE
bookmark_separator.visibility = View.GONE
bookmark_layout.isClickable = true
val bookmarkItemMenu = BookmarkItemMenu(containerView.context, item) {
protected fun setupMenu(item: BookmarkNode) {
val bookmarkItemMenu = BookmarkItemMenu(view.context, item) {
when (it) {
is BookmarkItemMenu.Item.Edit -> {
interactor.edit(item)
}
is BookmarkItemMenu.Item.Select -> {
interactor.select(item)
}
is BookmarkItemMenu.Item.Copy -> {
interactor.copy(item)
}
is BookmarkItemMenu.Item.Share -> {
interactor.share(item)
}
is BookmarkItemMenu.Item.OpenInNewTab -> {
interactor.openInNewTab(item)
}
is BookmarkItemMenu.Item.OpenInPrivateTab -> {
interactor.openInPrivateTab(item)
}
is BookmarkItemMenu.Item.Delete -> {
interactor.delete(item)
}
is BookmarkItemMenu.Item.Edit -> interactor.edit(item)
is BookmarkItemMenu.Item.Select -> interactor.select(item)
is BookmarkItemMenu.Item.Copy -> interactor.copy(item)
is BookmarkItemMenu.Item.Share -> interactor.share(item)
is BookmarkItemMenu.Item.OpenInNewTab -> interactor.openInNewTab(item)
is BookmarkItemMenu.Item.OpenInPrivateTab -> interactor.openInPrivateTab(item)
is BookmarkItemMenu.Item.Delete -> interactor.delete(item)
}
}
bookmark_overflow.increaseTapArea(bookmarkOverflowExtraDips)
bookmark_overflow.setOnClickListener {
bookmarkItemMenu.menuBuilder.build(containerView.context).show(anchor = it)
}
bookmark_title.text = if (item.title.isNullOrBlank()) item.url else item.title
bookmark_url.text = item.url
updateUrl(item, mode, selected)
}
private fun updateUrl(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {
setClickListeners(mode, item, selected)
setColorsAndIcons(selected, item)
}
private fun setColorsAndIcons(selected: Boolean, item: BookmarkNode) {
val backgroundTint =
if (selected) {
ThemeManager.resolveAttribute(R.attr.accentHighContrast, containerView!!.context)
} else {
ThemeManager.resolveAttribute(R.attr.neutral, containerView!!.context)
}
val backgroundTintList = ContextCompat.getColorStateList(containerView.context, backgroundTint)
bookmark_favicon.backgroundTintList = backgroundTintList
if (selected) bookmark_favicon.setImageResource(R.drawable.mozac_ic_check)
val url = item.url ?: return
if (!selected && url.startsWith("http")) {
bookmark_layout.context.components.core.icons.loadIntoView(bookmark_favicon, url)
}
}
private fun setClickListeners(
mode: BookmarkState.Mode,
item: BookmarkNode,
selected: Boolean
) {
bookmark_layout.setOnClickListener {
if (mode == BookmarkState.Mode.Normal) {
interactor.open(item)
} else {
if (selected) interactor.deselect(item) else interactor.select(item)
}
}
bookmark_layout.setOnLongClickListener {
if (mode == BookmarkState.Mode.Normal) {
if (selected) interactor.deselect(item) else interactor.select(item)
true
} else false
}
}
companion object {
internal const val TWO_DIGIT_MARGIN = 2F
val viewType = ViewType.ITEM
}
}
class BookmarkFolderViewHolder(
view: View,
interactor: BookmarkViewInteractor,
override val containerView: View? = view
) :
BookmarkNodeViewHolder(view, interactor, containerView) {
override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {
containerView?.context?.let {
val drawable = it.getDrawable(R.drawable.ic_folder_icon)
drawable?.setTint(
ContextCompat.getColor(
it,
R.color.primary_text_light_theme
)
)
bookmark_favicon.setImageDrawable(drawable)
}
bookmark_favicon.visibility = View.VISIBLE
bookmark_title.visibility = View.VISIBLE
bookmark_url.visibility = View.GONE
bookmark_overflow.visibility = View.VISIBLE
bookmark_separator.visibility = View.GONE
bookmark_layout.isClickable = true
setClickListeners(mode, item, selected)
setMenu(item, containerView!!)
val backgroundTint = if (selected) {
ThemeManager.resolveAttribute(R.attr.accentHighContrast, containerView.context)
} else {
ThemeManager.resolveAttribute(R.attr.neutral, containerView.context)
}
val backgroundTintList = ContextCompat.getColorStateList(containerView.context, backgroundTint)
bookmark_favicon.backgroundTintList = backgroundTintList
val res = if (selected) R.drawable.mozac_ic_check else R.drawable.ic_folder_icon
bookmark_favicon.setImageResource(res)
bookmark_title?.text = item.title
}
private fun setMenu(
item: BookmarkNode,
containerView: View
) {
val bookmarkItemMenu = BookmarkItemMenu(containerView.context, item) {
when (it) {
is BookmarkItemMenu.Item.Edit -> {
interactor.edit(item)
}
is BookmarkItemMenu.Item.Select -> {
interactor.select(item)
}
is BookmarkItemMenu.Item.Delete -> {
interactor.delete(item)
}
}
}
if (!item.inRoots()) {
bookmark_overflow.increaseTapArea(bookmarkOverflowExtraDips)
bookmark_overflow.setOnClickListener {
bookmarkItemMenu.menuBuilder.build(containerView.context).show(
anchor = it,
orientation = BrowserMenu.Orientation.DOWN
)
}
bookmark_layout.setOnLongClickListener(null)
} else {
bookmark_overflow.visibility = View.GONE
}
}
private fun setClickListeners(
mode: BookmarkState.Mode,
item: BookmarkNode,
selected: Boolean
) {
bookmark_layout.setOnClickListener {
if (mode == BookmarkState.Mode.Normal) {
interactor.expand(item)
} else {
if (selected) interactor.deselect(item) else interactor.select(item)
}
}
bookmark_layout.setOnLongClickListener {
if (mode == BookmarkState.Mode.Normal && !item.inRoots()) {
if (selected) interactor.deselect(item) else interactor.select(item)
true
} else false
}
}
companion object {
val viewType = ViewType.FOLDER
}
}
class BookmarkSeparatorViewHolder(
view: View,
interactor: BookmarkViewInteractor,
override val containerView: View? = view
) : BookmarkNodeViewHolder(view, interactor, containerView) {
override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {
bookmark_favicon.visibility = View.GONE
bookmark_title.visibility = View.GONE
bookmark_url.visibility = View.GONE
bookmark_overflow.increaseTapArea(bookmarkOverflowExtraDips)
bookmark_overflow.visibility = View.GONE
bookmark_separator.visibility = View.VISIBLE
bookmark_layout.isClickable = false
val bookmarkItemMenu = BookmarkItemMenu(containerView!!.context, item) {
when (it) {
is BookmarkItemMenu.Item.Delete -> {
interactor.delete(item)
}
}
}
bookmark_overflow.setOnClickListener {
bookmarkItemMenu.menuBuilder.build(containerView.context).show(
view.overflowView.setOnClickListener {
bookmarkItemMenu.menuBuilder.build(view.context).show(
anchor = it,
orientation = BrowserMenu.Orientation.DOWN
)
}
}
}
companion object {
val viewType = ViewType.SEPARATOR
class BookmarkItemViewHolder(
view: LibrarySiteItemView,
interactor: BookmarkViewInteractor
) :
BookmarkNodeViewHolder(view, interactor) {
@Suppress("ComplexMethod")
override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {
view.displayAs(LibrarySiteItemView.ItemType.SITE)
setupMenu(item)
view.titleView.text = if (item.title.isNullOrBlank()) item.url else item.title
view.urlView.text = item.url
setClickListeners(mode, item, selected)
view.changeSelected(selected)
setColorsAndIcons(item.url)
}
private fun setColorsAndIcons(url: String?) {
if (url != null && url.startsWith("http")) {
view.loadFavicon(url)
} else {
view.iconView.setImageDrawable(null)
}
}
private fun setClickListeners(
mode: BookmarkState.Mode,
item: BookmarkNode,
selected: Boolean
) {
view.setOnClickListener {
when {
mode == BookmarkState.Mode.Normal -> interactor.open(item)
selected -> interactor.deselect(item)
else -> interactor.select(item)
}
}
view.setOnLongClickListener {
if (mode == BookmarkState.Mode.Normal) {
interactor.select(item)
true
} else false
}
}
}
companion object {
private const val bookmarkOverflowExtraDips = 16
class BookmarkFolderViewHolder(
view: LibrarySiteItemView,
interactor: BookmarkViewInteractor
) :
BookmarkNodeViewHolder(view, interactor) {
override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {
view.displayAs(LibrarySiteItemView.ItemType.FOLDER)
setClickListeners(mode, item, selected)
if (!item.inRoots()) {
setupMenu(item)
view.setOnLongClickListener(null)
} else {
view.overflowView.visibility = View.GONE
}
view.changeSelected(selected)
view.iconView.image = view.context.getDrawable(R.drawable.ic_folder_icon)?.apply {
setTint(ContextCompat.getColor(view.context, R.color.primary_text_light_theme))
}
view.titleView.text = item.title
}
private fun setClickListeners(
mode: BookmarkState.Mode,
item: BookmarkNode,
selected: Boolean
) {
view.setOnClickListener {
when {
mode == BookmarkState.Mode.Normal -> interactor.expand(item)
selected -> interactor.deselect(item)
else -> interactor.select(item)
}
}
view.setOnLongClickListener {
if (mode == BookmarkState.Mode.Normal && !item.inRoots()) {
interactor.select(item)
true
} else false
}
}
}
enum class ViewType {
ITEM, FOLDER, SEPARATOR
class BookmarkSeparatorViewHolder(
view: LibrarySiteItemView,
interactor: BookmarkViewInteractor
) : BookmarkNodeViewHolder(view, interactor) {
override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {
view.displayAs(LibrarySiteItemView.ItemType.SEPARATOR)
setupMenu(item)
}
}
}

View File

@ -31,7 +31,7 @@ class BookmarkItemMenu(
val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
private val menuItems by lazy {
listOf(
listOfNotNull(
if (item.type in listOf(BookmarkNodeType.ITEM, BookmarkNodeType.FOLDER)) {
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_edit_button)) {
onItemTapped.invoke(BookmarkItemMenu.Item.Edit)
@ -63,6 +63,6 @@ class BookmarkItemMenu(
) {
onItemTapped.invoke(BookmarkItemMenu.Item.Delete)
}
).filterNotNull()
)
}
}

View File

@ -4,19 +4,15 @@
package org.mozilla.fenix.library.bookmarks.selectfolder
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.bookmark_row.*
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.support.ktx.android.util.dpToPx
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getColorResFromAttr
import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel
class SelectBookmarkFolderAdapter(private val sharedViewModel: BookmarksSharedViewModel) :
@ -30,21 +26,9 @@ class SelectBookmarkFolderAdapter(private val sharedViewModel: BookmarksSharedVi
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookmarkFolderViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.bookmark_row, parent, false)
val view = LibrarySiteItemView(parent.context)
return when (viewType) {
BookmarkFolderViewHolder.viewType -> SelectBookmarkFolderAdapter.BookmarkFolderViewHolder(
view
)
else -> throw IllegalStateException("ViewType $viewType does not match to a ViewHolder")
}
}
override fun getItemViewType(position: Int): Int {
return when (tree[position].node.type) {
BookmarkNodeType.FOLDER -> BookmarkFolderViewHolder.viewType
else -> throw IllegalStateException("Item $tree[position] does not match to a ViewType")
}
return BookmarkFolderViewHolder(view)
}
override fun getItemCount(): Int = tree.size
@ -52,63 +36,40 @@ class SelectBookmarkFolderAdapter(private val sharedViewModel: BookmarksSharedVi
override fun onBindViewHolder(holder: BookmarkFolderViewHolder, position: Int) {
holder.bind(
tree[position],
tree[position].node == sharedViewModel.selectedFolder,
object : SelectionInterface {
override fun itemSelected(node: BookmarkNode) {
sharedViewModel.apply {
when (selectedFolder) {
node -> selectedFolder = null
else -> selectedFolder = node
}
}
notifyDataSetChanged()
tree[position].node == sharedViewModel.selectedFolder
) { node ->
sharedViewModel.apply {
when (selectedFolder) {
node -> selectedFolder = null
else -> selectedFolder = node
}
}
)
}
interface SelectionInterface {
fun itemSelected(node: BookmarkNode)
notifyDataSetChanged()
}
}
class BookmarkFolderViewHolder(
view: View,
override val containerView: View? = view
val view: LibrarySiteItemView
) :
RecyclerView.ViewHolder(view), LayoutContainer {
override val containerView get() = view
init {
bookmark_favicon.visibility = View.VISIBLE
bookmark_title.visibility = View.VISIBLE
bookmark_url.visibility = View.GONE
bookmark_separator.visibility = View.GONE
bookmark_layout.isClickable = true
view.displayAs(LibrarySiteItemView.ItemType.FOLDER)
view.overflowView.visibility = View.GONE
}
fun bind(folder: BookmarkNodeWithDepth, selected: Boolean, selectionInterface: SelectionInterface) {
val backgroundTintAttr = if (selected) R.attr.accentBright else R.attr.neutral
// Center the bookmark title since we don't have a url
val constraintSet = ConstraintSet()
constraintSet.clone(bookmark_layout)
constraintSet.connect(
bookmark_title.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM
)
constraintSet.applyTo(bookmark_layout)
val backgroundTint = containerView!!.context.getColorResFromAttr(backgroundTintAttr)
val backgroundTintList = ContextCompat.getColorStateList(containerView.context, backgroundTint)
bookmark_favicon.backgroundTintList = backgroundTintList
val res = if (selected) R.drawable.mozac_ic_check else R.drawable.ic_folder_icon
bookmark_favicon.setImageResource(res)
bookmark_overflow.visibility = View.GONE
bookmark_title?.text = folder.node.title
bookmark_layout.setOnClickListener {
selectionInterface.itemSelected(folder.node)
fun bind(folder: BookmarkNodeWithDepth, selected: Boolean, onSelect: (BookmarkNode) -> Unit) {
view.changeSelected(selected)
view.iconView.setImageResource(R.drawable.ic_folder_icon)
view.titleView.text = folder.node.title
view.setOnClickListener {
onSelect(folder.node)
}
val pxToIndent = dpsToIndent.dpToPx(containerView.resources.displayMetrics)
val pxToIndent = dpsToIndent.dpToPx(view.context.resources.displayMetrics)
val padding = pxToIndent * if (folder.depth > maxDepth) maxDepth else folder.depth
bookmark_layout.setPadding(padding, 0, 0, 0)
view.setPadding(padding, 0, 0, 0)
}
companion object {
@ -118,17 +79,12 @@ class SelectBookmarkFolderAdapter(private val sharedViewModel: BookmarksSharedVi
data class BookmarkNodeWithDepth(val depth: Int, val node: BookmarkNode, val parent: String?)
private fun BookmarkNode?.convertToFolderDepthTree(
depth: Int = 0,
list: List<BookmarkNodeWithDepth> = listOf()
): List<BookmarkNodeWithDepth> {
return if (this != null) {
val newList = list.plus(listOf(BookmarkNodeWithDepth(depth, this, this.parentGuid)))
newList.plus(
children?.filter { it.type == BookmarkNodeType.FOLDER }
?.flatMap { it.convertToFolderDepthTree(depth + 1) }
?: listOf())
} else listOf()
private fun BookmarkNode.convertToFolderDepthTree(depth: Int = 0): List<BookmarkNodeWithDepth> {
val newList = listOf(BookmarkNodeWithDepth(depth, this, this.parentGuid))
return newList + children
?.filter { it.type == BookmarkNodeType.FOLDER }
?.flatMap { it.convertToFolderDepthTree(depth = depth + 1) }
.orEmpty()
}
companion object {

View File

@ -24,12 +24,12 @@ import kotlinx.android.synthetic.main.fragment_select_bookmark_folder.view.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getColorFromAttr
import org.mozilla.fenix.ext.nav
@ -75,26 +75,22 @@ class SelectBookmarkFolderFragment : Fragment(), AccountObserver {
override fun onResume() {
super.onResume()
context?.let {
setRootTitles(it, showMobileRoot = true)
}
(activity as AppCompatActivity).title =
getString(R.string.bookmark_select_folder_fragment_label)
(activity as AppCompatActivity).supportActionBar?.show()
context?.let { setRootTitles(it, showMobileRoot = true) }
activity?.title = getString(R.string.bookmark_select_folder_fragment_label)
(activity as? AppCompatActivity)?.supportActionBar?.show()
folderGuid = SelectBookmarkFolderFragmentArgs.fromBundle(arguments!!).folderGuid ?: BookmarkRoot.Root.id
checkIfSignedIn()
lifecycleScope.launch(IO) {
bookmarkNode =
lifecycleScope.launch(Main) {
bookmarkNode = withContext(IO) {
requireComponents.core.bookmarksStorage.getTree(BookmarkRoot.Root.id, true)
.withOptionalDesktopFolders(context, showMobileRoot = true)
launch(Main) {
(activity as HomeActivity).title = bookmarkNode?.title ?: getString(R.string.library_bookmarks)
val adapter = SelectBookmarkFolderAdapter(sharedViewModel)
recylerView_bookmark_folders.adapter = adapter
adapter.updateData(bookmarkNode)
}
activity?.title = bookmarkNode?.title ?: getString(R.string.library_bookmarks)
val adapter = SelectBookmarkFolderAdapter(sharedViewModel)
recylerView_bookmark_folders.adapter = adapter
adapter.updateData(bookmarkNode)
}
}

View File

@ -8,9 +8,12 @@ import android.content.Context
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R
import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.history.viewholders.HistoryDeleteButtonViewHolder
import org.mozilla.fenix.library.history.viewholders.HistoryHeaderViewHolder
import org.mozilla.fenix.library.history.viewholders.HistoryListItemViewHolder
@ -149,18 +152,23 @@ class HistoryAdapter(private val historyInteractor: HistoryInteractor) :
return when (historyList.items[position]) {
is AdapterItem.DeleteButton -> HistoryDeleteButtonViewHolder.LAYOUT_ID
is AdapterItem.SectionHeader -> HistoryHeaderViewHolder.LAYOUT_ID
is AdapterItem.Item -> HistoryListItemViewHolder.LAYOUT_ID
is AdapterItem.Item -> HistoryListItemViewHolder.ID
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) {
HistoryDeleteButtonViewHolder.LAYOUT_ID -> HistoryDeleteButtonViewHolder(view, historyInteractor)
HistoryHeaderViewHolder.LAYOUT_ID -> HistoryHeaderViewHolder(view)
HistoryListItemViewHolder.LAYOUT_ID -> HistoryListItemViewHolder(view, historyInteractor)
else -> throw IllegalStateException()
return if (viewType == HistoryListItemViewHolder.ID) {
val view = LibrarySiteItemView(parent.context).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
}
HistoryListItemViewHolder(view, historyInteractor)
} else {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
when (viewType) {
HistoryDeleteButtonViewHolder.LAYOUT_ID -> HistoryDeleteButtonViewHolder(view, historyInteractor)
HistoryHeaderViewHolder.LAYOUT_ID -> HistoryHeaderViewHolder(view)
else -> throw IllegalStateException()
}
}
}

View File

@ -4,47 +4,22 @@
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 kotlinx.android.synthetic.main.history_list_item.view.*
import mozilla.components.browser.menu.BrowserMenu
import org.mozilla.fenix.R
import org.mozilla.fenix.ThemeManager
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.history.HistoryInteractor
import org.mozilla.fenix.library.history.HistoryItem
import org.mozilla.fenix.library.history.HistoryItemMenu
import org.mozilla.fenix.library.history.HistoryState
class HistoryListItemViewHolder(
view: View,
val view: LibrarySiteItemView,
private val historyInteractor: HistoryInteractor
) : RecyclerView.ViewHolder(view) {
private val favicon = view.history_favicon
private val title = view.history_title
private val url = view.history_url
private val menuButton = view.history_item_overflow
private var item: HistoryItem? = null
private lateinit var historyMenu: HistoryItemMenu
private var mode: HistoryState.Mode = HistoryState.Mode.Normal
private val checkListener = CompoundButton.OnCheckedChangeListener { _, isChecked ->
if (mode is HistoryState.Mode.Normal) {
return@OnCheckedChangeListener
}
item?.apply {
if (isChecked) {
historyInteractor.onItemAddedForRemoval(this)
} else {
historyInteractor.onItemRemovedForRemoval(this)
}
}
}
init {
setupMenu()
@ -57,55 +32,33 @@ class HistoryListItemViewHolder(
true
}
menuButton.setOnClickListener {
view.overflowView.setOnClickListener {
historyMenu.menuBuilder.build(view.context).show(
anchor = it,
orientation = BrowserMenu.Orientation.DOWN
)
}
view.displayAs(LibrarySiteItemView.ItemType.SITE)
}
fun bind(item: HistoryItem, mode: HistoryState.Mode) {
this.item = item
this.mode = mode
title.text = item.title
url.text = item.url
view.titleView.text = item.title
view.urlView.text = item.url
val selected = when (mode) {
is HistoryState.Mode.Editing -> mode.selectedItems.contains(item)
else -> false
}
val selected = mode is HistoryState.Mode.Editing && mode.selectedItems.contains(item)
setClickListeners(item, selected)
if (mode is HistoryState.Mode.Editing) {
val backgroundTint =
if (selected) {
ThemeManager.resolveAttribute(R.attr.accentHighContrast, itemView.context)
} else {
ThemeManager.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 {
updateFavIcon(item.url)
}
} else {
val backgroundTint = ThemeManager.resolveAttribute(R.attr.neutral, itemView.context)
val backgroundTintList =
ContextCompat.getColorStateList(itemView.context, backgroundTint)
favicon.backgroundTintList = backgroundTintList
updateFavIcon(item.url)
}
view.changeSelected(selected)
view.loadFavicon(item.url)
}
private fun setupMenu() {
this.historyMenu = HistoryItemMenu(itemView.context) {
historyMenu = HistoryItemMenu(view.context) {
when (it) {
is HistoryItemMenu.Item.Delete -> {
item?.apply { historyInteractor.onDeleteOne(this) }
@ -114,28 +67,20 @@ class HistoryListItemViewHolder(
}
}
private fun updateFavIcon(url: String) {
favicon.context.components.core.icons.loadIntoView(favicon, url)
}
private fun setClickListeners(
item: HistoryItem,
selected: Boolean
) {
itemView.history_layout.setOnClickListener {
if (mode == HistoryState.Mode.Normal) {
historyInteractor.onHistoryItemOpened(item)
} else {
if (selected) {
historyInteractor.onItemRemovedForRemoval(item)
} else {
historyInteractor.onItemAddedForRemoval(item)
}
view.setOnClickListener {
when {
mode == HistoryState.Mode.Normal -> historyInteractor.onHistoryItemOpened(item)
selected -> historyInteractor.onItemRemovedForRemoval(item)
else -> historyInteractor.onItemAddedForRemoval(item)
}
}
}
companion object {
const val LAYOUT_ID = R.layout.history_list_item
val ID = LibrarySiteItemView.ItemType.SITE.ordinal
}
}

View File

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bookmark_layout"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/bookmark_favicon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="20dp"
android:background="@drawable/favicon_background"
android:importantForAccessibility="no"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_folder_icon" />
<TextView
android:id="@+id/bookmark_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:ellipsize="end"
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="?primaryText"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@+id/bookmark_url"
app:layout_constraintEnd_toStartOf="@id/bookmark_overflow"
app:layout_constraintStart_toEndOf="@id/bookmark_favicon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Internet" />
<TextView
android:id="@+id/bookmark_url"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="?secondaryText"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/bookmark_overflow"
app:layout_constraintStart_toEndOf="@id/bookmark_favicon"
app:layout_constraintTop_toBottomOf="@id/bookmark_title"
tools:text="https://github.com/mozilla-mobile/fenix" />
<ImageButton
android:id="@+id/bookmark_overflow"
android:layout_width="@dimen/glyph_button_width"
android:layout_height="@dimen/glyph_button_height"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/bookmark_menu_content_description"
android:src="@drawable/ic_menu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/bookmark_separator"
android:layout_width="0dp"
android:layout_height="2dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="?neutralFaded"
android:importantForAccessibility="no"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,67 +0,0 @@
<?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"
android:id="@+id/history_layout"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="4dp"
android:paddingStart="20dp"
android:paddingEnd="0dp">
<ImageButton
android:id="@+id/history_item_overflow"
android:layout_width="@dimen/glyph_button_width"
android:layout_height="@dimen/glyph_button_height"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/content_description_history_menu"
android:src="@drawable/ic_menu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/history_favicon"
android:layout_width="@dimen/history_favicon_width_height"
android:layout_height="@dimen/history_favicon_width_height"
android:background="@drawable/favicon_background"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/history_url"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="?secondaryText"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@id/history_item_overflow"
app:layout_constraintStart_toEndOf="@id/history_favicon"
app:layout_constraintTop_toBottomOf="@id/history_title" />
<TextView
android:id="@+id/history_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:ellipsize="end"
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="?primaryText"
android:textSize="18sp"
android:layout_marginTop="2dp"
app:layout_constraintEnd_toStartOf="@id/history_item_overflow"
app:layout_constraintStart_toEndOf="@id/history_favicon"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,98 @@
<?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="@dimen/library_item_height"
android:background="?android:attr/selectableItemBackground">
<ImageSwitcher
android:id="@+id/icon"
android:layout_width="@dimen/history_favicon_width_height"
android:layout_height="@dimen/history_favicon_width_height"
android:layout_marginStart="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:id="@+id/favicon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:background="@drawable/favicon_background"
android:backgroundTint="?neutral"
android:importantForAccessibility="no"
tools:src="@drawable/ic_folder_icon" />
<ImageView
android:id="@+id/checkmark"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:background="@drawable/favicon_background"
android:backgroundTint="?accentHighContrast"
android:src="@drawable/mozac_ic_check" />
</ImageSwitcher>
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:singleLine="true"
android:textAlignment="viewStart"
android:textSize="18sp"
android:textColor="?primaryText"
tools:text="Example site"
app:layout_constraintEnd_toStartOf="@id/overflow_menu"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/url"
app:layout_constraintVertical_chainStyle="packed"/>
<TextView
android:id="@+id/url"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="?secondaryText"
android:textSize="12sp"
tools:text="https://example.com/"
app:layout_constraintEnd_toStartOf="@id/overflow_menu"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toBottomOf="@+id/title"
app:layout_constraintBottom_toBottomOf="parent" />
<ImageButton
android:id="@+id/overflow_menu"
android:layout_width="@dimen/glyph_button_width"
android:layout_height="@dimen/glyph_button_height"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/content_description_menu"
android:src="@drawable/ic_menu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<View
android:id="@+id/separator"
android:layout_width="0dp"
android:layout_height="2dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:importantForAccessibility="no"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="?neutralFaded"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>