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