stream-chat-react-native-core 4.7.3 → 4.9.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/lib/commonjs/components/Attachment/GallaryImage.js +41 -0
  2. package/lib/commonjs/components/Attachment/GallaryImage.js.map +1 -0
  3. package/lib/commonjs/components/Attachment/Gallery.js +101 -36
  4. package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
  5. package/lib/commonjs/components/Attachment/Giphy.js +118 -25
  6. package/lib/commonjs/components/Attachment/Giphy.js.map +1 -1
  7. package/lib/commonjs/components/Attachment/ImageLoadingFailedIndicator.js +105 -0
  8. package/lib/commonjs/components/Attachment/ImageLoadingFailedIndicator.js.map +1 -0
  9. package/lib/commonjs/components/Attachment/ImageLoadingIndicator.js +58 -0
  10. package/lib/commonjs/components/Attachment/ImageLoadingIndicator.js.map +1 -0
  11. package/lib/commonjs/components/Attachment/hooks/useLoadingImage.js +32 -0
  12. package/lib/commonjs/components/Attachment/hooks/useLoadingImage.js.map +1 -0
  13. package/lib/commonjs/components/Channel/Channel.js +23 -13
  14. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  15. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js +4 -0
  16. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  17. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js +43 -19
  18. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  19. package/lib/commonjs/components/MessageInput/FileUploadPreview.js +20 -19
  20. package/lib/commonjs/components/MessageInput/FileUploadPreview.js.map +1 -1
  21. package/lib/commonjs/components/MessageInput/ImageUploadPreview.js +14 -15
  22. package/lib/commonjs/components/MessageInput/ImageUploadPreview.js.map +1 -1
  23. package/lib/commonjs/contexts/messagesContext/MessagesContext.js +2 -2
  24. package/lib/commonjs/contexts/messagesContext/MessagesContext.js.map +1 -1
  25. package/lib/commonjs/contexts/themeContext/utils/theme.js +4 -0
  26. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  27. package/lib/commonjs/i18n/he.json +67 -0
  28. package/lib/commonjs/index.js +9 -0
  29. package/lib/commonjs/index.js.map +1 -1
  30. package/lib/commonjs/mock-builders/api/channelMocks.js +299 -0
  31. package/lib/commonjs/mock-builders/api/channelMocks.js.map +1 -0
  32. package/lib/commonjs/utils/Streami18n.js +5 -0
  33. package/lib/commonjs/utils/Streami18n.js.map +1 -1
  34. package/lib/commonjs/version.json +1 -1
  35. package/lib/module/components/Attachment/GallaryImage.js +41 -0
  36. package/lib/module/components/Attachment/GallaryImage.js.map +1 -0
  37. package/lib/module/components/Attachment/Gallery.js +101 -36
  38. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  39. package/lib/module/components/Attachment/Giphy.js +118 -25
  40. package/lib/module/components/Attachment/Giphy.js.map +1 -1
  41. package/lib/module/components/Attachment/ImageLoadingFailedIndicator.js +105 -0
  42. package/lib/module/components/Attachment/ImageLoadingFailedIndicator.js.map +1 -0
  43. package/lib/module/components/Attachment/ImageLoadingIndicator.js +58 -0
  44. package/lib/module/components/Attachment/ImageLoadingIndicator.js.map +1 -0
  45. package/lib/module/components/Attachment/hooks/useLoadingImage.js +32 -0
  46. package/lib/module/components/Attachment/hooks/useLoadingImage.js.map +1 -0
  47. package/lib/module/components/Channel/Channel.js +23 -13
  48. package/lib/module/components/Channel/Channel.js.map +1 -1
  49. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js +4 -0
  50. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  51. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js +43 -19
  52. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  53. package/lib/module/components/MessageInput/FileUploadPreview.js +20 -19
  54. package/lib/module/components/MessageInput/FileUploadPreview.js.map +1 -1
  55. package/lib/module/components/MessageInput/ImageUploadPreview.js +14 -15
  56. package/lib/module/components/MessageInput/ImageUploadPreview.js.map +1 -1
  57. package/lib/module/contexts/messagesContext/MessagesContext.js +2 -2
  58. package/lib/module/contexts/messagesContext/MessagesContext.js.map +1 -1
  59. package/lib/module/contexts/themeContext/utils/theme.js +4 -0
  60. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  61. package/lib/module/i18n/he.json +67 -0
  62. package/lib/module/index.js +9 -0
  63. package/lib/module/index.js.map +1 -1
  64. package/lib/module/mock-builders/api/channelMocks.js +299 -0
  65. package/lib/module/mock-builders/api/channelMocks.js.map +1 -0
  66. package/lib/module/utils/Streami18n.js +5 -0
  67. package/lib/module/utils/Streami18n.js.map +1 -1
  68. package/lib/module/version.json +1 -1
  69. package/lib/typescript/components/Attachment/GallaryImage.d.ts +5 -0
  70. package/lib/typescript/components/Attachment/Gallery.d.ts +1 -1
  71. package/lib/typescript/components/Attachment/Giphy.d.ts +1 -1
  72. package/lib/typescript/components/Attachment/ImageLoadingFailedIndicator.d.ts +3 -0
  73. package/lib/typescript/components/Attachment/ImageLoadingIndicator.d.ts +3 -0
  74. package/lib/typescript/components/Attachment/hooks/useLoadingImage.d.ts +7 -0
  75. package/lib/typescript/components/Channel/Channel.d.ts +1 -1
  76. package/lib/typescript/components/Channel/hooks/useCreateMessagesContext.d.ts +1 -1
  77. package/lib/typescript/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.d.ts +1 -0
  78. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +10 -0
  79. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +4 -0
  80. package/lib/typescript/i18n/he.json +67 -0
  81. package/lib/typescript/index.d.ts +1 -0
  82. package/lib/typescript/mock-builders/api/channelMocks.d.ts +14 -0
  83. package/lib/typescript/utils/Streami18n.d.ts +1 -4
  84. package/package.json +1 -1
  85. package/src/components/Attachment/GallaryImage.tsx +22 -0
  86. package/src/components/Attachment/Gallery.tsx +68 -35
  87. package/src/components/Attachment/Giphy.tsx +68 -1
  88. package/src/components/Attachment/ImageLoadingFailedIndicator.tsx +56 -0
  89. package/src/components/Attachment/ImageLoadingIndicator.tsx +31 -0
  90. package/src/components/Attachment/__tests__/Attachment.test.js +9 -3
  91. package/src/components/Attachment/__tests__/Gallery.test.js +36 -1
  92. package/src/components/Attachment/__tests__/Giphy.test.js +110 -90
  93. package/src/components/Attachment/hooks/useLoadingImage.tsx +8 -0
  94. package/src/components/Channel/Channel.tsx +8 -0
  95. package/src/components/Channel/hooks/useCreateMessagesContext.ts +4 -0
  96. package/src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js +0 -1
  97. package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx +145 -0
  98. package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +50 -24
  99. package/src/components/MessageInput/FileUploadPreview.tsx +5 -4
  100. package/src/components/MessageInput/ImageUploadPreview.tsx +2 -3
  101. package/src/contexts/messagesContext/MessagesContext.tsx +15 -0
  102. package/src/contexts/themeContext/utils/theme.ts +8 -0
  103. package/src/i18n/he.json +67 -0
  104. package/src/index.ts +1 -0
  105. package/src/mock-builders/api/channelMocks.tsx +272 -0
  106. package/src/utils/Streami18n.ts +6 -3
  107. package/src/version.json +1 -1
