Limit height of images within posts
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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%',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -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]}
|
||||||
|
|||||||
Reference in New Issue
Block a user