Bluesky app fork with some witchin' additions 馃挮
1import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native'
2import {type AnimatedRef} from 'react-native-reanimated'
3import {Image, type ImageStyle} from 'expo-image'
4import {type AppBskyEmbedImages} from '@atproto/api'
5import {utils} from '@bsky.app/alf'
6import {msg} from '@lingui/core/macro'
7import {useLingui} from '@lingui/react'
8import {Trans} from '@lingui/react/macro'
9
10import {type Dimensions} from '#/lib/media/types'
11import {useHighQualityImages} from '#/state/preferences/high-quality-images'
12import {
13 applyImageTransforms,
14 useImageCdnHost,
15} from '#/state/preferences/image-cdn-host'
16import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
17import {atoms as a, useTheme} from '#/alf'
18import {MediaInsetBorder} from '#/components/MediaInsetBorder'
19import {PostEmbedViewContext} from '#/components/Post/Embed/types'
20import {Text} from '#/components/Typography'
21
22type EventFunction = (index: number) => void
23
24interface Props {
25 images: AppBskyEmbedImages.ViewImage[]
26 index: number
27 onPress?: (
28 index: number,
29 containerRefs: AnimatedRef<any>[],
30 fetchedDims: (Dimensions | null)[],
31 ) => void
32 onLongPress?: EventFunction
33 onPressIn?: EventFunction
34 imageStyle?: StyleProp<ImageStyle>
35 viewContext?: PostEmbedViewContext
36 insetBorderStyle?: StyleProp<ViewStyle>
37 containerRefs: AnimatedRef<any>[]
38 thumbDimsRef: React.RefObject<(Dimensions | null)[]>
39}
40
41export function GalleryItem({
42 images,
43 index,
44 imageStyle,
45 onPress,
46 onPressIn,
47 onLongPress,
48 viewContext,
49 insetBorderStyle,
50 containerRefs,
51 thumbDimsRef,
52}: Props) {
53 const t = useTheme()
54 const {_} = useLingui()
55 const largeAltBadge = useLargeAltBadgeEnabled()
56 const highQualityImages = useHighQualityImages()
57 const imageCdnHost = useImageCdnHost()
58 const image = images[index]
59 const hasAlt = !!image.alt
60 const hideBadges =
61 viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
62 return (
63 <View style={a.flex_1} ref={containerRefs[index]} collapsable={false}>
64 <Pressable
65 onPress={
66 onPress
67 ? () => onPress(index, containerRefs, thumbDimsRef.current.slice())
68 : undefined
69 }
70 onPressIn={onPressIn ? () => onPressIn(index) : undefined}
71 onLongPress={onLongPress ? () => onLongPress(index) : undefined}
72 android_ripple={{
73 color: utils.alpha(t.atoms.bg.backgroundColor, 0.2),
74 foreground: true,
75 }}
76 style={[
77 a.flex_1,
78 a.overflow_hidden,
79 t.atoms.bg_contrast_25,
80 imageStyle,
81 ]}
82 accessibilityRole="button"
83 accessibilityLabel={image.alt || _(msg`Image`)}
84 accessibilityHint="">
85 <Image
86 source={{
87 uri: applyImageTransforms(image.thumb, {
88 imageCdnHost,
89 highQualityImages,
90 }),
91 }}
92 style={[a.flex_1]}
93 accessible={true}
94 accessibilityLabel={image.alt}
95 accessibilityHint=""
96 accessibilityIgnoresInvertColors
97 onLoad={e => {
98 thumbDimsRef.current[index] = {
99 width: e.source.width,
100 height: e.source.height,
101 }
102 }}
103 loading="lazy"
104 />
105 <MediaInsetBorder style={insetBorderStyle} />
106 </Pressable>
107 {hasAlt && !hideBadges ? (
108 <View
109 accessible={false}
110 style={[
111 a.absolute,
112 a.flex_row,
113 a.align_center,
114 a.rounded_xs,
115 t.atoms.bg_contrast_25,
116 {
117 gap: 3,
118 padding: 3,
119 bottom: a.p_xs.padding,
120 right: a.p_xs.padding,
121 opacity: 0.8,
122 },
123 largeAltBadge && [
124 {
125 gap: 4,
126 padding: 5,
127 },
128 ],
129 ]}>
130 <Text
131 style={[a.font_bold, largeAltBadge ? a.text_xs : {fontSize: 8}]}>
132 <Trans>ALT</Trans>
133 </Text>
134 </View>
135 ) : null}
136 </View>
137 )
138}