mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-23 18:05:59 +03:00
RoomDetail: lazy load EmojiDataSource data (+ async)
This commit is contained in:
parent
ebd5095662
commit
fc5c6b9b00
11 changed files with 194 additions and 105 deletions
|
@ -17,6 +17,10 @@
|
|||
package im.vector.app.features.reactions.data
|
||||
|
||||
import im.vector.app.InstrumentedTest
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.FixMethodOrder
|
||||
|
@ -30,64 +34,80 @@ import kotlin.system.measureTimeMillis
|
|||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class EmojiDataSourceTest : InstrumentedTest {
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
@Test
|
||||
fun checkParsingTime() {
|
||||
val time = measureTimeMillis {
|
||||
EmojiDataSource(context().resources)
|
||||
createEmojiDataSource()
|
||||
}
|
||||
|
||||
assertTrue("Too long to parse", time < 100)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkNumberOfResult() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
assertTrue("Wrong number of emojis", emojiDataSource.rawData.emojis.size >= 500)
|
||||
assertTrue("Wrong number of categories", emojiDataSource.rawData.categories.size >= 8)
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val rawData = runBlocking {
|
||||
emojiDataSource.rawData.await()
|
||||
}
|
||||
assertTrue("Wrong number of emojis", rawData.emojis.size >= 500)
|
||||
assertTrue("Wrong number of categories", rawData.categories.size >= 8)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchTestEmptySearch() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
assertTrue("Empty search should return at least 500 results", emojiDataSource.filterWith("").size >= 500)
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.filterWith("")
|
||||
}
|
||||
assertTrue("Empty search should return at least 500 results", result.size >= 500)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchTestNoResult() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
assertTrue("Should not have result", emojiDataSource.filterWith("noresult").isEmpty())
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.filterWith("noresult")
|
||||
}
|
||||
assertTrue("Should not have result", result.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchTestOneResult() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
assertEquals("Should have 1 result", 1, emojiDataSource.filterWith("france").size)
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.filterWith("france")
|
||||
}
|
||||
assertEquals("Should have 1 result", 1, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchTestManyResult() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
assertTrue("Should have many result", emojiDataSource.filterWith("fra").size > 1)
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.filterWith("fra")
|
||||
}
|
||||
assertTrue("Should have many result", result.size > 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTada() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
val result = emojiDataSource.filterWith("tada")
|
||||
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.filterWith("tada")
|
||||
}
|
||||
assertEquals("Should find tada emoji", 1, result.size)
|
||||
assertEquals("Should find tada emoji", "🎉", result[0].emoji)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testQuickReactions() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
assertEquals("Should have 8 quick reactions", 8, emojiDataSource.getQuickReactions().size)
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.getQuickReactions()
|
||||
}
|
||||
assertEquals("Should have 8 quick reactions", 8, result.size)
|
||||
}
|
||||
|
||||
private fun createEmojiDataSource() = EmojiDataSource(coroutineScope, context().resources)
|
||||
}
|
||||
|
|
|
@ -98,6 +98,7 @@ import im.vector.app.features.usercode.UserCodeActivity
|
|||
import im.vector.app.features.widgets.WidgetActivity
|
||||
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
|
||||
import im.vector.app.features.workers.signout.SignOutBottomSheetDialogFragment
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
|
@ -129,6 +130,7 @@ interface ScreenComponent {
|
|||
fun uiStateRepository(): UiStateRepository
|
||||
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
|
||||
fun autoAcceptInvites(): AutoAcceptInvites
|
||||
fun appCoroutineScope(): CoroutineScope
|
||||
|
||||
/* ==========================================================================================
|
||||
* Activities
|
||||
|
|
|
@ -60,6 +60,7 @@ import im.vector.app.features.reactions.data.EmojiDataSource
|
|||
import im.vector.app.features.session.SessionListener
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
|
@ -165,6 +166,8 @@ interface VectorComponent {
|
|||
|
||||
fun webRtcCallManager(): WebRtcCallManager
|
||||
|
||||
fun appCoroutineScope(): CoroutineScope
|
||||
|
||||
fun jitsiActiveConferenceHolder(): JitsiActiveConferenceHolder
|
||||
|
||||
@Component.Factory
|
||||
|
|
|
@ -33,12 +33,18 @@ import im.vector.app.features.pin.PinCodeStore
|
|||
import im.vector.app.features.pin.SharedPrefPinCodeStore
|
||||
import im.vector.app.features.ui.SharedPreferencesUiStateRepository
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
abstract class VectorModule {
|
||||
|
@ -94,6 +100,14 @@ abstract class VectorModule {
|
|||
fun providesHomeServerHistoryService(matrix: Matrix): HomeServerHistoryService {
|
||||
return matrix.homeServerHistoryService()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
@Singleton
|
||||
fun providesApplicationCoroutineScope(): CoroutineScope {
|
||||
return CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Binds
|
||||
|
|
|
@ -21,6 +21,11 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import im.vector.app.features.autocomplete.AutocompleteClickListener
|
||||
import im.vector.app.features.autocomplete.RecyclerViewPresenter
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
||||
|
@ -28,11 +33,14 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
|||
private val controller: AutocompleteEmojiController) :
|
||||
RecyclerViewPresenter<String>(context), AutocompleteClickListener<String> {
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
init {
|
||||
controller.listener = this
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
coroutineScope.coroutineContext.cancelChildren()
|
||||
controller.listener = null
|
||||
}
|
||||
|
||||
|
@ -45,12 +53,14 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
|||
}
|
||||
|
||||
override fun onQuery(query: CharSequence?) {
|
||||
val data = if (query.isNullOrBlank()) {
|
||||
// Return common emojis
|
||||
emojiDataSource.getQuickReactions()
|
||||
} else {
|
||||
emojiDataSource.filterWith(query.toString())
|
||||
coroutineScope.launch {
|
||||
val data = if (query.isNullOrBlank()) {
|
||||
// Return common emojis
|
||||
emojiDataSource.getQuickReactions()
|
||||
} else {
|
||||
emojiDataSource.filterWith(query.toString())
|
||||
}
|
||||
controller.setData(data)
|
||||
}
|
||||
controller.setData(data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,15 +41,15 @@ class EmojiChooserFragment @Inject constructor(
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java)
|
||||
|
||||
emojiRecyclerAdapter.reactionClickListener = this
|
||||
emojiRecyclerAdapter.interactionListener = this
|
||||
|
||||
views.emojiRecyclerView.adapter = emojiRecyclerAdapter
|
||||
|
||||
viewModel.moveToSection.observe(viewLifecycleOwner) { section ->
|
||||
emojiRecyclerAdapter.scrollToSection(section)
|
||||
}
|
||||
viewModel.emojiData.observe(viewLifecycleOwner) {
|
||||
emojiRecyclerAdapter.update(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCoroutineScope() = lifecycleScope
|
||||
|
|
|
@ -17,11 +17,16 @@ package im.vector.app.features.reactions
|
|||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import im.vector.app.core.utils.LiveEvent
|
||||
import im.vector.app.features.reactions.data.EmojiData
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class EmojiChooserViewModel @Inject constructor() : ViewModel() {
|
||||
class EmojiChooserViewModel @Inject constructor(private val emojiDataSource: EmojiDataSource) : ViewModel() {
|
||||
|
||||
val emojiData: MutableLiveData<EmojiData> = MutableLiveData()
|
||||
val navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
||||
var selectedReaction: String? = null
|
||||
var eventId: String? = null
|
||||
|
@ -29,6 +34,17 @@ class EmojiChooserViewModel @Inject constructor() : ViewModel() {
|
|||
val currentSection: MutableLiveData<Int> = MutableLiveData()
|
||||
val moveToSection: MutableLiveData<Int> = MutableLiveData()
|
||||
|
||||
init {
|
||||
loadEmojiData()
|
||||
}
|
||||
|
||||
private fun loadEmojiData() {
|
||||
viewModelScope.launch {
|
||||
val rawData = emojiDataSource.rawData.await()
|
||||
emojiData.postValue(rawData)
|
||||
}
|
||||
}
|
||||
|
||||
fun onReactionSelected(reaction: String) {
|
||||
selectedReaction = reaction
|
||||
navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.view.MenuInflater
|
|||
import android.view.MenuItem
|
||||
import android.widget.SearchView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.jakewharton.rxbinding3.widget.queryTextChanges
|
||||
|
@ -36,6 +37,7 @@ import im.vector.app.core.platform.VectorBaseActivity
|
|||
import im.vector.app.databinding.ActivityEmojiReactionPickerBinding
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -91,17 +93,19 @@ class EmojiReactionPickerActivity : VectorBaseActivity<ActivityEmojiReactionPick
|
|||
viewModel = viewModelProvider.get(EmojiChooserViewModel::class.java)
|
||||
|
||||
viewModel.eventId = intent.getStringExtra(EXTRA_EVENT_ID)
|
||||
|
||||
emojiDataSource.rawData.categories.forEach { category ->
|
||||
val s = category.emojis[0]
|
||||
views.tabs.newTab()
|
||||
.also { tab ->
|
||||
tab.text = emojiDataSource.rawData.emojis[s]!!.emoji
|
||||
tab.contentDescription = category.name
|
||||
}
|
||||
.also { tab ->
|
||||
views.tabs.addTab(tab)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val rawData = emojiDataSource.rawData.await()
|
||||
rawData.categories.forEach { category ->
|
||||
val s = category.emojis[0]
|
||||
views.tabs.newTab()
|
||||
.also { tab ->
|
||||
tab.text = rawData.emojis[s]!!.emoji
|
||||
tab.contentDescription = category.name
|
||||
}
|
||||
.also { tab ->
|
||||
views.tabs.addTab(tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
views.tabs.addOnTabSelectedListener(tabLayoutSelectionListener)
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package im.vector.app.features.reactions
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Trace
|
||||
import android.text.Layout
|
||||
|
@ -30,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.reactions.data.EmojiData
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -43,13 +45,13 @@ import kotlin.math.abs
|
|||
* TODO: Performances
|
||||
* TODO: Scroll to section - Find a way to snap section to the top
|
||||
*/
|
||||
class EmojiRecyclerAdapter @Inject constructor(
|
||||
private val dataSource: EmojiDataSource
|
||||
) :
|
||||
class EmojiRecyclerAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() {
|
||||
|
||||
var reactionClickListener: ReactionClickListener? = null
|
||||
var interactionListener: InteractionListener? = null
|
||||
|
||||
private var rawData: EmojiData = EmojiData(emptyList(), emptyMap(), emptyMap())
|
||||
private var mRecyclerView: RecyclerView? = null
|
||||
|
||||
private var currentFirstVisibleSection = 0
|
||||
|
@ -61,6 +63,12 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
UNKNOWN
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun update(emojiData: EmojiData){
|
||||
rawData = emojiData
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private var scrollState = ScrollState.UNKNOWN
|
||||
private var isFastScroll = false
|
||||
|
||||
|
@ -71,10 +79,10 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
if (itemPosition != RecyclerView.NO_POSITION) {
|
||||
val sectionNumber = getSectionForAbsoluteIndex(itemPosition)
|
||||
if (!isSection(itemPosition)) {
|
||||
val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
|
||||
val sectionMojis = rawData.categories[sectionNumber].emojis
|
||||
val sectionOffset = getSectionOffset(sectionNumber)
|
||||
val emoji = sectionMojis[itemPosition - sectionOffset]
|
||||
val item = dataSource.rawData.emojis.getValue(emoji).emoji
|
||||
val item = rawData.emojis.getValue(emoji).emoji
|
||||
reactionClickListener?.onReactionSelected(item)
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +123,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
}
|
||||
|
||||
fun scrollToSection(section: Int) {
|
||||
if (section < 0 || section >= dataSource.rawData.categories.size) {
|
||||
if (section < 0 || section >= rawData.categories.size) {
|
||||
// ignore
|
||||
return
|
||||
}
|
||||
|
@ -149,7 +157,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
private fun isSection(position: Int): Boolean {
|
||||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
dataSource.rawData.categories.forEach { category ->
|
||||
rawData.categories.forEach { category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position == sectionOffset - 1) return true
|
||||
sectionOffset = lastItemInSection + 2
|
||||
|
@ -161,7 +169,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
var index = 0
|
||||
dataSource.rawData.categories.forEach { category ->
|
||||
rawData.categories.forEach { category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position <= lastItemInSection) return index
|
||||
sectionOffset = lastItemInSection + 2
|
||||
|
@ -174,7 +182,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
// Todo cache this for fast access
|
||||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
dataSource.rawData.categories.forEachIndexed { index, category ->
|
||||
rawData.categories.forEachIndexed { index, category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (section == index) return sectionOffset
|
||||
sectionOffset = lastItemInSection + 2
|
||||
|
@ -186,12 +194,12 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
Trace.beginSection("MyAdapter.onBindViewHolder")
|
||||
val sectionNumber = getSectionForAbsoluteIndex(position)
|
||||
if (isSection(position)) {
|
||||
holder.bind(dataSource.rawData.categories[sectionNumber].name)
|
||||
holder.bind(rawData.categories[sectionNumber].name)
|
||||
} else {
|
||||
val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
|
||||
val sectionMojis = rawData.categories[sectionNumber].emojis
|
||||
val sectionOffset = getSectionOffset(sectionNumber)
|
||||
val emoji = sectionMojis[position - sectionOffset]
|
||||
val item = dataSource.rawData.emojis[emoji]!!.emoji
|
||||
val item = rawData.emojis[emoji]!!.emoji
|
||||
(holder as EmojiViewHolder).data = item
|
||||
if (scrollState != ScrollState.SETTLING || !isFastScroll) {
|
||||
// Log.i("PERF","Bind with draw at position:$position")
|
||||
|
@ -220,7 +228,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
|||
super.onViewRecycled(holder)
|
||||
}
|
||||
|
||||
override fun getItemCount() = dataSource.rawData.categories
|
||||
override fun getItemCount() = rawData.categories
|
||||
.sumOf { emojiCategory -> 1 /* Section */ + emojiCategory.emojis.size }
|
||||
|
||||
abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
|
|
@ -15,17 +15,19 @@
|
|||
*/
|
||||
package im.vector.app.features.reactions
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import im.vector.app.features.reactions.data.EmojiItem
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class EmojiSearchResultViewState(
|
||||
val query: String = "",
|
||||
|
@ -58,11 +60,14 @@ class EmojiSearchResultViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun updateQuery(action: EmojiSearchAction.UpdateQuery) {
|
||||
setState {
|
||||
copy(
|
||||
query = action.queryString,
|
||||
results = dataSource.filterWith(action.queryString)
|
||||
)
|
||||
viewModelScope.launch {
|
||||
val results = dataSource.filterWith(action.queryString)
|
||||
setState {
|
||||
copy(
|
||||
query = action.queryString,
|
||||
results = results
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,53 +20,60 @@ import android.graphics.Paint
|
|||
import androidx.core.graphics.PaintCompat
|
||||
import com.squareup.moshi.Moshi
|
||||
import im.vector.app.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class EmojiDataSource @Inject constructor(
|
||||
appScope: CoroutineScope,
|
||||
resources: Resources
|
||||
) {
|
||||
private val paint = Paint()
|
||||
val rawData = resources.openRawResource(R.raw.emoji_picker_datasource)
|
||||
.use { input ->
|
||||
Moshi.Builder()
|
||||
.build()
|
||||
.adapter(EmojiData::class.java)
|
||||
.fromJson(input.bufferedReader().use { it.readText() })
|
||||
}
|
||||
?.let { parsedRawData ->
|
||||
// Add key as a keyword, it will solve the issue that ":tada" is not available in completion
|
||||
// Only add emojis to emojis/categories that can be rendered by the system
|
||||
parsedRawData.copy(
|
||||
emojis = mutableMapOf<String, EmojiItem>().apply {
|
||||
parsedRawData.emojis.keys.forEach { key ->
|
||||
val origin = parsedRawData.emojis[key] ?: return@forEach
|
||||
val rawData = appScope.async(Dispatchers.IO, CoroutineStart.LAZY) {
|
||||
resources.openRawResource(R.raw.emoji_picker_datasource)
|
||||
.use { input ->
|
||||
Moshi.Builder()
|
||||
.build()
|
||||
.adapter(EmojiData::class.java)
|
||||
.fromJson(input.bufferedReader().use { it.readText() })
|
||||
}
|
||||
?.let { parsedRawData ->
|
||||
// Add key as a keyword, it will solve the issue that ":tada" is not available in completion
|
||||
// Only add emojis to emojis/categories that can be rendered by the system
|
||||
parsedRawData.copy(
|
||||
emojis = mutableMapOf<String, EmojiItem>().apply {
|
||||
parsedRawData.emojis.keys.forEach { key ->
|
||||
val origin = parsedRawData.emojis[key] ?: return@forEach
|
||||
|
||||
// Do not add keys containing '_'
|
||||
if (isEmojiRenderable(origin.emoji)) {
|
||||
if (origin.keywords.contains(key) || key.contains("_")) {
|
||||
put(key, origin)
|
||||
} else {
|
||||
put(key, origin.copy(keywords = origin.keywords + key))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
categories = mutableListOf<EmojiCategory>().apply {
|
||||
parsedRawData.categories.forEach { entry ->
|
||||
add(EmojiCategory(entry.id, entry.name, mutableListOf<String>().apply {
|
||||
entry.emojis.forEach { e ->
|
||||
if (isEmojiRenderable(parsedRawData.emojis[e]!!.emoji)) {
|
||||
add(e)
|
||||
// Do not add keys containing '_'
|
||||
if (isEmojiRenderable(origin.emoji)) {
|
||||
if (origin.keywords.contains(key) || key.contains("_")) {
|
||||
put(key, origin)
|
||||
} else {
|
||||
put(key, origin.copy(keywords = origin.keywords + key))
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
},
|
||||
categories = mutableListOf<EmojiCategory>().apply {
|
||||
parsedRawData.categories.forEach { entry ->
|
||||
add(EmojiCategory(entry.id, entry.name, mutableListOf<String>().apply {
|
||||
entry.emojis.forEach { e ->
|
||||
if (isEmojiRenderable(parsedRawData.emojis[e]!!.emoji)) {
|
||||
add(e)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
?: EmojiData(emptyList(), emptyMap(), emptyMap())
|
||||
)
|
||||
}
|
||||
?: EmojiData(emptyList(), emptyMap(), emptyMap())
|
||||
}
|
||||
|
||||
private val quickReactions = mutableListOf<EmojiItem>()
|
||||
|
||||
|
@ -74,9 +81,9 @@ class EmojiDataSource @Inject constructor(
|
|||
return PaintCompat.hasGlyph(paint, emoji)
|
||||
}
|
||||
|
||||
fun filterWith(query: String): List<EmojiItem> {
|
||||
suspend fun filterWith(query: String): List<EmojiItem> {
|
||||
val words = query.split("\\s".toRegex())
|
||||
|
||||
val rawData = this.rawData.await()
|
||||
// First add emojis with name matching query, sorted by name
|
||||
return (rawData.emojis.values
|
||||
.asSequence()
|
||||
|
@ -87,9 +94,9 @@ class EmojiDataSource @Inject constructor(
|
|||
// Then emojis with keyword matching any of the word in the query, sorted by name
|
||||
rawData.emojis.values
|
||||
.filter { emojiItem ->
|
||||
words.fold(true, { prev, word ->
|
||||
words.fold(true) { prev, word ->
|
||||
prev && emojiItem.keywords.any { keyword -> keyword.contains(word, true) }
|
||||
})
|
||||
}
|
||||
}
|
||||
.sortedBy { it.name })
|
||||
// and ensure they will not be present twice
|
||||
|
@ -97,7 +104,7 @@ class EmojiDataSource @Inject constructor(
|
|||
.toList()
|
||||
}
|
||||
|
||||
fun getQuickReactions(): List<EmojiItem> {
|
||||
suspend fun getQuickReactions(): List<EmojiItem> {
|
||||
if (quickReactions.isEmpty()) {
|
||||
listOf(
|
||||
"thumbs-up", // 👍
|
||||
|
@ -109,7 +116,7 @@ class EmojiDataSource @Inject constructor(
|
|||
"rocket", // 🚀
|
||||
"eyes" // 👀
|
||||
)
|
||||
.mapNotNullTo(quickReactions) { rawData.emojis[it] }
|
||||
.mapNotNullTo(quickReactions) { rawData.await().emojis[it] }
|
||||
}
|
||||
|
||||
return quickReactions
|
||||
|
|
Loading…
Reference in a new issue