extracting the emoji processing to an interface so that we can override the behaviour in the unit test

This commit is contained in:
Adam Brown 2021-12-17 10:37:51 +00:00
parent e1eafd2c64
commit 6918372a87
7 changed files with 46 additions and 17 deletions

View file

@ -24,6 +24,7 @@ import android.text.Spanned
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.text.style.StrikethroughSpan import android.text.style.StrikethroughSpan
import android.text.style.UnderlineSpan import android.text.style.UnderlineSpan
import androidx.emoji2.text.EmojiCompat
import im.vector.app.InstrumentedTest import im.vector.app.InstrumentedTest
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeTrue import org.amshove.kluent.shouldBeTrue
@ -32,12 +33,18 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.JUnit4 import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
class SpanUtilsTest : InstrumentedTest { class SpanUtilsTest : InstrumentedTest {
private val spanUtils = SpanUtils() private val spanUtils = SpanUtils {
val emojiCompat = EmojiCompat.get()
emojiCompat.waitForInit()
emojiCompat.process(it) ?: it
}
private fun SpanUtils.canUseTextFuture(message: CharSequence): Boolean { private fun SpanUtils.canUseTextFuture(message: CharSequence): Boolean {
return getBindingOptions(message).canUseTextFuture return getBindingOptions(message).canUseTextFuture
@ -122,4 +129,17 @@ class SpanUtilsTest : InstrumentedTest {
} }
private fun trueIfAlwaysAllowed() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P private fun trueIfAlwaysAllowed() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P
private fun EmojiCompat.waitForInit() {
val latch = CountDownLatch(1)
registerInitCallback(object : EmojiCompat.InitCallback() {
override fun onInitialized() = latch.countDown()
override fun onFailed(throwable: Throwable?) {
latch.countDown()
throw RuntimeException(throwable)
}
})
EmojiCompat.init(context())
latch.await(30, TimeUnit.SECONDS)
}
} }

View file

@ -23,8 +23,12 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
fun interface EmojiSpanify {
fun spanify(sequence: CharSequence): CharSequence
}
@Singleton @Singleton
class EmojiCompatWrapper @Inject constructor(private val context: Context) { class EmojiCompatWrapper @Inject constructor(private val context: Context) : EmojiSpanify {
private var initialized = false private var initialized = false
@ -49,7 +53,7 @@ class EmojiCompatWrapper @Inject constructor(private val context: Context) {
}) })
} }
fun safeEmojiSpanify(sequence: CharSequence): CharSequence { override fun spanify(sequence: CharSequence): CharSequence {
if (initialized) { if (initialized) {
try { try {
return EmojiCompat.get().process(sequence) ?: sequence return EmojiCompat.get().process(sequence) ?: sequence

View file

@ -26,6 +26,8 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import im.vector.app.EmojiCompatWrapper
import im.vector.app.EmojiSpanify
import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.dispatchers.CoroutineDispatchers
import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
@ -76,6 +78,9 @@ abstract class VectorBindModule {
@Binds @Binds
abstract fun bindDefaultClock(clock: DefaultClock): Clock abstract fun bindDefaultClock(clock: DefaultClock): Clock
@Binds
abstract fun bindEmojiSpanify(emojiCompatWrapper: EmojiCompatWrapper): EmojiSpanify
} }
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)

View file

@ -17,7 +17,7 @@
package im.vector.app.features.home.room.detail.timeline.format package im.vector.app.features.home.room.detail.timeline.format
import dagger.Lazy import dagger.Lazy
import im.vector.app.EmojiCompatWrapper import im.vector.app.EmojiSpanify
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
@ -39,7 +39,7 @@ import javax.inject.Inject
class DisplayableEventFormatter @Inject constructor( class DisplayableEventFormatter @Inject constructor(
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val emojiCompatWrapper: EmojiCompatWrapper, private val emojiSpanify: EmojiSpanify,
private val noticeEventFormatter: NoticeEventFormatter, private val noticeEventFormatter: NoticeEventFormatter,
private val htmlRenderer: Lazy<EventHtmlRenderer> private val htmlRenderer: Lazy<EventHtmlRenderer>
) { ) {
@ -100,7 +100,7 @@ class DisplayableEventFormatter @Inject constructor(
} }
EventType.REACTION -> { EventType.REACTION -> {
timelineEvent.root.getClearContent().toModel<ReactionContent>()?.relatesTo?.let { timelineEvent.root.getClearContent().toModel<ReactionContent>()?.relatesTo?.let {
val emojiSpanned = emojiCompatWrapper.safeEmojiSpanify(stringProvider.getString(R.string.sent_a_reaction, it.key)) val emojiSpanned = emojiSpanify.spanify(stringProvider.getString(R.string.sent_a_reaction, it.key))
simpleFormat(senderName, emojiSpanned, appendAuthor) simpleFormat(senderName, emojiSpanned, appendAuthor)
} ?: span { } } ?: span { }
} }

View file

@ -20,7 +20,7 @@ import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import im.vector.app.EmojiCompatWrapper import im.vector.app.EmojiSpanify
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericFooterItem
@ -32,7 +32,7 @@ import javax.inject.Inject
*/ */
class ViewReactionsEpoxyController @Inject constructor( class ViewReactionsEpoxyController @Inject constructor(
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val emojiCompatWrapper: EmojiCompatWrapper) : private val emojiSpanify: EmojiSpanify) :
TypedEpoxyController<DisplayReactionsViewState>() { TypedEpoxyController<DisplayReactionsViewState>() {
var listener: Listener? = null var listener: Listener? = null
@ -56,7 +56,7 @@ class ViewReactionsEpoxyController @Inject constructor(
reactionInfoSimpleItem { reactionInfoSimpleItem {
id(reactionInfo.eventId) id(reactionInfo.eventId)
timeStamp(reactionInfo.timestamp) timeStamp(reactionInfo.timestamp)
reactionKey(host.emojiCompatWrapper.safeEmojiSpanify(reactionInfo.reactionKey)) reactionKey(host.emojiSpanify.spanify(reactionInfo.reactionKey))
authorDisplayName(reactionInfo.authorName ?: reactionInfo.authorId) authorDisplayName(reactionInfo.authorName ?: reactionInfo.authorId)
userClicked { host.listener?.didSelectUser(reactionInfo.authorId) } userClicked { host.listener?.didSelectUser(reactionInfo.authorId) }
} }

View file

@ -21,15 +21,15 @@ import android.text.Spanned
import android.text.style.MetricAffectingSpan import android.text.style.MetricAffectingSpan
import android.text.style.StrikethroughSpan import android.text.style.StrikethroughSpan
import android.text.style.UnderlineSpan import android.text.style.UnderlineSpan
import im.vector.app.EmojiCompatWrapper import im.vector.app.EmojiSpanify
import im.vector.app.features.home.room.detail.timeline.item.BindingOptions import im.vector.app.features.home.room.detail.timeline.item.BindingOptions
import javax.inject.Inject import javax.inject.Inject
class SpanUtils @Inject constructor( class SpanUtils @Inject constructor(
private val emojiCompat: EmojiCompatWrapper private val emojiSpanify: EmojiSpanify
) { ) {
fun getBindingOptions(charSequence: CharSequence): BindingOptions { fun getBindingOptions(charSequence: CharSequence): BindingOptions {
val emojiCharSequence = emojiCompat.safeEmojiSpanify(charSequence) val emojiCharSequence = emojiSpanify.spanify(charSequence)
if (emojiCharSequence !is Spanned) { if (emojiCharSequence !is Spanned) {
return BindingOptions() return BindingOptions()

View file

@ -24,7 +24,7 @@ import android.widget.LinearLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.EmojiCompatWrapper import im.vector.app.EmojiSpanify
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.core.utils.TextUtils import im.vector.app.core.utils.TextUtils
@ -41,7 +41,7 @@ class ReactionButton @JvmOverloads constructor(context: Context,
defStyleAttr: Int = 0) : defStyleAttr: Int = 0) :
LinearLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener { LinearLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener {
@Inject lateinit var emojiCompatWrapper: EmojiCompatWrapper @Inject lateinit var emojiSpanify: EmojiSpanify
private val views: ReactionButtonBinding private val views: ReactionButtonBinding
@ -57,7 +57,7 @@ class ReactionButton @JvmOverloads constructor(context: Context,
set(value) { set(value) {
field = value field = value
// maybe cache this for performances? // maybe cache this for performances?
val emojiSpanned = emojiCompatWrapper.safeEmojiSpanify(value) val emojiSpanned = emojiSpanify.spanify(value)
views.reactionText.text = emojiSpanned views.reactionText.text = emojiSpanned
} }