Skip to content

Commit

Permalink
Finish AccessibilityInfo implementation
Browse files Browse the repository at this point in the history
Summary:
This PR is based on files ericvicenti gave me. Specifically, he gave me:
  - AccessibilityInfo.android.js
  - AccessibilityInfo.ios.js
  - AccessibilityInfoModule.java

Before this change, only a native iOS implementation of AccessibilityInfo existed. This change includes:
  - A native Android implementation of AccessibilityInfo.
  - JavaScript wrappers for the AccessibilityInfo module for both iOS and Android.
  - UIExplorer changes to illustrate how to use AccessibilityInfo on iOS and Android.
  - Documentation for the AccessibilityInfo APIs.

**Test plan (required)**

Tested the UIExplorer AccessibilityInfo example on iOS and Android with the screen reader both enabled and disabled.

Adam Comella
Microsoft Corp.
Closes #12273

Reviewed By: mkonicek

Differential Revision: D4527224

Pulled By: ericvicenti

fbshipit-source-id: d04638465ccbdbb35ecfc9504daaeb8e33aab57a
  • Loading branch information
Adam Comella authored and facebook-github-bot committed Feb 28, 2017
1 parent 41f1bcc commit 04790f1
Show file tree
Hide file tree
Showing 11 changed files with 411 additions and 0 deletions.
33 changes: 33 additions & 0 deletions Examples/UIExplorer/js/AccessibilityAndroidExample.android.js
Expand Up @@ -25,6 +25,7 @@
var React = require('react');
var ReactNative = require('react-native');
var {
AccessibilityInfo,
StyleSheet,
Text,
View,
Expand All @@ -45,8 +46,34 @@ class AccessibilityAndroidExample extends React.Component {
count: 0,
backgroundImportantForAcc: 0,
forgroundImportantForAcc: 0,
screenReaderEnabled: false,
};

componentDidMount() {
AccessibilityInfo.addEventListener(
'change',
this._handleScreenReaderToggled
);
AccessibilityInfo.fetch().done((isEnabled) => {
this.setState({
screenReaderEnabled: isEnabled
});
});
}

componentWillUnmount() {
AccessibilityInfo.removeEventListener(
'change',
this._handleScreenReaderToggled
);
}

_handleScreenReaderToggled = (isEnabled) => {
this.setState({
screenReaderEnabled: isEnabled,
});
}

_addOne = () => {
this.setState({
count: ++this.state.count,
Expand Down Expand Up @@ -125,6 +152,12 @@ class AccessibilityAndroidExample extends React.Component {
</Text>
</UIExplorerBlock>

<UIExplorerBlock title="Check if the screen reader is enabled">
<Text>
The screen reader is {this.state.screenReaderEnabled ? 'enabled' : 'disabled'}.
</Text>
</UIExplorerBlock>

<UIExplorerBlock title="Overlapping views and importantForAccessibility property">
<View style={styles.container}>
<View
Expand Down
46 changes: 46 additions & 0 deletions Examples/UIExplorer/js/AccessibilityIOSExample.js
Expand Up @@ -26,6 +26,7 @@
var React = require('react');
var ReactNative = require('react-native');
var {
AccessibilityInfo,
Text,
View,
} = ReactNative;
Expand Down Expand Up @@ -64,11 +65,56 @@ class AccessibilityIOSExample extends React.Component {
}
}

class ScreenReaderStatusExample extends React.Component {
state = {
screenReaderEnabled: false,
}

componentDidMount() {
AccessibilityInfo.addEventListener(
'change',
this._handleScreenReaderToggled
);
AccessibilityInfo.fetch().done((isEnabled) => {
this.setState({
screenReaderEnabled: isEnabled
});
});
}

componentWillUnmount() {
AccessibilityInfo.removeEventListener(
'change',
this._handleScreenReaderToggled
);
}

_handleScreenReaderToggled = (isEnabled) => {
this.setState({
screenReaderEnabled: isEnabled,
});
}

render() {
return (
<View>
<Text>
The screen reader is {this.state.screenReaderEnabled ? 'enabled' : 'disabled'}.
</Text>
</View>
);
}
}

exports.title = 'AccessibilityIOS';
exports.description = 'Interface to show iOS\' accessibility samples';
exports.examples = [
{
title: 'Accessibility elements',
render(): React.Element<any> { return <AccessibilityIOSExample />; }
},
{
title: 'Check if the screen reader is enabled',
render(): React.Element<any> { return <ScreenReaderStatusExample />; }
},
];
@@ -0,0 +1,66 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule AccessibilityInfo
* @flow
*/
'use strict';

var NativeModules = require('NativeModules');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');

var RCTAccessibilityInfo = NativeModules.AccessibilityInfo;

var TOUCH_EXPLORATION_EVENT = 'touchExplorationDidChange';

type ChangeEventName = $Enum<{
change: string,
}>;

var _subscriptions = new Map();

var AccessibilityInfo = {

fetch: function(): Promise {
return new Promise((resolve, reject) => {
RCTAccessibilityInfo.isTouchExplorationEnabled(
function(resp) {
resolve(resp);
}
);
});
},

addEventListener: function (
eventName: ChangeEventName,
handler: Function
): void {
var listener = RCTDeviceEventEmitter.addListener(
TOUCH_EXPLORATION_EVENT,
(enabled) => {
handler(enabled);
}
);
_subscriptions.set(handler, listener);
},

removeEventListener: function(
eventName: ChangeEventName,
handler: Function
): void {
var listener = _subscriptions.get(handler);
if (!listener) {
return;
}
listener.remove();
_subscriptions.delete(handler);
},

};

module.exports = AccessibilityInfo;
132 changes: 132 additions & 0 deletions Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js
@@ -0,0 +1,132 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule AccessibilityInfo
* @flow
*/
'use strict';

var NativeModules = require('NativeModules');
var Promise = require('Promise');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');

var AccessibilityManager = NativeModules.AccessibilityManager;

var VOICE_OVER_EVENT = 'voiceOverDidChange';

type ChangeEventName = $Enum<{
change: string,
}>;

var _subscriptions = new Map();

/**
* Sometimes it's useful to know whether or not the device has a screen reader that is currently active. The
* `AccessibilityInfo` API is designed for this purpose. You can use it to query the current state of the
* screen reader as well as to register to be notified when the state of the screen reader changes.
*
* Here's a small example illustrating how to use `AccessibilityInfo`:
*
* ```javascript
* class ScreenReaderStatusExample extends React.Component {
* state = {
* screenReaderEnabled: false,
* }
*
* componentDidMount() {
* AccessibilityInfo.addEventListener(
* 'change',
* this._handleScreenReaderToggled
* );
* AccessibilityInfo.fetch().done((isEnabled) => {
* this.setState({
* screenReaderEnabled: isEnabled
* });
* });
* }
*
* componentWillUnmount() {
* AccessibilityInfo.removeEventListener(
* 'change',
* this._handleScreenReaderToggled
* );
* }
*
* _handleScreenReaderToggled = (isEnabled) => {
* this.setState({
* screenReaderEnabled: isEnabled,
* });
* }
*
* render() {
* return (
* <View>
* <Text>
* The screen reader is {this.state.screenReaderEnabled ? 'enabled' : 'disabled'}.
* </Text>
* </View>
* );
* }
* }
* ```
*/
var AccessibilityInfo = {

/**
* Query whether a screen reader is currently enabled. Returns a promise which
* resolves to a boolean. The result is `true` when a screen reader is enabled
* and `false` otherwise.
*/
fetch: function(): Promise {
return new Promise((resolve, reject) => {
AccessibilityManager.getCurrentVoiceOverState(
resolve,
reject
);
});
},

/**
* Add an event handler. Supported events:
*
* - `change`: Fires when the state of the screen reader changes. The argument
* to the event handler is a boolean. The boolean is `true` when a screen
* reader is enabled and `false` otherwise.
*/
addEventListener: function (
eventName: ChangeEventName,
handler: Function
): Object {
var listener = RCTDeviceEventEmitter.addListener(
VOICE_OVER_EVENT,
handler
);
_subscriptions.set(handler, listener);
return {
remove: AccessibilityInfo.removeEventListener.bind(null, eventName, handler),
};
},

/**
* Remove an event handler.
*/
removeEventListener: function(
eventName: ChangeEventName,
handler: Function
): void {
var listener = _subscriptions.get(handler);
if (!listener) {
return;
}
listener.remove();
_subscriptions.delete(handler);
},

};

module.exports = AccessibilityInfo;
1 change: 1 addition & 0 deletions Libraries/react-native/react-native-implementation.js
Expand Up @@ -28,6 +28,7 @@ if (__DEV__) {
// Export React, plus some native additions.
const ReactNative = {
// Components
get AccessibilityInfo() { return require('AccessibilityInfo'); },
get ActivityIndicator() { return require('ActivityIndicator'); },
get ART() { return require('ReactNativeART'); },
get Button() { return require('Button'); },
Expand Down

0 comments on commit 04790f1

Please sign in to comment.