2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 09:58:08 +00:00

Accessibility improvements when using a screen reader

Fixes based on the report done by HAN University of Applied Sciences.
This commit is contained in:
Albert Vaca Cintora 2024-03-01 00:18:02 +00:00
parent 47fe781685
commit 8ffee90255
16 changed files with 65 additions and 28 deletions

View File

@ -31,13 +31,16 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:gravity="center" android:gravity="center"
android:padding="12dip" android:padding="12dip"
android:importantForAccessibility="no"
android:text="@string/mousepad_info" /> android:text="@string/mousepad_info" />
<org.kde.kdeconnect.Plugins.MousePadPlugin.KeyListenerView <org.kde.kdeconnect.Plugins.MousePadPlugin.KeyListenerView
android:id="@+id/keyListener" android:id="@+id/keyListener"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_above="@id/mouse_buttons"/> android:layout_above="@id/mouse_buttons"
android:contentDescription="@string/mousepad_info_no_gestures"
/>
<LinearLayout <LinearLayout
android:id="@+id/mouse_buttons" android:id="@+id/mouse_buttons"
@ -50,6 +53,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:id="@+id/mouse_click_left" android:id="@+id/mouse_click_left"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:contentDescription="@string/left_click"
android:layout_weight="3" android:layout_weight="3"
app:icon="@drawable/left_click_48dp" app:icon="@drawable/left_click_48dp"
style="@style/KdeConnectButton.IconButton" /> style="@style/KdeConnectButton.IconButton" />
@ -57,6 +61,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/mouse_click_middle" android:id="@+id/mouse_click_middle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:contentDescription="@string/middle_click"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
style="@style/KdeConnectButton.IconButton.Secondary" /> style="@style/KdeConnectButton.IconButton.Secondary" />
@ -64,6 +69,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/mouse_click_right" android:id="@+id/mouse_click_right"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:contentDescription="@string/right_click"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="3" android:layout_weight="3"
app:icon="@drawable/right_click_48dp" app:icon="@drawable/right_click_48dp"

View File

@ -14,11 +14,12 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="org.kde.kdeconnect.Plugins.MprisPlugin.MprisActivity"> tools:context="org.kde.kdeconnect.Plugins.MprisPlugin.MprisActivity">
<!-- Keep in sync with toolbar.xml, copied here because it needs the nested TabLayout -->
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" /> android:layout_height="?attr/actionBarSize" />

View File

@ -48,7 +48,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:layout_gravity="top" android:layout_gravity="top"
android:paddingTop="@dimen/nav_header_vertical_spacing" android:paddingTop="@dimen/nav_header_vertical_spacing"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:contentDescription="@string/kde_connect" android:importantForAccessibility="no"
tools:src="@drawable/icon" /> tools:src="@drawable/icon" />
<LinearLayout <LinearLayout

View File

@ -30,9 +30,9 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:contentDescription="@string/device_icon_description"
android:src="@drawable/ic_device_laptop_32dp" android:src="@drawable/ic_device_laptop_32dp"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:importantForAccessibility="no"
app:tint="?attr/colorOnSurface" /> app:tint="?attr/colorOnSurface" />
<TextView <TextView

View File

@ -26,8 +26,8 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:contentDescription="@string/device_icon_description"
android:src="@drawable/ic_device_laptop_32dp" android:src="@drawable/ic_device_laptop_32dp"
android:importantForAccessibility="no"
app:tint="?attr/colorControlNormal"/> app:tint="?attr/colorControlNormal"/>
<LinearLayout <LinearLayout

View File

@ -14,5 +14,6 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/mouse_cursor" android:id="@+id/mouse_cursor"
android:importantForAccessibility="no"
android:src="@drawable/mouse_pointer" /> android:src="@drawable/mouse_pointer" />
</RelativeLayout> </RelativeLayout>

View File

@ -19,7 +19,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:layout_height="@dimen/mpris_now_playing_album_height" android:layout_height="@dimen/mpris_now_playing_album_height"
android:layout_margin="25dp" android:layout_margin="25dp"
android:layout_weight="@integer/mpris_now_playing_album_weight" android:layout_weight="@integer/mpris_now_playing_album_weight"
android:contentDescription="@string/mpris_coverart_description" android:importantForAccessibility="no"
android:scaleType="fitCenter" /> android:scaleType="fitCenter" />
<include <include

View File

