[Lightbox] Always rely on Expo Image cache (#6189)

* Inline useImageAspectRatio

* Switch AutoSizedImage to read dimensions from Expo Image cache

* Include thumbnail dimensions in image data

* Use dims from Expo Image cache in lightbox

* Fix wiring so all thumbnails get dimensions

* Fix type

* Oops
This commit is contained in:
dan
2024-11-09 22:41:14 +00:00
committed by GitHub
parent 2d73c5a24c
commit 42abd97f61
12 changed files with 105 additions and 146 deletions
+30 -37
View File
@@ -6,8 +6,7 @@ import {AppBskyEmbedImages} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useImageDimensions} from '#/lib/media/image-sizes'
import {Dimensions} from '#/lib/media/types'
import type {Dimensions} from '#/lib/media/types'
import {isNative} from '#/platform/detection'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
@@ -15,30 +14,6 @@ import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/compone
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography'
function useImageAspectRatio({
src,
knownDimensions,
}: {
src: string
knownDimensions: Dimensions | null
}) {
const [raw] = useImageDimensions({src, knownDimensions})
let constrained: number | undefined
let max: number | undefined
let isCropped: boolean | undefined
if (raw !== undefined) {
const ratio = 1 / 2 // max of 1:2 ratio in feeds
constrained = Math.max(raw, ratio)
max = Math.max(raw, 0.25) // max of 1:4 in thread
isCropped = raw < constrained
}
return {
constrained,
max,
isCropped,
}
}
export function ConstrainedImage({
aspectRatio,
fullBleed,
@@ -93,23 +68,38 @@ export function AutoSizedImage({
image: AppBskyEmbedImages.ViewImage
crop?: 'none' | 'square' | 'constrained'
hideBadge?: boolean
onPress?: (containerRef: AnimatedRef<React.Component<{}, {}, any>>) => void
onPress?: (
containerRef: AnimatedRef<React.Component<{}, {}, any>>,
fetchedDims: Dimensions | null,
) => void
onLongPress?: () => void
onPressIn?: () => void
}) {
const t = useTheme()
const {_} = useLingui()
const largeAlt = useLargeAltBadgeEnabled()
const {
constrained,
max,
isCropped: rawIsCropped,
} = useImageAspectRatio({
src: image.thumb,
knownDimensions: image.aspectRatio ?? null,
})
const containerRef = useAnimatedRef()
const [fetchedDims, setFetchedDims] = React.useState<Dimensions | null>(null)
const dims = fetchedDims ?? image.aspectRatio
let aspectRatio: number | undefined
if (dims) {
aspectRatio = dims.width / dims.height
if (Number.isNaN(aspectRatio)) {
aspectRatio = undefined
}
}
let constrained: number | undefined
let max: number | undefined
let rawIsCropped: boolean | undefined
if (aspectRatio !== undefined) {
const ratio = 1 / 2 // max of 1:2 ratio in feeds
constrained = Math.max(aspectRatio, ratio)
max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread
rawIsCropped = aspectRatio < constrained
}
const cropDisabled = crop === 'none'
const isCropped = rawIsCropped && !cropDisabled
const hasAlt = !!image.alt
@@ -123,6 +113,9 @@ export function AutoSizedImage({
accessibilityIgnoresInvertColors
accessibilityLabel={image.alt}
accessibilityHint=""
onLoad={e => {
setFetchedDims({width: e.source.width, height: e.source.height})
}}
/>
<MediaInsetBorder />
@@ -194,7 +187,7 @@ export function AutoSizedImage({
if (cropDisabled) {
return (
<Pressable
onPress={() => onPress?.(containerRef)}
onPress={() => onPress?.(containerRef, fetchedDims)}
onLongPress={onLongPress}
onPressIn={onPressIn}
// alt here is what screen readers actually use
@@ -216,7 +209,7 @@ export function AutoSizedImage({
fullBleed={crop === 'square'}
aspectRatio={constrained ?? 1}>
<Pressable
onPress={() => onPress?.(containerRef)}
onPress={() => onPress?.(containerRef, fetchedDims)}
onLongPress={onLongPress}
onPressIn={onPressIn}
// alt here is what screen readers actually use
+15 -1
View File
@@ -6,6 +6,7 @@ import {AppBskyEmbedImages} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {Dimensions} from '#/lib/media/types'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
import {atoms as a, useTheme} from '#/alf'
@@ -20,6 +21,7 @@ interface Props {
onPress?: (
index: number,
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
fetchedDims: (Dimensions | null)[],
) => void
onLongPress?: EventFunction
onPressIn?: EventFunction
@@ -27,6 +29,7 @@ interface Props {
viewContext?: PostEmbedViewContext
insetBorderStyle?: StyleProp<ViewStyle>
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[]
thumbDimsRef: React.MutableRefObject<(Dimensions | null)[]>
}
export function GalleryItem({
@@ -39,6 +42,7 @@ export function GalleryItem({
viewContext,
insetBorderStyle,
containerRefs,
thumbDimsRef,
}: Props) {
const t = useTheme()
const {_} = useLingui()
@@ -53,7 +57,11 @@ export function GalleryItem({
ref={containerRefs[index]}
collapsable={false}>
<Pressable
onPress={onPress ? () => onPress(index, containerRefs) : undefined}
onPress={
onPress
? () => onPress(index, containerRefs, thumbDimsRef.current.slice())
: undefined
}
onPressIn={onPressIn ? () => onPressIn(index) : undefined}
onLongPress={onLongPress ? () => onLongPress(index) : undefined}
style={[
@@ -72,6 +80,12 @@ export function GalleryItem({
accessibilityLabel={image.alt}
accessibilityHint=""
accessibilityIgnoresInvertColors
onLoad={e => {
thumbDimsRef.current[index] = {
width: e.source.width,
height: e.source.height,
}
}}
/>
<MediaInsetBorder style={insetBorderStyle} />
</Pressable>
@@ -5,6 +5,7 @@ import {AppBskyEmbedImages} from '@atproto/api'
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
import {atoms as a, useBreakpoints} from '#/alf'
import {Dimensions} from '../../lightbox/ImageViewing/@types'
import {GalleryItem} from './Gallery'
interface ImageLayoutGridProps {
@@ -12,6 +13,7 @@ interface ImageLayoutGridProps {
onPress?: (
index: number,
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
fetchedDims: (Dimensions | null)[],
) => void
onLongPress?: (index: number) => void
onPressIn?: (index: number) => void
@@ -42,6 +44,7 @@ interface ImageLayoutGridInnerProps {
onPress?: (
index: number,
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
fetchedDims: (Dimensions | null)[],
) => void
onLongPress?: (index: number) => void
onPressIn?: (index: number) => void
@@ -57,6 +60,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
const containerRef2 = useAnimatedRef()
const containerRef3 = useAnimatedRef()
const containerRef4 = useAnimatedRef()
const thumbDimsRef = React.useRef<(Dimensions | null)[]>([])
switch (count) {
case 2: {
@@ -69,6 +73,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
index={0}
insetBorderStyle={noCorners(['topRight', 'bottomRight'])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
<View style={[a.flex_1, {aspectRatio: 1}]}>
@@ -77,6 +82,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
index={1}
insetBorderStyle={noCorners(['topLeft', 'bottomLeft'])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
</View>
@@ -93,6 +99,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
index={0}
insetBorderStyle={noCorners(['topRight', 'bottomRight'])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
<View style={[a.flex_1, {aspectRatio: 1}, gap]}>
@@ -106,6 +113,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
<View style={[a.flex_1]}>
@@ -118,6 +126,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'topRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
</View>
@@ -145,6 +154,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
<View style={[a.flex_1, {aspectRatio: 1.5}]}>
@@ -157,6 +167,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
</View>
@@ -171,6 +182,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
<View style={[a.flex_1, {aspectRatio: 1.5}]}>
@@ -183,6 +195,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'topRight',
])}
containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/>
</View>
</View>
+8 -2
View File
@@ -35,6 +35,7 @@ import {atoms as a, useTheme} from '#/alf'
import * as ListCard from '#/components/ListCard'
import {Embed as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
import {ContentHider} from '../../../../components/moderation/ContentHider'
import {Dimensions} from '../../lightbox/ImageViewing/@types'
import {AutoSizedImage} from '../images/AutoSizedImage'
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
@@ -148,11 +149,13 @@ export function PostEmbeds({
const _openLightbox = (
index: number,
thumbRects: (MeasuredDimensions | null)[],
fetchedDims: (Dimensions | null)[],
) => {
openLightbox({
images: items.map((item, i) => ({
...item,
thumbRect: thumbRects[i] ?? null,
thumbDimensions: fetchedDims[i] ?? null,
type: 'image',
})),
index,
@@ -161,11 +164,12 @@ export function PostEmbeds({
const onPress = (
index: number,
refs: AnimatedRef<React.Component<{}, {}, any>>[],
fetchedDims: (Dimensions | null)[],
) => {
runOnUI(() => {
'worklet'
const rects = refs.map(ref => (ref ? measure(ref) : null))
runOnJS(_openLightbox)(index, rects)
runOnJS(_openLightbox)(index, rects, fetchedDims)
})()
}
const onPressIn = (_: number) => {
@@ -189,7 +193,9 @@ export function PostEmbeds({
: 'constrained'
}
image={image}
onPress={containerRef => onPress(0, [containerRef])}
onPress={(containerRef, dims) =>
onPress(0, [containerRef], [dims])
}
onPressIn={() => onPressIn(0)}
hideBadge={
viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia