[a11y] Ensure that the focus is not lost when the send button state change.
This commit is contained in:
parent
a84f387f4d
commit
24f08e8d20
4 changed files with 67 additions and 17 deletions
|
|
@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -39,7 +40,11 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.stringResource
|
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.hideFromAccessibility
|
||||||
|
import androidx.compose.ui.semantics.onClick
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
|
@ -280,6 +285,13 @@ fun TextComposer(
|
||||||
else -> sendButton
|
else -> sendButton
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val endButtonA11y = endButtonA11y(
|
||||||
|
composerMode = composerMode,
|
||||||
|
voiceMessageState = voiceMessageState,
|
||||||
|
enableVoiceMessages = enableVoiceMessages,
|
||||||
|
canSendMessage = canSendMessage,
|
||||||
|
)
|
||||||
|
|
||||||
val voiceRecording = @Composable {
|
val voiceRecording = @Composable {
|
||||||
when (voiceMessageState) {
|
when (voiceMessageState) {
|
||||||
is VoiceMessageState.Preview ->
|
is VoiceMessageState.Preview ->
|
||||||
|
|
@ -323,6 +335,7 @@ fun TextComposer(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
textFormatting = textFormattingOptions,
|
textFormatting = textFormattingOptions,
|
||||||
|
endButtonA11y = endButtonA11y,
|
||||||
sendButton = sendButton,
|
sendButton = sendButton,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -334,6 +347,7 @@ fun TextComposer(
|
||||||
composerOptionsButton = composerOptionsButton,
|
composerOptionsButton = composerOptionsButton,
|
||||||
textInput = textInput,
|
textInput = textInput,
|
||||||
endButton = sendOrRecordButton,
|
endButton = sendOrRecordButton,
|
||||||
|
endButtonA11y = endButtonA11y,
|
||||||
voiceRecording = voiceRecording,
|
voiceRecording = voiceRecording,
|
||||||
voiceDeleteButton = voiceDeleteButton,
|
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
|
@Composable
|
||||||
private fun StandardLayout(
|
private fun StandardLayout(
|
||||||
voiceMessageState: VoiceMessageState,
|
voiceMessageState: VoiceMessageState,
|
||||||
|
|
@ -369,6 +417,7 @@ private fun StandardLayout(
|
||||||
voiceRecording: @Composable () -> Unit,
|
voiceRecording: @Composable () -> Unit,
|
||||||
voiceDeleteButton: @Composable () -> Unit,
|
voiceDeleteButton: @Composable () -> Unit,
|
||||||
endButton: @Composable () -> Unit,
|
endButton: @Composable () -> Unit,
|
||||||
|
endButtonA11y: (SemanticsPropertyReceiver.() -> Unit),
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
|
|
@ -416,7 +465,8 @@ private fun StandardLayout(
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp)
|
.padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp)
|
||||||
.size(48.dp),
|
.size(48.dp)
|
||||||
|
.clearAndSetSemantics(endButtonA11y),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
endButton()
|
endButton()
|
||||||
|
|
@ -454,6 +504,7 @@ private fun TextFormattingLayout(
|
||||||
dismissTextFormattingButton: @Composable () -> Unit,
|
dismissTextFormattingButton: @Composable () -> Unit,
|
||||||
textFormatting: @Composable () -> Unit,
|
textFormatting: @Composable () -> Unit,
|
||||||
sendButton: @Composable () -> Unit,
|
sendButton: @Composable () -> Unit,
|
||||||
|
endButtonA11y: (SemanticsPropertyReceiver.() -> Unit),
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -485,10 +536,12 @@ private fun TextFormattingLayout(
|
||||||
textFormatting()
|
textFormatting()
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier
|
||||||
start = 14.dp,
|
.padding(
|
||||||
end = 6.dp
|
start = 14.dp,
|
||||||
)
|
end = 6.dp,
|
||||||
|
)
|
||||||
|
.clearAndSetSemantics(endButtonA11y)
|
||||||
) {
|
) {
|
||||||
sendButton()
|
sendButton()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.LinearGradientShader
|
import androidx.compose.ui.graphics.LinearGradientShader
|
||||||
import androidx.compose.ui.graphics.ShaderBrush
|
import androidx.compose.ui.graphics.ShaderBrush
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
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.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send button for the message composer.
|
* Send button for the message composer.
|
||||||
|
|
@ -60,10 +58,6 @@ internal fun SendButton(
|
||||||
composerMode.isEditing -> 0.dp
|
composerMode.isEditing -> 0.dp
|
||||||
else -> 2.dp
|
else -> 2.dp
|
||||||
}
|
}
|
||||||
val contentDescription = when {
|
|
||||||
composerMode.isEditing -> stringResource(CommonStrings.action_edit)
|
|
||||||
else -> stringResource(CommonStrings.action_send)
|
|
||||||
}
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
|
|
@ -81,7 +75,8 @@ internal fun SendButton(
|
||||||
.padding(start = iconStartPadding)
|
.padding(start = iconStartPadding)
|
||||||
.align(Alignment.Center),
|
.align(Alignment.Center),
|
||||||
imageVector = iconVector,
|
imageVector = iconVector,
|
||||||
contentDescription = contentDescription,
|
// Note: accessibility is managed in TextComposer.
|
||||||
|
contentDescription = null,
|
||||||
tint = if (canSendMessage) {
|
tint = if (canSendMessage) {
|
||||||
if (ElementTheme.colors.isLight) {
|
if (ElementTheme.colors.isLight) {
|
||||||
ElementTheme.colors.iconOnSolidPrimary
|
ElementTheme.colors.iconOnSolidPrimary
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
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.theme.components.IconButton
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
|
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun VoiceMessageRecorderButton(
|
internal fun VoiceMessageRecorderButton(
|
||||||
|
|
@ -70,7 +68,8 @@ private fun StartButton(
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
imageVector = CompoundIcons.MicOn(),
|
imageVector = CompoundIcons.MicOn(),
|
||||||
contentDescription = stringResource(CommonStrings.a11y_voice_message_record),
|
// Note: accessibility is managed in TextComposer.
|
||||||
|
contentDescription = null,
|
||||||
tint = ElementTheme.colors.iconSecondary,
|
tint = ElementTheme.colors.iconSecondary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +94,8 @@ private fun StopButton(
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
resourceId = CommonDrawables.ic_stop,
|
resourceId = CommonDrawables.ic_stop,
|
||||||
contentDescription = stringResource(CommonStrings.a11y_voice_message_stop_recording),
|
// Note: accessibility is managed in TextComposer.
|
||||||
|
contentDescription = null,
|
||||||
tint = ElementTheme.colors.iconOnSolidPrimary,
|
tint = ElementTheme.colors.iconOnSolidPrimary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<string name="a11y_jump_to_bottom">"Jump to bottom"</string>
|
<string name="a11y_jump_to_bottom">"Jump to bottom"</string>
|
||||||
<string name="a11y_notifications_mentions_only">"Mentions only"</string>
|
<string name="a11y_notifications_mentions_only">"Mentions only"</string>
|
||||||
<string name="a11y_notifications_muted">"Muted"</string>
|
<string name="a11y_notifications_muted">"Muted"</string>
|
||||||
<string name="a11y_other_user_avatar">"Other user avatar"</string>
|
<string name="a11y_other_user_avatar">"Other user\'s avatar"</string>
|
||||||
<string name="a11y_page_n">"Page %1$d"</string>
|
<string name="a11y_page_n">"Page %1$d"</string>
|
||||||
<string name="a11y_pause">"Pause"</string>
|
<string name="a11y_pause">"Pause"</string>
|
||||||
<string name="a11y_paused_voice_message">"Voice message, duration: %1$s, current position: %2$s"</string>
|
<string name="a11y_paused_voice_message">"Voice message, duration: %1$s, current position: %2$s"</string>
|
||||||
|
|
@ -125,7 +125,9 @@
|
||||||
<string name="action_save">"Save"</string>
|
<string name="action_save">"Save"</string>
|
||||||
<string name="action_search">"Search"</string>
|
<string name="action_search">"Search"</string>
|
||||||
<string name="action_send">"Send"</string>
|
<string name="action_send">"Send"</string>
|
||||||
|
<string name="action_send_edited_message">"Send edited message"</string>
|
||||||
<string name="action_send_message">"Send message"</string>
|
<string name="action_send_message">"Send message"</string>
|
||||||
|
<string name="action_send_voice_message">"Send voice message"</string>
|
||||||
<string name="action_share">"Share"</string>
|
<string name="action_share">"Share"</string>
|
||||||
<string name="action_share_link">"Share link"</string>
|
<string name="action_share_link">"Share link"</string>
|
||||||
<string name="action_show">"Show"</string>
|
<string name="action_show">"Show"</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue