diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index 47216d7e88..91e36de12c 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -39,7 +40,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.SemanticsPropertyReceiver +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.hideFromAccessibility +import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -280,6 +285,13 @@ fun TextComposer( else -> sendButton } + val endButtonA11y = endButtonA11y( + composerMode = composerMode, + voiceMessageState = voiceMessageState, + enableVoiceMessages = enableVoiceMessages, + canSendMessage = canSendMessage, + ) + val voiceRecording = @Composable { when (voiceMessageState) { is VoiceMessageState.Preview -> @@ -323,6 +335,7 @@ fun TextComposer( ) }, textFormatting = textFormattingOptions, + endButtonA11y = endButtonA11y, sendButton = sendButton, ) } else { @@ -334,6 +347,7 @@ fun TextComposer( composerOptionsButton = composerOptionsButton, textInput = textInput, endButton = sendOrRecordButton, + endButtonA11y = endButtonA11y, voiceRecording = voiceRecording, voiceDeleteButton = voiceDeleteButton, ) @@ -359,6 +373,40 @@ fun TextComposer( } } +@ReadOnlyComposable +@Composable +private fun endButtonA11y( + composerMode: MessageComposerMode, + voiceMessageState: VoiceMessageState, + enableVoiceMessages: Boolean, + canSendMessage: Boolean, +): (SemanticsPropertyReceiver) -> Unit { + val a11ySendButtonDescription = stringResource( + id = when { + enableVoiceMessages && !canSendMessage -> + when (voiceMessageState) { + VoiceMessageState.Idle, + is VoiceMessageState.Recording -> if (voiceMessageState is VoiceMessageState.Recording) { + CommonStrings.a11y_voice_message_stop_recording + } else { + CommonStrings.a11y_voice_message_record + } + is VoiceMessageState.Preview -> when (voiceMessageState.isSending) { + true -> CommonStrings.common_sending + false -> CommonStrings.action_send_voice_message + } + } + composerMode.isEditing -> CommonStrings.action_send_edited_message + else -> CommonStrings.action_send_message + } + ) + val endButtonA11y: (SemanticsPropertyReceiver.() -> Unit) = { + contentDescription = a11ySendButtonDescription + onClick(null, null) + } + return endButtonA11y +} + @Composable private fun StandardLayout( voiceMessageState: VoiceMessageState, @@ -369,6 +417,7 @@ private fun StandardLayout( voiceRecording: @Composable () -> Unit, voiceDeleteButton: @Composable () -> Unit, endButton: @Composable () -> Unit, + endButtonA11y: (SemanticsPropertyReceiver.() -> Unit), modifier: Modifier = Modifier, ) { Column(modifier = modifier) { @@ -416,7 +465,8 @@ private fun StandardLayout( Box( Modifier .padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp) - .size(48.dp), + .size(48.dp) + .clearAndSetSemantics(endButtonA11y), contentAlignment = Alignment.Center, ) { endButton() @@ -454,6 +504,7 @@ private fun TextFormattingLayout( dismissTextFormattingButton: @Composable () -> Unit, textFormatting: @Composable () -> Unit, sendButton: @Composable () -> Unit, + endButtonA11y: (SemanticsPropertyReceiver.() -> Unit), modifier: Modifier = Modifier ) { Column( @@ -485,10 +536,12 @@ private fun TextFormattingLayout( textFormatting() } Box( - modifier = Modifier.padding( - start = 14.dp, - end = 6.dp - ) + modifier = Modifier + .padding( + start = 14.dp, + end = 6.dp, + ) + .clearAndSetSemantics(endButtonA11y) ) { sendButton() } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt index aeeda86269..55e4350fb2 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt @@ -21,7 +21,6 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.LinearGradientShader import androidx.compose.ui.graphics.ShaderBrush -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons @@ -32,7 +31,6 @@ import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.textcomposer.model.MessageComposerMode -import io.element.android.libraries.ui.strings.CommonStrings /** * Send button for the message composer. @@ -60,10 +58,6 @@ internal fun SendButton( composerMode.isEditing -> 0.dp else -> 2.dp } - val contentDescription = when { - composerMode.isEditing -> stringResource(CommonStrings.action_edit) - else -> stringResource(CommonStrings.action_send) - } Box( modifier = Modifier .clip(CircleShape) @@ -81,7 +75,8 @@ internal fun SendButton( .padding(start = iconStartPadding) .align(Alignment.Center), imageVector = iconVector, - contentDescription = contentDescription, + // Note: accessibility is managed in TextComposer. + contentDescription = null, tint = if (canSendMessage) { if (ElementTheme.colors.isLight) { ElementTheme.colors.iconOnSolidPrimary diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt index 03965566e5..3a92dfbeb0 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons @@ -26,7 +25,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent -import io.element.android.libraries.ui.strings.CommonStrings @Composable internal fun VoiceMessageRecorderButton( @@ -70,7 +68,8 @@ private fun StartButton( Icon( modifier = Modifier.size(24.dp), imageVector = CompoundIcons.MicOn(), - contentDescription = stringResource(CommonStrings.a11y_voice_message_record), + // Note: accessibility is managed in TextComposer. + contentDescription = null, tint = ElementTheme.colors.iconSecondary, ) } @@ -95,7 +94,8 @@ private fun StopButton( Icon( modifier = Modifier.size(24.dp), resourceId = CommonDrawables.ic_stop, - contentDescription = stringResource(CommonStrings.a11y_voice_message_stop_recording), + // Note: accessibility is managed in TextComposer. + contentDescription = null, tint = ElementTheme.colors.iconOnSolidPrimary, ) } diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 41e87f83c5..f57bcef62d 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -12,7 +12,7 @@ "Jump to bottom" "Mentions only" "Muted" - "Other user avatar" + "Other user\'s avatar" "Page %1$d" "Pause" "Voice message, duration: %1$s, current position: %2$s" @@ -125,7 +125,9 @@ "Save" "Search" "Send" + "Send edited message" "Send message" + "Send voice message" "Share" "Share link" "Show"