stream-chat-react-native-core 9.0.2-beta.2 → 9.1.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 (212) hide show
  1. package/lib/commonjs/components/Attachment/Attachment.js +38 -10
  2. package/lib/commonjs/components/Attachment/Attachment.js.map +1 -1
  3. package/lib/commonjs/components/Attachment/AttachmentFileUploadProgressIndicator.js +97 -0
  4. package/lib/commonjs/components/Attachment/AttachmentFileUploadProgressIndicator.js.map +1 -0
  5. package/lib/commonjs/components/Attachment/AttachmentUploadIndicator.js +97 -0
  6. package/lib/commonjs/components/Attachment/AttachmentUploadIndicator.js.map +1 -0
  7. package/lib/commonjs/components/Attachment/CircularProgressIndicator.js +141 -0
  8. package/lib/commonjs/components/Attachment/CircularProgressIndicator.js.map +1 -0
  9. package/lib/commonjs/components/Attachment/FileAttachment.js +18 -4
  10. package/lib/commonjs/components/Attachment/FileAttachment.js.map +1 -1
  11. package/lib/commonjs/components/Attachment/Gallery.js +7 -1
  12. package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
  13. package/lib/commonjs/components/Attachment/MediaUploadProgressOverlay.js +59 -0
  14. package/lib/commonjs/components/Attachment/MediaUploadProgressOverlay.js.map +1 -0
  15. package/lib/commonjs/components/Attachment/VideoThumbnail.js +18 -8
  16. package/lib/commonjs/components/Attachment/VideoThumbnail.js.map +1 -1
  17. package/lib/commonjs/components/Attachment/utils/buildGallery/buildThumbnail.js +2 -0
  18. package/lib/commonjs/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  19. package/lib/commonjs/components/Channel/Channel.js +94 -92
  20. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  21. package/lib/commonjs/components/Chat/Chat.js +10 -1
  22. package/lib/commonjs/components/Chat/Chat.js.map +1 -1
  23. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.js +42 -54
  24. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.js.map +1 -1
  25. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js +10 -6
  26. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js.map +1 -1
  27. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.js +12 -6
  28. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.js.map +1 -1
  29. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js +13 -7
  30. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js.map +1 -1
  31. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.js +6 -2
  32. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.js.map +1 -1
  33. package/lib/commonjs/components/index.js +33 -0
  34. package/lib/commonjs/components/index.js.map +1 -1
  35. package/lib/commonjs/contexts/componentsContext/defaultComponents.js +6 -0
  36. package/lib/commonjs/contexts/componentsContext/defaultComponents.js.map +1 -1
  37. package/lib/commonjs/contexts/messageInputContext/hooks/useCreateMessageInputContext.js +3 -1
  38. package/lib/commonjs/contexts/messageInputContext/hooks/useCreateMessageInputContext.js.map +1 -1
  39. package/lib/commonjs/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.js +4 -1
  40. package/lib/commonjs/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.js.map +1 -1
  41. package/lib/commonjs/contexts/themeContext/utils/theme.js +5 -0
  42. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  43. package/lib/commonjs/hooks/index.js +11 -0
  44. package/lib/commonjs/hooks/index.js.map +1 -1
  45. package/lib/commonjs/hooks/usePendingAttachmentUpload.js +106 -0
  46. package/lib/commonjs/hooks/usePendingAttachmentUpload.js.map +1 -0
  47. package/lib/commonjs/index.js +12 -0
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/commonjs/middlewares/attachments.js +2 -0
  50. package/lib/commonjs/middlewares/attachments.js.map +1 -1
  51. package/lib/commonjs/native.js +8 -1
  52. package/lib/commonjs/native.js.map +1 -1
  53. package/lib/commonjs/nativeMultipartUpload.js +237 -0
  54. package/lib/commonjs/nativeMultipartUpload.js.map +1 -0
  55. package/lib/commonjs/types/types.js.map +1 -1
  56. package/lib/commonjs/utils/installNativeMultipartAdapter.js +223 -0
  57. package/lib/commonjs/utils/installNativeMultipartAdapter.js.map +1 -0
  58. package/lib/commonjs/utils/utils.js +2 -2
  59. package/lib/commonjs/utils/utils.js.map +1 -1
  60. package/lib/commonjs/version.json +1 -1
  61. package/lib/module/components/Attachment/Attachment.js +38 -10
  62. package/lib/module/components/Attachment/Attachment.js.map +1 -1
  63. package/lib/module/components/Attachment/AttachmentFileUploadProgressIndicator.js +97 -0
  64. package/lib/module/components/Attachment/AttachmentFileUploadProgressIndicator.js.map +1 -0
  65. package/lib/module/components/Attachment/AttachmentUploadIndicator.js +97 -0
  66. package/lib/module/components/Attachment/AttachmentUploadIndicator.js.map +1 -0
  67. package/lib/module/components/Attachment/CircularProgressIndicator.js +141 -0
  68. package/lib/module/components/Attachment/CircularProgressIndicator.js.map +1 -0
  69. package/lib/module/components/Attachment/FileAttachment.js +18 -4
  70. package/lib/module/components/Attachment/FileAttachment.js.map +1 -1
  71. package/lib/module/components/Attachment/Gallery.js +7 -1
  72. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  73. package/lib/module/components/Attachment/MediaUploadProgressOverlay.js +59 -0
  74. package/lib/module/components/Attachment/MediaUploadProgressOverlay.js.map +1 -0
  75. package/lib/module/components/Attachment/VideoThumbnail.js +18 -8
  76. package/lib/module/components/Attachment/VideoThumbnail.js.map +1 -1
  77. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js +2 -0
  78. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  79. package/lib/module/components/Channel/Channel.js +94 -92
  80. package/lib/module/components/Channel/Channel.js.map +1 -1
  81. package/lib/module/components/Chat/Chat.js +10 -1
  82. package/lib/module/components/Chat/Chat.js.map +1 -1
  83. package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.js +42 -54
  84. package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.js.map +1 -1
  85. package/lib/module/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js +10 -6
  86. package/lib/module/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js.map +1 -1
  87. package/lib/module/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.js +12 -6
  88. package/lib/module/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.js.map +1 -1
  89. package/lib/module/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js +13 -7
  90. package/lib/module/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js.map +1 -1
  91. package/lib/module/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.js +6 -2
  92. package/lib/module/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.js.map +1 -1
  93. package/lib/module/components/index.js +33 -0
  94. package/lib/module/components/index.js.map +1 -1
  95. package/lib/module/contexts/componentsContext/defaultComponents.js +6 -0
  96. package/lib/module/contexts/componentsContext/defaultComponents.js.map +1 -1
  97. package/lib/module/contexts/messageInputContext/hooks/useCreateMessageInputContext.js +3 -1
  98. package/lib/module/contexts/messageInputContext/hooks/useCreateMessageInputContext.js.map +1 -1
  99. package/lib/module/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.js +4 -1
  100. package/lib/module/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.js.map +1 -1
  101. package/lib/module/contexts/themeContext/utils/theme.js +5 -0
  102. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  103. package/lib/module/hooks/index.js +11 -0
  104. package/lib/module/hooks/index.js.map +1 -1
  105. package/lib/module/hooks/usePendingAttachmentUpload.js +106 -0
  106. package/lib/module/hooks/usePendingAttachmentUpload.js.map +1 -0
  107. package/lib/module/index.js +12 -0
  108. package/lib/module/index.js.map +1 -1
  109. package/lib/module/middlewares/attachments.js +2 -0
  110. package/lib/module/middlewares/attachments.js.map +1 -1
  111. package/lib/module/native.js +8 -1
  112. package/lib/module/native.js.map +1 -1
  113. package/lib/module/nativeMultipartUpload.js +237 -0
  114. package/lib/module/nativeMultipartUpload.js.map +1 -0
  115. package/lib/module/types/types.js.map +1 -1
  116. package/lib/module/utils/installNativeMultipartAdapter.js +223 -0
  117. package/lib/module/utils/installNativeMultipartAdapter.js.map +1 -0
  118. package/lib/module/utils/utils.js +2 -2
  119. package/lib/module/utils/utils.js.map +1 -1
  120. package/lib/module/version.json +1 -1
  121. package/lib/typescript/components/Attachment/Attachment.d.ts.map +1 -1
  122. package/lib/typescript/components/Attachment/AttachmentFileUploadProgressIndicator.d.ts +14 -0
  123. package/lib/typescript/components/Attachment/AttachmentFileUploadProgressIndicator.d.ts.map +1 -0
  124. package/lib/typescript/components/Attachment/AttachmentUploadIndicator.d.ts +18 -0
  125. package/lib/typescript/components/Attachment/AttachmentUploadIndicator.d.ts.map +1 -0
  126. package/lib/typescript/components/Attachment/CircularProgressIndicator.d.ts +18 -0
  127. package/lib/typescript/components/Attachment/CircularProgressIndicator.d.ts.map +1 -0
  128. package/lib/typescript/components/Attachment/FileAttachment.d.ts.map +1 -1
  129. package/lib/typescript/components/Attachment/Gallery.d.ts.map +1 -1
  130. package/lib/typescript/components/Attachment/MediaUploadProgressOverlay.d.ts +12 -0
  131. package/lib/typescript/components/Attachment/MediaUploadProgressOverlay.d.ts.map +1 -0
  132. package/lib/typescript/components/Attachment/VideoThumbnail.d.ts +4 -0
  133. package/lib/typescript/components/Attachment/VideoThumbnail.d.ts.map +1 -1
  134. package/lib/typescript/components/Attachment/utils/buildGallery/buildThumbnail.d.ts.map +1 -1
  135. package/lib/typescript/components/Attachment/utils/buildGallery/types.d.ts +2 -0
  136. package/lib/typescript/components/Attachment/utils/buildGallery/types.d.ts.map +1 -1
  137. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  138. package/lib/typescript/components/Chat/Chat.d.ts +10 -0
  139. package/lib/typescript/components/Chat/Chat.d.ts.map +1 -1
  140. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.d.ts +7 -2
  141. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.d.ts.map +1 -1
  142. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.d.ts.map +1 -1
  143. package/lib/typescript/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.d.ts.map +1 -1
  144. package/lib/typescript/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.d.ts.map +1 -1
  145. package/lib/typescript/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.d.ts.map +1 -1
  146. package/lib/typescript/components/index.d.ts +3 -0
  147. package/lib/typescript/components/index.d.ts.map +1 -1
  148. package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts +5 -2
  149. package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts.map +1 -1
  150. package/lib/typescript/contexts/messageInputContext/hooks/useCreateMessageInputContext.d.ts +1 -1
  151. package/lib/typescript/contexts/messageInputContext/hooks/useCreateMessageInputContext.d.ts.map +1 -1
  152. package/lib/typescript/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.d.ts.map +1 -1
  153. package/lib/typescript/contexts/themeContext/ThemeContext.d.ts +5 -0
  154. package/lib/typescript/contexts/themeContext/ThemeContext.d.ts.map +1 -1
  155. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +5 -0
  156. package/lib/typescript/contexts/themeContext/utils/theme.d.ts.map +1 -1
  157. package/lib/typescript/hooks/index.d.ts +1 -0
  158. package/lib/typescript/hooks/index.d.ts.map +1 -1
  159. package/lib/typescript/hooks/usePendingAttachmentUpload.d.ts +14 -0
  160. package/lib/typescript/hooks/usePendingAttachmentUpload.d.ts.map +1 -0
  161. package/lib/typescript/index.d.ts +1 -0
  162. package/lib/typescript/index.d.ts.map +1 -1
  163. package/lib/typescript/middlewares/attachments.d.ts.map +1 -1
  164. package/lib/typescript/native.d.ts +5 -2
  165. package/lib/typescript/native.d.ts.map +1 -1
  166. package/lib/typescript/nativeMultipartUpload.d.ts +98 -0
  167. package/lib/typescript/nativeMultipartUpload.d.ts.map +1 -0
  168. package/lib/typescript/types/types.d.ts +2 -0
  169. package/lib/typescript/types/types.d.ts.map +1 -1
  170. package/lib/typescript/utils/installNativeMultipartAdapter.d.ts +8 -0
  171. package/lib/typescript/utils/installNativeMultipartAdapter.d.ts.map +1 -0
  172. package/lib/typescript/utils/utils.d.ts +1 -1
  173. package/lib/typescript/utils/utils.d.ts.map +1 -1
  174. package/package.json +2 -2
  175. package/src/__tests__/nativeMultipartUpload.test.ts +267 -0
  176. package/src/components/Attachment/Attachment.tsx +54 -5
  177. package/src/components/Attachment/AttachmentFileUploadProgressIndicator.tsx +108 -0
  178. package/src/components/Attachment/AttachmentUploadIndicator.tsx +112 -0
  179. package/src/components/Attachment/CircularProgressIndicator.tsx +161 -0
  180. package/src/components/Attachment/FileAttachment.tsx +22 -6
  181. package/src/components/Attachment/Gallery.tsx +9 -1
  182. package/src/components/Attachment/MediaUploadProgressOverlay.tsx +77 -0
  183. package/src/components/Attachment/VideoThumbnail.tsx +17 -9
  184. package/src/components/Attachment/__tests__/Attachment.test.tsx +63 -16
  185. package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts +3 -0
  186. package/src/components/Attachment/utils/buildGallery/types.ts +2 -0
  187. package/src/components/Channel/Channel.tsx +65 -61
  188. package/src/components/Chat/Chat.tsx +20 -0
  189. package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx +51 -6
  190. package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx +31 -1
  191. package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.tsx +30 -44
  192. package/src/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.tsx +11 -5
  193. package/src/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.tsx +20 -5
  194. package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx +20 -9
  195. package/src/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.tsx +7 -1
  196. package/src/components/index.ts +3 -0
  197. package/src/contexts/componentsContext/defaultComponents.ts +6 -0
  198. package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +3 -1
  199. package/src/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.ts +5 -1
  200. package/src/contexts/themeContext/utils/theme.ts +10 -0
  201. package/src/hooks/__tests__/usePendingAttachmentUpload.test.tsx +106 -0
  202. package/src/hooks/index.ts +1 -0
  203. package/src/hooks/usePendingAttachmentUpload.ts +131 -0
  204. package/src/index.ts +1 -0
  205. package/src/middlewares/attachments.ts +2 -0
  206. package/src/native.ts +28 -0
  207. package/src/nativeMultipartUpload.ts +384 -0
  208. package/src/types/types.ts +2 -0
  209. package/src/utils/__tests__/installNativeMultipartAdapter.test.ts +437 -0
  210. package/src/utils/installNativeMultipartAdapter.ts +302 -0
  211. package/src/utils/utils.ts +3 -3
  212. package/src/version.json +1 -1
