@droppii-org/chat-mobile 0.2.17 → 0.2.19
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/icon_excel.png +0 -0
- package/lib/module/assets/images/icon_img.png +0 -0
- package/lib/module/assets/images/icon_pdf.png +0 -0
- package/lib/module/assets/images/icon_txt.png +0 -0
- package/lib/module/assets/images/icon_word.png +0 -0
- package/lib/module/assets/images/index.js +5 -0
- package/lib/module/assets/images/index.js.map +1 -1
- package/lib/module/components/AttachmentPreview/FilePreview.js +107 -0
- package/lib/module/components/AttachmentPreview/FilePreview.js.map +1 -0
- package/lib/module/components/AttachmentPreview/MediaPreview.js +212 -0
- package/lib/module/components/AttachmentPreview/MediaPreview.js.map +1 -0
- package/lib/module/components/AttachmentPreview/index.js +49 -0
- package/lib/module/components/AttachmentPreview/index.js.map +1 -0
- package/lib/module/components/ThreadCard/AvatarSection.js.map +1 -1
- package/lib/module/components/ThreadCard/ThreadCard.js +17 -9
- package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
- package/lib/module/components/flows/inputButtons/index.js +7 -3
- package/lib/module/components/flows/inputButtons/index.js.map +1 -1
- package/lib/module/components/messages/fileMessage/index.js +122 -12
- package/lib/module/components/messages/fileMessage/index.js.map +1 -1
- package/lib/module/context/ChatContext.js +27 -4
- package/lib/module/context/ChatContext.js.map +1 -1
- package/lib/module/context/ChatDetailContext.js.map +1 -1
- package/lib/module/core/useChatListener.js +2 -4
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/core/useUserListener.js +0 -7
- package/lib/module/core/useUserListener.js.map +1 -1
- package/lib/module/hooks/flows/useFlow.js +14 -3
- package/lib/module/hooks/flows/useFlow.js.map +1 -1
- package/lib/module/hooks/message/useSendMessage.js +1 -1
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/useConversationList.js +3 -27
- package/lib/module/hooks/useConversationList.js.map +1 -1
- package/lib/module/hooks/useFileAttachment.js +116 -0
- package/lib/module/hooks/useFileAttachment.js.map +1 -0
- package/lib/module/hooks/useFileDownload.js +175 -0
- package/lib/module/hooks/useFileDownload.js.map +1 -0
- package/lib/module/hooks/useLinkPreview/useLinkPreview.js +1 -1
- package/lib/module/hooks/useLinkPreview/useLinkPreview.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatComposer.js +44 -36
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +1 -6
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetailHeader.js +6 -6
- package/lib/module/screens/chat-detail/ChatDetailHeader.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatLinkPreview.js +3 -1
- package/lib/module/screens/chat-detail/ChatLinkPreview.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +2 -2
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/conversationHeader.utils.js +9 -0
- package/lib/module/screens/chat-detail/conversationHeader.utils.js.map +1 -1
- package/lib/module/screens/chat-detail/hooks/useAttachmentMapping.js +51 -0
- package/lib/module/screens/chat-detail/hooks/useAttachmentMapping.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useAttachmentSendHandler.js +39 -13
- package/lib/module/screens/chat-detail/hooks/useAttachmentSendHandler.js.map +1 -1
- package/lib/module/screens/inbox/Inbox.js +5 -1
- package/lib/module/screens/inbox/Inbox.js.map +1 -1
- package/lib/module/services/apis.js +0 -20
- package/lib/module/services/apis.js.map +1 -1
- package/lib/module/services/attachmentHandlers/fileAttachmentHandler.js +6 -6
- package/lib/module/services/attachmentHandlers/fileAttachmentHandler.js.map +1 -1
- package/lib/module/services/attachmentHandlers/index.js +1 -1
- package/lib/module/services/attachmentHandlers/index.js.map +1 -1
- package/lib/module/store/conversation.js +1 -38
- package/lib/module/store/conversation.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +9 -0
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/types/auth.js.map +1 -1
- package/lib/module/types/chat.js.map +1 -1
- package/lib/module/utils/conversation.js +1 -28
- package/lib/module/utils/conversation.js.map +1 -1
- package/lib/module/utils/device.js +32 -0
- package/lib/module/utils/device.js.map +1 -1
- package/lib/module/utils/imageUtils.js +44 -28
- package/lib/module/utils/imageUtils.js.map +1 -1
- package/lib/module/utils/message.js.map +1 -1
- package/lib/module/utils/messageUtils.js +5 -3
- package/lib/module/utils/messageUtils.js.map +1 -1
- package/lib/typescript/src/assets/images/index.d.ts +5 -0
- package/lib/typescript/src/assets/images/index.d.ts.map +1 -1
- package/lib/typescript/src/components/AttachmentPreview/FilePreview.d.ts +6 -0
- package/lib/typescript/src/components/AttachmentPreview/FilePreview.d.ts.map +1 -0
- package/lib/typescript/src/components/AttachmentPreview/MediaPreview.d.ts +9 -0
- package/lib/typescript/src/components/AttachmentPreview/MediaPreview.d.ts.map +1 -0
- package/lib/typescript/src/components/AttachmentPreview/index.d.ts +9 -0
- package/lib/typescript/src/components/AttachmentPreview/index.d.ts.map +1 -0
- package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +2 -2
- package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
- package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/fileMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/context/ChatContext.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/flows/useFlow.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useConversationList.d.ts +2 -2
- package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useFileAttachment.d.ts +20 -0
- package/lib/typescript/src/hooks/useFileAttachment.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useFileDownload.d.ts +15 -0
- package/lib/typescript/src/hooks/useFileDownload.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useImageAttachment.d.ts +1 -2
- package/lib/typescript/src/hooks/useImageAttachment.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useLinkPreview/useLinkPreview.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatLinkPreview.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts +7 -5
- package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useAttachmentMapping.d.ts +10 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useAttachmentMapping.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useAttachmentSendHandler.d.ts +4 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useAttachmentSendHandler.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/types.d.ts +6 -9
- package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
- package/lib/typescript/src/screens/inbox/Inbox.d.ts.map +1 -1
- package/lib/typescript/src/services/apis.d.ts +0 -4
- package/lib/typescript/src/services/apis.d.ts.map +1 -1
- package/lib/typescript/src/services/attachmentHandlers/fileAttachmentHandler.d.ts +2 -2
- package/lib/typescript/src/services/attachmentHandlers/fileAttachmentHandler.d.ts.map +1 -1
- package/lib/typescript/src/services/attachmentHandlers/index.d.ts +1 -1
- package/lib/typescript/src/services/attachmentHandlers/index.d.ts.map +1 -1
- package/lib/typescript/src/store/conversation.d.ts +5 -8
- package/lib/typescript/src/store/conversation.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/attachment.d.ts +22 -1
- package/lib/typescript/src/types/attachment.d.ts.map +1 -1
- package/lib/typescript/src/types/auth.d.ts +1 -0
- package/lib/typescript/src/types/auth.d.ts.map +1 -1
- package/lib/typescript/src/types/chat.d.ts +15 -13
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/utils/conversation.d.ts +2 -5
- package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
- package/lib/typescript/src/utils/device.d.ts +7 -0
- package/lib/typescript/src/utils/device.d.ts.map +1 -1
- package/lib/typescript/src/utils/imageUtils.d.ts +1 -0
- package/lib/typescript/src/utils/imageUtils.d.ts.map +1 -1
- package/lib/typescript/src/utils/message.d.ts +3 -3
- package/lib/typescript/src/utils/message.d.ts.map +1 -1
- package/lib/typescript/src/utils/messageUtils.d.ts +2 -1
- package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/assets/images/icon_excel.png +0 -0
- package/src/assets/images/icon_img.png +0 -0
- package/src/assets/images/icon_pdf.png +0 -0
- package/src/assets/images/icon_txt.png +0 -0
- package/src/assets/images/icon_word.png +0 -0
- package/src/assets/images/index.ts +5 -0
- package/src/components/AttachmentPreview/FilePreview.tsx +137 -0
- package/src/components/AttachmentPreview/MediaPreview.tsx +238 -0
- package/src/components/AttachmentPreview/index.tsx +68 -0
- package/src/components/ThreadCard/AvatarSection.tsx +2 -3
- package/src/components/ThreadCard/ThreadCard.tsx +22 -15
- package/src/components/flows/inputButtons/index.tsx +4 -1
- package/src/components/messages/fileMessage/index.tsx +156 -13
- package/src/context/ChatContext.tsx +35 -3
- package/src/context/ChatDetailContext.tsx +1 -1
- package/src/core/useChatListener.ts +2 -4
- package/src/core/useUserListener.ts +0 -8
- package/src/hooks/flows/useFlow.ts +12 -3
- package/src/hooks/message/useSendMessage.ts +1 -1
- package/src/hooks/useConversationList.ts +6 -40
- package/src/hooks/useFileAttachment.ts +163 -0
- package/src/hooks/useFileDownload.ts +200 -0
- package/src/hooks/useImageAttachment.ts +1 -1
- package/src/hooks/useLinkPreview/useLinkPreview.ts +6 -1
- package/src/screens/chat-detail/ChatComposer.tsx +61 -30
- package/src/screens/chat-detail/ChatDetail.tsx +1 -7
- package/src/screens/chat-detail/ChatDetailHeader.tsx +6 -7
- package/src/screens/chat-detail/ChatLinkPreview.tsx +16 -12
- package/src/screens/chat-detail/ChatListLegend.tsx +2 -6
- package/src/screens/chat-detail/conversationHeader.utils.ts +16 -6
- package/src/screens/chat-detail/hooks/useAttachmentMapping.ts +58 -0
- package/src/screens/chat-detail/hooks/useAttachmentSendHandler.ts +50 -11
- package/src/screens/chat-detail/types.ts +6 -13
- package/src/screens/inbox/Inbox.tsx +5 -1
- package/src/services/apis.ts +0 -38
- package/src/services/attachmentHandlers/fileAttachmentHandler.ts +8 -7
- package/src/services/attachmentHandlers/index.ts +4 -1
- package/src/store/conversation.ts +6 -55
- package/src/translation/resources/i18n.ts +9 -0
- package/src/types/attachment.ts +23 -1
- package/src/types/auth.ts +1 -0
- package/src/types/chat.ts +16 -20
- package/src/utils/conversation.ts +2 -40
- package/src/utils/device.ts +58 -0
- package/src/utils/imageUtils.ts +50 -28
- package/src/utils/message.ts +3 -2
- package/src/utils/messageUtils.ts +11 -3
- package/lib/module/components/AttachmentPreview.js +0 -252
- package/lib/module/components/AttachmentPreview.js.map +0 -1
- package/lib/typescript/src/components/AttachmentPreview.d.ts +0 -28
- package/lib/typescript/src/components/AttachmentPreview.d.ts.map +0 -1
- package/src/components/AttachmentPreview.tsx +0 -310
|
@@ -5,6 +5,7 @@ import VideoThumbnailUtil from '../../../utils/videoThumbnail';
|
|
|
5
5
|
import { ImageUploadAPI } from '../../../services/imageUpload';
|
|
6
6
|
import UIUtils from '../../../utils/ui';
|
|
7
7
|
import type { DMessageItem } from '../../../types/chat';
|
|
8
|
+
import type { FileWithUploadStatus } from '../../../hooks/useFileAttachment';
|
|
8
9
|
|
|
9
10
|
interface AttachmentItem {
|
|
10
11
|
image: any;
|
|
@@ -16,12 +17,14 @@ interface AttachmentItem {
|
|
|
16
17
|
|
|
17
18
|
interface UseAttachmentSendHandlerProps {
|
|
18
19
|
selectedImages: AttachmentItem[];
|
|
20
|
+
selectedFiles: FileWithUploadStatus[];
|
|
19
21
|
sendAttachments: (
|
|
20
22
|
items: any[],
|
|
21
23
|
_: any,
|
|
22
24
|
onMessageSent?: (message: DMessageItem) => void
|
|
23
25
|
) => Promise<boolean>;
|
|
24
26
|
clearAllImages: () => void;
|
|
27
|
+
clearAllFiles?: () => void;
|
|
25
28
|
onSend?: () => void;
|
|
26
29
|
onMessageSent?: (message: DMessageItem) => void;
|
|
27
30
|
textMessage?: string;
|
|
@@ -31,8 +34,10 @@ interface UseAttachmentSendHandlerProps {
|
|
|
31
34
|
|
|
32
35
|
export const useAttachmentSendHandler = ({
|
|
33
36
|
selectedImages,
|
|
37
|
+
selectedFiles,
|
|
34
38
|
sendAttachments,
|
|
35
39
|
clearAllImages,
|
|
40
|
+
clearAllFiles,
|
|
36
41
|
onSend,
|
|
37
42
|
onMessageSent,
|
|
38
43
|
textMessage,
|
|
@@ -316,26 +321,60 @@ export const useAttachmentSendHandler = ({
|
|
|
316
321
|
if (success) {
|
|
317
322
|
clearAllImages();
|
|
318
323
|
console.log('[ChatComposer] Images cleared after successful send');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
319
326
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
// Send each file as its own individual message
|
|
328
|
+
const uploadedFiles = selectedFiles.filter(
|
|
329
|
+
(f) => f.uploadedUrl && !f.error
|
|
330
|
+
);
|
|
331
|
+
if (uploadedFiles.length > 0) {
|
|
332
|
+
console.log('[ChatComposer] Sending files individually:', {
|
|
333
|
+
count: uploadedFiles.length,
|
|
334
|
+
});
|
|
335
|
+
for (const file of uploadedFiles) {
|
|
336
|
+
await sendAttachments(
|
|
337
|
+
[
|
|
338
|
+
{
|
|
339
|
+
type: 'file' as const,
|
|
340
|
+
file: { path: file.uri, filename: file.name, size: file.size },
|
|
341
|
+
uploadProgress: 100,
|
|
342
|
+
error: null,
|
|
343
|
+
isUploading: false,
|
|
344
|
+
uploadedUrl: file.uploadedUrl,
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
undefined,
|
|
348
|
+
onMessageSent
|
|
349
|
+
);
|
|
329
350
|
}
|
|
351
|
+
clearAllFiles?.();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Send text last
|
|
355
|
+
if (textMessage && onSendText) {
|
|
356
|
+
console.log('[ChatComposer] Sending text message after attachments', {
|
|
357
|
+
hasPreview: !!urlMetadata,
|
|
358
|
+
previewUrl: urlMetadata?.url,
|
|
359
|
+
});
|
|
360
|
+
onSendText(textMessage, urlMetadata);
|
|
330
361
|
return;
|
|
331
362
|
}
|
|
332
363
|
|
|
333
|
-
|
|
334
|
-
|
|
364
|
+
const hasAnySent =
|
|
365
|
+
uploadedImages.length > 0 ||
|
|
366
|
+
allUploadedVideos.length > 0 ||
|
|
367
|
+
uploadedFiles.length > 0;
|
|
368
|
+
if (!hasAnySent) {
|
|
369
|
+
onSend?.();
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
335
372
|
}, [
|
|
336
373
|
selectedImages,
|
|
374
|
+
selectedFiles,
|
|
337
375
|
sendAttachments,
|
|
338
376
|
clearAllImages,
|
|
377
|
+
clearAllFiles,
|
|
339
378
|
onSend,
|
|
340
379
|
onMessageSent,
|
|
341
380
|
textMessage,
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
|
|
3
3
|
import type { MediaItem } from '../../hooks/useMediaViewer';
|
|
4
|
-
import type {
|
|
5
|
-
DChatApplicationType,
|
|
6
|
-
DConversationItem,
|
|
7
|
-
DMessageItem,
|
|
8
|
-
} from '../../types/chat';
|
|
4
|
+
import type { DMessageItem } from '../../types/chat';
|
|
9
5
|
import type { IUrlMetadata } from '../../types/common';
|
|
10
|
-
import type {
|
|
6
|
+
import type { ConversationItem } from '@droppii/openim-rn-client-sdk';
|
|
11
7
|
|
|
12
8
|
export type DChatActionIconProvider = 'MaterialCommunityIcons' | 'DroppiiNew';
|
|
13
9
|
|
|
@@ -29,14 +25,13 @@ export interface ChatDetailHeaderProps {
|
|
|
29
25
|
subtitle?: string;
|
|
30
26
|
avatarUri?: string | null;
|
|
31
27
|
avatarFullName?: string;
|
|
32
|
-
peerType?: PeerType;
|
|
33
|
-
applicationType?: DConversationItem['applicationType'];
|
|
34
28
|
showAddMember?: boolean;
|
|
35
29
|
onBack?: () => void;
|
|
36
30
|
onPressSearch?: () => void;
|
|
37
31
|
onPressAddMember?: () => void;
|
|
38
32
|
onPressMenu?: () => void;
|
|
39
33
|
onPressAvatar?: () => void;
|
|
34
|
+
conversationId: string;
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
export interface ChatQuickActionsRenderParams {
|
|
@@ -103,9 +98,9 @@ export interface ChatDetailProps extends Omit<
|
|
|
103
98
|
| 'subtitle'
|
|
104
99
|
| 'avatarUri'
|
|
105
100
|
| 'avatarFullName'
|
|
106
|
-
| 'peerType'
|
|
107
101
|
| 'applicationType'
|
|
108
102
|
| 'showAddMember'
|
|
103
|
+
| 'conversationId'
|
|
109
104
|
> {
|
|
110
105
|
conversationId: string;
|
|
111
106
|
enabled?: boolean;
|
|
@@ -113,16 +108,14 @@ export interface ChatDetailProps extends Omit<
|
|
|
113
108
|
subtitle?: string;
|
|
114
109
|
avatarUri?: string | null;
|
|
115
110
|
avatarFullName?: string;
|
|
116
|
-
peerType?: PeerType;
|
|
117
|
-
applicationType?: DChatApplicationType;
|
|
118
111
|
showAddMember?: boolean;
|
|
119
|
-
getSubtitle?: (conversation:
|
|
112
|
+
getSubtitle?: (conversation: ConversationItem) => string | undefined;
|
|
120
113
|
renderChat?: (item: DMessageItem) => ReactNode;
|
|
121
114
|
showQuickActions?: boolean;
|
|
122
115
|
showAttachmentActions?: boolean;
|
|
123
116
|
quickActions?: DChatQuickAction[];
|
|
124
117
|
getQuickActions?: (
|
|
125
|
-
conversation:
|
|
118
|
+
conversation: ConversationItem
|
|
126
119
|
) => DChatQuickAction[] | undefined;
|
|
127
120
|
attachmentActions?: DChatAttachmentAction[];
|
|
128
121
|
inputPlaceholder?: string;
|
|
@@ -3,6 +3,8 @@ import { StyleSheet } from 'react-native';
|
|
|
3
3
|
import { TabView } from 'react-native-tab-view';
|
|
4
4
|
import { KContainer, KImage, KLabel, KColors } from '@droppii/libs';
|
|
5
5
|
import { MessagesTab } from './MessagesTab';
|
|
6
|
+
import { useUserStore } from '../../store';
|
|
7
|
+
import { LoginStatus } from '@droppii/openim-rn-client-sdk';
|
|
6
8
|
|
|
7
9
|
interface InboxProps {
|
|
8
10
|
onPressThread?: (item: string) => void;
|
|
@@ -15,6 +17,7 @@ type TabRoute = { key: string; title: string };
|
|
|
15
17
|
|
|
16
18
|
const Inbox = memo(
|
|
17
19
|
({ onPressThread, onPressSearch, onPressEdit, onPressGroup }: InboxProps) => {
|
|
20
|
+
const connectStatus = useUserStore((state) => state.connectStatus);
|
|
18
21
|
const [index, setIndex] = useState(0);
|
|
19
22
|
const [routes] = useState<TabRoute[]>([
|
|
20
23
|
{ key: 'messages', title: 'Tin nhắn' },
|
|
@@ -22,12 +25,13 @@ const Inbox = memo(
|
|
|
22
25
|
|
|
23
26
|
const renderScene = useCallback(
|
|
24
27
|
({ route }: { route: TabRoute }) => {
|
|
28
|
+
if (connectStatus !== LoginStatus.Logged) return null;
|
|
25
29
|
if (route.key === 'messages') {
|
|
26
30
|
return <MessagesTab onPressThread={onPressThread} />;
|
|
27
31
|
}
|
|
28
32
|
return null;
|
|
29
33
|
},
|
|
30
|
-
[onPressThread]
|
|
34
|
+
[onPressThread, connectStatus]
|
|
31
35
|
);
|
|
32
36
|
|
|
33
37
|
const renderTabBar = useCallback(
|
package/src/services/apis.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { type AxiosInstance } from 'axios';
|
|
2
|
-
import type {
|
|
3
|
-
DConversationItem,
|
|
4
|
-
DConversationQueryParams,
|
|
5
|
-
} from '../types/chat';
|
|
6
2
|
import OpenIMSDK, { LoginStatus } from '@droppii/openim-rn-client-sdk';
|
|
7
|
-
import type { BaseResponse, GetOpenIMTokenResponse } from '../types/auth';
|
|
8
|
-
import { Platform } from 'react-native';
|
|
9
3
|
import { useConversationStore } from '../store';
|
|
10
4
|
|
|
11
5
|
export let apiInstance: AxiosInstance | null = null;
|
|
@@ -19,25 +13,6 @@ export namespace ChatAPI {
|
|
|
19
13
|
return (await OpenIMSDK.getLoginStatus(operationID)) === LoginStatus.Logged;
|
|
20
14
|
};
|
|
21
15
|
|
|
22
|
-
export const login = async (
|
|
23
|
-
instance: AxiosInstance,
|
|
24
|
-
operationID?: string
|
|
25
|
-
) => {
|
|
26
|
-
const isLoggedIn = await getLoginStatus(operationID);
|
|
27
|
-
|
|
28
|
-
if (isLoggedIn) return;
|
|
29
|
-
|
|
30
|
-
const response = await instance.post<BaseResponse<GetOpenIMTokenResponse>>(
|
|
31
|
-
`/chat-service/v1/auth/token?platform=${Platform.OS.toUpperCase()}`
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
const token = response?.data?.data;
|
|
35
|
-
|
|
36
|
-
if (token == null) return;
|
|
37
|
-
|
|
38
|
-
return OpenIMSDK.login({ userID: token.id, token: token.token });
|
|
39
|
-
};
|
|
40
|
-
|
|
41
16
|
export const logout = async (operationID?: string) => {
|
|
42
17
|
useConversationStore.getState().reset();
|
|
43
18
|
return OpenIMSDK.logout(operationID);
|
|
@@ -46,17 +21,4 @@ export namespace ChatAPI {
|
|
|
46
21
|
export const getUser = async (operationID?: string | undefined) => {
|
|
47
22
|
return OpenIMSDK.getSelfUserInfo(operationID);
|
|
48
23
|
};
|
|
49
|
-
|
|
50
|
-
export async function queryConversations(params: DConversationQueryParams) {
|
|
51
|
-
if (!apiInstance) {
|
|
52
|
-
throw new Error(
|
|
53
|
-
'apiInstance is not initialized. Call initApiInstance() first.'
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
const response = await apiInstance.post<BaseResponse<DConversationItem[]>>(
|
|
57
|
-
'/chat-service/v1/app/conversations/query',
|
|
58
|
-
params
|
|
59
|
-
);
|
|
60
|
-
return response.data;
|
|
61
|
-
}
|
|
62
24
|
}
|
|
@@ -6,12 +6,13 @@ interface FileElem {
|
|
|
6
6
|
fileName: string;
|
|
7
7
|
fileSize: number;
|
|
8
8
|
fileType: string;
|
|
9
|
-
|
|
9
|
+
sourceUrl: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const MB = 1024 * 1024;
|
|
13
|
+
export const MAX_FILE_SIZE = 50 * MB;
|
|
14
14
|
|
|
15
|
+
export class FileAttachmentHandler implements IAttachmentHandler {
|
|
15
16
|
validate(
|
|
16
17
|
attachment: Attachment
|
|
17
18
|
): { valid: true } | { valid: false; reason: string } {
|
|
@@ -38,10 +39,10 @@ export class FileAttachmentHandler implements IAttachmentHandler {
|
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
if (sourceFile.size >
|
|
42
|
+
if (sourceFile.size > MAX_FILE_SIZE) {
|
|
42
43
|
return {
|
|
43
44
|
valid: false,
|
|
44
|
-
reason: `File size exceeds maximum (${
|
|
45
|
+
reason: `File size exceeds maximum (${MAX_FILE_SIZE / 1024 / 1024}MB)`,
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -60,7 +61,7 @@ export class FileAttachmentHandler implements IAttachmentHandler {
|
|
|
60
61
|
fileName: sourceFile.filename,
|
|
61
62
|
fileSize: sourceFile.size,
|
|
62
63
|
fileType: sourceFile.mimeType,
|
|
63
|
-
|
|
64
|
+
sourceUrl: uploadedUrl,
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
console.warn('[FileAttachmentHandler] Built payload', {
|
|
@@ -68,7 +69,7 @@ export class FileAttachmentHandler implements IAttachmentHandler {
|
|
|
68
69
|
fileName: fileElem.fileName,
|
|
69
70
|
fileSize: fileElem.fileSize,
|
|
70
71
|
fileType: fileElem.fileType,
|
|
71
|
-
|
|
72
|
+
sourceUrl: uploadedUrl.substring(0, 50) + '...',
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
return fileElem;
|
|
@@ -7,4 +7,7 @@ export function initializeAttachmentHandlers(): void {
|
|
|
7
7
|
|
|
8
8
|
export { ImageAttachmentHandler } from './imageAttachmentHandler';
|
|
9
9
|
export { default as VideoAttachmentHandler } from './videoAttachmentHandler';
|
|
10
|
-
export {
|
|
10
|
+
export {
|
|
11
|
+
default as FileAttachmentHandler,
|
|
12
|
+
MAX_FILE_SIZE,
|
|
13
|
+
} from './fileAttachmentHandler';
|
|
@@ -1,32 +1,23 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
mergeOpenIMIntoConversation,
|
|
5
|
-
} from '../utils/conversation';
|
|
6
|
-
import type { DConversationItem } from '../types/chat';
|
|
7
|
-
import type {
|
|
8
|
-
ConversationItem,
|
|
9
|
-
MessageItem,
|
|
10
|
-
} from '@droppii/openim-rn-client-sdk';
|
|
2
|
+
import { sortConversation } from '../utils/conversation';
|
|
3
|
+
import type { ConversationItem } from '@droppii/openim-rn-client-sdk';
|
|
11
4
|
|
|
12
5
|
type ConversationID = string;
|
|
13
6
|
|
|
14
7
|
export interface ConversationStore {
|
|
15
8
|
currentConversationId?: ConversationID;
|
|
16
9
|
setCurrentConversation(id: ConversationID): void;
|
|
17
|
-
getCurrentConversation():
|
|
10
|
+
getCurrentConversation(): ConversationItem | undefined;
|
|
18
11
|
list: ConversationID[];
|
|
19
|
-
map: Record<ConversationID,
|
|
12
|
+
map: Record<ConversationID, ConversationItem>;
|
|
20
13
|
/** Signal incremented when a truly new conversation arrives and a full refetch is needed. */
|
|
21
14
|
newConversationSignal: number;
|
|
22
|
-
updateConversations(conversations:
|
|
15
|
+
updateConversations(conversations: ConversationItem[]): void;
|
|
23
16
|
/**
|
|
24
17
|
* Patches existing conversations with real-time data from OpenIM events.
|
|
25
18
|
* Returns true if any conversation was not found in the store (triggers refetch).
|
|
26
19
|
*/
|
|
27
|
-
mergeOpenIMConversations(conversations: ConversationItem[]): boolean;
|
|
28
20
|
removeConversation(id: ConversationID): void;
|
|
29
|
-
updateLastMessage(id: ConversationID, message: MessageItem): void;
|
|
30
21
|
reset(): void;
|
|
31
22
|
}
|
|
32
23
|
|
|
@@ -56,35 +47,6 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
|
|
|
56
47
|
)
|
|
57
48
|
);
|
|
58
49
|
},
|
|
59
|
-
mergeOpenIMConversations(conversations) {
|
|
60
|
-
const currentMap = get().map;
|
|
61
|
-
let hasUnknown = false;
|
|
62
|
-
let changed = false;
|
|
63
|
-
|
|
64
|
-
const nextMap = { ...currentMap };
|
|
65
|
-
for (const openim of conversations) {
|
|
66
|
-
const existing = currentMap[openim.conversationID];
|
|
67
|
-
if (existing == null) {
|
|
68
|
-
hasUnknown = true;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
nextMap[openim.conversationID] = mergeOpenIMIntoConversation(
|
|
72
|
-
existing,
|
|
73
|
-
openim
|
|
74
|
-
);
|
|
75
|
-
changed = true;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (changed) {
|
|
79
|
-
set(sortConversation(nextMap));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (hasUnknown) {
|
|
83
|
-
set((s) => ({ newConversationSignal: s.newConversationSignal + 1 }));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return hasUnknown;
|
|
87
|
-
},
|
|
88
50
|
removeConversation(id) {
|
|
89
51
|
const newMap = { ...get().map };
|
|
90
52
|
delete newMap[id];
|
|
@@ -94,17 +56,6 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
|
|
|
94
56
|
list: get().list.filter((item) => item !== id),
|
|
95
57
|
});
|
|
96
58
|
},
|
|
97
|
-
updateLastMessage(id, message) {
|
|
98
|
-
const conversation = get().map[id];
|
|
99
|
-
if (conversation == null) return;
|
|
100
|
-
|
|
101
|
-
set(
|
|
102
|
-
sortConversation({
|
|
103
|
-
[id]: { ...conversation, lastMessage: message },
|
|
104
|
-
...get().map,
|
|
105
|
-
})
|
|
106
|
-
);
|
|
107
|
-
},
|
|
108
59
|
reset() {
|
|
109
60
|
set({
|
|
110
61
|
currentConversationId: undefined,
|
|
@@ -115,6 +66,6 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
|
|
|
115
66
|
},
|
|
116
67
|
}));
|
|
117
68
|
|
|
118
|
-
export const useConversation = (id: string):
|
|
69
|
+
export const useConversation = (id: string): ConversationItem | undefined => {
|
|
119
70
|
return useConversationStore((state) => state.map[id]);
|
|
120
71
|
};
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
const i18nKey: Record<string, string> = {
|
|
2
2
|
'thread_card_bot_crm_name': 'Droppii Hỗ Trợ',
|
|
3
3
|
'thread_card_fallback_name': 'Người dùng',
|
|
4
|
+
'chat:file_too_large': 'Tệp quá lớn. Tối đa 10MB',
|
|
5
|
+
'chat:file_max_count': 'Chỉ có thể đính kèm tối đa 3 tệp',
|
|
6
|
+
'chat:file_pick_failed': 'Không thể chọn tệp',
|
|
7
|
+
'chat:file_upload_failed': 'Lỗi tải tệp lên',
|
|
8
|
+
'chat:file_preview_unsupported': 'Không thể xem trước tệp này',
|
|
9
|
+
'chat:file_downloading': 'Đang tải {{received}}/{{total}} MB',
|
|
10
|
+
'chat:file_downloaded_success': 'Đã tải thành công {{fileName}}',
|
|
11
|
+
'chat:file_download_failed': 'Tải tệp thất bại',
|
|
12
|
+
'chat:file_open_failed': 'Không thể mở tệp này',
|
|
4
13
|
'chat:image_preview': 'Xem trước ảnh',
|
|
5
14
|
'chat:image_too_large': 'Ảnh quá lớn. Tối đa 5MB',
|
|
6
15
|
'chat:video_too_large': 'Video quá lớn. Tối đa 200MB',
|
package/src/types/attachment.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type AttachmentType = 'image' | 'video' | 'file' | 'audio';
|
|
1
|
+
export type AttachmentType = 'image' | 'video' | 'file' | 'audio' | 'document';
|
|
2
2
|
|
|
3
3
|
export interface BaseAttachment {
|
|
4
4
|
id: string;
|
|
@@ -83,3 +83,25 @@ export interface AttachmentSendResult {
|
|
|
83
83
|
mergedMessageId?: string;
|
|
84
84
|
errors: Record<string, string>;
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
interface AttachmentMetadata {
|
|
88
|
+
id?: string;
|
|
89
|
+
path: string;
|
|
90
|
+
filename?: string;
|
|
91
|
+
size?: number;
|
|
92
|
+
duration?: number;
|
|
93
|
+
mimeType?: string;
|
|
94
|
+
width?: number;
|
|
95
|
+
height?: number;
|
|
96
|
+
uri?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface AttachmentItem {
|
|
100
|
+
metadata: AttachmentMetadata;
|
|
101
|
+
uploadProgress: number;
|
|
102
|
+
error: string | null;
|
|
103
|
+
isUploading: boolean;
|
|
104
|
+
uploadedUrl?: string;
|
|
105
|
+
thumbnailUrl?: string;
|
|
106
|
+
type: AttachmentType;
|
|
107
|
+
}
|
package/src/types/auth.ts
CHANGED
package/src/types/chat.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type MessageItem,
|
|
3
|
-
type ConversationItem,
|
|
4
|
-
PeerType,
|
|
5
|
-
} from '@droppii/openim-rn-client-sdk';
|
|
1
|
+
import { type MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
6
2
|
import type { PropsWithChildren } from 'react';
|
|
7
3
|
import type { IUrlMetadata } from './common';
|
|
8
4
|
import type {
|
|
@@ -10,6 +6,7 @@ import type {
|
|
|
10
6
|
IConversationFlowStep,
|
|
11
7
|
IRunConversationFlowPayload,
|
|
12
8
|
} from './flows';
|
|
9
|
+
import type { GetOpenIMTokenResponse } from './auth';
|
|
13
10
|
|
|
14
11
|
export const EventProvider = {
|
|
15
12
|
ga: 'ga',
|
|
@@ -31,6 +28,7 @@ export interface ChatContextType {
|
|
|
31
28
|
export type ChatProviderProps = PropsWithChildren<{
|
|
32
29
|
logGA?: LogGA;
|
|
33
30
|
applicationType: DChatApplicationType;
|
|
31
|
+
getChatToken: () => Promise<GetOpenIMTokenResponse | null>;
|
|
34
32
|
}>;
|
|
35
33
|
|
|
36
34
|
export enum DChatApplicationType {
|
|
@@ -66,20 +64,6 @@ export interface DConversationPeer {
|
|
|
66
64
|
group: null; // to be defined when group is implemented
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
export interface DConversationItem extends ConversationItem {
|
|
70
|
-
// Droppii API fields
|
|
71
|
-
conversationId: string;
|
|
72
|
-
chatCategory: DChatCategory;
|
|
73
|
-
chatType: PeerType;
|
|
74
|
-
applicationType: DChatApplicationType;
|
|
75
|
-
memberRole: DMemberRole;
|
|
76
|
-
pinnedAt: string | null;
|
|
77
|
-
isMuted: boolean;
|
|
78
|
-
peer: DConversationPeerUser;
|
|
79
|
-
// Merged from OpenIM (available after getConversationListSplit merge)
|
|
80
|
-
lastMessage?: DMessageItem;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
67
|
export interface DConversationQueryParams {
|
|
84
68
|
applicationType: string;
|
|
85
69
|
page: number;
|
|
@@ -146,7 +130,19 @@ export interface ChatDetailContextType {
|
|
|
146
130
|
conversationFlow?: IConversationFlow;
|
|
147
131
|
refetchConversationFlow: () => void;
|
|
148
132
|
isBotConversation: boolean;
|
|
149
|
-
setConverationFlowStep: (step
|
|
133
|
+
setConverationFlowStep: (step?: IConversationFlowStep) => void;
|
|
150
134
|
}
|
|
151
135
|
|
|
152
136
|
export interface ChatDetailProviderProps extends PropsWithChildren {}
|
|
137
|
+
|
|
138
|
+
export interface IConversationItemEx {
|
|
139
|
+
applicationType: DChatApplicationType;
|
|
140
|
+
sessionInfo?: {
|
|
141
|
+
type: 'SESSION_INFO';
|
|
142
|
+
data: {
|
|
143
|
+
ownerId: string;
|
|
144
|
+
botId: string;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
chatCategory: DChatCategory;
|
|
148
|
+
}
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
SessionType,
|
|
4
4
|
type ConversationItem,
|
|
5
5
|
} from '@droppii/openim-rn-client-sdk';
|
|
6
|
-
import { type DConversationItem, type DMessageItem } from '../types/chat';
|
|
7
6
|
import { trans } from '../translation';
|
|
8
7
|
|
|
9
8
|
export const conversationCompare = (
|
|
@@ -24,43 +23,6 @@ export const conversationCompare = (
|
|
|
24
23
|
return bCompare - aCompare;
|
|
25
24
|
};
|
|
26
25
|
|
|
27
|
-
export const dConversationCompare = (
|
|
28
|
-
a: DConversationItem,
|
|
29
|
-
b: DConversationItem
|
|
30
|
-
) => {
|
|
31
|
-
if (a.isPinned !== b.isPinned) return a.isPinned ? -1 : 1;
|
|
32
|
-
|
|
33
|
-
const aTime =
|
|
34
|
-
a.draftTextTime > a.latestMsgSendTime
|
|
35
|
-
? a.draftTextTime
|
|
36
|
-
: a.latestMsgSendTime;
|
|
37
|
-
const bTime =
|
|
38
|
-
b.draftTextTime > b.latestMsgSendTime
|
|
39
|
-
? b.draftTextTime
|
|
40
|
-
: b.latestMsgSendTime;
|
|
41
|
-
|
|
42
|
-
return bTime - aTime;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const mergeOpenIMIntoConversation = (
|
|
46
|
-
existing: DConversationItem,
|
|
47
|
-
openim: ConversationItem
|
|
48
|
-
): DConversationItem => {
|
|
49
|
-
const { latestMsg, ...openimRest } = openim;
|
|
50
|
-
|
|
51
|
-
let lastMessage: DMessageItem | undefined = existing.lastMessage;
|
|
52
|
-
if (latestMsg) {
|
|
53
|
-
try {
|
|
54
|
-
lastMessage = JSON.parse(latestMsg) as DMessageItem;
|
|
55
|
-
} catch {
|
|
56
|
-
// keep existing lastMessage if parse fails
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Spread existing first so peerType (not in ConversationItem) is preserved
|
|
61
|
-
return { ...existing, ...openimRest, lastMessage };
|
|
62
|
-
};
|
|
63
|
-
|
|
64
26
|
export const getConversationID = (
|
|
65
27
|
sessionType: SessionType,
|
|
66
28
|
senderID?: string,
|
|
@@ -76,9 +38,9 @@ export const getConversationID = (
|
|
|
76
38
|
return [prefix, senderID, receiverID].filter(Boolean).join('_');
|
|
77
39
|
};
|
|
78
40
|
|
|
79
|
-
export const sortConversation = (map: Record<string,
|
|
41
|
+
export const sortConversation = (map: Record<string, ConversationItem>) => {
|
|
80
42
|
const list = Object.keys(map).sort((a, b) =>
|
|
81
|
-
|
|
43
|
+
conversationCompare(map[a]!, map[b]!)
|
|
82
44
|
);
|
|
83
45
|
return { map, list };
|
|
84
46
|
};
|
package/src/utils/device.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import ImagePicker from 'react-native-image-crop-picker';
|
|
2
|
+
import {
|
|
3
|
+
pick as pickDocument,
|
|
4
|
+
types as documentTypes,
|
|
5
|
+
isErrorWithCode,
|
|
6
|
+
errorCodes,
|
|
7
|
+
keepLocalCopy,
|
|
8
|
+
} from '@react-native-documents/picker';
|
|
9
|
+
|
|
10
|
+
export interface PickedFile {
|
|
11
|
+
uri: string;
|
|
12
|
+
name: string;
|
|
13
|
+
size: number;
|
|
14
|
+
mimeType: string;
|
|
15
|
+
}
|
|
2
16
|
|
|
3
17
|
const DeviceUtils = {
|
|
4
18
|
openImagePicker: async (multiple: boolean = false) => {
|
|
@@ -68,6 +82,50 @@ const DeviceUtils = {
|
|
|
68
82
|
throw error;
|
|
69
83
|
}
|
|
70
84
|
},
|
|
85
|
+
|
|
86
|
+
openFilePicker: async (
|
|
87
|
+
multiple: boolean = false
|
|
88
|
+
): Promise<PickedFile[] | null> => {
|
|
89
|
+
try {
|
|
90
|
+
const results = await pickDocument({
|
|
91
|
+
allowMultiSelection: multiple,
|
|
92
|
+
type: [documentTypes.allFiles],
|
|
93
|
+
mode: 'import',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Copy picked files to app's document directory so uploads can access them
|
|
97
|
+
const copies = await keepLocalCopy({
|
|
98
|
+
files: results.map((r) => ({
|
|
99
|
+
uri: r.uri,
|
|
100
|
+
fileName: r.name ?? 'file',
|
|
101
|
+
})) as [
|
|
102
|
+
{ uri: string; fileName: string },
|
|
103
|
+
...{ uri: string; fileName: string }[],
|
|
104
|
+
],
|
|
105
|
+
destination: 'documentDirectory',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const files = results.map((r, i) => ({
|
|
109
|
+
uri: decodeURIComponent(
|
|
110
|
+
copies[i]?.status === 'success' ? copies[i].localUri : r.uri
|
|
111
|
+
),
|
|
112
|
+
name: r.name ?? 'file',
|
|
113
|
+
size: r.size ?? 0,
|
|
114
|
+
mimeType: r.type ?? 'application/octet-stream',
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
return files;
|
|
118
|
+
} catch (error: any) {
|
|
119
|
+
if (
|
|
120
|
+
isErrorWithCode(error) &&
|
|
121
|
+
error.code === errorCodes.OPERATION_CANCELED
|
|
122
|
+
) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
console.error('[File Picker] Error:', error);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
},
|
|
71
129
|
};
|
|
72
130
|
|
|
73
131
|
export default DeviceUtils;
|