tooot/src/components/GracefullyImage.tsx

140 lines
3.5 KiB
TypeScript

import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useMemo, useRef, useState } from 'react'
import {
Image,
ImageStyle,
Pressable,
StyleProp,
StyleSheet,
ViewStyle
} from 'react-native'
import { Blurhash } from 'react-native-blurhash'
// blurhas -> if blurhash, show before any loading succeed
// original -> load original
// original, remote -> if original failed, then remote
// preview, original -> first show preview, then original
// preview, original, remote -> first show preview, then original, if original failed, then remote
export interface Props {
hidden?: boolean
uri: { preview?: string; original?: string; remote?: string }
blurhash?: string
dimension?: { width: number; height: number }
onPress?: () => void
style?: StyleProp<ViewStyle>
imageStyle?: StyleProp<ImageStyle>
// For image viewer when there is no image size available
setImageDimensions?: React.Dispatch<
React.SetStateAction<{
width: number
height: number
}>
>
}
const GracefullyImage = React.memo(
({
hidden = false,
uri,
blurhash,
dimension,
onPress,
style,
imageStyle,
setImageDimensions
}: Props) => {
const { theme } = useTheme()
const originalFailed = useRef(false)
const [imageLoaded, setImageLoaded] = useState(false)
const source = useMemo(() => {
if (originalFailed.current) {
return { uri: uri.remote || undefined }
} else {
return { uri: uri.original }
}
}, [originalFailed.current])
const onLoad = useCallback(
({ nativeEvent }) => {
setImageLoaded(true)
setImageDimensions &&
setImageDimensions({
width: nativeEvent.width,
height: nativeEvent.height
})
},
[source.uri]
)
const onError = useCallback(() => {
if (!originalFailed.current) {
originalFailed.current = true
}
}, [originalFailed.current])
const previewView = useMemo(
() =>
uri.preview && !imageLoaded ? (
<Image
source={{ uri: uri.preview }}
style={[{ flex: 1 }, imageStyle]}
/>
) : null,
[imageLoaded]
)
const originalView = useMemo(
() => (
<Image
source={source}
style={[{ flex: 1 }, imageStyle]}
onLoad={onLoad}
onError={onError}
/>
),
[source]
)
const blurhashView = useMemo(() => {
return blurhash && (hidden || !imageLoaded) ? (
<Blurhash decodeAsync blurhash={blurhash} style={styles.blurhash} />
) : null
}, [hidden, imageLoaded])
return (
<Pressable
style={[
style,
dimension,
{ backgroundColor: theme.backgroundOverlayDefault }
]}
{...(onPress
? hidden
? { disabled: true }
: { onPress }
: { disabled: true })}
>
{previewView}
{originalView}
{blurhashView}
</Pressable>
)
},
(prev, next) =>
prev.hidden === next.hidden &&
prev.uri.preview === next.uri.preview &&
prev.uri.original === next.uri.original &&
prev.uri.remote === next.uri.remote
)
const styles = StyleSheet.create({
blurhash: {
width: '100%',
height: '100%',
position: 'absolute',
top: StyleConstants.Spacing.XS / 2,
left: StyleConstants.Spacing.XS / 2
}
})
export default GracefullyImage