Add "View avatar" content description to all clickable Avatar that will open the avatar preview. (#4948)

* Add "View avatar" content description to all clickable Avatar that will open the avatar preview.

* Improve accessibility of avatar images.
This commit is contained in:
Benoit Marty
2025-06-30 17:06:10 +02:00
committed by GitHub
parent 971a1d9620
commit c843faff3a
5 changed files with 38 additions and 0 deletions

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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 })
)
}
}

View File

@@ -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
}
)

View File

@@ -12,6 +12,7 @@
<string name="a11y_jump_to_bottom">"Jump to bottom"</string>
<string name="a11y_notifications_mentions_only">"Mentions only"</string>
<string name="a11y_notifications_muted">"Muted"</string>
<string name="a11y_other_user_avatar">"Other user avatar"</string>
<string name="a11y_page_n">"Page %1$d"</string>
<string name="a11y_pause">"Pause"</string>
<string name="a11y_paused_voice_message">"Voice message, duration: %1$s, current position: %2$s"</string>
@@ -30,15 +31,18 @@
<string name="a11y_read_receipts_tap_to_show_all">"Tap to show all"</string>
<string name="a11y_remove_reaction">"Remove reaction: %1$s"</string>
<string name="a11y_remove_reaction_with">"Remove reaction with %1$s"</string>
<string name="a11y_room_avatar">"Room avatar"</string>
<string name="a11y_send_files">"Send files"</string>
<string name="a11y_show_password">"Show password"</string>
<string name="a11y_start_call">"Start a call"</string>
<string name="a11y_user_avatar">"User avatar"</string>
<string name="a11y_user_menu">"User menu"</string>
<string name="a11y_view_avatar">"View avatar"</string>
<string name="a11y_view_details">"View details"</string>
<string name="a11y_voice_message">"Voice message, duration: %1$s"</string>
<string name="a11y_voice_message_record">"Record voice message."</string>
<string name="a11y_voice_message_stop_recording">"Stop recording"</string>
<string name="a11y_your_avatar">"Your avatar"</string>
<string name="action_accept">"Accept"</string>
<string name="action_add_caption">"Add caption"</string>
<string name="action_add_to_timeline">"Add to timeline"</string>
@@ -137,6 +141,7 @@
<string name="action_tap_for_options">"Tap for options"</string>
<string name="action_try_again">"Try again"</string>
<string name="action_unpin">"Unpin"</string>
<string name="action_view">"View"</string>
<string name="action_view_in_timeline">"View in timeline"</string>
<string name="action_view_source">"View source"</string>
<string name="action_yes">"Yes"</string>