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