From 7ee2acc6c84c9ea6a51908495a6f14a26f346b29 Mon Sep 17 00:00:00 2001 From: fabriziobertoglio1987 Date: Mon, 5 Apr 2021 11:47:39 -0700 Subject: [PATCH] Selected State does not annonce when TextInput Component selected (#31144) Summary: This issue fixes https://github.com/facebook/react-native/issues/30955 and is a follow up to pr https://github.com/facebook/react-native/pull/24608 which added the basic Accessibility functionalities to React Native. TextInput should announce "selected" to the user when screenreader focused. The focus is moved to the TextInput by navigating with the screenreader to the TextInput. This PR adds call to View#setSelected in BaseViewManager https://developer.android.com/reference/android/view/View#setSelected(boolean) The View#setSelected method definition https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/view/View.java ```java /** * Changes the selection state of this view. A view can be selected or not. * Note that selection is not the same as focus. Views are typically * selected in the context of an AdapterView like ListView or GridView; * the selected view is the view that is highlighted. * * param selected true if the view must be selected, false otherwise */ public void setSelected(boolean selected) { if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) { // ... hidden logic if (selected) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } // ... hidden logic } } ``` VoiceOver and TalkBack was tested with video samples included below. ## Changelog [Android] [Fixed] - Fix Selected State does not announce when TextInput Component selected on Android Pull Request resolved: https://github.com/facebook/react-native/pull/31144 Test Plan: **
CLICK TO OPEN TESTS RESULTS**

**ENABLE THE AUDIO** to hear the TalkBack announcing **SELECTED** when the user taps on the TextInput ```javascript ``` | selected is true | |:-------------------------:| |

Reviewed By: blavalla Differential Revision: D27306166 Pulled By: kacieb fbshipit-source-id: 1b3cb37b2d0875cf53f6f1bff4bf095a877b2f0e --- .../react/uimanager/BaseViewManager.java | 6 +- .../react/uimanager/BaseViewManagerTest.java | 32 ++++++++++ .../Accessibility/AccessibilityExample.js | 61 +++++++++++++++---- 3 files changed, 86 insertions(+), 13 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index 7ab93dc676f..110b933f15d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -166,8 +166,12 @@ public abstract class BaseViewManager() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return new JavaOnlyMap(); + } + }); } @Test @@ -44,4 +67,13 @@ public class BaseViewManagerTest { mViewManager.setAccessibilityRole(mView, "image"); assertThat(mView.getTag(R.id.accessibility_role)).isEqualTo(AccessibilityRole.IMAGE); } + + @Test + public void testAccessibilityStateSelected() { + WritableMap accessibilityState = Arguments.createMap(); + accessibilityState.putBoolean("selected", true); + mViewManager.setViewState(mView, accessibilityState); + assertThat(mView.getTag(R.id.accessibility_state)).isEqualTo(accessibilityState); + assertThat(mView.isSelected()).isEqualTo(true); + } } diff --git a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js index d13b53db474..b975bf5bc1c 100644 --- a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js +++ b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js @@ -12,6 +12,7 @@ const React = require('react'); const { AccessibilityInfo, + TextInput, Button, Image, Text, @@ -31,12 +32,32 @@ const mixedCheckboxImageSource = require('./mixed.png'); const {createRef} = require('react'); const styles = StyleSheet.create({ + default: { + borderWidth: StyleSheet.hairlineWidth, + borderColor: '#0f0f0f', + flex: 1, + fontSize: 13, + padding: 4, + }, + touchable: { + backgroundColor: 'blue', + borderColor: 'red', + borderWidth: 1, + borderRadius: 10, + padding: 10, + borderStyle: 'solid', + }, image: { width: 20, height: 20, resizeMode: 'contain', marginRight: 10, }, + containerAlignCenter: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + }, }); class AccessibilityExample extends React.Component { @@ -230,37 +251,53 @@ class SelectionExample extends React.Component { }; render() { + const {isSelected, isEnabled} = this.state; let accessibilityHint = 'click me to select'; - if (this.state.isSelected) { + if (isSelected) { accessibilityHint = 'click me to unselect'; } - if (!this.state.isEnabled) { + if (!isEnabled) { accessibilityHint = 'use the button on the right to enable selection'; } - let buttonTitle = this.state.isEnabled - ? 'Disable selection' - : 'Enable selection'; - + let buttonTitle = isEnabled ? 'Disable selection' : 'Enable selection'; + const touchableHint = ` (touching the TouchableOpacity will ${ + isSelected ? 'disable' : 'enable' + } accessibilityState.selected)`; return ( - + { - if (this.state.isEnabled) { + if (isEnabled) { this.setState({ - isSelected: !this.state.isSelected, + isSelected: !isSelected, }); + } else { + console.warn('selection is disabled, please enable selection.'); } }} accessibilityLabel="element 19" accessibilityState={{ - selected: this.state.isSelected, - disabled: !this.state.isEnabled, + selected: isSelected, + disabled: !isEnabled, }} + style={styles.touchable} accessibilityHint={accessibilityHint}> - Selectable element example + + {`Selectable TouchableOpacity Example ${touchableHint}`} + +