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)