summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilip Stanis <fstanis@google.com>2024-04-03 16:30:45 +0100
committerGitHub <noreply@github.com>2024-04-03 16:30:45 +0100
commit41ffa4cfe1687cc566169a2bceddf83a3e00b9f4 (patch)
treee101db6d48f8639e66f78a440e5fca8287731002
parent284c9338acb73cd9f0434ac29ad5da1048dbfb8d (diff)
downloadhorologist-41ffa4cfe1687cc566169a2bceddf83a3e00b9f4.tar.gz
Updates a11y for volume (#2175)
-rw-r--r--media/audio-ui/src/main/java/com/google/android/horologist/audio/ui/VolumeScreen.kt36
-rw-r--r--media/audio-ui/src/main/res/values/strings.xml4
-rw-r--r--media/audio-ui/src/test/kotlin/com/google/android/horologist/audio/ui/VolumeScreenA11yTest.kt15
3 files changed, 32 insertions, 23 deletions
diff --git a/media/audio-ui/src/main/java/com/google/android/horologist/audio/ui/VolumeScreen.kt b/media/audio-ui/src/main/java/com/google/android/horologist/audio/ui/VolumeScreen.kt
index 8b840862..422a666e 100644
--- a/media/audio-ui/src/main/java/com/google/android/horologist/audio/ui/VolumeScreen.kt
+++ b/media/audio-ui/src/main/java/com/google/android/horologist/audio/ui/VolumeScreen.kt
@@ -17,8 +17,6 @@
package com.google.android.horologist.audio.ui
import android.media.AudioManager
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
@@ -27,10 +25,15 @@ import androidx.compose.material.icons.automirrored.outlined.VolumeUp
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.LiveRegionMode
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.liveRegion
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -48,6 +51,7 @@ import com.google.android.horologist.compose.material.IconRtlMode
import com.google.android.horologist.compose.material.util.DECORATIVE_ELEMENT_CONTENT_DESCRIPTION
import com.google.android.horologist.compose.rotaryinput.RotaryDefaults.isLowResInput
import com.google.android.horologist.images.base.paintable.ImageVectorPaintable.Companion.asPaintable
+import kotlin.math.roundToInt
/**
* Volume Screen with an [InlineSlider] and Increase/Decrease buttons for the Audio Stream Volume.
@@ -110,10 +114,13 @@ public fun VolumeScreen(
VolumeScreen(
volume = volume,
contentSlot = {
- val volumeState = volume()
DeviceChip(
modifier = Modifier.padding(horizontal = 18.dp),
- volumeDescription = volumeDescription(volumeState, audioOutputUi.isConnected),
+ volumeDescription = if (audioOutputUi.isConnected) {
+ stringResource(id = R.string.horologist_volume_screen_connected_state)
+ } else {
+ stringResource(id = R.string.horologist_volume_screen_not_connected_state)
+ },
deviceName = audioOutputUi.displayName,
icon = {
Icon(
@@ -180,9 +187,18 @@ internal fun VolumeScreen(
showVolumeIndicator: Boolean = true,
volumeColor: Color = MaterialTheme.colors.secondary,
) {
- Box(modifier = modifier.fillMaxSize())
val volumeState = volume()
+ val volumePercent = (100f * volumeState.current / volumeState.max).roundToInt()
+ val volumeDescription = if (volumeState.current == 0) {
+ stringResource(id = R.string.horologist_volume_screen_volume_zero)
+ } else {
+ stringResource(id = R.string.horologist_volume_screen_volume_percent, volumePercent)
+ }
Stepper(
+ modifier = modifier.semantics {
+ liveRegion = LiveRegionMode.Assertive
+ contentDescription = volumeDescription
+ },
value = volumeState.current.toFloat(),
onValueChange = { if (it > volumeState.current) increaseVolume() else decreaseVolume() },
steps = volumeState.max - 1,
@@ -193,6 +209,7 @@ internal fun VolumeScreen(
decreaseIcon = {
decreaseIcon()
},
+ enableRangeSemantics = false,
) {
contentSlot()
}
@@ -225,12 +242,3 @@ public object VolumeScreenDefaults {
)
}
}
-
-@Composable
-private fun volumeDescription(volumeUiState: VolumeUiState, isAudioOutputConnected: Boolean): String {
- return if (isAudioOutputConnected) {
- stringResource(id = R.string.horologist_volume_screen_connected_state, volumeUiState.current)
- } else {
- stringResource(id = R.string.horologist_volume_screen_not_connected_state)
- }
-}
diff --git a/media/audio-ui/src/main/res/values/strings.xml b/media/audio-ui/src/main/res/values/strings.xml
index 8681dfce..7c54462e 100644
--- a/media/audio-ui/src/main/res/values/strings.xml
+++ b/media/audio-ui/src/main/res/values/strings.xml
@@ -17,7 +17,9 @@
<resources>
<string description="Content description of the increase volume button. It lets the user increase the volume of the current audio output of the watch. [CHAR_LIMIT=NONE]" name="horologist_volume_screen_volume_up_content_description">Increase Volume</string>
<string description="Content description of the decrease volume button. It lets the user decrease the volume of the current audio output of the watch. [CHAR_LIMIT=NONE]" name="horologist_volume_screen_volume_down_content_description">Decrease Volume</string>
- <string description="State description of device output chip. It lets the user know that this device is connected to the watch and what the current volume is. [CHAR_LIMIT=NONE]" name="horologist_volume_screen_connected_state">Connected, Volume %1$d</string>
+ <string description="Content description of the current volume. It lets the user know what the current volume is, in percent. [CHAR_LIMIT=NONE]" name="horologist_volume_screen_volume_percent">Volume set to %1$d%%</string>
+ <string description="Content description used when the volume is set to zero, i.e. no sound is coming out. [CHAR_LIMIT=NONE]" name="horologist_volume_screen_volume_zero">Volume muted</string>
+ <string description="State description of device output chip. It lets the user know that this device is connected to the watch and what the current volume is. [CHAR_LIMIT=NONE]" name="horologist_volume_screen_connected_state">Connected</string>
<string description="State description of device output chip. It lets the user know that this device is not connected to the watch. [CHAR_LIMIT=NONE]" name="horologist_volume_screen_not_connected_state">Not Connected</string>
<string description="The description of the click action on the device output chip. It lets the user know the audio output can be changed by clicking on this chip. [CHAR_LIMIT=NONE]" name="horologist_volume_screen_change_audio_output">Change Audio Output</string>
<string description="Indicates that the current audio output is the watch speaker. Appears on the device output button chip. [CHAR LIMIT=20]" name="horologist_speaker_name">Watch Speaker</string>
diff --git a/media/audio-ui/src/test/kotlin/com/google/android/horologist/audio/ui/VolumeScreenA11yTest.kt b/media/audio-ui/src/test/kotlin/com/google/android/horologist/audio/ui/VolumeScreenA11yTest.kt
index 55d9a708..1e499e5e 100644
--- a/media/audio-ui/src/test/kotlin/com/google/android/horologist/audio/ui/VolumeScreenA11yTest.kt
+++ b/media/audio-ui/src/test/kotlin/com/google/android/horologist/audio/ui/VolumeScreenA11yTest.kt
@@ -16,8 +16,6 @@
package com.google.android.horologist.audio.ui
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
@@ -41,11 +39,9 @@ class VolumeScreenA11yTest {
@Test
fun testLabelOrdering() {
- val volumeState by mutableStateOf(
- VolumeState(
- current = 5,
- max = 10,
- ),
+ val volumeState = VolumeState(
+ current = 5,
+ max = 10,
)
val audioOutput = AudioOutput.BluetoothHeadset("id", "Pixelbuds")
@@ -56,6 +52,9 @@ class VolumeScreenA11yTest {
)
}
+ composeTestRule.onNodeWithContentDescription("Volume set to 50%")
+ .assertIsDisplayed()
+
composeTestRule.onNodeWithContentDescription("Increase Volume")
.assertIsDisplayed()
.assertHasClickAction()
@@ -63,7 +62,7 @@ class VolumeScreenA11yTest {
composeTestRule.onNodeWithText("Pixelbuds")
.assertIsDisplayed()
.assertHasClickAction()
- .assertHasStateDescription("Connected, Volume 5")
+ .assertHasStateDescription("Connected")
.assertHasClickLabel("Change Audio Output")
composeTestRule.onNodeWithContentDescription("Decrease Volume")