Add group member label editing screen.

This commit is contained in:
jeffrey-signal
2026-01-30 11:53:07 -05:00
committed by Greyson Parrelli
parent bc592cc4e2
commit 99d9c670b6
9 changed files with 505 additions and 11 deletions

View File

@@ -100,6 +100,7 @@ import org.thoughtcrime.securesms.util.ContextUtil
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.ExpirationUtil
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.navigation.safeNavigate
@@ -805,6 +806,18 @@ class ConversationSettingsFragment : DSLSettingsFragment(
}
)
if (RemoteConfig.sendMemberLabels) {
clickPref(
title = DSLSettingsText.from(R.string.ConversationSettingsFragment__group_member_label),
icon = DSLSettingsIcon.from(R.drawable.symbol_tag_24),
isEnabled = !state.isDeprecatedOrUnregistered,
onClick = {
val action = ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToMemberLabelFragment(groupState.groupId)
navController.safeNavigate(action)
}
)
}
clickPref(
title = DSLSettingsText.from(R.string.ConversationSettingsFragment__requests_and_invites),
icon = DSLSettingsIcon.from(R.drawable.ic_update_group_add_16),

View File

@@ -0,0 +1,270 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.groups.memberlabel
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
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.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
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.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelUiState.SaveState
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.viewModel
/**
* Screen for editing a user's group-specific label and emoji.
*/
class MemberLabelFragment : ComposeFragment(), ReactWithAnyEmojiBottomSheetDialogFragment.Callback {
companion object {
private const val EMOJI_PICKER_DIALOG_TAG = "emoji_picker_dialog"
}
private val args: MemberLabelFragmentArgs by navArgs()
private val viewModel: MemberLabelViewModel by viewModel {
MemberLabelViewModel(
groupId = (args.groupId as GroupId).requireV2(),
recipientId = Recipient.self().id
)
}
@Composable
override fun FragmentContent() {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val backPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
val callbacks = remember {
object : UiCallbacks {
override fun onClosePressed() {
backPressedDispatcher?.onBackPressed()
}
override fun onLabelEmojiChanged(emoji: String) = viewModel.onLabelEmojiChanged(emoji)
override fun onLabelTextChanged(text: String) = viewModel.onLabelTextChanged(text)
override fun onSetEmojiClicked() = showEmojiPicker()
override fun onClearLabelClicked() = viewModel.clearLabel()
override fun onSaveClicked() = viewModel.save()
}
}
LaunchedEffect(uiState.saveState) {
if (uiState.saveState is SaveState.Success) {
backPressedDispatcher?.onBackPressed()
viewModel.onSaveStateConsumed()
}
}
MemberLabelScreenUi(
state = uiState,
callbacks = callbacks
)
}
private fun showEmojiPicker() {
ReactWithAnyEmojiBottomSheetDialogFragment.createForAboutSelection()
.show(childFragmentManager, EMOJI_PICKER_DIALOG_TAG)
}
override fun onReactWithAnyEmojiSelected(emoji: String) = viewModel.onLabelEmojiChanged(emoji)
override fun onReactWithAnyEmojiDialogDismissed() = Unit
}
@Composable
private fun MemberLabelScreenUi(
state: MemberLabelUiState,
callbacks: UiCallbacks
) {
Scaffolds.Settings(
title = stringResource(R.string.GroupMemberLabel__title),
onNavigationClick = callbacks::onClosePressed,
navigationIcon = SignalIcons.X.imageVector,
navigationContentDescription = stringResource(R.string.GroupMemberLabel__accessibility_close_screen)
) { paddingValues ->
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(Unit) {
focusRequester.requestFocus()
keyboardController?.show()
}
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
) {
LabelTextField(
labelEmoji = state.labelEmoji,
labelText = state.labelText,
remainingCharacters = state.remainingCharacters,
onLabelTextChange = callbacks::onLabelTextChanged,
onEmojiChange = callbacks::onSetEmojiClicked,
onClear = callbacks::onClearLabelClicked,
onSave = callbacks::onSaveClicked,
modifier = Modifier
.padding(start = 24.dp, end = 24.dp, top = 8.dp, bottom = 40.dp)
.focusRequester(focusRequester)
)
Spacer(modifier = Modifier.weight(1f))
SaveButton(
enabled = state.isSaveEnabled,
onClick = callbacks::onSaveClicked,
modifier = Modifier
.align(Alignment.End)
.padding(24.dp)
)
}
}
}
@Composable
private fun LabelTextField(
labelEmoji: String?,
labelText: String,
remainingCharacters: Int,
onLabelTextChange: (String) -> Unit,
onEmojiChange: () -> Unit,
onClear: () -> Unit,
onSave: () -> Unit,
modifier: Modifier = Modifier
) {
ClearableTextField(
value = labelText,
onValueChange = onLabelTextChange,
onClear = onClear,
clearContentDescription = stringResource(R.string.GroupMemberLabel__accessibility_clear_label),
placeholder = { Text(stringResource(R.string.GroupMemberLabel__label_text_placeholder)) },
leadingIcon = {
EmojiPickerButton(
selectedEmoji = labelEmoji,
onEmojiSelected = onEmojiChange
)
},
enabled = true,
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Sentences,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(onDone = { onSave() }),
charactersRemaining = remainingCharacters,
countdownConfig = ClearableTextField.CountdownConfig(displayThreshold = 9, warnThreshold = 5),
colors = TextFieldDefaults.colors(
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant
),
modifier = modifier
)
}
@Composable
private fun EmojiPickerButton(
onEmojiSelected: () -> Unit,
selectedEmoji: String?
) {
IconButton(
onClick = onEmojiSelected
) {
if (selectedEmoji.isNotNullOrBlank()) {
Text(
text = selectedEmoji,
style = MaterialTheme.typography.bodyLarge
)
} else {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.symbol_emoji_plus_24),
contentDescription = stringResource(R.string.GroupMemberLabel__accessibility_select_emoji),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(24.dp)
)
}
}
}
@Composable
private fun SaveButton(
enabled: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Buttons.LargeTonal(
onClick = onClick,
enabled = enabled,
modifier = modifier
) {
Text(text = stringResource(R.string.GroupMemberLabel__save))
}
}
private interface UiCallbacks {
fun onClosePressed()
fun onLabelEmojiChanged(emoji: String)
fun onLabelTextChanged(text: String)
fun onSetEmojiClicked()
fun onClearLabelClicked()
fun onSaveClicked()
object Empty : UiCallbacks {
override fun onClosePressed() = Unit
override fun onLabelEmojiChanged(emoji: String) = Unit
override fun onLabelTextChanged(text: String) = Unit
override fun onSetEmojiClicked() = Unit
override fun onClearLabelClicked() = Unit
override fun onSaveClicked() = Unit
}
}
@AllDevicePreviews
@Composable
private fun MemberLabelScreenPreview() {
Previews.Preview {
MemberLabelScreenUi(
state = MemberLabelUiState(
labelEmoji = "⛑️",
labelText = "Vet Coordinator"
),
callbacks = UiCallbacks.Empty
)
}
}