@@ -0,0 +1,108 @@
1
+ import React, { useMemo } from 'react';
2
+ import { StyleSheet, Text, View } from 'react-native';
3
+ import type { StyleProp, ViewStyle } from 'react-native';
4
+
5
+ import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext';
6
+ import { useTheme } from '../../contexts/themeContext/ThemeContext';
7
+ import { usePendingAttachmentUpload } from '../../hooks/usePendingAttachmentUpload';
8
+ import { primitives } from '../../theme';
9
+ import { isLocalUrl } from '../../utils/utils';
10
+
11
+ export type AttachmentFileUploadProgressIndicatorProps = {
12
+ containerStyle?: StyleProp<ViewStyle>;
13
+ localId?: string;
14
+ sourceUrl?: string;
15
+ totalBytes?: number | string | null;
16
+ };
17
+
18
+ const parseTotalBytes = (value: number | string | null | undefined): number | null => {
19
+ if (value == null) {
20
+ return null;
21
+ }
22
+ if (typeof value === 'number' && Number.isFinite(value)) {
23
+ return value;
24
+ }
25
+ if (typeof value === 'string') {
26
+ const n = parseFloat(value);
27
+ return Number.isFinite(n) ? n : null;
28
+ }
29
+ return null;
30
+ };
31
+
32
+ const formatMegabytesOneDecimal = (bytes: number) => {
33
+ if (!Number.isFinite(bytes) || bytes <= 0) {
34
+ return '0.0 MB';
35
+ }
36
+ return `${(bytes / (1000 * 1000)).toFixed(1)} MB`;
37
+ };
38
+
39
+ /**
40
+ * Circular progress plus `uploaded / total` for file and audio attachments during upload.
41
+ */
42
+ export const AttachmentFileUploadProgressIndicatorUI = ({
43
+ containerStyle,
44
+ localId,
45
+ sourceUrl,
46
+ totalBytes,
47
+ }: AttachmentFileUploadProgressIndicatorProps) => {
48
+ const {
49
+ theme: { semantics },
50
+ } = useTheme();
51
+ const { AttachmentUploadIndicator } = useComponentsContext();
52
+ const shouldTrackPendingUpload = !!localId && !!sourceUrl && isLocalUrl(sourceUrl);
53
+ const pendingUpload = usePendingAttachmentUpload(shouldTrackPendingUpload ? localId : undefined);
54
+ const uploadProgress = pendingUpload.uploadProgress;
55
+ const shouldRender = pendingUpload.isUploading;
56
+
57
+ const progressLabel = useMemo(() => {
58
+ const bytes = parseTotalBytes(totalBytes);
59
+ if (bytes == null || bytes <= 0) {
60
+ return null;
61
+ }
62
+ const uploaded = ((uploadProgress ?? 0) / 100) * bytes;
63
+ return `${formatMegabytesOneDecimal(uploaded)} / ${formatMegabytesOneDecimal(bytes)}`;
64
+ }, [totalBytes, uploadProgress]);
65
+
66
+ if (!shouldRender) {
67
+ return null;
68
+ }
69
+
70
+ return (
71
+ <View style={[styles.row, containerStyle]}>
72
+ <AttachmentUploadIndicator localId={localId} sourceUrl={sourceUrl} />
73
+ {progressLabel ? (
74
+ <Text numberOfLines={1} style={[styles.label, { color: semantics.textSecondary }]}>
75
+ {progressLabel}
76
+ </Text>
77
+ ) : null}
78
+ </View>
79
+ );
80
+ };
81
+
82
+ export const AttachmentFileUploadProgressIndicator = (
83
+ props: AttachmentFileUploadProgressIndicatorProps,
84
+ ) => {
85
+ const { localId, sourceUrl } = props;
86
+ const shouldTrackPendingUpload = !!localId && !!sourceUrl && isLocalUrl(sourceUrl);
87
+
88
+ if (!shouldTrackPendingUpload) {
89
+ return null;
90
+ }
91
+
92
+ return <AttachmentFileUploadProgressIndicatorUI {...props} />;
93
+ };
94
+
95
+ const styles = StyleSheet.create({
96
+ label: {
97
+ flex: 1,
98
+ flexShrink: 1,
99
+ fontSize: primitives.typographyFontSizeXs,
100
+ fontWeight: primitives.typographyFontWeightRegular,
101
+ lineHeight: primitives.typographyLineHeightTight,
102
+ },
103
+ row: {
104
+ alignItems: 'center',
105
+ flexDirection: 'row',
106
+ gap: primitives.spacingXxs,
107
+ },
108
+ });
@@ -0,0 +1,112 @@
1
+ import React from 'react';
2
+ import { ActivityIndicator, StyleSheet, View } from 'react-native';
3
+ import type { StyleProp, ViewStyle } from 'react-native';
4
+
5
+ import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext';
6
+ import { useTheme } from '../../contexts/themeContext/ThemeContext';
7
+ import { usePendingAttachmentUpload } from '../../hooks/usePendingAttachmentUpload';
8
+ import { isLocalUrl } from '../../utils/utils';
9
+
10
+ export type AttachmentUploadIndicatorProps = {
11
+ containerStyle?: StyleProp<ViewStyle>;
12
+ localId?: string;
13
+ size?: number;
14
+ sourceUrl?: string;
15
+ strokeWidth?: number;
16
+ style?: StyleProp<ViewStyle>;
17
+ testID?: string;
18
+ variant?: 'compact' | 'overlay';
19
+ };
20
+
21
+ /**
22
+ * Upload state for attachment previews: determinate ring when progress is known, otherwise `ActivityIndicator`.
23
+ */
24
+ export const AttachmentUploadIndicatorUI = ({
25
+ containerStyle,
26
+ localId,
27
+ size = 16,
28
+ strokeWidth = 2,
29
+ style,
30
+ testID,
31
+ variant = 'compact',
32
+ }: AttachmentUploadIndicatorProps) => {
33
+ const {
34
+ theme: { semantics },
35
+ } = useTheme();
36
+ const { CircularProgressIndicator, MediaUploadProgressOverlay } = useComponentsContext();
37
+ const pendingUpload = usePendingAttachmentUpload(localId);
38
+ const uploadProgress = pendingUpload.uploadProgress;
39
+ const shouldRender = pendingUpload.isUploading;
40
+ const resolvedSize = variant === 'overlay' && size === 16 ? 28 : size;
41
+ const resolvedStrokeWidth = variant === 'overlay' && strokeWidth === 2 ? 3 : strokeWidth;
42
+
43
+ if (!shouldRender) {
44
+ return null;
45
+ }
46
+
47
+ if (variant === 'overlay') {
48
+ return (
49
+ <MediaUploadProgressOverlay
50
+ progress={uploadProgress}
51
+ size={resolvedSize}
52
+ strokeWidth={resolvedStrokeWidth}
53
+ testID={testID}
54
+ />
55
+ );
56
+ }
57
+
58
+ return (
59
+ <View pointerEvents='none' style={containerStyle}>
60
+ {uploadProgress === undefined ? (
61
+ <View
62
+ pointerEvents='none'
63
+ style={[styles.indeterminateWrap, { height: size, width: size }, style]}
64
+ testID={testID}
65
+ >
66
+ <ActivityIndicator color={semantics.accentPrimary} size='small' />
67
+ </View>
68
+ ) : (
69
+ <CircularProgressIndicator
70
+ backgroundColor={semantics.backgroundCoreElevation0}
71
+ filledColor={semantics.accentPrimary}
72
+ progress={uploadProgress}
73
+ size={resolvedSize}
74
+ strokeWidth={resolvedStrokeWidth}
75
+ style={style}
76
+ testID={testID}
77
+ unfilledColor={semantics.borderCoreDefault}
78
+ />
79
+ )}
80
+ </View>
81
+ );
82
+ };
83
+
84
+ export const AttachmentUploadIndicator = ({
85
+ containerStyle,
86
+ localId,
87
+ sourceUrl,
88
+ variant,
89
+ ...props
90
+ }: AttachmentUploadIndicatorProps) => {
91
+ const shouldTrackPendingUpload = !!localId && !!sourceUrl && isLocalUrl(sourceUrl);
92
+
93
+ if (!shouldTrackPendingUpload) {
94
+ return null;
95
+ }
96
+
97
+ return (
98
+ <AttachmentUploadIndicatorUI
99
+ {...props}
100
+ containerStyle={containerStyle}
101
+ localId={localId}
102
+ variant={variant}
103
+ />
104
+ );
105
+ };
106
+
107
+ const styles = StyleSheet.create({
108
+ indeterminateWrap: {
109
+ alignItems: 'center',
110
+ justifyContent: 'center',
111
+ },
112
+ });
@@ -0,0 +1,161 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import type { ColorValue, StyleProp, ViewStyle } from 'react-native';
3
+ import Animated, {
4
+ cancelAnimation,
5
+ Easing,
6
+ useAnimatedProps,
7
+ useAnimatedStyle,
8
+ useSharedValue,
9
+ withRepeat,
10
+ withTiming,
11
+ } from 'react-native-reanimated';
12
+ import Svg, { Circle } from 'react-native-svg';
13
+
14
+ const AnimatedCircle = Animated.createAnimatedComponent(Circle);
15
+ const SPIN_DURATION_MS = 900;
16
+ const PROGRESS_ANIMATION_DURATION_MS = 1200;
17
+
18
+ export type CircularProgressIndicatorProps = {
19
+ /** Upload percent **0–100**. */
20
+ backgroundColor: ColorValue;
21
+ filledColor: ColorValue;
22
+ progress: number;
23
+ unfilledColor: ColorValue;
24
+ size?: number;
25
+ strokeWidth?: number;
26
+ style?: StyleProp<ViewStyle>;
27
+ testID?: string;
28
+ };
29
+
30
+ /**
31
+ * Circular upload progress ring (determinate) or rotating arc (indeterminate).
32
+ */
33
+ export const CircularProgressIndicator = ({
34
+ backgroundColor,
35
+ filledColor,
36
+ progress,
37
+ size = 16,
38
+ strokeWidth = 2,
39
+ style,
40
+ testID,
41
+ unfilledColor,
42
+ }: CircularProgressIndicatorProps) => {
43
+ const animatedProgress = useSharedValue(0);
44
+ const rotation = useSharedValue(0);
45
+
46
+ const { cx, cy, r, circumference } = useMemo(() => {
47
+ const pad = strokeWidth / 2;
48
+ const rInner = size / 2 - pad;
49
+ return {
50
+ cx: size / 2,
51
+ cy: size / 2,
52
+ r: rInner,
53
+ circumference: 2 * Math.PI * rInner,
54
+ };
55
+ }, [size, strokeWidth]);
56
+
57
+ const fraction =
58
+ progress === undefined || Number.isNaN(progress)
59
+ ? undefined
60
+ : Math.min(100, Math.max(0, progress)) / 100;
61
+
62
+ useEffect(() => {
63
+ if (fraction === undefined) {
64
+ animatedProgress.value = 0;
65
+ return;
66
+ }
67
+
68
+ animatedProgress.value = withTiming(fraction, {
69
+ duration: PROGRESS_ANIMATION_DURATION_MS,
70
+ easing: Easing.out(Easing.cubic),
71
+ });
72
+ }, [animatedProgress, fraction]);
73
+
74
+ useEffect(() => {
75
+ if (fraction !== undefined) {
76
+ cancelAnimation(rotation);
77
+ rotation.value = 0;
78
+ return;
79
+ }
80
+
81
+ rotation.value = withRepeat(
82
+ withTiming(360, {
83
+ duration: SPIN_DURATION_MS,
84
+ easing: Easing.linear,
85
+ }),
86
+ -1,
87
+ false,
88
+ );
89
+
90
+ return () => {
91
+ cancelAnimation(rotation);
92
+ };
93
+ }, [fraction, rotation]);
94
+
95
+ const animatedCircleProps = useAnimatedProps(() => ({
96
+ strokeDashoffset: circumference * (1 - animatedProgress.value),
97
+ }));
98
+
99
+ const animatedSpinStyle = useAnimatedStyle<ViewStyle>(() => ({
100
+ transform: [{ rotate: `${rotation.value}deg` }],
101
+ }));
102
+
103
+ if (fraction !== undefined) {
104
+ return (
105
+ <Svg height={size} style={style} testID={testID} viewBox={`0 0 ${size} ${size}`} width={size}>
106
+ <Circle
107
+ cx={cx}
108
+ cy={cy}
109
+ fill={backgroundColor}
110
+ r={r}
111
+ stroke={unfilledColor}
112
+ strokeWidth={strokeWidth}
113
+ />
114
+ <AnimatedCircle
115
+ animatedProps={animatedCircleProps}
116
+ cx={cx}
117
+ cy={cy}
118
+ fill='none'
119
+ r={r}
120
+ stroke={filledColor}
121
+ strokeDasharray={`${circumference}`}
122
+ strokeLinecap='round'
123
+ strokeWidth={strokeWidth}
124
+ transform={`rotate(-90 ${cx} ${cy})`}
125
+ />
126
+ </Svg>
127
+ );
128
+ }
129
+
130
+ const arc = circumference * 0.22;
131
+ const gap = circumference - arc;
132
+
133
+ return (
134
+ <Animated.View
135
+ style={[{ height: size, width: size }, style, animatedSpinStyle]}
136
+ testID={testID}
137
+ >
138
+ <Svg height={size} viewBox={`0 0 ${size} ${size}`} width={size}>
139
+ <Circle
140
+ cx={cx}
141
+ cy={cy}
142
+ fill={backgroundColor}
143
+ r={r}
144
+ stroke={unfilledColor}
145
+ strokeWidth={strokeWidth}
146
+ />
147
+ <Circle
148
+ cx={cx}
149
+ cy={cy}
150
+ fill='none'
151
+ r={r}
152
+ stroke={filledColor}
153
+ strokeDasharray={`${arc} ${gap}`}
154
+ strokeLinecap='round'
155
+ strokeWidth={strokeWidth}
156
+ transform={`rotate(-90 ${cx} ${cy})`}
157
+ />
158
+ </Svg>
159
+ </Animated.View>
160
+ );
161
+ };
@@ -1,8 +1,9 @@
1
1
  import React, { useMemo } from 'react';
