mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 21:48:50 +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,12 +95,11 @@ 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 ->
|
||||||
|
@ -108,7 +107,6 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
|
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.currentSection.observe(this, Observer { section ->
|
viewModel.currentSection.observe(this, Observer { section ->
|
||||||
section?.let {
|
section?.let {
|
||||||
|
|
|
@ -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,15 +147,13 @@ 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
|
||||||
for (category in categories) {
|
dataSource.rawData.categories.forEach { category ->
|
||||||
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,14 +161,12 @@ 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,27 +174,24 @@ 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(categories[sectionNumber].name)
|
holder.bind(dataSource.rawData.categories[sectionNumber].name)
|
||||||
} else {
|
} else {
|
||||||
val sectionMojis = categories[sectionNumber].emojis
|
val sectionMojis = dataSource.rawData.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")
|
||||||
|
@ -211,7 +202,6 @@ class EmojiRecyclerAdapter @Inject constructor(
|
||||||
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?)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ->
|
||||||
|
words.fold(true, { prev, word ->
|
||||||
|
prev && emojiItem.keywords.any { keyword -> keyword.contains(word, true) }
|
||||||
})
|
})
|
||||||
} ?: emptyList()
|
}
|
||||||
|
.sortedBy { it.name }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
|
||||||
Timber.e("Emoji: $it millis")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
?: EmojiData(emptyList(), emptyMap(), emptyMap())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue