import React from 'react' import { ActivityIndicator, GestureResponderEvent, LayoutChangeEvent, Pressable, StyleSheet, } from 'react-native' import {Image, ImageLoadEventData} from 'expo-image' import {AppBskyEmbedExternal} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {EmbedPlayerParams, getGifDims} from '#/lib/strings/embed-player' import {isIOS, isNative, isWeb} from '#/platform/detection' import {useExternalEmbedsPrefs} from '#/state/preferences' import {atoms as a, useTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent' import {Fill} from '#/components/Fill' import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' export function ExternalGifEmbed({ link, params, }: { link: AppBskyEmbedExternal.ViewExternal params: EmbedPlayerParams }) { const t = useTheme() const externalEmbedsPrefs = useExternalEmbedsPrefs() const {_} = useLingui() const consentDialogControl = useDialogControl() const thumbHasLoaded = React.useRef(false) const viewWidth = React.useRef(0) // Tracking if the placer has been activated const [isPlayerActive, setIsPlayerActive] = React.useState(false) // Tracking whether the gif has been loaded yet const [isPrefetched, setIsPrefetched] = React.useState(false) // Tracking whether the image is animating const [isAnimating, setIsAnimating] = React.useState(true) const [imageDims, setImageDims] = React.useState({height: 100, width: 1}) // Used for controlling animation const imageRef = React.useRef(null) const load = React.useCallback(() => { setIsPlayerActive(true) Image.prefetch(params.playerUri).then(() => { // Replace the image once it's fetched setIsPrefetched(true) }) }, [params.playerUri]) const onPlayPress = React.useCallback( (event: GestureResponderEvent) => { // Don't propagate on web event.preventDefault() // Show consent if this is the first load if (externalEmbedsPrefs?.[params.source] === undefined) { consentDialogControl.open() return } // If the player isn't active, we want to activate it and prefetch the gif if (!isPlayerActive) { load() return } // Control animation on native setIsAnimating(prev => { if (prev) { if (isNative) { imageRef.current?.stopAnimating() } return false } else { if (isNative) { imageRef.current?.startAnimating() } return true } }) }, [ consentDialogControl, externalEmbedsPrefs, isPlayerActive, load, params.source, ], ) const onLoad = React.useCallback((e: ImageLoadEventData) => { if (thumbHasLoaded.current) return setImageDims(getGifDims(e.source.height, e.source.width, viewWidth.current)) thumbHasLoaded.current = true }, []) const onLayout = React.useCallback((e: LayoutChangeEvent) => { viewWidth.current = e.nativeEvent.layout.width }, []) return ( <> {(!isPrefetched || !isAnimating) && ( {!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active ) : ( // Activity indicator while gif loads )} )} ) } const styles = StyleSheet.create({ topRadius: { borderTopLeftRadius: 6, borderTopRightRadius: 6, }, layer: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, }, overlayContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, overlayLayer: { zIndex: 2, }, gifContainer: { width: '100%', overflow: 'hidden', }, })