diff options
author | dan <dan.abramov@gmail.com> | 2024-10-29 21:00:28 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-10-29 21:00:28 +0000 |
commit | ab492cd77a2588c58899793d5a51c7d4dd0a4968 (patch) | |
tree | 92ba094ba42e291913edb0f7e977a8c2216cfbb2 | |
parent | 339f45ccbb043b9b2f46a459af4dfb368dfb705d (diff) | |
download | voidsky-ab492cd77a2588c58899793d5a51c7d4dd0a4968.tar.zst |
Show almost-instant preview when opening lightbox (#6000)
* Plumb thumbUri down to the lightbox * Remove onLoad tracking from lightbox * Hook up placeholder URI to the image * Fix NaN causing crash on double tap while offline * Protect against NaNs in the future
9 files changed, 56 insertions, 78 deletions
diff --git a/src/state/lightbox.tsx b/src/state/lightbox.tsx index a97164327..0760d2c96 100644 --- a/src/state/lightbox.tsx +++ b/src/state/lightbox.tsx @@ -10,6 +10,7 @@ type ProfileImageLightbox = { type ImagesLightboxItem = { uri: string + thumbUri: string alt?: string } diff --git a/src/view/com/lightbox/ImageViewing/@types/index.ts b/src/view/com/lightbox/ImageViewing/@types/index.ts index 8400e12e4..8fdc3f364 100644 --- a/src/view/com/lightbox/ImageViewing/@types/index.ts +++ b/src/view/com/lightbox/ImageViewing/@types/index.ts @@ -16,4 +16,4 @@ export type Position = { y: number } -export type ImageSource = {uri: string; alt?: string} +export type ImageSource = {uri: string; thumbUri: string; alt?: string} diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx index 814e39fa9..d4ce0f735 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx @@ -34,7 +34,6 @@ const SCREEN = { const MIN_DOUBLE_TAP_SCALE = 2 const MAX_ORIGINAL_IMAGE_ZOOM = 2 -const AnimatedImage = Animated.createAnimatedComponent(Image) const initialTransform = createTransform() type Props = { @@ -53,7 +52,6 @@ const ImageItem = ({ isScrollViewBeingDragged, }: Props) => { const [isScaled, setIsScaled] = useState(false) - const [isLoaded, setIsLoaded] = useState(false) const imageDimensions = useImageDimensions(imageSrc) const committedTransform = useSharedValue(initialTransform) const panTranslation = useSharedValue({x: 0, y: 0}) @@ -313,20 +311,23 @@ const ImageItem = ({ singleTap, ) - const isLoading = !isLoaded || !imageDimensions return ( - <Animated.View ref={containerRef} style={styles.container}> - {isLoading && ( - <ActivityIndicator size="small" color="#FFF" style={styles.loading} /> - )} + <Animated.View + ref={containerRef} + // Necessary to make opacity work for both children together. + renderToHardwareTextureAndroid + style={[styles.container, animatedStyle]}> + <ActivityIndicator size="small" color="#FFF" style={styles.loading} /> <GestureDetector gesture={composedGesture}> - <AnimatedImage + <Image contentFit="contain" source={{uri: imageSrc.uri}} - style={[styles.image, animatedStyle]} + placeholderContentFit="contain" + placeholder={{uri: imageSrc.thumbUri}} + style={styles.image} accessibilityLabel={imageSrc.alt} accessibilityHint="" - onLoad={() => setIsLoaded(true)} + accessibilityIgnoresInvertColors cachePolicy="memory" /> </GestureDetector> diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx index 383490f4f..c81943948 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx @@ -7,9 +7,8 @@ */ import React, {useState} from 'react' - -import {Dimensions, StyleSheet} from 'react-native' -import {Image} from 'expo-image' +import {ActivityIndicator, Dimensions, StyleSheet} from 'react-native' +import {Gesture, GestureDetector} from 'react-native-gesture-handler' import Animated, { interpolate, runOnJS, @@ -17,14 +16,12 @@ import Animated, { useAnimatedStyle, useSharedValue, } from 'react-native-reanimated' -import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' -import {Gesture, GestureDetector} from 'react-native-gesture-handler' +import {Image} from 'expo-image' +import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' +import {Dimensions as ImageDimensions, ImageSource} from '../../@types' import useImageDimensions from '../../hooks/useImageDimensions' -import {ImageSource, Dimensions as ImageDimensions} from '../../@types' -import {ImageLoading} from './ImageLoading' - const SWIPE_CLOSE_OFFSET = 75 const SWIPE_CLOSE_VELOCITY = 1 const SCREEN = Dimensions.get('screen') @@ -40,8 +37,6 @@ type Props = { showControls: boolean } -const AnimatedImage = Animated.createAnimatedComponent(Image) - const ImageItem = ({ imageSrc, onTap, @@ -51,7 +46,6 @@ const ImageItem = ({ }: Props) => { const scrollViewRef = useAnimatedRef<Animated.ScrollView>() const translationY = useSharedValue(0) - const [loaded, setLoaded] = useState(false) const [scaled, setScaled] = useState(false) const imageDimensions = useImageDimensions(imageSrc) const maxZoomScale = imageDimensions @@ -141,18 +135,21 @@ const ImageItem = ({ showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} maximumZoomScale={maxZoomScale} - contentContainerStyle={styles.imageScrollContainer} onScroll={scrollHandler}> - {(!loaded || !imageDimensions) && <ImageLoading />} - <AnimatedImage - contentFit="contain" - source={{uri: imageSrc.uri}} - style={[styles.image, animatedStyle]} - accessibilityLabel={imageSrc.alt} - accessibilityHint="" - onLoad={() => setLoaded(true)} - enableLiveTextInteraction={showControls && !scaled} - /> + <Animated.View style={[styles.imageScrollContainer, animatedStyle]}> + <ActivityIndicator size="small" color="#FFF" style={styles.loading} /> + <Image + contentFit="contain" + source={{uri: imageSrc.uri}} + placeholderContentFit="contain" + placeholder={{uri: imageSrc.thumbUri}} + style={styles.image} + accessibilityLabel={imageSrc.alt} + accessibilityHint="" + enableLiveTextInteraction={showControls && !scaled} + accessibilityIgnoresInvertColors + /> + </Animated.View> </Animated.ScrollView> </GestureDetector> ) @@ -170,6 +167,13 @@ const styles = StyleSheet.create({ width: SCREEN.width, height: SCREEN.height, }, + loading: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + }, }) const getZoomRectAfterDoubleTap = ( diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageLoading.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageLoading.tsx deleted file mode 100644 index 9667fcaa7..000000000 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageLoading.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) JOB TODAY S.A. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import React from 'react' - -import {ActivityIndicator, Dimensions, StyleSheet, View} from 'react-native' - -const SCREEN = Dimensions.get('screen') -const SCREEN_WIDTH = SCREEN.width -const SCREEN_HEIGHT = SCREEN.height - -export const ImageLoading = () => ( - <View style={styles.loading}> - <ActivityIndicator size="small" color="#FFF" /> - </View> -) - -const styles = StyleSheet.create({ - listItem: { - width: SCREEN_WIDTH, - height: SCREEN_HEIGHT, - }, - loading: { - width: SCREEN_WIDTH, - height: SCREEN_HEIGHT, - alignItems: 'center', - justifyContent: 'center', - }, - imageScrollContainer: { - height: SCREEN_HEIGHT, - }, -}) diff --git a/src/view/com/lightbox/ImageViewing/hooks/useImageDimensions.ts b/src/view/com/lightbox/ImageViewing/hooks/useImageDimensions.ts index cb46fd0d9..8b5bc1b87 100644 --- a/src/view/com/lightbox/ImageViewing/hooks/useImageDimensions.ts +++ b/src/view/com/lightbox/ImageViewing/hooks/useImageDimensions.ts @@ -8,6 +8,7 @@ import {useEffect, useState} from 'react' import {Image, ImageURISource} from 'react-native' + import {Dimensions, ImageSource} from '../@types' const CACHE_SIZE = 50 @@ -36,8 +37,9 @@ const imageDimensionsCache = createCache(CACHE_SIZE) const useImageDimensions = (image: ImageSource): Dimensions | null => { const [dimensions, setDimensions] = useState<Dimensions | null>(null) - // eslint-disable-next-line @typescript-eslint/no-shadow - const getImageDimensions = (image: ImageSource): Promise<Dimensions> => { + const getImageDimensions = ( + image: ImageSource, + ): Promise<Dimensions | null> => { return new Promise(resolve => { if (image.uri) { const source = image as ImageURISource @@ -51,16 +53,20 @@ const useImageDimensions = (image: ImageSource): Dimensions | null => { source.uri, source.headers, (width: number, height: number) => { - imageDimensionsCache.set(cacheKey, {width, height}) - resolve({width, height}) + if (width > 0 && height > 0) { + imageDimensionsCache.set(cacheKey, {width, height}) + resolve({width, height}) + } else { + resolve(null) + } }, () => { - resolve({width: 0, height: 0}) + resolve(null) }, ) } } else { - resolve({width: 0, height: 0}) + resolve(null) } }) } diff --git a/src/view/com/lightbox/Lightbox.tsx b/src/view/com/lightbox/Lightbox.tsx index b6bc670c1..a7f8fed77 100644 --- a/src/view/com/lightbox/Lightbox.tsx +++ b/src/view/com/lightbox/Lightbox.tsx @@ -31,7 +31,9 @@ export function Lightbox() { const opts = activeLightbox return ( <ImageView - images={[{uri: opts.profile.avatar || ''}]} + images={[ + {uri: opts.profile.avatar || '', thumbUri: opts.profile.avatar || ''}, + ]} initialImageIndex={0} visible onRequestClose={onClose} diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index 6b267c6da..09f074e50 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -72,7 +72,7 @@ export function ProfileSubpageHeader({ ) { openLightbox({ type: 'images', - images: [{uri: avatar}], + images: [{uri: avatar, thumbUri: avatar}], index: 0, }) } diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index 5100e7032..575b26694 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -134,6 +134,7 @@ export function PostEmbeds({ if (images.length > 0) { const items = embed.images.map(img => ({ uri: img.fullsize, + thumbUri: img.thumb, alt: img.alt, aspectRatio: img.aspectRatio, })) |