/* eslint-disable react-native/no-inline-styles */ import * as React from 'react'; import { AccessibilityChangeEventName, AccessibilityInfo, AppState, AppStateStatus, NativeEventSubscription, Platform, Pressable, Text, View, } from 'react-native'; import { RED } from '../internal/error.style'; import { areAnimationsEnabled } from '../modules/AMAAnimationsStatusModule'; type AMAProviderProps = { children: React.ReactNode; }; type AccessibilityEvents = Exclude; type AccessibilityInfoEvents = { [key in AccessibilityEvents]: Exclude< keyof AMAContextValue, 'reactNavigationScreenOptions' | 'trackError' | 'removeError' >; }; const eventsMapping: AccessibilityInfoEvents = { reduceTransparencyChanged: 'isReduceTransparencyEnabled', reduceMotionChanged: 'isReduceMotionEnabled', grayscaleChanged: 'isGrayscaleEnabled', boldTextChanged: 'isBoldTextEnabled', invertColorsChanged: 'isInvertColorsEnabled', screenReaderChanged: 'isScreenReaderEnabled', }; export const AMAProvider: React.FC = ({ children }) => { const [values, setValues] = React.useState(DEFAULT_VALUES); const appState = React.useRef('inactive'); const handleAccessibilityInfoChanged = ( key: Exclude< keyof AMAContextValue, 'reactNavigationScreenOptions' | 'trackError' | 'removeError' >, ) => { return (newValue: boolean) => { setValues(oldValues => { const newValues = { ...oldValues }; newValues[key] = newValue; if (key === 'isReduceMotionEnabled') { newValues.reactNavigationScreenOptions.animationEnabled = !newValue; newValues.reactNavigationScreenOptions.animation = newValue ? 'fade' : 'default'; } return { ...oldValues, ...newValues, }; }); }; }; const checkAndroidAnimationStatus = (nextAppState: AppStateStatus) => { if ( appState.current.match(/inactive|background/) && nextAppState === 'active' ) { areAnimationsEnabled().then(enabled => { handleAccessibilityInfoChanged('isReduceMotionEnabled')(!enabled); }); } appState.current = nextAppState; }; React.useEffect(() => { const allInitPromises: Promise[] = []; const subscriptions: NativeEventSubscription[] = Object.entries( eventsMapping, ).map(([eventName, contextKey]) => { allInitPromises.push(AccessibilityInfo[contextKey]()); return AccessibilityInfo.addEventListener( eventName as AccessibilityEvents, handleAccessibilityInfoChanged(contextKey), ); }); if (Platform.OS === 'android') { subscriptions.push( AppState.addEventListener('change', checkAndroidAnimationStatus), ); checkAndroidAnimationStatus('active'); } Promise.all(allInitPromises).then(promisesValues => { const newValues = Object.values(eventsMapping).reduce( (list, key, index) => { list[key] = promisesValues[index] as boolean; return list; }, {} as AMAContextValue, ); setValues(oldValues => { return { ...oldValues, ...newValues, }; }); }); return () => { subscriptions.forEach(subscription => subscription?.remove()); AppState.removeEventListener?.('change', checkAndroidAnimationStatus); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const [failedItems, setFailedItems] = React.useState([]); const trackError = __DEV__ ? (id: string) => { if (failedItems.includes(id)) { return; } setFailedItems(items => [...items, id]); AccessibilityInfo.announceForAccessibility( "One or more component didn't pass the accessibility check, please check the console for more info", ); } : undefined; const removeError = __DEV__ ? (id: string) => { setFailedItems(items => { const index = items.indexOf(id); items.splice(index); return [...items]; }); } : undefined; return __DEV__ ? ( <> {children} { // @ts-ignore failedItems.length > 0 ? : null } ) : ( {children} ); }; export type AMAContextValue = { isBoldTextEnabled: boolean; isScreenReaderEnabled: boolean; isGrayscaleEnabled: boolean; isInvertColorsEnabled: boolean; isReduceMotionEnabled: boolean; isReduceTransparencyEnabled: boolean; reactNavigationScreenOptions: { animationEnabled: boolean; animation: 'default' | 'fade'; }; trackError: (id: string) => void; removeError: (id: string) => void; }; const DEFAULT_VALUES: AMAContextValue = { isReduceTransparencyEnabled: false, isBoldTextEnabled: false, isGrayscaleEnabled: false, isInvertColorsEnabled: false, isReduceMotionEnabled: false, isScreenReaderEnabled: false, reactNavigationScreenOptions: { animationEnabled: true, animation: 'default', }, trackError: (_: string) => {}, removeError: (_: string) => {}, }; const AMAError = __DEV__ ? () => { return ( AMA: One or more component didn't pass the accessibility check. Please check the console for more info... ); } : null; const AMAContext = React.createContext(DEFAULT_VALUES); export const useAMAContext = () => React.useContext(AMAContext);