mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-26 11:26:01 +03:00
Improve EmojiChooserFragment: improve filtering result: sort
This commit is contained in:
parent
f00f34b244
commit
3c18fd5335
6 changed files with 78 additions and 98 deletions
|
@ -95,20 +95,18 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
|
|||
|
||||
viewModel.eventId = intent.getStringExtra(EXTRA_EVENT_ID)
|
||||
|
||||
emojiDataSource.rawData?.categories?.let { categories ->
|
||||
for (category in categories) {
|
||||
val s = category.emojis[0]
|
||||
tabLayout.newTab()
|
||||
.also { tab ->
|
||||
tab.text = emojiDataSource.rawData!!.emojis[s]!!.emoji
|
||||
tab.contentDescription = category.name
|
||||
}
|
||||
.also { tab ->
|
||||
tabLayout.addTab(tab)
|
||||
}
|
||||
}
|
||||
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
|
||||
emojiDataSource.rawData.categories.forEach { category ->
|
||||
val s = category.emojis[0]
|
||||
tabLayout.newTab()
|
||||
.also { tab ->
|
||||
tab.text = emojiDataSource.rawData.emojis[s]!!.emoji
|
||||
tab.contentDescription = category.name
|
||||
}
|
||||
.also { tab ->
|
||||
tabLayout.addTab(tab)
|
||||
}
|
||||
}
|
||||
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
|
||||
|
||||
viewModel.currentSection.observe(this, Observer { section ->
|
||||
section?.let {
|
||||
|
|
|
@ -40,12 +40,11 @@ import kotlin.math.abs
|
|||
/**
|
||||
*
|
||||
* TODO: Configure Span using available width and emoji size
|
||||
* TODO: Search
|
||||
* TODO: Performances
|
||||
* TODO: Scroll to section - Find a way to snap section to the top
|
||||
*/
|
||||
class EmojiRecyclerAdapter @Inject constructor(
|
||||
private val dataSource: EmojiDataSource?
|
||||
private val dataSource: EmojiDataSource
|
||||
) :
|
||||
RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() {
|
||||
|
||||
|
@ -70,13 +69,12 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
private val itemClickListener = View.OnClickListener { view ->
|
||||
mRecyclerView?.getChildLayoutPosition(view)?.let { itemPosition ->
|
||||
if (itemPosition != RecyclerView.NO_POSITION) {
|
||||
val categories = dataSource?.rawData?.categories ?: return@OnClickListener
|
||||
val sectionNumber = getSectionForAbsoluteIndex(itemPosition)
|
||||
if (!isSection(itemPosition)) {
|
||||
val sectionMojis = categories[sectionNumber].emojis
|
||||
val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
|
||||
val sectionOffset = getSectionOffset(sectionNumber)
|
||||
val emoji = sectionMojis[itemPosition - sectionOffset]
|
||||
val item = dataSource.rawData!!.emojis.getValue(emoji).emoji
|
||||
val item = dataSource.rawData.emojis.getValue(emoji).emoji
|
||||
reactionClickListener?.onReactionSelected(item)
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +115,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
}
|
||||
|
||||
fun scrollToSection(section: Int) {
|
||||
if (section < 0 || section >= dataSource?.rawData?.categories?.size ?: 0) {
|
||||
if (section < 0 || section >= dataSource.rawData.categories.size) {
|
||||
// ignore
|
||||
return
|
||||
}
|
||||
|
@ -149,14 +147,12 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
}
|
||||
|
||||
private fun isSection(position: Int): Boolean {
|
||||
dataSource?.rawData?.categories?.let { categories ->
|
||||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
for (category in categories) {
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position == sectionOffset - 1) return true
|
||||
sectionOffset = lastItemInSection + 2
|
||||
}
|
||||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
dataSource.rawData.categories.forEach { category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position == sectionOffset - 1) return true
|
||||
sectionOffset = lastItemInSection + 2
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -165,13 +161,11 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
var index = 0
|
||||
dataSource?.rawData?.categories?.let {
|
||||
for (category in it) {
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position <= lastItemInSection) return index
|
||||
sectionOffset = lastItemInSection + 2
|
||||
index++
|
||||
}
|
||||
dataSource.rawData.categories.forEach { category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position <= lastItemInSection) return index
|
||||
sectionOffset = lastItemInSection + 2
|
||||
index++
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
@ -180,36 +174,32 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
// Todo cache this for fast access
|
||||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
dataSource?.rawData?.categories?.let {
|
||||
for ((index, category) in it.withIndex()) {
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (section == index) return sectionOffset
|
||||
sectionOffset = lastItemInSection + 2
|
||||
}
|
||||
dataSource.rawData.categories.forEachIndexed { index, category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (section == index) return sectionOffset
|
||||
sectionOffset = lastItemInSection + 2
|
||||
}
|
||||
return sectionOffset
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
beginTraceSession("MyAdapter.onBindViewHolder")
|
||||
dataSource?.rawData?.categories?.let { categories ->
|
||||
val sectionNumber = getSectionForAbsoluteIndex(position)
|
||||
if (isSection(position)) {
|
||||
holder.bind(categories[sectionNumber].name)
|
||||
} else {
|
||||
val sectionMojis = categories[sectionNumber].emojis
|
||||
val sectionOffset = getSectionOffset(sectionNumber)
|
||||
val emoji = sectionMojis[position - sectionOffset]
|
||||
val item = dataSource.rawData!!.emojis[emoji]!!.emoji
|
||||
(holder as EmojiViewHolder).data = item
|
||||
if (scrollState != ScrollState.SETTLING || !isFastScroll) {
|
||||
val sectionNumber = getSectionForAbsoluteIndex(position)
|
||||
if (isSection(position)) {
|
||||
holder.bind(dataSource.rawData.categories[sectionNumber].name)
|
||||
} else {
|
||||
val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
|
||||
val sectionOffset = getSectionOffset(sectionNumber)
|
||||
val emoji = sectionMojis[position - sectionOffset]
|
||||
val item = dataSource.rawData.emojis[emoji]!!.emoji
|
||||
(holder as EmojiViewHolder).data = item
|
||||
if (scrollState != ScrollState.SETTLING || !isFastScroll) {
|
||||
// Log.i("PERF","Bind with draw at position:$position")
|
||||
holder.bind(item)
|
||||
} else {
|
||||
holder.bind(item)
|
||||
} else {
|
||||
// Log.i("PERF","Bind without draw at position:$position")
|
||||
toUpdateWhenNotBusy.add(item to holder)
|
||||
holder.bind(null)
|
||||
}
|
||||
toUpdateWhenNotBusy.add(item to holder)
|
||||
holder.bind(null)
|
||||
}
|
||||
}
|
||||
endTraceSession()
|
||||
|
@ -230,15 +220,8 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
super.onViewRecycled(holder)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return dataSource?.rawData?.categories?.let {
|
||||
var count = /*number of sections*/ it.size
|
||||
for (ad in it) {
|
||||
count += ad.emojis.size
|
||||
}
|
||||
count
|
||||
} ?: 0
|
||||
}
|
||||
override fun getItemCount() = dataSource.rawData.categories
|
||||
.sumBy { emojiCategory -> 1 /* Section */ + emojiCategory.emojis.size }
|
||||
|
||||
abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
abstract fun bind(s: String?)
|
||||
|
|
|
@ -46,7 +46,7 @@ abstract class EmojiSearchResultItem : EpoxyModelWithHolder<EmojiSearchResultIte
|
|||
holder.emojiText.text = emojiItem.emoji
|
||||
holder.emojiText.typeface = emojiTypeFace ?: Typeface.DEFAULT
|
||||
holder.emojiNameText.text = emojiItem.name
|
||||
holder.emojiKeywordText.setTextOrHide(emojiItem.keywords?.joinToString())
|
||||
holder.emojiKeywordText.setTextOrHide(emojiItem.keywords.joinToString())
|
||||
holder.view.setOnClickListener {
|
||||
onClickListener?.onReactionSelected(emojiItem.emoji)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
*/
|
||||
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.AssistedInject
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
|
@ -52,21 +55,26 @@ class EmojiSearchResultViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun updateQuery(action: EmojiSearchAction.UpdateQuery) {
|
||||
val words = action.queryString.split("\\s".toRegex())
|
||||
setState {
|
||||
copy(
|
||||
query = action.queryString,
|
||||
results = dataSource.rawData?.emojis?.toList()
|
||||
?.map { it.second }
|
||||
?.filter {
|
||||
it.name.contains(action.queryString, true)
|
||||
|| action.queryString
|
||||
.split("\\s".toRegex())
|
||||
.fold(true, { prev, q ->
|
||||
prev
|
||||
&& (it.keywords?.any { it.contains(q, true) }
|
||||
?: false)
|
||||
})
|
||||
} ?: emptyList()
|
||||
// First add emojis with name matching query, sorted by name
|
||||
// Then emojis with keyword matching any of the word in the query, sorted by name
|
||||
results = dataSource.rawData.emojis
|
||||
.values
|
||||
.filter { emojiItem ->
|
||||
emojiItem.name.contains(action.queryString, true)
|
||||
}
|
||||
.sortedBy { it.name }
|
||||
+ dataSource.rawData.emojis
|
||||
.values
|
||||
.filter { emojiItem ->
|
||||
words.fold(true, { prev, word ->
|
||||
prev && emojiItem.keywords.any { keyword -> keyword.contains(word, true) }
|
||||
})
|
||||
}
|
||||
.sortedBy { it.name }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,31 +15,22 @@
|
|||
*/
|
||||
package im.vector.riotx.features.reactions.data
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import com.squareup.moshi.Moshi
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
@ScreenScope
|
||||
class EmojiDataSource @Inject constructor(
|
||||
context: Context
|
||||
resources: Resources
|
||||
) {
|
||||
|
||||
var rawData: EmojiData? = null
|
||||
|
||||
init {
|
||||
measureTimeMillis {
|
||||
context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input ->
|
||||
val moshi = Moshi.Builder().build()
|
||||
val jsonAdapter = moshi.adapter(EmojiData::class.java)
|
||||
val inputAsString = input.bufferedReader().use { it.readText() }
|
||||
this.rawData = jsonAdapter.fromJson(inputAsString)
|
||||
val rawData = resources.openRawResource(R.raw.emoji_picker_datasource)
|
||||
.use { input ->
|
||||
Moshi.Builder()
|
||||
.build()
|
||||
.adapter(EmojiData::class.java)
|
||||
.fromJson(input.bufferedReader().use { it.readText() })
|
||||
}
|
||||
}.also {
|
||||
Timber.e("Emoji: $it millis")
|
||||
}
|
||||
}
|
||||
?: EmojiData(emptyList(), emptyMap(), emptyMap())
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ import com.squareup.moshi.JsonClass
|
|||
data class EmojiItem(
|
||||
@Json(name = "a") val name: 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...
|
||||
var cache: String? = null
|
||||
|
|
Loading…
Reference in a new issue