@droppii-org/chat-mobile 0.2.24 → 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.
- package/lib/module/assets/images/btn_resend.png +0 -0
- package/lib/module/assets/images/index.js +2 -1
- package/lib/module/assets/images/index.js.map +1 -1
- package/lib/module/components/AttachmentPreview/FilePreview.js +1 -1
- package/lib/module/components/AttachmentPreview/FilePreview.js.map +1 -1
- package/lib/module/components/messages/MessageStatusIndicator.js +70 -0
- package/lib/module/components/messages/MessageStatusIndicator.js.map +1 -0
- package/lib/module/components/messages/fileMessage/index.js.map +1 -1
- package/lib/module/components/messages/imageMessage/index.js +59 -50
- package/lib/module/components/messages/imageMessage/index.js.map +1 -1
- package/lib/module/components/messages/linkMessage/index.js +14 -6
- package/lib/module/components/messages/linkMessage/index.js.map +1 -1
- package/lib/module/components/messages/mergedMessage/index.js.map +1 -1
- package/lib/module/components/messages/textMessage/index.js +14 -6
- package/lib/module/components/messages/textMessage/index.js.map +1 -1
- package/lib/module/components/messages/videoMessage/index.js.map +1 -1
- package/lib/module/config/uploadLimits.js +1 -1
- package/lib/module/config/uploadLimits.js.map +1 -1
- package/lib/module/core/useChatListener.js +35 -4
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/core/useUserListener.js +17 -7
- package/lib/module/core/useUserListener.js.map +1 -1
- package/lib/module/hooks/message/useSendMessage.js +62 -15
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/useChatCompose.js +39 -28
- package/lib/module/hooks/useChatCompose.js.map +1 -1
- package/lib/module/hooks/useChatMessages.js +39 -8
- package/lib/module/hooks/useChatMessages.js.map +1 -1
- package/lib/module/hooks/useFileAttachment.js +1 -1
- package/lib/module/hooks/useFileAttachment.js.map +1 -1
- package/lib/module/hooks/useFileDownload.js +1 -1
- package/lib/module/hooks/useFileDownload.js.map +1 -1
- package/lib/module/hooks/useImageAttachment.js +12 -3
- package/lib/module/hooks/useImageAttachment.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +13 -0
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +20 -15
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +18 -13
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/services/attachmentOrchestrator.js +34 -12
- package/lib/module/services/attachmentOrchestrator.js.map +1 -1
- package/lib/module/services/message.js +13 -5
- package/lib/module/services/message.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +4 -3
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/types/chat.js.map +1 -1
- package/lib/module/types/global.d.js +2 -0
- package/lib/module/types/global.d.js.map +1 -0
- package/lib/module/types/index.d.js +3 -0
- package/lib/module/types/index.d.js.map +1 -0
- package/lib/module/utils/UIUtils.js +37 -0
- package/lib/module/utils/UIUtils.js.map +1 -0
- package/lib/module/utils/videoThumbnailUpload.js +1 -1
- package/lib/module/utils/videoThumbnailUpload.js.map +1 -1
- package/lib/typescript/src/assets/images/index.d.ts +1 -0
- package/lib/typescript/src/assets/images/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/MessageStatusIndicator.d.ts +9 -0
- package/lib/typescript/src/components/messages/MessageStatusIndicator.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/fileMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/imageMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts +3 -8
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/mergedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts +2 -8
- package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/videoMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
- package/lib/typescript/src/core/useUserListener.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts +2 -0
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useImageAttachment.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +3 -5
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/services/attachmentOrchestrator.d.ts.map +1 -1
- package/lib/typescript/src/services/message.d.ts +7 -0
- package/lib/typescript/src/services/message.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/chat.d.ts +11 -0
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/utils/UIUtils.d.ts +4 -0
- package/lib/typescript/src/utils/UIUtils.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/assets/images/btn_resend.png +0 -0
- package/src/assets/images/index.ts +1 -0
- package/src/components/AttachmentPreview/FilePreview.tsx +1 -1
- package/src/components/messages/MessageStatusIndicator.tsx +82 -0
- package/src/components/messages/fileMessage/index.tsx +0 -1
- package/src/components/messages/imageMessage/index.tsx +71 -57
- package/src/components/messages/linkMessage/index.tsx +13 -19
- package/src/components/messages/mergedMessage/index.tsx +0 -1
- package/src/components/messages/textMessage/index.tsx +13 -18
- package/src/components/messages/videoMessage/index.tsx +0 -1
- package/src/config/uploadLimits.ts +1 -1
- package/src/core/useChatListener.ts +38 -4
- package/src/core/useUserListener.ts +17 -7
- package/src/hooks/message/useSendMessage.ts +103 -24
- package/src/hooks/useChatCompose.ts +42 -33
- package/src/hooks/useChatMessages.ts +54 -10
- package/src/hooks/useFileAttachment.ts +1 -1
- package/src/hooks/useFileDownload.ts +1 -1
- package/src/hooks/useImageAttachment.ts +13 -5
- package/src/index.tsx +2 -0
- package/src/screens/chat-detail/ChatDetail.tsx +12 -0
- package/src/screens/chat-detail/ChatListLegend.tsx +16 -13
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +19 -12
- package/src/services/attachmentOrchestrator.ts +45 -21
- package/src/services/message.ts +21 -4
- package/src/translation/resources/i18n.ts +6 -3
- package/src/types/chat.ts +6 -0
- package/src/types/global.d.ts +299 -0
- package/src/types/index.d.ts +1 -0
- package/src/utils/UIUtils.ts +31 -0
- package/src/utils/videoThumbnailUpload.ts +1 -1
- package/lib/module/hooks/useVideoAttachment.js +0 -251
- package/lib/module/hooks/useVideoAttachment.js.map +0 -1
- package/lib/module/utils/ui.js +0 -17
- package/lib/module/utils/ui.js.map +0 -1
- package/lib/typescript/src/hooks/useVideoAttachment.d.ts +0 -29
- package/lib/typescript/src/hooks/useVideoAttachment.d.ts.map +0 -1
- package/lib/typescript/src/utils/ui.d.ts +0 -13
- package/lib/typescript/src/utils/ui.d.ts.map +0 -1
- package/src/hooks/useVideoAttachment.ts +0 -334
- package/src/utils/ui.ts +0 -19
|
@@ -66,11 +66,17 @@ export function useChatCompose() {
|
|
|
66
66
|
(v) => v.uploadedUrl
|
|
67
67
|
) as unknown as VideoWithUploadStatus[];
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// Thumbnails are generated + uploaded at pick time (useImageAttachment).
|
|
70
|
+
// Only fall back to generating here for videos that don't already have one
|
|
71
|
+
// (e.g. thumbnail generation failed or hadn't finished before send).
|
|
72
|
+
const videosMissingThumbnail = uploadedVideos.filter(
|
|
73
|
+
(v) => !v.thumbnailUrl
|
|
74
|
+
);
|
|
70
75
|
let videoThumbnails: Record<string, { url: string; size: number }> = {};
|
|
71
|
-
if (
|
|
72
|
-
videoThumbnails =
|
|
73
|
-
|
|
76
|
+
if (videosMissingThumbnail.length > 0) {
|
|
77
|
+
videoThumbnails = await generateAndUploadVideoThumbnails(
|
|
78
|
+
videosMissingThumbnail
|
|
79
|
+
);
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
// Send images + videos
|
|
@@ -98,51 +104,54 @@ export function useChatCompose() {
|
|
|
98
104
|
error: vid.error,
|
|
99
105
|
isUploading: vid.isUploading,
|
|
100
106
|
uploadedUrl: vid.uploadedUrl,
|
|
101
|
-
thumbnailUrl: thumb?.url,
|
|
102
|
-
thumbnailSize: thumb?.size,
|
|
107
|
+
thumbnailUrl: vid.thumbnailUrl ?? thumb?.url,
|
|
108
|
+
thumbnailSize: vid.thumbnailSize ?? thumb?.size,
|
|
103
109
|
};
|
|
104
110
|
}
|
|
105
111
|
),
|
|
106
112
|
];
|
|
107
113
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
// Clear composer immediately — message is already in chat list with Sending status.
|
|
115
|
+
// User retries from the bubble, not the composer.
|
|
116
|
+
ImagePicker.clean().catch(() => {});
|
|
117
|
+
clearAllImages();
|
|
118
|
+
// Awaited so files (lower priority per attachment-priority config)
|
|
119
|
+
// only start sending after images/videos are done.
|
|
120
|
+
await sendAttachments(attachmentItems, undefined);
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
// Send each file as its own message
|
|
123
|
+
// Send each file as its own message (never merged), in priority order
|
|
124
|
+
// after images/videos, one at a time to preserve pick order.
|
|
117
125
|
const uploadedFiles = selectedFiles.filter(
|
|
118
126
|
(f) => f.uploadedUrl && !f.error
|
|
119
127
|
);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
if (uploadedFiles.length > 0) {
|
|
129
|
+
clearAllFiles();
|
|
130
|
+
for (const file of uploadedFiles) {
|
|
131
|
+
await sendAttachments(
|
|
132
|
+
[
|
|
133
|
+
{
|
|
134
|
+
type: 'file' as const,
|
|
135
|
+
file: { path: file.uri, filename: file.name, size: file.size },
|
|
136
|
+
uploadProgress: 100,
|
|
137
|
+
error: null,
|
|
138
|
+
isUploading: false,
|
|
139
|
+
uploadedUrl: file.uploadedUrl,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
undefined
|
|
143
|
+
);
|
|
144
|
+
}
|
|
134
145
|
}
|
|
135
|
-
if (uploadedFiles.length > 0) clearAllFiles();
|
|
136
146
|
}
|
|
137
147
|
|
|
138
148
|
// --- Text / link preview ---
|
|
139
149
|
if (text.trim() || urlMeta) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
urlMetadata: urlMeta,
|
|
144
|
-
});
|
|
150
|
+
const plainText = text.trim();
|
|
151
|
+
// Clear composer immediately — message is already in chat list with Sending status.
|
|
152
|
+
// User retries from the bubble, not the composer.
|
|
145
153
|
setText('');
|
|
154
|
+
await sendMessage({ files: [], plainText, urlMetadata: urlMeta });
|
|
146
155
|
}
|
|
147
156
|
}, [
|
|
148
157
|
imageAttachment,
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
setMessages(mergeMessages([], initialMessages), !history.isEnd);
|
|
65
|
+
if (userIdResult.status === 'fulfilled') {
|
|
66
|
+
setCurrentUserId(userIdResult.value);
|
|
67
|
+
}
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
|
|
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/
|
|
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/
|
|
5
|
+
import { UIUtils } from '../utils/UIUtils';
|
|
6
6
|
import { trans } from '../translation';
|
|
7
7
|
|
|
8
8
|
export type DownloadStatus = 'idle' | 'downloading' | 'done' | 'error';
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
Video as PickedVideo,
|
|
5
5
|
} from 'react-native-image-crop-picker';
|
|
6
6
|
import DeviceUtils from '../utils/device';
|
|
7
|
-
import UIUtils from '../utils/
|
|
7
|
+
import { UIUtils } from '../utils/UIUtils';
|
|
8
8
|
import { trans } from '../translation';
|
|
9
9
|
import { getMimeType, isVideoFromPicker } from '../utils/imageUtils';
|
|
10
10
|
import { UPLOAD_CONCURRENCY, UPLOAD_LIMITS } from '../config/uploadLimits';
|
|
@@ -86,9 +86,14 @@ export const useImageAttachment = (): UseImageAttachmentReturn => {
|
|
|
86
86
|
} catch (error: unknown) {
|
|
87
87
|
const msg =
|
|
88
88
|
error instanceof Error
|
|
89
|
-
? error
|
|
89
|
+
? error?.message
|
|
90
90
|
: trans('chat:image_upload_failed');
|
|
91
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',
|
|
96
|
+
});
|
|
92
97
|
}
|
|
93
98
|
},
|
|
94
99
|
[updateImageStatus]
|
|
@@ -171,11 +176,14 @@ export const useImageAttachment = (): UseImageAttachmentReturn => {
|
|
|
171
176
|
|
|
172
177
|
const oversizedItems = images.filter((img) => !isWithinLimit(img));
|
|
173
178
|
if (oversizedItems.length > 0) {
|
|
179
|
+
const isVideo = oversizedItems.some(isVideoFromPicker);
|
|
180
|
+
const maxSize = Math.floor(
|
|
181
|
+
(isVideo ? UPLOAD_LIMITS.video : UPLOAD_LIMITS.image) / 1024 / 1024
|
|
182
|
+
);
|
|
174
183
|
UIUtils.toast.open({
|
|
175
184
|
title: trans(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
: 'chat:image_too_large'
|
|
185
|
+
isVideo ? 'chat:video_too_large' : 'chat:image_too_large',
|
|
186
|
+
{ maxSize: String(maxSize) }
|
|
179
187
|
),
|
|
180
188
|
theme: 'danger',
|
|
181
189
|
});
|
package/src/index.tsx
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import type { ChatDetailProps } from './types';
|
|
24
24
|
import { ChatDetailProvider } from '../../context/ChatDetailContext';
|
|
25
25
|
import { normalizeUrl } from '../../utils/url';
|
|
26
|
+
// import { ChatMessageAPI } from '../../services/message';
|
|
26
27
|
|
|
27
28
|
const ChatDetail = memo(
|
|
28
29
|
({
|
|
@@ -73,6 +74,17 @@ const ChatDetail = memo(
|
|
|
73
74
|
useConversationStore.getState().setCurrentConversation(conversationId);
|
|
74
75
|
}, [conversationId]);
|
|
75
76
|
|
|
77
|
+
// useEffect(() => {
|
|
78
|
+
// if (!conversationId) return;
|
|
79
|
+
// ChatMessageAPI.searchLocalMessages(conversationId, { count: 20 })
|
|
80
|
+
// .then((result) => {
|
|
81
|
+
// console.log('[TEST searchLocalMessages] success:', result);
|
|
82
|
+
// })
|
|
83
|
+
// .catch((err) => {
|
|
84
|
+
// console.log('[TEST searchLocalMessages] error:', err);
|
|
85
|
+
// });
|
|
86
|
+
// }, [conversationId]);
|
|
87
|
+
|
|
76
88
|
const resolvedTitle = title ?? getConversationTitle(conversation);
|
|
77
89
|
const resolvedAvatarUri =
|
|
78
90
|
avatarUri ?? getConversationAvatarUri(conversation);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { memo, useCallback, useMemo, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
3
|
import Animated, {
|
|
4
4
|
useSharedValue,
|
|
5
5
|
useAnimatedStyle,
|
|
@@ -18,6 +18,7 @@ import { getFlowInputButtonsNode } from '../../utils/flows';
|
|
|
18
18
|
import { InputButtonFlow } from '../../components/flows/inputButtons';
|
|
19
19
|
import { useResetFlow, useRunFlow } from '../../hooks/flows/useFlow';
|
|
20
20
|
import type { IRunConversationFlowPayload } from '../../types/flows';
|
|
21
|
+
import { useSendMessage } from '../../hooks/message/useSendMessage';
|
|
21
22
|
|
|
22
23
|
export const ChatListLegend = memo(
|
|
23
24
|
({
|
|
@@ -25,7 +26,6 @@ export const ChatListLegend = memo(
|
|
|
25
26
|
currentUserId = '',
|
|
26
27
|
customMessageItemFactory,
|
|
27
28
|
onLoadEarlier,
|
|
28
|
-
isLoading,
|
|
29
29
|
isLoadingEarlier,
|
|
30
30
|
hasMoreEarlier,
|
|
31
31
|
onLoadNewer,
|
|
@@ -41,6 +41,7 @@ export const ChatListLegend = memo(
|
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
const { isBotConversation, conversationFlow } = useChatDetailContext();
|
|
44
|
+
const { retryMessage } = useSendMessage();
|
|
44
45
|
|
|
45
46
|
const lastMessageId = useMemo(() => {
|
|
46
47
|
const last = messages[messages.length - 1];
|
|
@@ -143,6 +144,7 @@ export const ChatListLegend = memo(
|
|
|
143
144
|
createdAtTime={createdAt}
|
|
144
145
|
onMediaPress={onMediaPress}
|
|
145
146
|
onPressUrl={onPressUrl}
|
|
147
|
+
onRetry={retryMessage}
|
|
146
148
|
customMessageItemFactory={customMessageItemFactory}
|
|
147
149
|
/>
|
|
148
150
|
{!!flowInputButtonsNode && (
|
|
@@ -179,6 +181,7 @@ export const ChatListLegend = memo(
|
|
|
179
181
|
onRunFlow,
|
|
180
182
|
conversationFlow?.flowCurrentStep,
|
|
181
183
|
handleItemLayout,
|
|
184
|
+
retryMessage,
|
|
182
185
|
]
|
|
183
186
|
);
|
|
184
187
|
|
|
@@ -327,16 +330,16 @@ export const ChatListLegend = memo(
|
|
|
327
330
|
};
|
|
328
331
|
}, []);
|
|
329
332
|
|
|
330
|
-
if (isLoading) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
333
|
+
// if (isLoading) {
|
|
334
|
+
// return (
|
|
335
|
+
// <KContainer.View flex center background={KColors.white}>
|
|
336
|
+
// <ActivityIndicator
|
|
337
|
+
// color={KColors.palette.primary.w400}
|
|
338
|
+
// size="small"
|
|
339
|
+
// />
|
|
340
|
+
// </KContainer.View>
|
|
341
|
+
// );
|
|
342
|
+
// }
|
|
340
343
|
|
|
341
344
|
return (
|
|
342
345
|
<KContainer.View flex background={KColors.white} style={styles.container}>
|
|
@@ -346,7 +349,7 @@ export const ChatListLegend = memo(
|
|
|
346
349
|
renderItem={renderItem}
|
|
347
350
|
estimatedItemSize={avgItemHeightRef.current}
|
|
348
351
|
keyExtractor={(item) => String(item.clientMsgID || item.serverMsgID)}
|
|
349
|
-
recycleItems={
|
|
352
|
+
recycleItems={true}
|
|
350
353
|
extraData={`${conversationFlow?.flowCurrentStep?.id ?? ''}-${lastMessageId ?? ''}`}
|
|
351
354
|
alignItemsAtEnd
|
|
352
355
|
maintainVisibleContentPosition={maintainVisiblePositionConfig}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
2
|
import type { ComponentType, ReactNode } from 'react';
|
|
3
|
+
import { KContainer } from '@droppii/libs';
|
|
3
4
|
import { MessageType } from '@droppii/openim-rn-client-sdk';
|
|
5
|
+
import type { MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
4
6
|
import type { DMessageItem } from '../../../types/chat';
|
|
5
7
|
import type { MediaItem } from '../../../hooks/useMediaViewer';
|
|
6
8
|
import { TextMessage } from '../../../components/messages/textMessage';
|
|
@@ -9,6 +11,7 @@ import { VideoMessage } from '../../../components/messages/videoMessage';
|
|
|
9
11
|
import { FileMessage } from '../../../components/messages/fileMessage';
|
|
10
12
|
import { LinkMessage } from '../../../components/messages/linkMessage';
|
|
11
13
|
import { MergedMessage } from '../../../components/messages/mergedMessage';
|
|
14
|
+
import { MessageStatusIndicator } from '../../../components/messages/MessageStatusIndicator';
|
|
12
15
|
import type { BaseLegendMessageProps } from '../../../components/messages/types';
|
|
13
16
|
|
|
14
17
|
type MessageProps = BaseLegendMessageProps & {
|
|
@@ -21,6 +24,7 @@ interface LegendChatMessageProps {
|
|
|
21
24
|
createdAtTime: number;
|
|
22
25
|
onMediaPress?: (items: MediaItem[], index?: number) => void;
|
|
23
26
|
onPressUrl?: (url: string) => void;
|
|
27
|
+
onRetry?: (message: MessageItem) => void;
|
|
24
28
|
customMessageItemFactory?: (m: DMessageItem) => ReactNode;
|
|
25
29
|
}
|
|
26
30
|
|
|
@@ -46,10 +50,6 @@ const MEDIA_TYPES: number[] = [
|
|
|
46
50
|
MERGED_MESSAGE_TYPE,
|
|
47
51
|
];
|
|
48
52
|
|
|
49
|
-
/**
|
|
50
|
-
* Dispatcher component - renders the appropriate message component
|
|
51
|
-
* based on message type. Extensible for new message types.
|
|
52
|
-
*/
|
|
53
53
|
export const LegendChatMessage = memo(
|
|
54
54
|
({
|
|
55
55
|
message,
|
|
@@ -57,9 +57,9 @@ export const LegendChatMessage = memo(
|
|
|
57
57
|
createdAtTime,
|
|
58
58
|
onMediaPress,
|
|
59
59
|
onPressUrl,
|
|
60
|
+
onRetry,
|
|
60
61
|
customMessageItemFactory,
|
|
61
62
|
}: LegendChatMessageProps) => {
|
|
62
|
-
console.log('customMessageItemFactory', customMessageItemFactory);
|
|
63
63
|
const customNode = customMessageItemFactory?.(message);
|
|
64
64
|
if (customNode) {
|
|
65
65
|
return customNode;
|
|
@@ -74,13 +74,20 @@ export const LegendChatMessage = memo(
|
|
|
74
74
|
onPressUrl && message?.contentType === MessageType.UrlTextMessage;
|
|
75
75
|
|
|
76
76
|
return (
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
<KContainer.View row alignItems>
|
|
78
|
+
<MessageStatusIndicator
|
|
79
|
+
message={message}
|
|
80
|
+
isOutgoing={isOutgoing}
|
|
81
|
+
onRetry={onRetry}
|
|
82
|
+
/>
|
|
83
|
+
<MessageComponent
|
|
84
|
+
message={message}
|
|
85
|
+
isOutgoing={isOutgoing}
|
|
86
|
+
createdAtTime={createdAtTime}
|
|
87
|
+
onMediaPress={hasMediaPress ? onMediaPress : undefined}
|
|
88
|
+
onPressUrl={hasUrlPress ? onPressUrl : undefined}
|
|
89
|
+
/>
|
|
90
|
+
</KContainer.View>
|
|
84
91
|
);
|
|
85
92
|
}
|
|
86
93
|
);
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import OpenIMSDK, {
|
|
1
|
+
import OpenIMSDK, {
|
|
2
|
+
MessageStatus,
|
|
3
|
+
type MessageItem,
|
|
4
|
+
} from '@droppii/openim-rn-client-sdk';
|
|
5
|
+
|
|
2
6
|
import type {
|
|
3
7
|
Attachment,
|
|
4
8
|
AttachmentSendOptions,
|
|
@@ -30,7 +34,6 @@ export namespace AttachmentOrchestratorService {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
// 1. Validate all attachments
|
|
33
|
-
console.log('[AttachmentOrchestrator] Validating prepared attachments...');
|
|
34
37
|
for (const att of attachments) {
|
|
35
38
|
try {
|
|
36
39
|
const handler = getAttachmentHandler(att.type);
|
|
@@ -267,32 +270,53 @@ export namespace AttachmentOrchestratorService {
|
|
|
267
270
|
}
|
|
268
271
|
);
|
|
269
272
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
console.log(
|
|
273
|
-
'[AttachmentOrchestrator] Sending message',
|
|
274
|
-
i + 1,
|
|
275
|
-
'of',
|
|
276
|
-
allMessages.length
|
|
277
|
-
);
|
|
273
|
+
const recvID = currentConversation.userID || '';
|
|
274
|
+
const groupID = currentConversation.groupID || '';
|
|
278
275
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
276
|
+
// Push all messages as "Sending" immediately so the user sees every
|
|
277
|
+
// bubble right away, instead of waiting for each network call in turn.
|
|
278
|
+
for (const m of allMessages) {
|
|
279
|
+
useMessageStore.getState().pushNewMessage({
|
|
280
|
+
...(m as MessageItem),
|
|
281
|
+
status: MessageStatus.Sending,
|
|
283
282
|
});
|
|
283
|
+
}
|
|
284
284
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
285
|
+
const sendResults = await Promise.allSettled(
|
|
286
|
+
allMessages.map(async (m) => {
|
|
287
|
+
const message = m as MessageItem;
|
|
288
|
+
const result = await OpenIMSDK.sendMessageNotOss({
|
|
289
|
+
recvID,
|
|
290
|
+
groupID,
|
|
291
|
+
message,
|
|
292
|
+
});
|
|
290
293
|
|
|
291
|
-
|
|
292
|
-
|
|
294
|
+
if (result) {
|
|
295
|
+
useMessageStore.getState().deleteOneMessage(message.clientMsgID);
|
|
296
|
+
useMessageStore.getState().pushNewMessage(result);
|
|
297
|
+
}
|
|
298
|
+
return result?.clientMsgID || result?.serverMsgID || '';
|
|
299
|
+
})
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
let firstSendError: unknown;
|
|
303
|
+
for (let i = 0; i < sendResults.length; i++) {
|
|
304
|
+
const settled = sendResults[i]!;
|
|
305
|
+
if (settled.status === 'fulfilled') {
|
|
306
|
+
lastMsgId = settled.value || lastMsgId;
|
|
307
|
+
} else {
|
|
308
|
+
firstSendError = firstSendError ?? settled.reason;
|
|
309
|
+
useMessageStore.getState().updateOneMessage({
|
|
310
|
+
...(allMessages[i] as MessageItem),
|
|
311
|
+
status: MessageStatus.Failed,
|
|
312
|
+
});
|
|
293
313
|
}
|
|
294
314
|
}
|
|
295
315
|
|
|
316
|
+
if (!lastMsgId && firstSendError) {
|
|
317
|
+
throw firstSendError;
|
|
318
|
+
}
|
|
319
|
+
|
|
296
320
|
return lastMsgId;
|
|
297
321
|
}
|
|
298
322
|
}
|
package/src/services/message.ts
CHANGED
|
@@ -20,16 +20,15 @@ export namespace ChatMessageAPI {
|
|
|
20
20
|
count?: number;
|
|
21
21
|
startClientMsgID?: string;
|
|
22
22
|
lastMinSeq?: number;
|
|
23
|
+
applicationType?: string;
|
|
23
24
|
}
|
|
24
25
|
): Promise<HistoryMessageResult> {
|
|
25
|
-
return OpenIMSDK.
|
|
26
|
+
return OpenIMSDK.getAdvancedHistoryMessageListApp({
|
|
26
27
|
conversationID,
|
|
27
28
|
count: options?.count ?? DEFAULT_PAGE_SIZE,
|
|
28
29
|
startClientMsgID: options?.startClientMsgID ?? '',
|
|
29
30
|
viewType: ViewType.History,
|
|
30
|
-
|
|
31
|
-
// ? { lastMinSeq: options.lastMinSeq ?? 0 }
|
|
32
|
-
// : {}),
|
|
31
|
+
applicationType: options?.applicationType ?? '',
|
|
33
32
|
}) as Promise<HistoryMessageResult>;
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -50,6 +49,24 @@ export namespace ChatMessageAPI {
|
|
|
50
49
|
return sentMessage;
|
|
51
50
|
}
|
|
52
51
|
|
|
52
|
+
export async function searchLocalMessages(
|
|
53
|
+
conversationID: string,
|
|
54
|
+
options?: {
|
|
55
|
+
keywordList?: string[];
|
|
56
|
+
messageTypeList?: number[];
|
|
57
|
+
pageIndex?: number;
|
|
58
|
+
count?: number;
|
|
59
|
+
}
|
|
60
|
+
) {
|
|
61
|
+
return OpenIMSDK.searchLocalMessages({
|
|
62
|
+
conversationID,
|
|
63
|
+
keywordList: options?.keywordList ?? ['a'],
|
|
64
|
+
messageTypeList: options?.messageTypeList,
|
|
65
|
+
pageIndex: options?.pageIndex ?? 1,
|
|
66
|
+
count: options?.count ?? 20,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
53
70
|
export async function markConversationAsRead(conversationID: string) {
|
|
54
71
|
return OpenIMSDK.markConversationMessageAsRead(conversationID);
|
|
55
72
|
}
|
|
@@ -11,10 +11,13 @@ const i18nKey: Record<string, string> = {
|
|
|
11
11
|
'chat:file_download_failed': 'Tải tệp thất bại',
|
|
12
12
|
'chat:file_open_failed': 'Không thể mở tệp này',
|
|
13
13
|
'chat:image_preview': 'Xem trước ảnh',
|
|
14
|
-
'chat:image_too_large':
|
|
15
|
-
|
|
14
|
+
'chat:image_too_large':
|
|
15
|
+
'Hình ảnh tải lên không hợp lệ. Kích thước hình ảnh không được vượt quá {{maxSize}}MB',
|
|
16
|
+
'chat:video_too_large':
|
|
17
|
+
'Video tải lên không hợp lệ. Kích thước video không được vượt quá {{maxSize}}MB',
|
|
16
18
|
'chat:image_sent': 'Đã gửi ảnh',
|
|
17
|
-
'chat:image_upload_failed': '
|
|
19
|
+
'chat:image_upload_failed': 'Có lỗi xảy ra vui lòng thử lại',
|
|
20
|
+
'chat:generic_error': 'Có lỗi xảy ra vui lòng thử lại',
|
|
18
21
|
'chat:failed_to_send_message': 'Không thể gửi tin nhắn. Thử lại?',
|
|
19
22
|
'chat:filename': 'Tên tệp',
|
|
20
23
|
'chat:file_size': 'Kích thước',
|
package/src/types/chat.ts
CHANGED
|
@@ -20,6 +20,12 @@ type LogGA = (
|
|
|
20
20
|
provider?: `${keyof typeof EventProvider}`
|
|
21
21
|
) => void;
|
|
22
22
|
|
|
23
|
+
export interface ChatUIAdapter {
|
|
24
|
+
toast?: { open: (params: any) => void };
|
|
25
|
+
alert?: { open: (params: any) => void };
|
|
26
|
+
popup?: { open: (params: any) => void };
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
export interface ChatContextType {
|
|
24
30
|
logGA?: LogGA;
|
|
25
31
|
applicationType: DChatApplicationType;
|