@droppii-org/chat-mobile 0.2.23 → 0.2.25

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 (194) hide show
  1. package/lib/module/assets/images/btn_resend.png +0 -0
  2. package/lib/module/assets/images/index.js +2 -1
  3. package/lib/module/assets/images/index.js.map +1 -1
  4. package/lib/module/components/AttachmentPreview/FilePreview.js +1 -1
  5. package/lib/module/components/AttachmentPreview/FilePreview.js.map +1 -1
  6. package/lib/module/components/messages/MessageStatusIndicator.js +70 -0
  7. package/lib/module/components/messages/MessageStatusIndicator.js.map +1 -0
  8. package/lib/module/components/messages/fileMessage/index.js.map +1 -1
  9. package/lib/module/components/messages/imageMessage/index.js +59 -50
  10. package/lib/module/components/messages/imageMessage/index.js.map +1 -1
  11. package/lib/module/components/messages/linkMessage/index.js +14 -6
  12. package/lib/module/components/messages/linkMessage/index.js.map +1 -1
  13. package/lib/module/components/messages/mergedMessage/index.js.map +1 -1
  14. package/lib/module/components/messages/textMessage/index.js +14 -6
  15. package/lib/module/components/messages/textMessage/index.js.map +1 -1
  16. package/lib/module/components/messages/videoMessage/index.js.map +1 -1
  17. package/lib/module/config/uploadLimits.js +13 -0
  18. package/lib/module/config/uploadLimits.js.map +1 -0
  19. package/lib/module/core/useChatListener.js +35 -4
  20. package/lib/module/core/useChatListener.js.map +1 -1
  21. package/lib/module/core/useUserListener.js +17 -7
  22. package/lib/module/core/useUserListener.js.map +1 -1
  23. package/lib/module/hooks/message/useSendMessage.js +62 -15
  24. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  25. package/lib/module/hooks/useChatCompose.js +153 -0
  26. package/lib/module/hooks/useChatCompose.js.map +1 -0
  27. package/lib/module/hooks/useChatMessages.js +39 -8
  28. package/lib/module/hooks/useChatMessages.js.map +1 -1
  29. package/lib/module/hooks/useFileAttachment.js +1 -1
  30. package/lib/module/hooks/useFileAttachment.js.map +1 -1
  31. package/lib/module/hooks/useFileDownload.js +1 -1
  32. package/lib/module/hooks/useFileDownload.js.map +1 -1
  33. package/lib/module/hooks/useImageAttachment.js +81 -152
  34. package/lib/module/hooks/useImageAttachment.js.map +1 -1
  35. package/lib/module/hooks/useSendAttachment.js +2 -2
  36. package/lib/module/hooks/useSendAttachment.js.map +1 -1
  37. package/lib/module/index.js +1 -1
  38. package/lib/module/index.js.map +1 -1
  39. package/lib/module/screens/chat-detail/ChatComposer.js +50 -185
  40. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  41. package/lib/module/screens/chat-detail/ChatDetail.js +29 -137
  42. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  43. package/lib/module/screens/chat-detail/ChatListLegend.js +20 -15
  44. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  45. package/lib/module/screens/chat-detail/index.js +0 -1
  46. package/lib/module/screens/chat-detail/index.js.map +1 -1
  47. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +18 -13
  48. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  49. package/lib/module/services/attachmentHandlers/fileAttachmentHandler.js +2 -2
  50. package/lib/module/services/attachmentHandlers/fileAttachmentHandler.js.map +1 -1
  51. package/lib/module/services/attachmentHandlers/imageAttachmentHandler.js +3 -3
  52. package/lib/module/services/attachmentHandlers/imageAttachmentHandler.js.map +1 -1
  53. package/lib/module/services/attachmentHandlers/videoAttachmentHandler.js +2 -1
  54. package/lib/module/services/attachmentHandlers/videoAttachmentHandler.js.map +1 -1
  55. package/lib/module/services/attachmentOrchestrator.js +40 -17
  56. package/lib/module/services/attachmentOrchestrator.js.map +1 -1
  57. package/lib/module/services/message.js +13 -5
  58. package/lib/module/services/message.js.map +1 -1
  59. package/lib/module/translation/resources/i18n.js +4 -3
  60. package/lib/module/translation/resources/i18n.js.map +1 -1
  61. package/lib/module/types/chat.js.map +1 -1
  62. package/lib/module/types/global.d.js +2 -0
  63. package/lib/module/types/global.d.js.map +1 -0
  64. package/lib/module/types/index.d.js +3 -0
  65. package/lib/module/types/index.d.js.map +1 -0
  66. package/lib/module/utils/UIUtils.js +37 -0
  67. package/lib/module/utils/UIUtils.js.map +1 -0
  68. package/lib/module/utils/imageUtils.js +3 -3
  69. package/lib/module/utils/imageUtils.js.map +1 -1
  70. package/lib/module/utils/pLimit.js +19 -0
  71. package/lib/module/utils/pLimit.js.map +1 -0
  72. package/lib/module/utils/videoThumbnail.js +8 -3
  73. package/lib/module/utils/videoThumbnail.js.map +1 -1
  74. package/lib/module/utils/videoThumbnailUpload.js +76 -0
  75. package/lib/module/utils/videoThumbnailUpload.js.map +1 -0
  76. package/lib/typescript/src/assets/images/index.d.ts +1 -0
  77. package/lib/typescript/src/assets/images/index.d.ts.map +1 -1
  78. package/lib/typescript/src/components/messages/MessageStatusIndicator.d.ts +9 -0
  79. package/lib/typescript/src/components/messages/MessageStatusIndicator.d.ts.map +1 -0
  80. package/lib/typescript/src/components/messages/fileMessage/index.d.ts.map +1 -1
  81. package/lib/typescript/src/components/messages/imageMessage/index.d.ts.map +1 -1
  82. package/lib/typescript/src/components/messages/linkMessage/index.d.ts +3 -8
  83. package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
  84. package/lib/typescript/src/components/messages/mergedMessage/index.d.ts.map +1 -1
  85. package/lib/typescript/src/components/messages/textMessage/index.d.ts +2 -8
  86. package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
  87. package/lib/typescript/src/components/messages/videoMessage/index.d.ts.map +1 -1
  88. package/lib/typescript/src/config/uploadLimits.d.ts +7 -0
  89. package/lib/typescript/src/config/uploadLimits.d.ts.map +1 -0
  90. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  91. package/lib/typescript/src/core/useUserListener.d.ts.map +1 -1
  92. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +2 -0
  93. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  94. package/lib/typescript/src/hooks/useChatCompose.d.ts +27 -0
  95. package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -0
  96. package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
  97. package/lib/typescript/src/hooks/useImageAttachment.d.ts +2 -2
  98. package/lib/typescript/src/hooks/useImageAttachment.d.ts.map +1 -1
  99. package/lib/typescript/src/hooks/useSendAttachment.d.ts +1 -1
  100. package/lib/typescript/src/hooks/useSendAttachment.d.ts.map +1 -1
  101. package/lib/typescript/src/index.d.ts +1 -2
  102. package/lib/typescript/src/index.d.ts.map +1 -1
  103. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +5 -2
  104. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  105. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
  106. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  107. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +1 -1
  108. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  109. package/lib/typescript/src/screens/chat-detail/index.d.ts +0 -2
  110. package/lib/typescript/src/screens/chat-detail/index.d.ts.map +1 -1
  111. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +3 -5
  112. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  113. package/lib/typescript/src/screens/chat-detail/types.d.ts +1 -35
  114. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  115. package/lib/typescript/src/services/attachmentHandlers/fileAttachmentHandler.d.ts.map +1 -1
  116. package/lib/typescript/src/services/attachmentHandlers/imageAttachmentHandler.d.ts.map +1 -1
  117. package/lib/typescript/src/services/attachmentHandlers/videoAttachmentHandler.d.ts.map +1 -1
  118. package/lib/typescript/src/services/attachmentOrchestrator.d.ts +1 -1
  119. package/lib/typescript/src/services/attachmentOrchestrator.d.ts.map +1 -1
  120. package/lib/typescript/src/services/message.d.ts +7 -0
  121. package/lib/typescript/src/services/message.d.ts.map +1 -1
  122. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  123. package/lib/typescript/src/types/chat.d.ts +11 -0
  124. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  125. package/lib/typescript/src/utils/UIUtils.d.ts +4 -0
  126. package/lib/typescript/src/utils/UIUtils.d.ts.map +1 -0
  127. package/lib/typescript/src/utils/imageUtils.d.ts.map +1 -1
  128. package/lib/typescript/src/utils/pLimit.d.ts +6 -0
  129. package/lib/typescript/src/utils/pLimit.d.ts.map +1 -0
  130. package/lib/typescript/src/utils/videoThumbnail.d.ts.map +1 -1
  131. package/lib/typescript/src/utils/videoThumbnailUpload.d.ts +11 -0
  132. package/lib/typescript/src/utils/videoThumbnailUpload.d.ts.map +1 -0
  133. package/package.json +2 -2
  134. package/src/assets/images/btn_resend.png +0 -0
  135. package/src/assets/images/index.ts +1 -0
  136. package/src/components/AttachmentPreview/FilePreview.tsx +1 -1
  137. package/src/components/messages/MessageStatusIndicator.tsx +82 -0
  138. package/src/components/messages/fileMessage/index.tsx +0 -1
  139. package/src/components/messages/imageMessage/index.tsx +71 -57
  140. package/src/components/messages/linkMessage/index.tsx +13 -19
  141. package/src/components/messages/mergedMessage/index.tsx +0 -1
  142. package/src/components/messages/textMessage/index.tsx +13 -18
  143. package/src/components/messages/videoMessage/index.tsx +0 -1
  144. package/src/config/uploadLimits.ts +11 -0
  145. package/src/core/useChatListener.ts +38 -4
  146. package/src/core/useUserListener.ts +17 -7
  147. package/src/hooks/message/useSendMessage.ts +103 -24
  148. package/src/hooks/useChatCompose.ts +195 -0
  149. package/src/hooks/useChatMessages.ts +54 -10
  150. package/src/hooks/useFileAttachment.ts +1 -1
  151. package/src/hooks/useFileDownload.ts +1 -1
  152. package/src/hooks/useImageAttachment.ts +120 -199
  153. package/src/hooks/useSendAttachment.ts +3 -6
  154. package/src/index.tsx +3 -5
  155. package/src/screens/chat-detail/ChatComposer.tsx +88 -240
  156. package/src/screens/chat-detail/ChatDetail.tsx +36 -195
  157. package/src/screens/chat-detail/ChatListLegend.tsx +16 -13
  158. package/src/screens/chat-detail/index.ts +1 -5
  159. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +19 -12
  160. package/src/screens/chat-detail/types.ts +1 -38
  161. package/src/services/attachmentHandlers/fileAttachmentHandler.ts +3 -2
  162. package/src/services/attachmentHandlers/imageAttachmentHandler.ts +6 -3
  163. package/src/services/attachmentHandlers/videoAttachmentHandler.ts +2 -1
  164. package/src/services/attachmentOrchestrator.ts +49 -27
  165. package/src/services/message.ts +21 -4
  166. package/src/translation/resources/i18n.ts +6 -3
  167. package/src/types/chat.ts +6 -0
  168. package/src/types/global.d.ts +299 -0
  169. package/src/types/index.d.ts +1 -0
  170. package/src/utils/UIUtils.ts +31 -0
  171. package/src/utils/imageUtils.ts +3 -4
  172. package/src/utils/pLimit.ts +20 -0
  173. package/src/utils/videoThumbnail.ts +10 -3
  174. package/src/utils/videoThumbnailUpload.ts +103 -0
  175. package/lib/module/hooks/useVideoAttachment.js +0 -251
  176. package/lib/module/hooks/useVideoAttachment.js.map +0 -1
  177. package/lib/module/screens/chat-detail/hooks/useAttachmentSendHandler.js +0 -288
  178. package/lib/module/screens/chat-detail/hooks/useAttachmentSendHandler.js.map +0 -1
  179. package/lib/module/utils/giftedChatMessage.js +0 -90
  180. package/lib/module/utils/giftedChatMessage.js.map +0 -1
  181. package/lib/module/utils/ui.js +0 -17
  182. package/lib/module/utils/ui.js.map +0 -1
  183. package/lib/typescript/src/hooks/useVideoAttachment.d.ts +0 -29
  184. package/lib/typescript/src/hooks/useVideoAttachment.d.ts.map +0 -1
  185. package/lib/typescript/src/screens/chat-detail/hooks/useAttachmentSendHandler.d.ts +0 -26
  186. package/lib/typescript/src/screens/chat-detail/hooks/useAttachmentSendHandler.d.ts.map +0 -1
  187. package/lib/typescript/src/utils/giftedChatMessage.d.ts +0 -10
  188. package/lib/typescript/src/utils/giftedChatMessage.d.ts.map +0 -1
  189. package/lib/typescript/src/utils/ui.d.ts +0 -13
  190. package/lib/typescript/src/utils/ui.d.ts.map +0 -1
  191. package/src/hooks/useVideoAttachment.ts +0 -334
  192. package/src/screens/chat-detail/hooks/useAttachmentSendHandler.ts +0 -386
  193. package/src/utils/giftedChatMessage.ts +0 -137
  194. package/src/utils/ui.ts +0 -19
