diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js index c7d4251bba..bc45973950 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js @@ -20,11 +20,17 @@ import NativeAccessibilityManager from './NativeAccessibilityManager'; const warning = require('fbjs/lib/warning'); const CHANGE_EVENT_NAME = { + invertColorsChanged: 'invertColorsChanged', + reduceMotionChanged: 'reduceMotionChanged', + reduceTransparencyChanged: 'reduceTransparencyChanged', screenReaderChanged: 'screenReaderChanged', }; type ChangeEventName = $Keys<{ change: string, + invertColorsChanged: string, + reduceMotionChanged: string, + reduceTransparencyChanged: string, screenReaderChanged: string, }>; @@ -45,24 +51,60 @@ const AccessibilityInfo = { }, /** - * iOS only + * Query whether inverted colors are currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when invert color is enabled and `false` otherwise. + * + * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#isInvertColorsEnabled */ isInvertColorsEnabled: function(): Promise { - return Promise.resolve(false); + return new Promise((resolve, reject) => { + if (NativeAccessibilityManager) { + NativeAccessibilityManager.getCurrentInvertColorsState(resolve, reject); + } else { + reject(reject); + } + }); }, /** - * Android and iOS only + * Query whether reduced motion is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a reduce motion is enabled and `false` otherwise. + * + * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#isReduceMotionEnabled */ isReduceMotionEnabled: function(): Promise { - return Promise.resolve(false); + return new Promise((resolve, reject) => { + if (NativeAccessibilityManager) { + NativeAccessibilityManager.getCurrentReduceMotionState(resolve, reject); + } else { + reject(reject); + } + }); }, /** - * iOS only + * Query whether reduced transparency is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a reduce transparency is enabled and `false` otherwise. + * + * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#isReduceTransparencyEnabled */ isReduceTransparencyEnabled: function(): Promise { - return Promise.resolve(false); + return new Promise((resolve, reject) => { + if (NativeAccessibilityManager) { + NativeAccessibilityManager.getCurrentReduceTransparencyState( + resolve, + reject, + ); + } else { + reject(reject); + } + }); }, /** diff --git a/RNTester/js/examples/Accessibility/AccessibilityExample.js b/RNTester/js/examples/Accessibility/AccessibilityExample.js index a00d7a77c3..c98870e38e 100644 --- a/RNTester/js/examples/Accessibility/AccessibilityExample.js +++ b/RNTester/js/examples/Accessibility/AccessibilityExample.js @@ -553,7 +553,101 @@ class ScreenReaderStatusExample extends React.Component<{}> { ); } } +// [TODO(OSS Candidate ISS#2710739) +class DisplayOptionsStatusExample extends React.Component<{}> { + state = {}; + componentDidMount() { + AccessibilityInfo.addEventListener( + 'invertColorsChanged', + this._handleInvertColorsToggled, + ); + AccessibilityInfo.isInvertColorsEnabled().done(isEnabled => { + this.setState({ + invertColorsEnabled: isEnabled, + }); + }); + + AccessibilityInfo.addEventListener( + 'reduceMotionChanged', + this._handleReduceMotionToggled, + ); + AccessibilityInfo.isReduceMotionEnabled().done(isEnabled => { + this.setState({ + reduceMotionEnabled: isEnabled, + }); + }); + + AccessibilityInfo.addEventListener( + 'reduceTransparencyChanged', + this._handleReduceTransparencyToggled, + ); + AccessibilityInfo.isReduceTransparencyEnabled().done(isEnabled => { + this.setState({ + reduceTransparencyEnabled: isEnabled, + }); + }); + } + + componentWillUnmount() { + AccessibilityInfo.removeEventListener( + 'invertColorsChanged', + this._handleInvertColorsToggled, + ); + AccessibilityInfo.removeEventListener( + 'reduceMotionChanged', + this._handleReduceMotionToggled, + ); + AccessibilityInfo.removeEventListener( + 'reduceTransparencyChanged', + this._handleReduceTransparencyToggled, + ); + } + + _handleInvertColorsToggled = isEnabled => { + this.setState({ + invertColorsEnabled: isEnabled, + }); + }; + + _handleReduceMotionToggled = isEnabled => { + this.setState({ + reduceMotionEnabled: isEnabled, + }); + }; + + _handleReduceTransparencyToggled = isEnabled => { + this.setState({ + reduceTransparencyEnabled: isEnabled, + }); + }; + + render() { + return ( + + + + Invert colors is{' '} + {this.state.invertColorsEnabled ? 'enabled' : 'disabled'}. + + + + + Reduce motion is{' '} + {this.state.reduceMotionEnabled ? 'enabled' : 'disabled'}. + + + + + Reduce transparency is{' '} + {this.state.reduceTransparencyEnabled ? 'enabled' : 'disabled'}. + + + + ); + } +} +// ]TODO(OSS Candidate ISS#2710739) class AnnounceForAccessibility extends React.Component<{}> { _handleOnPress = () => AccessibilityInfo.announceForAccessibility('Announcement Test'); @@ -617,6 +711,14 @@ exports.examples = [ return ; }, }, + // [TODO(OSS Candidate ISS#2710739) + { + title: 'Check if the display options are enabled', + render(): React.Element { + return ; + }, + }, + // ]TODO(OSS Candidate ISS#2710739) { title: 'Check if the screen reader announces', render(): React.Element { diff --git a/React/Modules/MacOS/RCTAccessibilityManager.m b/React/Modules/MacOS/RCTAccessibilityManager.m index d7df284c4c..0302e3678c 100644 --- a/React/Modules/MacOS/RCTAccessibilityManager.m +++ b/React/Modules/MacOS/RCTAccessibilityManager.m @@ -35,7 +35,15 @@ static void *AccessibilityVoiceOverChangeContext = &AccessibilityVoiceOverChange forKeyPath:@"voiceOverEnabled" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:AccessibilityVoiceOverChangeContext]; + [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self + selector:@selector(accessibilityDisplayOptionsChange:) + name:NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification + object:nil]; + _isInvertColorsEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldInvertColors]; + _isReduceMotionEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion]; + _isReduceTransparencyEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceTransparency]; _isVoiceOverEnabled = [[NSWorkspace sharedWorkspace] isVoiceOverEnabled]; + } return self; } @@ -45,6 +53,25 @@ static void *AccessibilityVoiceOverChangeContext = &AccessibilityVoiceOverChange [[NSWorkspace sharedWorkspace] removeObserver:self forKeyPath:@"voiceOverEnabled" context:AccessibilityVoiceOverChangeContext]; + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; +} + +RCT_EXPORT_METHOD(getCurrentInvertColorsState:(RCTResponseSenderBlock)callback + error:(__unused RCTResponseSenderBlock)error) +{ + callback(@[@(_isInvertColorsEnabled)]); +} + +RCT_EXPORT_METHOD(getCurrentReduceMotionState:(RCTResponseSenderBlock)callback + error:(__unused RCTResponseSenderBlock)error) +{ + callback(@[@(_isReduceMotionEnabled)]); +} + +RCT_EXPORT_METHOD(getCurrentReduceTransparencyState:(RCTResponseSenderBlock)callback + error:(__unused RCTResponseSenderBlock)error) +{ + callback(@[@(_isReduceTransparencyEnabled)]); } RCT_EXPORT_METHOD(getCurrentVoiceOverState:(RCTResponseSenderBlock)callback @@ -84,4 +111,26 @@ RCT_EXPORT_METHOD(setAccessibilityFocus:(nonnull NSNumber *)reactTag) } } +- (void)accessibilityDisplayOptionsChange:(NSNotification *)notification +{ + BOOL newInvertColorsEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldInvertColors]; + BOOL newReduceMotionEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion]; + BOOL newReduceTransparencyEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceTransparency]; + if (_isInvertColorsEnabled != newInvertColorsEnabled) { + _isInvertColorsEnabled = newInvertColorsEnabled; + [_bridge.eventDispatcher sendDeviceEventWithName:@"invertColorsChanged" + body:@(_isInvertColorsEnabled)]; + } + if (_isReduceMotionEnabled != newReduceMotionEnabled) { + _isReduceMotionEnabled = newReduceMotionEnabled; + [_bridge.eventDispatcher sendDeviceEventWithName:@"reduceMotionChanged" + body:@(_isReduceMotionEnabled)]; + } + if (_isReduceTransparencyEnabled != newReduceTransparencyEnabled) { + _isReduceTransparencyEnabled = newReduceTransparencyEnabled; + [_bridge.eventDispatcher sendDeviceEventWithName:@"reduceTransparencyChanged" + body:@(_isReduceTransparencyEnabled)]; + } +} + @end