diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index 02a6a637e3..c60a78dd22 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -58,6 +58,7 @@ import io.element.android.libraries.designsystem.components.button.MainActionBut import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch +import io.element.android.libraries.designsystem.modifiers.a11yClickLabel import io.element.android.libraries.designsystem.modifiers.niceClickable import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight @@ -400,6 +401,7 @@ private fun RoomHeaderSection( .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { + val actionView = stringResource(CommonStrings.action_view) Avatar( avatarData = AvatarData(roomId.value, roomName, avatarUrl, AvatarSize.RoomHeader), avatarType = AvatarType.Room( @@ -408,9 +410,11 @@ private fun RoomHeaderSection( }.toPersistentList(), isTombstoned = isTombstoned, ), + contentDescription = avatarUrl?.let { stringResource(CommonStrings.a11y_room_avatar) }, modifier = Modifier .clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) } .testTag(TestTags.roomDetailAvatar) + .a11yClickLabel(avatarUrl?.let { actionView }) ) TitleAndSubtitle( title = roomName, diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt index 2732e770f4..47bb9cbdc5 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt @@ -31,6 +31,7 @@ import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarType +import io.element.android.libraries.designsystem.modifiers.a11yClickLabel import io.element.android.libraries.designsystem.modifiers.niceClickable import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -60,13 +61,16 @@ fun UserProfileHeaderSection( .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { + val actionView = stringResource(CommonStrings.action_view) Avatar( avatarData = AvatarData(userId.value, userName, avatarUrl, AvatarSize.UserHeader), avatarType = AvatarType.User, + contentDescription = avatarUrl?.let { stringResource(CommonStrings.a11y_user_avatar) }, modifier = Modifier .clip(CircleShape) .clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) } .testTag(TestTags.memberDetailAvatar) + .a11yClickLabel(avatarUrl?.let { actionView }) ) Spacer(modifier = Modifier.height(24.dp)) if (userName != null) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt index d0134fca78..56071de747 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt @@ -23,13 +23,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.LayoutDirection +import io.element.android.libraries.designsystem.modifiers.a11yClickLabel import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.text.toPx import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag +import io.element.android.libraries.ui.strings.CommonStrings /** Ratio between the box size (120 on Figma) and the avatar size (75 on Figma). */ private const val SIZE_RATIO = 1.6f @@ -49,6 +52,7 @@ fun DmAvatars( val boxSizePx = boxSize.toPx() val otherAvatarRadius = otherUserAvatarData.size.dp.toPx() / 2 val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl + val actionView = stringResource(CommonStrings.action_view) Box( modifier = modifier.size(boxSize), ) { @@ -56,6 +60,7 @@ fun DmAvatars( Avatar( avatarData = userAvatarData, avatarType = AvatarType.User, + contentDescription = userAvatarData.url?.let { stringResource(CommonStrings.a11y_your_avatar) }, modifier = Modifier .align(Alignment.BottomStart) .graphicsLayer { @@ -82,11 +87,13 @@ fun DmAvatars( .clickable(enabled = userAvatarData.url != null) { userAvatarData.url?.let { openAvatarPreview(it) } } + .a11yClickLabel(userAvatarData.url?.let { actionView }) ) // Draw other user avatar Avatar( avatarData = otherUserAvatarData, avatarType = AvatarType.User, + contentDescription = otherUserAvatarData.url?.let { stringResource(CommonStrings.a11y_other_user_avatar) }, modifier = Modifier .align(Alignment.TopEnd) .clip(CircleShape) @@ -94,6 +101,7 @@ fun DmAvatars( otherUserAvatarData.url?.let { openOtherAvatarPreview(it) } } .testTag(TestTags.memberDetailAvatar) + .a11yClickLabel(otherUserAvatarData.url?.let { actionView }) ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt index 5789a4525d..753ad952a0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt @@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp fun Modifier.clickableIfNotNull(onClick: (() -> Unit)? = null): Modifier = this.then( @@ -29,3 +31,18 @@ fun Modifier.niceClickable( .clickable { onClick() } .padding(horizontal = 4.dp) } + +fun Modifier.a11yClickLabel( + label: String?, +): Modifier = then( + if (label != null) { + Modifier.semantics { + onClick( + label = label, + action = null, + ) + } + } else { + Modifier + } +) diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index bf97f4c46b..41e87f83c5 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -12,6 +12,7 @@ "Jump to bottom" "Mentions only" "Muted" + "Other user avatar" "Page %1$d" "Pause" "Voice message, duration: %1$s, current position: %2$s" @@ -30,15 +31,18 @@ "Tap to show all" "Remove reaction: %1$s" "Remove reaction with %1$s" + "Room avatar" "Send files" "Show password" "Start a call" + "User avatar" "User menu" "View avatar" "View details" "Voice message, duration: %1$s" "Record voice message." "Stop recording" + "Your avatar" "Accept" "Add caption" "Add to timeline" @@ -137,6 +141,7 @@ "Tap for options" "Try again" "Unpin" + "View" "View in timeline" "View source" "Yes"