Show member labels in conversation settings.

This commit is contained in:
jeffrey-signal
2026-02-09 13:09:46 -05:00
committed by Greyson Parrelli
parent 0199cd24ef
commit d7b7727aa6
13 changed files with 252 additions and 92 deletions

View File

@@ -33,6 +33,7 @@ import org.signal.core.util.Result
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.concurrent.addTo
import org.signal.core.util.getParcelableArrayListExtraCompat
import org.signal.core.util.orNull
import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.AvatarPreviewActivity
import org.thoughtcrime.securesms.BlockUnblockDialog
@@ -67,10 +68,13 @@ import org.thoughtcrime.securesms.components.settings.conversation.preferences.R
import org.thoughtcrime.securesms.components.settings.conversation.preferences.SharedMediaPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.Utils.formatMutedUntil
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.memberlabel.StyledMemberLabel
import org.thoughtcrime.securesms.groups.ui.GroupErrors
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry
import org.thoughtcrime.securesms.groups.ui.LeaveGroupDialog
import org.thoughtcrime.securesms.groups.ui.addmembers.AddMembersActivity
import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupsActivity
@@ -123,6 +127,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
private val args: ConversationSettingsFragmentArgs by navArgs()
private val alertTint by lazy { ContextCompat.getColor(requireContext(), R.color.signal_alert_primary) }
private val alertDisabledTint by lazy { ContextCompat.getColor(requireContext(), R.color.signal_alert_primary_50) }
private val colorizer = Colorizer()
private val blockIcon by lazy {
ContextUtil.requireDrawable(requireContext(), R.drawable.symbol_block_24).apply {
colorFilter = PorterDuffColorFilter(alertTint, PorterDuff.Mode.SRC_IN)
@@ -199,7 +204,9 @@ class ConversationSettingsFragment : DSLSettingsFragment(
}
REQUEST_CODE_RETURN_FROM_MEDIA -> viewModel.refreshSharedMedia()
REQUEST_CODE_ADD_CONTACT -> viewModel.refreshRecipient()
REQUEST_CODE_VIEW_CONTACT -> viewModel.refreshRecipient()
}
}
@@ -432,7 +439,13 @@ class ConversationSettingsFragment : DSLSettingsFragment(
.request(Manifest.permission.CAMERA)
.ifNecessary()
.withRationaleDialog(getString(R.string.CameraXFragment_allow_access_camera), getString(R.string.CameraXFragment_to_capture_photos_and_video_allow_camera), CoreUiR.drawable.symbol_camera_24)
.withPermanentDenialDialog(getString(R.string.CameraXFragment_signal_needs_camera_access_capture_photos), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_capture_photos_videos, getParentFragmentManager())
.withPermanentDenialDialog(
getString(R.string.CameraXFragment_signal_needs_camera_access_capture_photos),
null,
R.string.CameraXFragment_allow_access_camera,
R.string.CameraXFragment_to_capture_photos_videos,
getParentFragmentManager()
)
.onAllGranted { addToGroupStoryDelegate.addToStory(state.recipient.id) }
.onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_capture_photos, Toast.LENGTH_LONG).show() }
.execute()
@@ -768,11 +781,16 @@ class ConversationSettingsFragment : DSLSettingsFragment(
)
}
colorizer.onGroupMembershipChanged(
serviceIds = groupState.allMembers.mapNotNull { it.member.serviceId.orNull() }
)
for (member in groupState.members) {
customPref(
RecipientPreference.Model(
recipient = member.member,
isAdmin = member.isAdmin,
memberLabel = member.getMemberLabel(groupState),
lifecycleOwner = viewLifecycleOwner,
onClick = {
RecipientBottomSheetDialogFragment.show(parentFragmentManager, member.member.id, groupState.groupId)
@@ -949,6 +967,15 @@ class ConversationSettingsFragment : DSLSettingsFragment(
}
}
private fun GroupMemberEntry.FullMember.getMemberLabel(
groupState: SpecificSettingsState.GroupSettingsState
): StyledMemberLabel? {
return groupState.memberLabelsByRecipientId[member.id]?.let { label ->
val tintColor = colorizer.getIncomingGroupSenderColor(context = requireContext(), recipient = member)
StyledMemberLabel(label, tintColor)
}
}
private fun formatDisappearingMessagesLifespan(disappearingMessagesLifespan: Int): String {
return if (disappearingMessagesLifespan <= 0) {
getString(R.string.preferences_off)

View File

@@ -7,8 +7,10 @@ import org.thoughtcrime.securesms.database.MediaTable
import org.thoughtcrime.securesms.database.model.IdentityRecord
import org.thoughtcrime.securesms.database.model.StoryViewState
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
data class ConversationSettingsState(
val threadId: Long = -1,
@@ -81,7 +83,8 @@ sealed class SpecificSettingsState {
val groupLinkEnabled: Boolean = false,
val membershipCountDescription: String = "",
val legacyGroupState: LegacyGroupPreference.State = LegacyGroupPreference.State.NONE,
val isAnnouncementGroup: Boolean = false
val isAnnouncementGroup: Boolean = false,
val memberLabelsByRecipientId: Map<RecipientId, MemberLabel> = emptyMap()
) : SpecificSettingsState() {
override val isLoaded: Boolean = groupTitleLoaded && groupDescriptionLoaded

View File

@@ -31,7 +31,9 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.LiveGroup
import org.thoughtcrime.securesms.groups.SelectionLimits
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelRepository
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry
import org.thoughtcrime.securesms.groups.v2.GroupAddMembersResult
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository
@@ -358,6 +360,10 @@ sealed class ConversationSettingsViewModel(
val groupState = state.requireGroupSettingsState()
val canShowMore = !groupState.groupMembersExpanded && fullMembers.size > 6
if (groupId.isV2) {
loadMemberLabels(groupId.requireV2(), fullMembers)
}
state.copy(
specificSettingsState = groupState.copy(
allMembers = fullMembers,
@@ -501,6 +507,19 @@ sealed class ConversationSettingsViewModel(
override fun unblock() {
repository.unblock(groupId)
}
private fun loadMemberLabels(v2GroupId: GroupId.V2, groupMembers: List<GroupMemberEntry.FullMember>) = viewModelScope.launch(SignalDispatchers.IO) {
val labelsByRecipientId = MemberLabelRepository.instance
.getLabels(v2GroupId, groupMembers.map { it.member })
store.update { state ->
state.copy(
specificSettingsState = state.requireGroupSettingsState().copy(
memberLabelsByRecipientId = labelsByRecipientId
)
)
}
}
}
class Factory(

View File

@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.components.settings.conversation.preferences
import android.text.SpannableStringBuilder
import android.view.View
import android.widget.TextView
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
@@ -10,6 +12,8 @@ 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.MemberLabelPillView
import org.thoughtcrime.securesms.groups.memberlabel.StyledMemberLabel
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.ContextUtil
import org.thoughtcrime.securesms.util.SpanUtil
@@ -20,7 +24,7 @@ import org.thoughtcrime.securesms.util.visible
import org.signal.core.ui.R as CoreUiR
/**
* Renders a Recipient as a row item with an icon, avatar, status, and admin state
* Renders a Recipient as a row item with an icon, avatar, label/status, and admin state.
*/
object RecipientPreference {
@@ -31,6 +35,7 @@ object RecipientPreference {
class Model(
val recipient: Recipient,
val isAdmin: Boolean = false,
val memberLabel: StyledMemberLabel? = null,
val lifecycleOwner: LifecycleOwner? = null,
val onClick: (() -> Unit)? = null
) : PreferenceModel<Model>() {
@@ -41,7 +46,8 @@ object RecipientPreference {
override fun areContentsTheSame(newItem: Model): Boolean {
return super.areContentsTheSame(newItem) &&
recipient.hasSameContent(newItem.recipient) &&
isAdmin == newItem.isAdmin
isAdmin == newItem.isAdmin &&
memberLabel == newItem.memberLabel
}
}
@@ -49,15 +55,14 @@ object RecipientPreference {
private val avatar: AvatarImageView = itemView.findViewById(R.id.recipient_avatar)
private val name: TextView = itemView.findViewById(R.id.recipient_name)
private val about: TextView? = itemView.findViewById(R.id.recipient_about)
private val memberLabelView: MemberLabelPillView? = itemView.findViewById(R.id.recipient_member_label)
private val admin: View? = itemView.findViewById(R.id.admin)
private val badge: BadgeImageView = itemView.findViewById(R.id.recipient_badge)
private var recipient: Recipient? = null
private val recipientObserver = object : Observer<Recipient> {
override fun onChanged(recipient: Recipient) {
onRecipientChanged(recipient)
}
private val recipientObserver = Observer<Recipient> { recipient ->
onRecipientChanged(recipient)
}
override fun bind(model: Model) {
@@ -69,8 +74,9 @@ object RecipientPreference {
if (model.lifecycleOwner != null) {
observeRecipient(model.lifecycleOwner, model.recipient)
model.memberLabel?.let(::showMemberLabel)
} else {
onRecipientChanged(model.recipient)
onRecipientChanged(model.recipient, model.memberLabel)
}
admin?.visible = model.isAdmin
@@ -80,7 +86,7 @@ object RecipientPreference {
unbind()
}
private fun onRecipientChanged(recipient: Recipient) {
private fun onRecipientChanged(recipient: Recipient, memberLabel: StyledMemberLabel? = null) {
avatar.setRecipient(recipient)
badge.setBadgeFromRecipient(recipient)
name.text = if (recipient.isSelf) {
@@ -98,15 +104,36 @@ object RecipientPreference {
}
}
val aboutText = recipient.combinedAboutAndEmoji
if (aboutText.isNullOrEmpty()) {
about?.visibility = View.GONE
} else {
about?.text = recipient.combinedAboutAndEmoji
about?.visibility = View.VISIBLE
when {
memberLabel != null -> showMemberLabel(memberLabel)
!recipient.combinedAboutAndEmoji.isNullOrEmpty() -> {
about?.text = recipient.combinedAboutAndEmoji
about?.visible = true
memberLabelView?.visible = false
}
else -> {
memberLabelView?.visible = false
about?.visible = false
}
}
}
private fun showMemberLabel(styledLabel: StyledMemberLabel) {
memberLabelView?.apply {
style = MemberLabelPillView.Style(
horizontalPadding = 8.dp,
verticalPadding = 2.dp,
textStyle = { MaterialTheme.typography.bodySmall }
)
setLabel(styledLabel.label, styledLabel.tintColor)
visible = true
}
about?.visible = false
}
private fun observeRecipient(lifecycleOwner: LifecycleOwner?, recipient: Recipient?) {
this.recipient?.live()?.liveData?.removeObserver(recipientObserver)

View File

@@ -1289,6 +1289,18 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
null
}
}
/**
* Gets all member labels in the group. Only includes members that have a non-blank label.
*/
fun memberLabelsByAci(): Map<ACI, MemberLabel> = buildMap {
decryptedGroup.members.forEach { member ->
if (member.labelString.isNotBlank()) {
val aci = ACI.parseOrNull(member.aciBytes) ?: return@forEach
put(aci, MemberLabel(member.labelEmoji, member.labelString))
}
}
}
}
@Throws(BadGroupIdException::class)

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.groups.memberlabel
import androidx.annotation.ColorInt
/**
* A member's custom label within a group.
*/
data class MemberLabel(
val emoji: String?,
val text: String
)
data class StyledMemberLabel(
val label: MemberLabel,
@param:ColorInt val tintColor: Int
)

View File

@@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.text.TextStyle
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
@@ -66,7 +67,8 @@ fun MemberLabelPill(
text = text,
color = textColor,
style = textStyle,
maxLines = 1
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}

View File

@@ -8,12 +8,18 @@ package org.thoughtcrime.securesms.groups.memberlabel
import android.content.Context
import android.util.AttributeSet
import androidx.annotation.ColorInt
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* @see MemberLabelPill
@@ -26,6 +32,8 @@ class MemberLabelPillView : AbstractComposeView {
private var memberLabel: MemberLabel? by mutableStateOf(null)
private var tintColor: Color by mutableStateOf(Color.Unspecified)
var style: Style by mutableStateOf(Style())
fun setLabel(label: MemberLabel, tintColor: Color) {
this.memberLabel = label
this.tintColor = tintColor
@@ -42,8 +50,16 @@ class MemberLabelPillView : AbstractComposeView {
MemberLabelPill(
emoji = label.emoji,
text = label.text,
tintColor = tintColor
tintColor = tintColor,
modifier = Modifier.padding(horizontal = style.horizontalPadding, vertical = style.verticalPadding),
textStyle = style.textStyle()
)
}
}
data class Style(
val horizontalPadding: Dp = 12.dp,
val verticalPadding: Dp = 2.dp,
val textStyle: @Composable () -> TextStyle = { MaterialTheme.typography.bodyLarge }
)
}

View File

@@ -59,6 +59,27 @@ class MemberLabelRepository private constructor(
return@withContext groupRecord.requireV2GroupProperties().memberLabel(aci)
}
/**
* Gets member labels for a list of recipients in a group.
*
* Returns a map of [RecipientId] to [MemberLabel] for members that have labels.
*/
suspend fun getLabels(groupId: GroupId.V2, recipients: List<Recipient>): Map<RecipientId, MemberLabel> = withContext(Dispatchers.IO) {
if (!RemoteConfig.receiveMemberLabels) {
return@withContext emptyMap()
}
val groupRecord = groupsTable.getGroup(groupId).orNull() ?: return@withContext emptyMap()
val labelsByAci = groupRecord.requireV2GroupProperties().memberLabelsByAci()
buildMap {
recipients.forEach { recipient ->
val aci = recipient.serviceId.orNull() as? ServiceId.ACI
labelsByAci[aci]?.let { label -> put(recipient.id, label) }
}
}
}
/**
* Sets the group member label for the current user.
*/
@@ -70,11 +91,3 @@ class MemberLabelRepository private constructor(
GroupManager.updateMemberLabel(context, groupId, label.text, label.emoji.orEmpty())
}
}
/**
* A member's custom label within a group.
*/
data class MemberLabel(
val emoji: String?,
val text: String
)

View File

@@ -1,14 +1,8 @@
package org.thoughtcrime.securesms.recipients.ui.bottomsheet
import androidx.annotation.ColorInt
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel
import org.thoughtcrime.securesms.groups.memberlabel.StyledMemberLabel
data class RecipientDetailsState(
val memberLabel: StyledMemberLabel?,
val aboutText: String?
)
data class StyledMemberLabel(
val label: MemberLabel,
@param:ColorInt val tintColor: Int
)

View File

@@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel;
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelRepository;
import org.thoughtcrime.securesms.groups.memberlabel.StyledMemberLabel;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupsActivity;
@@ -69,6 +70,7 @@ final class RecipientDialogViewModel extends ViewModel {
private final MutableLiveData<RecipientDetailsState> recipientDetailsState;
private final CompositeDisposable disposables;
private final boolean isDeprecatedOrUnregistered;
private RecipientDialogViewModel(@NonNull Context context,
@NonNull RecipientDialogRepository recipientDialogRepository)
{
@@ -198,8 +200,8 @@ final class RecipientDialogViewModel extends ViewModel {
activity.startActivity(StoryViewerActivity.createIntent(
activity,
new StoryViewerArgs.Builder(recipientDialogRepository.getRecipientId(), recipient.getValue().getShouldHideStory())
.isFromQuote(true)
.build()));
.isFromQuote(true)
.build()));
}
}
@@ -238,65 +240,65 @@ final class RecipientDialogViewModel extends ViewModel {
activity.startActivity(StoryViewerActivity.createIntent(
activity,
new StoryViewerArgs.Builder(recipientDialogRepository.getRecipientId(), recipient.getValue().getShouldHideStory())
.isFromQuote(true)
.build()));
.isFromQuote(true)
.build()));
}
}
void onMakeGroupAdminClicked(@NonNull Activity activity) {
new MaterialAlertDialogBuilder(activity)
.setMessage(context.getString(R.string.RecipientBottomSheet_s_will_be_able_to_edit_group, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_make_admin,
(dialog, which) -> {
adminActionBusy.setValue(true);
recipientDialogRepository.setMemberAdmin(true, result -> {
adminActionBusy.setValue(false);
if (!result) {
Toast.makeText(activity, R.string.ManageGroupActivity_failed_to_update_the_group, Toast.LENGTH_SHORT).show();
}
},
this::showErrorToast);
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {})
.show();
.setMessage(context.getString(R.string.RecipientBottomSheet_s_will_be_able_to_edit_group, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_make_admin,
(dialog, which) -> {
adminActionBusy.setValue(true);
recipientDialogRepository.setMemberAdmin(true, result -> {
adminActionBusy.setValue(false);
if (!result) {
Toast.makeText(activity, R.string.ManageGroupActivity_failed_to_update_the_group, Toast.LENGTH_SHORT).show();
}
},
this::showErrorToast);
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {})
.show();
}
void onRemoveGroupAdminClicked(@NonNull Activity activity) {
new MaterialAlertDialogBuilder(activity)
.setMessage(context.getString(R.string.RecipientBottomSheet_remove_s_as_group_admin, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_remove_as_admin,
(dialog, which) -> {
adminActionBusy.setValue(true);
recipientDialogRepository.setMemberAdmin(false, result -> {
adminActionBusy.setValue(false);
if (!result) {
Toast.makeText(activity, R.string.ManageGroupActivity_failed_to_update_the_group, Toast.LENGTH_SHORT).show();
}
},
this::showErrorToast);
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {})
.show();
.setMessage(context.getString(R.string.RecipientBottomSheet_remove_s_as_group_admin, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_remove_as_admin,
(dialog, which) -> {
adminActionBusy.setValue(true);
recipientDialogRepository.setMemberAdmin(false, result -> {
adminActionBusy.setValue(false);
if (!result) {
Toast.makeText(activity, R.string.ManageGroupActivity_failed_to_update_the_group, Toast.LENGTH_SHORT).show();
}
},
this::showErrorToast);
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {})
.show();
}
void onRemoveFromGroupClicked(@NonNull Activity activity, boolean isLinkActive, @NonNull Runnable onSuccess) {
new MaterialAlertDialogBuilder(activity)
.setMessage(context.getString(isLinkActive ? R.string.RecipientBottomSheet_remove_s_from_the_group_they_will_not_be_able_to_rejoin
: R.string.RecipientBottomSheet_remove_s_from_the_group,
Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_remove,
(dialog, which) -> {
adminActionBusy.setValue(true);
recipientDialogRepository.removeMember(result -> {
adminActionBusy.setValue(false);
if (result) {
onSuccess.run();
}
},
this::showErrorToast);
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {})
.show();
.setMessage(context.getString(isLinkActive ? R.string.RecipientBottomSheet_remove_s_from_the_group_they_will_not_be_able_to_rejoin
: R.string.RecipientBottomSheet_remove_s_from_the_group,
Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_remove,
(dialog, which) -> {
adminActionBusy.setValue(true);
recipientDialogRepository.removeMember(result -> {
adminActionBusy.setValue(false);
if (result) {
onSuccess.run();
}
},
this::showErrorToast);
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {})
.show();
}
void refreshRecipient() {

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:viewBindingIgnore="true"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -9,7 +8,8 @@
android:clickable="true"
android:focusable="true"
android:minHeight="64dp"
android:paddingHorizontal="@dimen/dsl_settings_gutter">
android:paddingHorizontal="@dimen/dsl_settings_gutter"
tools:viewBindingIgnore="true">
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/recipient_avatar"
@@ -47,21 +47,43 @@
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/recipient_name"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:gravity="start|center_vertical"
android:maxLines="2"
android:textAlignment="viewStart"
android:textAppearance="@style/Signal.Text.BodyLarge"
app:layout_constraintBottom_toTopOf="@+id/recipient_about"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/recipient_member_label"
app:layout_constraintEnd_toStartOf="@+id/admin"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@+id/recipient_avatar"
app:layout_constraintTop_toTopOf="@+id/recipient_avatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Miles Morales" />
<org.thoughtcrime.securesms.groups.memberlabel.MemberLabelPillView
android:id="@+id/recipient_member_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/recipient_about"
app:layout_constraintEnd_toStartOf="@+id/popupMenuProgressContainer"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="@+id/recipient_name"
app:layout_constraintTop_toBottomOf="@+id/recipient_name"
app:layout_constraintWidth_default="wrap"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/recipient_content_end_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="end"
app:constraint_referenced_ids="recipient_name,recipient_member_label, recipient_about" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/recipient_about"
android:layout_width="0dp"
@@ -74,17 +96,18 @@
android:textAlignment="viewStart"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_text_secondary"
app:layout_constraintBottom_toBottomOf="@+id/recipient_avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/admin"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@+id/recipient_avatar"
app:layout_constraintTop_toBottomOf="@+id/recipient_name"
app:layout_constraintTop_toBottomOf="@+id/recipient_member_label"
tools:text="Hangin' around the web" />
<TextView
android:id="@+id/admin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:gravity="start|center_vertical"
android:text="@string/GroupRecipientListItem_admin"
@@ -93,8 +116,8 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/popupMenuProgressContainer"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@+id/recipient_name"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toEndOf="@+id/recipient_content_end_barrier"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="0dp"
tools:visibility="visible" />
@@ -132,4 +155,4 @@
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -119,6 +119,7 @@
android:id="@+id/rbs_member_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dsl_settings_gutter"
android:layout_marginTop="6dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"