Improve image layout

zio/stable
Paul Frazee 2022-12-30 11:55:25 -06:00
parent f609794bd2
commit 55500e2f66
3 changed files with 192 additions and 79 deletions

View File

@ -3,11 +3,10 @@ import {ImageStyle, StyleSheet, StyleProp, View, ViewStyle} from 'react-native'
import {AppBskyEmbedImages, AppBskyEmbedExternal} from '@atproto/api'
import {Link} from '../util/Link'
import {Text} from './text/Text'
import {colors} from '../../lib/styles'
import {AutoSizedImage} from './images/AutoSizedImage'
import {ImageLayoutGrid} from './images/ImageLayoutGrid'
import {ImagesLightbox} from '../../../state/models/shell-ui'
import {useStores} from '../../../state'
import {useTheme} from '../../lib/ThemeContext'
import {usePalette} from '../../lib/hooks/usePalette'
type Embed =
@ -22,7 +21,6 @@ export function PostEmbeds({
embed?: Embed
style?: StyleProp<ViewStyle>
}) {
const theme = useTheme()
const pal = usePalette('default')
const store = useStores()
if (embed?.$type === 'app.bsky.embed.images#presented') {
@ -32,59 +30,44 @@ export function PostEmbeds({
const openLightbox = (index: number) => {
store.shell.openLightbox(new ImagesLightbox(uris, index))
}
const Thumb = ({i, style}: {i: number; style: StyleProp<ImageStyle>}) => (
<AutoSizedImage
style={style}
uri={imgEmbed.images[i].thumb}
onPress={() => openLightbox(i)}
/>
)
if (imgEmbed.images.length === 4) {
return (
<View style={styles.imagesContainer}>
<View style={styles.imagePair}>
<Thumb i={0} style={styles.imagePairItem} />
<View style={styles.imagesWidthSpacer} />
<Thumb i={1} style={styles.imagePairItem} />
</View>
<View style={styles.imagesHeightSpacer} />
<View style={styles.imagePair}>
<Thumb i={2} style={styles.imagePairItem} />
<View style={styles.imagesWidthSpacer} />
<Thumb i={3} style={styles.imagePairItem} />
</View>
<ImageLayoutGrid
type="four"
uris={imgEmbed.images.map(img => img.thumb)}
onPress={openLightbox}
/>
</View>
)
} else if (imgEmbed.images.length === 3) {
return (
<View style={styles.imagesContainer}>
<View style={styles.imageWide}>
<Thumb i={0} style={styles.imageWideItem} />
</View>
<View style={styles.imagesHeightSpacer} />
<View style={styles.imagePair}>
<Thumb i={1} style={styles.imagePairItem} />
<View style={styles.imagesWidthSpacer} />
<Thumb i={2} style={styles.imagePairItem} />
</View>
<ImageLayoutGrid
type="three"
uris={imgEmbed.images.map(img => img.thumb)}
onPress={openLightbox}
/>
</View>
)
} else if (imgEmbed.images.length === 2) {
return (
<View style={styles.imagesContainer}>
<View style={styles.imagePair}>
<Thumb i={0} style={styles.imagePairItem} />
<View style={styles.imagesWidthSpacer} />
<Thumb i={1} style={styles.imagePairItem} />
</View>
<ImageLayoutGrid
type="two"
uris={imgEmbed.images.map(img => img.thumb)}
onPress={openLightbox}
/>
</View>
)
} else {
return (
<View style={styles.imagesContainer}>
<View style={styles.imageBig}>
<Thumb i={0} style={styles.imageBigItem} />
</View>
<AutoSizedImage
uri={imgEmbed.images[0].thumb}
onPress={() => openLightbox(0)}
containerStyle={{borderRadius: 4}}
/>
</View>
)
}
@ -99,7 +82,7 @@ export function PostEmbeds({
href={link.uri}
noFeedback>
{link.thumb ? (
<AutoSizedImage style={style} uri={link.thumb} />
<AutoSizedImage uri={link.thumb} containerStyle={{borderRadius: 4}} />
) : undefined}
<Text type="h5" numberOfLines={1} style={pal.text}>
{link.title || link.uri}
@ -123,34 +106,10 @@ export function PostEmbeds({
const styles = StyleSheet.create({
imagesContainer: {
marginTop: 4,
marginBottom: 6,
},
imagesWidthSpacer: {
width: 5,
},
imagesHeightSpacer: {
height: 5,
},
imagePair: {
flexDirection: 'row',
},
imagePairItem: {
resizeMode: 'contain',
flex: 1,
borderRadius: 4,
},
imageWide: {},
imageWideItem: {
resizeMode: 'contain',
borderRadius: 4,
},
imageBig: {},
imageBigItem: {
borderRadius: 4,
},
extOuter: {
borderRadius: 8,
padding: 10,
},
extDescription: {

View File

@ -10,7 +10,8 @@ import {
ViewStyle,
} from 'react-native'
import {Text} from '../text/Text'
import {colors} from '../../../lib/styles'
import {useTheme} from '../../../lib/ThemeContext'
import {usePalette} from '../../../lib/hooks/usePalette'
const MAX_HEIGHT = 300
@ -23,12 +24,16 @@ export function AutoSizedImage({
uri,
onPress,
style,
containerStyle,
}: {
uri: string
onPress?: () => void
style: StyleProp<ImageStyle>
style?: StyleProp<ImageStyle>
containerStyle?: StyleProp<ViewStyle>
}) {
const [error, setError] = useState<string | undefined>()
const theme = useTheme()
const errPal = usePalette('error')
const [error, setError] = useState<string | undefined>('')
const [imgInfo, setImgInfo] = useState<Dim | undefined>()
const [containerInfo, setContainerInfo] = useState<Dim | undefined>()
@ -77,15 +82,22 @@ export function AutoSizedImage({
<View style={style}>
<TouchableWithoutFeedback onPress={onPress}>
{error ? (
<View style={[styles.container, styles.errorContainer]}>
<Text style={styles.error}>{error}</Text>
<View style={[styles.errorContainer, errPal.view, containerStyle]}>
<Text style={errPal.text}>{error}</Text>
</View>
) : calculatedStyle ? (
<View style={styles.container}>
<View style={[styles.container, containerStyle]}>
<Image style={calculatedStyle} source={{uri}} />
</View>
) : (
<View style={[style, styles.placeholder]} onLayout={onLayout} />
<View
style={[
style,
styles.placeholder,
{backgroundColor: theme.palette.default.backgroundLight},
]}
onLayout={onLayout}
/>
)}
</TouchableWithoutFeedback>
</View>
@ -96,18 +108,12 @@ const styles = StyleSheet.create({
placeholder: {
width: '100%',
aspectRatio: 1,
backgroundColor: colors.gray1,
},
errorContainer: {
backgroundColor: colors.red1,
paddingHorizontal: 8,
paddingVertical: 4,
paddingHorizontal: 12,
paddingVertical: 8,
},
container: {
borderRadius: 8,
overflow: 'hidden',
},
error: {
color: colors.red5,
},
})

View File

@ -0,0 +1,148 @@
import React from 'react'
import {
Image,
ImageStyle,
LayoutChangeEvent,
StyleProp,
StyleSheet,
TouchableWithoutFeedback,
View,
ViewStyle,
} from 'react-native'
interface Dim {
width: number
height: number
}
export type ImageLayoutGridType = 'two' | 'three' | 'four'
export function ImageLayoutGrid({
type,
uris,
onPress,
style,
}: {
type: ImageLayoutGridType
uris: string
onPress?: (index: number) => void
style?: StyleProp<ViewStyle>
}) {
const [containerInfo, setContainerInfo] = React.useState<Dim | undefined>()
const onLayout = (evt: LayoutChangeEvent) => {
setContainerInfo({
width: evt.nativeEvent.layout.width,
height: evt.nativeEvent.layout.height,
})
}
return (
<View style={style} onLayout={onLayout}>
{containerInfo ? (
<ImageLayoutGridInner
type={type}
uris={uris}
onPress={onPress}
containerInfo={containerInfo}
/>
) : undefined}
</View>
)
}
function ImageLayoutGridInner({
type,
uris,
onPress,
containerInfo,
}: {
type: ImageLayoutGridType
uris: string
onPress?: (index: number) => void
containerInfo: Dim
}) {
const size1 = React.useMemo<ImageStyle>(() => {
if (type === 'three') {
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}
}
}, [type, containerInfo])
const size2 = React.useMemo<ImageStyle>(() => {
if (type === 'three') {
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}
}
}, [type, containerInfo])
if (type === 'two') {
return (
<View style={styles.flexRow}>
<TouchableWithoutFeedback onPress={() => onPress?.(0)}>
<Image source={{uri: uris[0]}} style={size1} />
</TouchableWithoutFeedback>
<View style={styles.wSpace} />
<TouchableWithoutFeedback onPress={() => onPress?.(1)}>
<Image source={{uri: uris[1]}} style={size1} />
</TouchableWithoutFeedback>
</View>
)
}
if (type === 'three') {
return (
<View style={styles.flexRow}>
<TouchableWithoutFeedback onPress={() => onPress?.(0)}>
<Image source={{uri: uris[0]}} style={size2} />
</TouchableWithoutFeedback>
<View style={styles.wSpace} />
<View>
<TouchableWithoutFeedback onPress={() => onPress?.(1)}>
<Image source={{uri: uris[1]}} style={size1} />
</TouchableWithoutFeedback>
<View style={{height: 5}} />
<TouchableWithoutFeedback onPress={() => onPress?.(2)}>
<Image source={{uri: uris[2]}} style={size1} />
</TouchableWithoutFeedback>
</View>
</View>
)
}
if (type === 'four') {
return (
<View style={styles.flexRow}>
<View>
<TouchableWithoutFeedback onPress={() => onPress?.(0)}>
<Image source={{uri: uris[0]}} style={size1} />
</TouchableWithoutFeedback>
<View style={styles.hSpace} />
<TouchableWithoutFeedback onPress={() => onPress?.(1)}>
<Image source={{uri: uris[1]}} style={size1} />
</TouchableWithoutFeedback>
</View>
<View style={styles.wSpace} />
<View>
<TouchableWithoutFeedback onPress={() => onPress?.(2)}>
<Image source={{uri: uris[2]}} style={size1} />
</TouchableWithoutFeedback>
<View style={styles.hSpace} />
<TouchableWithoutFeedback onPress={() => onPress?.(3)}>
<Image source={{uri: uris[3]}} style={size1} />
</TouchableWithoutFeedback>
</View>
</View>
)
}
return <View />
}
const styles = StyleSheet.create({
flexRow: {flexDirection: 'row'},
wSpace: {width: 5},
hSpace: {height: 5},
})