Limit height of images within posts

This commit is contained in:
Eric Bailey
2024-09-03 18:19:19 -05:00
parent 0bd0146efb
commit f9dae01118
3 changed files with 133 additions and 83 deletions
+5 -1
View File
@@ -363,7 +363,11 @@ let PostThreadItemLoaded = ({
) : undefined} ) : undefined}
{post.embed && ( {post.embed && (
<View style={[a.pb_sm]}> <View style={[a.pb_sm]}>
<PostEmbeds embed={post.embed} moderation={moderation} /> <PostEmbeds
embed={post.embed}
moderation={moderation}
isHighlightedThreadItem
/>
</View> </View>
)} )}
</ContentHider> </ContentHider>
+121 -75
View File
@@ -1,106 +1,152 @@
import React from 'react' import React from 'react'
import {StyleProp, StyleSheet, Pressable, View, ViewStyle} from 'react-native' import {Pressable, View} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {clamp} from 'lib/numbers' import {AppBskyEmbedImages} from '@atproto/api'
import {Dimensions} from 'lib/media/types'
import * as imageSizes from 'lib/media/image-sizes'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
const MIN_ASPECT_RATIO = 0.33 // 1/3 import * as imageSizes from '#/lib/media/image-sizes'
const MAX_ASPECT_RATIO = 10 // 10/1 import {Dimensions} from '#/lib/media/types'
import {atoms as a, useTheme} from '#/alf'
interface Props { export function useImageAspectRatio({
alt?: string src,
uri: string dimensions,
dimensionsHint?: Dimensions }: {
onPress?: () => void src: string
onLongPress?: () => void dimensions: Dimensions | undefined
onPressIn?: () => void }) {
style?: StyleProp<ViewStyle> const [aspectRatio, setAspectRatio] = React.useState<number>(
children?: React.ReactNode dimensions ? calc(dimensions) : 1,
)
React.useEffect(() => {
let aborted = false
if (dimensions) return
imageSizes.fetch(src).then(newDim => {
if (aborted) return
setAspectRatio(calc(newDim))
})
return () => {
aborted = true
}
}, [dimensions, setAspectRatio, src])
return {
dimensions,
aspectRatio,
}
}
export function SquareFramedImage({
aspectRatio,
children,
}: {
aspectRatio: number
children: React.ReactNode
}) {
const t = useTheme()
const outerAspectRatio = React.useMemo(() => {
return Math.min(1 / aspectRatio, 1)
}, [aspectRatio])
const innerAspectRatio = React.useMemo(() => {
return Math.max(aspectRatio, 0.75)
}, [aspectRatio])
return (
<View style={[a.w_full]}>
<View
style={[a.overflow_hidden, {paddingTop: `${outerAspectRatio * 100}%`}]}>
<View style={[a.absolute, a.inset_0, a.flex_row]}>
<View
style={[
a.h_full,
a.rounded_sm,
a.overflow_hidden,
t.atoms.bg_contrast_25,
{aspectRatio: innerAspectRatio},
]}>
{children}
</View>
</View>
</View>
</View>
)
} }
export function AutoSizedImage({ export function AutoSizedImage({
alt, image,
uri, disableCrop,
dimensionsHint,
onPress, onPress,
onLongPress, onLongPress,
onPressIn, onPressIn,
style,
children = null, children = null,
}: Props) { }: {
image: AppBskyEmbedImages.ViewImage
disableCrop?: boolean
children?: React.ReactNode
onPress?: () => void
onLongPress?: () => void
onPressIn?: () => void
}) {
const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const [dim, setDim] = React.useState<Dimensions | undefined>( const {aspectRatio} = useImageAspectRatio({
dimensionsHint || imageSizes.get(uri), src: image.thumb,
) dimensions: image.aspectRatio,
const [aspectRatio, setAspectRatio] = React.useState<number>( })
dim ? calc(dim) : 1,
)
React.useEffect(() => {
let aborted = false
if (dim) {
return
}
imageSizes.fetch(uri).then(newDim => {
if (aborted) {
return
}
setDim(newDim)
setAspectRatio(calc(newDim))
})
}, [dim, setDim, setAspectRatio, uri])
if (onPress || onLongPress || onPressIn) { const contents = (
<Image
style={[a.w_full, a.h_full]}
source={image.thumb}
accessible={true} // Must set for `accessibilityLabel` to work
accessibilityIgnoresInvertColors
accessibilityLabel={image.alt}
accessibilityHint=""
/>
)
if (disableCrop) {
return ( return (
// disable a11y rule because in this case we want the tags on the image (#1640)
// eslint-disable-next-line react-native-a11y/has-valid-accessibility-descriptors
<Pressable <Pressable
onPress={onPress} onPress={onPress}
onLongPress={onLongPress} onLongPress={onLongPress}
onPressIn={onPressIn} onPressIn={onPressIn}
style={[styles.container, style]}> accessibilityLabel={image.alt}
<Image accessibilityHint={_(msg`Tap to view fully`)}
style={[styles.image, {aspectRatio}]} style={[
source={uri} a.w_full,
accessible={true} // Must set for `accessibilityLabel` to work a.rounded_sm,
accessibilityIgnoresInvertColors a.overflow_hidden,
accessibilityLabel={alt} t.atoms.bg_contrast_25,
accessibilityHint={_(msg`Tap to view fully`)} {aspectRatio},
/> ]}>
{contents}
{children} {children}
</Pressable> </Pressable>
) )
} else {
return (
<SquareFramedImage aspectRatio={aspectRatio}>
<Pressable
onPress={onPress}
onLongPress={onLongPress}
onPressIn={onPressIn}
accessibilityLabel={image.alt}
accessibilityHint={_(msg`Tap to view fully`)}
style={[a.h_full]}>
{contents}
{children}
</Pressable>
</SquareFramedImage>
)
} }
return (
<View style={[styles.container, style]}>
<Image
style={[styles.image, {aspectRatio}]}
source={{uri}}
accessible={true} // Must set for `accessibilityLabel` to work
accessibilityIgnoresInvertColors
accessibilityLabel={alt}
accessibilityHint=""
/>
{children}
</View>
)
} }
function calc(dim: Dimensions) { function calc(dim: Dimensions) {
if (dim.width === 0 || dim.height === 0) { if (dim.width === 0 || dim.height === 0) {
return 1 return 1
} }
return clamp(dim.width / dim.height, MIN_ASPECT_RATIO, MAX_ASPECT_RATIO) return dim.width / dim.height
} }
const styles = StyleSheet.create({
container: {
overflow: 'hidden',
},
image: {
width: '100%',
},
})
+7 -7
View File
@@ -50,12 +50,14 @@ export function PostEmbeds({
onOpen, onOpen,
style, style,
allowNestedQuotes, allowNestedQuotes,
isHighlightedThreadItem,
}: { }: {
embed?: Embed embed?: Embed
moderation?: ModerationDecision moderation?: ModerationDecision
onOpen?: () => void onOpen?: () => void
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
allowNestedQuotes?: boolean allowNestedQuotes?: boolean
isHighlightedThreadItem?: boolean
}) { }) {
const {openLightbox} = useLightboxControls() const {openLightbox} = useLightboxControls()
const largeAltBadge = useLargeAltBadgeEnabled() const largeAltBadge = useLargeAltBadgeEnabled()
@@ -124,18 +126,16 @@ export function PostEmbeds({
} }
if (images.length === 1) { if (images.length === 1) {
const {alt, thumb, aspectRatio} = images[0] const image = images[0]
return ( return (
<ContentHider modui={moderation?.ui('contentMedia')}> <ContentHider modui={moderation?.ui('contentMedia')}>
<View style={[styles.container, style]}> <View style={[styles.container, style]}>
<AutoSizedImage <AutoSizedImage
alt={alt} disableCrop={isHighlightedThreadItem}
uri={thumb} image={image}
dimensionsHint={aspectRatio}
onPress={() => _openLightbox(0)} onPress={() => _openLightbox(0)}
onPressIn={() => onPressIn(0)} onPressIn={() => onPressIn(0)}>
style={a.rounded_sm}> {image.alt === '' ? null : (
{alt === '' ? null : (
<View style={styles.altContainer}> <View style={styles.altContainer}>
<Text <Text
style={[styles.alt, largeAltBadge && a.text_xs]} style={[styles.alt, largeAltBadge && a.text_xs]}