forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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})