118 lines
4.5 KiB
Kotlin
118 lines
4.5 KiB
Kotlin
/*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
|
*/
|
|
|
|
package emu.skyline.adapter
|
|
|
|
import android.view.ViewGroup
|
|
import android.widget.Filter
|
|
import android.widget.Filterable
|
|
import androidx.recyclerview.widget.AsyncListDiffer
|
|
import androidx.recyclerview.widget.DiffUtil
|
|
import androidx.recyclerview.widget.RecyclerView
|
|
import info.debatty.java.stringsimilarity.Cosine
|
|
import info.debatty.java.stringsimilarity.JaroWinkler
|
|
import java.util.*
|
|
|
|
/**
|
|
* Can handle any view types with [GenericListItem] implemented, [GenericListItem] are differentiated by the return value of [GenericListItem.getLayoutFactory]
|
|
*/
|
|
class GenericAdapter : RecyclerView.Adapter<GenericViewHolder>(), Filterable {
|
|
companion object {
|
|
private val DIFFER = object : DiffUtil.ItemCallback<GenericListItem>() {
|
|
override fun areItemsTheSame(oldItem : GenericListItem, newItem : GenericListItem) = oldItem.areItemsTheSame(newItem)
|
|
|
|
override fun areContentsTheSame(oldItem : GenericListItem, newItem : GenericListItem) = oldItem.areContentsTheSame(newItem)
|
|
}
|
|
}
|
|
|
|
private val asyncListDiffer = AsyncListDiffer(this, DIFFER)
|
|
private val allItems = mutableListOf<GenericListItem>()
|
|
val currentItems : List<GenericListItem> get() = asyncListDiffer.currentList
|
|
|
|
var currentSearchTerm = ""
|
|
|
|
private val viewTypesMapping = mutableMapOf<GenericLayoutFactory, Int>()
|
|
|
|
override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) = GenericViewHolder(viewTypesMapping.filterValues { it == viewType }.keys.single().createLayout(parent))
|
|
|
|
override fun onBindViewHolder(holder : GenericViewHolder, position : Int) {
|
|
currentItems[position].apply {
|
|
adapter = this@GenericAdapter
|
|
bind(holder, position)
|
|
}
|
|
}
|
|
|
|
override fun getItemCount() = currentItems.size
|
|
|
|
override fun getItemViewType(position : Int) = viewTypesMapping.getOrPut(currentItems[position].getLayoutFactory(), { viewTypesMapping.size })
|
|
|
|
fun setItems(items : List<GenericListItem>) {
|
|
allItems.clear()
|
|
allItems.addAll(items)
|
|
filter.filter(currentSearchTerm)
|
|
}
|
|
|
|
/**
|
|
* This returns an instance of the filter object which is used to search for items in the view
|
|
*/
|
|
override fun getFilter() = object : Filter() {
|
|
/**
|
|
* We use Jaro-Winkler distance for string similarity (https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance)
|
|
*/
|
|
private val jw = JaroWinkler()
|
|
|
|
/**
|
|
* We use Cosine similarity for string similarity (https://en.wikipedia.org/wiki/Cosine_similarity)
|
|
*/
|
|
private val cos = Cosine()
|
|
|
|
inner class ScoredItem(val score : Double, val item : GenericListItem)
|
|
|
|
/**
|
|
* This sorts the items in [allItems] in relation to how similar they are to [currentSearchTerm]
|
|
*/
|
|
fun extractSorted() = allItems.mapNotNull { item ->
|
|
item.key().toLowerCase(Locale.getDefault()).let {
|
|
val similarity = (jw.similarity(currentSearchTerm, it)) + cos.similarity(currentSearchTerm, it) / 2
|
|
if (similarity != 0.0) ScoredItem(similarity, item) else null
|
|
}
|
|
}.apply {
|
|
sortedByDescending { it.score }
|
|
}
|
|
|
|
/**
|
|
* This performs filtering on the items in [allItems] based on similarity to [term]
|
|
*/
|
|
override fun performFiltering(term : CharSequence) : FilterResults {
|
|
val results = FilterResults()
|
|
currentSearchTerm = (term as String).toLowerCase(Locale.getDefault())
|
|
|
|
if (term.isEmpty()) {
|
|
results.values = allItems.toMutableList()
|
|
results.count = allItems.size
|
|
} else {
|
|
val filterData = mutableListOf<GenericListItem>()
|
|
|
|
val topResults = extractSorted()
|
|
val avgScore = topResults.sumByDouble { it.score } / topResults.size
|
|
|
|
for (result in topResults)
|
|
if (result.score > avgScore) filterData.add(result.item)
|
|
|
|
results.values = filterData
|
|
results.count = filterData.size
|
|
}
|
|
return results
|
|
}
|
|
|
|
/**
|
|
* This publishes the results that were calculated in [performFiltering] to the view
|
|
*/
|
|
override fun publishResults(charSequence : CharSequence, results : FilterResults) {
|
|
@Suppress("UNCHECKED_CAST")
|
|
asyncListDiffer.submitList(results.values as List<GenericListItem>)
|
|
}
|
|
}
|
|
} |