mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Improve system emoji rendering across the app with EmojiCompat2.
Resolves #13327
This commit is contained in:
committed by
Greyson Parrelli
parent
abd80c5204
commit
10922594b3
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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?)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 🕷🕷🕷" />
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user