Improve system emoji rendering across the app with EmojiCompat2.

Resolves #13327
This commit is contained in:
Dan Brunwasser
2022-09-13 00:17:01 -07:00
committed by Greyson Parrelli
parent abd80c5204
commit 10922594b3
22 changed files with 87 additions and 27 deletions

View File

@@ -509,6 +509,7 @@ dependencies {
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.asynclayoutinflater)
implementation(libs.androidx.asynclayoutinflater.appcompat)
implementation(libs.androidx.emoji2)
implementation(libs.firebase.messaging) {
exclude(group = "com.google.firebase", module = "firebase-core")
exclude(group = "com.google.firebase", module = "firebase-analytics")

View File

@@ -42,11 +42,10 @@ public class EmojiEditText extends AppCompatEditText {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiTextView, 0, 0);
boolean forceCustom = a.getBoolean(R.styleable.EmojiTextView_emoji_forceCustom, false);
boolean jumboEmoji = a.getBoolean(R.styleable.EmojiTextView_emoji_forceJumbo, false);
a.recycle();
if (!isInEditMode() && (forceCustom || !SignalStore.settings().isPreferSystemEmoji())) {
if (!isInEditMode() && !SignalStore.settings().isPreferSystemEmoji()) {
setFilters(appendEmojiFilter(this.getFilters(), jumboEmoji));
setEmojiCompatEnabled(false);
}

View File

@@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.emoji.EmojiPageCache;
import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.DeviceProperties;
import org.thoughtcrime.securesms.util.FutureTaskListener;
@@ -121,6 +122,10 @@ public class EmojiProvider {
return null;
}
if (SignalStore.settings().isPreferSystemEmoji()) {
return new SystemEmojiDrawable(drawInfo.getEmoji());
}
final int lowMemoryDecodeScale = DeviceProperties.isLowMemoryDevice(context) ? 2 : 1;
final EmojiSource source = EmojiSource.getLatest();
final EmojiDrawable drawable = new EmojiDrawable(source, drawInfo, lowMemoryDecodeScale);
@@ -202,6 +207,10 @@ public class EmojiProvider {
return null;
}
if (SignalStore.settings().isPreferSystemEmoji()) {
return new SystemEmojiDrawable(drawInfo.getEmoji());
}
final int lowMemoryDecodeScale = DeviceProperties.isLowMemoryDevice(context) ? 2 : 1;
final EmojiSource source = EmojiSource.getLatest();
final EmojiDrawable drawable = new EmojiDrawable(source, drawInfo, lowMemoryDecodeScale);

View File

@@ -57,7 +57,6 @@ public class EmojiTextView extends AppCompatTextView {
private static final char ELLIPSIS = '…';
private static final float JUMBOMOJI_SCALE = 0.8f;
private boolean forceCustom;
private CharSequence previousText;
private BufferType previousBufferType;
private TransformationMethod previousTransformationMethod;
@@ -93,7 +92,6 @@ public class EmojiTextView extends AppCompatTextView {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiTextView, 0, 0);
scaleEmojis = a.getBoolean(R.styleable.EmojiTextView_scaleEmojis, false);
maxLength = a.getInteger(R.styleable.EmojiTextView_emoji_maxLength, -1);
forceCustom = a.getBoolean(R.styleable.EmojiTextView_emoji_forceCustom, false);
renderMentions = a.getBoolean(R.styleable.EmojiTextView_emoji_renderMentions, true);
measureLastLine = a.getBoolean(R.styleable.EmojiTextView_measureLastLine, false);
forceJumboEmoji = a.getBoolean(R.styleable.EmojiTextView_emoji_forceJumbo, false);
@@ -445,7 +443,7 @@ public class EmojiTextView extends AppCompatTextView {
}
private boolean useSystemEmoji() {
return isInEditMode() || (!forceCustom && SignalStore.settings().isPreferSystemEmoji());
return isInEditMode() || SignalStore.settings().isPreferSystemEmoji();
}
@Override

View File

@@ -0,0 +1,69 @@
package org.thoughtcrime.securesms.components.emoji
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Matrix
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.os.Build
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import androidx.core.graphics.toRectF
import androidx.core.graphics.withMatrix
import androidx.emoji2.text.EmojiCompat
/**
* [Drawable] that renders an emoji via the system font for available glyphs and EmojiCompat for
* missing glyphs.
*/
class SystemEmojiDrawable(emoji: CharSequence) : Drawable() {
private val emojiLayout: StaticLayout = getStaticLayout(getProcessedEmoji(emoji))
private val transform: Matrix = Matrix()
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
transform.setRectToRect(emojiLayout.getBounds(), bounds.toRectF(), Matrix.ScaleToFit.CENTER)
}
override fun draw(canvas: Canvas) {
canvas.withMatrix(transform) {
emojiLayout.draw(canvas)
}
}
override fun setAlpha(alpha: Int) {}
override fun setColorFilter(colorFilter: ColorFilter?) {}
@Deprecated(
"Deprecated in Java",
ReplaceWith("PixelFormat.TRANSLUCENT", "android.graphics.PixelFormat")
)
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
companion object {
private val textPaint: TextPaint = TextPaint()
private fun getStaticLayout(emoji: CharSequence): StaticLayout =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
StaticLayout.Builder.obtain(emoji, 0, emoji.length, textPaint, Int.MAX_VALUE).build()
} else {
@Suppress("DEPRECATION")
StaticLayout(emoji, textPaint, Int.MAX_VALUE, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true)
}
private fun getProcessedEmoji(emoji: CharSequence): CharSequence =
try {
EmojiCompat.get().process(emoji) ?: emoji
} catch (e: IllegalStateException) {
emoji
}
private fun StaticLayout.getBounds(): RectF =
RectF(getLineLeft(0), 0f, getLineRight(0), getLineDescent(0) - getLineAscent(0).toFloat())
}
}

View File

@@ -2,4 +2,4 @@ package org.thoughtcrime.securesms.components.emoji.parsing
import org.thoughtcrime.securesms.emoji.EmojiPage
data class EmojiDrawInfo(val page: EmojiPage, val index: Int, private val emoji: String, val rawEmoji: String?, val jumboSheet: String?)
data class EmojiDrawInfo(val page: EmojiPage, val index: Int, val emoji: String, val rawEmoji: String?, val jumboSheet: String?)

View File

@@ -34,7 +34,6 @@
android:layout_height="0dp"
android:fontFamily="sans-serif-medium"
android:gravity="center"
app:emoji_forceCustom="true"
app:emoji_forceJumbo="true"
app:layout_constraintBottom_toBottomOf="@id/avatar_picker_item_image"
app:layout_constraintEnd_toEndOf="@id/avatar_picker_item_image"

View File

@@ -24,8 +24,7 @@
android:fontFamily="sans-serif-medium"
android:gravity="center"
tools:text="AF"
app:emoji_forceJumbo="true"
app:emoji_forceCustom="true"/>
app:emoji_forceJumbo="true"/>
<View
android:id="@+id/avatar_picker_item_fader"

View File

@@ -79,7 +79,6 @@
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:textColor="@color/signal_text_secondary"
android:textDirection="ltr"
app:emoji_forceCustom="true"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/label"

View File

@@ -94,7 +94,6 @@
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:textColor="@color/signal_text_secondary"
android:textDirection="ltr"
app:emoji_forceCustom="true"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/name"

View File

@@ -79,7 +79,6 @@
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:textColor="@color/signal_text_secondary"
android:textDirection="ltr"
app:emoji_forceCustom="true"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/check_box"

View File

@@ -81,7 +81,6 @@
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textAppearance="@style/Signal.Text.MessageRequest.Subtitle"
app:emoji_forceCustom="true"
app:layout_constraintBottom_toTopOf="@id/message_request_divider"
app:layout_constraintTop_toBottomOf="@id/message_request_title"
tools:text="Hangin' on the web" />

View File

@@ -45,8 +45,7 @@
android:maxLines="1"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintStart_toEndOf="@id/edit_about_emoji"
app:layout_constraintEnd_toStartOf="@id/edit_about_clear"
app:emoji_forceCustom="true"/>
app:layout_constraintEnd_toStartOf="@id/edit_about_clear"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/edit_about_clear"

View File

@@ -265,7 +265,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:textAlignment="viewStart"
app:emoji_forceCustom="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/manage_profile_about_icon"
@@ -304,7 +303,6 @@
android:layout_marginStart="24dp"
android:text="@string/ManageProfileFragment_badges"
android:textAlignment="viewStart"
app:emoji_forceCustom="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/manage_profile_badges_icon"

View File

@@ -64,8 +64,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapSentences"
android:maxLines="1"
app:emoji_forceCustom="true" />
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>

View File

@@ -74,7 +74,6 @@
android:textAlignment="viewStart"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_text_secondary"
app:emoji_forceCustom="true"
app:layout_constraintBottom_toBottomOf="@+id/recipient_avatar"
app:layout_constraintEnd_toStartOf="@+id/admin"
app:layout_constraintHorizontal_bias="0"

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:viewBindingIgnore="true"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
@@ -20,8 +19,7 @@
android:layout_height="20dp"
android:layout_marginEnd="4dp"
android:gravity="center"
android:textStyle="bold"
app:emoji_forceCustom="true" />
android:textStyle="bold" />
<TextView
android:id="@+id/reactions_bottom_view_emoji_item_text"

View File

@@ -51,7 +51,6 @@
android:layout_marginEnd="16dp"
android:gravity="center"
android:textSize="22dp"
app:emoji_forceCustom="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"

View File

@@ -82,7 +82,6 @@
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:gravity="center"
android:textColor="@color/signal_text_secondary"
app:emoji_forceCustom="true"
app:layout_constraintTop_toBottomOf="@id/rbs_full_name"
tools:text="🕷🕷🕷 Hangin' on the web 🕷🕷🕷" />

View File

@@ -41,7 +41,6 @@
app:layout_constraintEnd_toEndOf="@id/avatar_picker_item_image"
app:layout_constraintStart_toStartOf="@id/avatar_picker_item_image"
app:layout_constraintTop_toTopOf="@id/avatar_picker_item_image"
app:emoji_forceCustom="true"
tools:ignore="SpUsage"
tools:text="AF" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -149,7 +149,6 @@
<attr name="measureLastLine" format="boolean" />
<attr name="scaleEmojis" format="boolean" />
<attr name="emoji_maxLength" format="integer" />
<attr name="emoji_forceCustom" format="boolean" />
<attr name="emoji_renderMentions" format="boolean" />
<attr name="emoji_forceJumbo" format="boolean" />
<attr name="emoji_renderSpoilers" format="boolean" />

View File

@@ -95,6 +95,7 @@ dependencyResolutionManagement {
library("androidx-profileinstaller", "androidx.profileinstaller:profileinstaller:1.2.2")
library("androidx-asynclayoutinflater", "androidx.asynclayoutinflater:asynclayoutinflater:1.1.0-alpha01")
library("androidx-asynclayoutinflater-appcompat", "androidx.asynclayoutinflater:asynclayoutinflater-appcompat:1.1.0-alpha01")
library("androidx-emoji2", "androidx.emoji2:emoji2:1.4.0")
// Material
library("material-material", "com.google.android.material:material:1.8.0")