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 30cb8c5006..c537c89b6d 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
@@ -20,6 +20,7 @@ import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
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.unit.Dp
import androidx.compose.ui.unit.dp
@@ -31,6 +32,7 @@ import io.element.android.features.messages.impl.timeline.components.layout.Cont
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
@@ -137,6 +139,8 @@ internal fun TimelineItemRow(
} else {
timelineItem.safeSenderName
}
+ // For Polls, allow the answers to be traversed by Talkback
+ isTraversalGroup = timelineItem.content is TimelineItemPollContent
}
// 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/components/event/TimelineItemPollViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt
index b89a3830be..3935d6d615 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt
@@ -47,7 +47,10 @@ class TimelineItemPollViewTest {
)
}
val answer = content.answerItems[answerIndex].answer
- rule.onNode(hasText(answer.text)).performClick()
+ rule.onNode(
+ matcher = hasText(answer.text),
+ useUnmergedTree = true,
+ ).performClick()
eventsRecorder.assertSingle(TimelineEvents.SelectPollAnswer(content.eventId!!, answer.id))
}
diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt
index 6243bd38d9..59d13fed1e 100644
--- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt
+++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt
@@ -20,9 +20,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.res.pluralStringResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
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.features.poll.api.R
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
@@ -32,14 +36,42 @@ import io.element.android.libraries.designsystem.theme.progressIndicatorTrackCol
import io.element.android.libraries.designsystem.toEnabledColor
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.ui.strings.CommonPlurals
+import io.element.android.libraries.ui.strings.CommonStrings
@Composable
internal fun PollAnswerView(
answerItem: PollAnswerItem,
modifier: Modifier = Modifier,
) {
+ val nbVotesText = pluralStringResource(
+ id = CommonPlurals.common_poll_votes_count,
+ count = answerItem.votesCount,
+ answerItem.votesCount,
+ )
+ val a11yText = buildString {
+ val sentenceDelimiter = stringResource(CommonStrings.common_sentence_delimiter)
+ append(answerItem.answer.text.removeSuffix("."))
+ if (answerItem.showVotes) {
+ append(sentenceDelimiter)
+ append(nbVotesText)
+ if (answerItem.votesCount != 0) {
+ append(sentenceDelimiter)
+ (answerItem.percentage * 100).toInt().let { percent ->
+ append(pluralStringResource(R.plurals.a11y_polls_percent_of_total, percent, percent))
+ }
+ }
+ if (answerItem.isWinner) {
+ append(sentenceDelimiter)
+ append(stringResource(R.string.a11y_polls_winning_answer))
+ }
+ }
+ }
Row(
- modifier = modifier.fillMaxWidth(),
+ modifier = modifier
+ .fillMaxWidth()
+ .clearAndSetSemantics {
+ contentDescription = a11yText
+ },
) {
Icon(
imageVector = if (answerItem.isSelected) {
@@ -70,11 +102,6 @@ internal fun PollAnswerView(
style = if (answerItem.isWinner) ElementTheme.typography.fontBodyLgMedium else ElementTheme.typography.fontBodyLgRegular,
)
if (answerItem.showVotes) {
- val text = pluralStringResource(
- id = CommonPlurals.common_poll_votes_count,
- count = answerItem.votesCount,
- answerItem.votesCount
- )
Row(
modifier = Modifier.align(Alignment.Bottom),
verticalAlignment = Alignment.CenterVertically,
@@ -87,13 +114,13 @@ internal fun PollAnswerView(
)
Spacer(modifier = Modifier.width(2.dp))
Text(
- text = text,
+ text = nbVotesText,
style = ElementTheme.typography.fontBodySmMedium,
color = ElementTheme.colors.textPrimary,
)
} else {
Text(
- text = text,
+ text = nbVotesText,
style = ElementTheme.typography.fontBodySmRegular,
color = ElementTheme.colors.textSecondary,
)
diff --git a/features/poll/api/src/main/res/values/localazy.xml b/features/poll/api/src/main/res/values/localazy.xml
new file mode 100644
index 0000000000..2d1142194c
--- /dev/null
+++ b/features/poll/api/src/main/res/values/localazy.xml
@@ -0,0 +1,8 @@
+
+
+
+ - "%1$d percent of total votes"
+ - "%1$d percents of total votes"
+
+ "This is the winning answer"
+
diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt
index f8384279bf..038fb397f3 100644
--- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt
+++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt
@@ -143,7 +143,7 @@ fun CreatePollView(
trailingContent = ListItemContent.Custom {
Icon(
imageVector = CompoundIcons.Delete(),
- contentDescription = null,
+ contentDescription = stringResource(R.string.screen_create_poll_delete_option_a11y, answer.text),
modifier = Modifier.clickable(answer.canDelete) {
state.eventSink(CreatePollEvents.RemoveAnswer(index))
},
diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt
index d9d639e8a8..1101276577 100644
--- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt
+++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt
@@ -29,6 +29,8 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.isTraversalGroup
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@@ -178,7 +180,9 @@ private fun PollHistoryList(
if (pollHistoryItems.isEmpty()) {
item {
Column(
- modifier = Modifier.fillParentMaxSize().padding(bottom = 24.dp),
+ modifier = Modifier
+ .fillParentMaxSize()
+ .padding(bottom = 24.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
@@ -191,7 +195,9 @@ private fun PollHistoryList(
text = emptyStringResource,
style = ElementTheme.typography.fontBodyLgRegular,
color = ElementTheme.colors.textSecondary,
- modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp, horizontal = 16.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 24.dp, horizontal = 16.dp),
textAlign = TextAlign.Center,
)
@@ -227,7 +233,10 @@ private fun PollHistoryItemRow(
modifier: Modifier = Modifier,
) {
Surface(
- modifier = modifier,
+ modifier = modifier.semantics(mergeDescendants = true) {
+ // Allow the answers to be traversed by Talkback
+ isTraversalGroup = true
+ },
border = BorderStroke(1.dp, ElementTheme.colors.borderInteractiveSecondary),
shape = RoundedCornerShape(size = 12.dp)
) {
diff --git a/features/poll/impl/src/main/res/values/localazy.xml b/features/poll/impl/src/main/res/values/localazy.xml
index 7a7a15ea3c..6019d908b4 100644
--- a/features/poll/impl/src/main/res/values/localazy.xml
+++ b/features/poll/impl/src/main/res/values/localazy.xml
@@ -5,6 +5,7 @@
"Hide votes"
"Option %1$d"
"Your changes have not been saved. Are you sure you want to go back?"
+ "Delete option %1$s"
"Question or topic"
"What is the poll about?"
"Create Poll"
diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt
index 3da8d50d49..54df066bbe 100644
--- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt
+++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt
@@ -131,7 +131,10 @@ class PollHistoryViewTest {
rule.setPollHistoryViewView(
state = state,
)
- rule.onNodeWithText(answer.text).performClick()
+ rule.onNodeWithText(
+ text = answer.text,
+ useUnmergedTree = true,
+ ).performClick()
eventsRecorder.assertSingle(
PollHistoryEvents.SelectPollAnswer(eventId, answer.id)
)
diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml
index d2cbee9a19..21004580f1 100644
--- a/libraries/ui-strings/src/main/res/values/localazy.xml
+++ b/libraries/ui-strings/src/main/res/values/localazy.xml
@@ -259,6 +259,7 @@ Reason: %1$s."
"Sending…"
"Sending failed"
"Sent"
+ ". "
"Server not supported"
"Server URL"
"Settings"
diff --git a/tools/localazy/config.json b/tools/localazy/config.json
index b299c39ef7..5c70f3fe50 100644
--- a/tools/localazy/config.json
+++ b/tools/localazy/config.json
@@ -242,6 +242,12 @@
"screen_polls_history_.*"
]
},
+ {
+ "name" : ":features:poll:api",
+ "includeRegex" : [
+ "a11y\\.polls\\..*"
+ ]
+ },
{
"name" : ":features:securebackup:impl",
"includeRegex" : [