Files
bsky-social-app/src/components/images/ImageLayoutGrid.tsx
T

237 lines
6.7 KiB
TypeScript

import {useRef} from 'react'
import {type StyleProp, View, type ViewStyle} from 'react-native'
import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated'
import {type AppBskyEmbedImages} from '@atproto/api'
import {atoms as a, useBreakpoints} from '#/alf'
import {type Dimensions} from '#/components/Lightbox/types'
import {PostEmbedViewContext} from '#/components/Post/Embed/types'
import {GalleryItem} from './ImageLayoutGridItem'
interface ImageLayoutGridProps {
images: AppBskyEmbedImages.ViewImage[]
onPress?: (
index: number,
containerRefs: AnimatedRef<any>[],
fetchedDims: (Dimensions | null)[],
) => void
onLongPress?: (index: number) => void
onPressIn?: (index: number) => void
style?: StyleProp<ViewStyle>
viewContext?: PostEmbedViewContext
isWithinQuote?: boolean
}
export function ImageLayoutGrid({
style,
isWithinQuote: isWithinQuoteProp,
...props
}: ImageLayoutGridProps) {
const {gtMobile} = useBreakpoints()
const isWithinQuote =
isWithinQuoteProp ??
props.viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
const gap = isWithinQuote ? (gtMobile ? a.gap_xs : a.gap_2xs) : a.gap_xs
return (
<View style={style}>
<View style={[gap, a.rounded_md, a.overflow_hidden]}>
<ImageLayoutGridInner
{...props}
gap={gap}
isWithinQuote={isWithinQuote}
/>
</View>
</View>
)
}
interface ImageLayoutGridInnerProps {
images: AppBskyEmbedImages.ViewImage[]
onPress?: (
index: number,
containerRefs: AnimatedRef<any>[],
fetchedDims: (Dimensions | null)[],
) => void
onLongPress?: (index: number) => void
onPressIn?: (index: number) => void
viewContext?: PostEmbedViewContext
isWithinQuote?: boolean
gap: {gap: number}
}
function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
const gap = props.gap
const count = props.images.length
const containerRef1 = useAnimatedRef()
const containerRef2 = useAnimatedRef()
const containerRef3 = useAnimatedRef()
const containerRef4 = useAnimatedRef()
const thumbDimsRef = useRef<(Dimensions | null)[]>([])
switch (count) {
case 2: {
const containerRefs = [containerRef1, containerRef2]
return (
<View style={[a.flex_1, a.flex_row, gap]}>
<View style={[a.flex_1, a.aspect_square]}>
<GalleryItem
{...props}
index={0}
insetBorderStyle={noCorners(['topRight', 'bottomRight'])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
<View style={[a.flex_1, a.aspect_square]}>
<GalleryItem
{...props}
index={1}
insetBorderStyle={noCorners(['topLeft', 'bottomLeft'])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
</View>
)
}
case 3: {
const containerRefs = [containerRef1, containerRef2, containerRef3]
return (
<View style={[a.flex_1, a.flex_row, gap]}>
<View style={[a.flex_1, a.aspect_square]}>
<GalleryItem
{...props}
index={0}
insetBorderStyle={noCorners(['topRight', 'bottomRight'])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
<View style={[a.flex_1, a.aspect_square, gap]}>
<View style={[a.flex_1]}>
<GalleryItem
{...props}
index={1}
insetBorderStyle={noCorners([
'topLeft',
'bottomLeft',
'bottomRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
<View style={[a.flex_1]}>
<GalleryItem
{...props}
index={2}
insetBorderStyle={noCorners([
'topLeft',
'bottomLeft',
'topRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
</View>
</View>
)
}
case 4: {
const containerRefs = [
containerRef1,
containerRef2,
containerRef3,
containerRef4,
]
return (
<>
<View style={[a.flex_row, gap]}>
<View style={[a.flex_1, {aspectRatio: 1.5}]}>
<GalleryItem
{...props}
index={0}
insetBorderStyle={noCorners([
'bottomLeft',
'topRight',
'bottomRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
<View style={[a.flex_1, {aspectRatio: 1.5}]}>
<GalleryItem
{...props}
index={1}
insetBorderStyle={noCorners([
'topLeft',
'bottomLeft',
'bottomRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
</View>
<View style={[a.flex_row, gap]}>
<View style={[a.flex_1, {aspectRatio: 1.5}]}>
<GalleryItem
{...props}
index={2}
insetBorderStyle={noCorners([
'topLeft',
'topRight',
'bottomRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
<View style={[a.flex_1, {aspectRatio: 1.5}]}>
<GalleryItem
{...props}
index={3}
insetBorderStyle={noCorners([
'topLeft',
'bottomLeft',
'topRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
</View>
</>
)
}
default:
return null
}
}
function noCorners(
corners: ('topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight')[],
) {
const styles: StyleProp<ViewStyle>[] = []
if (corners.includes('topLeft')) {
styles.push({borderTopLeftRadius: 0})
}
if (corners.includes('topRight')) {
styles.push({borderTopRightRadius: 0})
}
if (corners.includes('bottomLeft')) {
styles.push({borderBottomLeftRadius: 0})
}
if (corners.includes('bottomRight')) {
styles.push({borderBottomRightRadius: 0})
}
return styles
}