@ -30,6 +30,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:layout_gravity="top" android:layout_gravity="top"
android:paddingTop="@dimen/nav_header_vertical_spacing" android:paddingTop="@dimen/nav_header_vertical_spacing"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:importantForAccessibility="no"
tools:srcCompat="@drawable/ic_device_phone_32dp" /> tools:srcCompat="@drawable/ic_device_phone_32dp" />
<LinearLayout <LinearLayout

View File

@ -39,6 +39,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawablePadding="5dp" android:drawablePadding="5dp"
android:layout_marginBottom="8dip" android:layout_marginBottom="8dip"
android:importantForAccessibility="no"
android:visibility="gone" android:visibility="gone"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
app:drawableStartCompat="@drawable/ic_key" /> app:drawableStartCompat="@drawable/ic_key" />

View File

@ -30,7 +30,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:tint="@color/text_color" android:tint="@color/text_color"
android:src="@drawable/ic_kde_24dp" android:src="@drawable/ic_kde_24dp"
android:contentDescription="@string/device_icon_description" android:importantForAccessibility="no"
tools:ignore="UseAppTint"/> <!-- can't use app:tint in RemoteView --> tools:ignore="UseAppTint"/> <!-- can't use app:tint in RemoteView -->
<TextView <TextView

View File

@ -66,6 +66,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<string name="remotekeyboard_multiple_connections" translatable="true">There is more than one remote keyboard connection, select the device to configure</string> <string name="remotekeyboard_multiple_connections" translatable="true">There is more than one remote keyboard connection, select the device to configure</string>
<string name="open_mousepad">Remote input</string> <string name="open_mousepad">Remote input</string>
<string name="mousepad_info">Move a finger on the screen to move the mouse cursor. Tap for a click, and use two/three fingers for right and middle buttons. Use 2 fingers to scroll. Use a long press to drag\'n drop. Gyro mouse functionality can be enabled from plugin preferences</string> <string name="mousepad_info">Move a finger on the screen to move the mouse cursor. Tap for a click, and use two/three fingers for right and middle buttons. Use 2 fingers to scroll. Use a long press to drag\'n drop. Gyro mouse functionality can be enabled from plugin preferences</string>
<string name="mousepad_info_no_gestures">Move a finger on the screen to move the mouse cursor, tap for a click.</string>
<string name="mousepad_keyboard_input_not_supported">Keyboard input not supported by the paired device</string> <string name="mousepad_keyboard_input_not_supported">Keyboard input not supported by the paired device</string>
<string name="mousepad_single_tap_settings_title">Set one finger tap action</string> <string name="mousepad_single_tap_settings_title">Set one finger tap action</string>
<string name="mousepad_double_tap_settings_title">Set two finger tap action</string> <string name="mousepad_double_tap_settings_title">Set two finger tap action</string>
@ -181,6 +182,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<string name="my_device_fingerprint">SHA256 fingerprint of your device certificate is:</string> <string name="my_device_fingerprint">SHA256 fingerprint of your device certificate is:</string>
<string name="remote_device_fingerprint">SHA256 fingerprint of remote device certificate is:</string> <string name="remote_device_fingerprint">SHA256 fingerprint of remote device certificate is:</string>
<string name="pair_requested">Pair requested</string> <string name="pair_requested">Pair requested</string>
<string name="pair_succeeded">Pair succeeded</string>
<string name="pairing_request_from">Pairing request from %1s</string> <string name="pairing_request_from">Pairing request from %1s</string>
<string name="pairing_verification_code" translatable="false">🔑%1s...</string> <string name="pairing_verification_code" translatable="false">🔑%1s...</string>
<plurals name="incoming_file_title">Receiving file from %1s> <plurals name="incoming_file_title">Receiving file from %1s>
@ -219,6 +221,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<string name="received_file_text">Tap to open \'%1s\'</string> <string name="received_file_text">Tap to open \'%1s\'</string>
<string name="cannot_create_file">Cannot create file %s</string> <string name="cannot_create_file">Cannot create file %s</string>
<string name="tap_to_answer">Tap to answer</string> <string name="tap_to_answer">Tap to answer</string>
<string name="left_click">Send Left Click</string>
<string name="right_click">Send Right Click</string> <string name="right_click">Send Right Click</string>
<string name="middle_click">Send Middle Click</string> <string name="middle_click">Send Middle Click</string>
<string name="show_keyboard">Show Keyboard</string> <string name="show_keyboard">Show Keyboard</string>
@ -345,7 +348,6 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<string name="telephony_pref_blocked_title">Blocked numbers</string> <string name="telephony_pref_blocked_title">Blocked numbers</string>
<string name="telephony_pref_blocked_dialog_desc">Don\'t show calls and SMS from these numbers. Please specify one number per line</string> <string name="telephony_pref_blocked_dialog_desc">Don\'t show calls and SMS from these numbers. Please specify one number per line</string>
<string name="mpris_coverart_description">Cover art of current media</string> <string name="mpris_coverart_description">Cover art of current media</string>
<string name="device_icon_description">Device icon</string>
<string name="settings_icon_description">Settings icon</string> <string name="settings_icon_description">Settings icon</string>
<string name="presenter_fullscreen">Fullscreen</string> <string name="presenter_fullscreen">Fullscreen</string>

