Bluesky app fork with some witchin' additions 馃挮
at main 185 lines 4.5 kB view raw
1import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' 2import {Image} from 'expo-image' 3import {type AppBskyFeedDefs} from '@atproto/api' 4import {Trans} from '@lingui/react/macro' 5 6import {isTenorGifUri} from '#/lib/strings/embed-player' 7import {useHighQualityImages} from '#/state/preferences/high-quality-images' 8import { 9 applyImageTransforms, 10 useImageCdnHost, 11} from '#/state/preferences/image-cdn-host' 12import {atoms as a, useTheme} from '#/alf' 13import {MediaInsetBorder} from '#/components/MediaInsetBorder' 14import {Text} from '#/components/Typography' 15import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 16import * as bsky from '#/types/bsky' 17 18/** 19 * Streamlined MediaPreview component which just handles images, gifs, and videos 20 */ 21export function Embed({ 22 embed, 23 style, 24}: { 25 embed: AppBskyFeedDefs.PostView['embed'] 26 style?: StyleProp<ViewStyle> 27}) { 28 const highQualityImages = useHighQualityImages() 29 const imageCdnHost = useImageCdnHost() 30 const e = bsky.post.parseEmbed(embed) 31 32 if (!e) return null 33 34 if (e.type === 'images') { 35 return ( 36 <Outer style={style}> 37 {e.view.images.map(image => ( 38 <ImageItem 39 key={image.thumb} 40 thumbnail={applyImageTransforms(image.thumb, { 41 imageCdnHost, 42 highQualityImages, 43 })} 44 alt={image.alt} 45 /> 46 ))} 47 </Outer> 48 ) 49 } else if (e.type === 'link') { 50 if (!e.view.external.thumb) return null 51 if (!isTenorGifUri(e.view.external.uri)) return null 52 return ( 53 <Outer style={style}> 54 <GifItem 55 thumbnail={e.view.external.thumb} 56 alt={e.view.external.title} 57 /> 58 </Outer> 59 ) 60 } else if (e.type === 'video') { 61 return ( 62 <Outer style={style}> 63 {e.view.presentation === 'gif' ? ( 64 <GifItem thumbnail={e.view.thumbnail} alt={e.view.alt} /> 65 ) : ( 66 <VideoItem thumbnail={e.view.thumbnail} alt={e.view.alt} /> 67 )} 68 </Outer> 69 ) 70 } else if ( 71 e.type === 'post_with_media' && 72 // ignore further "nested" RecordWithMedia 73 e.media.type !== 'post_with_media' && 74 // ignore any unknowns 75 e.media.view !== null 76 ) { 77 return <Embed embed={e.media.view} style={style} /> 78 } 79 80 return null 81} 82 83export function Outer({ 84 children, 85 style, 86}: { 87 children?: React.ReactNode 88 style?: StyleProp<ViewStyle> 89}) { 90 return <View style={[a.flex_row, a.gap_xs, style]}>{children}</View> 91} 92 93export function ImageItem({ 94 thumbnail, 95 alt, 96 children, 97}: { 98 thumbnail?: string 99 alt?: string 100 children?: React.ReactNode 101}) { 102 const t = useTheme() 103 104 if (!thumbnail) { 105 return ( 106 <View 107 style={[ 108 {backgroundColor: 'black'}, 109 a.flex_1, 110 a.aspect_square, 111 {maxWidth: 100}, 112 a.rounded_xs, 113 ]} 114 accessibilityLabel={alt} 115 accessibilityHint=""> 116 {children} 117 </View> 118 ) 119 } 120 121 return ( 122 <View style={[a.relative, a.flex_1, a.aspect_square, {maxWidth: 100}]}> 123 <Image 124 key={thumbnail} 125 source={{uri: thumbnail}} 126 alt={alt} 127 style={[a.flex_1, a.rounded_xs, t.atoms.bg_contrast_25]} 128 contentFit="cover" 129 accessible={true} 130 accessibilityIgnoresInvertColors 131 /> 132 <MediaInsetBorder style={[a.rounded_xs]} /> 133 {children} 134 </View> 135 ) 136} 137 138export function GifItem({thumbnail, alt}: {thumbnail?: string; alt?: string}) { 139 return ( 140 <ImageItem thumbnail={thumbnail} alt={alt}> 141 <View style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}> 142 <PlayButtonIcon size={24} /> 143 </View> 144 <View style={styles.altContainer}> 145 <Text style={styles.alt}> 146 <Trans>GIF</Trans> 147 </Text> 148 </View> 149 </ImageItem> 150 ) 151} 152 153export function VideoItem({ 154 thumbnail, 155 alt, 156}: { 157 thumbnail?: string 158 alt?: string 159}) { 160 return ( 161 <ImageItem thumbnail={thumbnail} alt={alt}> 162 <View style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}> 163 <PlayButtonIcon size={24} /> 164 </View> 165 </ImageItem> 166 ) 167} 168 169const styles = StyleSheet.create({ 170 altContainer: { 171 backgroundColor: 'rgba(0, 0, 0, 0.75)', 172 borderRadius: 6, 173 paddingHorizontal: 6, 174 paddingVertical: 3, 175 position: 'absolute', 176 left: 5, 177 bottom: 5, 178 zIndex: 2, 179 }, 180 alt: { 181 color: 'white', 182 fontSize: 7, 183 fontWeight: '600', 184 }, 185})