mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Update image loading
This commit is contained in:
@ -1,80 +1,20 @@
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { Surface } from 'gl-react-expo'
|
||||
import { Blurhash } from 'gl-react-blurhash'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import {
|
||||
Image,
|
||||
ImageStyle,
|
||||
Pressable,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
View,
|
||||
ViewStyle
|
||||
} from 'react-native'
|
||||
import { Image as ImageCache } from 'react-native-expo-image-cache'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
|
||||
type CancelPromise = ((reason?: Error) => void) | undefined
|
||||
interface ImageSizeOperation {
|
||||
start: () => Promise<string>
|
||||
cancel: CancelPromise
|
||||
}
|
||||
const getImageSize = ({
|
||||
preview,
|
||||
original,
|
||||
remote
|
||||
}: {
|
||||
preview?: string
|
||||
original: string
|
||||
remote?: string
|
||||
}): ImageSizeOperation => {
|
||||
let cancel: CancelPromise
|
||||
const start = (): Promise<string> =>
|
||||
new Promise<string>((resolve, reject) => {
|
||||
cancel = reject
|
||||
Image.getSize(
|
||||
preview || '',
|
||||
() => {
|
||||
cancel = undefined
|
||||
resolve(preview!)
|
||||
},
|
||||
() => {
|
||||
cancel = reject
|
||||
Image.getSize(
|
||||
original,
|
||||
() => {
|
||||
cancel = undefined
|
||||
resolve(original)
|
||||
},
|
||||
() => {
|
||||
cancel = reject
|
||||
if (!remote) {
|
||||
reject()
|
||||
} else {
|
||||
Image.getSize(
|
||||
remote,
|
||||
() => {
|
||||
cancel = undefined
|
||||
resolve(remote)
|
||||
},
|
||||
error => {
|
||||
reject(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return { start, cancel }
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
hidden?: boolean
|
||||
cache?: boolean
|
||||
uri: { preview?: string; original?: string; remote?: string }
|
||||
uri: { preview?: string; original: string; remote?: string }
|
||||
blurhash?: string
|
||||
dimension?: { width: number; height: number }
|
||||
onPress?: () => void
|
||||
@ -84,7 +24,6 @@ export interface Props {
|
||||
|
||||
const GracefullyImage: React.FC<Props> = ({
|
||||
hidden = false,
|
||||
cache = false,
|
||||
uri,
|
||||
blurhash,
|
||||
dimension,
|
||||
@ -93,68 +32,32 @@ const GracefullyImage: React.FC<Props> = ({
|
||||
imageStyle
|
||||
}) => {
|
||||
const { mode, theme } = useTheme()
|
||||
|
||||
const [imageVisible, setImageVisible] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
let cancel: CancelPromise
|
||||
const sideEffect = async (): Promise<void> => {
|
||||
try {
|
||||
const prefetchImage = getImageSize(uri as { original: string })
|
||||
cancel = prefetchImage.cancel
|
||||
const res = await prefetchImage.start()
|
||||
if (mounted) {
|
||||
setImageVisible(res)
|
||||
}
|
||||
return
|
||||
} catch (error) {
|
||||
if (__DEV__) console.warn('Image', error)
|
||||
}
|
||||
}
|
||||
|
||||
if (uri.original) {
|
||||
sideEffect()
|
||||
}
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
if (cancel) {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}, [uri])
|
||||
const [imageLoaded, setImageLoaded] = useState(false)
|
||||
|
||||
const children = useCallback(() => {
|
||||
if (imageVisible && !hidden) {
|
||||
if (cache) {
|
||||
return (
|
||||
<ImageCache uri={imageVisible} style={[styles.image, imageStyle]} />
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: imageVisible }}
|
||||
style={[styles.image, imageStyle]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
} else if (blurhash) {
|
||||
return (
|
||||
<Surface
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
top: StyleConstants.Spacing.XS / 2,
|
||||
left: StyleConstants.Spacing.XS / 2
|
||||
}}
|
||||
>
|
||||
<Blurhash hash={blurhash} />
|
||||
</Surface>
|
||||
)
|
||||
}
|
||||
}, [hidden, mode, imageVisible])
|
||||
return (
|
||||
<>
|
||||
<FastImage
|
||||
source={{ uri: uri.preview || uri.original || uri.remote }}
|
||||
style={[styles.image, imageStyle]}
|
||||
onLoad={() => setImageLoaded(true)}
|
||||
/>
|
||||
{blurhash && (hidden || !imageLoaded) ? (
|
||||
<Surface
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
top: StyleConstants.Spacing.XS / 2,
|
||||
left: StyleConstants.Spacing.XS / 2
|
||||
}}
|
||||
>
|
||||
<Blurhash hash={blurhash} />
|
||||
</Surface>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}, [hidden, imageLoaded, mode, uri])
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo } from 'react'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import { Image } from 'react-native-expo-image-cache'
|
||||
import { StyleSheet, Text } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
|
||||
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
||||
|
||||
@ -53,10 +53,9 @@ const ParseEmojis: React.FC<Props> = ({
|
||||
<Text key={i}>
|
||||
{/* When emoji starts a paragraph, lineHeight will break */}
|
||||
{i === 0 ? <Text> </Text> : null}
|
||||
<Image
|
||||
transitionDuration={0}
|
||||
uri={emojis[emojiIndex].url}
|
||||
style={[styles.image]}
|
||||
<FastImage
|
||||
source={{ uri: emojis[emojiIndex].url }}
|
||||
style={styles.image}
|
||||
/>
|
||||
</Text>
|
||||
)
|
||||
|
@ -32,7 +32,6 @@ const Avatars: React.FC<{ accounts: Mastodon.Account[] }> = ({ accounts }) => {
|
||||
{accounts.slice(0, 4).map(account => (
|
||||
<GracefullyImage
|
||||
key={account.id}
|
||||
cache
|
||||
uri={{ original: account.avatar_static }}
|
||||
dimension={{
|
||||
width: StyleConstants.Avatar.M,
|
||||
|
@ -75,10 +75,11 @@ const AttachmentAudio: React.FC<Props> = ({
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{(audio.preview_url || audio.preview_remote_url) && (
|
||||
{audio.preview_url && (
|
||||
<GracefullyImage
|
||||
uri={{
|
||||
original: audio.preview_url || audio.preview_remote_url
|
||||
original: audio.preview_url,
|
||||
remote: audio.preview_remote_url
|
||||
}}
|
||||
style={styles.background}
|
||||
/>
|
||||
|
@ -23,7 +23,6 @@ const TimelineAvatar: React.FC<Props> = ({ queryKey, account }) => {
|
||||
|
||||
return (
|
||||
<GracefullyImage
|
||||
cache
|
||||
onPress={onPress}
|
||||
uri={{ original: account.avatar_static }}
|
||||
dimension={{
|
||||
|
Reference in New Issue
Block a user