WIP cleanup
This commit is contained in:
+210
-197
@@ -19,7 +19,7 @@ import Animated, {
|
|||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {useEvent, useEventListener} from 'expo'
|
import {useEvent, useEventListener} from 'expo'
|
||||||
import {Image} from 'expo-image'
|
import {Image, ImageStyle} from 'expo-image'
|
||||||
import {LinearGradient} from 'expo-linear-gradient'
|
import {LinearGradient} from 'expo-linear-gradient'
|
||||||
import {createVideoPlayer, VideoPlayer, VideoView} from 'expo-video'
|
import {createVideoPlayer, VideoPlayer, VideoView} from 'expo-video'
|
||||||
import {
|
import {
|
||||||
@@ -54,7 +54,7 @@ import {useSetLightStatusBar} from '#/state/shell/light-status-bar'
|
|||||||
import {List} from '#/view/com/util/List'
|
import {List} from '#/view/com/util/List'
|
||||||
import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
|
import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
|
||||||
import {formatTime} from '#/view/com/util/post-embeds/VideoEmbedInner/web-controls/utils'
|
import {formatTime} from '#/view/com/util/post-embeds/VideoEmbedInner/web-controls/utils'
|
||||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {Header} from '#/screens/VideoFeed/Header'
|
import {Header} from '#/screens/VideoFeed/Header'
|
||||||
import {atoms as a, ThemeProvider, tokens, useTheme} from '#/alf'
|
import {atoms as a, ThemeProvider, tokens, useTheme} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
@@ -350,41 +350,12 @@ function VideoItem({
|
|||||||
}) {
|
}) {
|
||||||
const postShadow = usePostShadow(post)
|
const postShadow = usePostShadow(post)
|
||||||
const {height, width} = useWindowDimensions()
|
const {height, width} = useWindowDimensions()
|
||||||
const insets = useSafeAreaInsets()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={[a.relative, {height, width}]}>
|
||||||
height,
|
|
||||||
width,
|
|
||||||
}}>
|
|
||||||
<SafeAreaView edges={['left', 'right', 'bottom']} style={[a.flex_1]}>
|
<SafeAreaView edges={['left', 'right', 'bottom']} style={[a.flex_1]}>
|
||||||
{player ? (
|
{postShadow === POST_TOMBSTONE ? (
|
||||||
<VideoItemInner player={player} embed={embed} active={active} />
|
|
||||||
) : (
|
|
||||||
embed.thumbnail && (
|
|
||||||
<Image
|
|
||||||
accessibilityIgnoresInvertColors
|
|
||||||
source={{uri: embed.thumbnail}}
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
a.absolute,
|
|
||||||
{
|
|
||||||
top: 0,
|
|
||||||
left: insets.left,
|
|
||||||
right: insets.right,
|
|
||||||
bottom: insets.bottom,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
contentFit="contain"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
{postShadow !== POST_TOMBSTONE ? (
|
|
||||||
player && (
|
|
||||||
<Overlay player={player} post={postShadow} active={active} />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.absolute,
|
a.absolute,
|
||||||
@@ -405,12 +376,21 @@ function VideoItem({
|
|||||||
<Trans>Post has been deleted</Trans>
|
<Trans>Post has been deleted</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<VideoItemPlaceholder embed={embed} />
|
||||||
|
{player && (
|
||||||
|
<VideoItemInner player={player} embed={embed} active={active} />
|
||||||
|
)}
|
||||||
|
<Overlay player={player} post={postShadow} active={active} />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO maybe unused
|
||||||
function VideoItemInner({
|
function VideoItemInner({
|
||||||
player,
|
player,
|
||||||
embed,
|
embed,
|
||||||
@@ -420,44 +400,25 @@ function VideoItemInner({
|
|||||||
embed: AppBskyEmbedVideo.View
|
embed: AppBskyEmbedVideo.View
|
||||||
active: boolean
|
active: boolean
|
||||||
}) {
|
}) {
|
||||||
const insets = useSafeAreaInsets()
|
// const {status} = useEvent(player, 'statusChange', {status: player.status})
|
||||||
const {status} = useEvent(player, 'statusChange', {status: player.status})
|
|
||||||
|
|
||||||
const videoAspectRatio =
|
|
||||||
(embed.aspectRatio?.width ?? 1) / (embed.aspectRatio?.height ?? 1)
|
|
||||||
|
|
||||||
// if the video tall enough (tiktok/reels are 9:16) go cover mode
|
|
||||||
const isCloseEnough = videoAspectRatio <= 9 / 16
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/*
|
||||||
|
<VideoItemPlaceholder embed={embed} style={{
|
||||||
|
zIndex: status === 'loading' && isAndroid ? 1 : -1,
|
||||||
|
}} />
|
||||||
|
*/}
|
||||||
|
|
||||||
{active && player && (
|
{active && player && (
|
||||||
<VideoView
|
<VideoView
|
||||||
style={[a.flex_1]}
|
style={[a.absolute, a.inset_0]}
|
||||||
player={player}
|
player={player}
|
||||||
nativeControls={false}
|
nativeControls={false}
|
||||||
contentFit={isCloseEnough ? 'cover' : 'contain'}
|
contentFit={isTallAspectRatio(embed.aspectRatio) ? 'cover' : 'contain'}
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{embed.thumbnail && (
|
|
||||||
<Image
|
|
||||||
accessibilityIgnoresInvertColors
|
|
||||||
source={{uri: embed.thumbnail}}
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
a.absolute,
|
|
||||||
{
|
|
||||||
zIndex: status === 'loading' && isAndroid ? 1 : -1,
|
|
||||||
top: 0,
|
|
||||||
left: insets.left,
|
|
||||||
right: insets.right,
|
|
||||||
bottom: insets.bottom,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
contentFit={isCloseEnough ? 'cover' : 'contain'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -467,41 +428,19 @@ function Overlay({
|
|||||||
post,
|
post,
|
||||||
active,
|
active,
|
||||||
}: {
|
}: {
|
||||||
player: VideoPlayer
|
player?: VideoPlayer
|
||||||
post: Shadow<AppBskyFeedDefs.PostView>
|
post: Shadow<AppBskyFeedDefs.PostView>
|
||||||
active: boolean
|
active: boolean
|
||||||
}) {
|
}) {
|
||||||
const insets = useSafeAreaInsets()
|
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const {status} = useEvent(player, 'statusChange', {status: player.status})
|
// const {status} = useEvent(player, 'statusChange', {status: player.status})
|
||||||
const doubleTapRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
||||||
const [queueLike] = usePostLikeMutationQueue(post, 'ImmersiveVideo')
|
|
||||||
const seekingAnimationSV = useSharedValue(0)
|
const seekingAnimationSV = useSharedValue(0)
|
||||||
|
|
||||||
const pushToProfile = useNonReactiveCallback(() => {
|
const pushToProfile = useNonReactiveCallback(() => {
|
||||||
navigation.navigate('Profile', {name: post.author.did})
|
navigation.navigate('Profile', {name: post.author.did})
|
||||||
})
|
})
|
||||||
|
|
||||||
const togglePlayPause = () => {
|
|
||||||
doubleTapRef.current = null
|
|
||||||
if (player.playing) {
|
|
||||||
player.pause()
|
|
||||||
} else {
|
|
||||||
player.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onPress = () => {
|
|
||||||
if (doubleTapRef.current) {
|
|
||||||
clearTimeout(doubleTapRef.current)
|
|
||||||
doubleTapRef.current = null
|
|
||||||
queueLike()
|
|
||||||
} else {
|
|
||||||
doubleTapRef.current = setTimeout(togglePlayPause, 200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const gesture = useMemo(() => {
|
const gesture = useMemo(() => {
|
||||||
const dragLeftGesture = Gesture.Pan()
|
const dragLeftGesture = Gesture.Pan()
|
||||||
.activeOffsetX([0, 10])
|
.activeOffsetX([0, 10])
|
||||||
@@ -533,49 +472,43 @@ function Overlay({
|
|||||||
<>
|
<>
|
||||||
<View style={[a.absolute, a.inset_0, a.z_20]}>
|
<View style={[a.absolute, a.inset_0, a.z_20]}>
|
||||||
<GestureDetector gesture={gesture}>
|
<GestureDetector gesture={gesture}>
|
||||||
<Animated.View style={[a.flex_1, animatedStyle]}>
|
<View style={[a.flex_1]}>
|
||||||
<Button
|
<PlayPauseTapArea player={player} post={post} />
|
||||||
label="Toggle play/pause"
|
</View>
|
||||||
accessibilityHint="Double tap to like"
|
</GestureDetector>
|
||||||
onPress={onPress}
|
|
||||||
style={[a.flex_1]}>
|
<LinearGradient
|
||||||
<View />
|
colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.8)', 'rgba(0,0,0,0.95)']}
|
||||||
</Button>
|
style={[a.w_full, a.pt_md]}>
|
||||||
<LinearGradient
|
<Animated.View style={[a.px_xl, animatedStyle]}>
|
||||||
colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.8)', 'rgba(0,0,0,0.95)']}
|
<View style={[a.flex_row, a.gap_md, a.pb_sm, a.align_center]}>
|
||||||
style={[
|
<UserAvatar type='user' avatar={post.author.avatar} size={32} />
|
||||||
a.w_full,
|
<View>
|
||||||
a.px_xl,
|
<Text
|
||||||
a.py_sm,
|
style={[a.text_md, a.font_heavy]}
|
||||||
a.gap_md,
|
emoji
|
||||||
a.pb_3xl,
|
numberOfLines={1}>
|
||||||
{marginBottom: tokens.space.xl * -1},
|
{sanitizeDisplayName(
|
||||||
]}>
|
post.author.displayName || post.author.handle,
|
||||||
<View style={[a.flex_row, a.gap_md, a.align_center]}>
|
)}
|
||||||
<PreviewableUserAvatar profile={post.author} size={32} />
|
</Text>
|
||||||
<View>
|
<Text
|
||||||
<Text
|
style={[a.text_sm, t.atoms.text_contrast_high]}
|
||||||
style={[a.text_md, a.font_heavy]}
|
numberOfLines={1}>
|
||||||
emoji
|
{sanitizeHandle(post.author.handle, '@')}
|
||||||
numberOfLines={1}>
|
</Text>
|
||||||
{sanitizeDisplayName(
|
|
||||||
post.author.displayName || post.author.handle,
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[a.text_sm, t.atoms.text_contrast_high]}
|
|
||||||
numberOfLines={1}>
|
|
||||||
{sanitizeHandle(post.author.handle, '@')}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
{record?.text?.trim() && (
|
</View>
|
||||||
|
{record?.text?.trim() && (
|
||||||
|
<View style={[a.pb_sm]}>
|
||||||
<ExpandableRichTextView
|
<ExpandableRichTextView
|
||||||
value={richText}
|
value={richText}
|
||||||
authorHandle={post.author.handle}
|
authorHandle={post.author.handle}
|
||||||
/>
|
/>
|
||||||
)}
|
</View>
|
||||||
{record && (
|
)}
|
||||||
|
{record && (
|
||||||
|
<View style={[{left: -5}]}>
|
||||||
<PostCtrls
|
<PostCtrls
|
||||||
richText={richText}
|
richText={richText}
|
||||||
post={post}
|
post={post}
|
||||||
@@ -589,22 +522,18 @@ function Overlay({
|
|||||||
}
|
}
|
||||||
big
|
big
|
||||||
/>
|
/>
|
||||||
)}
|
</View>
|
||||||
</LinearGradient>
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</GestureDetector>
|
|
||||||
{player && active ? (
|
{player && active ? (
|
||||||
<Scrubber player={player} seekingAnimationSV={seekingAnimationSV} />
|
<Scrubber player={player} seekingAnimationSV={seekingAnimationSV} />
|
||||||
) : (
|
) : (
|
||||||
<View
|
<ScrubberPlaceholder />
|
||||||
style={[
|
)}
|
||||||
{height: tokens.space.xl},
|
</LinearGradient>
|
||||||
a.w_full,
|
|
||||||
{paddingBottom: insets.bottom},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
|
{/*
|
||||||
{isAndroid && status === 'loading' && (
|
{isAndroid && status === 'loading' && (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -618,64 +547,16 @@ function Overlay({
|
|||||||
<Loader size="2xl" />
|
<Loader size="2xl" />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
*/}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExpandableRichTextView({
|
/**
|
||||||
value,
|
* Magic number that matches the Scrubber height
|
||||||
authorHandle,
|
*/
|
||||||
}: {
|
function ScrubberPlaceholder() {
|
||||||
value: RichTextAPI
|
return <View style={[a.w_full, {height: 62}]} />
|
||||||
authorHandle?: string
|
|
||||||
}) {
|
|
||||||
const {height: screenHeight} = useWindowDimensions()
|
|
||||||
const [expanded, setExpanded] = useState(false)
|
|
||||||
const [constrained, setConstrained] = useState(false)
|
|
||||||
const [contentHeight, setContentHeight] = useState(0)
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView
|
|
||||||
scrollEnabled={expanded}
|
|
||||||
onContentSizeChange={(_w, h) => {
|
|
||||||
if (expanded) {
|
|
||||||
LayoutAnimation.configureNext({
|
|
||||||
duration: 500,
|
|
||||||
update: {type: 'spring', springDamping: 0.6},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setContentHeight(h)
|
|
||||||
}}
|
|
||||||
style={{height: Math.min(contentHeight, screenHeight * 0.5)}}
|
|
||||||
contentContainerStyle={[
|
|
||||||
a.gap_xs,
|
|
||||||
expanded ? [a.align_start] : a.flex_row,
|
|
||||||
]}>
|
|
||||||
<RichText
|
|
||||||
value={value}
|
|
||||||
style={[a.text_sm, a.flex_1]}
|
|
||||||
authorHandle={authorHandle}
|
|
||||||
enableTags
|
|
||||||
numberOfLines={expanded ? undefined : constrained ? 1 : 2}
|
|
||||||
onTextLayout={evt => {
|
|
||||||
if (!constrained && evt.nativeEvent.lines.length > 1) {
|
|
||||||
setConstrained(true)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{constrained && (
|
|
||||||
<Button
|
|
||||||
label={expanded ? _(msg`Read less`) : _(msg`Read more`)}
|
|
||||||
hitSlop={HITSLOP_20}
|
|
||||||
onPress={() => setExpanded(prev => !prev)}>
|
|
||||||
<ButtonText>
|
|
||||||
{expanded ? <Trans>Read less</Trans> : <Trans>Read more</Trans>}
|
|
||||||
</ButtonText>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</ScrollView>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Scrubber({
|
function Scrubber({
|
||||||
@@ -796,7 +677,7 @@ function Scrubber({
|
|||||||
{
|
{
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: tokens.space.xl + tokens.space._4xl + insets.bottom,
|
bottom: insets.bottom + 48,
|
||||||
},
|
},
|
||||||
timeStyle,
|
timeStyle,
|
||||||
]}
|
]}
|
||||||
@@ -818,15 +699,18 @@ function Scrubber({
|
|||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
<GestureDetector gesture={gesture}>
|
<GestureDetector gesture={gesture}>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.w_full,
|
|
||||||
{height: tokens.space.md},
|
|
||||||
a.pt_sm,
|
|
||||||
a.justify_end,
|
|
||||||
{paddingBottom: insets.bottom},
|
|
||||||
a.relative,
|
a.relative,
|
||||||
|
a.w_full,
|
||||||
|
a.pt_lg,
|
||||||
|
a.justify_end,
|
||||||
|
{
|
||||||
|
height: tokens.space.md,
|
||||||
|
paddingBottom: insets.bottom + 12,
|
||||||
|
},
|
||||||
a.z_10,
|
a.z_10,
|
||||||
]}>
|
]}>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
@@ -838,7 +722,136 @@ function Scrubber({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ExpandableRichTextView({
|
||||||
|
value,
|
||||||
|
authorHandle,
|
||||||
|
}: {
|
||||||
|
value: RichTextAPI
|
||||||
|
authorHandle?: string
|
||||||
|
}) {
|
||||||
|
const {height: screenHeight} = useWindowDimensions()
|
||||||
|
const [expanded, setExpanded] = useState(false)
|
||||||
|
const [constrained, setConstrained] = useState(false)
|
||||||
|
const [contentHeight, setContentHeight] = useState(0)
|
||||||
|
const {_} = useLingui()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView
|
||||||
|
scrollEnabled={expanded}
|
||||||
|
onContentSizeChange={(_w, h) => {
|
||||||
|
if (expanded) {
|
||||||
|
LayoutAnimation.configureNext({
|
||||||
|
duration: 500,
|
||||||
|
update: {type: 'spring', springDamping: 0.6},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setContentHeight(h)
|
||||||
|
}}
|
||||||
|
style={{height: Math.min(contentHeight, screenHeight * 0.5)}}
|
||||||
|
contentContainerStyle={[
|
||||||
|
a.gap_xs,
|
||||||
|
expanded ? [a.align_start] : a.flex_row,
|
||||||
|
]}>
|
||||||
|
<RichText
|
||||||
|
value={value}
|
||||||
|
style={[a.text_sm, a.flex_1]}
|
||||||
|
authorHandle={authorHandle}
|
||||||
|
enableTags
|
||||||
|
numberOfLines={expanded ? undefined : constrained ? 1 : 2}
|
||||||
|
onTextLayout={evt => {
|
||||||
|
if (!constrained && evt.nativeEvent.lines.length > 1) {
|
||||||
|
setConstrained(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{constrained && (
|
||||||
|
<Button
|
||||||
|
label={expanded ? _(msg`Read less`) : _(msg`Read more`)}
|
||||||
|
hitSlop={HITSLOP_20}
|
||||||
|
onPress={() => setExpanded(prev => !prev)}>
|
||||||
|
<ButtonText>
|
||||||
|
{expanded ? <Trans>Read less</Trans> : <Trans>Read more</Trans>}
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function VideoItemPlaceholder({
|
||||||
|
embed,
|
||||||
|
style,
|
||||||
|
}: {
|
||||||
|
embed: AppBskyEmbedVideo.View
|
||||||
|
style?: ImageStyle
|
||||||
|
}) {
|
||||||
|
const src = embed.thumbnail
|
||||||
|
return src ? (
|
||||||
|
<Image
|
||||||
|
accessibilityIgnoresInvertColors
|
||||||
|
source={{uri: src}}
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
style,
|
||||||
|
]}
|
||||||
|
contentFit={isTallAspectRatio(embed.aspectRatio) ? 'cover' : 'contain'}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function PlayPauseTapArea({
|
||||||
|
player,
|
||||||
|
post,
|
||||||
|
}: {
|
||||||
|
player?: VideoPlayer
|
||||||
|
post: Shadow<AppBskyFeedDefs.PostView>
|
||||||
|
}) {
|
||||||
|
const doubleTapRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
const [queueLike] = usePostLikeMutationQueue(post, 'ImmersiveVideo')
|
||||||
|
const togglePlayPause = () => {
|
||||||
|
if (!player) return
|
||||||
|
doubleTapRef.current = null
|
||||||
|
if (player.playing) {
|
||||||
|
player.pause()
|
||||||
|
} else {
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPress = () => {
|
||||||
|
if (doubleTapRef.current) {
|
||||||
|
clearTimeout(doubleTapRef.current)
|
||||||
|
doubleTapRef.current = null
|
||||||
|
queueLike()
|
||||||
|
} else {
|
||||||
|
doubleTapRef.current = setTimeout(togglePlayPause, 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
disabled={!player}
|
||||||
|
label="Toggle play/pause"
|
||||||
|
accessibilityHint="Double tap to like"
|
||||||
|
onPress={onPress}
|
||||||
|
style={[a.absolute, a.inset_0]}
|
||||||
|
>
|
||||||
|
<View />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function clamp(num: number, min: number, max: number) {
|
function clamp(num: number, min: number, max: number) {
|
||||||
'worklet'
|
'worklet'
|
||||||
return Math.min(Math.max(num, min), max)
|
return Math.min(Math.max(num, min), max)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the video is taller than 9:16
|
||||||
|
*/
|
||||||
|
function isTallAspectRatio(aspectRatio: AppBskyEmbedVideo.View['aspectRatio']) {
|
||||||
|
const videoAspectRatio =
|
||||||
|
(aspectRatio?.width ?? 1) / (aspectRatio?.height ?? 1)
|
||||||
|
return videoAspectRatio <= 9 / 16
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user