tooot/src/components/GracefullyImage.tsx

171 lines
4.3 KiB
TypeScript
Raw Normal View History

2022-02-12 18:25:53 +01:00
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
2021-02-14 00:27:21 +01:00
import { useTheme } from '@utils/styles/ThemeManager'
2021-05-30 22:12:22 +02:00
import React, { useCallback, useMemo, useState } from 'react'
import {
2021-04-09 21:43:12 +02:00
AccessibilityProps,
Image,
ImageStyle,
Pressable,
StyleProp,
StyleSheet,
2021-04-01 18:39:53 +02:00
View,
ViewStyle
} from 'react-native'
2021-01-28 01:31:19 +01:00
import { Blurhash } from 'react-native-blurhash'
2021-02-14 00:27:21 +01:00
// 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
2021-01-16 00:00:31 +01:00
export interface Props {
2021-04-09 21:43:12 +02:00
accessibilityLabel?: AccessibilityProps['accessibilityLabel']
accessibilityHint?: AccessibilityProps['accessibilityHint']
2021-01-16 00:00:31 +01:00
hidden?: boolean
2022-02-12 18:25:53 +01:00
uri: { preview?: string; original?: string; remote?: string; static?: string }
2021-01-16 00:00:31 +01:00
blurhash?: string
dimension?: { width: number; height: number }
onPress?: () => void
style?: StyleProp<ViewStyle>
2021-01-23 02:41:50 +01:00
imageStyle?: StyleProp<ImageStyle>
2021-02-14 00:27:21 +01:00
// For image viewer when there is no image size available
setImageDimensions?: React.Dispatch<
React.SetStateAction<{
width: number
height: number
}>
>
2021-01-16 00:00:31 +01:00
}
2021-02-02 22:50:38 +01:00
const GracefullyImage = React.memo(
({
2021-04-09 21:43:12 +02:00
accessibilityLabel,
accessibilityHint,
2021-02-02 22:50:38 +01:00
hidden = false,
uri,
blurhash,
dimension,
onPress,
style,
2021-02-14 00:27:21 +01:00
imageStyle,
setImageDimensions
2021-02-02 22:50:38 +01:00
}: Props) => {
2022-02-12 18:25:53 +01:00
const { reduceMotionEnabled } = useAccessibility()
2022-02-12 14:51:01 +01:00
const { colors } = useTheme()
2021-05-30 22:12:22 +02:00
const [originalFailed, setOriginalFailed] = useState(false)
2021-02-14 00:27:21 +01:00
const [imageLoaded, setImageLoaded] = useState(false)
2021-01-16 00:00:31 +01:00
2021-02-14 00:27:21 +01:00
const source = useMemo(() => {
2021-05-30 22:12:22 +02:00
if (originalFailed) {
2021-02-14 00:27:21 +01:00
return { uri: uri.remote || undefined }
2021-02-02 22:50:38 +01:00
} else {
2022-02-12 18:25:53 +01:00
return {
uri: reduceMotionEnabled && uri.static ? uri.static : uri.original
}
2021-02-02 22:50:38 +01:00
}
2021-05-30 22:12:22 +02:00
}, [originalFailed])
const onLoad = useCallback(() => {
setImageLoaded(true)
if (setImageDimensions && source.uri) {
Image.getSize(source.uri, (width, height) =>
setImageDimensions({ width, height })
)
}
}, [source.uri])
2021-02-02 22:50:38 +01:00
const onError = useCallback(() => {
2021-05-30 22:12:22 +02:00
if (!originalFailed) {
setOriginalFailed(true)
2021-02-02 22:50:38 +01:00
}
2021-05-30 22:12:22 +02:00
}, [originalFailed])
2021-02-02 22:50:38 +01:00
2021-02-14 00:27:21 +01:00
const previewView = useMemo(
() =>
2021-04-19 22:50:55 +02:00
uri.preview && !imageLoaded ? (
<Image
2021-04-19 22:50:55 +02:00
fadeDuration={0}
2021-02-14 00:27:21 +01:00
source={{ uri: uri.preview }}
2021-04-19 17:15:52 +02:00
style={[
styles.placeholder,
2022-02-12 14:51:01 +01:00
{ backgroundColor: colors.shimmerDefault }
2021-04-19 17:15:52 +02:00
]}
2021-02-10 00:40:44 +01:00
/>
2021-02-14 00:27:21 +01:00
) : null,
2021-04-19 17:15:52 +02:00
[]
2021-02-14 00:27:21 +01:00
)
const originalView = useMemo(
() => (
<Image
2021-04-19 22:50:55 +02:00
fadeDuration={0}
2021-02-14 00:27:21 +01:00
source={source}
style={[{ flex: 1 }, imageStyle]}
2021-02-14 00:27:21 +01:00
onLoad={onLoad}
onError={onError}
/>
),
[source]
2021-02-14 00:27:21 +01:00
)
const blurhashView = useMemo(() => {
2021-04-01 18:39:53 +02:00
if (hidden || !imageLoaded) {
if (blurhash) {
return (
2021-04-19 17:15:52 +02:00
<Blurhash
decodeAsync
blurhash={blurhash}
style={styles.placeholder}
/>
2021-04-01 18:39:53 +02:00
)
} else {
return (
<View
2021-04-19 17:15:52 +02:00
style={[
styles.placeholder,
2022-02-12 14:51:01 +01:00
{ backgroundColor: colors.shimmerDefault }
2021-04-19 17:15:52 +02:00
]}
2021-04-01 18:39:53 +02:00
/>
)
}
} else {
return null
}
2021-02-14 00:27:21 +01:00
}, [hidden, imageLoaded])
2021-01-16 00:00:31 +01:00
2021-02-02 22:50:38 +01:00
return (
<Pressable
2021-04-09 21:43:12 +02:00
{...(onPress
? { accessibilityRole: 'imagebutton' }
: { accessibilityRole: 'image' })}
accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint}
2022-02-12 14:51:01 +01:00
style={[style, dimension, { backgroundColor: colors.shimmerDefault }]}
2021-02-02 22:50:38 +01:00
{...(onPress
? hidden
? { disabled: true }
: { onPress }
: { disabled: true })}
2021-02-14 00:27:21 +01:00
>
{previewView}
{originalView}
{blurhashView}
</Pressable>
2021-02-02 22:50:38 +01:00
)
},
2021-03-02 01:17:06 +01:00
(prev, next) =>
prev.hidden === next.hidden &&
prev.uri.preview === next.uri.preview &&
prev.uri.original === next.uri.original &&
prev.uri.remote === next.uri.remote
2021-02-02 22:50:38 +01:00
)
2021-01-16 00:00:31 +01:00
const styles = StyleSheet.create({
2021-04-19 17:15:52 +02:00
placeholder: {
2021-02-14 00:27:21 +01:00
width: '100%',
height: '100%',
position: 'absolute'
2021-01-16 00:00:31 +01:00
}
})
export default GracefullyImage