diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenu.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenu.kt index 27883e88d5..2a6cfee502 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenu.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenu.kt @@ -176,7 +176,6 @@ private fun RoomListModalBottomSheetContent( leadingContent = ListItemContent.Icon( iconSource = IconSource.Vector( CompoundIcons.ChatProblem(), - contentDescription = stringResource(CommonStrings.action_report_room), ) ), style = ListItemStyle.Destructive, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt index 644cbf2310..d8af3c1736 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt @@ -18,8 +18,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.hideFromAccessibility -import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme @@ -44,15 +42,15 @@ fun TimelineEventTimestampView( modifier: Modifier = Modifier, ) { val formattedTime = event.sentTime - val hasError = event.localSendState is LocalEventSendState.Failed + val hasError = event.failedToSend val hasEncryptionCritical = event.messageShield?.isCritical.orFalse() val isMessageEdited = event.content.isEdited() val isMessageRedacted = event.content.isRedacted() val tint = if (hasError || hasEncryptionCritical && !isMessageRedacted) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textSecondary Row( modifier = Modifier - .padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing)) - .then(modifier), + .padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing)) + .then(modifier), verticalAlignment = Alignment.CenterVertically, ) { if (isMessageEdited) { @@ -76,11 +74,13 @@ fun TimelineEventTimestampView( contentDescription = stringResource(id = CommonStrings.common_sending_failed), tint = tint, modifier = Modifier - .size(15.dp, 18.dp) - .clickable(isVerifiedUserSendFailure) { - eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event)) - } - .semantics { hideFromAccessibility() } + .size(15.dp, 18.dp) + .clickable( + enabled = isVerifiedUserSendFailure, + onClickLabel = stringResource(CommonStrings.action_open_context_menu), + ) { + eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event)) + } ) } @@ -89,13 +89,14 @@ fun TimelineEventTimestampView( Spacer(modifier = Modifier.width(2.dp)) Icon( imageVector = shield.toIcon(), - contentDescription = shield.toText(), + contentDescription = stringResource(id = CommonStrings.a11y_encryption_details), modifier = Modifier .size(15.dp) - .clickable { + .clickable( + onClickLabel = stringResource(CommonStrings.a11y_view_details), + ) { eventSink(TimelineEvents.ShowShieldDialog(shield)) - } - .semantics { hideFromAccessibility() }, + }, tint = shield.toIconColor(), ) Spacer(modifier = Modifier.width(4.dp)) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 9628fb2bb4..59ec63d842 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -140,7 +140,10 @@ internal fun TimelineItemRow( timelineItem.safeSenderName } // For Polls, allow the answers to be traversed by Talkback - isTraversalGroup = timelineItem.content is TimelineItemPollContent + isTraversalGroup = timelineItem.content is TimelineItemPollContent || + timelineItem.failedToSend || + timelineItem.messageShield != null + // TODO Also set to true when the event has link(s) } // Custom clickable that applies over the whole item for accessibility .then( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index c4a2351990..73969a2860 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -116,7 +116,7 @@ class TimelineViewTest { eventSink = eventsRecorder, ), ) - val contentDescription = rule.activity.getString(CommonStrings.event_shield_reason_unverified_identity) + val contentDescription = rule.activity.getString(CommonStrings.a11y_encryption_details) rule.onNodeWithContentDescription(contentDescription).performClick() eventsRecorder.assertList( listOf( diff --git a/features/poll/api/src/main/res/values/localazy.xml b/features/poll/api/src/main/res/values/localazy.xml index 2d1142194c..ebba470b6a 100644 --- a/features/poll/api/src/main/res/values/localazy.xml +++ b/features/poll/api/src/main/res/values/localazy.xml @@ -4,5 +4,6 @@ "%1$d percent of total votes" "%1$d percents of total votes" + "Will remove previous selection" "This is the winning answer" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt index 4bb0253e6a..6f2179d4d4 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt @@ -225,7 +225,10 @@ class ChangeRolesViewTest { ) // Unselect the user from the row list val contentDescription = rule.activity.getString(CommonStrings.action_remove) - rule.onNodeWithContentDescription(contentDescription).performClick() + rule.onNodeWithContentDescription( + label = contentDescription, + useUnmergedTree = true, + ).performClick() eventsRecorder.assertList( listOf( ChangeRolesEvent.QueryChanged(""), @@ -248,7 +251,7 @@ class ChangeRolesViewTest { rule.setChangeRolesContent( state = state, ) - // Select the user from the row list + // Select the user from the user list rule.onNodeWithText("Carol").performClick() eventsRecorder.assertList( listOf( @@ -271,8 +274,11 @@ class ChangeRolesViewTest { rule.setChangeRolesContent( state = state, ) - // Select the user from the rom list - rule.onAllNodesWithText("Bob")[1].performClick() + // Unselect the user from the user list + rule.onAllNodesWithText( + text = "Bob", + useUnmergedTree = true, + )[1].performClick() eventsRecorder.assertList( listOf( ChangeRolesEvent.QueryChanged(""), diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/accessibility/ContextExt.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/accessibility/ContextExt.kt deleted file mode 100644 index 02250297b3..0000000000 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/accessibility/ContextExt.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2023, 2024 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.androidutils.accessibility - -import android.content.Context -import android.view.accessibility.AccessibilityManager -import androidx.core.content.getSystemService - -/** - * Whether a screen reader is enabled. - * - * Avoid changing UI or app behavior based on the state of accessibility. - * See [AccessibilityManager.isTouchExplorationEnabled] for more details. - * - * @return true if the screen reader is enabled. - */ -fun Context.isScreenReaderEnabled(): Boolean { - val accessibilityManager = getSystemService() - ?: return false - - return accessibilityManager.let { - it.isEnabled && it.isTouchExplorationEnabled - } -} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt index 3b8b6831ca..ad06985ab7 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt @@ -62,6 +62,7 @@ fun EditableAvatarView( modifier = Modifier .clickable( interactionSource = remember { MutableInteractionSource() }, + onClickLabel = stringResource(CommonStrings.a11y_edit_avatar), onClick = onAvatarClick, indication = ripple(bounded = false), ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUser.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUser.kt index 31cc724838..f3c3c634e6 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUser.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUser.kt @@ -23,6 +23,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.onClick import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -48,9 +51,24 @@ fun SelectedUser( onUserRemove: (MatrixUser) -> Unit, modifier: Modifier = Modifier, ) { + val actionRemove = stringResource(id = CommonStrings.action_remove) Box( modifier = modifier .width(AvatarSize.SelectedUser.dp) + .clearAndSetSemantics { + contentDescription = matrixUser.getBestName() + if (canRemove) { + // Note: this does not set the click effect to the whole Box + // when talkback is not enabled + onClick( + label = actionRemove, + action = { + onUserRemove(matrixUser) + true + } + ) + } + } ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -83,6 +101,7 @@ fun SelectedUser( ) { Icon( imageVector = CompoundIcons.Close(), + // Note: keep the context description for the test contentDescription = stringResource(id = CommonStrings.action_remove), tint = ElementTheme.colors.iconOnSolidPrimary, modifier = Modifier.padding(2.dp) diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 270e862300..362b2310d4 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -7,11 +7,16 @@ "%1$d digit entered" "%1$d digits entered" + "Edit avatar" + "Encryption details" "Hide password" "Join call" "Jump to bottom" "Mentions only" "Muted" + "New mentions" + "New messages" + "Ongoing call" "Other user\'s avatar" "Page %1$d" "Pause" @@ -35,6 +40,7 @@ "Send files" "Show password" "Start a call" + "Time limited action required" "User avatar" "User menu" "View avatar"