View File

@ -95,6 +95,7 @@ class ComposeSendActivity : AppCompatActivity() {
KdeTopAppBar( KdeTopAppBar(
title = stringResource(R.string.compose_send_title), title = stringResource(R.string.compose_send_title),
navIconOnClick = { onBackPressedDispatcher.onBackPressed() }, navIconOnClick = { onBackPressedDispatcher.onBackPressed() },
navIconDescription = getString(androidx.appcompat.R.string.abc_action_bar_up_description),
actions = { actions = {
KdeTextButton( KdeTextButton(
modifier = Modifier.padding(horizontal = 8.dp), modifier = Modifier.padding(horizontal = 8.dp),

View File

@ -133,10 +133,12 @@ class PresenterActivity : AppCompatActivity(), SensorEventListener {
KdeButton( KdeButton(
onClick = { plugin.sendPrevious() }, onClick = { plugin.sendPrevious() },
modifier = Modifier.fillMaxSize().weight(1f), modifier = Modifier.fillMaxSize().weight(1f),
contentDescription = getString(R.string.mpris_previous),
icon = Icons.Default.ArrowBack, icon = Icons.Default.ArrowBack,
) )
KdeButton( KdeButton(
onClick = { plugin.sendNext() }, onClick = { plugin.sendNext() },
contentDescription = getString(R.string.mpris_next),
modifier = Modifier.fillMaxSize().weight(1f), modifier = Modifier.fillMaxSize().weight(1f),
icon = Icons.Default.ArrowForward, icon = Icons.Default.ArrowForward,
) )
@ -176,20 +178,25 @@ class PresenterActivity : AppCompatActivity(), SensorEventListener {
var dropdownShownState by remember { mutableStateOf(false) } var dropdownShownState by remember { mutableStateOf(false) }
KdeTopAppBar(navIconOnClick = { onBackPressedDispatcher.onBackPressed() }, actions = { KdeTopAppBar(
IconButton(onClick = { dropdownShownState = true }) { title = stringResource(R.string.pref_plugin_presenter),
Icon(Icons.Default.MoreVert, stringResource(R.string.extra_options)) navIconOnClick = { onBackPressedDispatcher.onBackPressed() },
navIconDescription = getString(androidx.appcompat.R.string.abc_action_bar_up_description),
actions = {
IconButton(onClick = { dropdownShownState = true }) {
Icon(Icons.Default.MoreVert, stringResource(R.string.extra_options))
}
DropdownMenu(expanded = dropdownShownState, onDismissRequest = { dropdownShownState = false }) {
DropdownMenuItem(
onClick = { plugin.sendFullscreen() },
text = { Text(stringResource(R.string.presenter_fullscreen)) },
)
DropdownMenuItem(
onClick = { plugin.sendEsc() },
text = { Text(stringResource(R.string.presenter_exit)) },
)
}
} }
DropdownMenu(expanded = dropdownShownState, onDismissRequest = { dropdownShownState = false }) { )
DropdownMenuItem(
onClick = { plugin.sendFullscreen() },
text = { Text(stringResource(R.string.presenter_fullscreen)) },
)
DropdownMenuItem(
onClick = { plugin.sendEsc() },
text = { Text(stringResource(R.string.presenter_exit)) },
)
}
})
} }
} }

View File

@ -23,6 +23,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
@ -113,7 +117,7 @@ class DeviceFragment : Fragment() {
requirePairingBinding().pairButton.setOnClickListener { requirePairingBinding().pairButton.setOnClickListener {
with(requirePairingBinding()) { with(requirePairingBinding()) {
pairButton.visibility = View.GONE pairButton.visibility = View.GONE
pairMessage.text = null pairMessage.text = getString(R.string.pair_requested)
pairVerification.visibility = View.VISIBLE pairVerification.visibility = View.VISIBLE
pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device?.certificate) pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device?.certificate)
pairProgress.visibility = View.VISIBLE pairProgress.visibility = View.VISIBLE
@ -306,6 +310,7 @@ class DeviceFragment : Fragment() {
} }
override fun pairingSuccessful() { override fun pairingSuccessful() {
requirePairingBinding().pairMessage.announceForAccessibility(getString(R.string.pair_succeeded))
mActivity?.runOnUiThread { refreshUI() } mActivity?.runOnUiThread { refreshUI() }
} }
@ -383,7 +388,7 @@ class DeviceFragment : Fragment() {
fun PluginButton(plugin : Plugin, modifier: Modifier) { fun PluginButton(plugin : Plugin, modifier: Modifier) {
Card( Card(
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
modifier = modifier, modifier = modifier.semantics { role = Role.Button },
onClick = { plugin.startMainActivity(mActivity) } onClick = { plugin.startMainActivity(mActivity) }
) { ) {
Column( Column(
@ -435,7 +440,7 @@ class DeviceFragment : Fragment() {
fun PluginsWithoutPermissions(title : String, plugins: Collection<Plugin>, action : (plugin: Plugin) -> Unit) { fun PluginsWithoutPermissions(title : String, plugins: Collection<Plugin>, action : (plugin: Plugin) -> Unit) {
Text( Text(
text = title, text = title,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp) modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp).semantics { heading() }
) )
plugins.forEach { plugin -> plugins.forEach { plugin ->
Text( Text(
@ -444,6 +449,7 @@ class DeviceFragment : Fragment() {
.fillMaxWidth() .fillMaxWidth()
.clickable { action(plugin) } .clickable { action(plugin) }
.padding(start = 28.dp, end = 16.dp, top = 12.dp, bottom = 12.dp) .padding(start = 28.dp, end = 16.dp, top = 12.dp, bottom = 12.dp)
.semantics { role = Role.Button }
) )
} }
} }

View File

@ -51,6 +51,7 @@ fun KdeButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: ButtonColors = ButtonDefaults.buttonColors(), colors: ButtonColors = ButtonDefaults.buttonColors(),
text: String? = null, text: String? = null,
contentDescription: String? = null,
icon: ImageVector? = null, icon: ImageVector? = null,
) { ) {
//TODO uncomment when button is widely used //TODO uncomment when button is widely used
@ -69,7 +70,7 @@ fun KdeButton(
colors = colors, colors = colors,
// interactionSource = interactionSource, // interactionSource = interactionSource,
content = { content = {
icon?.let { Icon(imageVector = it, contentDescription = text) } icon?.let { Icon(imageVector = it, contentDescription = contentDescription ?: text) }
text?.let { Text(it, maxLines = 1, overflow = Ellipsis) } text?.let { Text(it, maxLines = 1, overflow = Ellipsis) }
} }
) )
@ -83,6 +84,7 @@ fun IconButtonPreview() {
Modifier.width(120.dp), Modifier.width(120.dp),
ButtonDefaults.buttonColors(Color.Gray, Color.DarkGray), ButtonDefaults.buttonColors(Color.Gray, Color.DarkGray),
"Button Text", "Button Text",
null,
Icons.Default.Build, Icons.Default.Build,
) )
} }

View File

@ -11,8 +11,11 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import org.kde.kdeconnect_tp.R import org.kde.kdeconnect_tp.R
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -20,14 +23,20 @@ import org.kde.kdeconnect_tp.R
fun KdeTopAppBar( fun KdeTopAppBar(
title: String = stringResource(R.string.kde_connect), title: String = stringResource(R.string.kde_connect),
navIcon: ImageVector = Icons.Default.ArrowBack, navIcon: ImageVector = Icons.Default.ArrowBack,
navIconDescription: String = "",
navIconOnClick: () -> Unit, // = { onBackPressedDispatcher.onBackPressed() } navIconOnClick: () -> Unit, // = { onBackPressedDispatcher.onBackPressed() }
actions: @Composable (RowScope.() -> Unit) = {}, actions: @Composable (RowScope.() -> Unit) = {},
) { ) {
TopAppBar( TopAppBar(
navigationIcon = { navigationIcon = {
IconButton(onClick = navIconOnClick, content = { Icon(navIcon, null) }) IconButton(onClick = navIconOnClick, content = { Icon(navIcon, navIconDescription) })
}, },
title = { Text(title) }, title = { Text(title,
// Commented for now because the MDC and androidx toolbars don't set this either
// https://github.com/material-components/material-components-android/issues/4073
// https://github.com/androidx/androidx/blob/androidx-main/appcompat/appcompat/src/main/res/layout/abc_action_bar_title_item.xml
// modifier = Modifier.semantics { heading() }
) },
actions = actions actions = actions
) )
} }