2
- import { Pressable, StyleProp, StyleSheet, TextStyle, ViewStyle } from 'react-native';
2
+ import { Pressable, StyleProp, StyleSheet, TextStyle, View, ViewStyle } from 'react-native';
3
3
 
4
4
  import type { Attachment } from 'stream-chat';
5
5
 
6
+ import { AttachmentFileUploadProgressIndicator } from './AttachmentFileUploadProgressIndicator';
6
7
  import { openUrlSafely } from './utils/openUrlSafely';
7
8
 
8
9
  import { FileIconProps } from '../../components/Attachment/FileIcon';
@@ -17,6 +18,7 @@ import {
17
18
  useMessagesContext,
18
19
  } from '../../contexts/messagesContext/MessagesContext';
19
20
  import { useTheme } from '../../contexts/themeContext/ThemeContext';
21
+ import type { DefaultAttachmentData } from '../../types/types';
20
22
 
21
23
  export type FileAttachmentPropsWithContext = Pick<
22
24
  MessageContextValue,
@@ -50,6 +52,8 @@ const FileAttachmentWithContext = (props: FileAttachmentPropsWithContext) => {
50
52
  } = props;
51
53
  const { FilePreview } = useComponentsContext();
52
54
 
55
+ const localId = (attachment as DefaultAttachmentData).localId;
56
+
53
57
  const defaultOnPress = () => openUrlSafely(attachment.asset_url);
