Skip to content

Commit

Permalink
Make PollContentView a11y friendly #1345
Browse files Browse the repository at this point in the history
Improves a bit how screen readers read polls in the timeline.
- Adds a few `contentDescription` so that talkback reads “poll” or “ended poll” before the poll question.
- Changes the compose structure of the answers so that they are properly scanned by the screen reader. This meant getting rid of the `IconToggleButton` which was made redundant by the use of the `selectable`.
  • Loading branch information
Marco Romano authored Sep 15, 2023
2 parents 0bdb5d0 + 626ee7f commit d6dac9a
Show file tree
Hide file tree
Showing 41 changed files with 144 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,21 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.RadioButtonUnchecked
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.runtime.Composable
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.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconToggleButton
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor
Expand All @@ -47,41 +44,33 @@ import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonPlurals

@Composable
fun PollAnswerView(
internal fun PollAnswerView(
answerItem: PollAnswerItem,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier
.fillMaxWidth()
.selectable(
selected = answerItem.isSelected,
enabled = answerItem.isEnabled,
onClick = onClick,
role = Role.RadioButton,
)
modifier = modifier.fillMaxWidth(),
) {
IconToggleButton(
modifier = Modifier.size(22.dp),
checked = answerItem.isSelected,
enabled = answerItem.isEnabled,
colors = IconButtonDefaults.iconToggleButtonColors(
contentColor = ElementTheme.colors.iconSecondary,
checkedContentColor = ElementTheme.colors.iconPrimary,
disabledContentColor = ElementTheme.colors.iconDisabled,
),
onCheckedChange = { onClick() },
) {
Icon(
imageVector = if (answerItem.isSelected) {
Icons.Default.CheckCircle
Icon(
imageVector = if (answerItem.isSelected) {
Icons.Default.CheckCircle
} else {
Icons.Default.RadioButtonUnchecked
},
contentDescription = null,
modifier = Modifier
.padding(0.5.dp)
.size(22.dp),
tint = if (answerItem.isEnabled) {
if (answerItem.isSelected) {
ElementTheme.colors.iconPrimary
} else {
Icons.Default.RadioButtonUnchecked
},
contentDescription = null,
)
}
ElementTheme.colors.iconSecondary
}
} else {
ElementTheme.colors.iconDisabled
},
)
Spacer(modifier = Modifier.width(12.dp))
Column {
Row {
Expand Down Expand Up @@ -119,65 +108,58 @@ fun PollAnswerView(
}
}

@Preview
@DayNightPreviews
@Composable
internal fun PollAnswerDisclosedNotSelectedPreview() = ElementThemedPreview {
internal fun PollAnswerDisclosedNotSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = false),
onClick = { },
)
}

@Preview
@DayNightPreviews
@Composable
internal fun PollAnswerDisclosedSelectedPreview() = ElementThemedPreview {
internal fun PollAnswerDisclosedSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true),
onClick = { }
)
}

@Preview
@DayNightPreviews
@Composable
internal fun PollAnswerUndisclosedNotSelectedPreview() = ElementThemedPreview {
internal fun PollAnswerUndisclosedNotSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isDisclosed = false, isSelected = false),
onClick = { },
)
}

@Preview
@DayNightPreviews
@Composable
internal fun PollAnswerUndisclosedSelectedPreview() = ElementThemedPreview {
internal fun PollAnswerUndisclosedSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isDisclosed = false, isSelected = true),
onClick = { }
)
}

@Preview
@DayNightPreviews
@Composable
internal fun PollAnswerEndedWinnerNotSelectedPreview() = ElementThemedPreview {
internal fun PollAnswerEndedWinnerNotSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = false, isEnabled = false, isWinner = true),
onClick = { }
)
}

@Preview
@DayNightPreviews
@Composable
internal fun PollAnswerEndedWinnerSelectedPreview() = ElementThemedPreview {
internal fun PollAnswerEndedWinnerSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true, isEnabled = false, isWinner = true),
onClick = { }
)
}

