forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import React, {useCallback} from 'react'
2import {type StyleProp, View, type ViewStyle} from 'react-native'
3import {Image} from 'expo-image'
4import {type AppBskyEmbedExternal} from '@atproto/api'
5import {msg} from '@lingui/macro'
6import {useLingui} from '@lingui/react'
7
8import {parseAltFromGIFDescription} from '#/lib/gif-alt-text'
9import {useHaptics} from '#/lib/haptics'
10import {shareUrl} from '#/lib/sharing'
11import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
12import {toNiceDomain} from '#/lib/strings/url-helpers'
13import {useExternalEmbedsPrefs} from '#/state/preferences'
14import {atoms as a, useTheme} from '#/alf'
15import {Divider} from '#/components/Divider'
16import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
17import {Link} from '#/components/Link'
18import {Text} from '#/components/Typography'
19import {IS_NATIVE} from '#/env'
20import {ExternalGif} from './ExternalGif'
21import {ExternalPlayer} from './ExternalPlayer'
22import {GifEmbed} from './Gif'
23
24export const ExternalEmbed = ({
25 link,
26 onOpen,
27 style,
28 hideAlt,
29}: {
30 link: AppBskyEmbedExternal.ViewExternal
31 onOpen?: () => void
32 style?: StyleProp<ViewStyle>
33 hideAlt?: boolean
34}) => {
35 const {_} = useLingui()
36 const t = useTheme()
37 const playHaptic = useHaptics()
38 const externalEmbedPrefs = useExternalEmbedsPrefs()
39 const niceUrl = toNiceDomain(link.uri)
40 const imageUri = link.thumb
41 const embedPlayerParams = React.useMemo(() => {
42 const params = parseEmbedPlayerFromUrl(link.uri)
43
44 if (params && externalEmbedPrefs?.[params.source] !== 'hide') {
45 return params
46 }
47 }, [link.uri, externalEmbedPrefs])
48 const hasMedia = Boolean(imageUri || embedPlayerParams)
49
50 const onPress = useCallback(() => {
51 playHaptic('Light')
52 onOpen?.()
53 }, [playHaptic, onOpen])
54
55 const onShareExternal = useCallback(() => {
56 if (link.uri && IS_NATIVE) {
57 playHaptic('Heavy')
58 shareUrl(link.uri)
59 }
60 }, [link.uri, playHaptic])
61
62 if (embedPlayerParams?.source === 'tenor') {
63 const parsedAlt = parseAltFromGIFDescription(link.description)
64 return (
65 <View style={style}>
66 <GifEmbed
67 params={embedPlayerParams}
68 thumb={link.thumb}
69 altText={parsedAlt.alt}
70 isPreferredAltText={parsedAlt.isPreferred}
71 hideAlt={hideAlt}
72 />
73 </View>
74 )
75 }
76
77 return (
78 <Link
79 label={link.title || _(msg`Open link to ${niceUrl}`)}
80 to={link.uri}
81 shouldProxy={true}
82 onPress={onPress}
83 onLongPress={onShareExternal}>
84 {({hovered}) => (
85 <View
86 style={[
87 a.transition_color,
88 a.flex_col,
89 a.rounded_md,
90 a.overflow_hidden,
91 a.w_full,
92 a.border,
93 style,
94 hovered
95 ? t.atoms.border_contrast_high
96 : t.atoms.border_contrast_low,
97 ]}>
98 {imageUri && !embedPlayerParams ? (
99 <Image
100 style={[a.aspect_card]}
101 source={{uri: imageUri}}
102 accessibilityIgnoresInvertColors
103 loading="lazy"
104 />
105 ) : undefined}
106
107 {embedPlayerParams?.isGif ? (
108 <ExternalGif link={link} params={embedPlayerParams} />
109 ) : embedPlayerParams ? (
110 <ExternalPlayer link={link} params={embedPlayerParams} />
111 ) : undefined}
112
113 <View
114 style={[
115 a.flex_1,
116 a.pt_sm,
117 {gap: 3},
118 hasMedia && a.border_t,
119 hovered
120 ? t.atoms.border_contrast_high
121 : t.atoms.border_contrast_low,
122 ]}>
123 <View style={[{gap: 3}, a.pb_xs, a.px_md]}>
124 {!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
125 <Text
126 emoji
127 numberOfLines={3}
128 style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
129 {link.title || link.uri}
130 </Text>
131 )}
132 {link.description ? (
133 <Text
134 emoji
135 numberOfLines={link.thumb ? 2 : 4}
136 style={[a.text_sm, a.leading_snug]}>
137 {link.description}
138 </Text>
139 ) : undefined}
140 </View>
141 <View style={[a.px_md]}>
142 <Divider />
143 <View
144 style={[
145 a.flex_row,
146 a.align_center,
147 a.gap_2xs,
148 a.pb_sm,
149 {
150 paddingTop: 6, // off menu
151 },
152 ]}>
153 <Globe
154 size="xs"
155 style={[
156 a.transition_color,
157 hovered
158 ? t.atoms.text_contrast_medium
159 : t.atoms.text_contrast_low,
160 ]}
161 />
162 <Text
163 numberOfLines={1}
164 style={[
165 a.transition_color,
166 a.text_xs,
167 a.leading_snug,
168 hovered
169 ? t.atoms.text_contrast_high
170 : t.atoms.text_contrast_medium,
171 ]}>
172 {toNiceDomain(link.uri)}
173 </Text>
174 </View>
175 </View>
176 </View>
177 </View>
178 )}
179 </Link>
180 )
181}