Improve EmojiChooserFragment: DI

This commit is contained in:
Benoit Marty 2019-12-09 23:41:35 +01:00
parent 63e0b15f3d
commit f00f34b244
10 changed files with 105 additions and 69 deletions

View File

@ -43,7 +43,6 @@ import im.vector.riotx.core.di.HasVectorInjector
import im.vector.riotx.core.di.VectorComponent
import im.vector.riotx.core.extensions.configureAndStart
import im.vector.riotx.core.rx.setupRxPlugin
import im.vector.riotx.core.utils.initKnownEmojiHashSet
import im.vector.riotx.features.configuration.VectorConfiguration
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotx.features.notifications.NotificationDrawerManager
@ -137,7 +136,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
})
ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler)
// This should be done as early as possible
initKnownEmojiHashSet(appContext)
// initKnownEmojiHashSet(appContext)
}
override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION)

View File

@ -38,6 +38,7 @@ import im.vector.riotx.features.home.room.detail.RoomDetailFragment
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.login.*
import im.vector.riotx.features.login.terms.LoginTermsFragment
import im.vector.riotx.features.reactions.EmojiChooserFragment
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
@ -255,4 +256,9 @@ interface FragmentModule {
@IntoMap
@FragmentKey(BreadcrumbsFragment::class)
fun bindBreadcrumbsFragment(fragment: BreadcrumbsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(EmojiChooserFragment::class)
fun bindEmojiChooserFragment(fragment: EmojiChooserFragment): Fragment
}

View File

@ -49,6 +49,7 @@ private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
"|\uD83C\uDCCF\uFE0F?" +
"|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))")
/*
// A hashset from all supported emoji
private var knownEmojiSet: HashSet<String>? = null
@ -77,6 +78,7 @@ fun isSingleEmoji(string: String): Boolean {
}
return knownEmojiSet?.contains(string) ?: false
}
*/
/**
* Test if a string contains emojis.

View File

@ -17,13 +17,18 @@ package im.vector.riotx.features.reactions
import android.os.Bundle
import android.view.View
import androidx.lifecycle.observe
import androidx.recyclerview.widget.RecyclerView
import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.platform.VectorBaseFragment
import javax.inject.Inject
class EmojiChooserFragment @Inject constructor() : VectorBaseFragment() {
class EmojiChooserFragment @Inject constructor(
private val emojiRecyclerAdapter: EmojiRecyclerAdapter
) : VectorBaseFragment(),
EmojiRecyclerAdapter.InteractionListener,
ReactionClickListener {
override fun getLayoutResId() = R.layout.emoji_chooser_fragment
@ -32,15 +37,32 @@ class EmojiChooserFragment @Inject constructor() : VectorBaseFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java)
viewModel.initWithContext(context!!)
emojiRecyclerAdapter.reactionClickListener = this
emojiRecyclerAdapter.interactionListener = this
(view as? RecyclerView)?.let {
it.adapter = viewModel.adapter
it.adapter = emojiRecyclerAdapter
it.adapter?.notifyDataSetChanged()
}
viewModel.moveToSection.observe(viewLifecycleOwner) { section ->
emojiRecyclerAdapter.scrollToSection(section)
}
}
override fun firstVisibleSectionChange(section: Int) {
viewModel.setCurrentSection(section)
}
override fun onReactionSelected(reaction: String) {
viewModel.onReactionSelected(reaction)
}
override fun onDestroyView() {
(view as? RecyclerView)?.cleanup()
emojiRecyclerAdapter.reactionClickListener = null
emojiRecyclerAdapter.interactionListener = null
super.onDestroyView()
}
}

View File

@ -15,46 +15,33 @@
*/
package im.vector.riotx.features.reactions
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.riotx.core.utils.LiveEvent
import im.vector.riotx.features.reactions.data.EmojiDataSource
import javax.inject.Inject
class EmojiChooserViewModel @Inject constructor() : ViewModel() {
// TODO Move the adapter out of the ViewModel
var adapter: EmojiRecyclerAdapter? = null
val emojiSourceLiveData: MutableLiveData<EmojiDataSource> = MutableLiveData()
val navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
var selectedReaction: String? = null
var eventId: String? = null
val currentSection: MutableLiveData<Int> = MutableLiveData()
val moveToSection: MutableLiveData<Int> = MutableLiveData()
var reactionClickListener = object : ReactionClickListener {
override fun onReactionSelected(reaction: String) {
selectedReaction = reaction
navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
}
fun onReactionSelected(reaction: String) {
selectedReaction = reaction
navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
}
fun initWithContext(context: Context) {
// TODO load async
val emojiDataSource = EmojiDataSource(context)
emojiSourceLiveData.value = emojiDataSource
adapter = EmojiRecyclerAdapter(emojiDataSource, reactionClickListener)
adapter?.interactionListener = object : EmojiRecyclerAdapter.InteractionListener {
override fun firstVisibleSectionChange(section: Int) {
currentSection.value = section
}
}
// Called by the Fragment, when the List is scrolled
fun setCurrentSection(section: Int) {
currentSection.value = section
}
fun scrollToSection(sectionIndex: Int) {
adapter?.scrollToSection(sectionIndex)
// Called by the Activity, when a tab item is clicked
fun scrollToSection(section: Int) {
moveToSection.value = section
}
companion object {

View File

@ -35,6 +35,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.reactions.data.EmojiDataSource
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.activity_emoji_reaction_picker.*
import timber.log.Timber
@ -44,7 +45,6 @@ import javax.inject.Inject
/**
*
* TODO: Loading indicator while getting emoji data source?
* TODO: migrate to MvRx
* TODO: Finish Refactor to vector base activity
*/
class EmojiReactionPickerActivity : VectorBaseActivity(),
@ -60,7 +60,9 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
override fun getTitleRes() = R.string.title_activity_emoji_reaction_picker
@Inject lateinit var emojiSearchResultViewModelFactory: EmojiSearchResultViewModel.Factory
@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
@Inject lateinit var emojiDataSource: EmojiDataSource
private val searchResultViewModel: EmojiSearchResultViewModel by viewModel()
@ -93,22 +95,20 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
viewModel.eventId = intent.getStringExtra(EXTRA_EVENT_ID)
viewModel.emojiSourceLiveData.observe(this, Observer {
it.rawData?.categories?.let { categories ->
for (category in categories) {
val s = category.emojis[0]
tabLayout.newTab()
.also { tab ->
tab.text = it.rawData!!.emojis[s]!!.emojiString()
tab.contentDescription = category.name
}
.also { tab ->
tabLayout.addTab(tab)
}
}
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
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)
}
viewModel.currentSection.observe(this, Observer { section ->
section?.let {
@ -136,7 +136,6 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
override fun compatibilityFontUpdate(typeface: Typeface?) {
EmojiDrawView.configureTextPaint(this, typeface)
searchResultViewModel.dataSource
}
override fun onDestroy() {

View File

@ -34,6 +34,7 @@ import im.vector.riotx.features.reactions.data.EmojiDataSource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.math.abs
/**
@ -43,10 +44,12 @@ import kotlin.math.abs
* TODO: Performances
* TODO: Scroll to section - Find a way to snap section to the top
*/
class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
private var reactionClickListener: ReactionClickListener?) :
class EmojiRecyclerAdapter @Inject constructor(
private val dataSource: EmojiDataSource?
) :
RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() {
var reactionClickListener: ReactionClickListener? = null
var interactionListener: InteractionListener? = null
private var mRecyclerView: RecyclerView? = null
@ -73,7 +76,7 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
val sectionMojis = categories[sectionNumber].emojis
val sectionOffset = getSectionOffset(sectionNumber)
val emoji = sectionMojis[itemPosition - sectionOffset]
val item = dataSource.rawData!!.emojis.getValue(emoji).emojiString()
val item = dataSource.rawData!!.emojis.getValue(emoji).emoji
reactionClickListener?.onReactionSelected(item)
}
}
@ -197,7 +200,7 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
val sectionMojis = categories[sectionNumber].emojis
val sectionOffset = getSectionOffset(sectionNumber)
val emoji = sectionMojis[position - sectionOffset]
val item = dataSource.rawData!!.emojis[emoji]!!.emojiString()
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")

View File

@ -43,12 +43,12 @@ abstract class EmojiSearchResultItem : EpoxyModelWithHolder<EmojiSearchResultIte
override fun bind(holder: Holder) {
super.bind(holder)
// TODO use query string to highlight the matched query in name and keywords?
holder.emojiText.text = emojiItem.emojiString()
holder.emojiText.text = emojiItem.emoji
holder.emojiText.typeface = emojiTypeFace ?: Typeface.DEFAULT
holder.emojiNameText.text = emojiItem.name
holder.emojiKeywordText.setTextOrHide(emojiItem.keywords?.joinToString())
holder.view.setOnClickListener {
onClickListener?.onReactionSelected(emojiItem.emojiString())
onClickListener?.onReactionSelected(emojiItem.emoji)
}
}

View File

@ -15,9 +15,9 @@
*/
package im.vector.riotx.features.reactions
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.reactions.data.EmojiDataSource
import im.vector.riotx.features.reactions.data.EmojiItem
@ -27,9 +27,24 @@ data class EmojiSearchResultViewState(
val results: List<EmojiItem> = emptyList()
) : MvRxState
class EmojiSearchResultViewModel(val dataSource: EmojiDataSource, initialState: EmojiSearchResultViewState)
class EmojiSearchResultViewModel @AssistedInject constructor(
@Assisted initialState: EmojiSearchResultViewState,
private val dataSource: EmojiDataSource)
: VectorViewModel<EmojiSearchResultViewState, EmojiSearchAction>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: EmojiSearchResultViewState): EmojiSearchResultViewModel
}
companion object : MvRxViewModelFactory<EmojiSearchResultViewModel, EmojiSearchResultViewState> {
override fun create(viewModelContext: ViewModelContext, state: EmojiSearchResultViewState): EmojiSearchResultViewModel? {
val activity: EmojiReactionPickerActivity = (viewModelContext as ActivityViewModelContext).activity()
return activity.emojiSearchResultViewModelFactory.create(state)
}
}
override fun handle(action: EmojiSearchAction) {
when (action) {
is EmojiSearchAction.UpdateQuery -> updateQuery(action)
@ -55,12 +70,4 @@ class EmojiSearchResultViewModel(val dataSource: EmojiDataSource, initialState:
)
}
}
companion object : MvRxViewModelFactory<EmojiSearchResultViewModel, EmojiSearchResultViewState> {
override fun create(viewModelContext: ViewModelContext, state: EmojiSearchResultViewState): EmojiSearchResultViewModel? {
// TODO get the data source from activity? share it with other fragment
return EmojiSearchResultViewModel(EmojiDataSource(viewModelContext.activity), state)
}
}
}

View File

@ -18,17 +18,28 @@ package im.vector.riotx.features.reactions.data
import android.content.Context
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
class EmojiDataSource(val context: Context) {
@ScreenScope
class EmojiDataSource @Inject constructor(
context: Context
) {
var rawData: EmojiData? = null
init {
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)
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)
}
}.also {
Timber.e("Emoji: $it millis")
}
}
}