@Preview
@DayNightPreviews
@Composable
internal fun PollAnswerEndedSelectedPreview() = ElementThemedPreview {
internal fun PollAnswerEndedSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true, isEnabled = false, isWinner = false),
onClick = { }
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.VectorIcons
import io.element.android.libraries.designsystem.preview.DayNightPreviews
Expand Down Expand Up @@ -56,24 +59,24 @@ fun PollContentView(
}

Column(
modifier = modifier
.selectableGroup()
.fillMaxWidth(),
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
PollTitle(title = question, isPollEnded = isPollEnded)

PollAnswers(answerItems = answerItems, onAnswerSelected = ::onAnswerSelected)

when {
isPollEnded || pollKind == PollKind.Disclosed -> DisclosedPollBottomNotice(answerItems)
pollKind == PollKind.Undisclosed -> UndisclosedPollBottomNotice()
if (isPollEnded || pollKind == PollKind.Disclosed) {
val votesCount = remember(answerItems) { answerItems.sumOf { it.votesCount } }
DisclosedPollBottomNotice(votesCount = votesCount)
} else {
UndisclosedPollBottomNotice()
}
}
}

@Composable
internal fun PollTitle(
private fun PollTitle(
title: String,
isPollEnded: Boolean,
modifier: Modifier = Modifier
Expand All @@ -85,13 +88,13 @@ internal fun PollTitle(
if (isPollEnded) {
Icon(
resourceId = VectorIcons.PollEnd,
contentDescription = null,
contentDescription = stringResource(id = CommonStrings.a11y_poll_end),
modifier = Modifier.size(22.dp)
)
} else {
Icon(
resourceId = VectorIcons.Poll,
contentDescription = null,
contentDescription = stringResource(id = CommonStrings.a11y_poll),
modifier = Modifier.size(22.dp)
)
}
Expand All @@ -103,27 +106,35 @@ internal fun PollTitle(
}

@Composable
internal fun PollAnswers(
private fun PollAnswers(
answerItems: ImmutableList<PollAnswerItem>,
onAnswerSelected: (PollAnswer) -> Unit,
modifier: Modifier = Modifier,
) {

answerItems.forEach { answerItem ->
PollAnswerView(
modifier = modifier,
answerItem = answerItem,
onClick = { onAnswerSelected(answerItem.answer) }
)
Column(
modifier = modifier.selectableGroup(),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
answerItems.forEach {
PollAnswerView(
answerItem = it,
modifier = Modifier
.selectable(
selected = it.isSelected,
enabled = it.isEnabled,
onClick = { onAnswerSelected(it.answer) },
role = Role.RadioButton,
),
)
}
}
}

@Composable
internal fun ColumnScope.DisclosedPollBottomNotice(
answerItems: ImmutableList<PollAnswerItem>,
private fun ColumnScope.DisclosedPollBottomNotice(
votesCount: Int,
modifier: Modifier = Modifier
) {
val votesCount = answerItems.sumOf { it.votesCount }
Text(
modifier = modifier.align(Alignment.End),
style = ElementTheme.typography.fontBodyXsRegular,
Expand All @@ -133,7 +144,9 @@ internal fun ColumnScope.DisclosedPollBottomNotice(
}

@Composable
fun ColumnScope.UndisclosedPollBottomNotice(modifier: Modifier = Modifier) {
private fun ColumnScope.UndisclosedPollBottomNotice(
modifier: Modifier = Modifier
) {
Text(
modifier = modifier
.align(Alignment.Start)
Expand Down
2 changes: 2 additions & 0 deletions libraries/ui-strings/src/main/res/values/localazy.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<string name="a11y_hide_password">"Hide password"</string>
<string name="a11y_notifications_mentions_only">"Mentions only"</string>
<string name="a11y_notifications_muted">"Muted"</string>
<string name="a11y_poll">"Poll"</string>
<string name="a11y_poll_end">"Ended poll"</string>
<string name="a11y_send_files">"Send files"</string>
<string name="a11y_show_password">"Show password"</string>
<string name="a11y_user_menu">"User menu"</string>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit d6dac9a

Please sign in to comment.