Skip to content

Commit 1485ca0

Browse files
committed
feat: gallery loading and error state redesign
1 parent 3d03fc5 commit 1485ca0

File tree

21 files changed

+436
-343
lines changed

21 files changed

+436
-343
lines changed

package/src/components/Attachment/Gallery.tsx

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ export type GalleryPropsWithContext = Pick<ImageGalleryContextValue, 'imageGalle
5959
| 'VideoThumbnail'
6060
| 'ImageLoadingIndicator'
6161
| 'ImageLoadingFailedIndicator'
62-
| 'ImageReloadIndicator'
6362
| 'myMessageTheme'
6463
> &
6564
Pick<OverlayContextValue, 'setOverlay'> & {
@@ -74,7 +73,6 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
7473
imageGalleryStateStore,
7574
ImageLoadingFailedIndicator,
7675
ImageLoadingIndicator,
77-
ImageReloadIndicator,
7876
images,
7977
message,
8078
onLongPress,
@@ -148,8 +146,8 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
148146
images.length !== 1
149147
? { width: gridWidth, height: gridHeight }
150148
: {
151-
height,
152-
width,
149+
minHeight: height,
150+
minWidth: width,
153151
},
154152
galleryContainer,
155153
]}
@@ -194,7 +192,6 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
194192
imageGalleryStateStore={imageGalleryStateStore}
195193
ImageLoadingFailedIndicator={ImageLoadingFailedIndicator}
196194
ImageLoadingIndicator={ImageLoadingIndicator}
197-
ImageReloadIndicator={ImageReloadIndicator}
198195
imagesAndVideos={imagesAndVideos}
199196
invertedDirections={invertedDirections || false}
200197
key={rowIndex}
@@ -235,7 +232,6 @@ type GalleryThumbnailProps = {
235232
| 'VideoThumbnail'
236233
| 'ImageLoadingIndicator'
237234
| 'ImageLoadingFailedIndicator'
238-
| 'ImageReloadIndicator'
239235
> &
240236
Pick<ImageGalleryContextValue, 'imageGalleryStateStore'> &
241237
Pick<MessageContextValue, 'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress'> &
@@ -248,7 +244,6 @@ const GalleryThumbnail = ({
248244
imageGalleryStateStore,
249245
ImageLoadingFailedIndicator,
250246
ImageLoadingIndicator,
251-
ImageReloadIndicator,
252247
imagesAndVideos,
253248
invertedDirections,
254249
message,
@@ -352,7 +347,6 @@ const GalleryThumbnail = ({
352347
borderRadius={imageBorderRadius ?? borderRadius}
353348
ImageLoadingFailedIndicator={ImageLoadingFailedIndicator}
354349
ImageLoadingIndicator={ImageLoadingIndicator}
355-
ImageReloadIndicator={ImageReloadIndicator}
356350
thumbnail={thumbnail}
357351
/>
358352
)}
@@ -379,15 +373,10 @@ const GalleryImageThumbnail = ({
379373
borderRadius,
380374
ImageLoadingFailedIndicator,
381375
ImageLoadingIndicator,
382-
ImageReloadIndicator,
383376
thumbnail,
384377
}: Pick<
385378
GalleryThumbnailProps,
386-
| 'ImageLoadingFailedIndicator'
387-
| 'ImageLoadingIndicator'
388-
| 'ImageReloadIndicator'
389-
| 'thumbnail'
390-
| 'borderRadius'
379+
'ImageLoadingFailedIndicator' | 'ImageLoadingIndicator' | 'thumbnail' | 'borderRadius'
391380
>) => {
392381
const {
393382
isLoadingImage,
@@ -406,15 +395,9 @@ const GalleryImageThumbnail = ({
406395
const styles = useStyles();
407396

408397
return (
409-
<View style={styles.image}>
398+
<View style={[styles.image, borderRadius]}>
410399
{isLoadingImageError ? (
411-
<>
412-
<ImageLoadingFailedIndicator style={styles.imageLoadingErrorIndicatorStyle} />
413-
<ImageReloadIndicator
414-
onReloadImage={onReloadImage}
415-
style={styles.imageReloadContainerStyle}
416-
/>
417-
</>
400+
<ImageLoadingFailedIndicator onReloadImage={onReloadImage} />
418401
) : (
419402
<>
420403
<GalleryImage
@@ -426,14 +409,10 @@ const GalleryImageThumbnail = ({
426409
onLoadEnd={() => setTimeout(() => setLoadingImage(false), 0)}
427410
onLoadStart={() => setLoadingImage(true)}
428411
resizeMode={thumbnail.resizeMode}
429-
style={[borderRadius, gallery.image]}
412+
style={gallery.image}
430413
uri={thumbnail.url}
431414
/>
432-
{isLoadingImage && (
433-
<View style={styles.imageLoadingIndicatorContainer}>
434-
<ImageLoadingIndicator style={styles.imageLoadingIndicatorStyle} />
435-
</View>
436-
)}
415+
{isLoadingImage ? <ImageLoadingIndicator /> : null}
437416
</>
438417
)}
439418
</View>
@@ -512,7 +491,6 @@ export const Gallery = (props: GalleryProps) => {
512491
additionalPressableProps: propAdditionalPressableProps,
513492
ImageLoadingFailedIndicator: PropImageLoadingFailedIndicator,
514493
ImageLoadingIndicator: PropImageLoadingIndicator,
515-
ImageReloadIndicator: PropImageReloadIndicator,
516494
images: propImages,
517495
message: propMessage,
518496
myMessageTheme: propMyMessageTheme,
@@ -542,7 +520,6 @@ export const Gallery = (props: GalleryProps) => {
542520
additionalPressableProps: contextAdditionalPressableProps,
543521
ImageLoadingFailedIndicator: ContextImageLoadingFailedIndicator,
544522
ImageLoadingIndicator: ContextImageLoadingIndicator,
545-
ImageReloadIndicator: ContextImageReloadIndicator,
546523
myMessageTheme: contextMyMessageTheme,
547524
VideoThumbnail: ContextVideoThumnbnail,
548525
} = useMessagesContext();
@@ -567,7 +544,6 @@ export const Gallery = (props: GalleryProps) => {
567544
const ImageLoadingFailedIndicator =
568545
PropImageLoadingFailedIndicator || ContextImageLoadingFailedIndicator;
569546
const ImageLoadingIndicator = PropImageLoadingIndicator || ContextImageLoadingIndicator;
570-
const ImageReloadIndicator = PropImageReloadIndicator || ContextImageReloadIndicator;
571547
const myMessageTheme = propMyMessageTheme || contextMyMessageTheme;
572548
const messageContentOrder = propMessageContentOrder || contextMessageContentOrder;
573549

@@ -585,7 +561,6 @@ export const Gallery = (props: GalleryProps) => {
585561
imageGalleryStateStore,
586562
ImageLoadingFailedIndicator,
587563
ImageLoadingIndicator,
588-
ImageReloadIndicator,
589564
images,
590565
message,
591566
myMessageTheme,
@@ -607,6 +582,7 @@ const useStyles = () => {
607582
const {
608583
theme: { semantics },
609584
} = useTheme();
585+
const { isMyMessage } = useMessageContext();
610586
return useMemo(() => {
611587
return StyleSheet.create({
612588
errorTextSize: {
@@ -626,11 +602,15 @@ const useStyles = () => {
626602
imageContainer: {},
627603
image: {
628604
flex: 1,
605+
backgroundColor: isMyMessage
606+
? semantics.chatBgAttachmentOutgoing
607+
: semantics.chatBgAttachmentIncoming,
608+
overflow: 'hidden',
629609
},
630610
imageLoadingErrorIndicatorStyle: {
631-
bottom: 4,
632-
left: 4,
633-
position: 'absolute',
611+
...StyleSheet.absoluteFillObject,
612+
alignItems: 'center',
613+
justifyContent: 'center',
634614
},
635615
imageLoadingIndicatorContainer: {
636616
height: '100%',
@@ -658,8 +638,17 @@ const useStyles = () => {
658638
lineHeight: primitives.typographyLineHeightRelaxed,
659639
fontWeight: primitives.typographyFontWeightSemiBold,
660640
},
641+
imageLoadingErrorContainer: {
642+
...StyleSheet.absoluteFillObject,
643+
alignItems: 'center',
644+
justifyContent: 'center',
645+
},
646+
imageLoadingErrorWrapper: {
647+
...StyleSheet.absoluteFillObject,
648+
overflow: 'hidden',
649+
},
661650
});
662-
}, [semantics]);
651+
}, [semantics, isMyMessage]);
663652
};
664653

665654
Gallery.displayName = 'Gallery{messageSimple{gallery}}';

package/src/components/Attachment/Giphy/GiphyImage.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,13 @@ const GiphyImageWithContext = (props: GiphyImagePropsWithContext) => {
3939

4040
const { giphy: giphyData, image_url, thumb_url, type } = attachment;
4141

42-
const { isLoadingImage, isLoadingImageError, setLoadingImage, setLoadingImageError } =
43-
useLoadingImage();
42+
const {
43+
isLoadingImage,
44+
isLoadingImageError,
45+
setLoadingImage,
46+
setLoadingImageError,
47+
onReloadImage,
48+
} = useLoadingImage();
4449

4550
const {
4651
theme: {
@@ -93,7 +98,7 @@ const GiphyImageWithContext = (props: GiphyImagePropsWithContext) => {
9398
/>
9499
{isLoadingImageError && (
95100
<View style={[styles.imageIndicatorContainer, imageIndicatorContainer]}>
96-
<ImageLoadingFailedIndicator />
101+
<ImageLoadingFailedIndicator onReloadImage={onReloadImage} />
97102
</View>
98103
)}
99104
{isLoadingImage && (
Lines changed: 37 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,45 @@
1-
import React from 'react';
2-
import { StyleSheet, Text, View, ViewProps } from 'react-native';
1+
import React, { useMemo } from 'react';
2+
import { Pressable, StyleSheet, ViewProps } from 'react-native';
33

44
import { useTheme } from '../../contexts/themeContext/ThemeContext';
5-
import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
6-
7-
import { Warning } from '../../icons/Warning';
8-
9-
const WARNING_ICON_SIZE = 14;
5+
import { RetryBadge } from '../ui/Badge/RetryBadge';
6+
7+
export type ImageLoadingFailedIndicatorProps = ViewProps & {
8+
/**
9+
* Callback to reload the image
10+
* @returns Callback to reload the image
11+
*/
12+
onReloadImage: () => void;
13+
};
1014

11-
const styles = StyleSheet.create({
12-
container: {
13-
alignContent: 'center',
14-
alignItems: 'center',
15-
borderRadius: 20,
16-
flexDirection: 'row',
17-
justifyContent: 'center',
18-
},
19-
errorText: {
20-
fontSize: 8,
21-
justifyContent: 'center',
22-
paddingHorizontal: 8,
23-
},
24-
warningIconStyle: {
25-
borderRadius: 24,
26-
marginLeft: 4,
27-
marginTop: 6,
28-
},
29-
});
15+
export const ImageLoadingFailedIndicator = ({
16+
onReloadImage,
17+
}: ImageLoadingFailedIndicatorProps) => {
18+
const styles = useStyles();
3019

31-
export type ImageLoadingFailedIndicatorProps = ViewProps;
20+
return (
21+
<Pressable
22+
accessibilityLabel='Image Loading Error Indicator'
23+
onPress={onReloadImage}
24+
style={styles.imageLoadingErrorIndicatorStyle}
25+
>
26+
<RetryBadge size='lg' />
27+
</Pressable>
28+
);
29+
};
3230

33-
export const ImageLoadingFailedIndicator = (props: ImageLoadingFailedIndicatorProps) => {
31+
const useStyles = () => {
3432
const {
35-
theme: {
36-
colors: { accent_red, overlay, white },
37-
},
33+
theme: { semantics },
3834
} = useTheme();
39-
40-
const { t } = useTranslationContext();
41-
42-
const { style, ...rest } = props;
43-
return (
44-
<View {...rest} accessibilityHint='image-loading-error' style={[style]}>
45-
<View style={[styles.container, { backgroundColor: overlay }]}>
46-
<Warning
47-
height={WARNING_ICON_SIZE}
48-
fill={accent_red}
49-
style={styles.warningIconStyle}
50-
width={WARNING_ICON_SIZE}
51-
/>
52-
<Text style={[styles.errorText, { color: white }]}>{t('Error loading')}</Text>
53-
</View>
54-
</View>
55-
);
35+
return useMemo(() => {
36+
return StyleSheet.create({
37+
imageLoadingErrorIndicatorStyle: {
38+
...StyleSheet.absoluteFillObject,
39+
alignItems: 'center',
40+
justifyContent: 'center',
41+
backgroundColor: semantics.backgroundCoreOverlayLight,
42+
},
43+
});
44+
}, [semantics]);
5645
};
Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,14 @@
11
import React from 'react';
2-
import { ActivityIndicator, StyleSheet, View, ViewProps } from 'react-native';
2+
import { ActivityIndicator, StyleSheet, View } from 'react-native';
33

4-
import { useTheme } from '../../contexts/themeContext/ThemeContext';
4+
import { ShimmerView } from '../UIComponents/Shimmer/ShimmerView';
55

6-
const styles = StyleSheet.create({
7-
container: {
8-
alignItems: 'center',
9-
display: 'flex',
10-
justifyContent: 'center',
11-
width: '100%',
12-
},
13-
});
14-
15-
export type ImageLoadingIndicatorProps = ViewProps;
16-
17-
export const ImageLoadingIndicator = (props: ImageLoadingIndicatorProps) => {
18-
const {
19-
theme: {
20-
messageSimple: {
21-
loadingIndicator: { container },
22-
},
23-
},
24-
} = useTheme();
25-
const { style, ...rest } = props;
6+
export const ImageLoadingIndicator = () => {
267
return (
27-
<View {...rest} accessibilityHint='image-loading' style={[styles.container, container, style]}>
28-
<ActivityIndicator />
8+
<View style={StyleSheet.absoluteFillObject}>
9+
<ShimmerView>
10+
<ActivityIndicator />
11+
</ShimmerView>
2912
</View>
3013
);
3114
};

package/src/components/Attachment/ImageReloadIndicator.tsx

Lines changed: 0 additions & 26 deletions
This file was deleted.

package/src/components/Attachment/__tests__/Gallery.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ describe('Gallery', () => {
272272
fireEvent(screen.getByLabelText('Gallery Image'), 'error', {
273273
nativeEvent: { error: 'error loading image' },
274274
});
275-
expect(screen.getByAccessibilityHint('image-loading-error')).toBeTruthy();
275+
expect(screen.getByLabelText('Image Loading Error Indicator')).toBeTruthy();
276276
});
277277

278278
it('should render a loading indicator and when successful render the image', async () => {
@@ -289,10 +289,10 @@ describe('Gallery', () => {
289289
});
290290

291291
fireEvent(screen.getByLabelText('Gallery Image'), 'onLoadStart');
292-
expect(screen.getByAccessibilityHint('image-loading')).toBeTruthy();
292+
expect(screen.getByLabelText('Image Loading Indicator')).toBeTruthy();
293293

294294
fireEvent(screen.getByLabelText('Gallery Image'), 'onLoadFinish');
295-
waitForElementToBeRemoved(() => screen.getByAccessibilityHint('image-loading'));
295+
waitForElementToBeRemoved(() => screen.getByLabelText('Image Loading Indicator'));
296296
expect(screen.getByLabelText('Gallery Image')).toBeTruthy();
297297
});
298298
});

0 commit comments

Comments
 (0)