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:
@@ -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]}>
|
||||||
|
{player && (
|
||||||
<PlayPauseTapArea
|
<PlayPauseTapArea
|
||||||
player={player}
|
player={player}
|
||||||
post={post}
|
post={post}
|
||||||
feedContext={feedContext}
|
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>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user