diff --git a/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt b/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt new file mode 100644 index 000000000..a5cdfa7c1 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt @@ -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 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkAdapter.kt index 2c90aeddf..5697315e1 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkAdapter.kt @@ -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() { @@ -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) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkItemMenu.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkItemMenu.kt index 85d79e0c5..31c43562b 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkItemMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkItemMenu.kt @@ -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() + ) } } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderAdapter.kt index beb54c9ea..1b0bbf130 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderAdapter.kt @@ -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 = listOf() - ): List { - 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 { + 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 { diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderFragment.kt index 889c7da6e..54997b84e 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderFragment.kt @@ -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) } } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt index fe83c229f..b744d4629 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt @@ -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() + } } } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt index 0cf675d6f..efd9bd104 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt @@ -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 } } diff --git a/app/src/main/res/layout/bookmark_row.xml b/app/src/main/res/layout/bookmark_row.xml deleted file mode 100644 index ef1964aac..000000000 --- a/app/src/main/res/layout/bookmark_row.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/history_list_item.xml b/app/src/main/res/layout/history_list_item.xml deleted file mode 100644 index 49da091b6..000000000 --- a/app/src/main/res/layout/history_list_item.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/library_site_item.xml b/app/src/main/res/layout/library_site_item.xml new file mode 100644 index 000000000..7cbbf49ac --- /dev/null +++ b/app/src/main/res/layout/library_site_item.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + +