Improve accessibility of the timeline (#4579)
* Make whole messages selectable and readable as a single unit when possible. * Make most UI components not clickable when talkback is enabled. * Make voice messages work with talkback too. * Read grouped state events even if the events are collapsed. * Move image and video item actions to the timeline item. * Improve accessibility in the message context menu too * Fix a11y issue on add attachment button. * Add `contentDescription` to file icon so it's read aloud --------- Co-authored-by: Benoit Marty <benoit@matrix.org>
This commit is contained in:
parent
f9c00ac99d
commit
740c28eda9
18 changed files with 226 additions and 70 deletions
|
|
@ -19,13 +19,11 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
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
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
/**
|
||||
* Button with colored background.
|
||||
|
|
@ -35,6 +33,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
fun IconColorButton(
|
||||
onClick: () -> Unit,
|
||||
imageVector: ImageVector,
|
||||
contentDescription: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
buttonSize: ButtonSize = ButtonSize.Large,
|
||||
iconColorButtonStyle: IconColorButtonStyle = IconColorButtonStyle.Primary,
|
||||
|
|
@ -55,7 +54,7 @@ fun IconColorButton(
|
|||
.background(bgColor)
|
||||
.padding(buttonSize.toContainerPadding()),
|
||||
imageVector = imageVector,
|
||||
contentDescription = stringResource(CommonStrings.action_close),
|
||||
contentDescription = contentDescription,
|
||||
tint = ElementTheme.colors.iconOnSolidPrimary
|
||||
)
|
||||
}
|
||||
|
|
@ -101,6 +100,7 @@ internal fun IconColorButtonPreview() = ElementPreview {
|
|||
IconColorButton(
|
||||
onClick = {},
|
||||
imageVector = CompoundIcons.Close(),
|
||||
contentDescription = null,
|
||||
buttonSize = size,
|
||||
iconColorButtonStyle = style,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.isTraversalGroup
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.traversalIndex
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
|
@ -58,7 +60,7 @@ fun InReplyToView(
|
|||
senderId = inReplyTo.senderId,
|
||||
senderProfile = inReplyTo.senderProfile,
|
||||
metadata = inReplyTo.metadata(hideImage),
|
||||
modifier = modifier
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
is InReplyToDetails.Error ->
|
||||
|
|
@ -96,13 +98,18 @@ private fun ReplyToReadyContent(
|
|||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
val a11InReplyToText = stringResource(CommonStrings.common_in_reply_to, senderProfile.getDisambiguatedDisplayName(senderId))
|
||||
Column(verticalArrangement = Arrangement.SpaceBetween) {
|
||||
Column(
|
||||
modifier = Modifier.semantics(mergeDescendants = false) { isTraversalGroup = true },
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
SenderName(
|
||||
senderId = senderId,
|
||||
senderProfile = senderProfile,
|
||||
senderNameMode = SenderNameMode.Reply,
|
||||
modifier = Modifier.semantics {
|
||||
contentDescription = a11InReplyToText
|
||||
isTraversalGroup = true
|
||||
traversalIndex = 1f
|
||||
},
|
||||
)
|
||||
ReplyToContentText(metadata)
|
||||
|
|
@ -169,6 +176,10 @@ private fun ReplyToContentText(metadata: InReplyToMetadata?) {
|
|||
else -> FontStyle.Normal
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.semantics(mergeDescendants = false) {
|
||||
isTraversalGroup = true
|
||||
traversalIndex = -1f
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (iconResourceId != null) {
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ fun TextComposer(
|
|||
IconColorButton(
|
||||
onClick = onAddAttachment,
|
||||
imageVector = CompoundIcons.Plus(),
|
||||
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -292,6 +293,7 @@ fun TextComposer(
|
|||
IconColorButton(
|
||||
onClick = onDismissTextFormatting,
|
||||
imageVector = CompoundIcons.Close(),
|
||||
contentDescription = stringResource(CommonStrings.action_close),
|
||||
)
|
||||
},
|
||||
textFormatting = textFormattingOptions,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.ui.utils.time
|
||||
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
@Composable
|
||||
fun isTalkbackActive(): Boolean {
|
||||
val context = LocalContext.current
|
||||
val accessibilityManager = remember { context.getSystemService(AccessibilityManager::class.java) }
|
||||
return accessibilityManager.isTouchExplorationEnabled
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue