diff --git a/native/media/encrypted-image.react.js b/native/media/encrypted-image.react.js --- a/native/media/encrypted-image.react.js +++ b/native/media/encrypted-image.react.js @@ -4,7 +4,7 @@ import { MediaCacheContext } from 'lib/components/media-cache-provider.react.js'; -import { decryptMedia } from './encryption-utils.js'; +import { decryptBase64, decryptMedia } from './encryption-utils.js'; import LoadableImage from './loadable-image.react.js'; import { useSelector } from '../redux/redux-utils.js'; import type { ImageStyle } from '../types/styles.js'; @@ -16,13 +16,19 @@ +spinnerColor: string, +style: ImageStyle, +invisibleLoad: boolean, + +thumbHash?: ?string, }; type Props = { ...BaseProps, }; function EncryptedImage(props: Props): React.Node { - const { holder, encryptionKey, onLoad: onLoadProp } = props; + const { + holder, + encryptionKey, + onLoad: onLoadProp, + thumbHash: encryptedThumbHash, + } = props; const mediaCache = React.useContext(MediaCacheContext); const [source, setSource] = React.useState(null); @@ -38,6 +44,21 @@ prevConnectionStatusRef.current = connectionStatus; } + const placeholder = React.useMemo(() => { + if (!encryptedThumbHash) { + return null; + } + try { + const decryptedThumbHash = decryptBase64( + encryptedThumbHash, + encryptionKey, + ); + return { thumbhash: decryptedThumbHash }; + } catch (e) { + return null; + } + }, [encryptedThumbHash, encryptionKey]); + React.useEffect(() => { let isMounted = true; setSource(null); @@ -74,6 +95,7 @@ return ( void, +spinnerColor: string, @@ -15,7 +16,7 @@ +invisibleLoad: boolean, }; function LoadableImage(props: Props): React.Node { - const { source, onLoad: onLoadProp } = props; + const { source, placeholder, onLoad: onLoadProp } = props; const [loaded, setLoaded] = React.useState(false); @@ -30,7 +31,14 @@ ); if (!loaded && props.invisibleLoad) { - return ; + return ( + + ); } let spinner; @@ -44,8 +52,13 @@ return ( + {spinner} - ); } diff --git a/native/media/multimedia.react.js b/native/media/multimedia.react.js --- a/native/media/multimedia.react.js +++ b/native/media/multimedia.react.js @@ -16,11 +16,13 @@ | { +kind: 'uri', +uri: string, + +thumbHash?: ?string, } | { +kind: 'encrypted', +holder: string, +encryptionKey: string, + +thumbHash?: ?string, }; type BaseProps = { +mediaInfo: MediaInfo | AvatarMediaInfo, @@ -111,6 +113,7 @@ if (source.kind === 'encrypted') { return ( ); } - const { uri } = source; + const { uri, thumbHash } = source; + const placeholder = thumbHash ? { thumbhash: thumbHash } : null; if (uri.startsWith('http')) { return ( diff --git a/native/media/remote-image.react.js b/native/media/remote-image.react.js --- a/native/media/remote-image.react.js +++ b/native/media/remote-image.react.js @@ -1,6 +1,7 @@ // @flow import * as React from 'react'; +import type { ImageSource } from 'react-native/Libraries/Image/ImageSource'; import { type ConnectionStatus } from 'lib/types/socket-types.js'; @@ -14,6 +15,7 @@ +spinnerColor: string, +style: ImageStyle, +invisibleLoad: boolean, + +placeholder?: ?ImageSource, }; type Props = { ...BaseProps, @@ -41,12 +43,13 @@ } render() { - const { style, spinnerColor, invisibleLoad, uri } = this.props; + const { style, spinnerColor, invisibleLoad, uri, placeholder } = this.props; const source = { uri }; return (