54
58
 
55
59
  return (
@@ -87,11 +91,20 @@ const FileAttachmentWithContext = (props: FileAttachmentPropsWithContext) => {
87
91
  testID='file-attachment'
88
92
  {...additionalPressableProps}
89
93
  >
90
- <FilePreview
91
- attachment={attachment}
92
- attachmentIconSize={attachmentIconSize}
93
- styles={stylesProp}
94
- />
94
+ <View style={styles.previewWrap}>
95
+ <FilePreview
96
+ attachment={attachment}
97
+ attachmentIconSize={attachmentIconSize}
98
+ indicator={
99
+ <AttachmentFileUploadProgressIndicator
100
+ localId={localId}
101
+ sourceUrl={attachment.asset_url ?? attachment.originalFile?.uri}
102
+ totalBytes={attachment.file_size}
103
+ />
104
+ }
105
+ styles={stylesProp}
106
+ />
107
+ </View>
95
108
  </Pressable>
96
109
  );
97
110
  };
@@ -135,6 +148,9 @@ const useStyles = () => {
135
148
  ? semantics.chatBgAttachmentOutgoing
136
149
  : semantics.chatBgAttachmentIncoming,
137
150
  },
151
+ previewWrap: {
152
+ position: 'relative',
153
+ },
138
154
  });
