diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt index ff4c1c2468..55635e94ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt index f7f637f967..eabe5801c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt @@ -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 = emptyMap() ) : SpecificSettingsState() { override val isLoaded: Boolean = groupTitleLoaded && groupDescriptionLoaded diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt index 5ff9c12ea4..bc4eab23eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt @@ -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) = 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( 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 803c715b99..ed35288b23 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,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() { @@ -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 { - override fun onChanged(recipient: Recipient) { - onRecipientChanged(recipient) - } + private val recipientObserver = Observer { 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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt index 1d25b1a1b5..8cbb017a87 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt @@ -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 = 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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabel.kt new file mode 100644 index 0000000000..cce7b1ff89 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabel.kt @@ -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 +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelPill.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelPill.kt index 0ceba7a879..ec625a3be1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelPill.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelPill.kt @@ -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 ) } } 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 5c7c8e2564..7ffc4acec1 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 @@ -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 } + ) } 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 c6f1ffd0c2..4154981282 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 @@ -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): Map = 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 -) diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDetailsState.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDetailsState.kt index 9d4c06f3ce..5f2021669f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDetailsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDetailsState.kt @@ -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 -) diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java index 9ed2c7545a..990bf9e5a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java @@ -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; 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() { diff --git a/app/src/main/res/layout/group_recipient_list_item.xml b/app/src/main/res/layout/group_recipient_list_item.xml index 67763f019c..25ec304d21 100644 --- a/app/src/main/res/layout/group_recipient_list_item.xml +++ b/app/src/main/res/layout/group_recipient_list_item.xml @@ -1,7 +1,6 @@ + android:paddingHorizontal="@dimen/dsl_settings_gutter" + tools:viewBindingIgnore="true"> + + + + @@ -132,4 +155,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/recipient_bottom_sheet.xml b/app/src/main/res/layout/recipient_bottom_sheet.xml index 7f8844cc66..2531d62f83 100644 --- a/app/src/main/res/layout/recipient_bottom_sheet.xml +++ b/app/src/main/res/layout/recipient_bottom_sheet.xml @@ -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"