Ensure that the keyboard focus and accessibility focus is not lost when deleting a pending voice message.

This commit is contained in:
Benoit Marty
2026-01-08 12:09:29 +01:00
parent 9f9a017ffa
commit 75fc734892
2 changed files with 76 additions and 74 deletions

View File

@@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeightIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
@@ -143,26 +144,6 @@ fun TextComposer(
.fillMaxSize()
.height(IntrinsicSize.Min)
val composerOptionsButton: @Composable () -> Unit = remember(composerMode) {
@Composable {
when (composerMode) {
is MessageComposerMode.Attachment -> {
Spacer(modifier = Modifier.width(9.dp))
}
is MessageComposerMode.EditCaption -> {
Spacer(modifier = Modifier.width(16.dp))
}
else -> {
IconColorButton(
onClick = onAddAttachment,
imageVector = CompoundIcons.Plus(),
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
)
}
}
}
}
val placeholder = if (composerMode.inThread) {
stringResource(id = CommonStrings.action_reply_in_thread)
} else if (composerMode is MessageComposerMode.Attachment || composerMode is MessageComposerMode.EditCaption) {
@@ -337,16 +318,6 @@ fun TextComposer(
}
}
val voiceDeleteButton = @Composable {
when (voiceMessageState) {
is VoiceMessageState.Preview ->
VoiceMessageDeleteButton(enabled = !voiceMessageState.isSending, onClick = onDeleteVoiceMessage)
is VoiceMessageState.Recording ->
VoiceMessageDeleteButton(enabled = true, onClick = { onVoiceRecorderEvent(VoiceMessageRecorderEvent.Cancel) })
else -> {}
}
}
if (showTextFormatting && textFormattingOptions != null) {
TextFormattingLayout(
modifier = layoutModifier,
@@ -366,16 +337,18 @@ fun TextComposer(
)
} else {
StandardLayout(
composerMode = composerMode,
voiceMessageState = voiceMessageState,
isRoomEncrypted = state.isRoomEncrypted,
modifier = layoutModifier,
composerOptionsButton = composerOptionsButton,
textInput = textInput,
endButton = sendOrRecordButton,
endButtonClick = ::endButtonClickStandard,
endButtonA11y = endButtonA11y,
voiceRecording = voiceRecording,
voiceDeleteButton = voiceDeleteButton,
onAddAttachment = onAddAttachment,
onDeleteVoiceMessage = onDeleteVoiceMessage,
onVoiceRecorderEvent = onVoiceRecorderEvent,
)
}
@@ -434,15 +407,17 @@ private fun endButtonA11y(
@Composable
private fun StandardLayout(
composerMode: MessageComposerMode,
voiceMessageState: VoiceMessageState,
isRoomEncrypted: Boolean?,
textInput: @Composable () -> Unit,
composerOptionsButton: @Composable () -> Unit,
voiceRecording: @Composable () -> Unit,
voiceDeleteButton: @Composable () -> Unit,
endButton: @Composable () -> Unit,
endButtonClick: () -> Unit,
endButtonA11y: (SemanticsPropertyReceiver.() -> Unit),
onAddAttachment: () -> Unit,
onDeleteVoiceMessage: () -> Unit,
onVoiceRecorderEvent: (VoiceMessageRecorderEvent) -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier) {
@@ -452,21 +427,54 @@ private fun StandardLayout(
Spacer(Modifier.height(4.dp))
}
Row(verticalAlignment = Alignment.Bottom) {
if (voiceMessageState is VoiceMessageState.Idle) {
Box(
Modifier
.padding(bottom = 5.dp, top = 5.dp, start = 3.dp)
) {
composerOptionsButton()
when (composerMode) {
is MessageComposerMode.Attachment -> {
Spacer(modifier = Modifier.width(12.dp))
}
} else {
Box(
modifier = Modifier
.padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp)
.size(48.dp),
contentAlignment = Alignment.Center,
) {
voiceDeleteButton()
is MessageComposerMode.EditCaption -> {
Spacer(modifier = Modifier.width(19.dp))
}
else -> {
val endPadding = if (voiceMessageState is VoiceMessageState.Idle) 0.dp else 3.dp
// To avoid loosing keyboard focus, the IconButton has to be defined here and has to be always enabled.
IconButton(
modifier = Modifier
.padding(top = 5.dp, bottom = 5.dp, start = 3.dp, end = endPadding)
.size(48.dp),
onClick = {
if (voiceMessageState is VoiceMessageState.Idle) {
onAddAttachment()
} else {
when (voiceMessageState) {
is VoiceMessageState.Preview -> if (!voiceMessageState.isSending) {
onDeleteVoiceMessage()
}
is VoiceMessageState.Recording ->
onVoiceRecorderEvent(VoiceMessageRecorderEvent.Cancel)
}
}
},
) {
if (voiceMessageState is VoiceMessageState.Idle) {
Icon(
modifier = Modifier
.clip(CircleShape)
.size(30.dp)
.background(ElementTheme.colors.iconPrimary)
.padding(3.dp),
imageVector = CompoundIcons.Plus(),
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
tint = ElementTheme.colors.iconOnSolidPrimary
)
} else {
when (voiceMessageState) {
is VoiceMessageState.Preview ->
VoiceMessageDeleteButton(enabled = !voiceMessageState.isSending)
is VoiceMessageState.Recording ->
VoiceMessageDeleteButton(enabled = true)
}
}
}
}
}
Box(

View File

@@ -25,39 +25,33 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun VoiceMessageDeleteButton(
enabled: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
IconButton(
modifier = modifier
.size(48.dp),
enabled = enabled,
onClick = onClick,
) {
Icon(
modifier = Modifier.size(24.dp),
imageVector = CompoundIcons.Delete(),
contentDescription = stringResource(CommonStrings.a11y_delete),
tint = if (enabled) {
ElementTheme.colors.iconCriticalPrimary
} else {
ElementTheme.colors.iconDisabled
},
)
}
Icon(
modifier = modifier.size(24.dp),
imageVector = CompoundIcons.Delete(),
contentDescription = stringResource(CommonStrings.a11y_delete),
tint = if (enabled) {
ElementTheme.colors.iconCriticalPrimary
} else {
ElementTheme.colors.iconDisabled
},
)
}
@PreviewsDayNight
@Composable
internal fun VoiceMessageDeleteButtonPreview() = ElementPreview {
Row {
VoiceMessageDeleteButton(
enabled = true,
onClick = {},
)
VoiceMessageDeleteButton(
enabled = false,
onClick = {},
)
IconButton(onClick = {}) {
VoiceMessageDeleteButton(
enabled = true,
)
}
IconButton(onClick = {}) {
VoiceMessageDeleteButton(
enabled = false,
)
}
}
}