forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {InteractionManager, View} from 'react-native'
2import {
3 type AnimatedRef,
4 measure,
5 type MeasuredDimensions,
6 runOnJS,
7 runOnUI,
8} from 'react-native-reanimated'
9import {Image} from 'expo-image'
10
11import {useLightboxControls} from '#/state/lightbox'
12import {type Dimensions} from '#/view/com/lightbox/ImageViewing/@types'
13import {atoms as a} from '#/alf'
14import {AutoSizedImage} from '#/components/images/AutoSizedImage'
15import {ImageLayoutGrid} from '#/components/images/ImageLayoutGrid'
16import {PostEmbedViewContext} from '#/components/Post/Embed/types'
17import {type EmbedType} from '#/types/bsky/post'
18import {type CommonProps} from './types'
19
20export function ImageEmbed({
21 embed,
22 ...rest
23}: CommonProps & {
24 embed: EmbedType<'images'>
25}) {
26 const {openLightbox} = useLightboxControls()
27 const {images} = embed.view
28
29 if (images.length > 0) {
30 const items = images.map(img => ({
31 uri: img.fullsize,
32 thumbUri: img.thumb,
33 alt: img.alt,
34 dimensions: img.aspectRatio ?? null,
35 }))
36 const _openLightbox = (
37 index: number,
38 thumbRects: (MeasuredDimensions | null)[],
39 fetchedDims: (Dimensions | null)[],
40 ) => {
41 openLightbox({
42 images: items.map((item, i) => ({
43 ...item,
44 thumbRect: thumbRects[i] ?? null,
45 thumbDimensions: fetchedDims[i] ?? null,
46 type: 'image',
47 })),
48 index,
49 })
50 }
51 const onPress = (
52 index: number,
53 refs: AnimatedRef<any>[],
54 fetchedDims: (Dimensions | null)[],
55 ) => {
56 runOnUI(() => {
57 'worklet'
58 const rects: (MeasuredDimensions | null)[] = []
59 for (const r of refs) {
60 rects.push(measure(r))
61 }
62 runOnJS(_openLightbox)(index, rects, fetchedDims)
63 })()
64 }
65 const onPressIn = (_: number) => {
66 InteractionManager.runAfterInteractions(() => {
67 Image.prefetch(
68 items.map(i => i.uri),
69 'memory',
70 )
71 })
72 }
73
74 if (images.length === 1) {
75 const image = images[0]
76 return (
77 <View style={[a.mt_sm, rest.style]}>
78 <AutoSizedImage
79 crop={
80 rest.viewContext === PostEmbedViewContext.ThreadHighlighted
81 ? 'none'
82 : rest.viewContext ===
83 PostEmbedViewContext.FeedEmbedRecordWithMedia
84 ? 'square'
85 : 'constrained'
86 }
87 image={image}
88 onPress={(containerRef, dims) => onPress(0, [containerRef], [dims])}
89 onPressIn={() => onPressIn(0)}
90 hideBadge={
91 rest.viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
92 }
93 />
94 </View>
95 )
96 }
97
98 return (
99 <View style={[a.mt_sm, rest.style]}>
100 <ImageLayoutGrid
101 images={images}
102 onPress={onPress}
103 onPressIn={onPressIn}
104 viewContext={rest.viewContext}
105 />
106 </View>
107 )
108 }
109}