Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spike/5010-roettger-ScreenReaderResponsiveTransitionHeader #5047

Merged

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import HooksInfo from '../../../../src/components/HooksInfo'

export const exampleString = `const screenReaderEnabled = useIsScreenReaderEnabled()\n
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only changes were fixing the spelling typo.

const setFocus = useCallback(() => {
if (ref.current && screenReaderEnabled) {}
}`

<HooksInfo componentName="useIsScreenReaderEnabled" example={exampleString}/>
5 changes: 3 additions & 2 deletions VAMobile/src/components/Templates/CategoryLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
import React, { FC, useState } from 'react'

import { CrisisLineCta, TextView, TextViewProps, VAIconProps } from 'components'
import { useRouteNavigation, useTheme } from 'utils/hooks'
import { useIsScreenReaderEnabled, useRouteNavigation, useTheme } from 'utils/hooks'
import HeaderBanner, { HeaderBannerProps } from './HeaderBanner'
import VAScrollView, { VAScrollViewProps } from 'components/VAScrollView'

Expand Down Expand Up @@ -34,6 +34,7 @@ export const CategoryLanding: FC<CategoryLandingProps> = ({ title, headerButton,
const fontScale = useWindowDimensions().fontScale
const theme = useTheme()
const navigateTo = useRouteNavigation()
const screenReaderEnabled = useIsScreenReaderEnabled(true)

const [scrollOffset, setScrollOffset] = useState(0)
const [trackScrollOffset, setTrackScrollOffset] = useState(true)
Expand Down Expand Up @@ -99,7 +100,7 @@ export const CategoryLanding: FC<CategoryLandingProps> = ({ title, headerButton,
<VAScrollView scrollEventThrottle={title ? 1 : 0} onScroll={onScroll} {...scrollViewProps}>
<View onLayout={getTransitionHeaderHeight}>
<CrisisLineCta onPress={navigateTo('VeteransCrisisLine')} />
{title ? <TextView {...subtitleProps}>{title}</TextView> : null}
{title && !screenReaderEnabled ? <TextView {...subtitleProps}>{title}</TextView> : null}
</View>
{children}
</VAScrollView>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
import React, { FC, ReactNode, useState } from 'react'

import { TextView, TextViewProps, VAIconProps } from 'components'
import { useTheme } from 'utils/hooks'
import { useIsScreenReaderEnabled, useTheme } from 'utils/hooks'
import HeaderBanner, { HeaderBannerProps } from './HeaderBanner'
import VAScrollView, { VAScrollViewProps } from 'components/VAScrollView'

Expand Down Expand Up @@ -46,6 +46,7 @@ export const ChildTemplate: FC<ChildTemplateProps> = ({ backLabel, backLabelA11y
const insets = useSafeAreaInsets()
const fontScale = useWindowDimensions().fontScale
const theme = useTheme()
const screenReaderEnabled = useIsScreenReaderEnabled(true)

const [scrollOffset, setScrollOffset] = useState(0)
const [trackScrollOffset, setTrackScrollOffset] = useState(true)
Expand Down Expand Up @@ -108,9 +109,7 @@ export const ChildTemplate: FC<ChildTemplateProps> = ({ backLabel, backLabelA11y
transitionHeader(event.nativeEvent.contentOffset.y)
}}
{...scrollViewProps}>
<View onLayout={getTransitionHeaderHeight}>
<TextView {...subtitleProps}>{title}</TextView>
</View>
<View onLayout={getTransitionHeaderHeight}>{!screenReaderEnabled ? <TextView {...subtitleProps}>{title}</TextView> : null}</View>
{children}
</VAScrollView>
{footerContent}
Expand Down
21 changes: 13 additions & 8 deletions VAMobile/src/components/Templates/HeaderBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useFocusEffect } from '@react-navigation/native'
import React, { FC, useEffect, useReducer, useState } from 'react'

import { Box, BoxProps, DescriptiveBackButton, TextView, TextViewProps, VAIcon, VAIconProps } from 'components'
import { useAccessibilityFocus, useTheme } from 'utils/hooks'
import { useAccessibilityFocus, useIsScreenReaderEnabled, useTheme } from 'utils/hooks'
import MenuView, { MenuViewActionsType } from 'components/Menu'

export type HeaderLeftButtonProps = {
Expand Down Expand Up @@ -63,6 +63,7 @@ const HeaderBanner: FC<HeaderBannerProps> = ({ leftButton, title, rightButton, d
const [focusTitle, setFocusTitle] = useAccessibilityFocus<View>()
const focus = leftButton ? 'Left' : title ? 'Title' : 'Right'
useFocusEffect(focus === 'Title' ? setFocusTitle : setFocus)
const screenReaderEnabled = useIsScreenReaderEnabled(true)

const TEXT_CONSTRAINT_THRESHOLD = 30

Expand All @@ -79,6 +80,9 @@ const HeaderBanner: FC<HeaderBannerProps> = ({ leftButton, title, rightButton, d
* Reducer to swap between "VA" and title based on scroll
*/
const titleShowingReducer = (initTitleShowing: boolean) => {
if (screenReaderEnabled) {
return true
}
return transition ? title.scrollOffset >= title.transitionHeaderHeight : initTitleShowing
}

Expand Down Expand Up @@ -123,13 +127,14 @@ const HeaderBanner: FC<HeaderBannerProps> = ({ leftButton, title, rightButton, d
zIndex: 1,
}

const headerDropShadow: ShadowProps = titleShowing
? {
startColor: theme.colors.background.headerDropShadow,
distance: 4,
sides: { start: false, top: false, bottom: true, end: false },
}
: { disabled: true }
const headerDropShadow: ShadowProps =
titleShowing && !screenReaderEnabled
? {
startColor: theme.colors.background.headerDropShadow,
distance: 4,
sides: { start: false, top: false, bottom: true, end: false },
}
: { disabled: true }

const titleBannerProps: BoxProps = {
alignItems: 'center',
Expand Down
50 changes: 39 additions & 11 deletions VAMobile/src/utils/hooks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
import { AccessibilityInfo, ActionSheetIOS, Alert, AlertButton, AppState, Dimensions, Linking, PixelRatio, ScrollView, UIManager, View, findNodeHandle } from 'react-native'
import {
AccessibilityInfo,
ActionSheetIOS,
Alert,
AlertButton,
AppState,
Dimensions,
EmitterSubscription,
Linking,
PixelRatio,
ScrollView,
UIManager,
View,
findNodeHandle,
} from 'react-native'
import { EventArg, useNavigation } from '@react-navigation/native'
import { ImagePickerResponse } from 'react-native-image-picker'
import { MutableRefObject, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react'
Expand Down Expand Up @@ -147,10 +161,10 @@ export function useAccessibilityFocus<T>(): [MutableRefObject<T>, () => void] {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ref: MutableRefObject<any> = useRef<T>(null)
const dispatch = useAppDispatch()
const screanReaderEnabled = useIsScreanReaderEnabled()
const screenReaderEnabled = useIsScreenReaderEnabled()

const setFocus = useCallback(() => {
if (ref.current && screanReaderEnabled) {
if (ref.current && screenReaderEnabled) {
/**
* There is a race condition during transition that causes the accessibility focus
* to intermittently fail to be set https://github.com/facebook/react-native/issues/30097
Expand Down Expand Up @@ -181,32 +195,46 @@ export function useAccessibilityFocus<T>(): [MutableRefObject<T>, () => void] {

return () => clearTimeout(timeOutPageFocus)
}
}, [ref, dispatch, screanReaderEnabled])
}, [ref, dispatch, screenReaderEnabled])

return [ref, setFocus]
}

/**
* Hook to check if the screan reader is enabled
* Hook to check if the screen reader is enabled
*
* withListener - True to add a listener to live update screen reader status, default false
* @returns boolean if the screen reader is on
*/
export function useIsScreanReaderEnabled(): boolean {
const [screanReaderEnabled, setScreanReaderEnabled] = useState(false)
export function useIsScreenReaderEnabled(withListener = false): boolean {
const [screenReaderEnabled, setScreenReaderEnabled] = useState(false)

useEffect(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple notes:

  1. A lot of updates in this file are just fixing the spelling error of "screan" to "screen"
  2. I added the withListener stuff making this potentially needlessly complicated to preserve the two existing use cases behaving exactly as before, I am not sure if it'd matter for those uses

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the details, but is it worth adding a todo for future cleanup or investigation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have created an upkeep ticket #5057 to track cleaning it up at a later date.

let isMounted = true
let screenReaderChangedSubscription: EmitterSubscription

if (withListener) {
screenReaderChangedSubscription = AccessibilityInfo.addEventListener('screenReaderChanged', (isScreenReaderEnabled) => {
if (isMounted) {
setScreenReaderEnabled(isScreenReaderEnabled)
}
})
}
AccessibilityInfo.isScreenReaderEnabled().then((isScreenReaderEnabled) => {
if (isMounted) {
setScreanReaderEnabled(isScreenReaderEnabled)
setScreenReaderEnabled(isScreenReaderEnabled)
}
})

return () => {
isMounted = false
if (withListener) {
screenReaderChangedSubscription.remove()
}
}
}, [screanReaderEnabled])
}, [screenReaderEnabled, withListener])

return screanReaderEnabled
return screenReaderEnabled
}

/**
Expand Down Expand Up @@ -301,7 +329,7 @@ export function useAutoScrollToElement(): [MutableRefObject<ScrollView>, Mutable
const scrollRef = useRef() as MutableRefObject<ScrollView>
const [viewRef, setFocus] = useAccessibilityFocus<View>()
const [shouldFocus, setShouldFocus] = useState(true)
const screenReaderEnabled = useIsScreanReaderEnabled()
const screenReaderEnabled = useIsScreenReaderEnabled()

const scrollToElement = useCallback(
(offset?: number) => {
Expand Down