diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ffca222bf6..4a8f715be4 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -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")
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java
index b62ed01e03..b77a98f5fa 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java
@@ -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);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java
index b617522d40..431dcf226d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java
@@ -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);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
index d44efa9c37..e18ca73878 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
@@ -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
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/SystemEmojiDrawable.kt b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/SystemEmojiDrawable.kt
new file mode 100644
index 0000000000..d4a5b43116
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/SystemEmojiDrawable.kt
@@ -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())
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt
index 28de3aca78..76978f5212 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt
@@ -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?)
diff --git a/app/src/main/res/layout/avatar_picker_fragment.xml b/app/src/main/res/layout/avatar_picker_fragment.xml
index 5835deac5c..09f368e8ae 100644
--- a/app/src/main/res/layout/avatar_picker_fragment.xml
+++ b/app/src/main/res/layout/avatar_picker_fragment.xml
@@ -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"
diff --git a/app/src/main/res/layout/avatar_picker_item.xml b/app/src/main/res/layout/avatar_picker_item.xml
index d65160801a..3e531811cc 100644
--- a/app/src/main/res/layout/avatar_picker_item.xml
+++ b/app/src/main/res/layout/avatar_picker_item.xml
@@ -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"/>
diff --git a/app/src/main/res/layout/edit_about_fragment.xml b/app/src/main/res/layout/edit_about_fragment.xml
index 26b3f4cd4f..5bb293652f 100644
--- a/app/src/main/res/layout/edit_about_fragment.xml
+++ b/app/src/main/res/layout/edit_about_fragment.xml
@@ -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"/>
+ android:maxLines="1" />
diff --git a/app/src/main/res/layout/group_recipient_list_item.xml b/app/src/main/res/layout/group_recipient_list_item.xml
index 0cb100a759..67763f019c 100644
--- a/app/src/main/res/layout/group_recipient_list_item.xml
+++ b/app/src/main/res/layout/group_recipient_list_item.xml
@@ -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"
diff --git a/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_emoji_item.xml b/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_emoji_item.xml
index 3fe4af0f84..62e05ee0a2 100644
--- a/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_emoji_item.xml
+++ b/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_emoji_item.xml
@@ -1,6 +1,5 @@
+ android:textStyle="bold" />
diff --git a/app/src/main/res/layout/text_avatar_creation_fragment_content_hidden_recycler.xml b/app/src/main/res/layout/text_avatar_creation_fragment_content_hidden_recycler.xml
index 0232587eae..6b55709296 100644
--- a/app/src/main/res/layout/text_avatar_creation_fragment_content_hidden_recycler.xml
+++ b/app/src/main/res/layout/text_avatar_creation_fragment_content_hidden_recycler.xml
@@ -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" />
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 1c1bb2c3e9..aa60e16d66 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -149,7 +149,6 @@
-
diff --git a/dependencies.gradle.kts b/dependencies.gradle.kts
index c5bb2aee70..6dbd96e202 100644
--- a/dependencies.gradle.kts
+++ b/dependencies.gradle.kts
@@ -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")