Skip to content

Commit e608ac0

Browse files
committed
[a11y] Ensure that the focus is not lost when the send button state change.
1 parent 3530ba5 commit e608ac0

File tree

4 files changed

+67
-17
lines changed

4 files changed

+67
-17
lines changed

libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.size
2828
import androidx.compose.foundation.layout.width
2929
import androidx.compose.runtime.Composable
3030
import androidx.compose.runtime.LaunchedEffect
31+
import androidx.compose.runtime.ReadOnlyComposable
3132
import androidx.compose.runtime.getValue
3233
import androidx.compose.runtime.mutableStateOf
3334
import androidx.compose.runtime.remember
@@ -39,7 +40,11 @@ import androidx.compose.ui.Modifier
3940
import androidx.compose.ui.draw.clip
4041
import androidx.compose.ui.platform.LocalView
4142
import androidx.compose.ui.res.stringResource
43+
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
44+
import androidx.compose.ui.semantics.clearAndSetSemantics
45+
import androidx.compose.ui.semantics.contentDescription
4246
import androidx.compose.ui.semantics.hideFromAccessibility
47+
import androidx.compose.ui.semantics.onClick
4348
import androidx.compose.ui.semantics.semantics
4449
import androidx.compose.ui.tooling.preview.Preview
4550
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -280,6 +285,13 @@ fun TextComposer(
280285
else -> sendButton
281286
}
282287

288+
val endButtonA11y = endButtonA11y(
289+
composerMode = composerMode,
290+
voiceMessageState = voiceMessageState,
291+
enableVoiceMessages = enableVoiceMessages,
292+
canSendMessage = canSendMessage,
293+
)
294+
283295
val voiceRecording = @Composable {
284296
when (voiceMessageState) {
285297
is VoiceMessageState.Preview ->
@@ -323,6 +335,7 @@ fun TextComposer(
323335
)
324336
},
325337
textFormatting = textFormattingOptions,
338+
endButtonA11y = endButtonA11y,
326339
sendButton = sendButton,
327340
)
328341
} else {
@@ -334,6 +347,7 @@ fun TextComposer(
334347
composerOptionsButton = composerOptionsButton,
335348
textInput = textInput,
336349
endButton = sendOrRecordButton,
350+
endButtonA11y = endButtonA11y,
337351
voiceRecording = voiceRecording,
338352
voiceDeleteButton = voiceDeleteButton,
339353
)
@@ -359,6 +373,40 @@ fun TextComposer(
359373
}
360374
}
361375

