mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 00:29:11 +01:00
Add group member labels to conversation items.
This commit is contained in:
committed by
Greyson Parrelli
parent
d709d67f54
commit
78e7f99344
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -106,6 +105,7 @@ import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectCollection;
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
|
||||
import org.thoughtcrime.securesms.conversation.ui.payment.PaymentMessageView;
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.InteractiveConversationElement;
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.SenderNameWithLabelView;
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemUtils;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.MediaTable;
|
||||
@@ -212,8 +212,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
private EmojiTextView bodyText;
|
||||
private ConversationItemFooter footer;
|
||||
@Nullable private ConversationItemFooter stickerFooter;
|
||||
@Nullable private TextView groupSender;
|
||||
@Nullable private View groupSenderHolder;
|
||||
@Nullable private SenderNameWithLabelView senderWithLabelView;
|
||||
private AvatarImageView contactPhoto;
|
||||
private AlertView alertView;
|
||||
private ReactionsConversationView reactionsView;
|
||||
@@ -334,7 +333,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
this.bodyText = findViewById(R.id.conversation_item_body);
|
||||
this.footer = findViewById(R.id.conversation_item_footer);
|
||||
this.stickerFooter = findViewById(R.id.conversation_item_sticker_footer);
|
||||
this.groupSender = findViewById(R.id.group_message_sender);
|
||||
this.senderWithLabelView = findViewById(R.id.group_sender_name_with_label);
|
||||
this.alertView = findViewById(R.id.indicators_parent);
|
||||
this.contactPhoto = findViewById(R.id.contact_photo);
|
||||
this.contactPhotoHolder = findViewById(R.id.contact_photo_container);
|
||||
@@ -348,7 +347,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
this.revealableStub = new Stub<>(findViewById(R.id.revealable_view_stub));
|
||||
this.joinCallLinkStub = ViewUtil.findStubById(this, R.id.conversation_item_join_button);
|
||||
this.callToActionStub = ViewUtil.findStubById(this, R.id.conversation_item_call_to_action_stub);
|
||||
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
||||
this.quoteView = findViewById(R.id.quote_view);
|
||||
this.reply = findViewById(R.id.reply_icon_wrapper);
|
||||
this.replyIcon = findViewById(R.id.reply_icon);
|
||||
@@ -417,8 +415,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
setInteractionState(conversationMessage, pulse);
|
||||
setStatusIcons(messageRecord, hasWallpaper);
|
||||
setContactPhoto(author.get());
|
||||
setGroupMessageStatus(messageRecord, author.get());
|
||||
setGroupAuthorColor(messageRecord, hasWallpaper, colorizer);
|
||||
setSenderNameAndLabel(author.get());
|
||||
setAuthor(messageRecord, previousMessageRecord, nextMessageRecord, groupThread, hasWallpaper);
|
||||
setQuote(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
setMessageSpacing(context, messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
@@ -456,7 +453,9 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
|
||||
@Override
|
||||
public void updateContactNameColor() {
|
||||
setGroupAuthorColor(messageRecord, hasWallpaper, colorizer);
|
||||
if (senderWithLabelView != null && messageRecord != null) {
|
||||
setSenderNameAndLabel(messageRecord.getFromRecipient());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -723,7 +722,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
|
||||
if (author.getId().equals(modified.getId())) {
|
||||
setContactPhoto(modified);
|
||||
setGroupMessageStatus(messageRecord, modified);
|
||||
setSenderNameAndLabel(modified);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1119,7 +1118,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
if (hasExtraText(messageRecord)) {
|
||||
bodyText.setOverflowText(getLongMessageSpan(messageRecord));
|
||||
int trimmedLength = TextUtils.getTrimmedLength(styledText);
|
||||
int maxLength = Math.min(MessageRecordUtil.MAX_BODY_DISPLAY_LENGTH, trimmedLength - 2);
|
||||
int maxLength = Math.min(MessageRecordUtil.MAX_BODY_DISPLAY_LENGTH, trimmedLength - 2);
|
||||
bodyText.setMaxLength(maxLength);
|
||||
}
|
||||
|
||||
@@ -1215,7 +1214,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
setSharedContactCorners(messageRecord, previousRecord, nextRecord, isGroupThread);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
footer.setVisibility(GONE);
|
||||
} else if (hasLinkPreview(messageRecord) && messageRequestAccepted) {
|
||||
linkPreviewStub.get().setVisibility(View.VISIBLE);
|
||||
@@ -1261,14 +1259,12 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
setLinkPreviewCorners(messageRecord, previousRecord, nextRecord, isGroupThread, true);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.setTopMargin(linkPreviewStub.get(), 0);
|
||||
} else {
|
||||
linkPreviewStub.get().setLinkPreview(requestManager, linkPreview, true, !isContentCondensed(), displayMode.getMessageMode() == ConversationItemDisplayMode.MessageMode.SCHEDULED);
|
||||
linkPreviewStub.get().setDownloadClickedListener(downloadClickListener);
|
||||
setLinkPreviewCorners(messageRecord, previousRecord, nextRecord, isGroupThread, false);
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
int topMargin = isGroupThread && isStartOfMessageCluster(messageRecord, previousRecord, isGroupThread) && !messageRecord.isOutgoing() ? readDimen(R.dimen.message_bubble_top_padding) : 0;
|
||||
@@ -1304,7 +1300,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
}
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
footer.setPlaybackSpeedListener(new AudioPlaybackSpeedToggleListener());
|
||||
footer.setVisibility(VISIBLE);
|
||||
@@ -1333,7 +1328,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
documentViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.setTopMargin(bodyText, 0);
|
||||
|
||||
footer.setVisibility(VISIBLE);
|
||||
@@ -1366,7 +1360,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
stickerStub.get().setOnClickListener(passthroughClickListener);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
footer.setVisibility(VISIBLE);
|
||||
} else if (hasNoBubble(messageRecord)) {
|
||||
@@ -1418,7 +1411,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
setThumbnailCorners(messageRecord, previousRecord, nextRecord, isGroupThread);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
footer.setVisibility(VISIBLE);
|
||||
|
||||
@@ -1503,11 +1495,9 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
paymentViewStub.setVisibility(View.GONE);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
footer.setVisibility(VISIBLE);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
int topMargin = !messageRecord.isOutgoing() && isGroupThread && isStartOfMessageCluster(messageRecord, previousRecord, isGroupThread)
|
||||
? readDimen(R.dimen.message_bubble_text_only_top_margin)
|
||||
: readDimen(R.dimen.message_bubble_top_padding);
|
||||
@@ -1652,12 +1642,12 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
|
||||
if (conversationMessage.hasStyleLinks()) {
|
||||
for (PlaceholderURLSpan placeholder : messageBody.getSpans(0, messageBody.length(), PlaceholderURLSpan.class)) {
|
||||
int start = messageBody.getSpanStart(placeholder);
|
||||
int end = messageBody.getSpanEnd(placeholder);
|
||||
URLSpan span = new InterceptableLongClickCopyLinkSpan(placeholder.getValue(),
|
||||
urlClickListener,
|
||||
ContextCompat.getColor(getContext(), R.color.signal_accent_primary),
|
||||
false);
|
||||
int start = messageBody.getSpanStart(placeholder);
|
||||
int end = messageBody.getSpanEnd(placeholder);
|
||||
URLSpan span = new InterceptableLongClickCopyLinkSpan(placeholder.getValue(),
|
||||
urlClickListener,
|
||||
ContextCompat.getColor(getContext(), R.color.signal_accent_primary),
|
||||
false);
|
||||
|
||||
messageBody.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
@@ -1966,16 +1956,16 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
messageRecord.isBundleKeyExchange());
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) {
|
||||
if (groupThread && !messageRecord.isOutgoing() && groupSender != null) {
|
||||
groupSender.setText(recipient.getDisplayName(getContext()));
|
||||
}
|
||||
}
|
||||
private void setSenderNameAndLabel(@NonNull Recipient recipient) {
|
||||
if (senderWithLabelView == null) return;
|
||||
|
||||
private void setGroupAuthorColor(@NonNull MessageRecord messageRecord, boolean hasWallpaper, @NonNull Colorizer colorizer) {
|
||||
if (groupSender != null) {
|
||||
groupSender.setTextColor(colorizer.getIncomingGroupSenderColor(getContext(), messageRecord.getFromRecipient()));
|
||||
if (groupThread && !messageRecord.isOutgoing()) {
|
||||
String senderName = recipient.getDisplayName(getContext());
|
||||
int senderColor = colorizer.getIncomingGroupSenderColor(getContext(), messageRecord.getFromRecipient());
|
||||
senderWithLabelView.setSender(senderName, senderColor);
|
||||
senderWithLabelView.setLabel(conversationMessage.getMemberLabel());
|
||||
} else {
|
||||
senderWithLabelView.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1987,16 +1977,18 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
if (!previous.isPresent() || previous.get().isUpdate() || !current.getFromRecipient().equals(previous.get().getFromRecipient()) ||
|
||||
!DateUtils.isSameDay(previous.get().getTimestamp(), current.getTimestamp()) || !isWithinClusteringTime(current, previous.get()) || forceGroupHeader(current))
|
||||
{
|
||||
groupSenderHolder.setVisibility(VISIBLE);
|
||||
senderWithLabelView.setVisibility(VISIBLE);
|
||||
adjustMarginsForSenderVisibility(true);
|
||||
|
||||
if (hasWallpaper && hasNoBubble(current)) {
|
||||
groupSenderHolder.setBackgroundResource(R.drawable.wallpaper_bubble_background_tintable_11);
|
||||
groupSenderHolder.getBackground().setColorFilter(getDefaultBubbleColor(hasWallpaper), PorterDuff.Mode.MULTIPLY);
|
||||
senderWithLabelView.setBackgroundResource(R.drawable.wallpaper_bubble_background_tintable_11);
|
||||
senderWithLabelView.getBackground().setColorFilter(getDefaultBubbleColor(hasWallpaper), PorterDuff.Mode.MULTIPLY);
|
||||
} else {
|
||||
groupSenderHolder.setBackground(null);
|
||||
senderWithLabelView.setBackground(null);
|
||||
}
|
||||
} else {
|
||||
groupSenderHolder.setVisibility(GONE);
|
||||
senderWithLabelView.setVisibility(GONE);
|
||||
adjustMarginsForSenderVisibility(false);
|
||||
}
|
||||
|
||||
if (!next.isPresent() || next.get().isUpdate() || !current.getFromRecipient().equals(next.get().getFromRecipient()) || !isWithinClusteringTime(current, next.get()) || forceGroupHeader(current)) {
|
||||
@@ -2007,9 +1999,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
badgeImageView.setVisibility(GONE);
|
||||
}
|
||||
} else {
|
||||
if (groupSenderHolder != null) {
|
||||
groupSenderHolder.setVisibility(GONE);
|
||||
if (senderWithLabelView != null) {
|
||||
senderWithLabelView.setVisibility(GONE);
|
||||
}
|
||||
adjustMarginsForSenderVisibility(false);
|
||||
|
||||
if (contactPhotoHolder != null) {
|
||||
contactPhotoHolder.setVisibility(GONE);
|
||||
@@ -2021,6 +2014,14 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustMarginsForSenderVisibility(boolean senderNameVisible) {
|
||||
ViewUtil.setTopMargin(bodyText, senderNameVisible ? 0 : readDimen(R.dimen.message_bubble_top_padding));
|
||||
|
||||
if (audioViewStub.resolved()) {
|
||||
ViewUtil.setTopMargin(audioViewStub.get(), senderNameVisible ? 0 : readDimen(R.dimen.message_bubble_top_padding_audio));
|
||||
}
|
||||
}
|
||||
|
||||
private void setOutlinerRadii(Outliner outliner, int topStart, int topEnd, int bottomEnd, int bottomStart) {
|
||||
if (ViewUtil.isRtl(this)) {
|
||||
outliner.setRadii(topEnd, topStart, bottomStart, bottomEnd);
|
||||
@@ -2491,6 +2492,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SharedContactEventListener implements SharedContactView.EventListener {
|
||||
@Override
|
||||
public void onAddToContactsClicked(@NonNull Contact contact) {
|
||||
@@ -2622,7 +2624,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
public void onClick(View v, Slide slide) {
|
||||
if (MediaUtil.isInstantVideoSupported(slide)) {
|
||||
final DatabaseAttachment databaseAttachment = (DatabaseAttachment) slide.asAttachment();
|
||||
String jobId = AttachmentDownloadJob.downloadAttachmentIfNeeded(databaseAttachment);
|
||||
String jobId = AttachmentDownloadJob.downloadAttachmentIfNeeded(databaseAttachment);
|
||||
if (jobId != null) {
|
||||
setup(v, slide);
|
||||
AppDependencies.getJobManager().addListener(jobId, (job, jobState) -> {
|
||||
@@ -2660,8 +2662,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
return;
|
||||
}
|
||||
|
||||
final View currentParentView = parentView;
|
||||
float progressPercent = ((float) event.progress) / event.total;
|
||||
final View currentParentView = parentView;
|
||||
float progressPercent = ((float) event.progress) / event.total;
|
||||
if (progressPercent >= MINIMUM_DOWNLOADED_THRESHOLD && currentParentView != null) {
|
||||
cleanup();
|
||||
launchMediaPreview(currentParentView, currentActiveSlide);
|
||||
|
||||
@@ -17,10 +17,12 @@ import org.thoughtcrime.securesms.database.BodyRangeUtil;
|
||||
import org.thoughtcrime.securesms.database.MentionUtil;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
|
||||
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel;
|
||||
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelRepository;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.MessageRecordUtil;
|
||||
@@ -47,6 +49,7 @@ public class ConversationMessage {
|
||||
private final boolean hasBeenQuoted;
|
||||
@Nullable private final MessageRecord originalMessage;
|
||||
@NonNull private final ComputedProperties computedProperties;
|
||||
@Nullable private final MemberLabel memberLabel;
|
||||
|
||||
private ConversationMessage(@NonNull MessageRecord messageRecord,
|
||||
@Nullable CharSequence body,
|
||||
@@ -55,7 +58,8 @@ public class ConversationMessage {
|
||||
@Nullable MessageStyler.Result styleResult,
|
||||
@NonNull Recipient threadRecipient,
|
||||
@Nullable MessageRecord originalMessage,
|
||||
@NonNull ComputedProperties computedProperties)
|
||||
@NonNull ComputedProperties computedProperties,
|
||||
@Nullable MemberLabel memberLabel)
|
||||
{
|
||||
this.messageRecord = messageRecord;
|
||||
this.hasBeenQuoted = hasBeenQuoted;
|
||||
@@ -64,6 +68,7 @@ public class ConversationMessage {
|
||||
this.threadRecipient = threadRecipient;
|
||||
this.originalMessage = originalMessage;
|
||||
this.computedProperties = computedProperties;
|
||||
this.memberLabel = memberLabel;
|
||||
|
||||
if (body != null) {
|
||||
this.body = SpannableString.valueOf(body);
|
||||
@@ -100,6 +105,10 @@ public class ConversationMessage {
|
||||
return computedProperties;
|
||||
}
|
||||
|
||||
public @Nullable MemberLabel getMemberLabel() {
|
||||
return memberLabel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
@@ -234,6 +243,7 @@ public class ConversationMessage {
|
||||
}
|
||||
|
||||
FormattedDate formattedDate = getFormattedDate(context, messageRecord);
|
||||
MemberLabel memberLabel = getMemberLabel(messageRecord, threadRecipient);
|
||||
|
||||
return new ConversationMessage(messageRecord,
|
||||
styledAndMentionBody != null ? styledAndMentionBody : mentionsUpdate != null ? mentionsUpdate.getBody() : body,
|
||||
@@ -242,7 +252,8 @@ public class ConversationMessage {
|
||||
styleResult,
|
||||
threadRecipient,
|
||||
originalMessage,
|
||||
new ComputedProperties(formattedDate));
|
||||
new ComputedProperties(formattedDate),
|
||||
memberLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,5 +290,13 @@ public class ConversationMessage {
|
||||
|
||||
return createWithUnresolvedData(context, messageRecord, body, mentions, hasBeenQuoted, threadRecipient);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static @Nullable MemberLabel getMemberLabel(@NonNull MessageRecord messageRecord, @NonNull Recipient threadRecipient) {
|
||||
if (messageRecord.isOutgoing() || !threadRecipient.isPushV2Group()) {
|
||||
return null;
|
||||
}
|
||||
return MemberLabelRepository.getInstance().getLabelJava(threadRecipient.requireGroupId().requireV2(), messageRecord.getFromRecipient());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2.items
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.AbstractComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel
|
||||
import org.thoughtcrime.securesms.groups.memberlabel.SenderNameWithLabel
|
||||
|
||||
/**
|
||||
* @see SenderNameWithLabel
|
||||
*/
|
||||
class SenderNameWithLabelView : AbstractComposeView {
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
init {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
}
|
||||
|
||||
private var senderName: String by mutableStateOf("")
|
||||
private var senderColor: Color by mutableStateOf(Color.Unspecified)
|
||||
private var memberLabel: MemberLabel? by mutableStateOf(null)
|
||||
|
||||
fun setSender(name: String, @ColorInt tintColor: Int) {
|
||||
senderName = name
|
||||
senderColor = Color(tintColor)
|
||||
}
|
||||
|
||||
fun setLabel(label: MemberLabel?) {
|
||||
memberLabel = label
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
SenderNameWithLabel(
|
||||
senderName = senderName,
|
||||
senderColor = senderColor,
|
||||
label = memberLabel
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ data class V2ConversationItemMediaBindingBridge(
|
||||
fun V2ConversationItemMediaIncomingBinding.bridge(): V2ConversationItemMediaBindingBridge {
|
||||
val textBridge = V2ConversationItemTextOnlyBindingBridge(
|
||||
root = root,
|
||||
senderName = groupMessageSender,
|
||||
senderNameWithLabel = groupSenderNameWithLabel,
|
||||
senderPhoto = contactPhoto,
|
||||
senderBadge = badge,
|
||||
body = conversationItemBody,
|
||||
@@ -61,7 +61,7 @@ fun V2ConversationItemMediaIncomingBinding.bridge(): V2ConversationItemMediaBind
|
||||
fun V2ConversationItemMediaOutgoingBinding.bridge(): V2ConversationItemMediaBindingBridge {
|
||||
val textBridge = V2ConversationItemTextOnlyBindingBridge(
|
||||
root = root,
|
||||
senderName = null,
|
||||
senderNameWithLabel = null,
|
||||
senderPhoto = null,
|
||||
senderBadge = null,
|
||||
body = conversationItemBody,
|
||||
|
||||
@@ -130,7 +130,7 @@ class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
|
||||
}
|
||||
|
||||
private fun hasGroupSenderName(): Boolean {
|
||||
return binding.textBridge.senderName?.visible == true
|
||||
return binding.textBridge.senderNameWithLabel?.visible == true
|
||||
}
|
||||
|
||||
private fun hasThumbnail(): Boolean {
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.reactions.ReactionsConversationView
|
||||
*/
|
||||
data class V2ConversationItemTextOnlyBindingBridge(
|
||||
val root: V2ConversationItemLayout,
|
||||
val senderName: EmojiTextView?,
|
||||
val senderNameWithLabel: SenderNameWithLabelView?,
|
||||
val senderPhoto: AvatarImageView?,
|
||||
val senderBadge: BadgeImageView?,
|
||||
val bodyWrapper: ViewGroup,
|
||||
@@ -52,7 +52,7 @@ data class V2ConversationItemTextOnlyBindingBridge(
|
||||
fun V2ConversationItemTextOnlyIncomingBinding.bridge(): V2ConversationItemTextOnlyBindingBridge {
|
||||
return V2ConversationItemTextOnlyBindingBridge(
|
||||
root = root,
|
||||
senderName = groupMessageSender,
|
||||
senderNameWithLabel = groupSenderNameWithLabel,
|
||||
senderPhoto = contactPhoto,
|
||||
senderBadge = badge,
|
||||
body = conversationItemBody,
|
||||
@@ -76,7 +76,7 @@ fun V2ConversationItemTextOnlyIncomingBinding.bridge(): V2ConversationItemTextOn
|
||||
fun V2ConversationItemTextOnlyOutgoingBinding.bridge(): V2ConversationItemTextOnlyBindingBridge {
|
||||
return V2ConversationItemTextOnlyBindingBridge(
|
||||
root = root,
|
||||
senderName = null,
|
||||
senderNameWithLabel = null,
|
||||
senderPhoto = null,
|
||||
senderBadge = null,
|
||||
body = conversationItemBody,
|
||||
|
||||
@@ -241,7 +241,7 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||
}
|
||||
|
||||
if (ConversationAdapterBridge.PAYLOAD_NAME_COLORS in payload) {
|
||||
presentSenderNameColor()
|
||||
presentSender()
|
||||
hasProcessedSupportedPayload = true
|
||||
}
|
||||
|
||||
@@ -263,8 +263,6 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||
presentFooterEndPadding()
|
||||
presentAlert()
|
||||
presentSender()
|
||||
presentSenderNameColor()
|
||||
presentSenderNameBackground()
|
||||
presentReactions()
|
||||
|
||||
bodyBubbleDrawable.setCorners(shapeDelegate.cornersLTR)
|
||||
@@ -547,42 +545,26 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentSenderNameBackground() {
|
||||
if (binding.senderName == null || !shape.isStartingShape || !conversationMessage.threadRecipient.isGroup || !conversationMessage.messageRecord.hasNoBubble(context)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (conversationContext.hasWallpaper()) {
|
||||
senderDrawable.setCorners(footerCorners)
|
||||
senderDrawable.setLocalChatColors(ChatColors.forColor(ChatColors.Id.BuiltIn, themeDelegate.getFooterBubbleColor(conversationMessage)))
|
||||
|
||||
binding.senderName.background = senderDrawable
|
||||
private fun presentSender() {
|
||||
if (conversationMessage.threadRecipient.isGroup) {
|
||||
presentSenderPhoto()
|
||||
presentSenderBadge()
|
||||
presentSenderNameWithLabel()
|
||||
} else {
|
||||
binding.senderName.background = null
|
||||
binding.senderPhoto?.visible = false
|
||||
binding.senderBadge?.visible = false
|
||||
binding.senderNameWithLabel?.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentSender() {
|
||||
if (binding.senderName == null || binding.senderPhoto == null || binding.senderBadge == null) {
|
||||
return
|
||||
}
|
||||
private fun presentSenderPhoto() {
|
||||
val photoView = binding.senderPhoto ?: return
|
||||
|
||||
if (conversationMessage.threadRecipient.isGroup) {
|
||||
val sender = conversationMessage.messageRecord.fromRecipient
|
||||
photoView.apply {
|
||||
visibility = if (shape.isEndingShape) View.VISIBLE else View.INVISIBLE
|
||||
setAvatar(conversationContext.requestManager, conversationMessage.messageRecord.fromRecipient, false)
|
||||
|
||||
binding.senderPhoto.visibility = if (shape.isEndingShape) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.INVISIBLE
|
||||
}
|
||||
|
||||
binding.senderName.visible = shape.isStartingShape
|
||||
binding.senderBadge.visible = shape.isEndingShape
|
||||
|
||||
binding.senderName.text = sender.getDisplayName(context)
|
||||
binding.senderPhoto.setAvatar(conversationContext.requestManager, sender, false)
|
||||
binding.senderBadge.setBadgeFromRecipient(sender, conversationContext.requestManager)
|
||||
binding.senderPhoto.setOnClickListener {
|
||||
setOnClickListener {
|
||||
if (conversationContext.selectedItems.isEmpty()) {
|
||||
conversationContext.clickListener.onGroupMemberClicked(
|
||||
conversationMessage.messageRecord.fromRecipient.id,
|
||||
@@ -592,20 +574,56 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||
conversationContext.clickListener.onItemClick(getMultiselectPartForLatestTouch())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.senderName.visible = false
|
||||
binding.senderPhoto.visible = false
|
||||
binding.senderBadge.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentSenderNameColor() {
|
||||
if (binding.senderName == null || !conversationMessage.threadRecipient.isGroup) {
|
||||
private fun presentSenderBadge() {
|
||||
val badgeView = binding.senderBadge ?: return
|
||||
|
||||
badgeView.apply {
|
||||
visible = shape.isEndingShape
|
||||
setBadgeFromRecipient(conversationMessage.messageRecord.fromRecipient, conversationContext.requestManager)
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentSenderNameWithLabel() {
|
||||
val nameWithLabelView = binding.senderNameWithLabel ?: return
|
||||
|
||||
if (!shape.isStartingShape) {
|
||||
nameWithLabelView.visible = false
|
||||
|
||||
binding.body.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = context.resources.getDimensionPixelSize(R.dimen.message_bubble_top_padding)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val sender = conversationMessage.messageRecord.fromRecipient
|
||||
binding.senderName.setTextColor(conversationContext.getColorizer().getIncomingGroupSenderColor(context, sender))
|
||||
val tintColor = conversationContext.getColorizer().getIncomingGroupSenderColor(context, sender)
|
||||
|
||||
nameWithLabelView.apply {
|
||||
setSender(sender.getDisplayName(context), tintColor)
|
||||
setLabel(conversationMessage.memberLabel)
|
||||
visible = true
|
||||
}
|
||||
|
||||
binding.body.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = 0
|
||||
}
|
||||
|
||||
presentSenderNameBackground()
|
||||
}
|
||||
|
||||
private fun presentSenderNameBackground() {
|
||||
val nameWithLabelView = binding.senderNameWithLabel ?: return
|
||||
|
||||
if (shape.isStartingShape && conversationMessage.messageRecord.hasNoBubble(context) && conversationContext.hasWallpaper()) {
|
||||
senderDrawable.setCorners(footerCorners)
|
||||
senderDrawable.setLocalChatColors(ChatColors.forColor(ChatColors.Id.BuiltIn, themeDelegate.getFooterBubbleColor(conversationMessage)))
|
||||
nameWithLabelView.background = senderDrawable
|
||||
} else {
|
||||
nameWithLabelView.background = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentAlert() {
|
||||
@@ -795,15 +813,19 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||
conversationContext.selectedItems.isNotEmpty() -> {
|
||||
conversationContext.clickListener.onItemClick(getMultiselectPartForLatestTouch())
|
||||
}
|
||||
|
||||
messageRecord.isFailed -> {
|
||||
conversationContext.clickListener.onMessageWithErrorClicked(messageRecord)
|
||||
}
|
||||
|
||||
messageRecord.isRateLimited && SignalStore.rateLimit.needsRecaptcha() -> {
|
||||
conversationContext.clickListener.onMessageWithRecaptchaNeededClicked(messageRecord)
|
||||
}
|
||||
|
||||
messageRecord.isOutgoing && messageRecord.isIdentityMismatchFailure -> {
|
||||
conversationContext.clickListener.onIncomingIdentityMismatchClicked(messageRecord.fromRecipient.id)
|
||||
}
|
||||
|
||||
else -> {
|
||||
conversationContext.clickListener.onItemClick(getMultiselectPartForLatestTouch())
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.AbstractComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -29,6 +30,10 @@ class MemberLabelPillView : AbstractComposeView {
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
init {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
}
|
||||
|
||||
private var memberLabel: MemberLabel? by mutableStateOf(null)
|
||||
private var tintColor: Color by mutableStateOf(Color.Unspecified)
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.groups.memberlabel
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
|
||||
/**
|
||||
* Displays a sender name with an optional member label pill.
|
||||
*/
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun SenderNameWithLabel(
|
||||
senderName: String,
|
||||
senderColor: Color,
|
||||
label: MemberLabel?,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
itemVerticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = senderName,
|
||||
color = senderColor,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
if (label != null) {
|
||||
MemberLabelPill(
|
||||
emoji = label.emoji,
|
||||
text = label.text,
|
||||
tintColor = senderColor,
|
||||
textStyle = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun SenderNameWithLabelPreview() = Previews.Preview {
|
||||
Box(modifier = Modifier.width(200.dp)) {
|
||||
SenderNameWithLabel(
|
||||
senderName = "Foo Bar",
|
||||
senderColor = Color(0xFF7C4DFF),
|
||||
label = MemberLabel(emoji = "\uD83D\uDC36", text = "Vet Coordinator")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun SenderNameWithLabelLongLabelPreview() = Previews.Preview {
|
||||
Box(modifier = Modifier.width(200.dp)) {
|
||||
SenderNameWithLabel(
|
||||
senderName = "Foo Bar",
|
||||
senderColor = Color(0xFF7C4DFF),
|
||||
label = MemberLabel(emoji = "🧠", text = "Zero-Knowledge Know-It-All")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun SenderNameWithLabelLongNamePreview() = Previews.Preview {
|
||||
Box(modifier = Modifier.width(200.dp)) {
|
||||
SenderNameWithLabel(
|
||||
senderName = "Cassandra NullPointer-Exception",
|
||||
senderColor = Color(0xFF7C4DFF),
|
||||
label = MemberLabel(emoji = "🧠", text = "Vet Coordinator")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun SenderNameWithLabelNoLabelPreview() = Previews.Preview {
|
||||
Box(modifier = Modifier.width(200.dp)) {
|
||||
SenderNameWithLabel(
|
||||
senderName = "Sam",
|
||||
senderColor = Color(0xFF4CAF50),
|
||||
label = null
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user