From da3fc408f8d3cddcc8e8a9587b98dfc6abcf1ef0 Mon Sep 17 00:00:00 2001 From: Michelle Tang Date: Fri, 14 Mar 2025 17:44:02 -0400 Subject: [PATCH] Update conversation header with group members. --- .../conversation/ConversationHeaderView.java | 11 +++-- .../conversation/v2/ConversationAdapterV2.kt | 42 +++++++++++++++---- .../securesms/messagerequests/GroupInfo.kt | 5 ++- .../MessageRequestRepository.java | 22 +++++----- .../res/layout/conversation_header_view.xml | 6 ++- .../conversation_item_thread_header.xml | 21 +++++++--- app/src/main/res/values/strings.xml | 21 ++++++++++ 7 files changed, 98 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationHeaderView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationHeaderView.java index ae4321d3c4..073ed83989 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationHeaderView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationHeaderView.java @@ -162,17 +162,17 @@ public class ConversationHeaderView extends ConstraintLayout { binding.messageRequestAbout.setVisibility(TextUtils.isEmpty(about) || recipient.isReleaseNotes() ? GONE : VISIBLE); } - public void setSubtitle(@NonNull CharSequence subtitle, @DrawableRes int iconRes, @Nullable Runnable onClick) { + public void setSubtitle(@NonNull CharSequence subtitle, @DrawableRes int iconRes, @Nullable String substring, @Nullable Runnable onClick) { if (TextUtils.isEmpty(subtitle)) { hideSubtitle(); return; } - if (onClick != null) { + if (onClick != null && substring != null) { binding.messageRequestSubtitle.setMovementMethod(LinkMovementMethod.getInstance()); CharSequence builder = SpanUtil.clickSubstring( subtitle, - subtitle, + substring, listener -> onClick.run(), ContextCompat.getColor(getContext(), R.color.signal_colorOnSurface), true @@ -304,6 +304,11 @@ public class ConversationHeaderView extends ConstraintLayout { } } + if (getBackground() != null) { + ViewUtil.setPaddingTop(binding.messageRequestInfo, 0); + ViewUtil.setPaddingBottom(binding.messageRequestInfo, getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_header_padding)); + } + int padding = visibleCount == 1 ? getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_header_padding) : getContext().getResources().getDimensionPixelOffset(R.dimen.conversation_header_padding_expanded); ViewUtil.setPaddingStart(binding.messageRequestInfo, padding); ViewUtil.setPaddingEnd(binding.messageRequestInfo, padding); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt index a256aae947..324015ea89 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt @@ -531,7 +531,7 @@ class ConversationAdapterV2( } inner class ThreadHeaderViewHolder(itemView: View) : MappingViewHolder(itemView) { - private val conversationBanner: ConversationHeaderView = itemView as ConversationHeaderView + private val conversationBanner: ConversationHeaderView = itemView.findViewById(R.id.header) override fun bind(model: ThreadHeader) { val (recipient, groupInfo, sharedGroups, messageRequestState) = model.recipientInfo @@ -570,19 +570,16 @@ class ConversationAdapterV2( conversationBanner.hideUnverifiedNameSubtitle() } - if (groupInfo.pendingMemberCount > 0) { - val invited = context.resources.getQuantityString(R.plurals.MessageRequestProfileView_invited, groupInfo.pendingMemberCount, groupInfo.pendingMemberCount) - conversationBanner.setSubtitle(context.resources.getQuantityString(R.plurals.MessageRequestProfileView_members_and_invited, groupInfo.fullMemberCount, groupInfo.fullMemberCount, invited), R.drawable.symbol_group_compact_16) { goToGroupSettings(recipient) } - } else if (groupInfo.fullMemberCount > 0) { + if (groupInfo.fullMemberCount > 0 || groupInfo.pendingMemberCount > 0) { if (groupInfo.fullMemberCount == 1 && recipient.isActiveGroup) { conversationBanner.hideUnverifiedNameSubtitle() } - conversationBanner.setSubtitle(context.resources.getQuantityString(R.plurals.MessageRequestProfileView_members, groupInfo.fullMemberCount, groupInfo.fullMemberCount), R.drawable.symbol_group_compact_16) { goToGroupSettings(recipient) } + setSubtitle(context, groupInfo.pendingMemberCount, groupInfo.fullMemberCount, groupInfo.membersPreview, recipient) } else { conversationBanner.hideSubtitle() } } else if (isSelf) { - conversationBanner.setSubtitle(context.getString(R.string.ConversationFragment__you_can_add_notes_for_yourself_in_this_conversation), R.drawable.symbol_note_compact_16, null) + conversationBanner.setSubtitle(context.getString(R.string.ConversationFragment__you_can_add_notes_for_yourself_in_this_conversation), R.drawable.symbol_note_compact_16, null, null) } else { if ((recipient.profileName.toString() == recipient.getDisplayName(context)) && recipient.nickname.isEmpty && !recipient.isSystemContact) { conversationBanner.setUnverifiedNameSubtitle(R.drawable.symbol_person_question_16, false) { @@ -596,7 +593,7 @@ class ConversationAdapterV2( if (subtitle == null || subtitle == title) { conversationBanner.hideSubtitle() } else { - conversationBanner.setSubtitle(subtitle, R.drawable.symbol_phone_compact_16, null) + conversationBanner.setSubtitle(subtitle, R.drawable.symbol_phone_compact_16, null, null) } } @@ -641,6 +638,35 @@ class ConversationAdapterV2( conversationBanner.updateOutlineBoxSize() } + private fun setSubtitle(context: Context, pendingMemberCount: Int, size: Int, members: List, recipient: Recipient) { + val names = members.map { member -> member.getDisplayName(context) } + val otherMembers = if (size > 3) context.resources.getQuantityString(R.plurals.MessageRequestProfileView_other_members, size - 3, size - 3) else null + val membersSubtitle = if (recipient.isActiveGroup) { + when (size) { + 1 -> context.getString(R.string.MessageRequestProfileView_group_members_zero) + 2 -> context.getString(R.string.MessageRequestProfileView_group_members_one_and_you, names[0]) + 3 -> context.getString(R.string.MessageRequestProfileView_group_members_two_and_you, names[0], names[1]) + else -> context.getString(R.string.MessageRequestProfileView_group_members_other, names[0], names[1], names[2], otherMembers) + } + } else { + when (size) { + 0 -> context.getString(R.string.MessageRequestProfileView_group_members_zero) + 1 -> context.getString(R.string.MessageRequestProfileView_group_members_one, names[0]) + 2 -> context.getString(R.string.MessageRequestProfileView_group_members_two, names[0], names[1]) + 3 -> context.getString(R.string.MessageRequestProfileView_group_members_three, names[0], names[1], names[2]) + else -> context.getString(R.string.MessageRequestProfileView_group_members_other, names[0], names[1], names[2], otherMembers) + } + } + + if (pendingMemberCount > 0) { + val invited = context.resources.getQuantityString(R.plurals.MessageRequestProfileView_invited, pendingMemberCount, pendingMemberCount) + val subtitle = context.getString(R.string.MessageRequestProfileView_member_names_and_invited, membersSubtitle, invited) + conversationBanner.setSubtitle(subtitle, R.drawable.symbol_group_compact_16, otherMembers) { goToGroupSettings(recipient) } + } else { + conversationBanner.setSubtitle(membersSubtitle, R.drawable.symbol_group_compact_16, otherMembers) { goToGroupSettings(recipient) } + } + } + private fun getDescription(context: Context, sharedGroups: List): String { return when (sharedGroups.size) { 0 -> context.getString(R.string.ConversationUpdateItem_no_groups_in_common_review_requests_carefully) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/GroupInfo.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/GroupInfo.kt index 4f3d1c30d5..2433bdfc98 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/GroupInfo.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/GroupInfo.kt @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.messagerequests +import org.thoughtcrime.securesms.recipients.Recipient + /** * Group info needed to show message request state UX. */ @@ -7,7 +9,8 @@ class GroupInfo( val fullMemberCount: Int = 0, val pendingMemberCount: Int = 0, val description: String = "", - val hasExistingContacts: Boolean = false + val hasExistingContacts: Boolean = false, + val membersPreview: List = emptyList() ) { companion object { @JvmField diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java index bb4174fa0d..0442e1439b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java @@ -35,10 +35,12 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.internal.push.exceptions.GroupPatchNotAcceptedException; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Single; @@ -49,6 +51,7 @@ public final class MessageRequestRepository { private static final String TAG = Log.tag(MessageRequestRepository.class); private static final int MIN_GROUPS_THRESHOLD = 2; + private static final int MAX_MEMBER_NAMES = 3; private final Context context; private final Executor executor; @@ -65,20 +68,17 @@ public final class MessageRequestRepository { GroupInfo groupInfo = GroupInfo.ZERO; if (groupRecord.isPresent()) { - boolean groupHasExistingContacts = false; + List recipients = Recipient.resolvedList(groupRecord.get().getMembers()); if (groupRecord.get().isV2Group()) { - List recipients = Recipient.resolvedList(groupRecord.get().getMembers()); - for (Recipient recipient : recipients) { - if ((recipient.isProfileSharing() || recipient.isSystemContact()) && !recipient.isSelf()) { - groupHasExistingContacts = true; - break; - } - } + boolean groupHasExistingContacts = recipients.stream().filter(r -> !r.isSelf()).anyMatch(r -> r.isProfileSharing() || r.isSystemContact()); + List membersPreview = recipients.stream().filter(r -> !r.isSelf()).limit(MAX_MEMBER_NAMES).collect(Collectors.toList()); + DecryptedGroup decryptedGroup = groupRecord.get().requireV2GroupProperties().getDecryptedGroup(); - DecryptedGroup decryptedGroup = groupRecord.get().requireV2GroupProperties().getDecryptedGroup(); - groupInfo = new GroupInfo(decryptedGroup.members.size(), decryptedGroup.pendingMembers.size(), decryptedGroup.description, groupHasExistingContacts); + groupInfo = new GroupInfo(decryptedGroup.members.size(), decryptedGroup.pendingMembers.size(), decryptedGroup.description, groupHasExistingContacts, membersPreview); } else { - groupInfo = new GroupInfo(groupRecord.get().getMembers().size(), 0, "", false); + List membersPreview = recipients.stream().filter(r -> !r.isSelf()).limit(MAX_MEMBER_NAMES).collect(Collectors.toList()); + + groupInfo = new GroupInfo(groupRecord.get().getMembers().size(), 0, "", false, membersPreview); } } diff --git a/app/src/main/res/layout/conversation_header_view.xml b/app/src/main/res/layout/conversation_header_view.xml index 7246d322f0..cf72915338 100644 --- a/app/src/main/res/layout/conversation_header_view.xml +++ b/app/src/main/res/layout/conversation_header_view.xml @@ -97,10 +97,14 @@ - + android:layout_height="wrap_content"> + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e6f33e8ce4..e0ce7c96a6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2139,6 +2139,27 @@ %d additional group %d additional groups + + %1$s (%2$s) + + No other group members yet + + %1$s + + %1$s and you + + %1$s and %2$s + + %1$s, %2$s, and you + + %1$s, %2$s, and %3$s + + %1$s, %2$s, %3$s, and %4$s + + + %1$d other + %1$d others + Report…