@@ -11,6 +11,8 @@ import {
11
11
  mergeMessages,
12
12
  } from '../utils/message';
13
13
  import { useMessageStore } from '../store/message';
14
+ import { useUserStore } from '../store/user';
15
+ import { useChatContext } from '../context';
14
16
 
15
17
  type UseChatMessagesOptions = {
16
18
  conversationId: string;
@@ -30,7 +32,11 @@ export function useChatMessages({
30
32
  const pushNewMessage = useMessageStore((s) => s.pushNewMessage);
31
33
  const reset = useMessageStore((s) => s.reset);
32
34
 
33
- const [currentUserId, setCurrentUserId] = useState<string>();
35
+ const { applicationType } = useChatContext();
36
+ const cachedUserId = useUserStore((s) => s.user?.userID);
37
+ const [currentUserId, setCurrentUserId] = useState<string | undefined>(
38
+ cachedUserId
39
+ );
34
40
  const [isLoading, setIsLoading] = useState(false);
35
41
  const [isLoadingEarlier, setIsLoadingEarlier] = useState(false);
36
42
  const [isLoadingNewer, setIsLoadingNewer] = useState(false);
@@ -46,27 +52,60 @@ export function useChatMessages({
46
52
  setError(null);
47
53
 
48
54
  try {
49
- const [history, userId] = await Promise.all([
55
+ // Fetch userId and history independently so an offline SDK state
56
+ // doesn't block reading local SQLite history.
57
+ const [historyResult, userIdResult] = await Promise.allSettled([
50
58
  ChatMessageAPI.fetchHistoryMessages(conversationId, {
51
59
  count: pageSize,
60
+ applicationType,
52
61
  }),
53
62
  ChatMessageAPI.getCurrentUserId(),
54
63
  ]);
55
64
 
56
- const initialMessages = history.messageList as DMessageItem[];
57
-
58
- setCurrentUserId(userId);
59
- setMessages(mergeMessages([], initialMessages), !history.isEnd);
65
+ if (userIdResult.status === 'fulfilled') {
66
+ setCurrentUserId(userIdResult.value);
67
+ }
60
68
 
61
- ChatMessageAPI.markConversationAsRead(conversationId).catch(
62
- () => undefined
63
- );
69
+ if (historyResult.status === 'fulfilled') {
70
+ const initialMessages = historyResult.value
71
+ .messageList as DMessageItem[];
72
+ setMessages(
73
+ mergeMessages([], initialMessages),
74
+ !historyResult.value.isEnd
75
+ );
76
+ ChatMessageAPI.markConversationAsRead(conversationId).catch(
77
+ () => undefined
78
+ );
79
+ } else {
80
+ // Fallback: try searchLocalMessages to verify local SQLite is accessible offline
81
+ console.warn(
82
+ '[useChatMessages] getAdvancedHistoryMessageList failed, trying searchLocalMessages fallback:',
83
+ historyResult.reason
84
+ );
85
+ const localResult = await ChatMessageAPI.searchLocalMessages(
86
+ conversationId,
87
+ { count: pageSize }
88
+ );
89
+ const localMessages = (localResult?.searchResultItems ?? []).flatMap(
90
+ (item: any) => item.messageList ?? []
91
+ ) as DMessageItem[];
92
+ console.log(
93
+ '[useChatMessages] searchLocalMessages fallback result:',
94
+ localMessages.length,
95
+ 'messages'
96
+ );
97
+ if (localMessages.length > 0) {
98
+ setMessages(mergeMessages([], localMessages), false);
99
+ } else {
100
+ throw historyResult.reason;
101
+ }
102
+ }
64
103
  } catch (err) {
65
104
  setError(err instanceof Error ? err : new Error(String(err)));
66
105
  } finally {
67
106
  setIsLoading(false);
68
107
  }
69
- }, [conversationId, pageSize, setMessages]);
108
+ }, [applicationType, conversationId, pageSize, setMessages]);
70
109
 
71
110
  const onLoadEarlier = useCallback(async () => {
72
111
  if (isLoadingEarlier || !hasMoreEarlier || !messages.length) return;
@@ -148,6 +187,11 @@ export function useChatMessages({
148
187
 
149
188
  const appendIncomingMessages = useCallback(
150
189
  (incoming: MessageItem[]) => {
190
+ console.log('[Listener] OnRecvNewMessages', {
191
+ total: incoming.length,
192
+ conversationId: conversationIdRef.current,
193
+ clientMsgIDs: incoming.map((m) => m.clientMsgID),
194
+ });
151
195
  const relevant = incoming.filter((message) =>
152
196
  belongsToConversation(message, conversationIdRef.current)
153
197
  );
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useState } from 'react';
2
2
  import { ImageUploadAPI } from '../services/imageUpload';
3
3
  import DeviceUtils from '../utils/device';
4
- import UIUtils from '../utils/ui';
4
+ import { UIUtils } from '../utils/UIUtils';
5
5
  import { trans } from '../translation';
6
6
  import { MAX_FILE_SIZE } from '../services/attachmentHandlers';
7
7
 
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { Platform, Share } from 'react-native';
3
3
  import RNFS from 'react-native-fs';
4
4
  import FileViewer from 'react-native-file-viewer';
5
- import UIUtils from '../utils/ui';
5
+ import { UIUtils } from '../utils/UIUtils';
6
6
  import { trans } from '../translation';
7
7
 
8
8
  export type DownloadStatus = 'idle' | 'downloading' | 'done' | 'error';
@@ -1,11 +1,16 @@
1
1
  import { useCallback, useState } from 'react';
2
- import type { Image as PickedImage } from 'react-native-image-crop-picker';
2
+ import type {
3
+ ImageOrVideo as PickedImage,
4
+ Video as PickedVideo,
5
+ } from 'react-native-image-crop-picker';
3
6
  import DeviceUtils from '../utils/device';
4
- import UIUtils from '../utils/ui';
7
+ import { UIUtils } from '../utils/UIUtils';
5
8
  import { trans } from '../translation';
6
9
  import { getMimeType, isVideoFromPicker } from '../utils/imageUtils';
10
+ import { UPLOAD_CONCURRENCY, UPLOAD_LIMITS } from '../config/uploadLimits';
7
11
  import { ImageUploadAPI } from '../services/imageUpload';
8
12
  import VideoThumbnailUtil from '../utils/videoThumbnail';
13
+ import { pLimit } from '../utils/pLimit';
9
14
 
10
15
  export interface ImageWithUploadStatus {
11
16
  image: PickedImage;
@@ -20,7 +25,7 @@ export interface ImageWithUploadStatus {
20
25
  export interface UseImageAttachmentReturn {
21
26
  selectedImages: ImageWithUploadStatus[];
22
27
  isImagePreviewVisible: boolean;
23
- handleImageAttach: () => Promise<void>;
28
+ handleImageAttach: (multipleMode?: boolean) => Promise<void>;
24
29
  handleImageRemove: (index: number) => void;
25
30
  clearAllImages: () => void;
26
31
  }
@@ -46,81 +51,49 @@ export const useImageAttachment = (): UseImageAttachmentReturn => {
46
51
 
47
52
  const uploadImage = useCallback(
48
53
  async (index: number, image: PickedImage) => {
49
- console.log('Upload', 'Step 1: Starting upload for image', { index });
50
-
51
- try {
52
- console.log('Upload', 'Step 2: Updating status to uploading...');
53
- updateImageStatus(index, {
54
- isUploading: true,
55
- error: null,
56
- uploadProgress: 0,
57
- });
58
-
59
- // ✅ OPTIMIZED: Use original path directly, no copy
60
- const filePath = image.path.startsWith('file://')
61
- ? image.path.replace('file://', '')
62
- : image.path;
54
+ updateImageStatus(index, {
55
+ isUploading: true,
56
+ error: null,
57
+ uploadProgress: 0,
58
+ });
63
59
 
64
- const filename = image.filename || 'image.jpg';
60
+ const filePath = image.path.startsWith('file://')
61
+ ? image.path.replace('file://', '')
62
+ : image.path;
65
63
 
66
- const setProgress = (progress: number) => {
67
- try {
68
- console.log('Upload', 'Progress', { percentage: progress });
69
- updateImageStatus(index, { uploadProgress: progress });
70
- } catch (err) {
71
- console.error('Upload', 'Progress update error', err);
72
- }
73
- };
64
+ const filename = image.filename || 'image.jpg';
74
65
 
75
- let uploadedImage;
76
- try {
77
- console.log('Upload', 'Uploading to server...', {
66
+ try {
67
+ const uploadedImage = await ImageUploadAPI.uploadImageFile(
68
+ {
69
+ path: filePath,
78
70
  filename,
79
- size: image.size,
80
- filePath,
81
- });
82
- uploadedImage = await ImageUploadAPI.uploadImageFile(
83
- {
84
- path: filePath,
85
- filename,
86
- size: image.size || 0,
87
- width: image.width || 0,
88
- height: image.height || 0,
89
- mime: getMimeType(filename),
90
- },
91
- setProgress
92
- );
93
- console.log('Upload', 'Upload successful!', {
94
- url: uploadedImage?.url,
95
- });
96
- } catch (uploadError: any) {
97
- console.error('Upload', 'Server upload error', uploadError);
98
- throw new Error(
99
- `Upload failed: ${uploadError?.message || 'Unknown error'}`
100
- );
101
- }
71
+ size: image.size || 0,
72
+ width: image.width || 0,
73
+ height: image.height || 0,
74
+ mime: getMimeType(filename),
75
+ },
76
+ (progress) => updateImageStatus(index, { uploadProgress: progress })
77
+ );
102
78
 
103
- if (!uploadedImage?.url) {
104
- throw new Error('No URL returned from server');
105
- }
79
+ if (!uploadedImage?.url) throw new Error('No URL returned from server');
106
80
 
107
- console.log('Upload', 'Upload complete, updating status...');
108
81
  updateImageStatus(index, {
109
82
  isUploading: false,
110
83
  uploadProgress: 100,
111
84
  uploadedUrl: uploadedImage.url,
112
85
  });
113
-
114
- console.log('Upload', 'COMPLETE ✓');
115
- } catch (error: any) {
116
- console.error('Upload', 'ERROR', error);
117
- const errorMsg = error?.message || trans('chat:image_upload_failed');
118
- updateImageStatus(index, {
119
- isUploading: false,
120
- error: errorMsg,
86
+ } catch (error: unknown) {
87
+ const msg =
88
+ error instanceof Error
89
+ ? error?.message
90
+ : trans('chat:image_upload_failed');
91
+ updateImageStatus(index, { isUploading: false, error: msg });
92
+ UIUtils.toast.open({
93
+ title: trans('chat:image_upload_failed'),
94
+ message: trans('chat:generic_error'),
95
+ theme: 'danger',
121
96
  });
122
-
123
- console.error('uploadImage', 'Full error', error);
124
97
  }
125
98
  },
126
99
  [updateImageStatus]
@@ -128,55 +101,36 @@ export const useImageAttachment = (): UseImageAttachmentReturn => {
128
101
 
129
102
  const generateVideoThumbnail = useCallback(
130
103
  async (index: number, image: PickedImage) => {
131
- if (!isVideoFromPicker(image)) return; // Only for videos
132
- console.log('[ImageAttach] Generating thumbnail for video:', image);
104
+ if (!isVideoFromPicker(image)) return;
133
105
 
134
- try {
135
- let videoDuration = (image as any).duration || 0;
106
+ const video = image as PickedVideo;
107
+ let videoDuration =
108
+ typeof video.duration === 'number' ? video.duration : 0;
136
109
 
137
- // FIX: Validate duration is reasonable
138
- if (videoDuration <= 0 || videoDuration > 3600000) {
139
- // Duration should be between 0 and 1 hour (3600000ms)
140
- console.warn(
141
- '[ImageAttach] Invalid duration, using default:',
142
- videoDuration
143
- );
144
- videoDuration = 5000; // Default 5 seconds
145
- }
146
-
147
- // ✅ FIX: Choose frame at 35% for better representation
148
- const timeMs = Math.min(
149
- Math.max(1000, Math.floor(videoDuration * 0.35)),
150
- videoDuration - 1000
151
- );
152
-
153
- // ✅ OPTIMIZED: Use original path directly, no copy
154
- const videoPath = image.path.startsWith('file://')
155
- ? image.path.replace('file://', '')
156
- : image.path;
157
-
158
- console.log('[ImageAttach] Generating thumbnail for video:', {
159
- filename: image.filename,
160
- videoPath,
161
- videoDuration,
162
- timeMs,
163
- });
110
+ if (videoDuration <= 0 || videoDuration > 3600000) {
111
+ videoDuration = 5000;
112
+ }
164
113
 
165
- // Generate thumbnail directly from original path
166
- console.log('[ImageAttach] Generating thumbnail at timeMs:', timeMs);
167
- const thumbnail = await VideoThumbnailUtil.generateThumbnail(
168
- videoPath,
169
- {
170
- time: timeMs,
171
- quality: 80,
172
- cacheId: `thumb-${index}-${Date.now()}`,
173
- }
174
- );
114
+ // Clamp to avoid negative timeMs on short clips.
115
+ const timeMs = Math.min(
116
+ Math.max(1000, Math.floor(videoDuration * 0.35)),
117
+ Math.max(0, videoDuration - 1000)
118
+ );
119
+
120
+ // Pass image.path directly — VideoThumbnailUtil owns path normalization
121
+ // (Android requires file:// to be preserved; stripping it causes a crash).
122
+ const baseName =
123
+ image.filename?.replace(/\.[^.]+$/, '') || `video-${index}`;
124
+
125
+ const thumbnail = await VideoThumbnailUtil.generateThumbnail(image.path, {
126
+ time: timeMs,
127
+ quality: 80,
128
+ cacheId: `thumb-${baseName}-${Date.now()}`,
129
+ });
175
130
 
176
- if (!thumbnail) throw new Error('Thumbnail generation failed');
131
+ if (!thumbnail) return;
177
132
 
178
- // Upload thumbnail (force .jpg extension for image file)
179
- const baseName = image.filename?.split('.')[0] || 'video';
133
+ try {
180
134
  const uploadedThumbnail = await ImageUploadAPI.uploadImageFile(
181
135
  {
182
136
  path: thumbnail.path,
@@ -186,34 +140,22 @@ export const useImageAttachment = (): UseImageAttachmentReturn => {
186
140
  height: thumbnail.height || 180,
187
141
  mime: 'image/jpeg',
188
142
  },
189
- (progress) => {
190
- console.log('[ImageAttach] Thumbnail upload progress:', {
191
- index,
192
- percentage: progress,
193
- });
194
- }
143
+ () => {}
195
144
  );
196
145
 
197
- if (!uploadedThumbnail?.url) {
198
- throw new Error('No thumbnail URL returned');
146
+ if (uploadedThumbnail?.url) {
147
+ updateImageStatus(index, {
148
+ thumbnailUrl: uploadedThumbnail.url,
149
+ thumbnailSize: uploadedThumbnail.size || 0,
150
+ });
199
151
  }
200
-
201
- // Update state with thumbnail
202
- updateImageStatus(index, {
203
- thumbnailUrl: uploadedThumbnail.url,
204
- thumbnailSize: uploadedThumbnail.size || 0,
205
- });
206
-
207
- console.log(
208
- `✅ [ImageAttach] THUMBNAIL URL (picked): ${uploadedThumbnail.url}`
209
- );
210
- } catch (error) {
211
- console.error('[ImageAttach] Video thumbnail generation error:', error);
212
- UIUtils.toast.open({
213
- title: 'Thumbnail Error',
214
- message: `Failed to generate thumbnail for ${image.filename}`,
215
- theme: 'danger',
216
- });
152
+ } catch (error: unknown) {
153
+ // Thumbnail failure is non-fatal — video still sends without preview.
154
+ if (__DEV__) {
155
+ console.warn('[ImageAttach] Thumbnail upload failed:', error);
156
+ }
157
+ } finally {
158
+ VideoThumbnailUtil.cleanupThumbnail(thumbnail.path).catch(() => {});
217
159
  }
218
160
  },
219
161
  [updateImageStatus]
@@ -221,89 +163,68 @@ export const useImageAttachment = (): UseImageAttachmentReturn => {
221
163
 
222
164
  const handleImageAttach = useCallback(
223
165
  async (multipleMode: boolean = true) => {
224
- console.log('ImageAttach', 'Step 1: User tapped attachment button', {
225
- multipleMode,
226
- });
227
166
  try {
228
- console.log('ImageAttach', 'Step 2: Opening image picker...');
229
167
  const images = await DeviceUtils.openImagePicker(multipleMode);
168
+ if (!images || images.length === 0) return;
230
169
 
231
- if (!images || images.length === 0) {
232
- console.log('ImageAttach', 'Step 3: User cancelled picker');
233
- return;
234
- }
235
-
236
- console.log('ImageAttach', 'Step 4: Images picked', {
237
- count: images.length,
238
- });
239
-
240
- // The gallery picker can return both images and videos. Apply the
241
- // correct size limit per type: 5MB for images, 200MB for videos.
242
- const MAX_IMAGE_SIZE = 5 * 1024 * 1024;
243
- const MAX_VIDEO_SIZE = 200 * 1024 * 1024;
244
170
  const isWithinLimit = (item: PickedImage) => {
245
171
  const maxSize = isVideoFromPicker(item)
246
- ? MAX_VIDEO_SIZE
247
- : MAX_IMAGE_SIZE;
172
+ ? UPLOAD_LIMITS.video
173
+ : UPLOAD_LIMITS.image;
248
174
  return (item.size || 0) <= maxSize;
249
175
  };
250
176
 
251
177
  const oversizedItems = images.filter((img) => !isWithinLimit(img));
252
-
253
178
  if (oversizedItems.length > 0) {
254
- const hasOversizedVideo = oversizedItems.some(isVideoFromPicker);
255
- console.warn('ImageAttach', 'Step 5: Some files too large', {
256
- count: oversizedItems.length,
257
- });
179
+ const isVideo = oversizedItems.some(isVideoFromPicker);
180
+ const maxSize = Math.floor(
181
+ (isVideo ? UPLOAD_LIMITS.video : UPLOAD_LIMITS.image) / 1024 / 1024
182
+ );
258
183
  UIUtils.toast.open({
259
184
  title: trans(
260
- hasOversizedVideo
261
- ? 'chat:video_too_large'
262
- : 'chat:image_too_large'
185
+ isVideo ? 'chat:video_too_large' : 'chat:image_too_large',
186
+ { maxSize: String(maxSize) }
263
187
  ),
264
188
  theme: 'danger',
265
189
  });
266
- const validImages = images.filter(isWithinLimit);
267
- if (validImages.length === 0) return;
268
- images.splice(0, images.length, ...validImages);
269
190
  }
270
191
 
271
- console.log('ImageAttach', 'Step 6: Size validation passed');
272
- const startIndex = selectedImages.length;
273
-
274
- console.log('ImageAttach', 'Step 7: Adding to preview list', {
275
- count: images.length,
276
- startIndex,
192
+ const finalImages = images.filter(isWithinLimit);
193
+ if (finalImages.length === 0) return;
194
+
195
+ // Derive startIndex inside the updater so concurrent calls don't
196
+ // produce the same index when state hasn't committed yet.
197
+ let startIndex = 0;
198
+ setSelectedImages((prev) => {
199
+ startIndex = prev.length;
200
+ return [
201
+ ...prev,
202
+ ...finalImages.map((image) => ({
203
+ image,
204
+ uploadProgress: 0,
205
+ error: null,
206
+ isUploading: true,
207
+ })),
208
+ ];
277
209
  });
278
-
279
- setSelectedImages((prev) => [
280
- ...prev,
281
- ...images.map((image) => ({
282
- image,
283
- uploadProgress: 0,
284
- error: null,
285
- isUploading: true,
286
- })),
287
- ]);
288
210
  setIsImagePreviewVisible(true);
289
211
 
290
- // Generate thumbnails for videos (parallel)
291
- console.log(
292
- 'ImageAttach',
293
- 'Step 7.5: Generating thumbnails for videos...'
294
- );
295
- await Promise.all(
296
- images.map((image, offset) =>
297
- generateVideoThumbnail(startIndex + offset, image)
298
- )
299
- );
300
-
301
- console.log('ImageAttach', 'Step 8: Starting uploads...');
302
- await Promise.all(
303
- images.map((image, offset) => uploadImage(startIndex + offset, image))
304
- );
305
- } catch (error) {
306
- console.error('ImageAttach', 'ERROR', error);
212
+ // Thumbnail generation and upload run concurrently per-item,
213
+ // both limited to UPLOAD_CONCURRENCY to avoid OOM on low-end devices.
214
+ await Promise.all([
215
+ pLimit(
216
+ finalImages,
217
+ (image, offset) =>
218
+ generateVideoThumbnail(startIndex + offset, image),
219
+ UPLOAD_CONCURRENCY
220
+ ),
221
+ pLimit(
222
+ finalImages,
223
+ (image, offset) => uploadImage(startIndex + offset, image),
224
+ UPLOAD_CONCURRENCY
225
+ ),
226
+ ]);
227
+ } catch (error: unknown) {
307
228
  UIUtils.toast.open({
308
229
  title: trans('chat:image_pick_failed'),
309
230
  message: error instanceof Error ? error.message : 'Unknown error',
@@ -311,7 +232,7 @@ export const useImageAttachment = (): UseImageAttachmentReturn => {
311
232
  });
312
233
  }
313
234
  },
314
- [selectedImages.length, uploadImage, generateVideoThumbnail]
235
+ [uploadImage, generateVideoThumbnail]
315
236
  );
316
237
 
317
238
  const handleImageRemove = useCallback((index: number) => {
@@ -56,8 +56,7 @@ export interface UseSendAttachmentReturn {
56
56
  error: string | null;
57
57
  send(
58
58
  items: AttachmentItem[],
59
- options?: Partial<AttachmentSendOptions>,
60
- onMessageSent?: (message: any) => void
59
+ options?: Partial<AttachmentSendOptions>
61
60
  ): Promise<boolean>;
62
61
  clearError(): void;
63
62
  }
@@ -75,8 +74,7 @@ export function useSendAttachment(): UseSendAttachmentReturn {
75
74
  const send = useCallback(
76
75
  async (
77
76
  items: AttachmentItem[],
78
- options?: Partial<AttachmentSendOptions>,
79
- onMessageSent?: (message: any) => void
77
+ options?: Partial<AttachmentSendOptions>
80
78
  ): Promise<boolean> => {
81
79
  console.log('[useSendAttachment] Start sending', {
82
80
  totalItems: items.length,
@@ -245,8 +243,7 @@ export function useSendAttachment(): UseSendAttachmentReturn {
245
243
  {
246
244
  strategy: 'individual',
247
245
  ...options,
248
- },
249
- onMessageSent
246
+ }
250
247
  );
251
248
 
252
249
  console.log('[useSendAttachment] Send result:', {
package/src/index.tsx CHANGED
@@ -50,9 +50,7 @@ export type {
50
50
  ChatQuickActionsRenderParams,
51
51
  ChatAttachmentPanelProps,
52
52
  } from './screens/chat-detail';
53
- export type { DGiftedChatMessage } from './utils/giftedChatMessage';
54
- export {
55
- mapOpenIMMessageToGiftedChat,
56
- mapOpenIMMessagesToGiftedChat,
57
- } from './utils/giftedChatMessage';
53
+
58
54
  export type { PeerType } from '@droppii/openim-rn-client-sdk';
55
+
56
+ export { registerUIUtils, UIUtils } from './utils/UIUtils';