diff --git a/native/data/sqlite-data-handler.js b/native/data/sqlite-data-handler.js --- a/native/data/sqlite-data-handler.js +++ b/native/data/sqlite-data-handler.js @@ -14,6 +14,7 @@ import { getMessageForException } from 'lib/utils/errors.js'; import { convertClientDBThreadInfosToRawThreadInfos } from 'lib/utils/thread-ops-utils.js'; +import { filesystemMediaCache } from '../media/media-cache.js'; import { commCoreModule } from '../native-modules.js'; import { setStoreLoadedActionType } from '../redux/action-types.js'; import { useSelector } from '../redux/redux-utils.js'; @@ -69,6 +70,7 @@ Alert.alert('Starting SQLite database deletion process'); } await commCoreModule.clearSensitiveData(); + await filesystemMediaCache.clearCache(); if (staffCanSee || staffUserHasBeenLoggedIn) { Alert.alert( 'SQLite database successfully deleted', 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 @@ -2,6 +2,8 @@ import * as React from 'react'; +import { MediaCacheContext } from 'lib/components/media-cache-provider.react.js'; + import { decryptMedia } from './encryption-utils.js'; import LoadableImage from './loadable-image.react.js'; import { useSelector } from '../redux/redux-utils.js'; @@ -22,6 +24,7 @@ function EncryptedImage(props: Props): React.Node { const { holder, encryptionKey, onLoad: onLoadProp } = props; + const mediaCache = React.useContext(MediaCacheContext); const [source, setSource] = React.useState(null); const connectionStatus = useSelector(state => state.connection.status); @@ -40,11 +43,18 @@ setSource(null); const loadDecrypted = async () => { + const cached = await mediaCache?.get(holder); + if (cached && isMounted) { + setSource({ uri: cached }); + return; + } + const { result } = await decryptMedia(holder, encryptionKey, { destination: 'data_uri', }); // TODO: decide what to do if decryption fails if (result.success && isMounted) { + mediaCache?.set(holder, result.uri); setSource({ uri: result.uri }); } }; @@ -54,7 +64,7 @@ return () => { isMounted = false; }; - }, [attempt, holder, encryptionKey]); + }, [attempt, holder, encryptionKey, mediaCache]); const onLoad = React.useCallback(() => { onLoadProp && onLoadProp(holder); diff --git a/native/media/video-playback-modal.react.js b/native/media/video-playback-modal.react.js --- a/native/media/video-playback-modal.react.js +++ b/native/media/video-playback-modal.react.js @@ -12,6 +12,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import Video from 'react-native-video'; +import { MediaCacheContext } from 'lib/components/media-cache-provider.react.js'; import { useIsAppBackgroundedOrInactive } from 'lib/shared/lifecycle-utils.js'; import type { MediaInfo } from 'lib/types/media-types.js'; @@ -78,11 +79,13 @@ function VideoPlaybackModal(props: Props): React.Node { const { mediaInfo } = props.route.params; - const { uri, holder, encryptionKey } = mediaInfo; + const { uri: videoUri, holder, encryptionKey } = mediaInfo; const [videoSource, setVideoSource] = React.useState( - uri ? { uri } : undefined, + videoUri ? { uri: videoUri } : undefined, ); + const mediaCache = React.useContext(MediaCacheContext); + React.useEffect(() => { // skip for unencrypted videos if (!holder || !encryptionKey) { @@ -94,12 +97,20 @@ setVideoSource(undefined); const loadDecrypted = async () => { + const cached = await mediaCache?.get(holder); + if (cached && isMounted) { + setVideoSource({ uri: cached }); + return; + } + const { result } = await decryptMedia(holder, encryptionKey, { destination: 'file', }); if (result.success && isMounted) { - uriToDispose = result.uri; - setVideoSource({ uri: result.uri }); + const { uri } = result; + mediaCache?.set(holder, uri); + uriToDispose = uri; + setVideoSource({ uri: uri }); } }; loadDecrypted(); @@ -111,7 +122,7 @@ filesystem.unlink(uriToDispose); } }; - }, [holder, encryptionKey]); + }, [holder, encryptionKey, mediaCache]); const closeButtonX = useValue(-1); const closeButtonY = useValue(-1); diff --git a/native/root.react.js b/native/root.react.js --- a/native/root.react.js +++ b/native/root.react.js @@ -19,6 +19,7 @@ import { PersistGate as ReduxPersistGate } from 'redux-persist/es/integration/react.js'; import { ENSCacheProvider } from 'lib/components/ens-cache-provider.react.js'; +import { MediaCacheProvider } from 'lib/components/media-cache-provider.react.js'; import { actionLogger } from 'lib/utils/action-logger.js'; import ChatContextProvider from './chat/chat-context-provider.react.js'; @@ -30,6 +31,7 @@ import InputStateContainer from './input/input-state-container.react.js'; import LifecycleHandler from './lifecycle/lifecycle-handler.react.js'; import MarkdownContextProvider from './markdown/markdown-context-provider.react.js'; +import { filesystemMediaCache } from './media/media-cache.js'; import { defaultNavigationState } from './navigation/default-state.js'; import DisconnectedBarVisibilityHandler from './navigation/disconnected-bar-visibility-handler.react.js'; import { setGlobalNavContext } from './navigation/icky-global.js'; @@ -253,23 +255,25 @@ - - - - - - {gated} - - - - - {navigation} - - + + + + + + + {gated} + + + + + {navigation} + + +