376+
@ReadOnlyComposable
377+
@Composable
378+
private fun endButtonA11y(
379+
composerMode: MessageComposerMode,
380+
voiceMessageState: VoiceMessageState,
381+
enableVoiceMessages: Boolean,
382+
canSendMessage: Boolean,
383+
): (SemanticsPropertyReceiver) -> Unit {
384+
val a11ySendButtonDescription = stringResource(
385+
id = when {
386+
enableVoiceMessages && !canSendMessage ->
387+
when (voiceMessageState) {
388+
VoiceMessageState.Idle,
389+
is VoiceMessageState.Recording -> if (voiceMessageState is VoiceMessageState.Recording) {
390+
CommonStrings.a11y_voice_message_stop_recording
391+
} else {
392+
CommonStrings.a11y_voice_message_record
393+
}
394+
is VoiceMessageState.Preview -> when (voiceMessageState.isSending) {
395+
true -> CommonStrings.common_sending
396+
false -> CommonStrings.action_send_voice_message
397+
}
398+
}
399+
composerMode.isEditing -> CommonStrings.action_send_edited_message
400+
else -> CommonStrings.action_send_message
401+
}
402+
)
403+
val endButtonA11y: (SemanticsPropertyReceiver.() -> Unit) = {
404+
contentDescription = a11ySendButtonDescription
405+
onClick(null, null)
406+
}
407+
return endButtonA11y
408+
}
409+
362410
@Composable
363411
private fun StandardLayout(
364412
voiceMessageState: VoiceMessageState,
@@ -369,6 +417,7 @@ private fun StandardLayout(
369417
voiceRecording: @Composable () -> Unit,
370418
voiceDeleteButton: @Composable () -> Unit,
371419
endButton: @Composable () -> Unit,
420+
endButtonA11y: (SemanticsPropertyReceiver.() -> Unit),
372421
modifier: Modifier = Modifier,
373422
) {
374423
Column(modifier = modifier) {
@@ -416,7 +465,8 @@ private fun StandardLayout(
416465
Box(
417466
Modifier
418467
.padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp)
419-
.size(48.dp),
468+
.size(48.dp)
469+
.clearAndSetSemantics(endButtonA11y),
420470
contentAlignment = Alignment.Center,
421471
) {
422472
endButton()
@@ -454,6 +504,7 @@ private fun TextFormattingLayout(
454504
dismissTextFormattingButton: @Composable () -> Unit,
455505
textFormatting: @Composable () -> Unit,
456506
sendButton: @Composable () -> Unit,
507+
endButtonA11y: (SemanticsPropertyReceiver.() -> Unit),
457508
modifier: Modifier = Modifier
458509
) {
459510
Column(
@@ -485,10 +536,12 @@ private fun TextFormattingLayout(
485536
textFormatting()
486537
}
487538
Box(
488-
modifier = Modifier.padding(
489-
start = 14.dp,
490-
end = 6.dp
491-
)
539+
modifier = Modifier
540+
.padding(
541+
start = 14.dp,
542+
end = 6.dp,
543+
)
544+
.clearAndSetSemantics(endButtonA11y)
492545
) {
493546
sendButton()
494547
}

libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import androidx.compose.ui.geometry.Offset
2121
import androidx.compose.ui.graphics.Color
2222
import androidx.compose.ui.graphics.LinearGradientShader
2323
import androidx.compose.ui.graphics.ShaderBrush
24-
import androidx.compose.ui.res.stringResource
2524
import androidx.compose.ui.unit.dp
2625
import io.element.android.compound.theme.ElementTheme
2726
import io.element.android.compound.tokens.generated.CompoundIcons
@@ -32,7 +31,6 @@ import io.element.android.libraries.designsystem.theme.components.IconButton
3231
import io.element.android.libraries.matrix.api.core.EventId
3332
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
3433
import io.element.android.libraries.textcomposer.model.MessageComposerMode
35-
import io.element.android.libraries.ui.strings.CommonStrings
3634

3735
/**
3836
* Send button for the message composer.
@@ -60,10 +58,6 @@ internal fun SendButton(
6058
composerMode.isEditing -> 0.dp
6159
else -> 2.dp
6260
}
63-
val contentDescription = when {
64-
composerMode.isEditing -> stringResource(CommonStrings.action_edit)
65-
else -> stringResource(CommonStrings.action_send)
66-
}
6761
Box(
6862
modifier = Modifier
6963
.clip(CircleShape)
@@ -81,7 +75,8 @@ internal fun SendButton(
8175
.padding(start = iconStartPadding)
8276
.align(Alignment.Center),
8377
imageVector = iconVector,
84-
contentDescription = contentDescription,
78+
// Note: accessibility is managed in TextComposer.
79+
contentDescription = null,
8580
tint = if (canSendMessage) {
8681
if (ElementTheme.colors.isLight) {
8782
ElementTheme.colors.iconOnSolidPrimary

libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import androidx.compose.runtime.Composable
1616
import androidx.compose.ui.Modifier
1717
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
1818
import androidx.compose.ui.platform.LocalHapticFeedback
19-
import androidx.compose.ui.res.stringResource
2019
import androidx.compose.ui.unit.dp
2120
import io.element.android.compound.theme.ElementTheme
2221
import io.element.android.compound.tokens.generated.CompoundIcons
@@ -26,7 +25,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon
2625
import io.element.android.libraries.designsystem.theme.components.IconButton
2726
import io.element.android.libraries.designsystem.utils.CommonDrawables
2827
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
29-
import io.element.android.libraries.ui.strings.CommonStrings
3028

3129
@Composable
3230
internal fun VoiceMessageRecorderButton(
@@ -70,7 +68,8 @@ private fun StartButton(
7068
Icon(
7169
modifier = Modifier.size(24.dp),
7270
imageVector = CompoundIcons.MicOn(),
73-
contentDescription = stringResource(CommonStrings.a11y_voice_message_record),
71+
// Note: accessibility is managed in TextComposer.
72+
contentDescription = null,
7473
tint = ElementTheme.colors.iconSecondary,
7574
)
7675
}
@@ -95,7 +94,8 @@ private fun StopButton(
9594
Icon(
9695
modifier = Modifier.size(24.dp),
9796
resourceId = CommonDrawables.ic_stop,
98-
contentDescription = stringResource(CommonStrings.a11y_voice_message_stop_recording),
97+
// Note: accessibility is managed in TextComposer.
98+
contentDescription = null,
9999
tint = ElementTheme.colors.iconOnSolidPrimary,
100100
)
101101
}

libraries/ui-strings/src/main/res/values/localazy.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<string name="a11y_jump_to_bottom">"Jump to bottom"</string>
1313
<string name="a11y_notifications_mentions_only">"Mentions only"</string>
1414
<string name="a11y_notifications_muted">"Muted"</string>
15-
<string name="a11y_other_user_avatar">"Other user avatar"</string>
15+
<string name="a11y_other_user_avatar">"Other user\'s avatar"</string>
1616
<string name="a11y_page_n">"Page %1$d"</string>
1717
<string name="a11y_pause">"Pause"</string>
1818
<string name="a11y_paused_voice_message">"Voice message, duration: %1$s, current position: %2$s"</string>
@@ -125,7 +125,9 @@
125125
<string name="action_save">"Save"</string>
126126
<string name="action_search">"Search"</string>
127127
<string name="action_send">"Send"</string>
128+
<string name="action_send_edited_message">"Send edited message"</string>
128129
<string name="action_send_message">"Send message"</string>
130+
<string name="action_send_voice_message">"Send voice message"</string>
129131
<string name="action_share">"Share"</string>
130132
<string name="action_share_link">"Share link"</string>
131133
<string name="action_show">"Show"</string>

0 commit comments

Comments
 (0)