View File

@@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.RemoteConfig
/**
* Handles the retrieval and modification of group member labels.
@@ -41,14 +42,11 @@ class MemberLabelRepository(
* Sets the group member label for the current user.
*/
suspend fun setLabel(label: MemberLabel): Unit = withContext(Dispatchers.IO) {
GroupManager.updateMemberLabel(context, groupId, label.text, label.emoji ?: "")
}
if (!RemoteConfig.sendMemberLabels) {
throw IllegalStateException("Set member label not allowed due to remote config.")
}
/**
* Clears the group member label for the current user.
*/
suspend fun removeLabel(): Unit = withContext(Dispatchers.IO) {
GroupManager.updateMemberLabel(context, groupId, "", "")
GroupManager.updateMemberLabel(context, groupId, label.text, label.emoji.orEmpty())
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2025 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.groups.memberlabel.MemberLabelUiState.SaveState
import org.thoughtcrime.securesms.recipients.RecipientId
private const val MIN_LABEL_TEXT_LENGTH = 1
private const val MAX_LABEL_TEXT_LENGTH = 24
class MemberLabelViewModel(
private val memberLabelRepo: MemberLabelRepository,
private val recipientId: RecipientId
) : ViewModel() {
constructor(
groupId: GroupId.V2,
recipientId: RecipientId
) : this(
memberLabelRepo = MemberLabelRepository(groupId = groupId),
recipientId = recipientId
)
private var originalLabelEmoji: String = ""
private var originalLabelText: String = ""
private val internalUiState = MutableStateFlow(MemberLabelUiState())
val uiState: StateFlow<MemberLabelUiState> = internalUiState.asStateFlow()
init {
loadExistingLabel()
}
private fun loadExistingLabel() {
viewModelScope.launch(SignalDispatchers.IO) {
val memberLabel = memberLabelRepo.getLabel(recipientId)
originalLabelEmoji = memberLabel?.emoji.orEmpty()
originalLabelText = memberLabel?.text.orEmpty()
internalUiState.update {
it.copy(
labelEmoji = originalLabelEmoji,
labelText = originalLabelText
)
}
}
}
fun onLabelEmojiChanged(emoji: String) {
internalUiState.update {
it.copy(
labelEmoji = emoji,
hasChanges = hasChanges(emoji, it.labelText)
)
}
}
fun onLabelTextChanged(text: String) {
val sanitizedText = text.take(MAX_LABEL_TEXT_LENGTH)
internalUiState.update {
it.copy(
labelText = sanitizedText,
hasChanges = hasChanges(labelEmoji = it.labelEmoji, labelText = sanitizedText)
)
}
}
fun clearLabel() {
internalUiState.update {
it.copy(
labelEmoji = "",
labelText = "",
hasChanges = hasChanges(labelEmoji = "", labelText = "")
)
}
}
private fun hasChanges(labelEmoji: String, labelText: String): Boolean {
return labelEmoji != originalLabelEmoji || labelText != originalLabelText
}
fun save() {
if (!internalUiState.value.isSaveEnabled) {
return
}
viewModelScope.launch(SignalDispatchers.IO) {
internalUiState.update {
it.copy(saveState = SaveState.InProgress)
}
val currentState = internalUiState.value
memberLabelRepo.setLabel(
label = MemberLabel(
emoji = currentState.labelEmoji.ifEmpty { null },
text = currentState.labelText
)
)
internalUiState.update {
it.copy(saveState = SaveState.Success)
}
}
}
fun onSaveStateConsumed() {
internalUiState.update {
it.copy(saveState = null)
}
}
}
data class MemberLabelUiState(
val labelEmoji: String = "",
val labelText: String = "",
val hasChanges: Boolean = false,
val saveState: SaveState? = null
) {
val remainingCharacters: Int
get() = MAX_LABEL_TEXT_LENGTH - labelText.length
val isSaveEnabled: Boolean
get() {
val isCleared = labelText.isEmpty() && labelEmoji.isEmpty()
val hasValidLabel = labelText.length >= MIN_LABEL_TEXT_LENGTH
return hasChanges && (hasValidLabel || isCleared) && saveState != SaveState.InProgress
}
sealed interface SaveState {
data object InProgress : SaveState
data object Success : SaveState
}
}

View File

@@ -1249,5 +1249,16 @@ object RemoteConfig {
hotSwappable = true
)
/**
* Whether to enable modifying group member labels.
*/
@JvmStatic
@get:JvmName("sendMemberLabels")
val sendMemberLabels: Boolean by remoteBoolean(
key = "android.sendMemberLabels",
defaultValue = false,
hotSwappable = true
)
// endregion
}