@@ -1,5 +1,7 @@
1
1
  import React, { PropsWithChildren } from 'react';
2
2
  import type { TouchableOpacityProps } from 'react-native';
3
+ import type { ImageLoadingFailedIndicatorProps } from 'src/components/Attachment/ImageLoadingFailedIndicator';
4
+ import type { ImageLoadingIndicatorProps } from 'src/components/Attachment/ImageLoadingIndicator';
3
5
  import type { MessagePinnedHeaderProps } from 'src/components/Message/MessageSimple/MessagePinnedHeader';
4
6
  import type { Attachment, ChannelState, MessageResponse } from 'stream-chat';
5
7
  import type { AttachmentProps } from '../../components/Attachment/Attachment';
@@ -96,6 +98,14 @@ export declare type MessagesContextValue<StreamChatGenerics extends DefaultStrea
96
98
  * The giphy version to render - check the keys of the [Image Object](https://developers.giphy.com/docs/api/schema#image-object) for possible values. Uses 'fixed_height' by default
97
99
  * */
98
100
  giphyVersion: keyof NonNullable<Attachment['giphy']>;
101
+ /**
102
+ * The indicator rendered when loading an image fails.
103
+ */
104
+ ImageLoadingFailedIndicator: React.ComponentType<ImageLoadingFailedIndicatorProps>;
105
+ /**
106
+ * The indicator rendered when image is loading. By default renders <ActivityIndicator/>
107
+ */
108
+ ImageLoadingIndicator: React.ComponentType<ImageLoadingIndicatorProps>;
99
109
  /**
100
110
  * When true, messageList will be scrolled at first unread message, when opened.
101
111
  */
@@ -450,6 +450,10 @@ export declare type Theme = {
450
450
  shuffle: TextStyle;
451
451
  title: TextStyle;
452
452
  };
453
+ loadingIndicator: {
454
+ container: ViewStyle;
455
+ roundedView: ViewStyle;
456
+ };
453
457
  pinnedHeader: {
454
458
  container: ViewStyle;
455
459
  label: TextStyle;
@@ -0,0 +1,67 @@
1
+ {
2
+ "1 Reply": "תגובה אחת",
3
+ "1 Thread Reply": "תגובה אחת לשרשור",
4
+ "Allow access to your Gallery": "אפשר גישה לגלריה שלך",
5
+ "Also send to channel": "שלח/י הודעה לשיחה",
6
+ "Are you sure you want to permanently delete this message?": "האם את/ה בטוח/ה שאת/ה רוצה למחוק את ההודעה הזו לצמיתות?",
7
+ "Block User": "חסום משתמש",
8
+ "Cancel": "ביטול",
9
+ "Cannot Flag Message": "סימון הודעה לא אפשרי",
10
+ "Copy Message": "העתק/י הודעה",
11
+ "Delete": "מחק",
12
+ "Delete Message": "מחק/י הודעה",
13
+ "Do you want to send a copy of this message to a moderator for further investigation?": "האם את/ה רוצה לשלוח עותק של הודעה זו למנחה להמשך חקירה?",
14
+ "Edit Message": "ערוך הודעה",
15
+ "Editing Message": "הודעה בעריכה",
16
+ "Emoji matching": "התאמת אמוג'י",
17
+ "Empty message...": "הודעה ריקה...",
18
+ "Error loading": "שגיאה ארעה בעת הטעינה",
19
+ "Error loading channel list...": "שגיאה ארעה בטעינת השיחות...",
20
+ "Error loading messages for this channel...": "שגיאה ארעה בטעינת הודעות עבור שיחה זאת...",
21
+ "Error while loading, please reload/refresh": "שגיאה ארעה בזמן הטעינה, אנא טען מחדש/רענן",
22
+ "File type not supported": "סוג הקובץ אינו נתמך",
23
+ "Flag": "סמן",
24
+ "Flag Message": "סמן הודעה",
25
+ "Flag action failed either due to a network issue or the message is already flagged": "פעולת הסימון נכשלה בגלל בעיית רשת או שההודעה כבר סומנה.",
26
+ "Instant Commands": "פעולות מיידיות",
27
+ "Links are disabled": "הקישורים מבוטלים",
28
+ "Loading channels...": "השיחות בטעינה...",
29
+ "Loading messages...": "ההודעות בטעינה..",
30
+ "Loading...": "טוען...",
31
+ "Message Reactions": "תגובות להודעה",
32
+ "Message deleted": "ההודעה נמחקה",
33
+ "Message flagged": "ההודעה סומנה",
34
+ "Mute User": "השתק/י משתמש",
35
+ "Not supported": "לא נתמך",
36
+ "Nothing yet...": "אינפורמציה תתקבל בהמשך...",
37
+ "Ok": "אוקיי",
38
+ "Only visible to you": "גלוי רק לך",
39
+ "Photo": "תמונה",
40
+ "Photos": "תמונות",
41
+ "Pin to Conversation": "הצמד/י לשיחה",
42
+ "Please enable access to your photos and videos so you can share them.": "אפשר/י גישה לתמונות ולסרטונים שלך כדי שתוכל/י לשתף אותם.",
43
+ "Please select a channel first": "אנא בחר/י שיחה תחילה",
44
+ "Reconnecting...": "מתחבר מחדש...",
45
+ "Reply": "השב/י",
46
+ "Reply to Message": "השב/י להודעה",
47
+ "Resend": "שלח/י שוב",
48
+ "Search GIFs": "חפש/י GIFs",
49
+ "Send a message": "שלח/י הודעה",
50
+ "Sending links is not allowed in this conversation": "שליחת קישורים אינה מותרת בשיחה זו",
51
+ "Slow mode ON": "מצב איטי מופעל",
52
+ "The message has been reported to a moderator.": "ההודעה דווחה למנהל",
53
+ "Thread Reply": "הגב/י בשרשור",
54
+ "Unblock User": "בטל/י חסימת משתמש",
55
+ "Unknown User": "משתמש לא ידוע",
56
+ "Unmute User": "בטל/י השתקת משתמש",
57
+ "Unpin from Conversation": "בטל/י הצמדה לשיחה",
58
+ "Unread Messages": "הודעות שטרם נקרו",
59
+ "You": "את/ה",
60
+ "You can't send messages in this channel": "את/ב לא יכול/ה לשלוח הודעות בשיחה זו",
61
+ "{{ firstUser }} and {{ nonSelfUserLength }} more are typing": "{{ firstUser }} ו-{{ nonSelfUserLength }} משתמש/ים אחר/ים מקלידים",
62
+ "{{ index }} of {{ photoLength }}": "{{ index }} מתוך {{ photoLength }}",
63
+ "{{ replyCount }} Replies": "{{ replyCount }} תגובות",
64
+ "{{ replyCount }} Thread Replies": "{{ replyCount }} תגובות שרשור",
65
+ "{{ user }} is typing": "{{ user }} מקליד/ה",
66
+ "🏙 Attachment...": "🏙 קובץ מצורף..."
67
+ }
@@ -14,4 +14,5 @@ export { default as itTranslations } from './i18n/it.json';
14
14
  export { default as nlTranslations } from './i18n/nl.json';
15
15
  export { default as ruTranslations } from './i18n/ru.json';
16
16
  export { default as trTranslations } from './i18n/tr.json';
17
+ export { default as heTranslations } from './i18n/he.json';
17
18
  export { version } from './version.json';
@@ -0,0 +1,14 @@
1
+ import type { Channel, FormatMessageResponse, MessageResponse } from 'stream-chat';
2
+ import type { DefaultStreamChatGenerics } from '../../types/types';
3
+ declare const CHANNEL: Channel<DefaultStreamChatGenerics>;
4
+ declare const CHANNEL_WITH_MESSAGES_TEXT: Channel<DefaultStreamChatGenerics>;
5
+ declare const CHANNEL_WITH_DELETED_MESSAGES: Channel<DefaultStreamChatGenerics>;
6
+ declare const CHANNEL_WITH_NO_MESSAGES: Channel<DefaultStreamChatGenerics>;
7
+ declare const CHANNEL_WITH_MESSAGE_COMMAND: Channel<DefaultStreamChatGenerics>;
8
+ declare const CHANNEL_WITH_MESSAGES_ATTACHMENTS: Channel<DefaultStreamChatGenerics>;
9
+ declare const LATEST_MESSAGE: MessageResponse<DefaultStreamChatGenerics>;
10
+ declare const FORMATTED_MESSAGE: FormatMessageResponse<DefaultStreamChatGenerics>;
11
+ declare const CHANNEL_WITH_MENTIONED_USERS: Channel<DefaultStreamChatGenerics>;
12
+ declare const CHANNEL_WITH_EMPTY_MESSAGE: Channel<DefaultStreamChatGenerics>;
13
+ declare const CHANNEL_WITH_MESSAGES: Channel<DefaultStreamChatGenerics>;
14
+ export { CHANNEL, CHANNEL_WITH_EMPTY_MESSAGE, CHANNEL_WITH_MESSAGES, CHANNEL_WITH_MENTIONED_USERS, FORMATTED_MESSAGE, LATEST_MESSAGE, CHANNEL_WITH_MESSAGES_ATTACHMENTS, CHANNEL_WITH_MESSAGE_COMMAND as CHANNEL_WITH_MESSAGES_COMMAND, CHANNEL_WITH_NO_MESSAGES, CHANNEL_WITH_DELETED_MESSAGES, CHANNEL_WITH_MESSAGES_TEXT, };
@@ -11,6 +11,7 @@ import 'dayjs/locale/ko';
11
11
  import 'dayjs/locale/nl';
12
12
  import 'dayjs/locale/ru';
13
13
  import 'dayjs/locale/tr';
14
+ import 'dayjs/locale/he';
14
15
  /**
15
16
  * These locale imports also set these locales globally.
16
17
  * So as a last step we import the english locale to make
@@ -154,10 +155,6 @@ export declare class Streami18n {
154
155
  "Please enable access to your photos and videos so you can share them.": string;
155
156
  "Please select a channel first": string;
156
157
  "Reconnecting...": string;
157
- /**
158
- * Hindi notation for meridiems are quite fuzzy in practice. While there exists
159
- * a rigid notion of a 'Pahar' it is not used as rigidly in modern Hindi.
160
- */
161
158
  Reply: string;
162
159
  "Reply to Message": string;
163
160
  Resend: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stream-chat-react-native-core",
3
3
  "description": "The official React Native and Expo components for Stream Chat, a service for building chat applications",
4
- "version": "4.7.3",
4
+ "version": "4.9.0-beta.1",
5
5
  "author": {
6
6
  "company": "Stream.io Inc",
7
7
  "name": "Stream.io Inc"
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { ImageBackground, ImageProps } from 'react-native';
3
+
4
+ import { makeImageCompatibleUrl } from '../../utils/utils';
5
+
6
+ export const GalleryImage: React.FC<
7
+ Omit<ImageProps, 'height' | 'source'> & {
8
+ uri: string;
9
+ }
10
+ > = (props) => {
11
+ const { uri, ...rest } = props;
12
+
13
+ return (
14
+ <ImageBackground
15
+ {...rest}
16
+ accessibilityLabel='gallery-image'
17
+ source={{
18
+ uri: makeImageCompatibleUrl(uri),
19
+ }}
20
+ />
21
+ );
22
+ };
@@ -1,6 +1,8 @@
1
1
  import React, { useMemo } from 'react';
2
- import { Image, ImageProps, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
2
+ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3
3
 
4
+ import { GalleryImage } from './GallaryImage';
5
+ import { useLoadingImage } from './hooks/useLoadingImage';
4
6
  import { buildGallery } from './utils/buildGallery/buildGallery';
5
7
 
6
8
  import { getGalleryImageBorderRadius } from './utils/getGalleryImageBorderRadius';
@@ -27,25 +29,7 @@ import {
27
29
  import { useTheme } from '../../contexts/themeContext/ThemeContext';
28
30
  import { isVideoPackageAvailable } from '../../native';
29
31
  import type { DefaultStreamChatGenerics } from '../../types/types';
30
- import { getUrlWithoutParams, makeImageCompatibleUrl } from '../../utils/utils';
31
-
32
- const GalleryImage: React.FC<
33
- Omit<ImageProps, 'height' | 'source'> & {
34
- uri: string;
35
- }
36
- > = (props) => {
37
- const { uri, ...rest } = props;
38
-
39
- return (
40
- <Image
41
- {...rest}
42
- source={{
43
- uri: makeImageCompatibleUrl(uri),
44
- }}
45
- testID='image-attachment-single'
46
- />
47
- );
48
- };
32
+ import { getUrlWithoutParams } from '../../utils/utils';
49
33
 
50
34
  const MemoizedGalleryImage = React.memo(
51
35
  GalleryImage,
@@ -54,7 +38,7 @@ const MemoizedGalleryImage = React.memo(
54
38
  ) as typeof GalleryImage;
55
39
 
56
40
  const styles = StyleSheet.create({
57
- flex: { flex: 1 },
41
+ errorTextSize: { fontSize: 10 },
58
42
  galleryContainer: {
59
43
  borderTopLeftRadius: 13,
60
44
  borderTopRightRadius: 13,
@@ -62,7 +46,19 @@ const styles = StyleSheet.create({
62
46
  flexWrap: 'wrap',
63
47
  overflow: 'hidden',
64
48
  },
65
- imageContainer: { display: 'flex', flexDirection: 'row', justifyContent: 'center', padding: 1 },
49
+ imageContainer: {
50
+ alignItems: 'center',
51
+ display: 'flex',
52
+ flexDirection: 'row',
53
+ justifyContent: 'center',
54
+ padding: 1,
55
+ },
56
+ imageContainerStyle: { alignItems: 'center', flex: 1, justifyContent: 'center' },
57
+ imageLoadingIndicatorStyle: {
58
+ alignItems: 'center',
59
+ justifyContent: 'center',
60
+ position: 'absolute',
61
+ },
66
62
  moreImagesContainer: {
67
63
  alignItems: 'center',
68
64
  justifyContent: 'center',
@@ -88,7 +84,11 @@ export type GalleryPropsWithContext<
88
84
  > &
89
85
  Pick<
90
86
  MessagesContextValue<StreamChatGenerics>,
91
- 'additionalTouchableProps' | 'legacyImageViewerSwipeBehaviour' | 'VideoThumbnail'
87
+ | 'additionalTouchableProps'
88
+ | 'legacyImageViewerSwipeBehaviour'
89
+ | 'VideoThumbnail'
90
+ | 'ImageLoadingIndicator'
91
+ | 'ImageLoadingFailedIndicator'
92
92
  > &
93
93
  Pick<OverlayContextValue, 'setOverlay'> & {
94
94
  channelId: string | undefined;
@@ -118,6 +118,8 @@ const GalleryWithContext = <
118
118
  alignment,
119
119
  groupStyles,
120
120
  hasThreadReplies,
121
+ ImageLoadingFailedIndicator,
122
+ ImageLoadingIndicator,
121
123
  images,
122
124
  legacyImageViewerSwipeBehaviour,
123
125
  message,
@@ -133,6 +135,9 @@ const GalleryWithContext = <
133
135
  VideoThumbnail,
134
136
  } = props;
135
137
 
138
+ const { isLoadingImage, isLoadingImageError, setLoadingImage, setLoadingImageError } =
139
+ useLoadingImage();
140
+
136
141
  const {
137
142
  theme: {
138
143
  colors: { overlay },
@@ -304,18 +309,37 @@ const GalleryWithContext = <
304
309
  ]}
305
310
  />
306
311
  ) : (
307
- <MemoizedGalleryImage
308
- resizeMode={resizeMode}
309
- style={[
310
- borderRadius,
311
- image,
312
- {
313
- height: height - 1,
314
- width: width - 1,
315
- },
316
- ]}
317
- uri={url}
318
- />
312
+ <View style={styles.imageContainerStyle}>
313
+ <MemoizedGalleryImage
314
+ onError={(error) => {
315
+ console.warn(error);
316
+ setLoadingImage(false);
317
+ setLoadingImageError(true);
318
+ }}
319
+ onLoadEnd={() => setLoadingImage(false)}
320
+ onLoadStart={() => setLoadingImage(true)}
321
+ resizeMode={resizeMode}
322
+ style={[
323
+ borderRadius,
324
+ image,
325
+ {
326
+ height: height - 1,
327
+ width: width - 1,
328
+ },
329
+ ]}
330
+ uri={url}
331
+ />
332
+ {isLoadingImage && (
333
+ <View style={{ position: 'absolute' }}>
334
+ <ImageLoadingIndicator style={styles.imageLoadingIndicatorStyle} />
335
+ </View>
336
+ )}
337
+ {isLoadingImageError && (
338
+ <View style={{ position: 'absolute' }}>
339
+ <ImageLoadingFailedIndicator style={styles.imageLoadingIndicatorStyle} />
340
+ </View>
341
+ )}
342
+ </View>
319
343
  )}
320
344
  {colIndex === numOfColumns - 1 &&
321
345
  rowIndex === numOfRows - 1 &&
@@ -412,6 +436,8 @@ export const Gallery = <
412
436
  alignment: propAlignment,
413
437
  groupStyles: propGroupStyles,
414
438
  hasThreadReplies,
439
+ ImageLoadingFailedIndicator: PropImageLoadingFailedIndicator,
440
+ ImageLoadingIndicator: PropImageLoadingIndicator,
415
441
  images: propImages,
416
442
  onLongPress: propOnLongPress,
417
443
  onPress: propOnPress,
@@ -439,6 +465,8 @@ export const Gallery = <
439
465
  } = useMessageContext<StreamChatGenerics>();
440
466
  const {
441
467
  additionalTouchableProps: contextAdditionalTouchableProps,
468
+ ImageLoadingFailedIndicator: ContextImageLoadingFailedIndicator,
469
+ ImageLoadingIndicator: ContextImageLoadingIndicator,
442
470
  legacyImageViewerSwipeBehaviour,
443
471
  VideoThumbnail: ContextVideoThumnbnail,
444
472
  } = useMessagesContext<StreamChatGenerics>();
@@ -461,6 +489,9 @@ export const Gallery = <
461
489
  const setOverlay = propSetOverlay || contextSetOverlay;
462
490
  const threadList = propThreadList || contextThreadList;
463
491
  const VideoThumbnail = PropVideoThumbnail || ContextVideoThumnbnail;
492
+ const ImageLoadingFailedIndicator =
493
+ PropImageLoadingFailedIndicator || ContextImageLoadingFailedIndicator;
494
+ const ImageLoadingIndicator = PropImageLoadingIndicator || ContextImageLoadingIndicator;
464
495
 
465
496
  return (
466
497
  <MemoizedGallery
@@ -470,6 +501,8 @@ export const Gallery = <
470
501
  channelId: message?.cid,
471
502
  groupStyles,
472
503
  hasThreadReplies: hasThreadReplies || !!message?.reply_count,
504
+ ImageLoadingFailedIndicator,
505
+ ImageLoadingIndicator,
473
506
  images,
474
507
  legacyImageViewerSwipeBehaviour,
475
508
  message,
@@ -3,6 +3,8 @@ import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3
3
 
4
4
  import type { Attachment } from 'stream-chat';
5
5
 
6
+ import { useLoadingImage } from './hooks/useLoadingImage';
7
+
6
8
  import {
7
9
  ImageGalleryContextValue,
8
10
  useImageGalleryContext,
@@ -40,6 +42,11 @@ const styles = StyleSheet.create({
40
42
  fontWeight: '600',
41
43
  paddingVertical: 16,
42
44
  },
45
+ centerChild: {
46
+ alignItems: 'center',
47
+ display: 'flex',
48
+ justifyContent: 'center',
49
+ },
43
50
  container: {
44
51
  overflow: 'hidden',
45
52
  width: 270,
@@ -84,6 +91,17 @@ const styles = StyleSheet.create({
84
91
  padding: 8,
85
92
  width: '60%',
86
93
  },
94
+ imageErrorIndicatorStyle: {
95
+ alignItems: 'center',
96
+ flex: 1,
97
+ justifyContent: 'center',
98
+ },
99
+ imageLoadingIndicatorStyle: {
100
+ alignItems: 'center',
101
+ flex: 1,
102
+ justifyContent: 'center',
103
+ position: 'absolute',
104
+ },
87
105
  selectionContainer: {
88
106
  borderBottomRightRadius: 0,
89
107
  borderRadius: 16,
@@ -128,7 +146,13 @@ export type GiphyPropsWithContext<
128
146
  | 'onPressIn'
129
147
  | 'preventPress'
130
148
  > &
131
- Pick<MessagesContextValue<StreamChatGenerics>, 'giphyVersion' | 'additionalTouchableProps'> & {
149
+ Pick<
150
+ MessagesContextValue<StreamChatGenerics>,
151
+ | 'giphyVersion'
152
+ | 'additionalTouchableProps'
153
+ | 'ImageLoadingIndicator'
154
+ | 'ImageLoadingFailedIndicator'
155
+ > & {
132
156
  attachment: Attachment<StreamChatGenerics>;
133
157
  } & Pick<OverlayContextValue, 'setOverlay'>;
134
158
 
@@ -142,6 +166,8 @@ const GiphyWithContext = <
142
166
  attachment,
143
167
  giphyVersion,
144
168
  handleAction,
169
+ ImageLoadingFailedIndicator,
170
+ ImageLoadingIndicator,
145
171
  isMyMessage,
146
172
  message,
147
173
  onLongPress,
@@ -155,6 +181,9 @@ const GiphyWithContext = <
155
181
 
156
182
  const { actions, giphy: giphyData, image_url, thumb_url, title, type } = attachment;
157
183
 
184
+ const { isLoadingImage, isLoadingImageError, setLoadingImage, setLoadingImageError } =
185
+ useLoadingImage();
186
+
158
187
  const {
159
188
  theme: {
160
189
  colors: { accent_blue, black, grey, grey_dark, grey_gainsboro, label_bg_transparent, white },
@@ -214,6 +243,13 @@ const GiphyWithContext = <
214
243
  </View>
215
244
  <View style={styles.selectionImageContainer}>
216
245
  <Image
246
+ onError={(error) => {
247
+ console.warn(error);
248
+ setLoadingImage(false);
249
+ setLoadingImageError(true);
250
+ }}
251
+ onLoadEnd={() => setLoadingImage(false)}
252
+ onLoadStart={() => setLoadingImage(true)}
217
253
  resizeMode='contain'
218
254
  source={{ uri: makeImageCompatibleUrl(uri) }}
219
255
  style={[styles.giphy, giphyDimensions, giphy]}
@@ -299,16 +335,36 @@ const GiphyWithContext = <
299
335
  <View
300
336
  style={[
301
337
  styles.container,
338
+ styles.centerChild,
302
339
  { backgroundColor: isMyMessage ? grey_gainsboro : white },
303
340
  container,
304
341
  ]}
305
342
  >
306
343
  <Image
344
+ accessibilityLabel='giphy-attachment-image'
345
+ onError={(error) => {
346
+ console.warn(error);
347
+ setLoadingImage(false);
348
+ setLoadingImageError(true);
349
+ }}
350
+ onLoadEnd={() => setLoadingImage(false)}
351
+ onLoadStart={() => setLoadingImage(true)}
307
352
  resizeMode='contain'
308
353
  source={{ uri: makeImageCompatibleUrl(uri) }}
309
354
  style={[styles.giphy, giphyDimensions, giphy]}
310
355
  testID='giphy-attachment-image'
311
356
  />
357
+
358
+ {isLoadingImageError && (
359
+ <View style={{ position: 'absolute' }}>
360
+ <ImageLoadingFailedIndicator style={styles.imageErrorIndicatorStyle} />
361
+ </View>
362
+ )}
363
+ {isLoadingImage && (
364
+ <View style={{ position: 'absolute' }}>
365
+ <ImageLoadingIndicator style={styles.imageLoadingIndicatorStyle} />
366
+ </View>
367
+ )}
312
368
  <View style={[styles.giphyMask, giphyMask]}>
313
369
  <View
314
370
  style={[
@@ -387,12 +443,23 @@ export const Giphy = <
387
443
  const { additionalTouchableProps, giphyVersion } = useMessagesContext<StreamChatGenerics>();
388
444
  const { setImage, setImages } = useImageGalleryContext<StreamChatGenerics>();
389
445
  const { setOverlay } = useOverlayContext();
446
+
447
+ const {
448
+ ImageLoadingFailedIndicator: ContextImageLoadingFailedIndicator,
449
+ ImageLoadingIndicator: ContextImageLoadingIndicator,
450
+ } = useMessagesContext<StreamChatGenerics>();
451
+ const ImageLoadingFailedIndicator =
452
+ ContextImageLoadingFailedIndicator || props.ImageLoadingFailedIndicator;
453
+ const ImageLoadingIndicator = ContextImageLoadingIndicator || props.ImageLoadingIndicator;
454
+
390
455
  return (
391
456
  <MemoizedGiphy
392
457
  {...{
393
458
  additionalTouchableProps,
394
459
  giphyVersion,
395
460
  handleAction,
461
+ ImageLoadingFailedIndicator,
462
+ ImageLoadingIndicator,
396
463
  isMyMessage,
397
464
  message,
398
465
  onLongPress,
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { StyleSheet, Text, View, ViewProps } from 'react-native';
3
+
4
+ import { useTheme } from '../../contexts/themeContext/ThemeContext';
5
+ import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
6
+
7
+ import { Warning } from '../../icons';
8
+
9
+ const WARNING_ICON_SIZE = 14;
10
+
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
+ });
30
+
31
+ export type ImageLoadingFailedIndicatorProps = ViewProps;
32
+
33
+ export const ImageLoadingFailedIndicator = (props: ImageLoadingFailedIndicatorProps) => {
34
+ const {
35
+ theme: {
36
+ colors: { accent_red, overlay, white },
37
+ },
38
+ } = 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
+ pathFill={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
+ );
56
+ };
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { ActivityIndicator, StyleSheet, View, ViewProps } from 'react-native';
3
+
4
+ import { useTheme } from '../../contexts/themeContext/ThemeContext';
5
+
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;
26
+ return (
27
+ <View {...rest} accessibilityHint='image-loading' style={[styles.container, container, style]}>
28
+ <ActivityIndicator />
29
+ </View>
30
+ );
31
+ };
@@ -4,6 +4,7 @@ import { fireEvent, render, waitFor } from '@testing-library/react-native';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
 
6
6
  import { MessageProvider } from '../../../contexts/messageContext/MessageContext';
7
+ import { MessagesProvider } from '../../../contexts/messagesContext/MessagesContext';
7
8
  import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
8
9
  import {
9
10
  generateAttachmentAction,
@@ -12,6 +13,9 @@ import {
12
13
  generateImageAttachment,
13
14
  } from '../../../mock-builders/generator/attachment';
14
15
  import { generateMessage } from '../../../mock-builders/generator/message';
16
+
17
+ import { ImageLoadingFailedIndicator } from '../../Attachment/ImageLoadingFailedIndicator';
18
+ import { ImageLoadingIndicator } from '../../Attachment/ImageLoadingIndicator';
15
19
  import { Attachment } from '../Attachment';
16
20
  import { AttachmentActions } from '../AttachmentActions';
17
21
 
@@ -19,9 +23,11 @@ const getAttachmentComponent = (props) => {
19
23
  const message = generateMessage();
20
24
  return (
21
25
  <ThemeProvider>
22
- <MessageProvider value={{ message }}>
23
- <Attachment {...props} />
24
- </MessageProvider>
26
+ <MessagesProvider value={{ ImageLoadingFailedIndicator, ImageLoadingIndicator }}>
27
+ <MessageProvider value={{ message }}>
28
+ <Attachment {...props} />
29
+ </MessageProvider>
30
+ </MessagesProvider>
25
31
  </ThemeProvider>
26
32
  );
27
33
  };