stream-chat-react-native-core 4.8.0 → 4.9.0-beta.3

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 (119) 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/AutoCompleteInput/AutoCompleteInput.js +3 -2
  14. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteInput.js.map +1 -1
  15. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteSuggestionItem.js +1 -1
  16. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteSuggestionItem.js.map +1 -1
  17. package/lib/commonjs/components/Channel/Channel.js +23 -13
  18. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  19. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js +4 -0
  20. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  21. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js +43 -19
  22. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  23. package/lib/commonjs/components/MessageInput/FileUploadPreview.js +28 -21
  24. package/lib/commonjs/components/MessageInput/FileUploadPreview.js.map +1 -1
  25. package/lib/commonjs/components/MessageInput/ImageUploadPreview.js +16 -17
  26. package/lib/commonjs/components/MessageInput/ImageUploadPreview.js.map +1 -1
  27. package/lib/commonjs/components/RTLComponents/WritingDirectionAwareText.js +43 -0
  28. package/lib/commonjs/components/RTLComponents/WritingDirectionAwareText.js.map +1 -0
  29. package/lib/commonjs/components/Reply/Reply.js +7 -2
  30. package/lib/commonjs/components/Reply/Reply.js.map +1 -1
  31. package/lib/commonjs/contexts/messagesContext/MessagesContext.js +2 -2
  32. package/lib/commonjs/contexts/messagesContext/MessagesContext.js.map +1 -1
  33. package/lib/commonjs/contexts/themeContext/utils/theme.js +4 -0
  34. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  35. package/lib/commonjs/mock-builders/api/channelMocks.js +299 -0
  36. package/lib/commonjs/mock-builders/api/channelMocks.js.map +1 -0
  37. package/lib/commonjs/version.json +1 -1
  38. package/lib/module/components/Attachment/GallaryImage.js +41 -0
  39. package/lib/module/components/Attachment/GallaryImage.js.map +1 -0
  40. package/lib/module/components/Attachment/Gallery.js +101 -36
  41. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  42. package/lib/module/components/Attachment/Giphy.js +118 -25
  43. package/lib/module/components/Attachment/Giphy.js.map +1 -1
  44. package/lib/module/components/Attachment/ImageLoadingFailedIndicator.js +105 -0
  45. package/lib/module/components/Attachment/ImageLoadingFailedIndicator.js.map +1 -0
  46. package/lib/module/components/Attachment/ImageLoadingIndicator.js +58 -0
  47. package/lib/module/components/Attachment/ImageLoadingIndicator.js.map +1 -0
  48. package/lib/module/components/Attachment/hooks/useLoadingImage.js +32 -0
  49. package/lib/module/components/Attachment/hooks/useLoadingImage.js.map +1 -0
  50. package/lib/module/components/AutoCompleteInput/AutoCompleteInput.js +3 -2
  51. package/lib/module/components/AutoCompleteInput/AutoCompleteInput.js.map +1 -1
  52. package/lib/module/components/AutoCompleteInput/AutoCompleteSuggestionItem.js +1 -1
  53. package/lib/module/components/AutoCompleteInput/AutoCompleteSuggestionItem.js.map +1 -1
  54. package/lib/module/components/Channel/Channel.js +23 -13
  55. package/lib/module/components/Channel/Channel.js.map +1 -1
  56. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js +4 -0
  57. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  58. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js +43 -19
  59. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  60. package/lib/module/components/MessageInput/FileUploadPreview.js +28 -21
  61. package/lib/module/components/MessageInput/FileUploadPreview.js.map +1 -1
  62. package/lib/module/components/MessageInput/ImageUploadPreview.js +16 -17
  63. package/lib/module/components/MessageInput/ImageUploadPreview.js.map +1 -1
  64. package/lib/module/components/RTLComponents/WritingDirectionAwareText.js +43 -0
  65. package/lib/module/components/RTLComponents/WritingDirectionAwareText.js.map +1 -0
  66. package/lib/module/components/Reply/Reply.js +7 -2
  67. package/lib/module/components/Reply/Reply.js.map +1 -1
  68. package/lib/module/contexts/messagesContext/MessagesContext.js +2 -2
  69. package/lib/module/contexts/messagesContext/MessagesContext.js.map +1 -1
  70. package/lib/module/contexts/themeContext/utils/theme.js +4 -0
  71. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  72. package/lib/module/mock-builders/api/channelMocks.js +299 -0
  73. package/lib/module/mock-builders/api/channelMocks.js.map +1 -0
  74. package/lib/module/version.json +1 -1
  75. package/lib/typescript/components/Attachment/GallaryImage.d.ts +5 -0
  76. package/lib/typescript/components/Attachment/Gallery.d.ts +1 -1
  77. package/lib/typescript/components/Attachment/Giphy.d.ts +1 -1
  78. package/lib/typescript/components/Attachment/ImageLoadingFailedIndicator.d.ts +3 -0
  79. package/lib/typescript/components/Attachment/ImageLoadingIndicator.d.ts +3 -0
  80. package/lib/typescript/components/Attachment/hooks/useLoadingImage.d.ts +7 -0
  81. package/lib/typescript/components/Channel/Channel.d.ts +1 -1
  82. package/lib/typescript/components/Channel/hooks/useCreateMessagesContext.d.ts +1 -1
  83. package/lib/typescript/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.d.ts +1 -0
  84. package/lib/typescript/components/RTLComponents/WritingDirectionAwareText.d.ts +4 -0
  85. package/lib/typescript/components/Reply/__tests__/Reply.test.d.ts +1 -0
  86. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +10 -0
  87. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +4 -0
  88. package/lib/typescript/mock-builders/api/channelMocks.d.ts +14 -0
  89. package/lib/typescript/mock-builders/api/getOrCreateChannel.d.ts +7 -0
  90. package/lib/typescript/mock-builders/api/useMockedApis.d.ts +1 -0
  91. package/lib/typescript/mock-builders/generator/channel.d.ts +116 -0
  92. package/package.json +3 -2
  93. package/src/components/Attachment/GallaryImage.tsx +22 -0
  94. package/src/components/Attachment/Gallery.tsx +68 -35
  95. package/src/components/Attachment/Giphy.tsx +68 -1
  96. package/src/components/Attachment/ImageLoadingFailedIndicator.tsx +56 -0
  97. package/src/components/Attachment/ImageLoadingIndicator.tsx +31 -0
  98. package/src/components/Attachment/__tests__/Attachment.test.js +9 -3
  99. package/src/components/Attachment/__tests__/Gallery.test.js +36 -1
  100. package/src/components/Attachment/__tests__/Giphy.test.js +110 -90
  101. package/src/components/Attachment/hooks/useLoadingImage.tsx +8 -0
  102. package/src/components/AutoCompleteInput/AutoCompleteInput.tsx +2 -1
  103. package/src/components/AutoCompleteInput/AutoCompleteSuggestionItem.tsx +1 -1
  104. package/src/components/Channel/Channel.tsx +8 -0
  105. package/src/components/Channel/hooks/useCreateMessagesContext.ts +4 -0
  106. package/src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js +0 -1
  107. package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx +145 -0
  108. package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +50 -24
  109. package/src/components/MessageInput/FileUploadPreview.tsx +11 -8
  110. package/src/components/MessageInput/ImageUploadPreview.tsx +4 -5
  111. package/src/components/MessageInput/__tests__/__snapshots__/MessageInput.test.js.snap +1 -0
  112. package/src/components/RTLComponents/WritingDirectionAwareText.tsx +17 -0
  113. package/src/components/Reply/Reply.tsx +19 -3
  114. package/src/components/Reply/__tests__/Reply.test.tsx +48 -0
  115. package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +1 -0
  116. package/src/contexts/messagesContext/MessagesContext.tsx +15 -0
  117. package/src/contexts/themeContext/utils/theme.ts +8 -0
  118. package/src/mock-builders/api/channelMocks.tsx +272 -0
  119. package/src/version.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext';