View File

@@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="#000000" android:pathData="M15.75,6.75C16.578,6.75 17.25,7.422 17.25,8.25C17.25,9.078 16.578,9.75 15.75,9.75C14.922,9.75 14.25,9.078 14.25,8.25C14.25,7.422 14.922,6.75 15.75,6.75Z"/>
<path android:fillColor="#000000" android:fillType="evenOdd" android:pathData="M15.593,2.375C16.163,2.375 16.59,2.369 17.002,2.468C17.347,2.551 17.676,2.688 17.978,2.872C18.339,3.094 18.638,3.4 19.04,3.803L20.197,4.959C20.6,5.362 20.906,5.661 21.127,6.022C21.312,6.324 21.449,6.653 21.532,6.997C21.631,7.409 21.625,7.837 21.625,8.407V10.343C21.625,10.913 21.631,11.341 21.532,11.752C21.449,12.097 21.312,12.426 21.127,12.728C20.906,13.089 20.6,13.388 20.197,13.79L14.013,19.975C13.429,20.558 12.954,21.034 12.538,21.387C12.114,21.747 11.693,22.032 11.197,22.193C10.419,22.446 9.581,22.446 8.803,22.193C8.307,22.032 7.886,21.747 7.462,21.387C7.046,21.034 6.571,20.558 5.987,19.975L4.025,18.013C3.442,17.429 2.966,16.954 2.613,16.538C2.253,16.114 1.968,15.693 1.807,15.197C1.554,14.419 1.554,13.581 1.807,12.803C1.968,12.307 2.253,11.886 2.613,11.462C2.966,11.046 3.441,10.571 4.025,9.987L10.209,3.803C10.612,3.4 10.911,3.094 11.272,2.872C11.574,2.687 11.903,2.551 12.247,2.468C12.659,2.369 13.087,2.375 13.657,2.375H15.593ZM13.657,4.125C13.004,4.125 12.82,4.131 12.656,4.17C12.49,4.21 12.332,4.275 12.186,4.364C12.043,4.452 11.909,4.579 11.447,5.04L5.263,11.224C4.658,11.829 4.243,12.245 3.947,12.594C3.657,12.935 3.533,13.154 3.472,13.343C3.333,13.77 3.333,14.23 3.472,14.657C3.533,14.845 3.657,15.065 3.947,15.406C4.243,15.755 4.658,16.171 5.263,16.775L7.224,18.737C7.829,19.341 8.245,19.757 8.594,20.053C8.935,20.343 9.154,20.467 9.343,20.528C9.77,20.667 10.23,20.667 10.657,20.528C10.845,20.467 11.064,20.343 11.406,20.053C11.755,19.757 12.171,19.341 12.775,18.737L18.959,12.553C19.421,12.091 19.548,11.957 19.636,11.813C19.725,11.668 19.79,11.509 19.83,11.344C19.869,11.18 19.875,10.996 19.875,10.343V8.407C19.875,7.754 19.869,7.57 19.83,7.406C19.79,7.24 19.725,7.082 19.636,6.936C19.548,6.793 19.421,6.659 18.959,6.197L17.803,5.04C17.341,4.579 17.207,4.452 17.063,4.364C16.918,4.275 16.759,4.21 16.594,4.17C16.43,4.131 16.246,4.125 15.593,4.125H13.657Z"/>
</vector>

