Improve EmojiChooserFragment: improve filtering result: sort

This commit is contained in:
Benoit Marty 2019-12-10 00:42:24 +01:00
parent f00f34b244
commit 3c18fd5335
6 changed files with 78 additions and 98 deletions

View file

@ -95,20 +95,18 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
viewModel.eventId = intent.getStringExtra(EXTRA_EVENT_ID) viewModel.eventId = intent.getStringExtra(EXTRA_EVENT_ID)
emojiDataSource.rawData?.categories?.let { categories -> emojiDataSource.rawData.categories.forEach { category ->
for (category in categories) { val s = category.emojis[0]
val s = category.emojis[0] tabLayout.newTab()
tabLayout.newTab() .also { tab ->
.also { tab -> tab.text = emojiDataSource.rawData.emojis[s]!!.emoji
tab.text = emojiDataSource.rawData!!.emojis[s]!!.emoji tab.contentDescription = category.name
tab.contentDescription = category.name }
} .also { tab ->
.also { tab -> tabLayout.addTab(tab)
tabLayout.addTab(tab) }
}
}
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
} }
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
viewModel.currentSection.observe(this, Observer { section -> viewModel.currentSection.observe(this, Observer { section ->
section?.let { section?.let {

View file

@ -40,12 +40,11 @@ import kotlin.math.abs
/** /**
* *
* TODO: Configure Span using available width and emoji size * TODO: Configure Span using available width and emoji size
* TODO: Search
* TODO: Performances * TODO: Performances
* TODO: Scroll to section - Find a way to snap section to the top * TODO: Scroll to section - Find a way to snap section to the top
*/ */
class EmojiRecyclerAdapter @Inject constructor( class EmojiRecyclerAdapter @Inject constructor(
private val dataSource: EmojiDataSource? private val dataSource: EmojiDataSource
) : ) :
RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() { RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() {
@ -70,13 +69,12 @@ class EmojiRecyclerAdapter @Inject constructor(
private val itemClickListener = View.OnClickListener { view -> private val itemClickListener = View.OnClickListener { view ->
mRecyclerView?.getChildLayoutPosition(view)?.let { itemPosition -> mRecyclerView?.getChildLayoutPosition(view)?.let { itemPosition ->
if (itemPosition != RecyclerView.NO_POSITION) { if (itemPosition != RecyclerView.NO_POSITION) {
val categories = dataSource?.rawData?.categories ?: return@OnClickListener
val sectionNumber = getSectionForAbsoluteIndex(itemPosition) val sectionNumber = getSectionForAbsoluteIndex(itemPosition)
if (!isSection(itemPosition)) { if (!isSection(itemPosition)) {
val sectionMojis = categories[sectionNumber].emojis val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
val sectionOffset = getSectionOffset(sectionNumber) val sectionOffset = getSectionOffset(sectionNumber)
val emoji = sectionMojis[itemPosition - sectionOffset] val emoji = sectionMojis[itemPosition - sectionOffset]
val item = dataSource.rawData!!.emojis.getValue(emoji).emoji val item = dataSource.rawData.emojis.getValue(emoji).emoji
reactionClickListener?.onReactionSelected(item) reactionClickListener?.onReactionSelected(item)
} }
} }
@ -117,7 +115,7 @@ class EmojiRecyclerAdapter @Inject constructor(
} }
fun scrollToSection(section: Int) { fun scrollToSection(section: Int) {
if (section < 0 || section >= dataSource?.rawData?.categories?.size ?: 0) { if (section < 0 || section >= dataSource.rawData.categories.size) {
// ignore // ignore
return return
} }
@ -149,14 +147,12 @@ class EmojiRecyclerAdapter @Inject constructor(
} }
private fun isSection(position: Int): Boolean { private fun isSection(position: Int): Boolean {
dataSource?.rawData?.categories?.let { categories -> var sectionOffset = 1
var sectionOffset = 1 var lastItemInSection: Int
var lastItemInSection: Int dataSource.rawData.categories.forEach { category ->
for (category in categories) { lastItemInSection = sectionOffset + category.emojis.size - 1
lastItemInSection = sectionOffset + category.emojis.size - 1 if (position == sectionOffset - 1) return true
if (position == sectionOffset - 1) return true sectionOffset = lastItemInSection + 2
sectionOffset = lastItemInSection + 2
}
} }
return false return false
} }
@ -165,13 +161,11 @@ class EmojiRecyclerAdapter @Inject constructor(
var sectionOffset = 1 var sectionOffset = 1
var lastItemInSection: Int var lastItemInSection: Int
var index = 0 var index = 0
dataSource?.rawData?.categories?.let { dataSource.rawData.categories.forEach { category ->
for (category in it) { lastItemInSection = sectionOffset + category.emojis.size - 1
lastItemInSection = sectionOffset + category.emojis.size - 1 if (position <= lastItemInSection) return index
if (position <= lastItemInSection) return index sectionOffset = lastItemInSection + 2
sectionOffset = lastItemInSection + 2 index++
index++
}
} }
return index return index
} }
@ -180,36 +174,32 @@ class EmojiRecyclerAdapter @Inject constructor(
// Todo cache this for fast access // Todo cache this for fast access
var sectionOffset = 1 var sectionOffset = 1
var lastItemInSection: Int var lastItemInSection: Int
dataSource?.rawData?.categories?.let { dataSource.rawData.categories.forEachIndexed { index, category ->
for ((index, category) in it.withIndex()) { lastItemInSection = sectionOffset + category.emojis.size - 1
lastItemInSection = sectionOffset + category.emojis.size - 1 if (section == index) return sectionOffset
if (section == index) return sectionOffset sectionOffset = lastItemInSection + 2
sectionOffset = lastItemInSection + 2
}
} }
return sectionOffset return sectionOffset
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
beginTraceSession("MyAdapter.onBindViewHolder") beginTraceSession("MyAdapter.onBindViewHolder")
dataSource?.rawData?.categories?.let { categories -> val sectionNumber = getSectionForAbsoluteIndex(position)
val sectionNumber = getSectionForAbsoluteIndex(position) if (isSection(position)) {
if (isSection(position)) { holder.bind(dataSource.rawData.categories[sectionNumber].name)
holder.bind(categories[sectionNumber].name) } else {
} else { val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
val sectionMojis = categories[sectionNumber].emojis val sectionOffset = getSectionOffset(sectionNumber)
val sectionOffset = getSectionOffset(sectionNumber) val emoji = sectionMojis[position - sectionOffset]
val emoji = sectionMojis[position - sectionOffset] val item = dataSource.rawData.emojis[emoji]!!.emoji
val item = dataSource.rawData!!.emojis[emoji]!!.emoji (holder as EmojiViewHolder).data = item
(holder as EmojiViewHolder).data = item if (scrollState != ScrollState.SETTLING || !isFastScroll) {
if (scrollState != ScrollState.SETTLING || !isFastScroll) {
// Log.i("PERF","Bind with draw at position:$position") // Log.i("PERF","Bind with draw at position:$position")
holder.bind(item) holder.bind(item)
} else { } else {
// Log.i("PERF","Bind without draw at position:$position") // Log.i("PERF","Bind without draw at position:$position")
toUpdateWhenNotBusy.add(item to holder) toUpdateWhenNotBusy.add(item to holder)
holder.bind(null) holder.bind(null)
}
} }
} }
endTraceSession() endTraceSession()
@ -230,15 +220,8 @@ class EmojiRecyclerAdapter @Inject constructor(
super.onViewRecycled(holder) super.onViewRecycled(holder)
} }
override fun getItemCount(): Int { override fun getItemCount() = dataSource.rawData.categories
return dataSource?.rawData?.categories?.let { .sumBy { emojiCategory -> 1 /* Section */ + emojiCategory.emojis.size }
var count = /*number of sections*/ it.size
for (ad in it) {
count += ad.emojis.size
}
count
} ?: 0
}
abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(s: String?) abstract fun bind(s: String?)

View file

@ -46,7 +46,7 @@ abstract class EmojiSearchResultItem : EpoxyModelWithHolder<EmojiSearchResultIte
holder.emojiText.text = emojiItem.emoji holder.emojiText.text = emojiItem.emoji
holder.emojiText.typeface = emojiTypeFace ?: Typeface.DEFAULT holder.emojiText.typeface = emojiTypeFace ?: Typeface.DEFAULT
holder.emojiNameText.text = emojiItem.name holder.emojiNameText.text = emojiItem.name
holder.emojiKeywordText.setTextOrHide(emojiItem.keywords?.joinToString()) holder.emojiKeywordText.setTextOrHide(emojiItem.keywords.joinToString())
holder.view.setOnClickListener { holder.view.setOnClickListener {
onClickListener?.onReactionSelected(emojiItem.emoji) onClickListener?.onReactionSelected(emojiItem.emoji)
} }

View file

@ -15,7 +15,10 @@
*/ */
package im.vector.riotx.features.reactions package im.vector.riotx.features.reactions
import com.airbnb.mvrx.* import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
@ -52,21 +55,26 @@ class EmojiSearchResultViewModel @AssistedInject constructor(
} }
private fun updateQuery(action: EmojiSearchAction.UpdateQuery) { private fun updateQuery(action: EmojiSearchAction.UpdateQuery) {
val words = action.queryString.split("\\s".toRegex())
setState { setState {
copy( copy(
query = action.queryString, query = action.queryString,
results = dataSource.rawData?.emojis?.toList() // First add emojis with name matching query, sorted by name
?.map { it.second } // Then emojis with keyword matching any of the word in the query, sorted by name
?.filter { results = dataSource.rawData.emojis
it.name.contains(action.queryString, true) .values
|| action.queryString .filter { emojiItem ->
.split("\\s".toRegex()) emojiItem.name.contains(action.queryString, true)
.fold(true, { prev, q -> }
prev .sortedBy { it.name }
&& (it.keywords?.any { it.contains(q, true) } + dataSource.rawData.emojis
?: false) .values
}) .filter { emojiItem ->
} ?: emptyList() words.fold(true, { prev, word ->
prev && emojiItem.keywords.any { keyword -> keyword.contains(word, true) }
})
}
.sortedBy { it.name }
) )
} }
} }

View file

@ -15,31 +15,22 @@
*/ */
package im.vector.riotx.features.reactions.data package im.vector.riotx.features.reactions.data
import android.content.Context import android.content.res.Resources
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenScope import im.vector.riotx.core.di.ScreenScope
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis
@ScreenScope @ScreenScope
class EmojiDataSource @Inject constructor( class EmojiDataSource @Inject constructor(
context: Context resources: Resources
) { ) {
val rawData = resources.openRawResource(R.raw.emoji_picker_datasource)
var rawData: EmojiData? = null .use { input ->
Moshi.Builder()
init { .build()
measureTimeMillis { .adapter(EmojiData::class.java)
context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input -> .fromJson(input.bufferedReader().use { it.readText() })
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(EmojiData::class.java)
val inputAsString = input.bufferedReader().use { it.readText() }
this.rawData = jsonAdapter.fromJson(inputAsString)
} }
}.also { ?: EmojiData(emptyList(), emptyMap(), emptyMap())
Timber.e("Emoji: $it millis")
}
}
} }

View file

@ -40,7 +40,7 @@ import com.squareup.moshi.JsonClass
data class EmojiItem( data class EmojiItem(
@Json(name = "a") val name: String, @Json(name = "a") val name: String,
@Json(name = "b") val unicode: String, @Json(name = "b") val unicode: String,
@Json(name = "j") val keywords: List<String>? @Json(name = "j") val keywords: List<String> = emptyList()
) { ) {
// Cannot be private... // Cannot be private...
var cache: String? = null var cache: String? = null