diff --git a/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt b/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt index a5cdfa7c1..2a6243ee8 100644 --- a/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt +++ b/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.library import android.content.Context +import android.util.AttributeSet import android.view.LayoutInflater import android.widget.ImageButton import android.widget.ImageView @@ -17,9 +18,12 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ext.loadIntoView -class LibrarySiteItemView( - context: Context -) : ConstraintLayout(context) { +class LibrarySiteItemView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) { val titleView: TextView get() = title 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 31f4cea46..7c4047b86 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 @@ -8,20 +8,19 @@ 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 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.library.LibrarySiteItemView +import org.mozilla.fenix.library.bookmarks.viewholders.BookmarkFolderViewHolder +import org.mozilla.fenix.library.bookmarks.viewholders.BookmarkItemViewHolder +import org.mozilla.fenix.library.bookmarks.viewholders.BookmarkNodeViewHolder +import org.mozilla.fenix.library.bookmarks.viewholders.BookmarkSeparatorViewHolder class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteractor) : - RecyclerView.Adapter() { + RecyclerView.Adapter() { private var tree: List = listOf() private var mode: BookmarkState.Mode = BookmarkState.Mode.Normal @@ -106,146 +105,6 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto tree[position] in selected ) } - - abstract class BookmarkNodeViewHolder( - val view: LibrarySiteItemView, - val interactor: BookmarkViewInteractor - ) : - RecyclerView.ViewHolder(view), LayoutContainer { - - override val containerView get() = view - - abstract fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) - - 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) - } - } - - view.overflowView.setOnClickListener { - bookmarkItemMenu.menuBuilder.build(view.context).show( - anchor = it, - orientation = BrowserMenu.Orientation.DOWN - ) - } - } - } - - 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 - } - } - } - - 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) - } 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 - } - } - } - - 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) - } - } } fun BookmarkNode.inRoots() = enumValues().any { it.id == guid } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkFolderViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkFolderViewHolder.kt new file mode 100644 index 000000000..524ab1734 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkFolderViewHolder.kt @@ -0,0 +1,64 @@ +/* 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.viewholders + +import android.view.View +import androidx.core.content.ContextCompat +import mozilla.components.concept.storage.BookmarkNode +import org.jetbrains.anko.image +import org.mozilla.fenix.R +import org.mozilla.fenix.library.LibrarySiteItemView +import org.mozilla.fenix.library.bookmarks.BookmarkState +import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor +import org.mozilla.fenix.library.bookmarks.inRoots + +/** + * Represents a folder with other bookmarks inside. + */ +class BookmarkFolderViewHolder( + view: LibrarySiteItemView, + interactor: BookmarkViewInteractor +) : BookmarkNodeViewHolder(view, interactor) { + + override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) { + + containerView.displayAs(LibrarySiteItemView.ItemType.FOLDER) + + setClickListeners(mode, item, selected) + + if (!item.inRoots()) { + setupMenu(item) + } else { + containerView.overflowView.visibility = View.GONE + } + + containerView.changeSelected(selected) + containerView.iconView.image = containerView.context.getDrawable(R.drawable.ic_folder_icon)?.apply { + setTint(ContextCompat.getColor(containerView.context, R.color.primary_text_light_theme)) + } + containerView.titleView.text = item.title + } + + private fun setClickListeners( + mode: BookmarkState.Mode, + item: BookmarkNode, + selected: Boolean + ) { + containerView.setOnClickListener { + when { + mode == BookmarkState.Mode.Normal -> interactor.expand(item) + selected -> interactor.deselect(item) + else -> interactor.select(item) + } + } + + containerView.setOnLongClickListener { + if (mode == BookmarkState.Mode.Normal && !item.inRoots()) { + interactor.select(item) + true + } else false + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkItemViewHolder.kt new file mode 100644 index 000000000..3944bb42f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkItemViewHolder.kt @@ -0,0 +1,61 @@ +/* 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.viewholders + +import mozilla.components.concept.storage.BookmarkNode +import org.mozilla.fenix.library.LibrarySiteItemView +import org.mozilla.fenix.library.bookmarks.BookmarkState +import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor + +/** + * Represents a bookmarked website in the bookmarks page. + */ +class BookmarkItemViewHolder( + view: LibrarySiteItemView, + interactor: BookmarkViewInteractor +) : BookmarkNodeViewHolder(view, interactor) { + + override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) { + + containerView.displayAs(LibrarySiteItemView.ItemType.SITE) + + setupMenu(item) + containerView.titleView.text = if (item.title.isNullOrBlank()) item.url else item.title + containerView.urlView.text = item.url + + setClickListeners(mode, item, selected) + containerView.changeSelected(selected) + setColorsAndIcons(item.url) + } + + private fun setColorsAndIcons(url: String?) { + if (url != null && url.startsWith("http")) { + containerView.loadFavicon(url) + } else { + containerView.iconView.setImageDrawable(null) + } + } + + private fun setClickListeners( + mode: BookmarkState.Mode, + item: BookmarkNode, + selected: Boolean + ) { + containerView.setOnClickListener { + when { + mode == BookmarkState.Mode.Normal -> interactor.open(item) + selected -> interactor.deselect(item) + else -> interactor.select(item) + } + } + + containerView.setOnLongClickListener { + if (mode == BookmarkState.Mode.Normal) { + interactor.select(item) + true + } else false + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkNodeViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkNodeViewHolder.kt new file mode 100644 index 000000000..15dc36431 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkNodeViewHolder.kt @@ -0,0 +1,47 @@ +/* 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.viewholders + +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.extensions.LayoutContainer +import mozilla.components.browser.menu.BrowserMenu +import mozilla.components.concept.storage.BookmarkNode +import org.mozilla.fenix.library.LibrarySiteItemView +import org.mozilla.fenix.library.bookmarks.BookmarkItemMenu +import org.mozilla.fenix.library.bookmarks.BookmarkState +import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor + +/** + * Base class for bookmark node view holders. + */ +abstract class BookmarkNodeViewHolder( + override val containerView: LibrarySiteItemView, + val interactor: BookmarkViewInteractor +) : + RecyclerView.ViewHolder(containerView), LayoutContainer { + + abstract fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) + + protected fun setupMenu(item: BookmarkNode) { + val bookmarkItemMenu = BookmarkItemMenu(containerView.context, item) { + when (it) { + BookmarkItemMenu.Item.Edit -> interactor.edit(item) + BookmarkItemMenu.Item.Select -> interactor.select(item) + BookmarkItemMenu.Item.Copy -> interactor.copy(item) + BookmarkItemMenu.Item.Share -> interactor.share(item) + BookmarkItemMenu.Item.OpenInNewTab -> interactor.openInNewTab(item) + BookmarkItemMenu.Item.OpenInPrivateTab -> interactor.openInPrivateTab(item) + BookmarkItemMenu.Item.Delete -> interactor.delete(item) + } + } + + containerView.overflowView.setOnClickListener { + bookmarkItemMenu.menuBuilder.build(containerView.context).show( + anchor = it, + orientation = BrowserMenu.Orientation.DOWN + ) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkSeparatorViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkSeparatorViewHolder.kt new file mode 100644 index 000000000..2cebc3be9 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/viewholders/BookmarkSeparatorViewHolder.kt @@ -0,0 +1,24 @@ +/* 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.viewholders + +import mozilla.components.concept.storage.BookmarkNode +import org.mozilla.fenix.library.LibrarySiteItemView +import org.mozilla.fenix.library.bookmarks.BookmarkState +import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor + +/** + * Simple view holder for dividers in the bookmarks list. + */ +class BookmarkSeparatorViewHolder( + view: LibrarySiteItemView, + interactor: BookmarkViewInteractor +) : BookmarkNodeViewHolder(view, interactor) { + + override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) { + containerView.displayAs(LibrarySiteItemView.ItemType.SEPARATOR) + setupMenu(item) + } +} 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 57eb7bc7d..d44e4b951 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 @@ -5,14 +5,10 @@ package org.mozilla.fenix.library.history.viewholders import android.view.View -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.history.HistoryInteractor import org.mozilla.fenix.library.history.HistoryItem import org.mozilla.fenix.library.history.HistoryItemMenu @@ -20,52 +16,29 @@ import org.mozilla.fenix.library.history.HistoryItemTimeGroup import org.mozilla.fenix.library.history.HistoryState class HistoryListItemViewHolder( - private val view: View, + view: View, private val historyInteractor: HistoryInteractor ) : RecyclerView.ViewHolder(view) { - private val layout = view.history_layout - 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 val headerWrapper = view.header_wrapper - private val headerTitle = view.header_title - private val deleteButtonWrapper = view.delete_button_wrapper - private val deleteButton = view.delete_button - private var item: HistoryItem? = null - private lateinit var historyMenu: HistoryItemMenu private var mode: HistoryState.Mode = HistoryState.Mode.Normal init { setupMenu() - layout.setOnLongClickListener { - item?.apply { - historyInteractor.onItemLongPress(this) - } - + itemView.history_layout.setOnLongClickListener { + item?.also(historyInteractor::onItemLongPress) true } - menuButton.setOnClickListener { - historyMenu.menuBuilder.build(view.context).show( - anchor = it, - orientation = BrowserMenu.Orientation.DOWN - ) - } - itemView.history_layout.setOnClickListener { item?.also(historyInteractor::onItemPress) } - deleteButton.setOnClickListener { - mode?.also { - when (it) { - is HistoryState.Mode.Normal -> historyInteractor.onDeleteAll() - is HistoryState.Mode.Editing -> historyInteractor.onDeleteSome(it.selectedItems) - } + itemView.delete_button.setOnClickListener { + when (val mode = this.mode) { + HistoryState.Mode.Normal -> historyInteractor.onDeleteAll() + is HistoryState.Mode.Editing -> historyInteractor.onDeleteSome(mode.selectedItems) } } } @@ -73,76 +46,42 @@ class HistoryListItemViewHolder( fun bind( item: HistoryItem, timeGroup: HistoryItemTimeGroup?, - showDeletebutton: Boolean, + showDeleteButton: Boolean, mode: HistoryState.Mode ) { this.item = item this.mode = mode - title.text = item.title - url.text = item.url + itemView.history_layout.titleView.text = item.title + itemView.history_layout.urlView.text = item.url - toggleDeleteButton(showDeletebutton, mode) + toggleDeleteButton(showDeleteButton, mode) - val headerText = timeGroup?.let { it.humanReadable(view.context) } + val headerText = timeGroup?.humanReadable(itemView.context) toggleHeader(headerText) - val selected = toggleSelected(mode, item) - - 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) - } - } - - private fun toggleSelected( - mode: HistoryState.Mode, - item: HistoryItem - ): Boolean { - return when (mode) { - is HistoryState.Mode.Editing -> mode.selectedItems.contains(item) - else -> false - } + itemView.history_layout.changeSelected(item in mode.selectedItems) + itemView.history_layout.loadFavicon(item.url) } private fun toggleHeader(text: String?) { - text?.also { - headerWrapper.visibility = View.VISIBLE - headerTitle.text = it - } ?: run { - headerWrapper.visibility = View.GONE + if (text != null) { + itemView.header_title.visibility = View.VISIBLE + itemView.header_title.text = text + } else { + itemView.header_title.visibility = View.GONE } } private fun toggleDeleteButton( - showDeletebutton: Boolean, + showDeleteButton: Boolean, mode: HistoryState.Mode ) { - if (showDeletebutton) { - deleteButtonWrapper.visibility = View.VISIBLE + if (showDeleteButton) { + itemView.delete_button.run { + visibility = View.VISIBLE - deleteButton.run { - val isDeleting = mode is HistoryState.Mode.Deleting - if (isDeleting || mode is HistoryState.Mode.Editing && mode.selectedItems.isNotEmpty()) { + if (mode === HistoryState.Mode.Deleting || mode.selectedItems.isNotEmpty()) { isEnabled = false alpha = DELETE_BUTTON_DISABLED_ALPHA } else { @@ -151,22 +90,23 @@ class HistoryListItemViewHolder( } } } else { - deleteButtonWrapper.visibility = View.GONE + itemView.delete_button.visibility = View.GONE } } private fun setupMenu() { - this.historyMenu = HistoryItemMenu(itemView.context) { + val historyMenu = HistoryItemMenu(itemView.context) { when (it) { - is HistoryItemMenu.Item.Delete -> { - item?.apply { historyInteractor.onDeleteOne(this) } - } + HistoryItemMenu.Item.Delete -> item?.also(historyInteractor::onDeleteOne) } } - } - private fun updateFavIcon(url: String) { - favicon.context.components.core.icons.loadIntoView(favicon, url) + itemView.history_layout.overflowView.setOnClickListener { + historyMenu.menuBuilder.build(itemView.context).show( + anchor = it, + orientation = BrowserMenu.Orientation.DOWN + ) + } } companion object { diff --git a/app/src/main/res/layout/history_list_item.xml b/app/src/main/res/layout/history_list_item.xml index 05e414138..fe60e4b64 100644 --- a/app/src/main/res/layout/history_list_item.xml +++ b/app/src/main/res/layout/history_list_item.xml @@ -3,103 +3,38 @@ - 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/. --> - - + + - + app:rippleColor="?secondaryText" + android:visibility="gone" /> - - - - - - - - - - - - - - + android:paddingStart="20dp" + android:paddingEnd="0dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="8dp" + tools:text="Header" + android:visibility="gone" /> + +