Some immersive video a11y fixes (#7528)

* Add playing/paused handling to tap area

* Show text and remove expand button if screen reader enabled

* Clarify actions

* DRY
This commit is contained in:
Eric Bailey
2025-01-21 12:05:46 -06:00
committed by GitHub
parent 76885f8592
commit bdda3020f6
+33 -14
View File
@@ -21,6 +21,7 @@ import {
useSafeAreaFrame, useSafeAreaFrame,
useSafeAreaInsets, useSafeAreaInsets,
} from 'react-native-safe-area-context' } from 'react-native-safe-area-context'
import {useEvent} from 'expo'
import {useEventListener} from 'expo' import {useEventListener} from 'expo'
import {Image, ImageStyle} from 'expo-image' import {Image, ImageStyle} from 'expo-image'
import {LinearGradient} from 'expo-linear-gradient' import {LinearGradient} from 'expo-linear-gradient'
@@ -52,6 +53,7 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {isAndroid} from '#/platform/detection' import {isAndroid} from '#/platform/detection'
import {useA11y} from '#/state/a11y'
import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow' import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import { import {
@@ -140,7 +142,7 @@ export function VideoFeed({}: NativeStackScreenProps<
<View <View
style={[ style={[
a.absolute, a.absolute,
a.z_30, a.z_50,
{top: 0, left: 0, right: 0, paddingTop: top}, {top: 0, left: 0, right: 0, paddingTop: top},
]}> ]}>
<Header sourceContext={params} /> <Header sourceContext={params} />
@@ -692,6 +694,7 @@ function Overlay({
text: record?.text || '', text: record?.text || '',
facets: record?.facets, facets: record?.facets,
}) })
const handle = sanitizeHandle(post.author.handle, '@')
const animatedStyle = useAnimatedStyle(() => ({ const animatedStyle = useAnimatedStyle(() => ({
opacity: 1 - seekingAnimationSV.get(), opacity: 1 - seekingAnimationSV.get(),
@@ -731,11 +734,13 @@ function Overlay({
<Hider.Content> <Hider.Content>
<View style={[a.absolute, a.inset_0, a.z_20]}> <View style={[a.absolute, a.inset_0, a.z_20]}>
<View style={[a.flex_1]}> <View style={[a.flex_1]}>
<PlayPauseTapArea {player && (
player={player} <PlayPauseTapArea
post={post} player={player}
feedContext={feedContext} post={post}
/> feedContext={feedContext}
/>
)}
</View> </View>
<LinearGradient <LinearGradient
@@ -776,7 +781,7 @@ function Overlay({
<Text <Text
style={[a.text_sm, t.atoms.text_contrast_high]} style={[a.text_sm, t.atoms.text_contrast_high]}
numberOfLines={1}> numberOfLines={1}>
{sanitizeHandle(post.author.handle, '@')} {handle}
</Text> </Text>
</View> </View>
</Link> </Link>
@@ -785,8 +790,8 @@ function Overlay({
<Button <Button
label={ label={
profile.viewer?.following profile.viewer?.following
? _(msg`Following`) ? _(msg`Following ${handle}`)
: _(msg`Follow`) : _(msg`Follow ${handle}`)
} }
accessibilityHint={ accessibilityHint={
profile.viewer?.following ? _(msg`Unfollow user`) : '' profile.viewer?.following ? _(msg`Unfollow user`) : ''
@@ -879,6 +884,7 @@ function ExpandableRichTextView({
const [constrained, setConstrained] = useState(false) const [constrained, setConstrained] = useState(false)
const [contentHeight, setContentHeight] = useState(0) const [contentHeight, setContentHeight] = useState(0)
const {_} = useLingui() const {_} = useLingui()
const {screenReaderEnabled} = useA11y()
if (expanded && !hasBeenExpanded) { if (expanded && !hasBeenExpanded) {
setHasBeenExpanded(true) setHasBeenExpanded(true)
@@ -907,14 +913,16 @@ function ExpandableRichTextView({
style={[a.text_sm, a.flex_1, a.leading_normal]} style={[a.text_sm, a.flex_1, a.leading_normal]}
authorHandle={authorHandle} authorHandle={authorHandle}
enableTags enableTags
numberOfLines={expanded ? undefined : constrained ? 2 : 2} numberOfLines={
expanded || screenReaderEnabled ? undefined : constrained ? 2 : 2
}
onTextLayout={evt => { onTextLayout={evt => {
if (!constrained && evt.nativeEvent.lines.length > 1) { if (!constrained && evt.nativeEvent.lines.length > 1) {
setConstrained(true) setConstrained(true)
} }
}} }}
/> />
{constrained && ( {constrained && !screenReaderEnabled && (
<Pressable <Pressable
accessibilityHint={_(msg`Tap to expand or collapse post text.`)} accessibilityHint={_(msg`Tap to expand or collapse post text.`)}
accessibilityLabel={expanded ? _(msg`Read less`) : _(msg`Read more`)} accessibilityLabel={expanded ? _(msg`Read less`) : _(msg`Read more`)}
@@ -971,7 +979,7 @@ function PlayPauseTapArea({
post, post,
feedContext, feedContext,
}: { }: {
player?: VideoPlayer player: VideoPlayer
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<AppBskyFeedDefs.PostView>
feedContext: string | undefined feedContext: string | undefined
}) { }) {
@@ -980,6 +988,9 @@ function PlayPauseTapArea({
const playHaptic = useHaptics() const playHaptic = useHaptics()
const [queueLike] = usePostLikeMutationQueue(post, 'ImmersiveVideo') const [queueLike] = usePostLikeMutationQueue(post, 'ImmersiveVideo')
const {sendInteraction} = useFeedFeedbackContext() const {sendInteraction} = useFeedFeedbackContext()
const {isPlaying} = useEvent(player, 'playingChange', {
isPlaying: player.playing,
})
const togglePlayPause = () => { const togglePlayPause = () => {
if (!player) return if (!player) return
@@ -1010,10 +1021,18 @@ function PlayPauseTapArea({
return ( return (
<Button <Button
disabled={!player} disabled={!player}
label={_(`Tap to play or pause the video`)} aria-valuetext={
isPlaying ? _(msg`Video is playing`) : _(msg`Video is paused`)
}
label={_(
`Video from ${sanitizeHandle(
post.author.handle,
'@',
)}. Tap to play or pause the video`,
)}
accessibilityHint={_(msg`Double tap to like`)} accessibilityHint={_(msg`Double tap to like`)}
onPress={onPress} onPress={onPress}
style={[a.absolute, a.inset_0]}> style={[a.absolute, a.inset_0, a.z_10]}>
<View /> <View />
</Button> </Button>
) )