Fix chevron appearing below the recipient name.

This commit is contained in:
jeffrey-signal
2026-03-17 11:52:17 -04:00
committed by Michelle Tang
parent 95149764eb
commit 6fbf4d4ae6
4 changed files with 67 additions and 124 deletions

View File

@@ -2,23 +2,16 @@ package org.thoughtcrime.securesms.components.settings.conversation.preferences
import android.content.ClipData
import android.content.Context
import android.text.SpannableStringBuilder
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
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
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
import org.signal.core.ui.R as CoreUiR
/**
* Renders name, description, about, etc. for a given group or recipient.
@@ -44,54 +37,7 @@ object BioTextPreference {
) : BioTextPreferenceModel<RecipientModel>() {
override fun getHeadlineText(context: Context): CharSequence {
val name = if (recipient.isSelf) {
context.getString(R.string.note_to_self)
} else {
recipient.getDisplayName(context)
}
if (!recipient.showVerified && !recipient.isIndividual) {
return name
}
return SpannableStringBuilder(name).apply {
if (recipient.showVerified) {
SpanUtil.appendSpacer(this, 8)
SpanUtil.appendCenteredImageSpanWithoutSpace(this, ContextUtil.requireDrawable(context, R.drawable.ic_official_28), 28, 28)
} else if (recipient.isSystemContact) {
val systemContactGlyph = SignalSymbols.getSpannedString(
context,
SignalSymbols.Weight.BOLD,
SignalSymbols.Glyph.PERSON_CIRCLE
).let {
SpanUtil.ofSize(it, 20)
}
append(" ")
append(systemContactGlyph)
}
if (recipient.isIndividual && !recipient.isSelf) {
val isLtr = ViewUtil.isLtr(context)
val chevronGlyph = SignalSymbols.getSpannedString(
context,
SignalSymbols.Weight.BOLD,
if (isLtr) SignalSymbols.Glyph.CHEVRON_RIGHT else SignalSymbols.Glyph.CHEVRON_LEFT
).let {
SpanUtil.ofSize(it, 24)
}.let {
SpanUtil.color(ContextCompat.getColor(context, CoreUiR.color.signal_colorOutline), it)
}
if (isLtr) {
append(" ")
append(chevronGlyph)
} else {
insert(0, " ")
insert(0, chevronGlyph)
}
}
}
return recipient.getDisplayNameForHeadline(context)
}
override fun getSubhead1Text(context: Context): String? {

View File

@@ -16,7 +16,6 @@ import android.view.View;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
@@ -33,11 +32,9 @@ import org.thoughtcrime.securesms.conversation.colors.AvatarGradientColors;
import org.thoughtcrime.securesms.conversation.v2.data.AvatarDownloadStateCache;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.databinding.ConversationHeaderViewBinding;
import org.thoughtcrime.securesms.fonts.SignalSymbols;
import org.thoughtcrime.securesms.jobs.AvatarGroupsV2DownloadJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ContextUtil;
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -130,23 +127,9 @@ public class ConversationHeaderView extends ConstraintLayout {
}
public String setTitle(@NonNull Recipient recipient, @NonNull Runnable onTitleClicked) {
SpannableStringBuilder title = new SpannableStringBuilder(recipient.isSelf() ? getContext().getString(R.string.note_to_self) : recipient.getDisplayName(getContext()));
if (recipient.getShowVerified()) {
SpanUtil.appendCenteredImageSpan(title, ContextUtil.requireDrawable(getContext(), R.drawable.ic_official_28), 28, 28);
}
CharSequence title = recipient.getDisplayNameForHeadline(getContext());
if (recipient.isIndividual() && !recipient.isSelf()) {
boolean isLtr = ViewUtil.isLtr(this);
CharSequence chevron = SignalSymbols.getSpannedString(getContext(), SignalSymbols.Weight.BOLD, isLtr ? SignalSymbols.Glyph.CHEVRON_RIGHT : SignalSymbols.Glyph.CHEVRON_LEFT, org.signal.core.ui.R.color.signal_colorOutline);
if (isLtr) {
title.append(" ");
title.append(SpanUtil.ofSize(chevron, 24));
} else {
title.insert(0, " ");
title.insert(0, SpanUtil.ofSize(chevron, 24));
}
binding.messageRequestTitle.setOnClickListener(v -> onTitleClicked.run());
} else {
binding.messageRequestTitle.setOnClickListener(null);
@@ -225,8 +208,8 @@ public class ConversationHeaderView extends ConstraintLayout {
binding.messageRequestProfileNameUnverified.setVisibility(View.VISIBLE);
binding.messageRequestProfileNameUnverified.setOnClickListener(view -> onClick.run());
String substring = forGroup ? getContext().getString(R.string.ConversationFragment_group_names)
: getContext().getString(R.string.ConversationFragment_profile_names);
String substring = forGroup ? getContext().getString(R.string.ConversationFragment_group_names)
: getContext().getString(R.string.ConversationFragment_profile_names);
String fullString = forGroup ? getContext().getString(R.string.ConversationFragment_group_names_not_verified, substring)
: getContext().getString(R.string.ConversationFragment_profile_names_not_verified, substring);
@@ -273,8 +256,8 @@ public class ConversationHeaderView extends ConstraintLayout {
}
private void animateAvatarLoading(@NonNull Recipient recipient) {
Drawable loadingProfile = AppCompatResources.getDrawable(getContext(), R.drawable.circle_profile_photo);
ObjectAnimator animator = ObjectAnimator.ofFloat(binding.messageRequestAvatar, "alpha", 1f, 0f).setDuration(FADE_DURATION);
Drawable loadingProfile = AppCompatResources.getDrawable(getContext(), R.drawable.circle_profile_photo);
ObjectAnimator animator = ObjectAnimator.ofFloat(binding.messageRequestAvatar, "alpha", 1f, 0f).setDuration(FADE_DURATION);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri
import androidx.annotation.AnyThread
import androidx.annotation.WorkerThread
import androidx.core.text.buildSpannedString
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.collections.immutable.toImmutableList
@@ -41,6 +42,7 @@ import org.thoughtcrime.securesms.database.model.ProfileAvatarFileDetails
import org.thoughtcrime.securesms.database.model.RecipientRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.fonts.SignalSymbols
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
@@ -48,18 +50,22 @@ import org.thoughtcrime.securesms.phonenumbers.NumberUtil
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient.Companion.external
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.thoughtcrime.securesms.util.ContextUtil
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.UsernameUtil.isValidUsernameForSearch
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.api.util.OptionalUtil
import java.util.LinkedList
import java.util.Objects
import java.util.Optional
import org.signal.core.ui.R as CoreUiR
/**
* A recipient represents something you can send messages to, or receive messages from. They could be individuals, groups, or even distribution lists.
* A recipient represents something you can send messages to or receive messages from. They could be individuals, groups, or even distribution lists.
* This class is a snapshot of common state that is used to present recipients through the UI.
*
* It's important to note that this is only a snapshot, and the actual state of a recipient can change over time.
@@ -387,13 +393,14 @@ class Recipient(
* The badge to feature on a recipient's avatar, if any.
* This value respects the local user's [SignalStore.inAppPayments.getDisplayBadgesOnProfile()] preference.
*/
val featuredBadge: Badge? get() {
return if (isSelf && !SignalStore.inAppPayments.getDisplayBadgesOnProfile()) {
null
} else {
badges.firstOrNull()
val featuredBadge: Badge?
get() {
return if (isSelf && !SignalStore.inAppPayments.getDisplayBadgesOnProfile()) {
null
} else {
badges.firstOrNull()
}
}
}
/** A string combining the about emoji + text for displaying various places. */
val combinedAboutAndEmoji: String? by lazy { listOf(aboutEmoji, about).filter { it.isNotNullOrBlank() }.joinToString(separator = " ").nullIfBlank() }
@@ -659,6 +666,47 @@ class Recipient(
}
}
/**
* Gets the recipient's display name with any applicable decorations:
* - A badge icon for verified recipients
* - A person-circle glyph for system contacts
* - A directional chevron for tappable individual profiles
*/
fun getDisplayNameForHeadline(context: Context): CharSequence {
val name = if (isSelf) context.getString(R.string.note_to_self) else getDisplayName(context)
return buildSpannedString {
append(name)
if (showVerified) {
val verifiedBadge = ContextUtil.requireDrawable(context, R.drawable.ic_official_28)
SpanUtil.appendSpacer(this, 8)
SpanUtil.appendCenteredImageSpanWithoutSpace(this, verifiedBadge, 28, 28)
} else if (isSystemContact) {
val systemContactGlyph = SignalSymbols
.getSpannedString(context, SignalSymbols.Weight.BOLD, SignalSymbols.Glyph.PERSON_CIRCLE)
.let { SpanUtil.ofSize(it, 20) }
append("\u00A0")
append(systemContactGlyph)
}
if (isIndividual && !isSelf) {
val isLtr = ViewUtil.isLtr(context)
val chevronGlyph = SignalSymbols.getSpannedString(context, SignalSymbols.Weight.BOLD, if (isLtr) SignalSymbols.Glyph.CHEVRON_RIGHT else SignalSymbols.Glyph.CHEVRON_LEFT, CoreUiR.color.signal_colorOutline)
.let { SpanUtil.ofSize(it, 24) }
if (isLtr) {
append("\u00A0")
append(chevronGlyph)
} else {
insert(0, "\u00A0")
insert(0, chevronGlyph)
}
}
}
}
fun getFallbackAvatar(): FallbackAvatar {
return if (isSelf) {
FallbackAvatar.Resource.Local(avatarColor)

View File

@@ -8,7 +8,6 @@ import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -34,7 +33,6 @@ import org.thoughtcrime.securesms.calls.YouAreAlreadyInACallSnackbar
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference
import org.thoughtcrime.securesms.conversation.v2.data.AvatarDownloadStateCache
import org.thoughtcrime.securesms.fonts.SignalSymbols
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelEducationSheet
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelPillView
@@ -45,11 +43,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.recipients.RecipientUtil
import org.thoughtcrime.securesms.recipients.ui.about.AboutSheet
import org.thoughtcrime.securesms.util.ContextUtil
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.WindowUtil
import org.thoughtcrime.securesms.util.visible
import org.signal.core.ui.R as CoreUiR
/**
* A bottom sheet that shows some simple recipient details, as well as some actions (like calling,
@@ -216,42 +211,13 @@ class RecipientBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDialogFr
tapToView.setOnClickListener(null)
}
val name = if (recipient.isSelf) requireContext().getString(R.string.note_to_self) else recipient.getDisplayName(requireContext())
fullName.visible = name.isNotEmpty()
val nameBuilder = SpannableStringBuilder(name)
if (recipient.showVerified) {
SpanUtil.appendSpacer(nameBuilder, 8)
SpanUtil.appendCenteredImageSpanWithoutSpace(nameBuilder, ContextUtil.requireDrawable(requireContext(), R.drawable.ic_official_28), 28, 28)
} else if (recipient.isSystemContact) {
val systemContactGlyph = SignalSymbols.getSpannedString(
requireContext(),
SignalSymbols.Weight.BOLD,
SignalSymbols.Glyph.PERSON_CIRCLE
)
nameBuilder.append(" ")
nameBuilder.append(SpanUtil.ofSize(systemContactGlyph, 20))
val name = recipient.getDisplayNameForHeadline(requireContext())
fullName.apply {
text = name
visible = name.isNotEmpty()
}
if (!recipient.isSelf && recipient.isIndividual) {
val isLtr = ViewUtil.isLtr(view)
val chevronGlyph = SignalSymbols.getSpannedString(
requireContext(),
SignalSymbols.Weight.BOLD,
if (isLtr) SignalSymbols.Glyph.CHEVRON_RIGHT else SignalSymbols.Glyph.CHEVRON_LEFT,
CoreUiR.color.signal_colorOutline
)
if (isLtr) {
nameBuilder.append(" ")
nameBuilder.append(SpanUtil.ofSize(chevronGlyph, 24))
} else {
nameBuilder.insert(0, " ")
nameBuilder.insert(0, SpanUtil.ofSize(chevronGlyph, 24))
}
fullName.text = nameBuilder
fullName.setOnClickListener {
dismiss()
AboutSheet.create(recipient).show(getParentFragmentManager(), null)
@@ -261,8 +227,8 @@ class RecipientBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDialogFr
nickname.setOnClickListener {
nicknameLauncher.launch(NicknameActivity.Args(recipientId, false))
}
} else if (recipient.isReleaseNotes) {
fullName.text = name
} else {
fullName.setOnClickListener(null)
}
noteToSelfDescription.visible = recipient.isSelf