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