diff --git a/native/chat/inline-multimedia.react.js b/native/chat/inline-multimedia.react.js
--- a/native/chat/inline-multimedia.react.js
+++ b/native/chat/inline-multimedia.react.js
@@ -3,7 +3,7 @@
import Icon from '@expo/vector-icons/Feather.js';
import IonIcon from '@expo/vector-icons/Ionicons.js';
import * as React from 'react';
-import { View, StyleSheet, Text } from 'react-native';
+import { View, Text } from 'react-native';
import * as Progress from 'react-native-progress';
import tinycolor from 'tinycolor2';
@@ -13,6 +13,7 @@
import GestureTouchableOpacity from '../components/gesture-touchable-opacity.react.js';
import type { PendingMultimediaUpload } from '../input/input-state.js';
import Multimedia from '../media/multimedia.react.js';
+import { useStyles } from '../themes/colors.js';
type Props = {
+mediaInfo: MediaInfo,
@@ -23,6 +24,7 @@
};
function InlineMultimedia(props: Props): React.Node {
const { mediaInfo, pendingUpload, postInProgress } = props;
+ const styles = useStyles(unboundStyles);
let failed = isLocalUploadID(mediaInfo.id) && !postInProgress;
let progressPercent = 1;
@@ -35,7 +37,7 @@
if (failed) {
progressIndicator = (
-
+
);
} else if (progressPercent !== 1) {
@@ -100,7 +102,7 @@
);
}
-const styles = StyleSheet.create({
+const unboundStyles = {
centerContainer: {
alignItems: 'center',
bottom: 0,
@@ -114,14 +116,14 @@
flex: 1,
},
playButton: {
- color: 'white',
+ color: 'whiteText',
opacity: 0.9,
textShadowColor: '#000',
textShadowOffset: { width: 0, height: 1 },
textShadowRadius: 1,
},
processingStepText: {
- color: 'white',
+ color: 'whiteText',
fontSize: 12,
textShadowColor: '#000',
textShadowRadius: 1,
@@ -132,18 +134,18 @@
position: 'absolute',
},
progressPercentText: {
- color: 'white',
+ color: 'whiteText',
fontSize: 24,
fontWeight: 'bold',
textShadowColor: '#000',
textShadowRadius: 1,
},
uploadError: {
- color: 'white',
- textShadowColor: '#000',
- textShadowOffset: { width: 0, height: 1 },
- textShadowRadius: 1,
+ color: 'whiteText',
+ backgroundColor: 'vibrantRedButton',
+ borderRadius: 21,
+ overflow: 'hidden',
},
-});
+};
export default InlineMultimedia;
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
@@ -36,6 +36,7 @@
const connectionStatus = useSelector(state => state.connection.status);
const prevConnectionStatusRef = React.useRef(connectionStatus);
const [attempt, setAttempt] = React.useState(0);
+ const [errorOccured, setErrorOccured] = React.useState(false);
if (prevConnectionStatusRef.current !== connectionStatus) {
if (!source && connectionStatus === 'connected') {
@@ -73,10 +74,14 @@
const { result } = await decryptMedia(blobURI, encryptionKey, {
destination: 'data_uri',
});
- // TODO: decide what to do if decryption fails
- if (result.success && isMounted) {
- mediaCache?.set(blobURI, result.uri);
- setSource({ uri: result.uri });
+
+ if (isMounted) {
+ if (result.success) {
+ mediaCache?.set(blobURI, result.uri);
+ setSource({ uri: result.uri });
+ } else {
+ setErrorOccured(true);
+ }
}
};
@@ -102,6 +107,7 @@
style={style}
invisibleLoad={invisibleLoad}
key={attempt}
+ errorOccured={errorOccured}
/>
);
}
diff --git a/native/media/loadable-image.react.js b/native/media/loadable-image.react.js
--- a/native/media/loadable-image.react.js
+++ b/native/media/loadable-image.react.js
@@ -1,10 +1,12 @@
// @flow
+import Icon from '@expo/vector-icons/Feather.js';
import { Image } from 'expo-image';
import * as React from 'react';
-import { View, StyleSheet, ActivityIndicator } from 'react-native';
+import { View, ActivityIndicator } from 'react-native';
import type { ImageSource } from 'react-native/Libraries/Image/ImageSource';
+import { useStyles } from '../themes/colors.js';
import type { ImageStyle } from '../types/styles.js';
type Props = {
@@ -14,20 +16,28 @@
+spinnerColor: string,
+style: ImageStyle,
+invisibleLoad: boolean,
+ +errorOccured?: boolean,
};
function LoadableImage(props: Props): React.Node {
- const { source, placeholder, onLoad: onLoadProp } = props;
+ const { source, placeholder, onLoad: onLoadProp, errorOccured } = props;
+ const styles = useStyles(unboundStyles);
const [loaded, setLoaded] = React.useState(false);
+ const [error, setError] = React.useState(false);
+
+ const onError = React.useCallback(() => {
+ setError(true);
+ }, []);
const onLoad = React.useCallback(() => {
+ setError(false);
setLoaded(true);
onLoadProp && onLoadProp();
}, [onLoadProp]);
const invisibleStyle = React.useMemo(
() => [props.style, styles.invisible],
- [props.style],
+ [props.style, styles.invisible],
);
if (!loaded && props.invisibleLoad) {
@@ -36,15 +46,22 @@
source={source}
placeholder={placeholder}
onLoad={onLoad}
+ onError={onError}
style={invisibleStyle}
/>
);
}
- let spinner;
- if (!loaded) {
- spinner = (
-
+ let statusIndicator;
+ if (error || errorOccured) {
+ statusIndicator = (
+
+
+
+ );
+ } else if (!loaded) {
+ statusIndicator = (
+
);
@@ -56,21 +73,28 @@
source={source}
placeholder={placeholder}
onLoad={onLoad}
+ onError={onError}
style={props.style}
/>
- {spinner}
+ {statusIndicator}
);
}
-const styles = StyleSheet.create({
+const unboundStyles = {
container: {
flex: 1,
},
+ errorIndicator: {
+ color: 'whiteText',
+ backgroundColor: 'vibrantRedButton',
+ borderRadius: 21,
+ overflow: 'hidden',
+ },
invisible: {
opacity: 0,
},
- spinnerContainer: {
+ statusIndicatorContainer: {
alignItems: 'center',
bottom: 0,
justifyContent: 'center',
@@ -79,6 +103,6 @@
right: 0,
top: 0,
},
-});
+};
export default LoadableImage;