diff --git a/app/src/main/assets/fonts/SignalSymbols-Bold.otf b/app/src/main/assets/fonts/SignalSymbols-Bold.otf new file mode 100644 index 0000000000..8aed1da3c7 Binary files /dev/null and b/app/src/main/assets/fonts/SignalSymbols-Bold.otf differ diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/BioTextPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/BioTextPreference.kt index 79f282c0fa..936cf6e725 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/BioTextPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/BioTextPreference.kt @@ -7,9 +7,9 @@ import android.view.View import android.widget.TextView import android.widget.Toast import androidx.core.content.ContextCompat -import org.signal.core.util.dp import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.PreferenceModel +import org.thoughtcrime.securesms.fonts.SignalSymbols import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.ContextUtil import org.thoughtcrime.securesms.util.ServiceUtil @@ -57,20 +57,31 @@ object BioTextPreference { SpanUtil.appendSpacer(this, 8) SpanUtil.appendCenteredImageSpanWithoutSpace(this, ContextUtil.requireDrawable(context, R.drawable.ic_official_28), 28, 28) } else if (recipient.isSystemContact) { - val drawable = ContextUtil.requireDrawable(context, R.drawable.symbol_person_circle_24).apply { - setTint(ContextCompat.getColor(context, R.color.signal_colorOnSurface)) + val systemContactGlyph = SignalSymbols.getSpannedString( + context, + SignalSymbols.Weight.BOLD, + SignalSymbols.Glyph.PERSON_CIRCLE + ).let { + SpanUtil.ofSize(it, 20) } - SpanUtil.appendSpacer(this, 8) - SpanUtil.appendCenteredImageSpanWithoutSpace(this, drawable, 24, 24) + + append(" ") + append(systemContactGlyph) } if (recipient.isIndividual && !recipient.isSelf) { - val drawable = ContextUtil.requireDrawable(context, R.drawable.symbol_chevron_right_24).apply { - setBounds(0, 0, 24.dp, 24.dp) - setTint(ContextCompat.getColor(context, R.color.signal_colorOutline)) + val chevronGlyph = SignalSymbols.getSpannedString( + context, + SignalSymbols.Weight.BOLD, + SignalSymbols.Glyph.CHEVRON_RIGHT + ).let { + SpanUtil.ofSize(it, 24) + }.let { + SpanUtil.color(ContextCompat.getColor(context, R.color.signal_colorOutline), it) } - append(SpanUtil.buildCenteredImageSpan(drawable)) + append(" ") + append(chevronGlyph) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/fonts/SignalSymbols.kt b/app/src/main/java/org/thoughtcrime/securesms/fonts/SignalSymbols.kt new file mode 100644 index 0000000000..52fc3cf5aa --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/fonts/SignalSymbols.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.fonts + +import android.content.Context +import android.graphics.Typeface +import android.text.SpannableStringBuilder +import android.text.TextPaint +import android.text.style.MetricAffectingSpan + +/** + * Helper object for working with the SignalSymbols font + */ +object SignalSymbols { + + enum class Glyph(val unicode: Char) { + CHEVRON_RIGHT('\uE025'), + PERSON_CIRCLE('\uE05E') + } + + enum class Weight { + BOLD + } + + private val cache = mutableMapOf() + + fun getSpannedString( + context: Context, + weight: Weight, + glyph: Glyph + ): CharSequence { + val typeface = getTypeface(context, weight) + val span = CustomTypefaceSpan(typeface) + + val text = SpannableStringBuilder(glyph.unicode.toString()) + text.setSpan(span, 0, text.length, 0) + + return text + } + + private fun getTypeface(context: Context, weight: Weight): Typeface { + return when (weight) { + Weight.BOLD -> getBoldWeightedFont(context) + else -> error("Unsupported weight: $weight") + } + } + + private fun getBoldWeightedFont(context: Context): Typeface { + return cache.getOrPut( + Weight.BOLD + ) { + Typeface.createFromAsset( + context.assets, + "fonts/SignalSymbols-Bold.otf" + ) + } + } + + /** + * Custom TypefaceSpan to support TypefaceSpan in API<28 + * + * Source: https://www.youtube.com/watch?v=x-FcOX6ErdI&t=486s + */ + private class CustomTypefaceSpan(val font: Typeface?) : MetricAffectingSpan() { + override fun updateMeasureState(textPaint: TextPaint) = update(textPaint) + override fun updateDrawState(textPaint: TextPaint?) = update(textPaint) + + private fun update(tp: TextPaint?) { + tp.apply { + val old = this!!.typeface + val oldStyle = old?.style ?: 0 + val font = Typeface.create(font, oldStyle) + typeface = font + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java index 2a3fc5f564..0d100ab2e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java @@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon; import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference; import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp; +import org.thoughtcrime.securesms.fonts.SignalSymbols; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.Recipient; @@ -191,17 +192,22 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF SpanUtil.appendSpacer(nameBuilder, 8); SpanUtil.appendCenteredImageSpanWithoutSpace(nameBuilder, ContextUtil.requireDrawable(requireContext(), R.drawable.ic_official_28), 28, 28); } else if (recipient.isSystemContact()) { - Drawable drawable = ContextUtil.requireDrawable(requireContext(), R.drawable.symbol_person_circle_24); - drawable.setTint(ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurface)); - SpanUtil.appendSpacer(nameBuilder, 8); - SpanUtil.appendCenteredImageSpanWithoutSpace(nameBuilder, drawable, 24, 24); + CharSequence systemContactGlyph = SignalSymbols.INSTANCE.getSpannedString(requireContext(), + SignalSymbols.Weight.BOLD, + SignalSymbols.Glyph.PERSON_CIRCLE); + + nameBuilder.append(" "); + nameBuilder.append(SpanUtil.ofSize(systemContactGlyph, 20)); } if (!recipient.isSelf() && recipient.isIndividual()) { - Drawable drawable = ContextUtil.requireDrawable(requireContext(), R.drawable.symbol_chevron_right_24); - drawable.setBounds(0, 0, (int) DimensionUnit.DP.toPixels(24), (int) DimensionUnit.DP.toPixels(24)); - drawable.setTint(ContextCompat.getColor(requireContext(), R.color.signal_colorOutline)); - nameBuilder.append(SpanUtil.buildCenteredImageSpan(drawable)); + CharSequence chevronGlyph = SignalSymbols.INSTANCE.getSpannedString(requireContext(), + SignalSymbols.Weight.BOLD, + SignalSymbols.Glyph.CHEVRON_RIGHT); + + nameBuilder.append(" "); + nameBuilder.append(SpanUtil.color(ContextCompat.getColor(requireContext(), R.color.signal_colorOutline), + SpanUtil.ofSize(chevronGlyph, 24))); fullName.setText(nameBuilder); fullName.setOnClickListener(v -> {