View File

@@ -102,6 +102,20 @@
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
<action
android:id="@+id/action_conversationSettingsFragment_to_memberLabelFragment"
app:destination="@id/memberLabelFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit">
<argument
android:name="group_id"
app:argType="android.os.Parcelable"
app:nullable="false" />
</action>
</fragment>
<fragment
@@ -162,6 +176,16 @@
</fragment>
<fragment
android:id="@+id/memberLabelFragment"
android:name="org.thoughtcrime.securesms.groups.memberlabel.MemberLabelFragment">
<argument
android:name="group_id"
app:argType="android.os.Parcelable"
app:nullable="false" />
</fragment>
<include app:graph="@navigation/app_settings_expire_timer" />
</navigation>

View File

@@ -5973,6 +5973,8 @@
<string name="ConversationSettingsFragment__permissions">Permissions</string>
<string name="ConversationSettingsFragment__requests_and_invites">Requests &amp; invites</string>
<string name="ConversationSettingsFragment__group_link">Group link</string>
<!-- Label for button that opens the group member label permissions screen. -->
<string name="ConversationSettingsFragment__group_member_label">Member Label</string>
<!-- Option in conversation settings to add a user as a contact -->
<string name="ConversationSettingsFragment__add_as_a_contact">Add as a contact</string>
<string name="ConversationSettingsFragment__unmute">Unmute</string>
@@ -9273,5 +9275,20 @@
<!-- Dialog body when failing to pin a message -->
<string name="PinnedMessage__check_connection">Check your connection and try again.</string>
<!-- Group member label screen title. -->
<string name="GroupMemberLabel__title">Member label</string>
<!-- Group member label text field placeholder. -->
<string name="GroupMemberLabel__label_text_placeholder">Add your role</string>
<!-- Group member label preview section header. -->
<string name="GroupMemberLabel__preview_section_header">Preview</string>
<!-- Group member label save button label. -->
<string name="GroupMemberLabel__save">Save</string>
<!-- Accessibility label for the button to open the group member label emoji picker. -->
<string name="GroupMemberLabel__accessibility_select_emoji">Select emoji</string>
<!-- Accessibility label for the group member label close screen button. -->
<string name="GroupMemberLabel__accessibility_close_screen">Close screen</string>
<!-- Accessibility label for the group member label text field clear button. -->
<string name="GroupMemberLabel__accessibility_clear_label">Clear label</string>
<!-- EOF -->
</resources>