139
155
  }, [showBackgroundTransparent, isMyMessage, semantics]);
140
156
  };
@@ -299,6 +299,7 @@ const GalleryThumbnail = ({
299
299
  >
300
300
  {thumbnail.type === FileTypes.Video ? (
301
301
  <VideoThumbnail
302
+ localId={thumbnail.localId}
302
303
  style={[styles.image, imageBorderRadius ?? borderRadius, image]}
303
304
  thumb_url={thumbnail.thumb_url}
304
305
  />
@@ -330,7 +331,8 @@ const GalleryImageThumbnail = ({
330
331
  borderRadius,
331
332
  thumbnail,
332
333
  }: Pick<GalleryThumbnailProps, 'thumbnail' | 'borderRadius'>) => {
333
- const { ImageLoadingFailedIndicator, ImageLoadingIndicator } = useComponentsContext();
334
+ const { AttachmentUploadIndicator, ImageLoadingFailedIndicator, ImageLoadingIndicator } =
335
+ useComponentsContext();
334
336
  const {
335
337
  isLoadingImage,
336
338
  isLoadingImageError,
@@ -344,6 +346,7 @@ const GalleryImageThumbnail = ({
344
346
  },
345
347
  } = useTheme();
346
348
  const styles = useStyles();
349
+
347
350
  const onLoadStart = useStableCallback(() => {
348
351
  setLoadingImageError(false);
349
352
  setLoadingImage(true);
@@ -374,6 +377,11 @@ const GalleryImageThumbnail = ({
374
377
  uri={thumbnail.url}
375
378
  />
376
379
  {isLoadingImage ? <ImageLoadingIndicator /> : null}
380
+ <AttachmentUploadIndicator
381
+ localId={thumbnail.localId}
382
+ sourceUrl={thumbnail.url}
383
+ variant='overlay'
384
+ />
377
385
  </>
378
386
  )}
379
387
  </View>
@@ -0,0 +1,77 @@
1
+ import React, { useMemo } from 'react';
2
+ import { ActivityIndicator, StyleSheet, View } from 'react-native';
3
+
4
+ import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext';
5
+ import { useTheme } from '../../contexts/themeContext/ThemeContext';
6
+
7
+ export type MediaUploadProgressOverlayProps = {
8
+ progress?: number;
9
+ size?: number;
10
+ strokeWidth?: number;
11
+ testID?: string;
12
+ };
13
+
14
+ /**
15
+ * Full-cover upload overlay for image and video thumbnails.
16
+ */
17
+ export const MediaUploadProgressOverlay = ({
18
+ progress,
19
+ size = 18,
20
+ strokeWidth = 3,
21
+ testID,
22
+ }: MediaUploadProgressOverlayProps) => {
23
+ const styles = useStyles();
24
+ const { CircularProgressIndicator } = useComponentsContext();
25
+ const {
26
+ theme: {
27
+ messageItemView: { attachmentUploadIndicator },
28
+ semantics,
29
+ },
30
+ } = useTheme();
31
+
32
+ return (
33
+ <View
34
+ pointerEvents='none'
35
+ style={[
36
+ StyleSheet.absoluteFill,
37
+ styles.indicatorContainer,
38
+ attachmentUploadIndicator.overlayContent,
39
+ ]}
40
+ testID={testID ? `${testID}-content` : undefined}
41
+ >
42
+ {typeof progress === 'number' ? (
43
+ <CircularProgressIndicator
44
+ backgroundColor={semantics.backgroundCoreElevation0}
45
+ filledColor={semantics.accentPrimary}
46
+ progress={progress}
47
+ size={size}
48
+ strokeWidth={strokeWidth}
49
+ style={attachmentUploadIndicator.indicator}
50
+ unfilledColor={semantics.borderCoreDefault}
51
+ />
52
+ ) : (
53
+ <ActivityIndicator
54
+ color={semantics.accentPrimary}
55
+ style={attachmentUploadIndicator.indicator}
56
+ />
57
+ )}
58
+ </View>
59
+ );
60
+ };
61
+
62
+ const useStyles = () => {
63
+ const {
64
+ theme: { semantics },
65
+ } = useTheme();
66
+ return useMemo(
67
+ () =>
68
+ StyleSheet.create({
69
+ indicatorContainer: {
70
+ alignItems: 'center',
71
+ justifyContent: 'center',
72
+ backgroundColor: semantics.backgroundCoreOverlayLight,
73
+ },
74
+ }),
75
+ [semantics],
76
+ );
77
+ };
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
- import { ImageBackground, ImageStyle, StyleProp, StyleSheet, ViewStyle } from 'react-native';
2
+ import { ImageStyle, StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
3
3
 
4
+ import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext';
4
5
  import { useTheme } from '../../contexts/themeContext/ThemeContext';
5
6
  import { VideoPlayIndicator } from '../ui/VideoPlayIndicator';
6
7
 
@@ -15,6 +16,10 @@ const styles = StyleSheet.create({
15
16
 
16
17
  export type VideoThumbnailProps = {
17
18
  imageStyle?: StyleProp<ImageStyle>;
19
+ /**
20
+ * When set, upload state is read from `client.uploadManager` for this pending attachment id.
21
+ */
22
+ localId?: string;
18
23
  style?: StyleProp<ViewStyle>;
19
24
  thumb_url?: string;
20
25
  };
@@ -27,15 +32,18 @@ export const VideoThumbnail = (props: VideoThumbnailProps) => {
27
32
  },
28
33
  },
29
34
  } = useTheme();
30
- const { imageStyle, style, thumb_url } = props;
35
+ const { AttachmentUploadIndicator, ImageComponent } = useComponentsContext();
36
+ const { imageStyle, localId, style, thumb_url } = props;
37
+
31
38
  return (
32
- <ImageBackground
33
- accessibilityLabel='Video Thumbnail'
34
- imageStyle={imageStyle}
35
- source={{ uri: thumb_url }}
36
- style={[styles.container, container, style]}
37
- >
39
+ <View style={[styles.container, container, style]}>
40
+ <ImageComponent
41
+ accessibilityLabel='Video Thumbnail'
42
+ source={{ uri: thumb_url }}
43
+ style={[StyleSheet.absoluteFill, imageStyle]}
44
+ />
38
45
  <VideoPlayIndicator size='md' />
39
- </ImageBackground>
46
+ <AttachmentUploadIndicator localId={localId} sourceUrl={thumb_url} variant='overlay' />
47
+ </View>
40
48
  );
41
49
  };