2
2
  import type { DefaultStreamChatGenerics } from '../../../types/types';
3
- export declare const useCreateMessagesContext: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ additionalTouchableProps, Attachment, AttachmentActions, Card, CardCover, CardFooter, CardHeader, channelId, DateHeader, deletedMessagesVisibilityType, disableTypingIndicator, dismissKeyboardOnMessageTouch, enableMessageGroupingByUser, FileAttachment, FileAttachmentGroup, FileAttachmentIcon, FlatList, forceAlignMessages, formatDate, Gallery, Giphy, giphyVersion, handleBlock, handleCopy, handleDelete, handleEdit, handleFlag, handleMute, handlePinMessage, handleQuotedReply, handleReaction, handleRetry, handleThreadReply, initialScrollToFirstUnreadMessage, InlineDateSeparator, InlineUnreadIndicator, isAttachmentEqual, legacyImageViewerSwipeBehaviour, markdownRules, Message, messageActions, MessageAvatar, MessageContent, messageContentOrder, MessageDeleted, MessageFooter, MessageHeader, MessageList, MessagePinnedHeader, MessageReplies, MessageRepliesAvatars, MessageSimple, MessageStatus, MessageSystem, MessageText, myMessageTheme, onLongPressMessage, onPressInMessage, onPressMessage, OverlayReactionList, ReactionList, removeMessage, Reply, retrySendMessage, ScrollToBottomButton, selectReaction, setEditingState, setQuotedMessageState, supportedReactions, targetedMessage, TypingIndicator, TypingIndicatorContainer, updateMessage, UrlPreview, VideoThumbnail, }: MessagesContextValue<StreamChatGenerics> & {
3
+ export declare const useCreateMessagesContext: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ additionalTouchableProps, Attachment, AttachmentActions, Card, CardCover, CardFooter, CardHeader, channelId, DateHeader, deletedMessagesVisibilityType, disableTypingIndicator, dismissKeyboardOnMessageTouch, enableMessageGroupingByUser, FileAttachment, FileAttachmentGroup, FileAttachmentIcon, FlatList, forceAlignMessages, formatDate, Gallery, Giphy, giphyVersion, handleBlock, handleCopy, handleDelete, handleEdit, handleFlag, handleMute, handlePinMessage, handleQuotedReply, handleReaction, handleRetry, handleThreadReply, ImageLoadingFailedIndicator, ImageLoadingIndicator, initialScrollToFirstUnreadMessage, InlineDateSeparator, InlineUnreadIndicator, isAttachmentEqual, legacyImageViewerSwipeBehaviour, markdownRules, Message, messageActions, MessageAvatar, MessageContent, messageContentOrder, MessageDeleted, MessageFooter, MessageHeader, MessageList, MessagePinnedHeader, MessageReplies, MessageRepliesAvatars, MessageSimple, MessageStatus, MessageSystem, MessageText, myMessageTheme, onLongPressMessage, onPressInMessage, onPressMessage, OverlayReactionList, ReactionList, removeMessage, Reply, retrySendMessage, ScrollToBottomButton, selectReaction, setEditingState, setQuotedMessageState, supportedReactions, targetedMessage, TypingIndicator, TypingIndicatorContainer, updateMessage, UrlPreview, VideoThumbnail, }: MessagesContextValue<StreamChatGenerics> & {
4
4
  /**
5
5
  * To ensure we allow re-render, when channel is changed
6
6
  */
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { ViewProps } from 'react-native';
3
+ export declare type WritingDirectionAwareTextProps = ViewProps;
4
+ export declare const WritingDirectionAwareText: React.FC<WritingDirectionAwareTextProps>;
@@ -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,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, };
@@ -0,0 +1,7 @@
1
+ export function getOrCreateChannelApi(channel?: any): {
2
+ response: {
3
+ data: any;
4
+ status: number;
5
+ };
6
+ type: string;
7
+ };
@@ -0,0 +1 @@
1
+ export function useMockedApis(client: any, apiResponses: any): void;
@@ -0,0 +1,116 @@
1
+ export function generateChannel(customValues: any): {
2
+ _client: {};
3
+ data: {
4
+ cid: string;
5
+ config: {
6
+ name: string;
7
+ type: string;
8
+ automod: string;
9
+ automod_behavior: string;
10
+ commands: {
11
+ args: string;
12
+ description: string;
13
+ name: string;
14
+ set: string;
15
+ }[];
16
+ connect_events: boolean;
17
+ created_at: string;
18
+ max_message_length: number;
19
+ message_retention: string;
20
+ mutes: boolean;
21
+ reactions: boolean;
22
+ read_events: boolean;
23
+ replies: boolean;
24
+ search: boolean;
25
+ typing_events: boolean;
26
+ updated_at: string;
27
+ uploads: boolean;
28
+ url_enrichment: boolean;
29
+ };
30
+ created_at: string;
31
+ created_by: {
32
+ banned: boolean;
33
+ created_at: string;
34
+ id: any;
35
+ image: any;
36
+ name: any;
37
+ online: boolean;
38
+ role: string;
39
+ updated_at: string;
40
+ };
41
+ frozen: boolean;
42
+ id: any;
43
+ own_capabilities: string[];
44
+ type: string;
45
+ updated_at: string;
46
+ };
47
+ id: any;
48
+ state: {
49
+ members: {};
50
+ messages: never[];
51
+ setIsUpToDate: jest.Mock<any, any>;
52
+ };
53
+ type: string;
54
+ };
55
+ export function generateChannelResponse(customValues?: {
56
+ channel: {};
57
+ id: any;
58
+ type: string;
59
+ }): {
60
+ channel: {
61
+ id: any;
62
+ type: string;
63
+ user: {
64
+ banned: boolean;
65
+ created_at: string;
66
+ id: any;
67
+ image: any;
68
+ name: any;
69
+ online: boolean;
70
+ role: string;
71
+ updated_at: string;
72
+ };
73
+ cid: string;
74
+ config: {
75
+ name: string;
76
+ type: string;
77
+ automod: string;
78
+ automod_behavior: string;
79
+ commands: {
80
+ args: string;
81
+ description: string;
82
+ name: string;
83
+ set: string;
84
+ }[];
85
+ connect_events: boolean;
86
+ created_at: string;
87
+ max_message_length: number;
88
+ message_retention: string;
89
+ mutes: boolean;
90
+ reactions: boolean;
91
+ read_events: boolean;
92
+ replies: boolean;
93
+ search: boolean;
94
+ typing_events: boolean;
95
+ updated_at: string;
96
+ uploads: boolean;
97
+ url_enrichment: boolean;
98
+ };
99
+ created_at: string;
100
+ created_by: {
101
+ banned: boolean;
102
+ created_at: string;
103
+ id: any;
104
+ image: any;
105
+ name: any;
106
+ online: boolean;
107
+ role: string;
108
+ updated_at: string;
109
+ };
110
+ frozen: boolean;
111
+ own_capabilities: string[];
112
+ updated_at: string;
113
+ };
114
+ members: never[];
115
+ messages: never[];
116
+ };
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.8.0",
4
+ "version": "4.9.0-beta.3",
5
5
  "author": {
6
6
  "company": "Stream.io Inc",
7
7
  "name": "Stream.io Inc"
@@ -34,7 +34,8 @@
34
34
  "prepare-distribution": "cd examples/SampleApp && npm version patch && react-native-version && git add ./ && git commit -m 'Bump build version' --no-verify",
35
35
  "prettier": "prettier --list-different '**/*.{js,ts,tsx,md,json}' .eslintrc.json ../.prettierrc .babelrc",
36
36
  "prettier-fix": "prettier --write '**/*.{js,ts,tsx,md,json}' .eslintrc.json ../.prettierrc .babelrc",
37
- "test:unit": "TZ=UTC jest --coverage",
37
+ "test:coverage": "yarn test:unit --coverage",
38
+ "test:unit": "TZ=UTC jest",
38
39
  "validate-translations": "node bin/validate-translations.js",
39
40
  "get-version": "echo $npm_package_version",
40
41
  "version": "bash ./bin/before-tag.sh",
@@ -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
+ };