import React from 'react' import { TypeGuards } from '@codeleap/types' import { arePropsEqual } from '@codeleap/utils' import { Image as NativeImage } from 'react-native' import { useImageSpotlight } from '../ImageView/Spotlight' import { Touchable } from '../Touchable' import { isFile, toMultipartFile } from '../../utils' import { LoadingOverlay } from '../LoadingOverlay' import FastImage from '@d11/react-native-fast-image' import { ImageProps } from './types' import { AnyRecord, useNestedStylesByKey, IJSX, StyledComponentProps, StyledComponentWithProps } from '@codeleap/styles' import { MobileStyleRegistry } from '../../Registry' import { useStylesFor } from '../../hooks' export * from './styles' export * from './types' export const Image = React.memo((props: ImageProps) => { const { style, fast, spotlight = null, resizeMode, source, withLoadingOverlay, maintainAspectRatio, touchProps, ...imageProps } = { ...Image.defaultProps, ...props, } const [loading, setLoading] = React.useState(false) const styles = useStylesFor(Image.styleRegistryName, style) let imSource = source if (isFile(imSource)) { imSource = toMultipartFile(imSource) } else if (TypeGuards.isString(source)) { imSource = { uri: source } } const spotlightActions = useImageSpotlight(spotlight, props.source) const Wrapper = !!spotlight ? Touchable : ({ children }) => <>{children} const wrapperProps = { onPress: spotlightActions.onImagePressed, debugName: `Press spotlight image ${props.source}`, style: styles.touchable, android_ripple: null, ...touchProps, } const aspectRatioStyle = React.useMemo(() => { if (!maintainAspectRatio || !imSource) return null try { // @ts-ignore const assetSource = NativeImage.resolveAssetSource(imSource) const aspectRatio = assetSource.width / assetSource.height if (Number.isNaN(aspectRatio)) { return null } return { aspectRatio, } } catch (e) { return null } }, [maintainAspectRatio, imSource]) const loadEndedEarly = React.useRef(false) // The 60 ms delay before showing the overlay suppresses a flash when the image is already // cached and resolves synchronously — `loadEndedEarly` acts as the cancellation flag. const loadProps = React.useRef({ onLoadStart: () => { if (withLoadingOverlay) { setTimeout(() => { if (!loadEndedEarly.current) { setLoading(true) } }, 60) } }, onLoadEnd: () => { loadEndedEarly.current = true if (withLoadingOverlay) setLoading(false) }, }) const Loading = TypeGuards.isFunction(withLoadingOverlay) ? withLoadingOverlay : LoadingOverlay const showLoading = !!withLoadingOverlay const overlayStyle = useNestedStylesByKey('overlay', styles) const loadingElement = React.useMemo(() => { return showLoading ? ( ) : null }, [showLoading, loading]) if (fast) { return ( {loadingElement} ) } return ( {loadingElement} ) }, (prevProps, nextProps) => { const equal = arePropsEqual(prevProps, nextProps, { check: ['source', 'style', 'resizeMode', 'fast'] }) return equal }) as StyledComponentWithProps Image.styleRegistryName = 'Image' Image.elements = ['wrapper', 'touchable', 'overlay'] Image.rootElement = 'wrapper' Image.withVariantTypes = (styles: S) => { return Image as (props: StyledComponentProps) => IJSX } /** `fast` defaults to true, meaning FastImage is used by default for disk/memory caching. * Set to false only when you need native `Image` semantics (e.g. animated GIFs, data URIs). */ Image.defaultProps = { fast: true, resizeMode: 'contain', withLoadingOverlay: false, maintainAspectRatio: true, } MobileStyleRegistry.registerComponent(Image)