You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
6.3 KiB
Kotlin
160 lines
6.3 KiB
Kotlin
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.fenix.library.bookmarks
|
|
|
|
import android.view.LayoutInflater
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import androidx.annotation.VisibleForTesting
|
|
import androidx.core.view.isVisible
|
|
import androidx.recyclerview.widget.DiffUtil
|
|
import androidx.recyclerview.widget.RecyclerView
|
|
import mozilla.appservices.places.BookmarkRoot
|
|
import mozilla.components.concept.storage.BookmarkNode
|
|
import mozilla.components.concept.storage.BookmarkNodeType
|
|
import org.mozilla.fenix.library.LibrarySiteItemView
|
|
import org.mozilla.fenix.library.bookmarks.viewholders.BookmarkNodeViewHolder
|
|
import org.mozilla.fenix.library.bookmarks.viewholders.BookmarkSeparatorViewHolder
|
|
|
|
class BookmarkAdapter(private val emptyView: View, private val interactor: BookmarkViewInteractor) :
|
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
|
|
@VisibleForTesting var tree: List<BookmarkNode> = listOf()
|
|
private var mode: BookmarkFragmentState.Mode = BookmarkFragmentState.Mode.Normal()
|
|
private var isFirstRun = true
|
|
|
|
fun updateData(tree: BookmarkNode?, mode: BookmarkFragmentState.Mode) {
|
|
val allNodes = tree?.children.orEmpty()
|
|
val folders: MutableList<BookmarkNode> = mutableListOf()
|
|
val notFolders: MutableList<BookmarkNode> = mutableListOf()
|
|
val separators: MutableList<BookmarkNode> = mutableListOf()
|
|
allNodes.forEach {
|
|
when (it.type) {
|
|
BookmarkNodeType.SEPARATOR -> separators.add(it)
|
|
BookmarkNodeType.FOLDER -> folders.add(it)
|
|
else -> notFolders.add(it)
|
|
}
|
|
}
|
|
// Display folders above all other bookmarks. Exclude separators.
|
|
// For separator removal, see discussion in https://github.com/mozilla-mobile/fenix/issues/15214
|
|
val newTree = folders + notFolders - separators
|
|
|
|
val diffUtil = DiffUtil.calculateDiff(
|
|
BookmarkDiffUtil(
|
|
this.tree,
|
|
newTree,
|
|
this.mode,
|
|
mode
|
|
)
|
|
)
|
|
|
|
this.tree = newTree
|
|
|
|
isFirstRun = if (isFirstRun) false else {
|
|
emptyView.isVisible = this.tree.isEmpty()
|
|
false
|
|
}
|
|
this.mode = mode
|
|
|
|
diffUtil.dispatchUpdatesTo(this)
|
|
}
|
|
|
|
@VisibleForTesting
|
|
internal class BookmarkDiffUtil(
|
|
val old: List<BookmarkNode>,
|
|
val new: List<BookmarkNode>,
|
|
val oldMode: BookmarkFragmentState.Mode,
|
|
val newMode: BookmarkFragmentState.Mode
|
|
) : DiffUtil.Callback() {
|
|
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
|
old[oldItemPosition].guid == new[newItemPosition].guid
|
|
|
|
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
|
oldMode::class == newMode::class &&
|
|
old[oldItemPosition] in oldMode.selectedItems == new[newItemPosition] in newMode.selectedItems &&
|
|
old[oldItemPosition] == new[newItemPosition]
|
|
|
|
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
|
|
val oldItem = old[oldItemPosition]
|
|
val newItem = new[newItemPosition]
|
|
return BookmarkPayload(
|
|
titleChanged = oldItem.title != newItem.title,
|
|
urlChanged = oldItem.url != newItem.url,
|
|
selectedChanged = oldItem in oldMode.selectedItems != newItem in newMode.selectedItems,
|
|
modeChanged = oldMode::class != newMode::class,
|
|
iconChanged = oldItem.type != newItem.type || oldItem.url != newItem.url
|
|
)
|
|
}
|
|
|
|
override fun getOldListSize(): Int = old.size
|
|
override fun getNewListSize(): Int = new.size
|
|
}
|
|
|
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
|
|
|
return when (viewType) {
|
|
BookmarkNodeViewHolder.LAYOUT_ID ->
|
|
BookmarkNodeViewHolder(view as LibrarySiteItemView, interactor)
|
|
BookmarkSeparatorViewHolder.LAYOUT_ID ->
|
|
BookmarkSeparatorViewHolder(view)
|
|
else -> throw IllegalStateException("ViewType $viewType does not match to a ViewHolder")
|
|
}
|
|
}
|
|
|
|
override fun getItemViewType(position: Int) = when (tree[position].type) {
|
|
BookmarkNodeType.ITEM, BookmarkNodeType.FOLDER -> BookmarkNodeViewHolder.LAYOUT_ID
|
|
BookmarkNodeType.SEPARATOR -> BookmarkSeparatorViewHolder.LAYOUT_ID
|
|
}
|
|
|
|
override fun getItemCount(): Int = tree.size
|
|
|
|
override fun onBindViewHolder(
|
|
holder: RecyclerView.ViewHolder,
|
|
position: Int,
|
|
payloads: MutableList<Any>
|
|
) {
|
|
(holder as? BookmarkNodeViewHolder)?.apply {
|
|
val diffPayload = if (payloads.isNotEmpty() && payloads[0] is BookmarkPayload) {
|
|
payloads[0] as BookmarkPayload
|
|
} else {
|
|
BookmarkPayload()
|
|
}
|
|
bind(tree[position], mode, diffPayload)
|
|
}
|
|
}
|
|
|
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
(holder as? BookmarkNodeViewHolder)?.bind(tree[position], mode, BookmarkPayload())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A RecyclerView Adapter payload class that contains information about changes to a [BookmarkNode].
|
|
*
|
|
* @property titleChanged true if there has been a change to [BookmarkNode.title].
|
|
* @property urlChanged true if there has been a change to [BookmarkNode.url].
|
|
* @property selectedChanged true if there has been a change in the BookmarkNode's selected state.
|
|
* @property modeChanged true if there has been a change in the state's mode type.
|
|
* @property iconChanged true if the icon displayed for the node should be changed.
|
|
*/
|
|
data class BookmarkPayload(
|
|
val titleChanged: Boolean,
|
|
val urlChanged: Boolean,
|
|
val selectedChanged: Boolean,
|
|
val modeChanged: Boolean,
|
|
val iconChanged: Boolean
|
|
) {
|
|
constructor() : this(
|
|
titleChanged = true,
|
|
urlChanged = true,
|
|
selectedChanged = true,
|
|
modeChanged = true,
|
|
iconChanged = true
|
|
)
|
|
}
|
|
|
|
fun BookmarkNode.inRoots() = enumValues<BookmarkRoot>().any { it.id == guid }
|