Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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