feat(a11y): Announce a low battery instead of only colouring it

Crossing the warn or critical threshold was expressed as a colour change only,
so it did not exist for a screen reader. Each battery slot on the overview card
now carries the level as a state description.
This commit is contained in:
darken
2026-08-25 22:33:42 +02:00
committed by Matthias Urhahn
parent 7960144be6
commit b9461d3259
5 changed files with 36 additions and 5 deletions
@@ -50,6 +50,7 @@ import eu.darken.capod.monitor.core.visibleAncModes
import eu.darken.capod.main.ui.overview.cards.components.AncModeSelector
import eu.darken.capod.main.ui.overview.cards.components.BatteryCapsule
import eu.darken.capod.main.ui.overview.cards.components.CompactBatterySummary
import eu.darken.capod.main.ui.overview.cards.components.batteryTierState
import eu.darken.capod.main.ui.overview.cards.components.DebugSection
import eu.darken.capod.main.ui.overview.cards.components.DeviceConnectionBadge
import eu.darken.capod.main.ui.overview.cards.components.MissingPairedDeviceBanner
@@ -330,7 +331,7 @@ private fun PodGauge(
val ringColor = tier.fillColor()
Column(
modifier = modifier,
modifier = modifier.batteryTierState(tier),
horizontalAlignment = Alignment.CenterHorizontally,
) {
// Ring with icon inside
@@ -420,7 +421,9 @@ private fun CaseRow(
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.batteryTierState(tier),
) {
Image(
painter = painterResource(device.caseIcon),
@@ -52,6 +52,7 @@ import eu.darken.capod.main.ui.overview.cards.components.DeviceConnectionBadge
import eu.darken.capod.main.ui.overview.cards.components.MissingPairedDeviceBanner
import eu.darken.capod.main.ui.overview.cards.components.SignalIndicator
import eu.darken.capod.main.ui.overview.cards.components.StatusChip
import eu.darken.capod.main.ui.overview.cards.components.batteryTierState
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
@@ -231,7 +232,9 @@ private fun ColumnScope.SinglePodsCardExpanded(
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.size(88.dp),
modifier = Modifier
.size(88.dp)
.batteryTierState(tier),
) {
// Track ring
CircularProgressIndicator(
@@ -0,0 +1,23 @@
package eu.darken.capod.main.ui.overview.cards.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import eu.darken.capod.R
import eu.darken.capod.monitor.core.battery.BatteryTier
/**
* Announces a low or critical level on a battery slot — crossing either threshold is otherwise
* only visible as a colour change. A no-op at the other tiers, which make no claim to announce.
*/
@Composable
fun Modifier.batteryTierState(tier: BatteryTier): Modifier {
val description = when (tier) {
BatteryTier.CRITICAL -> stringResource(R.string.battery_state_critical_cd)
BatteryTier.WARN -> stringResource(R.string.battery_state_low_cd)
BatteryTier.UNKNOWN, BatteryTier.GOOD -> return this
}
return semantics(mergeDescendants = true) { stateDescription = description }
}
@@ -142,7 +142,7 @@ private fun MiniPodRing(
val ringColor = tier.fillColor()
Row(
modifier = modifier,
modifier = modifier.batteryTierState(tier),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
@@ -197,7 +197,7 @@ private fun MiniCaseCluster(
val tier = batteryTier(device.batteryCase)
Row(
modifier = modifier,
modifier = modifier.batteryTierState(tier),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
+2
View File
@@ -274,6 +274,8 @@
<string name="pods_dual_right_short_label">R</string>
<string name="pods_case_label">Case</string>
<string name="battery_unavailable_label">Battery unavailable</string>
<string name="battery_state_low_cd">Battery low</string>
<string name="battery_state_critical_cd">Battery critically low</string>
<string name="pods_case_status_open_label">Open</string>
<string name="pods_case_status_closed_label">Closed</string>
<string name="pods_connection_state_disconnected_label">Not connected to a device</string>