diff options
author | Filip Stanis <fstanis@google.com> | 2024-04-03 16:30:45 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-03 16:30:45 +0100 |
commit | 41ffa4cfe1687cc566169a2bceddf83a3e00b9f4 (patch) | |
tree | e101db6d48f8639e66f78a440e5fca8287731002 | |
parent | 284c9338acb73cd9f0434ac29ad5da1048dbfb8d (diff) | |
download | horologist-41ffa4cfe1687cc566169a2bceddf83a3e00b9f4.tar.gz |
Updates a11y for volume (#2175)
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") |