Improve image layout
parent
f609794bd2
commit
55500e2f66
|
@ -3,11 +3,10 @@ import {ImageStyle, StyleSheet, StyleProp, View, ViewStyle} from 'react-native'
|
||||||
import {AppBskyEmbedImages, AppBskyEmbedExternal} from '@atproto/api'
|
import {AppBskyEmbedImages, AppBskyEmbedExternal} from '@atproto/api'
|
||||||
import {Link} from '../util/Link'
|
import {Link} from '../util/Link'
|
||||||
import {Text} from './text/Text'
|
import {Text} from './text/Text'
|
||||||
import {colors} from '../../lib/styles'
|
|
||||||
import {AutoSizedImage} from './images/AutoSizedImage'
|
import {AutoSizedImage} from './images/AutoSizedImage'
|
||||||
|
import {ImageLayoutGrid} from './images/ImageLayoutGrid'
|
||||||
import {ImagesLightbox} from '../../../state/models/shell-ui'
|
import {ImagesLightbox} from '../../../state/models/shell-ui'
|
||||||
import {useStores} from '../../../state'
|
import {useStores} from '../../../state'
|
||||||
import {useTheme} from '../../lib/ThemeContext'
|
|
||||||
import {usePalette} from '../../lib/hooks/usePalette'
|
import {usePalette} from '../../lib/hooks/usePalette'
|
||||||
|
|
||||||
type Embed =
|
type Embed =
|
||||||
|
@ -22,7 +21,6 @@ export function PostEmbeds({
|
||||||
embed?: Embed
|
embed?: Embed
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
}) {
|
}) {
|
||||||
const theme = useTheme()
|
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
if (embed?.$type === 'app.bsky.embed.images#presented') {
|
if (embed?.$type === 'app.bsky.embed.images#presented') {
|
||||||
|
@ -32,59 +30,44 @@ export function PostEmbeds({
|
||||||
const openLightbox = (index: number) => {
|
const openLightbox = (index: number) => {
|
||||||
store.shell.openLightbox(new ImagesLightbox(uris, index))
|
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) {
|
if (imgEmbed.images.length === 4) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.imagesContainer}>
|
<View style={styles.imagesContainer}>
|
||||||
<View style={styles.imagePair}>
|
<ImageLayoutGrid
|
||||||
<Thumb i={0} style={styles.imagePairItem} />
|
type="four"
|
||||||
<View style={styles.imagesWidthSpacer} />
|
uris={imgEmbed.images.map(img => img.thumb)}
|
||||||
<Thumb i={1} style={styles.imagePairItem} />
|
onPress={openLightbox}
|
||||||
</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>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
} else if (imgEmbed.images.length === 3) {
|
} else if (imgEmbed.images.length === 3) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.imagesContainer}>
|
<View style={styles.imagesContainer}>
|
||||||
<View style={styles.imageWide}>
|
<ImageLayoutGrid
|
||||||
<Thumb i={0} style={styles.imageWideItem} />
|
type="three"
|
||||||
</View>
|
uris={imgEmbed.images.map(img => img.thumb)}
|
||||||
<View style={styles.imagesHeightSpacer} />
|
onPress={openLightbox}
|
||||||
<View style={styles.imagePair}>
|
/>
|
||||||
<Thumb i={1} style={styles.imagePairItem} />
|
|
||||||
<View style={styles.imagesWidthSpacer} />
|
|
||||||
<Thumb i={2} style={styles.imagePairItem} />
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
} else if (imgEmbed.images.length === 2) {
|
} else if (imgEmbed.images.length === 2) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.imagesContainer}>
|
<View style={styles.imagesContainer}>
|
||||||
<View style={styles.imagePair}>
|
<ImageLayoutGrid
|
||||||
<Thumb i={0} style={styles.imagePairItem} />
|
type="two"
|
||||||
<View style={styles.imagesWidthSpacer} />
|
uris={imgEmbed.images.map(img => img.thumb)}
|
||||||
<Thumb i={1} style={styles.imagePairItem} />
|
onPress={openLightbox}
|
||||||
</View>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<View style={styles.imagesContainer}>
|
<View style={styles.imagesContainer}>
|
||||||
<View style={styles.imageBig}>
|
<AutoSizedImage
|
||||||
<Thumb i={0} style={styles.imageBigItem} />
|
uri={imgEmbed.images[0].thumb}
|
||||||
</View>
|
onPress={() => openLightbox(0)}
|
||||||
|
containerStyle={{borderRadius: 4}}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -99,7 +82,7 @@ export function PostEmbeds({
|
||||||
href={link.uri}
|
href={link.uri}
|
||||||
noFeedback>
|
noFeedback>
|
||||||
{link.thumb ? (
|
{link.thumb ? (
|
||||||
<AutoSizedImage style={style} uri={link.thumb} />
|
<AutoSizedImage uri={link.thumb} containerStyle={{borderRadius: 4}} />
|
||||||
) : undefined}
|
) : undefined}
|
||||||
<Text type="h5" numberOfLines={1} style={pal.text}>
|
<Text type="h5" numberOfLines={1} style={pal.text}>
|
||||||
{link.title || link.uri}
|
{link.title || link.uri}
|
||||||
|
@ -123,34 +106,10 @@ export function PostEmbeds({
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
imagesContainer: {
|
imagesContainer: {
|
||||||
|
marginTop: 4,
|
||||||
marginBottom: 6,
|
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: {
|
extOuter: {
|
||||||
borderRadius: 8,
|
|
||||||
padding: 10,
|
padding: 10,
|
||||||
},
|
},
|
||||||
extDescription: {
|
extDescription: {
|
||||||
|
|
|
@ -10,7 +10,8 @@ import {
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {Text} from '../text/Text'
|
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
|
const MAX_HEIGHT = 300
|
||||||
|
|
||||||
|
@ -23,12 +24,16 @@ export function AutoSizedImage({
|
||||||
uri,
|
uri,
|
||||||
onPress,
|
onPress,
|
||||||
style,
|
style,
|
||||||
|
containerStyle,
|
||||||
}: {
|
}: {
|
||||||
uri: string
|
uri: string
|
||||||
onPress?: () => void
|
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 [imgInfo, setImgInfo] = useState<Dim | undefined>()
|
||||||
const [containerInfo, setContainerInfo] = useState<Dim | undefined>()
|
const [containerInfo, setContainerInfo] = useState<Dim | undefined>()
|
||||||
|
|
||||||
|
@ -77,15 +82,22 @@ export function AutoSizedImage({
|
||||||
<View style={style}>
|
<View style={style}>
|
||||||
<TouchableWithoutFeedback onPress={onPress}>
|
<TouchableWithoutFeedback onPress={onPress}>
|
||||||
{error ? (
|
{error ? (
|
||||||
<View style={[styles.container, styles.errorContainer]}>
|
<View style={[styles.errorContainer, errPal.view, containerStyle]}>
|
||||||
<Text style={styles.error}>{error}</Text>
|
<Text style={errPal.text}>{error}</Text>
|
||||||
</View>
|
</View>
|
||||||
) : calculatedStyle ? (
|
) : calculatedStyle ? (
|
||||||
<View style={styles.container}>
|
<View style={[styles.container, containerStyle]}>
|
||||||
<Image style={calculatedStyle} source={{uri}} />
|
<Image style={calculatedStyle} source={{uri}} />
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View style={[style, styles.placeholder]} onLayout={onLayout} />
|
<View
|
||||||
|
style={[
|
||||||
|
style,
|
||||||
|
styles.placeholder,
|
||||||
|
{backgroundColor: theme.palette.default.backgroundLight},
|
||||||
|
]}
|
||||||
|
onLayout={onLayout}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
</View>
|
</View>
|
||||||
|
@ -96,18 +108,12 @@ const styles = StyleSheet.create({
|
||||||
placeholder: {
|
placeholder: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
backgroundColor: colors.gray1,
|
|
||||||
},
|
},
|
||||||
errorContainer: {
|
errorContainer: {
|
||||||
backgroundColor: colors.red1,
|
paddingHorizontal: 12,
|
||||||
paddingHorizontal: 8,
|
paddingVertical: 8,
|
||||||
paddingVertical: 4,
|
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
borderRadius: 8,
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
error: {
|
|
||||||
color: colors.red5,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -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},
|
||||||
|
})
|
Loading…
Reference in New Issue