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