(a11y) add more screen reader labels

Close #975
This commit is contained in:
MM20 2024-07-23 22:12:54 +02:00
parent 8ae5fdfcda
commit 7ec17b7de8
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
15 changed files with 110 additions and 39 deletions

View File

@ -36,7 +36,10 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.preferences.SearchBarStyle
@ -62,6 +65,7 @@ fun SearchBar(
) {
val transition = updateTransition(level, label = "Searchbar")
val context = LocalContext.current
val elevation by transition.animateDp(
label = "elevation",
@ -169,7 +173,10 @@ fun SearchBar(
if (it.hasFocus) onFocus()
}
.focusRequester(focusRequester)
.fillMaxWidth(),
.fillMaxWidth()
.semantics {
contentDescription = context.getString(R.string.search_bar_placeholder)
},
textStyle = MaterialTheme.typography.titleMedium.copy(
color = contentColor
),

View File

@ -30,6 +30,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.integerResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.ktx.toDp
@ -62,7 +63,7 @@ fun Icons(actions: List<ToolbarAction>, slots: Int) {
var showMenu by remember { mutableStateOf(false) }
Box {
IconButton(onClick = { showMenu = true }) {
Icon(Icons.Rounded.MoreVert, contentDescription = "")
Icon(Icons.Rounded.MoreVert, contentDescription = stringResource(R.string.action_more_actions))
}
Box(
modifier = Modifier

View File

@ -591,7 +591,7 @@ fun PagerScaffold(
},
navigationIcon = {
IconButton(onClick = { viewModel.setWidgetEditMode(false) }) {
Icon(imageVector = Icons.Rounded.Done, contentDescription = null)
Icon(imageVector = Icons.Rounded.Done, contentDescription = stringResource(R.string.action_done))
}
},
)

View File

@ -567,7 +567,7 @@ fun PullDownScaffold(
},
navigationIcon = {
IconButton(onClick = { viewModel.setWidgetEditMode(false) }) {
Icon(imageVector = Icons.Rounded.Done, contentDescription = null)
Icon(imageVector = Icons.Rounded.Done, contentDescription = stringResource(R.string.action_done))
}
}
)

View File

@ -341,7 +341,7 @@ fun AppItem(
) {
Icon(
if (isPinned) Icons.Rounded.Star else Icons.Rounded.StarOutline,
null
stringResource(if (isPinned) R.string.menu_favorites_unpin else R.string.menu_favorites_pin),
)
}
}

View File

@ -4,9 +4,7 @@ import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.detectTapGestures
@ -43,6 +41,8 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@ -159,7 +159,10 @@ fun GridItem(
MaterialTheme.colorScheme.surfaceVariant,
iconShape
)
} else Modifier,
} else Modifier then if (showLabels) Modifier else Modifier
.semantics {
contentDescription = item.label
},
) {
ShapedLauncherIcon(
modifier = Modifier
@ -209,16 +212,20 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
}
LaunchedEffect(show.targetState) {
if (!show.targetState) {
animationProgress.animateTo(0f, spring(
Spring.DampingRatioNoBouncy,
Spring.StiffnessMediumLow,
))
animationProgress.animateTo(
0f, spring(
Spring.DampingRatioNoBouncy,
Spring.StiffnessMediumLow,
)
)
onDismissRequest()
} else {
animationProgress.animateTo(1f, spring(
Spring.DampingRatioLowBouncy,
Spring.StiffnessMediumLow,
))
animationProgress.animateTo(
1f, spring(
Spring.DampingRatioLowBouncy,
Spring.StiffnessMediumLow,
)
)
}
}
BackHandler {
@ -228,7 +235,14 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
Overlay {
Box(
modifier = Modifier
.background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.32f * animationProgress.value.coerceIn(0f, 1f)))
.background(
MaterialTheme.colorScheme.scrim.copy(
alpha = 0.32f * animationProgress.value.coerceIn(
0f,
1f
)
)
)
.fillMaxSize()
.systemBarsPadding()
.imePadding()

View File

@ -50,6 +50,9 @@ import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.roundToIntRect
@ -140,7 +143,10 @@ fun AppShortcutItem(
}.collectAsState(null)
InputChip(
modifier = Modifier.width(IntrinsicSize.Max).padding(top = 8.dp),
modifier = Modifier
.width(IntrinsicSize.Max)
.padding(top = 8.dp)
.semantics { role = Role.Button },
selected = false,
onClick = {
viewModel.launchChild(context, app)
@ -164,7 +170,7 @@ fun AppShortcutItem(
{
Icon(
if (isPinned) Icons.Rounded.Star else Icons.Rounded.StarOutline,
null,
stringResource(if (isPinned) R.string.menu_favorites_unpin else R.string.menu_favorites_pin),
modifier = Modifier
.clip(CircleShape)
.requiredSize(InputChipDefaults.IconSize)

View File

@ -32,11 +32,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.preferences.SearchBarStyle
import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.SearchBar
import de.mm20.launcher2.ui.component.SearchBarLevel
import de.mm20.launcher2.ui.launcher.search.SearchVM
@ -115,7 +117,9 @@ fun LauncherSearchBar(
else IconButtonDefaults.iconButtonColors()
) {
Box {
Icon(imageVector = Icons.Rounded.FilterAlt, contentDescription = null)
Icon(imageVector = Icons.Rounded.FilterAlt, contentDescription = stringResource(
if (searchVM.showFilters.value) R.string.menu_hide_filters else R.string.menu_show_filters
))
androidx.compose.animation.AnimatedVisibility(
!searchVM.filters.value.allCategoriesEnabled,
enter = scaleIn(tween(100)),

View File

@ -55,7 +55,7 @@ fun RowScope.SearchBarMenu(
rightIcon,
atEnd = searchBarValue.isNotEmpty()
),
contentDescription = null,
contentDescription = stringResource(if (searchBarValue.isNotBlank()) R.string.action_clear else R.string.action_more_actions),
tint = LocalContentColor.current
)
}

View File

@ -29,6 +29,10 @@ import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
@ -136,10 +140,18 @@ fun WidgetColumn(
val editButton by viewModel.editButton.collectAsState()
if (editMode || editButton == true) {
val title = stringResource(
if (editMode) R.string.widget_add_widget
else R.string.menu_edit_widgets
)
val icon =
AnimatedImageVector.animatedVectorResource(R.drawable.anim_ic_edit_add)
ExtendedFloatingActionButton(
modifier = Modifier
.semantics {
role = Role.Button
contentDescription = title
}
.padding(16.dp)
.align(Alignment.CenterHorizontally),
icon = {
@ -151,12 +163,7 @@ fun WidgetColumn(
)
},
text = {
Text(
stringResource(
if (editMode) R.string.widget_add_widget
else R.string.menu_edit_widgets
)
)
Text(title)
}, onClick = {
if (!editMode) {
onEditModeChange(true)

View File

@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.OpenInNew
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.ArrowDropDown
import androidx.compose.material.icons.rounded.ChevronLeft
@ -85,7 +86,7 @@ fun CalendarWidget(
modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 4.dp)
) {
IconButton(onClick = { viewModel.previousDay() }) {
Icon(imageVector = Icons.Rounded.ChevronLeft, contentDescription = null)
Icon(imageVector = Icons.Rounded.ChevronLeft, contentDescription = stringResource(R.string.calendar_widget_previous_day))
}
Box(
modifier = Modifier.weight(1f),
@ -119,13 +120,13 @@ fun CalendarWidget(
}
}
IconButton(onClick = { viewModel.nextDay() }) {
Icon(imageVector = Icons.Rounded.ChevronRight, contentDescription = null)
Icon(imageVector = Icons.Rounded.ChevronRight, contentDescription = stringResource(R.string.calendar_widget_next_day))
}
IconButton(onClick = { viewModel.createEvent(context) }) {
Icon(imageVector = Icons.Rounded.Add, contentDescription = null)
Icon(imageVector = Icons.Rounded.Add, contentDescription = stringResource(R.string.calendar_widget_create_event))
}
IconButton(onClick = { viewModel.openCalendarApp(context) }) {
Icon(imageVector = Icons.Rounded.OpenInNew, contentDescription = null)
Icon(imageVector = Icons.AutoMirrored.Rounded.OpenInNew, contentDescription = stringResource(R.string.calendar_widget_open_calendar))
}
}
val events by viewModel.calendarEvents

View File

@ -6,7 +6,6 @@ import android.media.session.PlaybackState.CustomAction
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@ -74,6 +73,8 @@ import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.core.content.res.ResourcesCompat
@ -261,8 +262,12 @@ fun MusicWidget(widget: MusicWidget) {
},
onLongClick = {
viewModel.openPlayerSelector(context)
}
},
)
.semantics {
contentDescription =
context.getString(R.string.music_widget_open_player)
}
.conditional(
art == null,
Modifier.background(
@ -333,7 +338,7 @@ fun MusicWidget(widget: MusicWidget) {
}) {
Icon(
imageVector = Icons.Rounded.SkipPrevious,
null
stringResource(R.string.music_widget_previous_track)
)
}
}
@ -350,7 +355,11 @@ fun MusicWidget(widget: MusicWidget) {
playPauseIcon,
atEnd = playbackState == PlaybackState.Playing
),
contentDescription = null
contentDescription = if (playbackState == PlaybackState.Playing) {
stringResource(R.string.music_widget_pause)
} else {
stringResource(R.string.music_widget_play)
}
)
}
if (supportedActions.skipToNext) {
@ -359,7 +368,7 @@ fun MusicWidget(widget: MusicWidget) {
}) {
Icon(
imageVector = Icons.Rounded.SkipNext,
null
stringResource(R.string.music_widget_next_track)
)
}
}

View File

@ -217,7 +217,10 @@ fun NotesWidget(
Icon(
if (widget.config.linkedFile == null) Icons.Rounded.Link
else Icons.Rounded.LinkOff,
null
stringResource(
if (widget.config.linkedFile == null) R.string.note_widget_link_file
else R.string.note_widget_action_unlink_file
)
)
}
}
@ -295,7 +298,7 @@ fun NotesWidget(
Spacer(modifier = Modifier.weight(1f))
Box {
IconButton(onClick = { showMenu = true }) {
Icon(Icons.Rounded.MoreVert, null)
Icon(Icons.Rounded.MoreVert, stringResource(R.string.action_more_actions))
}
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
DropdownMenuItem(

View File

@ -51,6 +51,8 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle
@ -400,7 +402,10 @@ fun WeatherTimeSelector(
verticalArrangement = Arrangement.SpaceEvenly
) {
WeatherIcon(
modifier = Modifier.align(Alignment.CenterHorizontally),
modifier = Modifier.align(Alignment.CenterHorizontally)
.semantics {
contentDescription = fc.condition
},
icon = weatherIconById(fc.icon),
night = fc.night
)

View File

@ -37,6 +37,9 @@
<string name="msg_item_hidden">%1$s has been hidden.</string>
<string name="action_undo">Undo</string>
<string name="action_import">Import</string>
<string name="action_done">Done</string>
<string name="action_more_actions">More actions</string>
<string name="action_clear">Clear</string>
<!-- Delete something (a file or a web search shortcut) -->
<string name="menu_delete">Delete</string>
<string name="menu_remove">Remove</string>
@ -839,6 +842,8 @@
<string name="clock_style_empty">No clock</string>
<string name="clock_style_custom">Custom widget</string>
<string name="clock_variant_outlined">Outlined</string>
<string name="menu_show_filters">Show filters</string>
<string name="menu_hide_filters">Hide filters</string>
<string name="search_filter_tools">Tools</string>
<string name="search_filter_online">Online results</string>
<string name="search_filter_apps">Apps</string>
@ -970,4 +975,13 @@
<item quantity="other">%1$s notifications</item>
</plurals>
<string name="weather_widget_no_provider">No weather provider selected or the selected provider is not available</string>
<string name="music_widget_previous_track">Previous title</string>
<string name="music_widget_next_track">Next title</string>
<string name="music_widget_play">Play</string>
<string name="music_widget_pause">Pause</string>
<string name="music_widget_open_player">Open player</string>
<string name="calendar_widget_previous_day">Previous day</string>
<string name="calendar_widget_next_day">Next day</string>
<string name="calendar_widget_create_event">Create new event</string>
<string name="calendar_widget_open_calendar">Open calendar app</string>
</resources>