From ff9585ec7dbcf68932b7fb6fc3a85241b1164492 Mon Sep 17 00:00:00 2001 From: jeffrey-signal Date: Thu, 26 Feb 2026 20:00:36 -0500 Subject: [PATCH] Show member labels on the admin sheet. --- .../preferences/RecipientPreference.kt | 8 +---- .../ShowAdminsBottomSheetDialog.java | 36 +++++++++++++++---- .../conversation/colors/NameColor.kt | 6 ++-- .../groups/memberlabel/MemberLabelPillView.kt | 14 +++++++- .../memberlabel/MemberLabelRepository.kt | 6 ++++ .../securesms/groups/ui/GroupMemberEntry.java | 35 ++++++++++++++---- .../groups/ui/GroupMemberListAdapter.java | 23 ++++++++++++ .../res/layout/group_recipient_list_item.xml | 4 +-- 8 files changed, 105 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/RecipientPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/RecipientPreference.kt index 51b4421902..88ab58bfc9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/RecipientPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/RecipientPreference.kt @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.components.settings.conversation.preferences import android.text.SpannableStringBuilder import android.view.View import android.widget.TextView -import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.Observer @@ -11,7 +10,6 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.badges.BadgeImageView import org.thoughtcrime.securesms.components.AvatarImageView import org.thoughtcrime.securesms.components.settings.PreferenceModel -import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelPill import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelPillView import org.thoughtcrime.securesms.groups.memberlabel.StyledMemberLabel import org.thoughtcrime.securesms.recipients.Recipient @@ -133,11 +131,7 @@ object RecipientPreference { private fun showMemberLabel(styledLabel: StyledMemberLabel) { memberLabelView?.apply { - style = MemberLabelPillView.Style( - horizontalPadding = 8.dp, - verticalPadding = 2.dp, - textStyle = { MemberLabelPill.textStyleCompact } - ) + style = MemberLabelPillView.Style.Compact setLabel(styledLabel.label, styledLabel.tintColor) visible = true } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ShowAdminsBottomSheetDialog.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ShowAdminsBottomSheetDialog.java index 27e7c7ec56..0c95ad0f8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ShowAdminsBottomSheetDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ShowAdminsBottomSheetDialog.java @@ -14,19 +14,28 @@ import androidx.fragment.app.FragmentManager; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import org.signal.core.models.ServiceId; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.conversation.colors.ColorizerV2; +import org.thoughtcrime.securesms.conversation.colors.NameColor; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.model.GroupRecord; import org.thoughtcrime.securesms.groups.GroupId; +import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel; +import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelRepository; +import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry; import org.thoughtcrime.securesms.groups.ui.GroupMemberListView; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.signal.core.ui.BottomSheetUtil; import org.thoughtcrime.securesms.util.CommunicationActions; import org.signal.core.util.concurrent.LifecycleDisposable; import org.thoughtcrime.securesms.util.WindowUtil; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; @@ -68,7 +77,7 @@ public final class ShowAdminsBottomSheetDialog extends BottomSheetDialogFragment GroupMemberListView list = view.findViewById(R.id.show_admin_list); list.initializeAdapter(getViewLifecycleOwner()); - list.setDisplayOnlyMembers(Collections.emptyList()); + list.setMembers(Collections.emptyList()); list.setRecipientClickListener(recipient -> { CommunicationActions.startConversation(requireContext(), recipient, null); @@ -78,7 +87,7 @@ public final class ShowAdminsBottomSheetDialog extends BottomSheetDialogFragment disposables.add(Single.fromCallable(() -> getAdmins(requireContext().getApplicationContext(), getGroupId())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(list::setDisplayOnlyMembers)); + .subscribe(list::setMembers)); } @Override @@ -97,10 +106,23 @@ public final class ShowAdminsBottomSheetDialog extends BottomSheetDialogFragment } @WorkerThread - private static @NonNull List getAdmins(@NonNull Context context, @NonNull GroupId groupId) { - return SignalDatabase.groups() - .getGroup(groupId) - .map(GroupRecord::getAdmins) - .orElse(Collections.emptyList()); + private static @NonNull List getAdmins(@NonNull Context context, @NonNull GroupId groupId) { + GroupRecord groupRecord = SignalDatabase.groups().getGroup(groupId).orElse(null); + if (groupRecord == null) { + return Collections.emptyList(); + } + + List admins = groupRecord.getAdmins(); + Map labelsByRecipientId = MemberLabelRepository.getInstance().getLabelsJava(groupId.requireV2(), admins); + List memberIds = groupRecord.requireV2GroupProperties().getMemberServiceIds(); + ColorizerV2 colorizer = new ColorizerV2(memberIds); + + List result = new ArrayList<>(); + for (Recipient admin : admins) { + MemberLabel label = labelsByRecipientId.get(admin.getId()); + NameColor nameColor = label != null ? colorizer.getNameColor(context, admin) : null; + result.add(new GroupMemberEntry.FullMember(admin, true, label, nameColor)); + } + return result; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/NameColor.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/NameColor.kt index 77567bb0dd..b26b6caaca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/NameColor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/NameColor.kt @@ -7,9 +7,9 @@ import org.signal.core.ui.util.ThemeUtil /** * Class which stores information for a Recipient's name color in a group. */ -class NameColor( - @ColorInt private val lightColor: Int, - @ColorInt private val darkColor: Int +data class NameColor( + @get:ColorInt private val lightColor: Int, + @get:ColorInt private val darkColor: Int ) { @ColorInt fun getColor(context: Context): Int { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelPillView.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelPillView.kt index eff1ab3e0a..45521e8570 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelPillView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelPillView.kt @@ -65,5 +65,17 @@ class MemberLabelPillView : AbstractComposeView { val horizontalPadding: Dp = 12.dp, val verticalPadding: Dp = 2.dp, val textStyle: @Composable () -> TextStyle = { MemberLabelPill.textStyleNormal } - ) + ) { + companion object { + @JvmField + val Normal = Style() + + @JvmField + val Compact = Style( + horizontalPadding = 8.dp, + verticalPadding = 2.dp, + textStyle = { MemberLabelPill.textStyleCompact } + ) + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelRepository.kt index 875a4fd860..072af073bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelRepository.kt @@ -53,6 +53,12 @@ class MemberLabelRepository private constructor( @WorkerThread fun getLabelJava(groupId: GroupId.V2, recipient: Recipient): MemberLabel? = runBlocking { getLabel(groupId, recipient) } + /** + * Gets member labels for a list of recipients in a group (blocking version for Java compatibility). + */ + @WorkerThread + fun getLabelsJava(groupId: GroupId.V2, recipients: List): Map = runBlocking { getLabels(groupId, recipients) } + /** * Gets the member label for a specific recipient in the group. */ diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberEntry.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberEntry.java index e73c0173a6..19cd46cdcf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberEntry.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberEntry.java @@ -5,6 +5,8 @@ import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; import org.signal.libsignal.zkgroup.groups.UuidCiphertext; +import org.thoughtcrime.securesms.conversation.colors.NameColor; +import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.DefaultValueLiveData; @@ -69,12 +71,20 @@ public abstract class GroupMemberEntry { public final static class FullMember extends GroupMemberEntry { - private final Recipient member; - private final boolean isAdmin; + private final Recipient member; + private final boolean isAdmin; + @Nullable private final MemberLabel memberLabel; + @Nullable private final NameColor nameColor; public FullMember(@NonNull Recipient member, boolean isAdmin) { - this.member = member; - this.isAdmin = isAdmin; + this(member, isAdmin, null, null); + } + + public FullMember(@NonNull Recipient member, boolean isAdmin, @Nullable MemberLabel memberLabel, @Nullable NameColor nameColor) { + this.member = member; + this.isAdmin = isAdmin; + this.memberLabel = memberLabel; + this.nameColor = nameColor; } public Recipient getMember() { @@ -85,6 +95,14 @@ public abstract class GroupMemberEntry { return isAdmin; } + public @Nullable MemberLabel getMemberLabel() { + return memberLabel; + } + + public @Nullable NameColor getNameColor() { + return nameColor; + } + @Override boolean sameId(@NonNull GroupMemberEntry newItem) { if (getClass() != newItem.getClass()) return false; @@ -98,12 +116,14 @@ public abstract class GroupMemberEntry { FullMember other = (FullMember) obj; return other.member.equals(member) && - other.isAdmin == isAdmin; + other.isAdmin == isAdmin && + Objects.equals(other.memberLabel, memberLabel) && + Objects.equals(other.nameColor, nameColor); } @Override public int hashCode() { - return member.hashCode() * 31 + (isAdmin ? 1 : 0); + return ((member.hashCode() * 31 + (isAdmin ? 1 : 0)) * 31 + Objects.hashCode(memberLabel)) * 31 + Objects.hashCode(nameColor); } } @@ -174,7 +194,8 @@ public abstract class GroupMemberEntry { public UnknownPendingMemberCount(@NonNull Recipient inviter, @NonNull Collection ciphertexts, - boolean cancellable) { + boolean cancellable) + { this.inviter = inviter; this.ciphertexts = ciphertexts; this.cancellable = cancellable; diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberListAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberListAdapter.java index 48fa2b28a0..510c1914b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberListAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberListAdapter.java @@ -19,6 +19,9 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.badges.BadgeImageView; import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; +import org.thoughtcrime.securesms.conversation.colors.NameColor; +import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel; +import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelPillView; import org.thoughtcrime.securesms.recipients.Recipient; import org.signal.core.util.Util; @@ -194,6 +197,8 @@ final class GroupMemberListAdapter extends RecyclerView.Adapter + android:visibility="gone" /> + tools:visibility="visible" />