Add member labels education sheet.

This commit is contained in:
jeffrey-signal
2026-02-25 17:24:31 -05:00
committed by Cody Henthorne
parent 955bcde062
commit 0b2d3edcce
12 changed files with 385 additions and 10 deletions

View File

@@ -933,6 +933,11 @@
android:exported="false"
android:theme="@style/Signal.DayNight.NoActionBar" />
<activity
android:name=".groups.memberlabel.MemberLabelActivity"
android:exported="false"
android:theme="@style/Signal.DayNight.NoActionBar" />
<!-- ======================================= -->
<!-- Activity Aliases -->
<!-- ======================================= -->

View File

@@ -35,6 +35,7 @@ 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.core.util.requireParcelableCompat
import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.AvatarPreviewActivity
import org.thoughtcrime.securesms.BlockUnblockDialog
@@ -71,6 +72,7 @@ import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.conversation.colors.ColorizerV2
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelEducationSheet
import org.thoughtcrime.securesms.groups.memberlabel.StyledMemberLabel
import org.thoughtcrime.securesms.groups.ui.GroupErrors
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog
@@ -189,6 +191,16 @@ class ConversationSettingsFragment :
super.onViewCreated(view, savedInstanceState)
parentFragmentManager.setFragmentResultListener(MemberLabelEducationSheet.RESULT_EDIT_MEMBER_LABEL, viewLifecycleOwner) { _, bundle ->
val groupId = bundle.requireParcelableCompat(MemberLabelEducationSheet.KEY_GROUP_ID, GroupId.V2::class.java)
navController.safeNavigate(ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToMemberLabelFragment(groupId))
}
parentFragmentManager.setFragmentResultListener(AboutSheet.RESULT_EDIT_MEMBER_LABEL, viewLifecycleOwner) { _, bundle ->
val groupId = bundle.requireParcelableCompat(AboutSheet.RESULT_GROUP_ID, GroupId.V2::class.java)
navController.safeNavigate(ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToMemberLabelFragment(groupId))
}
recyclerView?.addOnScrollListener(ConversationSettingsOnUserScrolledAnimationHelper(toolbarAvatarContainer, toolbarTitle, toolbarBackground))
}

View File

