All files / components/lazy LazyImage.tsx

68.42% Statements 13/19
91.66% Branches 11/12
37.5% Functions 3/8
68.42% Lines 13/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114                                                                      5x 5x   5x 1x 1x     5x 1x 1x 1x     5x 1x                   1x                             4x                                                                            
import { useCallback, useState } from 'react';
import { Image, ImageSourcePropType, ImageStyle, StyleProp, DimensionValue } from 'react-native';
import { YStack } from 'tamagui';
 
import { neutralScale } from '@/src/design-system/tokens';
 
import { Skeleton } from './Skeleton';
 
interface LazyImageProps {
  source: ImageSourcePropType;
  width: DimensionValue;
  height: DimensionValue;
  borderRadius?: number;
  resizeMode?: 'cover' | 'contain' | 'stretch' | 'center';
  placeholder?: ImageSourcePropType;
  style?: StyleProp<ImageStyle>;
  showSkeleton?: boolean;
  onLoad?: () => void;
  onError?: () => void;
  testID?: string;
}
 
export function LazyImage({
  source,
  width,
  height,
  borderRadius = 0,
  resizeMode = 'cover',
  placeholder,
  style,
  showSkeleton = true,
  onLoad,
  onError,
  testID,
}: LazyImageProps) {
  const [isLoading, setIsLoading] = useState(true);
  const [hasError, setHasError] = useState(false);
 
  const handleLoad = useCallback(() => {
    setIsLoading(false);
    onLoad?.();
  }, [onLoad]);
 
  const handleError = useCallback(() => {
    setIsLoading(false);
    setHasError(true);
    onError?.();
  }, [onError]);
 
  if (hasError) {
    Iif (placeholder) {
      return (
        <Image
          source={placeholder}
          style={[{ width, height, borderRadius }, style]}
          resizeMode={resizeMode}
          testID={testID}
        />
      );
    }
    return (
      <YStack
        width={width}
        height={height}
        backgroundColor={neutralScale.neutral2}
        borderRadius={borderRadius}
        alignItems="center"
        justifyContent="center"
        testID={testID}
      >
        {/* Optionally, you can add an error icon or text here */}
      </YStack>
    );
  }
 
  return (
    <YStack
      width={width as any}
      height={height as any}
      borderRadius={borderRadius}
      overflow="hidden"
      testID="lazy-image-container"
    >
      {showSkeleton && isLoading && (
        <Skeleton width={width as any} height={height as any} borderRadius={borderRadius} />
      )}
      <Image
        source={source}
        style={[
          { width, height, borderRadius },
          isLoading ? { opacity: 0 } : { opacity: 1 },
          style,
        ]}
        resizeMode={resizeMode}
        onLoad={handleLoad}
        onError={handleError}
        testID={testID}
      />
    </YStack>
  );
}
 
export async function preloadImage(uri: string): Promise<boolean> {
  return new Promise((resolve) => {
    Image.prefetch(uri)
      .then(() => resolve(true))
      .catch(() => resolve(false));
  });
}
 
export async function preloadImages(uris: string[]): Promise<boolean[]> {
  return Promise.all(uris.map(preloadImage));
}