@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.
- 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 +13 -0
- package/lib/module/config/uploadLimits.js.map +1 -0
- 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 +153 -0
- package/lib/module/hooks/useChatCompose.js.map +1 -0
- 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 +81 -152
- package/lib/module/hooks/useImageAttachment.js.map +1 -1
- package/lib/module/hooks/useSendAttachment.js +2 -2
- package/lib/module/hooks/useSendAttachment.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 +50 -185
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +29 -137
- 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/index.js +0 -1
- package/lib/module/screens/chat-detail/index.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/attachmentHandlers/fileAttachmentHandler.js +2 -2
- package/lib/module/services/attachmentHandlers/fileAttachmentHandler.js.map +1 -1
- package/lib/module/services/attachmentHandlers/imageAttachmentHandler.js +3 -3
- package/lib/module/services/attachmentHandlers/imageAttachmentHandler.js.map +1 -1
- package/lib/module/services/attachmentHandlers/videoAttachmentHandler.js +2 -1
- package/lib/module/services/attachmentHandlers/videoAttachmentHandler.js.map +1 -1
- package/lib/module/services/attachmentOrchestrator.js +40 -17
- 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/imageUtils.js +3 -3
- package/lib/module/utils/imageUtils.js.map +1 -1
- package/lib/module/utils/pLimit.js +19 -0
- package/lib/module/utils/pLimit.js.map +1 -0
- package/lib/module/utils/videoThumbnail.js +8 -3
- package/lib/module/utils/videoThumbnail.js.map +1 -1
- package/lib/module/utils/videoThumbnailUpload.js +76 -0
- package/lib/module/utils/videoThumbnailUpload.js.map +1 -0
- 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/config/uploadLimits.d.ts +7 -0
- package/lib/typescript/src/config/uploadLimits.d.ts.map +1 -0
- 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 +27 -0
- package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useImageAttachment.d.ts +2 -2
- package/lib/typescript/src/hooks/useImageAttachment.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useSendAttachment.d.ts +1 -1
- package/lib/typescript/src/hooks/useSendAttachment.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +5 -2
- 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/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/index.d.ts +0 -2
- package/lib/typescript/src/screens/chat-detail/index.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/screens/chat-detail/types.d.ts +1 -35
- package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
- package/lib/typescript/src/services/attachmentHandlers/fileAttachmentHandler.d.ts.map +1 -1
- package/lib/typescript/src/services/attachmentHandlers/imageAttachmentHandler.d.ts.map +1 -1
- package/lib/typescript/src/services/attachmentHandlers/videoAttachmentHandler.d.ts.map +1 -1
- package/lib/typescript/src/services/attachmentOrchestrator.d.ts +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/lib/typescript/src/utils/imageUtils.d.ts.map +1 -1
- package/lib/typescript/src/utils/pLimit.d.ts +6 -0
- package/lib/typescript/src/utils/pLimit.d.ts.map +1 -0
- package/lib/typescript/src/utils/videoThumbnail.d.ts.map +1 -1
- package/lib/typescript/src/utils/videoThumbnailUpload.d.ts +11 -0
- package/lib/typescript/src/utils/videoThumbnailUpload.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 +11 -0
- 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 +195 -0
- 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 +120 -199
- package/src/hooks/useSendAttachment.ts +3 -6
- package/src/index.tsx +3 -5
- package/src/screens/chat-detail/ChatComposer.tsx +88 -240
- package/src/screens/chat-detail/ChatDetail.tsx +36 -195
- package/src/screens/chat-detail/ChatListLegend.tsx +16 -13
- package/src/screens/chat-detail/index.ts +1 -5
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +19 -12
- package/src/screens/chat-detail/types.ts +1 -38
- package/src/services/attachmentHandlers/fileAttachmentHandler.ts +3 -2
- package/src/services/attachmentHandlers/imageAttachmentHandler.ts +6 -3
- package/src/services/attachmentHandlers/videoAttachmentHandler.ts +2 -1
- package/src/services/attachmentOrchestrator.ts +49 -27
- 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/imageUtils.ts +3 -4
- package/src/utils/pLimit.ts +20 -0
- package/src/utils/videoThumbnail.ts +10 -3
- package/src/utils/videoThumbnailUpload.ts +103 -0
- package/lib/module/hooks/useVideoAttachment.js +0 -251
- package/lib/module/hooks/useVideoAttachment.js.map +0 -1
- package/lib/module/screens/chat-detail/hooks/useAttachmentSendHandler.js +0 -288
- package/lib/module/screens/chat-detail/hooks/useAttachmentSendHandler.js.map +0 -1
- package/lib/module/utils/giftedChatMessage.js +0 -90
- package/lib/module/utils/giftedChatMessage.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/screens/chat-detail/hooks/useAttachmentSendHandler.d.ts +0 -26
- package/lib/typescript/src/screens/chat-detail/hooks/useAttachmentSendHandler.d.ts.map +0 -1
- package/lib/typescript/src/utils/giftedChatMessage.d.ts +0 -10
- package/lib/typescript/src/utils/giftedChatMessage.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/screens/chat-detail/hooks/useAttachmentSendHandler.ts +0 -386
- package/src/utils/giftedChatMessage.ts +0 -137
- package/src/utils/ui.ts +0 -19
|
@@ -7,18 +7,10 @@ import {
|
|
|
7
7
|
KColors,
|
|
8
8
|
KSpacingValue,
|
|
9
9
|
} from '@droppii/libs';
|
|
10
|
-
import type { DMessageItem } from '../../../types/chat';
|
|
11
10
|
import type { IUrlMetadata } from '../../../types/common';
|
|
12
11
|
import { formatMessageTime } from '../types';
|
|
12
|
+
import type { BaseLegendMessageProps } from '../types';
|
|
13
13
|
import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
|
|
14
|
-
|
|
15
|
-
interface BaseLegendMessageProps {
|
|
16
|
-
message: DMessageItem;
|
|
17
|
-
isOutgoing: boolean;
|
|
18
|
-
createdAtTime: number;
|
|
19
|
-
onPressUrl?: (url: string) => void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
14
|
const parseUrlMetadata = (ex?: string): IUrlMetadata | undefined => {
|
|
23
15
|
if (!ex) return undefined;
|
|
24
16
|
try {
|
|
@@ -80,7 +72,7 @@ export const LinkMessage = memo(
|
|
|
80
72
|
isOutgoing,
|
|
81
73
|
createdAtTime,
|
|
82
74
|
onPressUrl,
|
|
83
|
-
}: BaseLegendMessageProps) => {
|
|
75
|
+
}: BaseLegendMessageProps & { onPressUrl?: (url: string) => void }) => {
|
|
84
76
|
const content = message?.urlTextElem?.content;
|
|
85
77
|
const urls = message?.urlTextElem?.urls ?? [];
|
|
86
78
|
const metadata = parseUrlMetadata(message.ex);
|
|
@@ -147,15 +139,17 @@ export const LinkMessage = memo(
|
|
|
147
139
|
)}
|
|
148
140
|
</KContainer.View>
|
|
149
141
|
|
|
150
|
-
{
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
142
|
+
<KContainer.View row alignItems style={{ gap: 4, marginTop: 2 }}>
|
|
143
|
+
{!isOutgoing && timeLabel ? (
|
|
144
|
+
<KLabel.Text
|
|
145
|
+
typo="TextXsNormal"
|
|
146
|
+
color={CHAT_BUBBLE_COLORS.timestamp}
|
|
147
|
+
marginL="0.25rem"
|
|
148
|
+
>
|
|
149
|
+
{timeLabel}
|
|
150
|
+
</KLabel.Text>
|
|
151
|
+
) : null}
|
|
152
|
+
</KContainer.View>
|
|
159
153
|
</KContainer.View>
|
|
160
154
|
);
|
|
161
155
|
}
|
|
@@ -3,7 +3,6 @@ import { KContainer } from '@droppii/libs';
|
|
|
3
3
|
import { MergedImageGrid } from '../../MergedImageGrid';
|
|
4
4
|
import { messageStyles } from '../styles';
|
|
5
5
|
import type { BaseLegendMessageProps } from '../types';
|
|
6
|
-
|
|
7
6
|
export const MergedMessage = memo(
|
|
8
7
|
({ message, isOutgoing, onMediaPress }: BaseLegendMessageProps) => {
|
|
9
8
|
const mergeElem = (message as any).mergeElem;
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
2
|
import { KContainer, KLabel } from '@droppii/libs';
|
|
3
3
|
import { getMessageText } from '../../../utils/legendListMessage';
|
|
4
|
-
import type { DMessageItem } from '../../../types/chat';
|
|
5
4
|
import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
|
|
6
5
|
import { messageStyles } from '../styles';
|
|
7
6
|
import { formatMessageTime } from '../types';
|
|
8
|
-
|
|
9
|
-
interface TextMessageProps {
|
|
10
|
-
message: DMessageItem;
|
|
11
|
-
isOutgoing: boolean;
|
|
12
|
-
createdAtTime: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
7
|
+
import type { BaseLegendMessageProps } from '../types';
|
|
15
8
|
export const TextMessage = memo(
|
|
16
|
-
({ message, isOutgoing, createdAtTime }:
|
|
9
|
+
({ message, isOutgoing, createdAtTime }: BaseLegendMessageProps) => {
|
|
17
10
|
const messageText = getMessageText(message);
|
|
18
11
|
const timeLabel = formatMessageTime(createdAtTime);
|
|
19
12
|
|
|
@@ -36,15 +29,17 @@ export const TextMessage = memo(
|
|
|
36
29
|
</KLabel.Text>
|
|
37
30
|
</KContainer.View>
|
|
38
31
|
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
<KContainer.View row alignItems style={{ gap: 4, marginTop: 2 }}>
|
|
33
|
+
{!isOutgoing && timeLabel ? (
|
|
34
|
+
<KLabel.Text
|
|
35
|
+
typo="TextXsNormal"
|
|
36
|
+
color={CHAT_BUBBLE_COLORS.timestamp}
|
|
37
|
+
marginL={'0.25rem'}
|
|
38
|
+
>
|
|
39
|
+
{timeLabel}
|
|
40
|
+
</KLabel.Text>
|
|
41
|
+
) : null}
|
|
42
|
+
</KContainer.View>
|
|
48
43
|
</KContainer.View>
|
|
49
44
|
);
|
|
50
45
|
}
|
|
@@ -7,7 +7,6 @@ import ChatImageDimens, {
|
|
|
7
7
|
import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
|
|
8
8
|
import { messageStyles } from '../styles';
|
|
9
9
|
import type { BaseLegendMessageProps } from '../types';
|
|
10
|
-
|
|
11
10
|
const formatDuration = (durationMs: number): string => {
|
|
12
11
|
const totalSeconds = Math.floor(durationMs / 1000);
|
|
13
12
|
const minutes = Math.floor(totalSeconds / 60);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const MB = 1024 * 1024;
|
|
2
|
+
|
|
3
|
+
export const UPLOAD_LIMITS = {
|
|
4
|
+
image: 15 * MB,
|
|
5
|
+
video: 200 * MB,
|
|
6
|
+
file: 50 * MB,
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
// Max concurrent thumbnail generations and file uploads.
|
|
10
|
+
// Keep low to avoid OOM on low-end devices.
|
|
11
|
+
export const UPLOAD_CONCURRENCY = 2;
|
|
@@ -2,30 +2,58 @@ import OpenIMSDK, {
|
|
|
2
2
|
OpenIMEvent,
|
|
3
3
|
type ConversationItem,
|
|
4
4
|
type GroupMemberItem,
|
|
5
|
+
type MessageItem,
|
|
5
6
|
} from '@droppii/openim-rn-client-sdk';
|
|
6
7
|
import { useCallback, useEffect } from 'react';
|
|
7
8
|
import { useConversationStore } from '../store/conversation';
|
|
9
|
+
import { useMessageStore } from '../store/message';
|
|
8
10
|
|
|
9
11
|
export const useChatListener = () => {
|
|
12
|
+
const updateOneMessage = useMessageStore((state) => state.updateOneMessage);
|
|
13
|
+
|
|
14
|
+
const onSendMessageProgress = useCallback(
|
|
15
|
+
({ progress, message }: { progress: number; message: MessageItem }) => {
|
|
16
|
+
console.log('[Listener] SendMessageProgress', {
|
|
17
|
+
clientMsgID: message.clientMsgID,
|
|
18
|
+
status: message.status,
|
|
19
|
+
progress,
|
|
20
|
+
});
|
|
21
|
+
updateOneMessage(message);
|
|
22
|
+
},
|
|
23
|
+
[updateOneMessage]
|
|
24
|
+
);
|
|
25
|
+
|
|
10
26
|
const onConversationChanged = useCallback(
|
|
11
27
|
(conversations: ConversationItem[]) => {
|
|
12
|
-
console.log('
|
|
28
|
+
console.log('[Listener] OnConversationChanged', {
|
|
29
|
+
count: conversations.length,
|
|
30
|
+
ids: conversations.map((c) => c.conversationID),
|
|
31
|
+
});
|
|
13
32
|
useConversationStore.getState().updateConversations(conversations);
|
|
14
33
|
},
|
|
15
34
|
[]
|
|
16
35
|
);
|
|
17
36
|
|
|
18
37
|
const onNewConversation = useCallback((conversations: ConversationItem[]) => {
|
|
19
|
-
console.log('
|
|
38
|
+
console.log('[Listener] OnNewConversation', {
|
|
39
|
+
count: conversations.length,
|
|
40
|
+
ids: conversations.map((c) => c.conversationID),
|
|
41
|
+
});
|
|
20
42
|
useConversationStore.getState().updateConversations(conversations);
|
|
21
43
|
}, []);
|
|
22
44
|
|
|
23
45
|
const onGroupMemberAdded = useCallback((member: GroupMemberItem) => {
|
|
24
|
-
console.log('
|
|
46
|
+
console.log('[Listener] OnGroupMemberAdded', {
|
|
47
|
+
userID: member.userID,
|
|
48
|
+
groupID: member.groupID,
|
|
49
|
+
});
|
|
25
50
|
}, []);
|
|
26
51
|
|
|
27
52
|
const onGroupMemberDeleted = useCallback((member: GroupMemberItem) => {
|
|
28
|
-
console.log('
|
|
53
|
+
console.log('[Listener] OnGroupMemberDeleted', {
|
|
54
|
+
userID: member.userID,
|
|
55
|
+
groupID: member.groupID,
|
|
56
|
+
});
|
|
29
57
|
}, []);
|
|
30
58
|
|
|
31
59
|
useEffect(() => {
|
|
@@ -37,6 +65,9 @@ export const useChatListener = () => {
|
|
|
37
65
|
OpenIMSDK.on(OpenIMEvent.OnGroupMemberAdded, onGroupMemberAdded);
|
|
38
66
|
OpenIMSDK.on(OpenIMEvent.OnGroupMemberDeleted, onGroupMemberDeleted);
|
|
39
67
|
|
|
68
|
+
// Message status
|
|
69
|
+
OpenIMSDK.on(OpenIMEvent.SendMessageProgress, onSendMessageProgress);
|
|
70
|
+
|
|
40
71
|
return () => {
|
|
41
72
|
// Conversation
|
|
42
73
|
OpenIMSDK.off(OpenIMEvent.OnConversationChanged, onConversationChanged);
|
|
@@ -45,6 +76,9 @@ export const useChatListener = () => {
|
|
|
45
76
|
// Group members
|
|
46
77
|
OpenIMSDK.off(OpenIMEvent.OnGroupMemberAdded, onGroupMemberAdded);
|
|
47
78
|
OpenIMSDK.off(OpenIMEvent.OnGroupMemberDeleted, onGroupMemberDeleted);
|
|
79
|
+
|
|
80
|
+
// Message status
|
|
81
|
+
OpenIMSDK.off(OpenIMEvent.SendMessageProgress, onSendMessageProgress);
|
|
48
82
|
};
|
|
49
83
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
50
84
|
}, []);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import OpenIMSDK, {
|
|
2
2
|
LoginStatus,
|
|
3
3
|
OpenIMEvent,
|
|
4
|
+
type OpenIMEventBaseErrorType,
|
|
4
5
|
} from '@droppii/openim-rn-client-sdk';
|
|
5
6
|
import { useCallback, useEffect } from 'react';
|
|
6
7
|
import { useUserStore } from '../store';
|
|
@@ -28,36 +29,45 @@ export const useUserListener = () => {
|
|
|
28
29
|
}, [isLoggedIn, fetchAndUpdateUser]);
|
|
29
30
|
|
|
30
31
|
const onConnecting = useCallback(() => {
|
|
32
|
+
console.log('[Listener] OnConnecting');
|
|
31
33
|
useUserStore.getState().updateConnectStatus(LoginStatus.Logging);
|
|
32
34
|
}, []);
|
|
33
35
|
|
|
34
36
|
const onConnectSuccess = useCallback(() => {
|
|
37
|
+
console.log('[Listener] OnConnectSuccess');
|
|
35
38
|
useUserStore.getState().updateConnectStatus(LoginStatus.Logged);
|
|
36
39
|
}, []);
|
|
37
40
|
|
|
38
|
-
const onConnectFailed = useCallback(() => {
|
|
39
|
-
|
|
41
|
+
const onConnectFailed = useCallback((data: OpenIMEventBaseErrorType) => {
|
|
42
|
+
console.log('[Listener] OnConnectFailed', data);
|
|
40
43
|
}, []);
|
|
41
44
|
|
|
42
|
-
const onSyncServerStart = useCallback(() => {
|
|
45
|
+
const onSyncServerStart = useCallback((isSyncing: boolean) => {
|
|
46
|
+
console.log('[Listener] OnSyncServerStart', { isSyncing });
|
|
43
47
|
useUserStore.getState().updateSyncStatus(SyncStatus.Loading);
|
|
44
48
|
}, []);
|
|
45
49
|
|
|
46
50
|
const onSyncServerFinish = useCallback(() => {
|
|
51
|
+
console.log('[Listener] OnSyncServerFinish');
|
|
47
52
|
useUserStore.getState().updateSyncStatus(SyncStatus.Success);
|
|
48
53
|
}, []);
|
|
49
54
|
|
|
50
55
|
const onSyncServerFailed = useCallback(() => {
|
|
56
|
+
console.log('[Listener] OnSyncServerFailed');
|
|
51
57
|
useUserStore.getState().updateSyncStatus(SyncStatus.Failed);
|
|
52
58
|
}, []);
|
|
53
59
|
|
|
60
|
+
const onUserToken = useCallback(() => {
|
|
61
|
+
console.log('[Listener] OnUserToken expired/invalid');
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
54
64
|
useEffect(() => {
|
|
55
65
|
// User
|
|
56
66
|
OpenIMSDK.on(OpenIMEvent.OnConnecting, onConnecting);
|
|
57
67
|
OpenIMSDK.on(OpenIMEvent.OnConnectSuccess, onConnectSuccess);
|
|
58
68
|
OpenIMSDK.on(OpenIMEvent.OnConnectFailed, onConnectFailed);
|
|
59
|
-
OpenIMSDK.on(OpenIMEvent.OnUserTokenExpired,
|
|
60
|
-
OpenIMSDK.on(OpenIMEvent.OnUserTokenInvalid,
|
|
69
|
+
OpenIMSDK.on(OpenIMEvent.OnUserTokenExpired, onUserToken);
|
|
70
|
+
OpenIMSDK.on(OpenIMEvent.OnUserTokenInvalid, onUserToken);
|
|
61
71
|
|
|
62
72
|
// Sync
|
|
63
73
|
OpenIMSDK.on(OpenIMEvent.OnSyncServerStart, onSyncServerStart);
|
|
@@ -69,8 +79,8 @@ export const useUserListener = () => {
|
|
|
69
79
|
OpenIMSDK.off(OpenIMEvent.OnConnecting, onConnecting);
|
|
70
80
|
OpenIMSDK.off(OpenIMEvent.OnConnectSuccess, onConnectSuccess);
|
|
71
81
|
OpenIMSDK.off(OpenIMEvent.OnConnectFailed, onConnectFailed);
|
|
72
|
-
OpenIMSDK.
|
|
73
|
-
OpenIMSDK.
|
|
82
|
+
OpenIMSDK.off(OpenIMEvent.OnUserTokenExpired, onUserToken);
|
|
83
|
+
OpenIMSDK.off(OpenIMEvent.OnUserTokenInvalid, onUserToken);
|
|
74
84
|
|
|
75
85
|
// Sync
|
|
76
86
|
OpenIMSDK.off(OpenIMEvent.OnSyncServerStart, onSyncServerStart);
|
|
@@ -1,23 +1,64 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import type { UploadFileItem } from 'react-native-fs';
|
|
3
|
-
import { useConversationStore } from '../../store';
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
MessageType,
|
|
7
|
-
OfflinePush,
|
|
8
|
-
} from '@droppii/openim-rn-client-sdk';
|
|
3
|
+
import { useConversationStore } from '../../store/conversation';
|
|
4
|
+
import type { MessageItem, OfflinePush } from '@droppii/openim-rn-client-sdk';
|
|
5
|
+
import { MessageStatus, MessageType } from '@droppii/openim-rn-client-sdk';
|
|
9
6
|
import { extractUrls } from '../../utils/url';
|
|
10
7
|
import OpenIMSDK from '@droppii/openim-rn-client-sdk';
|
|
11
8
|
import type { IMessageItemEx } from '../../types/chat';
|
|
12
9
|
import { useChatContext } from '../../context';
|
|
13
10
|
import { generateContentBasedOnMessageType } from '../../utils/conversation';
|
|
14
11
|
import { useMessageStore } from '../../store/message';
|
|
12
|
+
|
|
15
13
|
import type { IUrlMetadata } from '../../types/common';
|
|
16
14
|
import type { IRunConversationFlowPayload } from '../../types/flows';
|
|
17
15
|
|
|
16
|
+
const buildSendParams = (
|
|
17
|
+
message: MessageItem,
|
|
18
|
+
recvID: string,
|
|
19
|
+
groupID: string,
|
|
20
|
+
extendMessageInfo: IMessageItemEx,
|
|
21
|
+
plainText: string
|
|
22
|
+
) => ({
|
|
23
|
+
groupID,
|
|
24
|
+
recvID,
|
|
25
|
+
message: { ...message, ex: JSON.stringify(extendMessageInfo) || '{}' },
|
|
26
|
+
offlinePushInfo: generateOfflinePushInfo(message.contentType, plainText),
|
|
27
|
+
});
|
|
28
|
+
|
|
18
29
|
export const useSendMessage = () => {
|
|
19
30
|
const applicationType = useChatContext().applicationType;
|
|
20
31
|
const pushNewMessage = useMessageStore((state) => state.pushNewMessage);
|
|
32
|
+
const updateOneMessage = useMessageStore((state) => state.updateOneMessage);
|
|
33
|
+
const deleteOneMessage = useMessageStore((state) => state.deleteOneMessage);
|
|
34
|
+
const dispatchSend = useCallback(
|
|
35
|
+
async (
|
|
36
|
+
message: MessageItem,
|
|
37
|
+
recvID: string,
|
|
38
|
+
groupID: string,
|
|
39
|
+
extendMessageInfo: IMessageItemEx,
|
|
40
|
+
plainText: string
|
|
41
|
+
) => {
|
|
42
|
+
pushNewMessage({ ...message, status: MessageStatus.Sending });
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const sent = await OpenIMSDK.sendMessage(
|
|
46
|
+
buildSendParams(
|
|
47
|
+
message,
|
|
48
|
+
recvID,
|
|
49
|
+
groupID,
|
|
50
|
+
extendMessageInfo,
|
|
51
|
+
plainText
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
deleteOneMessage(message.clientMsgID);
|
|
55
|
+
pushNewMessage(sent);
|
|
56
|
+
} catch {
|
|
57
|
+
updateOneMessage({ ...message, status: MessageStatus.Failed });
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
[pushNewMessage, updateOneMessage, deleteOneMessage]
|
|
61
|
+
);
|
|
21
62
|
|
|
22
63
|
const sendMessage = useCallback(
|
|
23
64
|
async ({
|
|
@@ -27,7 +68,7 @@ export const useSendMessage = () => {
|
|
|
27
68
|
flow,
|
|
28
69
|
}: {
|
|
29
70
|
plainText?: string;
|
|
30
|
-
files?: UploadFileItem[];
|
|
71
|
+
files?: UploadFileItem[];
|
|
31
72
|
urlMetadata?: IUrlMetadata;
|
|
32
73
|
flow?: IRunConversationFlowPayload;
|
|
33
74
|
}) => {
|
|
@@ -41,7 +82,6 @@ export const useSendMessage = () => {
|
|
|
41
82
|
if (!file) continue;
|
|
42
83
|
}
|
|
43
84
|
|
|
44
|
-
// Allow empty text if urlMetadata (preview link) is present
|
|
45
85
|
const hasText = !!plainText && plainText.trim() !== '';
|
|
46
86
|
const hasPreview = !!urlMetadata?.url;
|
|
47
87
|
|
|
@@ -51,7 +91,6 @@ export const useSendMessage = () => {
|
|
|
51
91
|
let message: MessageItem | null = null;
|
|
52
92
|
|
|
53
93
|
if (isUrlMessage) {
|
|
54
|
-
// Create URL message even with empty text if preview exists
|
|
55
94
|
message = await createUrlTextMessage(plainText || '', urls);
|
|
56
95
|
} else if (hasText) {
|
|
57
96
|
message = await createTextMessage(plainText);
|
|
@@ -68,27 +107,67 @@ export const useSendMessage = () => {
|
|
|
68
107
|
flow,
|
|
69
108
|
};
|
|
70
109
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
recvID
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
await dispatchSend(
|
|
111
|
+
message,
|
|
112
|
+
recvID || '',
|
|
113
|
+
groupID || '',
|
|
114
|
+
extendMessageInfo,
|
|
115
|
+
plainText || ''
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
[applicationType, dispatchSend]
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const retryMessage = useCallback(
|
|
123
|
+
async (message: MessageItem) => {
|
|
124
|
+
const { userID: recvID, groupID } =
|
|
125
|
+
useConversationStore.getState().getCurrentConversation() || {};
|
|
126
|
+
if (!recvID && !groupID) return;
|
|
127
|
+
|
|
128
|
+
updateOneMessage({ ...message, status: MessageStatus.Sending });
|
|
129
|
+
|
|
130
|
+
// Attachment messages were sent via sendMessageNotOss (URL already uploaded).
|
|
131
|
+
// Text/URL messages use sendMessage (SDK handles upload).
|
|
132
|
+
const isAttachment = (
|
|
133
|
+
[
|
|
134
|
+
MessageType.PictureMessage,
|
|
135
|
+
MessageType.VideoMessage,
|
|
136
|
+
MessageType.FileMessage,
|
|
137
|
+
MessageType.MergeMessage,
|
|
138
|
+
] as MessageType[]
|
|
139
|
+
).includes(message.contentType);
|
|
140
|
+
|
|
141
|
+
const params = {
|
|
142
|
+
recvID: recvID || '',
|
|
143
|
+
groupID: groupID || '',
|
|
144
|
+
message,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const sent = isAttachment
|
|
149
|
+
? await OpenIMSDK.sendMessageNotOss(params)
|
|
150
|
+
: await OpenIMSDK.sendMessage(
|
|
151
|
+
buildSendParams(
|
|
152
|
+
message,
|
|
153
|
+
recvID || '',
|
|
154
|
+
groupID || '',
|
|
155
|
+
JSON.parse(message.ex || '{}') as IMessageItemEx,
|
|
156
|
+
''
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
deleteOneMessage(message.clientMsgID);
|
|
160
|
+
pushNewMessage(sent);
|
|
161
|
+
} catch {
|
|
162
|
+
updateOneMessage({ ...message, status: MessageStatus.Failed });
|
|
85
163
|
}
|
|
86
164
|
},
|
|
87
|
-
[
|
|
165
|
+
[pushNewMessage, updateOneMessage, deleteOneMessage]
|
|
88
166
|
);
|
|
89
167
|
|
|
90
168
|
return {
|
|
91
169
|
sendMessage,
|
|
170
|
+
retryMessage,
|
|
92
171
|
};
|
|
93
172
|
};
|
|
94
173
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import ImagePicker from 'react-native-image-crop-picker';
|
|
3
|
+
import {
|
|
4
|
+
useImageAttachment,
|
|
5
|
+
type ImageWithUploadStatus,
|
|
6
|
+
} from './useImageAttachment';
|
|
7
|
+
import { useFileAttachment } from './useFileAttachment';
|
|
8
|
+
import { useLinkPreview } from './useLinkPreview/useLinkPreview';
|
|
9
|
+
import { useSendAttachment } from './useSendAttachment';
|
|
10
|
+
import { useSendMessage } from './message/useSendMessage';
|
|
11
|
+
import { isVideoFromPicker } from '../utils/imageUtils';
|
|
12
|
+
import {
|
|
13
|
+
generateAndUploadVideoThumbnails,
|
|
14
|
+
type VideoWithUploadStatus,
|
|
15
|
+
} from '../utils/videoThumbnailUpload';
|
|
16
|
+
|
|
17
|
+
export function useChatCompose() {
|
|
18
|
+
const [text, setText] = useState('');
|
|
19
|
+
|
|
20
|
+
const imageAttachment = useImageAttachment();
|
|
21
|
+
const fileAttachment = useFileAttachment();
|
|
22
|
+
const linkPreview = useLinkPreview(text);
|
|
23
|
+
const { send: sendAttachments } = useSendAttachment();
|
|
24
|
+
const { sendMessage } = useSendMessage();
|
|
25
|
+
|
|
26
|
+
const isUploading = useMemo(
|
|
27
|
+
() =>
|
|
28
|
+
imageAttachment.selectedImages.some(
|
|
29
|
+
(img: ImageWithUploadStatus) => img.isUploading
|
|
30
|
+
) || fileAttachment.selectedFiles.some((f) => f.isUploading || !!f.error),
|
|
31
|
+
[imageAttachment.selectedImages, fileAttachment.selectedFiles]
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const canSend = useMemo(
|
|
35
|
+
() =>
|
|
36
|
+
(text.trim().length > 0 ||
|
|
37
|
+
imageAttachment.selectedImages.length > 0 ||
|
|
38
|
+
fileAttachment.selectedFiles.length > 0) &&
|
|
39
|
+
!isUploading,
|
|
40
|
+
[
|
|
41
|
+
text,
|
|
42
|
+
imageAttachment.selectedImages,
|
|
43
|
+
fileAttachment.selectedFiles,
|
|
44
|
+
isUploading,
|
|
45
|
+
]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const send = useCallback(async () => {
|
|
49
|
+
const { selectedImages, clearAllImages } = imageAttachment;
|
|
50
|
+
const { selectedFiles, clearAllFiles } = fileAttachment;
|
|
51
|
+
const urlMeta = linkPreview.isVisible ? linkPreview.metadata : undefined;
|
|
52
|
+
|
|
53
|
+
// --- Attachments ---
|
|
54
|
+
if (selectedImages.length > 0 || selectedFiles.length > 0) {
|
|
55
|
+
const actualImages = selectedImages.filter(
|
|
56
|
+
(img: ImageWithUploadStatus) => !isVideoFromPicker(img.image)
|
|
57
|
+
);
|
|
58
|
+
const videos = selectedImages.filter((img: ImageWithUploadStatus) =>
|
|
59
|
+
isVideoFromPicker(img.image)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const uploadedImages = actualImages.filter(
|
|
63
|
+
(img: ImageWithUploadStatus) => img.uploadedUrl
|
|
64
|
+
);
|
|
65
|
+
const uploadedVideos = videos.filter(
|
|
66
|
+
(v) => v.uploadedUrl
|
|
67
|
+
) as unknown as VideoWithUploadStatus[];
|
|
68
|
+
|
|
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
|
+
);
|
|
75
|
+
let videoThumbnails: Record<string, { url: string; size: number }> = {};
|
|
76
|
+
if (videosMissingThumbnail.length > 0) {
|
|
77
|
+
videoThumbnails = await generateAndUploadVideoThumbnails(
|
|
78
|
+
videosMissingThumbnail
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Send images + videos
|
|
83
|
+
if (uploadedImages.length > 0 || uploadedVideos.length > 0) {
|
|
84
|
+
const attachmentItems = [
|
|
85
|
+
...uploadedImages.map((img: ImageWithUploadStatus) => ({
|
|
86
|
+
...img,
|
|
87
|
+
type: 'image' as const,
|
|
88
|
+
})),
|
|
89
|
+
...uploadedVideos.map(
|
|
90
|
+
(vid: VideoWithUploadStatus, vidIdx: number) => {
|
|
91
|
+
const videoId = vid.uploadedUrl || `video-${vidIdx}`;
|
|
92
|
+
const thumb = videoThumbnails[videoId];
|
|
93
|
+
return {
|
|
94
|
+
type: 'video' as const,
|
|
95
|
+
video: {
|
|
96
|
+
path: vid.image.path,
|
|
97
|
+
filename: vid.image.filename ?? undefined,
|
|
98
|
+
size: vid.image.size ?? undefined,
|
|
99
|
+
width: vid.image.width ?? undefined,
|
|
100
|
+
height: vid.image.height ?? undefined,
|
|
101
|
+
duration: vid.image.duration ?? undefined,
|
|
102
|
+
},
|
|
103
|
+
uploadProgress: vid.uploadProgress,
|
|
104
|
+
error: vid.error,
|
|
105
|
+
isUploading: vid.isUploading,
|
|
106
|
+
uploadedUrl: vid.uploadedUrl,
|
|
107
|
+
thumbnailUrl: vid.thumbnailUrl ?? thumb?.url,
|
|
108
|
+
thumbnailSize: vid.thumbnailSize ?? thumb?.size,
|
|
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);
|
|
121
|
+
}
|
|
122
|
+
|
|
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.
|
|
125
|
+
const uploadedFiles = selectedFiles.filter(
|
|
126
|
+
(f) => f.uploadedUrl && !f.error
|
|
127
|
+
);
|
|
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
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// --- Text / link preview ---
|
|
149
|
+
if (text.trim() || urlMeta) {
|
|
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.
|
|
153
|
+
setText('');
|
|
154
|
+
await sendMessage({ files: [], plainText, urlMetadata: urlMeta });
|
|
155
|
+
}
|
|
156
|
+
}, [
|
|
157
|
+
imageAttachment,
|
|
158
|
+
fileAttachment,
|
|
159
|
+
linkPreview.isVisible,
|
|
160
|
+
linkPreview.metadata,
|
|
161
|
+
sendAttachments,
|
|
162
|
+
sendMessage,
|
|
163
|
+
text,
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
// Text
|
|
168
|
+
text,
|
|
169
|
+
setText,
|
|
170
|
+
// Attachments
|
|
171
|
+
selectedImages: imageAttachment.selectedImages,
|
|
172
|
+
selectedFiles: fileAttachment.selectedFiles,
|
|
173
|
+
isImagePreviewVisible: imageAttachment.isImagePreviewVisible,
|
|
174
|
+
isFilePreviewVisible: fileAttachment.isFilePreviewVisible,
|
|
175
|
+
addImages: imageAttachment.handleImageAttach,
|
|
176
|
+
addFiles: fileAttachment.handleFileAttach,
|
|
177
|
+
removeImage: imageAttachment.handleImageRemove,
|
|
178
|
+
removeFile: fileAttachment.handleFileRemove,
|
|
179
|
+
// Link preview
|
|
180
|
+
showLinkPreview: linkPreview.isVisible,
|
|
181
|
+
urlMetadata: linkPreview.metadata,
|
|
182
|
+
isMetadataLoading: linkPreview.isLoading,
|
|
183
|
+
dismissPreview: linkPreview.dismiss,
|
|
184
|
+
// Status
|
|
185
|
+
isUploading,
|
|
186
|
+
canSend,
|
|
187
|
+
// Actions
|
|
188
|
+
send,
|
|
189
|
+
clearAll: () => {
|
|
190
|
+
imageAttachment.clearAllImages();
|
|
191
|
+
fileAttachment.clearAllFiles();
|
|
192
|
+
setText('');
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|