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")