diff --git a/changelog.d/4733.bugfix b/changelog.d/4733.bugfix new file mode 100644 index 0000000000..53979d21c4 --- /dev/null +++ b/changelog.d/4733.bugfix @@ -0,0 +1 @@ +Fixes unable to render messages by allowing them to render whilst the emoji library is initialising \ No newline at end of file diff --git a/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt b/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt index d33fd4948a..a42a6f0212 100644 --- a/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt @@ -24,6 +24,7 @@ import android.text.Spanned import android.text.style.ForegroundColorSpan import android.text.style.StrikethroughSpan import android.text.style.UnderlineSpan +import androidx.emoji2.text.EmojiCompat import im.vector.app.InstrumentedTest import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeTrue @@ -32,12 +33,18 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) 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 { return getBindingOptions(message).canUseTextFuture @@ -122,4 +129,17 @@ class SpanUtilsTest : InstrumentedTest { } 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) + } } diff --git a/vector/src/main/java/im/vector/app/EmojiCompatWrapper.kt b/vector/src/main/java/im/vector/app/EmojiCompatWrapper.kt index 995e2e8049..47133a61b4 100644 --- a/vector/src/main/java/im/vector/app/EmojiCompatWrapper.kt +++ b/vector/src/main/java/im/vector/app/EmojiCompatWrapper.kt @@ -23,8 +23,12 @@ import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton +fun interface EmojiSpanify { + fun spanify(sequence: CharSequence): CharSequence +} + @Singleton -class EmojiCompatWrapper @Inject constructor(private val context: Context) { +class EmojiCompatWrapper @Inject constructor(private val context: Context) : EmojiSpanify { 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) { try { return EmojiCompat.get().process(sequence) ?: sequence diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt index 14ed17d0bb..d83bb5cb57 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -26,6 +26,8 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn 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.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter @@ -76,6 +78,9 @@ abstract class VectorBindModule { @Binds abstract fun bindDefaultClock(clock: DefaultClock): Clock + + @Binds + abstract fun bindEmojiSpanify(emojiCompatWrapper: EmojiCompatWrapper): EmojiSpanify } @InstallIn(SingletonComponent::class) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index 3616367e7d..3892bfff85 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.format import dagger.Lazy -import im.vector.app.EmojiCompatWrapper +import im.vector.app.EmojiSpanify import im.vector.app.R import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider @@ -39,7 +39,7 @@ import javax.inject.Inject class DisplayableEventFormatter @Inject constructor( private val stringProvider: StringProvider, private val colorProvider: ColorProvider, - private val emojiCompatWrapper: EmojiCompatWrapper, + private val emojiSpanify: EmojiSpanify, private val noticeEventFormatter: NoticeEventFormatter, private val htmlRenderer: Lazy ) { @@ -100,7 +100,7 @@ class DisplayableEventFormatter @Inject constructor( } EventType.REACTION -> { timelineEvent.root.getClearContent().toModel()?.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) } ?: span { } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt index 0b3d381619..0031cf9feb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt @@ -20,7 +20,7 @@ import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success -import im.vector.app.EmojiCompatWrapper +import im.vector.app.EmojiSpanify import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericFooterItem @@ -32,8 +32,8 @@ import javax.inject.Inject */ class ViewReactionsEpoxyController @Inject constructor( private val stringProvider: StringProvider, - private val emojiCompatWrapper: EmojiCompatWrapper) : - TypedEpoxyController() { + private val emojiSpanify: EmojiSpanify) : + TypedEpoxyController() { var listener: Listener? = null @@ -56,7 +56,7 @@ class ViewReactionsEpoxyController @Inject constructor( reactionInfoSimpleItem { id(reactionInfo.eventId) timeStamp(reactionInfo.timestamp) - reactionKey(host.emojiCompatWrapper.safeEmojiSpanify(reactionInfo.reactionKey)) + reactionKey(host.emojiSpanify.spanify(reactionInfo.reactionKey)) authorDisplayName(reactionInfo.authorName ?: reactionInfo.authorId) userClicked { host.listener?.didSelectUser(reactionInfo.authorId) } } diff --git a/vector/src/main/java/im/vector/app/features/html/SpanUtils.kt b/vector/src/main/java/im/vector/app/features/html/SpanUtils.kt index 27c966e33e..e668f29a6a 100644 --- a/vector/src/main/java/im/vector/app/features/html/SpanUtils.kt +++ b/vector/src/main/java/im/vector/app/features/html/SpanUtils.kt @@ -21,13 +21,15 @@ import android.text.Spanned import android.text.style.MetricAffectingSpan import android.text.style.StrikethroughSpan import android.text.style.UnderlineSpan -import androidx.emoji2.text.EmojiCompat +import im.vector.app.EmojiSpanify import im.vector.app.features.home.room.detail.timeline.item.BindingOptions import javax.inject.Inject -class SpanUtils @Inject constructor() { +class SpanUtils @Inject constructor( + private val emojiSpanify: EmojiSpanify +) { fun getBindingOptions(charSequence: CharSequence): BindingOptions { - val emojiCharSequence = EmojiCompat.get().process(charSequence) + val emojiCharSequence = emojiSpanify.spanify(charSequence) if (emojiCharSequence !is Spanned) { return BindingOptions() diff --git a/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt b/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt index 2b4e9ee5ab..67095b974a 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt @@ -24,7 +24,7 @@ import android.widget.LinearLayout import androidx.core.content.ContextCompat import androidx.core.content.withStyledAttributes import dagger.hilt.android.AndroidEntryPoint -import im.vector.app.EmojiCompatWrapper +import im.vector.app.EmojiSpanify import im.vector.app.R import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.TextUtils @@ -39,9 +39,9 @@ import javax.inject.Inject class ReactionButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, 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 @@ -57,7 +57,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, set(value) { field = value // maybe cache this for performances? - val emojiSpanned = emojiCompatWrapper.safeEmojiSpanify(value) + val emojiSpanned = emojiSpanify.spanify(value) views.reactionText.text = emojiSpanned }