diff options
author | Paul Frazee <pfrazee@gmail.com> | 2022-12-30 11:55:25 -0600 |
---|---|---|
committer | Paul Frazee <pfrazee@gmail.com> | 2022-12-30 11:55:25 -0600 |
commit | 55500e2f66d5f30f1aedf63d4355a6b7568d4552 (patch) | |
tree | c8c9fea21991a47ad491812fb0e4f0b13dc8ce76 | |
parent | f609794bd2dec01937a31fff142efcaf44fa396b (diff) | |
download | voidsky-55500e2f66d5f30f1aedf63d4355a6b7568d4552.tar.zst |
Improve image layout
-rw-r--r-- | src/view/com/util/PostEmbeds.tsx | 87 | ||||
-rw-r--r-- | src/view/com/util/images/AutoSizedImage.tsx | 36 | ||||
-rw-r--r-- | src/view/com/util/images/ImageLayoutGrid.tsx | 148 |
3 files changed, 192 insertions, 79 deletions
diff --git a/src/view/com/util/PostEmbeds.tsx b/src/view/com/util/PostEmbeds.tsx index 870df50a7..1d5c690f6 100644 --- a/src/view/com/util/PostEmbeds.tsx +++ b/src/view/com/util/PostEmbeds.tsx @@ -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: { diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx index 9de443b7f..a711323a9 100644 --- a/src/view/com/util/images/AutoSizedImage.tsx +++ b/src/view/com/util/images/AutoSizedImage.tsx @@ -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, - }, }) diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx new file mode 100644 index 000000000..cb560dd35 --- /dev/null +++ b/src/view/com/util/images/ImageLayoutGrid.tsx @@ -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}, +}) |