Bluesky app fork with some witchin' additions 馃挮
at main 4.2 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 <VideoItem thumbnail={e.view.thumbnail} alt={e.view.alt} /> 63 </Outer> 64 ) 65 } else if ( 66 e.type === 'post_with_media' && 67 // ignore further "nested" RecordWithMedia 68 e.media.type !== 'post_with_media' && 69 // ignore any unknowns 70 e.media.view !== null 71 ) { 72 return <Embed embed={e.media.view} style={style} /> 73 } 74 75 return null 76} 77 78export function Outer({ 79 children, 80 style, 81}: { 82 children?: React.ReactNode 83 style?: StyleProp<ViewStyle> 84}) { 85 return <View style={[a.flex_row, a.gap_xs, style]}>{children}</View> 86} 87 88export function ImageItem({ 89 thumbnail, 90 alt, 91 children, 92}: { 93 thumbnail: string 94 alt?: string 95 children?: React.ReactNode 96}) { 97 const t = useTheme() 98 return ( 99 <View style={[a.relative, a.flex_1, a.aspect_square, {maxWidth: 100}]}> 100 <Image 101 key={thumbnail} 102 source={{uri: thumbnail}} 103 alt={alt} 104 style={[a.flex_1, a.rounded_xs, t.atoms.bg_contrast_25]} 105 contentFit="cover" 106 accessible={true} 107 accessibilityIgnoresInvertColors 108 /> 109 <MediaInsetBorder style={[a.rounded_xs]} /> 110 {children} 111 </View> 112 ) 113} 114 115export function GifItem({thumbnail, alt}: {thumbnail: string; alt?: string}) { 116 return ( 117 <ImageItem thumbnail={thumbnail} alt={alt}> 118 <View style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}> 119 <PlayButtonIcon size={24} /> 120 </View> 121 <View style={styles.altContainer}> 122 <Text style={styles.alt}> 123 <Trans>GIF</Trans> 124 </Text> 125 </View> 126 </ImageItem> 127 ) 128} 129 130export function VideoItem({ 131 thumbnail, 132 alt, 133}: { 134 thumbnail?: string 135 alt?: string 136}) { 137 if (!thumbnail) { 138 return ( 139 <View 140 style={[ 141 {backgroundColor: 'black'}, 142 a.flex_1, 143 a.aspect_square, 144 {maxWidth: 100}, 145 a.justify_center, 146 a.align_center, 147 ]}> 148 <PlayButtonIcon size={24} /> 149 </View> 150 ) 151 } 152 return ( 153 <ImageItem thumbnail={thumbnail} alt={alt}> 154 <View style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}> 155 <PlayButtonIcon size={24} /> 156 </View> 157 </ImageItem> 158 ) 159} 160 161const styles = StyleSheet.create({ 162 altContainer: { 163 backgroundColor: 'rgba(0, 0, 0, 0.75)', 164 borderRadius: 6, 165 paddingHorizontal: 6, 166 paddingVertical: 3, 167 position: 'absolute', 168 right: 5, 169 bottom: 5, 170 zIndex: 2, 171 }, 172 alt: { 173 color: 'white', 174 fontSize: 7, 175 fontWeight: '600', 176 }, 177})