Bluesky app fork with some witchin' additions 馃挮
at readme-update 181 lines 5.5 kB view raw
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}