mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {ActivityIndicator, GestureResponderEvent, Pressable} from 'react-native'
3import {Image} from 'expo-image'
4import {AppBskyEmbedExternal} from '@atproto/api'
5import {msg} from '@lingui/macro'
6import {useLingui} from '@lingui/react'
7
8import {EmbedPlayerParams} from '#/lib/strings/embed-player'
9import {isIOS, isNative, isWeb} from '#/platform/detection'
10import {useExternalEmbedsPrefs} from '#/state/preferences'
11import {atoms as a, useTheme} from '#/alf'
12import {useDialogControl} from '#/components/Dialog'
13import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
14import {Fill} from '#/components/Fill'
15import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
16
17export function ExternalGifEmbed({
18 link,
19 params,
20}: {
21 link: AppBskyEmbedExternal.ViewExternal
22 params: EmbedPlayerParams
23}) {
24 const t = useTheme()
25 const externalEmbedsPrefs = useExternalEmbedsPrefs()
26 const {_} = useLingui()
27 const consentDialogControl = useDialogControl()
28
29 // Tracking if the placer has been activated
30 const [isPlayerActive, setIsPlayerActive] = React.useState(false)
31 // Tracking whether the gif has been loaded yet
32 const [isPrefetched, setIsPrefetched] = React.useState(false)
33 // Tracking whether the image is animating
34 const [isAnimating, setIsAnimating] = React.useState(true)
35
36 // Used for controlling animation
37 const imageRef = React.useRef<Image>(null)
38
39 const load = React.useCallback(() => {
40 setIsPlayerActive(true)
41 Image.prefetch(params.playerUri).then(() => {
42 // Replace the image once it's fetched
43 setIsPrefetched(true)
44 })
45 }, [params.playerUri])
46
47 const onPlayPress = React.useCallback(
48 (event: GestureResponderEvent) => {
49 // Don't propagate on web
50 event.preventDefault()
51
52 // Show consent if this is the first load
53 if (externalEmbedsPrefs?.[params.source] === undefined) {
54 consentDialogControl.open()
55 return
56 }
57 // If the player isn't active, we want to activate it and prefetch the gif
58 if (!isPlayerActive) {
59 load()
60 return
61 }
62 // Control animation on native
63 setIsAnimating(prev => {
64 if (prev) {
65 if (isNative) {
66 imageRef.current?.stopAnimating()
67 }
68 return false
69 } else {
70 if (isNative) {
71 imageRef.current?.startAnimating()
72 }
73 return true
74 }
75 })
76 },
77 [
78 consentDialogControl,
79 externalEmbedsPrefs,
80 isPlayerActive,
81 load,
82 params.source,
83 ],
84 )
85
86 return (
87 <>
88 <EmbedConsentDialog
89 control={consentDialogControl}
90 source={params.source}
91 onAccept={load}
92 />
93
94 <Pressable
95 style={[
96 {height: 300},
97 a.w_full,
98 a.overflow_hidden,
99 {
100 borderBottomLeftRadius: 0,
101 borderBottomRightRadius: 0,
102 },
103 ]}
104 onPress={onPlayPress}
105 accessibilityRole="button"
106 accessibilityHint={_(msg`Plays the GIF`)}
107 accessibilityLabel={_(msg`Play ${link.title}`)}>
108 <Image
109 source={{
110 uri:
111 !isPrefetched || (isWeb && !isAnimating)
112 ? link.thumb
113 : params.playerUri,
114 }} // Web uses the thumb to control playback
115 style={{flex: 1}}
116 ref={imageRef}
117 autoplay={isAnimating}
118 contentFit="contain"
119 accessibilityIgnoresInvertColors
120 accessibilityLabel={link.title}
121 accessibilityHint={link.title}
122 cachePolicy={isIOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios
123 />
124
125 {(!isPrefetched || !isAnimating) && (
126 <Fill style={[a.align_center, a.justify_center]}>
127 <Fill
128 style={[
129 t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
130 {
131 opacity: 0.3,
132 },
133 ]}
134 />
135
136 {!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active
137 <PlayButtonIcon />
138 ) : (
139 // Activity indicator while gif loads
140 <ActivityIndicator size="large" color="white" />
141 )}
142 </Fill>
143 )}
144 </Pressable>
145 </>
146 )
147}