Fix layout shift for multi-image posts (#1673)

* Fix layout shift for multi-image posts

* Add a comment for the hack

* Use margins instead of gap

* Move alt label
zio/stable
dan 2023-10-12 19:14:27 +01:00 committed by GitHub
parent d98e3a8b45
commit c2a1cf4e56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 66 deletions

View File

@ -23,19 +23,19 @@ export const GalleryItem: FC<GalleryItemProps> = ({
onLongPress,
}) => {
const image = images[index]
return (
<View>
<View style={styles.fullWidth}>
<Pressable
onPress={onPress ? () => onPress(index) : undefined}
onPressIn={onPressIn ? () => onPressIn(index) : undefined}
onLongPress={onLongPress ? () => onLongPress(index) : undefined}
style={styles.fullWidth}
accessibilityRole="button"
accessibilityLabel={image.alt || 'Image'}
accessibilityHint="">
<Image
source={{uri: image.thumb}}
style={imageStyle}
style={[styles.image, imageStyle]}
accessible={true}
accessibilityLabel={image.alt}
accessibilityHint=""
@ -54,14 +54,21 @@ export const GalleryItem: FC<GalleryItemProps> = ({
}
const styles = StyleSheet.create({
fullWidth: {
flex: 1,
},
image: {
flex: 1,
borderRadius: 4,
},
altContainer: {
backgroundColor: 'rgba(0, 0, 0, 0.75)',
borderRadius: 6,
paddingHorizontal: 6,
paddingVertical: 3,
position: 'absolute',
left: 6,
bottom: 6,
left: 8,
bottom: 8,
},
alt: {
color: 'white',

View File

@ -1,13 +1,5 @@
import React, {useMemo, useState} from 'react'
import {
LayoutChangeEvent,
StyleProp,
StyleSheet,
View,
ViewStyle,
} from 'react-native'
import {ImageStyle} from 'expo-image'
import {Dimensions} from 'lib/media/types'
import React from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {AppBskyEmbedImages} from '@atproto/api'
import {GalleryItem} from './Gallery'
@ -20,21 +12,11 @@ interface ImageLayoutGridProps {
}
export function ImageLayoutGrid({style, ...props}: ImageLayoutGridProps) {
const [containerInfo, setContainerInfo] = useState<Dimensions | undefined>()
const onLayout = (evt: LayoutChangeEvent) => {
const {width, height} = evt.nativeEvent.layout
setContainerInfo({
width,
height,
})
}
return (
<View style={style} onLayout={onLayout}>
{containerInfo ? (
<ImageLayoutGridInner {...props} containerInfo={containerInfo} />
) : undefined}
<View style={style}>
<View style={styles.container}>
<ImageLayoutGridInner {...props} />
</View>
</View>
)
}
@ -44,70 +26,80 @@ interface ImageLayoutGridInnerProps {
onPress?: (index: number) => void
onLongPress?: (index: number) => void
onPressIn?: (index: number) => void
containerInfo: Dimensions
}
function ImageLayoutGridInner({
containerInfo,
...props
}: ImageLayoutGridInnerProps) {
function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
const count = props.images.length
const size1 = useMemo<ImageStyle>(() => {
if (count === 3) {
const size = (containerInfo.width - 10) / 3
return {width: size, height: size, resizeMode: 'cover', borderRadius: 4}
} else {
const size = (containerInfo.width - 5) / 2
return {width: size, height: size, resizeMode: 'cover', borderRadius: 4}
}
}, [count, containerInfo])
const size2 = React.useMemo<ImageStyle>(() => {
if (count === 3) {
const size = ((containerInfo.width - 10) / 3) * 2 + 5
return {width: size, height: size, resizeMode: 'cover', borderRadius: 4}
} else {
const size = (containerInfo.width - 5) / 2
return {width: size, height: size, resizeMode: 'cover', borderRadius: 4}
}
}, [count, containerInfo])
switch (count) {
case 2:
return (
<View style={styles.flexRow}>
<GalleryItem index={0} {...props} imageStyle={size1} />
<GalleryItem index={1} {...props} imageStyle={size1} />
<View style={styles.smallItem}>
<GalleryItem {...props} index={0} imageStyle={styles.image} />
</View>
<View style={styles.smallItem}>
<GalleryItem {...props} index={1} imageStyle={styles.image} />
</View>
</View>
)
case 3:
return (
<View style={styles.flexRow}>
<GalleryItem index={0} {...props} imageStyle={size2} />
<View style={styles.flexColumn}>
<GalleryItem index={1} {...props} imageStyle={size1} />
<GalleryItem index={2} {...props} imageStyle={size1} />
<View style={{flex: 2, aspectRatio: 1}}>
<GalleryItem {...props} index={0} imageStyle={styles.image} />
</View>
<View style={{flex: 1}}>
<View style={styles.smallItem}>
<GalleryItem {...props} index={1} imageStyle={styles.image} />
</View>
<View style={styles.smallItem}>
<GalleryItem {...props} index={2} imageStyle={styles.image} />
</View>
</View>
</View>
)
case 4:
return (
<View style={styles.flexRow}>
<View style={styles.flexColumn}>
<GalleryItem index={0} {...props} imageStyle={size1} />
<GalleryItem index={2} {...props} imageStyle={size1} />
<View style={{flex: 1}}>
<View style={styles.smallItem}>
<GalleryItem {...props} index={0} imageStyle={styles.image} />
</View>
<View style={styles.smallItem}>
<GalleryItem {...props} index={2} imageStyle={styles.image} />
</View>
</View>
<View style={{flex: 1}}>
<View style={styles.smallItem}>
<GalleryItem {...props} index={1} imageStyle={styles.image} />
</View>
<View style={styles.smallItem}>
<GalleryItem {...props} index={3} imageStyle={styles.image} />
</View>
<View style={styles.flexColumn}>
<GalleryItem index={1} {...props} imageStyle={size1} />
<GalleryItem index={3} {...props} imageStyle={size1} />
</View>
</View>
)
default:
return null
}
}
// This is used to compute margins (rather than flexbox gap) due to Yoga bugs:
// https://github.com/facebook/yoga/issues/1418
const IMAGE_GAP = 5
const styles = StyleSheet.create({
flexRow: {flexDirection: 'row', gap: 5},
flexColumn: {flexDirection: 'column', gap: 5},
container: {
marginHorizontal: -IMAGE_GAP / 2,
marginVertical: -IMAGE_GAP / 2,
},
flexRow: {flexDirection: 'row'},
smallItem: {flex: 1, aspectRatio: 1},
image: {
margin: IMAGE_GAP / 2,
},
})