@@ -120,6 +120,7 @@ import org.signal.core.util.concurrent.addTo
import org.signal.core.util.dp
import org.signal.core.util.logging.Log
import org.signal.core.util.orNull
import org.signal.core.util.requireParcelableCompat
import org.signal.core.util.setActionItemTint
import org.signal.donations.InAppPaymentType
import org.signal.ringrtc.CallLinkRootKey
@@ -158,7 +159,6 @@ import org.thoughtcrime.securesms.components.mention.MentionAnnotation
import org.thoughtcrime.securesms.components.menu.ActionItem
import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity.Companion.remoteBackups
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.CheckoutFlowActivity
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalFragment
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity
@@ -257,6 +257,8 @@ import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionPlayerHolder
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionRecycler
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelActivity
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelEducationSheet
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason
import org.thoughtcrime.securesms.groups.ui.GroupErrors
import org.thoughtcrime.securesms.groups.ui.LeaveGroupDialog
@@ -319,6 +321,7 @@ import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDial
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientExporter
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.recipients.ui.about.AboutSheet
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment
import org.thoughtcrime.securesms.recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity
import org.thoughtcrime.securesms.registration.ui.RegistrationActivity
@@ -686,6 +689,16 @@ class ConversationFragment :
container.fragmentManager = childFragmentManager
childFragmentManager.setFragmentResultListener(MemberLabelEducationSheet.RESULT_EDIT_MEMBER_LABEL, viewLifecycleOwner) { _, bundle ->
val groupId = bundle.requireParcelableCompat(MemberLabelEducationSheet.KEY_GROUP_ID, GroupId.V2::class.java)
startActivity(MemberLabelActivity.createIntent(requireContext(), groupId))
}
childFragmentManager.setFragmentResultListener(AboutSheet.RESULT_EDIT_MEMBER_LABEL, viewLifecycleOwner) { _, bundle ->
val groupId = bundle.requireParcelableCompat(AboutSheet.RESULT_GROUP_ID, GroupId.V2::class.java)
startActivity(MemberLabelActivity.createIntent(requireContext(), groupId))
}
ToolbarDependentMarginListener(binding.toolbar)
initializeMediaKeyboard()
@@ -1004,9 +1017,13 @@ class ConversationFragment :
when {
state.isReactionDelegateShowing -> reactionDelegate.hide()
state.isSearchRequested -> searchMenuItem?.collapseActionView()
state.isInActionMode -> finishActionMode()
state.isMediaKeyboardShowing -> container.hideInput()
else -> {
// State has changed since the back handler was enabled. Let the back press proceed
// to the next handler by triggering onBackPressed again after setting a skip flag
@@ -1886,13 +1903,16 @@ class ConversationFragment :
when (data) {
is ShareOrDraftData.SendKeyboardImage -> sendMessageWithoutComposeInput(slide = data.slide, clearCompose = false)
is ShareOrDraftData.SendSticker -> sendMessageWithoutComposeInput(slide = data.slide, clearCompose = true)
is ShareOrDraftData.SetText -> {
composeText.setDraftText(data.text)
inputPanel.clickOnComposeInput()
}
is ShareOrDraftData.SetLocation -> attachmentManager.setLocation(data.location, MediaConstraints.getPushMediaConstraints())
is ShareOrDraftData.SetEditMessage -> {
composeText.setDraftText(data.draftText)
inputPanel.enterEditMessageMode(Glide.with(this), data.messageEdit, true, data.clearQuote)
@@ -2689,6 +2709,7 @@ class ConversationFragment :
.subscribeBy { result ->
when (result) {
is Result.Success -> Log.d(TAG, "$logMessage complete")
is Result.Failure -> {
Log.d(TAG, "$logMessage failed ${result.failure}")
toast(GroupErrors.getUserDisplayMessage(result.failure))
@@ -4156,8 +4177,11 @@ class ConversationFragment :
val slides: List<Slide> = result.nonUploadedMedia.mapNotNull {
when {
MediaUtil.isVideoType(it.contentType) -> VideoSlide(requireContext(), it.uri, it.size, it.isVideoGif, it.width, it.height, it.caption, it.transformProperties)
MediaUtil.isGif(it.contentType) -> GifSlide(requireContext(), it.uri, it.size, it.width, it.height, it.isBorderless, it.caption)
MediaUtil.isImageType(it.contentType) -> ImageSlide(requireContext(), it.uri, it.contentType, it.size, it.width, it.height, it.isBorderless, it.caption, null, it.transformProperties)
MediaUtil.isDocumentType(it.contentType) -> {
DocumentSlide(requireContext(), it.uri, it.contentType!!, it.size, it.fileName)
}
@@ -4359,6 +4383,7 @@ class ConversationFragment :
.subscribeBy { result ->
when (result) {
is Result.Success -> Log.d(TAG, "Cancel request complete")
is Result.Failure -> {
Log.d(TAG, "Cancel join request failed ${result.failure}")
toast(GroupErrors.getUserDisplayMessage(result.failure))
@@ -4737,9 +4762,13 @@ class ConversationFragment :
if (button != null) {
when (button) {
AttachmentKeyboardButton.GALLERY -> conversationActivityResultContracts.launchGallery(recipient.id, composeText.textTrimmed, inputPanel.quote.isPresent)
AttachmentKeyboardButton.CONTACT -> conversationActivityResultContracts.launchSelectContact()
AttachmentKeyboardButton.LOCATION -> conversationActivityResultContracts.launchSelectLocation(recipient.chatColors)
AttachmentKeyboardButton.PAYMENT -> AttachmentManager.selectPayment(this@ConversationFragment, recipient)
AttachmentKeyboardButton.FILE -> {
if (!conversationActivityResultContracts.launchSelectFile()) {
toast(R.string.AttachmentManager_cant_open_media_selection, Toast.LENGTH_LONG)

View File

@@ -244,6 +244,10 @@ sealed class GroupId(private val encodedId: String) : DatabaseId, Parcelable {
return this as V2
}
fun v2OrNull(): V2? {
return if (isV2) (this as V2) else null
}
fun requirePush(): Push {
assert(this is Push)
return this as Push

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.groups.memberlabel
import android.content.Context
import android.content.Intent
import android.os.Bundle
import org.signal.core.util.getParcelableExtraCompat
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.groups.GroupId
/**
* Hosts [MemberLabelFragment], allowing navigation to the member label editor from any context.
*/
class MemberLabelActivity : PassphraseRequiredActivity() {
companion object {
private const val EXTRA_GROUP_ID = "group_id"
fun createIntent(context: Context, groupId: GroupId.V2): Intent {
return Intent(context, MemberLabelActivity::class.java).apply {
putExtra(EXTRA_GROUP_ID, groupId)
}
}
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
if (savedInstanceState == null) {
val groupId = intent.getParcelableExtraCompat(EXTRA_GROUP_ID, GroupId.V2::class.java)!!
val fragment = MemberLabelFragment.newInstance(groupId)
supportFragmentManager.beginTransaction()
.replace(android.R.id.content, fragment)
.commit()
}
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.groups.memberlabel
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.setFragmentResult
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.signal.core.ui.compose.AllDevicePreviews
import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.util.requireParcelableCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.util.viewModel
/**
* Explains what member labels are and provides options to edit the current user's label.
*/
class MemberLabelEducationSheet : ComposeBottomSheetDialogFragment() {
companion object {
const val RESULT_EDIT_MEMBER_LABEL = "edit_member_label"
const val KEY_GROUP_ID = "group_id"
private const val FRAGMENT_TAG = "MemberLabelEducationSheet"
private const val ARGS_GROUP_ID = "group_id"
fun show(fragmentManager: FragmentManager, groupId: GroupId.V2) {
val fragment = MemberLabelEducationSheet().apply {
arguments = bundleOf(ARGS_GROUP_ID to groupId)
}
fragment.show(fragmentManager, FRAGMENT_TAG)
}
}
private val groupId: GroupId.V2 by lazy {
requireArguments().requireParcelableCompat(ARGS_GROUP_ID, GroupId.V2::class.java)
}
private val viewModel: MemberLabelEducationViewModel by viewModel {
MemberLabelEducationViewModel(groupId)
}
override val peekHeightPercentage: Float = 1f
@Composable
override fun SheetContent() {
val state by viewModel.uiState.collectAsStateWithLifecycle()
val callbacks = remember {
object : MemberLabelEducationUiCallbacks {
override fun onSetLabelClicked() {
setFragmentResult(RESULT_EDIT_MEMBER_LABEL, bundleOf(KEY_GROUP_ID to groupId))
dismiss()
}
override fun onDismiss() = dismiss()
}
}
MemberLabelEducationSheetContent(
state = state,
callbacks = callbacks
)
}
}
@Composable
private fun MemberLabelEducationSheetContent(
state: MemberLabelEducationViewModel.UiState,
callbacks: MemberLabelEducationUiCallbacks = MemberLabelEducationUiCallbacks.Empty
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(top = 4.dp, bottom = 28.dp, start = 28.dp, end = 28.dp)
.verticalScroll(rememberScrollState())
) {
BottomSheets.Handle()
Image(
painter = painterResource(R.drawable.symbol_tag_filled_64),
contentDescription = null,
modifier = Modifier
.padding(top = 24.dp)
.size(64.dp)
)
Text(
text = stringResource(R.string.MemberLabelsEducation__title),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(top = 16.dp)
)
Text(
text = stringResource(R.string.MemberLabelsEducation__body),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 12.dp)
)
if (state.selfCanSetLabel) {
TextButton(
onClick = callbacks::onSetLabelClicked,
modifier = Modifier.padding(top = 56.dp)
) {
Text(
text = stringResource(
if (state.selfHasLabel) R.string.MemberLabelsEducation__edit_label
else R.string.MemberLabelsEducation__set_label
)
)
}
} else {
Spacer(modifier = Modifier.height(56.dp))
}
Buttons.LargeTonal(
onClick = callbacks::onDismiss,
modifier = Modifier
.padding(top = 16.dp)
.defaultMinSize(minWidth = 220.dp)
) {
Text(text = stringResource(android.R.string.ok))
}
}
}
private interface MemberLabelEducationUiCallbacks {
fun onSetLabelClicked()
fun onDismiss()
object Empty : MemberLabelEducationUiCallbacks {
override fun onSetLabelClicked() = Unit
override fun onDismiss() = Unit
}
}
@AllDevicePreviews
@Composable
private fun MemberLabelEducationSheetPreviewCanSetNoLabel() = Previews.Preview {
MemberLabelEducationSheetContent(
state = MemberLabelEducationViewModel.UiState(
selfHasLabel = false,
selfCanSetLabel = true
)
)
}
@DayNightPreviews
@Composable
private fun MemberLabelEducationSheetPreviewCanSetHasLabel() = Previews.Preview {
MemberLabelEducationSheetContent(
state = MemberLabelEducationViewModel.UiState(
selfHasLabel = true,
selfCanSetLabel = true
)
)
}
@DayNightPreviews
@Composable
private fun MemberLabelEducationSheetPreviewCannotSet() = Previews.Preview {
MemberLabelEducationSheetContent(
state = MemberLabelEducationViewModel.UiState(
selfHasLabel = false,
selfCanSetLabel = false
)
)
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.groups.memberlabel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.signal.core.util.concurrent.SignalDispatchers
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.recipients.Recipient
class MemberLabelEducationViewModel(
private val groupId: GroupId.V2,
private val repository: MemberLabelRepository = MemberLabelRepository.instance
) : ViewModel() {
data class UiState(
val selfHasLabel: Boolean = false,
val selfCanSetLabel: Boolean = false
)
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
init {
viewModelScope.launch(SignalDispatchers.IO) {
val self = Recipient.self()
val selfMemberLabel = repository.getLabel(groupId, self.id)
val selfCanSetLabel = repository.canSetLabel(groupId, self)
_uiState.update {
it.copy(
selfHasLabel = selfMemberLabel != null,
selfCanSetLabel = selfCanSetLabel
)
}
}
}
}

View File

@@ -38,8 +38,8 @@ import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.fragment.navArgs
import org.signal.core.ui.compose.AllDevicePreviews
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ClearableTextField
@@ -48,6 +48,7 @@ import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.util.isNotNullOrBlank
import org.signal.core.util.requireParcelableCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelUiState.SaveState
@@ -62,12 +63,22 @@ import org.thoughtcrime.securesms.util.viewModel
class MemberLabelFragment : ComposeFragment(), ReactWithAnyEmojiBottomSheetDialogFragment.Callback {
companion object {
private const val EMOJI_PICKER_DIALOG_TAG = "emoji_picker_dialog"
private const val ARG_GROUP_ID = "group_id"
fun newInstance(groupId: GroupId.V2): MemberLabelFragment {
return MemberLabelFragment().apply {
arguments = bundleOf(ARG_GROUP_ID to groupId)
}
}
}
private val groupId: GroupId.V2 by lazy {
requireArguments().requireParcelableCompat(ARG_GROUP_ID, GroupId.V2::class.java)
}
private val args: MemberLabelFragmentArgs by navArgs()
private val viewModel: MemberLabelViewModel by viewModel {
MemberLabelViewModel(
groupId = (args.groupId as GroupId).requireV2(),
groupId = groupId,
recipientId = Recipient.self().id
)
}

View File

@@ -40,8 +40,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.os.bundleOf
import androidx.core.widget.TextViewCompat
import androidx.fragment.app.setFragmentResult
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.Navigation
import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
@@ -53,7 +53,6 @@ import org.thoughtcrime.securesms.AvatarPreviewActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.AvatarImage
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsFragmentDirections
import org.thoughtcrime.securesms.conversation.v2.UnverifiedProfileNameBottomSheet
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel
@@ -72,6 +71,9 @@ import org.signal.core.ui.R as CoreUiR
class AboutSheet : ComposeBottomSheetDialogFragment() {
companion object {
const val RESULT_EDIT_MEMBER_LABEL = "edit_member_label"
const val RESULT_GROUP_ID = "group_id"
private const val RECIPIENT_ID = "recipient_id"
private const val VIEWING_FROM_GROUP_ID = "viewing_from_group_id"
@@ -157,8 +159,7 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
private fun openMemberLabelScreen() {
viewingFromGroupId?.let { groupId ->
val navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment)
navController.navigate(ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToMemberLabelFragment(groupId))
setFragmentResult(RESULT_EDIT_MEMBER_LABEL, bundleOf(RESULT_GROUP_ID to groupId))
dismiss()
}
}

View File

@@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.components.settings.conversation.preferences.B
import org.thoughtcrime.securesms.conversation.v2.data.AvatarDownloadStateCache
import org.thoughtcrime.securesms.fonts.SignalSymbols
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelEducationSheet
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelPillView
import org.thoughtcrime.securesms.nicknames.NicknameActivity
import org.thoughtcrime.securesms.recipients.Recipient
@@ -334,7 +335,7 @@ class RecipientBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDialogFr
}
viewModel.recipientDetails.observe(viewLifecycleOwner) { state ->
updateRecipientDetails(state, memberLabelView, aboutView)
updateRecipientDetails(state, memberLabelView, aboutView, groupId?.v2OrNull())
}
viewModel.canAddToAGroup.observe(getViewLifecycleOwner()) { canAdd: Boolean ->
@@ -450,13 +451,23 @@ class RecipientBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDialogFr
private fun updateRecipientDetails(
state: RecipientDetailsState,
memberLabelView: MemberLabelPillView,
aboutView: TextView
aboutView: TextView,
groupId: GroupId.V2?
) {
when {
state.memberLabel != null -> {
memberLabelView.setLabel(state.memberLabel.label, state.memberLabel.tintColor)
memberLabelView.visible = true
aboutView.visible = false
if (groupId != null) {
memberLabelView.setOnClickListener {
dismiss()
MemberLabelEducationSheet.show(parentFragmentManager, groupId)
}
} else {
memberLabelView.setOnClickListener(null)
}
}
!state.aboutText.isNullOrBlank() -> {

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="64dp" android:viewportHeight="64" android:viewportWidth="64" android:width="64dp">
<path android:fillColor="#D2D8FE" android:pathData="M45.5,8H33L19,20L7.5,32V40L24,56.5H30L54.5,33L56,17.5L45.5,8Z"/>
<path android:fillColor="#3B45FD" android:pathData="M42,18.333C44.025,18.333 45.667,19.975 45.667,22C45.667,24.025 44.025,25.667 42,25.667C39.975,25.667 38.334,24.025 38.334,22C38.334,19.975 39.975,18.333 42,18.333Z"/>
<path android:fillColor="#3B45FD" android:fillType="evenOdd" android:pathData="M41.582,6.667C43.118,6.667 44.211,6.654 45.262,6.906C46.146,7.119 46.992,7.469 47.767,7.944C48.689,8.509 49.453,9.291 50.538,10.376L53.624,13.462C54.709,14.547 55.492,15.312 56.056,16.233C56.532,17.008 56.882,17.854 57.094,18.738C57.346,19.789 57.334,20.883 57.334,22.418V27.582C57.334,29.117 57.346,30.211 57.094,31.262C56.882,32.146 56.532,32.992 56.056,33.767C55.492,34.688 54.709,35.452 53.624,36.538L37.132,53.03C35.571,54.59 34.317,55.847 33.22,56.779C32.105,57.725 31.021,58.452 29.757,58.863C27.749,59.516 25.585,59.516 23.577,58.863C22.313,58.452 21.229,57.725 20.114,56.779C19.017,55.847 17.763,54.59 16.202,53.03L10.97,47.798C9.41,46.238 8.153,44.984 7.222,43.887C6.275,42.772 5.548,41.688 5.137,40.423C4.485,38.415 4.485,36.252 5.137,34.243C5.548,32.979 6.275,31.895 7.222,30.78C8.153,29.683 9.41,28.429 10.97,26.868L27.463,10.376C28.548,9.291 29.312,8.509 30.233,7.944C31.009,7.469 31.854,7.118 32.739,6.906C33.789,6.654 34.883,6.667 36.418,6.667H41.582ZM36.418,10.667C34.693,10.667 34.156,10.679 33.672,10.795C33.196,10.91 32.741,11.098 32.323,11.354C31.899,11.614 31.51,11.985 30.291,13.204L13.799,29.697C12.191,31.304 11.072,32.426 10.271,33.37C9.485,34.295 9.123,34.917 8.94,35.479C8.549,36.684 8.549,37.982 8.94,39.187C9.123,39.75 9.485,40.371 10.271,41.297C11.072,42.24 12.191,43.363 13.799,44.97L19.03,50.202C20.638,51.809 21.76,52.928 22.704,53.729C23.629,54.515 24.251,54.877 24.813,55.06C26.018,55.451 27.316,55.451 28.521,55.06C29.083,54.877 29.705,54.515 30.631,53.729C31.574,52.928 32.696,51.809 34.304,50.202L50.796,33.71C52.016,32.49 52.386,32.101 52.646,31.677C52.902,31.26 53.09,30.804 53.205,30.328C53.321,29.845 53.334,29.307 53.334,27.582V22.418C53.334,20.692 53.321,20.155 53.205,19.672C53.09,19.196 52.902,18.74 52.646,18.323C52.386,17.899 52.016,17.51 50.796,16.29L47.71,13.204C46.49,11.985 46.101,11.614 45.677,11.354C45.26,11.098 44.805,10.91 44.328,10.795C43.845,10.679 43.308,10.667 41.582,10.667H36.418Z"/>
</vector>

View File

@@ -9385,5 +9385,14 @@
<string name="GroupMemberLabel__accessibility_clear_label">Clear label</string>
<string name="GroupMemberLabel__description">Add a member label to describe yourself or your role in this group. Labels are only visible within this group.</string>
<!-- Title for the member labels education sheet. -->
<string name="MemberLabelsEducation__title">Member labels</string>
<!-- Body for the member labels education sheet. -->
<string name="MemberLabelsEducation__body">Use a member label to describe yourself or your role in this group. Member labels are only visible within this group.</string>
<!-- Button to set a new member label. -->
<string name="MemberLabelsEducation__set_label">Set a member label</string>
<!-- Button to edit an existing member label. -->
<string name="MemberLabelsEducation__edit_label">Edit your label</string>
<!-- EOF -->
</resources>