diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt index 7c2a9349e0..96536e1f16 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt index a955189b17..efccb9c917 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt @@ -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() { @@ -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?) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt index fd0a6d70ff..55bf29e25f 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt @@ -46,7 +46,7 @@ abstract class EmojiSearchResultItem : EpoxyModelWithHolder - 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 } ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt index c9d683a2b9..a326828112 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt @@ -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()) } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt index fdd9a80911..caf6672964 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt @@ -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? + @Json(name = "j") val keywords: List = emptyList() ) { // Cannot be private... var cache: String? = null