From 40de0495b96f6ef611a8344f8ca61dc6f8f6e020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Estev=C3=A3o=20Lucas?= Date: Fri, 15 Mar 2019 11:17:42 -0700 Subject: [PATCH] - add more iOS flags into AccessibilityInfo (#23913) Summary: As a follow-up to this other PR #23839, it adds support for other, iOS only, flags into `AccessibilityInfo`. It adds these other 4 methods: * `isBoldTextEnabled()` * `isGrayscaleEnabled()` * `isInvertColorsEnabled()` * `isReduceTransparencyEnabled()` P.S: Android implementation for those methods just return `false` (with `Promise.resolve(false)`) And the corresponding event listeners: * `boldTextChanged` * `grayscaleChanged`, * `invertColorsChanged`, * `reduceTransparencyChanged` Pull Request resolved: https://github.com/facebook/react-native/pull/23913 Differential Revision: D14482214 Pulled By: cpojer fbshipit-source-id: b97725fd12706957d4dad880a97e6b0993738272 --- .../AccessibilityInfo.android.js | 28 ++++ .../AccessibilityInfo.ios.js | 107 ++++++++++--- React/Modules/RCTAccessibilityManager.h | 4 + React/Modules/RCTAccessibilityManager.m | 142 +++++++++++++++--- 4 files changed, 243 insertions(+), 38 deletions(-) diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js index 455ba91263704d..9ff5fe0a82fbc1 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js @@ -38,12 +38,40 @@ const _subscriptions = new Map(); */ const AccessibilityInfo = { + /** + * iOS only + */ + isBoldTextEnabled: function(): Promise { + return Promise.resolve(false); + }, + + /** + * iOS only + */ + isGrayscaleEnabled: function(): Promise { + return Promise.resolve(false); + }, + + /** + * iOS only + */ + isInvertColorsEnabled: function(): Promise { + return Promise.resolve(false); + }, + isReduceMotionEnabled: function(): Promise { return new Promise((resolve, reject) => { RCTAccessibilityInfo.isReduceMotionEnabled(resolve); }); }, + /** + * iOS only + */ + isReduceTransparencyEnabled: function(): Promise { + return Promise.resolve(false); + }, + isScreenReaderEnabled: function(): Promise { return new Promise((resolve, reject) => { RCTAccessibilityInfo.isTouchExplorationEnabled(resolve); diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js index 51ebadbbc045ce..002ae96785da10 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js @@ -16,14 +16,24 @@ const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); const AccessibilityManager = NativeModules.AccessibilityManager; -const ANNOUNCEMENT_DID_FINISH_EVENT = 'announcementDidFinish'; -const REDUCE_MOTION_EVENT = 'reduceMotionDidChange'; -const VOICE_OVER_EVENT = 'voiceOverDidChange'; +const CHANGE_EVENT_NAME = { + announcementFinished: 'announcementFinished', + boldTextChanged: 'boldTextChanged', + grayscaleChanged: 'grayscaleChanged', + invertColorsChanged: 'invertColorsChanged', + reduceMotionChanged: 'reduceMotionChanged', + reduceTransparencyChanged: 'reduceTransparencyChanged', + screenReaderChanged: 'screenReaderChanged', +}; type ChangeEventName = $Enum<{ announcementFinished: string, + boldTextChanged: string, change: string, + grayscaleChanged: string, + invertColorsChanged: string, reduceMotionChanged: string, + reduceTransparencyChanged: string, screenReaderChanged: string, }>; @@ -40,16 +50,72 @@ const _subscriptions = new Map(); */ const AccessibilityInfo = { /** - * Query whether a reduce motion is currently enabled. + * Query whether bold text is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when bold text is enabled and `false` otherwise. + * + * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#isBoldTextEnabled + */ + isBoldTextEnabled: function(): Promise { + return new Promise((resolve, reject) => { + AccessibilityManager.getCurrentBoldTextState(resolve, reject); + }); + }, + + /** + * Query whether grayscale is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when grayscale is enabled and `false` otherwise. + * + * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#isGrayscaleEnabled + */ + isGrayscaleEnabled: function(): Promise { + return new Promise((resolve, reject) => { + AccessibilityManager.getCurrentGrayscaleState(resolve, reject); + }); + }, + + /** + * 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 new Promise((resolve, reject) => { + AccessibilityManager.getCurrentInvertColorsState(resolve, reject); + }); + }, + + /** + * Query whether reduced motion is currently enabled. * * Returns a promise which resolves to a boolean. - * The result is `true` when a screen reader is enabledand `false` otherwise. + * 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 { + isReduceMotionEnabled: function(): Promise { + return new Promise((resolve, reject) => { + AccessibilityManager.getCurrentReduceMotionState(resolve, reject); + }); + }, + + /** + * 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 new Promise((resolve, reject) => { - AccessibilityManager.getReduceMotionState(resolve, reject); + AccessibilityManager.getCurrentReduceTransparencyState(resolve, reject); }); }, @@ -61,7 +127,7 @@ const AccessibilityInfo = { * * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#isScreenReaderEnabled */ - isScreenReaderEnabled: function(): Promise { + isScreenReaderEnabled: function(): Promise { return new Promise((resolve, reject) => { AccessibilityManager.getCurrentVoiceOverState(resolve, reject); }); @@ -79,10 +145,22 @@ const AccessibilityInfo = { /** * Add an event handler. Supported events: * + * - `boldTextChanged`: iOS-only event. Fires when the state of the bold text toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a bold text + * is enabled and `false` otherwise. + * - `grayscaleChanged`: iOS-only event. Fires when the state of the gray scale toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a gray scale + * is enabled and `false` otherwise. + * - `invertColorsChanged`: iOS-only event. Fires when the state of the invert colors toggle + * changes. The argument to the event handler is a boolean. The boolean is `true` when a invert + * colors is enabled and `false` otherwise. * - `reduceMotionChanged`: Fires when the state of the reduce motion toggle changes. * The argument to the event handler is a boolean. The boolean is `true` when a reduce * motion is enabled (or when "Transition Animation Scale" in "Developer options" is * "Animation off") and `false` otherwise. + * - `reduceTransparencyChanged`: iOS-only event. Fires when the state of the reduce transparency + * toggle changes. The argument to the event handler is a boolean. The boolean is `true` + * when a reduce transparency is enabled and `false` otherwise. * - `screenReaderChanged`: 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. @@ -101,18 +179,13 @@ const AccessibilityInfo = { ): Object { let listener; - if (eventName === 'change' || eventName === 'screenReaderChanged') { - listener = RCTDeviceEventEmitter.addListener(VOICE_OVER_EVENT, handler); - } else if (eventName === 'reduceMotionChanged') { - listener = RCTDeviceEventEmitter.addListener( - REDUCE_MOTION_EVENT, - handler, - ); - } else if (eventName === 'announcementFinished') { + if (eventName === 'change') { listener = RCTDeviceEventEmitter.addListener( - ANNOUNCEMENT_DID_FINISH_EVENT, + CHANGE_EVENT_NAME.screenReaderChanged, handler, ); + } else if (CHANGE_EVENT_NAME[eventName]) { + listener = RCTDeviceEventEmitter.addListener(eventName, handler); } _subscriptions.set(handler, listener); diff --git a/React/Modules/RCTAccessibilityManager.h b/React/Modules/RCTAccessibilityManager.h index c0d286f2088b93..5f929883cb0c44 100644 --- a/React/Modules/RCTAccessibilityManager.h +++ b/React/Modules/RCTAccessibilityManager.h @@ -19,7 +19,11 @@ extern NSString *const RCTAccessibilityManagerDidUpdateMultiplierNotification; / /// map from UIKit categories to multipliers @property (nonatomic, copy) NSDictionary *multipliers; +@property (nonatomic, assign) BOOL isBoldTextEnabled; +@property (nonatomic, assign) BOOL isGrayscaleEnabled; +@property (nonatomic, assign) BOOL isInvertColorsEnabled; @property (nonatomic, assign) BOOL isReduceMotionEnabled; +@property (nonatomic, assign) BOOL isReduceTransparencyEnabled; @property (nonatomic, assign) BOOL isVoiceOverEnabled; @end diff --git a/React/Modules/RCTAccessibilityManager.m b/React/Modules/RCTAccessibilityManager.m index a1c7746ce171d7..7283947731e6f8 100644 --- a/React/Modules/RCTAccessibilityManager.m +++ b/React/Modules/RCTAccessibilityManager.m @@ -67,13 +67,23 @@ - (instancetype)init object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(didReceiveNewVoiceOverStatus:) - name:UIAccessibilityVoiceOverStatusChanged + selector:@selector(accessibilityAnnouncementDidFinish:) + name:UIAccessibilityAnnouncementDidFinishNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(accessibilityAnnouncementDidFinish:) - name:UIAccessibilityAnnouncementDidFinishNotification + selector:@selector(boldTextStatusDidChange:) + name:UIAccessibilityBoldTextStatusDidChangeNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(grayscaleStatusDidChange:) + name:UIAccessibilityGrayscaleStatusDidChangeNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(invertColorsStatusDidChange:) + name:UIAccessibilityInvertColorsStatusDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self @@ -81,8 +91,22 @@ - (instancetype)init name:UIAccessibilityReduceMotionStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reduceTransparencyStatusDidChange:) + name:UIAccessibilityReduceTransparencyStatusDidChangeNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(voiceVoiceOverStatusDidChange:) + name:UIAccessibilityVoiceOverStatusChanged + object:nil]; + self.contentSizeCategory = RCTSharedApplication().preferredContentSizeCategory; + _isBoldTextEnabled = UIAccessibilityIsBoldTextEnabled(); + _isGrayscaleEnabled = UIAccessibilityIsGrayscaleEnabled(); + _isInvertColorsEnabled = UIAccessibilityIsInvertColorsEnabled(); _isReduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled(); + _isReduceTransparencyEnabled = UIAccessibilityIsReduceTransparencyEnabled(); _isVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning(); } return self; @@ -98,31 +122,57 @@ - (void)didReceiveNewContentSizeCategory:(NSNotification *)note self.contentSizeCategory = note.userInfo[UIContentSizeCategoryNewValueKey]; } -- (void)didReceiveNewVoiceOverStatus:(__unused NSNotification *)notification +- (void)accessibilityAnnouncementDidFinish:(__unused NSNotification *)notification { - BOOL newIsVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning(); - if (_isVoiceOverEnabled != newIsVoiceOverEnabled) { - _isVoiceOverEnabled = newIsVoiceOverEnabled; + NSDictionary *userInfo = notification.userInfo; + // Response dictionary to populate the event with. + NSDictionary *response = @{@"announcement": userInfo[UIAccessibilityAnnouncementKeyStringValue], + @"success": userInfo[UIAccessibilityAnnouncementKeyWasSuccessful]}; + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_bridge.eventDispatcher sendDeviceEventWithName:@"voiceOverDidChange" - body:@(_isVoiceOverEnabled)]; + [_bridge.eventDispatcher sendDeviceEventWithName:@"announcementFinished" + body:response]; +#pragma clang diagnostic pop +} + +- (void)boldTextStatusDidChange:(__unused NSNotification *)notification +{ + BOOL newBoldTextEnabled = UIAccessibilityIsBoldTextEnabled(); + if (_isBoldTextEnabled != newBoldTextEnabled) { + _isBoldTextEnabled = newBoldTextEnabled; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [_bridge.eventDispatcher sendDeviceEventWithName:@"boldTextChanged" + body:@(_isBoldTextEnabled)]; #pragma clang diagnostic pop } } -- (void)accessibilityAnnouncementDidFinish:(__unused NSNotification *)notification +- (void)grayscaleStatusDidChange:(__unused NSNotification *)notification { - NSDictionary *userInfo = notification.userInfo; - // Response dictionary to populate the event with. - NSDictionary *response = @{@"announcement": userInfo[UIAccessibilityAnnouncementKeyStringValue], - @"success": userInfo[UIAccessibilityAnnouncementKeyWasSuccessful]}; + BOOL newGrayscaleEnabled = UIAccessibilityIsGrayscaleEnabled(); + if (_isGrayscaleEnabled != newGrayscaleEnabled) { + _isGrayscaleEnabled = newGrayscaleEnabled; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [_bridge.eventDispatcher sendDeviceEventWithName:@"grayscaleChanged" + body:@(_isGrayscaleEnabled)]; +#pragma clang diagnostic pop + } +} +- (void)invertColorsStatusDidChange:(__unused NSNotification *)notification +{ + BOOL newInvertColorsEnabled = UIAccessibilityIsInvertColorsEnabled(); + if (_isInvertColorsEnabled != newInvertColorsEnabled) { + _isInvertColorsEnabled = newInvertColorsEnabled; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_bridge.eventDispatcher sendDeviceEventWithName:@"announcementDidFinish" - body:response]; + [_bridge.eventDispatcher sendDeviceEventWithName:@"invertColorsChanged" + body:@(_isInvertColorsEnabled)]; #pragma clang diagnostic pop + } } - (void)reduceMotionStatusDidChange:(__unused NSNotification *)notification @@ -132,12 +182,38 @@ - (void)reduceMotionStatusDidChange:(__unused NSNotification *)notification _isReduceMotionEnabled = newReduceMotionEnabled; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_bridge.eventDispatcher sendDeviceEventWithName:@"reduceMotionDidChange" + [_bridge.eventDispatcher sendDeviceEventWithName:@"reduceMotionChanged" body:@(_isReduceMotionEnabled)]; #pragma clang diagnostic pop } } +- (void)reduceTransparencyStatusDidChange:(__unused NSNotification *)notification +{ + BOOL newReduceTransparencyEnabled = UIAccessibilityIsReduceTransparencyEnabled(); + if (_isReduceTransparencyEnabled != newReduceTransparencyEnabled) { + _isReduceTransparencyEnabled = newReduceTransparencyEnabled; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [_bridge.eventDispatcher sendDeviceEventWithName:@"reduceTransparencyChanged" + body:@(_isReduceTransparencyEnabled)]; +#pragma clang diagnostic pop + } +} + +- (void)voiceVoiceOverStatusDidChange:(__unused NSNotification *)notification +{ + BOOL newIsVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning(); + if (_isVoiceOverEnabled != newIsVoiceOverEnabled) { + _isVoiceOverEnabled = newIsVoiceOverEnabled; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [_bridge.eventDispatcher sendDeviceEventWithName:@"screenReaderChanged" + body:@(_isVoiceOverEnabled)]; +#pragma clang diagnostic pop + } +} + - (void)setContentSizeCategory:(NSString *)contentSizeCategory { if (_contentSizeCategory != contentSizeCategory) { @@ -220,18 +296,42 @@ - (void)setMultipliers:(NSDictionary *)multipliers } } -RCT_EXPORT_METHOD(getCurrentVoiceOverState:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(getCurrentBoldTextState:(RCTResponseSenderBlock)callback error:(__unused RCTResponseSenderBlock)error) { - callback(@[@(_isVoiceOverEnabled)]); + callback(@[@(_isBoldTextEnabled)]); +} + +RCT_EXPORT_METHOD(getCurrentGrayscaleState:(RCTResponseSenderBlock)callback + error:(__unused RCTResponseSenderBlock)error) +{ + callback(@[@(_isGrayscaleEnabled)]); +} + +RCT_EXPORT_METHOD(getCurrentInvertColorsState:(RCTResponseSenderBlock)callback + error:(__unused RCTResponseSenderBlock)error) +{ + callback(@[@(_isInvertColorsEnabled)]); } -RCT_EXPORT_METHOD(getReduceMotionState:(RCTResponseSenderBlock)callback +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 + error:(__unused RCTResponseSenderBlock)error) +{ + callback(@[@(_isVoiceOverEnabled)]); +} + @end @implementation RCTBridge (RCTAccessibilityManager)