View File

@@ -67,6 +67,7 @@ fun ClearableTextField(
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
clearable: Boolean = true,
onClear: () -> Unit = { onValueChange("") },
charactersRemainingBeforeLimit: Int = Int.MAX_VALUE,
countdownConfig: ClearableTextField.CountdownConfig? = null,
colors: TextFieldColors = defaultTextFieldColors()
@@ -84,6 +85,7 @@ fun ClearableTextField(
keyboardActions = keyboardActions,
singleLine = singleLine,
clearable = clearable,
onClear = onClear,
charactersRemaining = charactersRemainingBeforeLimit,
countdownConfig = countdownConfig,
colors = colors
@@ -104,11 +106,13 @@ fun ClearableTextField(
enabled: Boolean = true,
textStyle: TextStyle = LocalTextStyle.current,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
clearable: Boolean = true,
onClear: () -> Unit = { onValueChange("") },
charactersRemaining: Int = Int.MAX_VALUE,
countdownConfig: ClearableTextField.CountdownConfig? = null,
colors: TextFieldColors = defaultTextFieldColors()
@@ -119,7 +123,7 @@ fun ClearableTextField(
val clearButton: @Composable () -> Unit = {
ClearButton(
visible = focused,
onClick = { onValueChange("") },
onClick = onClear,
contentDescription = clearContentDescription
)
}
@@ -130,6 +134,7 @@ fun ClearableTextField(
onValueChange = onValueChange,
textStyle = textStyle,
label = label,
placeholder = placeholder,
enabled = enabled,
singleLine = singleLine,
keyboardActions = keyboardActions,
@@ -140,7 +145,11 @@ fun ClearableTextField(
colors = colors,
leadingIcon = leadingIcon,
trailingIcon = if (clearable) clearButton else null,
contentPadding = TextFieldDefaults.contentPaddingWithLabel(end = if (displayCountdown) 48.dp else 16.dp)
contentPadding = if (label == null) {
TextFieldDefaults.contentPaddingWithoutLabel(end = if (displayCountdown) 48.dp else 16.dp)
} else {
TextFieldDefaults.contentPaddingWithLabel(end = if (displayCountdown) 48.dp else 16.dp)
}
)
AnimatedVisibility(
@@ -170,9 +179,9 @@ private fun ClearButton(
onClick = onClick
) {
Icon(
painter = SignalIcons.XCircleFill.painter,
painter = SignalIcons.X.painter,
contentDescription = contentDescription,
tint = MaterialTheme.colorScheme.outline
tint = MaterialTheme.colorScheme.onSurface
)
}
}