@droppii-org/chat-mobile 0.2.24 → 0.2.26
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/flows/inputButtons/index.js +3 -6
- package/lib/module/components/flows/inputButtons/index.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 +1 -1
- package/lib/module/components/messages/fileMessage/index.js.map +1 -1
- package/lib/module/components/messages/imageMessage/index.js +60 -51
- package/lib/module/components/messages/imageMessage/index.js.map +1 -1
- package/lib/module/components/messages/linkMessage/index.js +17 -9
- package/lib/module/components/messages/linkMessage/index.js.map +1 -1
- package/lib/module/components/messages/mergedMessage/index.js +5 -8
- package/lib/module/components/messages/mergedMessage/index.js.map +1 -1
- package/lib/module/components/messages/styles.js +12 -0
- package/lib/module/components/messages/styles.js.map +1 -1
- package/lib/module/components/messages/textMessage/index.js +20 -9
- package/lib/module/components/messages/textMessage/index.js.map +1 -1
- package/lib/module/components/messages/videoMessage/index.js +7 -11
- 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 +24 -1
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +40 -28
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/constants.js +3 -2
- package/lib/module/screens/chat-detail/constants.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +48 -15
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js +98 -0
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js.map +1 -0
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +110 -0
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -0
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js +64 -0
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js.map +1 -0
- package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js +37 -0
- package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js.map +1 -0
- package/lib/module/screens/chat-detail/messageToolbox/types.js +4 -0
- package/lib/module/screens/chat-detail/messageToolbox/types.js.map +1 -0
- 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 +9 -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/ui.js +15 -13
- package/lib/module/utils/ui.js.map +1 -1
- 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/flows/inputButtons/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/styles.d.ts +15 -3
- package/lib/typescript/src/components/messages/styles.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/constants.d.ts +2 -2
- package/lib/typescript/src/screens/chat-detail/constants.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +4 -5
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts +11 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts +10 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts +11 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts +18 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/types.d.ts +12 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/types.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/types.d.ts +2 -0
- package/lib/typescript/src/screens/chat-detail/types.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/lib/typescript/src/utils/ui.d.ts +1 -12
- package/lib/typescript/src/utils/ui.d.ts.map +1 -1
- package/package.json +4 -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/flows/inputButtons/index.tsx +3 -5
- package/src/components/messages/MessageStatusIndicator.tsx +82 -0
- package/src/components/messages/fileMessage/index.tsx +0 -2
- package/src/components/messages/imageMessage/index.tsx +72 -58
- package/src/components/messages/linkMessage/index.tsx +16 -21
- package/src/components/messages/mergedMessage/index.tsx +13 -18
- package/src/components/messages/styles.ts +12 -0
- package/src/components/messages/textMessage/index.tsx +20 -20
- package/src/components/messages/videoMessage/index.tsx +8 -11
- 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 +29 -0
- package/src/screens/chat-detail/ChatListLegend.tsx +45 -28
- package/src/screens/chat-detail/constants.ts +3 -2
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +60 -15
- package/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.tsx +114 -0
- package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +130 -0
- package/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx +99 -0
- package/src/screens/chat-detail/messageToolbox/messageToolbox.utils.ts +56 -0
- package/src/screens/chat-detail/messageToolbox/types.ts +13 -0
- package/src/screens/chat-detail/types.ts +2 -0
- package/src/services/attachmentOrchestrator.ts +45 -21
- package/src/services/message.ts +21 -4
- package/src/translation/resources/i18n.ts +11 -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/ui.ts +20 -16
- 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/typescript/src/hooks/useVideoAttachment.d.ts +0 -29
- package/lib/typescript/src/hooks/useVideoAttachment.d.ts.map +0 -1
- package/src/hooks/useVideoAttachment.ts +0 -334
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
KContainer,
|
|
5
|
+
KImage,
|
|
6
|
+
KLabel,
|
|
7
|
+
KColors,
|
|
8
|
+
KSpacingValue,
|
|
9
|
+
} from '@droppii/libs';
|
|
10
|
+
import { trans } from '../../../translation';
|
|
11
|
+
|
|
12
|
+
interface MessageToolboxMenuProps {
|
|
13
|
+
showCopyMessage?: boolean;
|
|
14
|
+
showCopyMessageLink?: boolean;
|
|
15
|
+
showRetryMessage?: boolean;
|
|
16
|
+
onCopyMessage: () => void;
|
|
17
|
+
onCopyMessageLink: () => void;
|
|
18
|
+
onRetryMessage: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const MessageToolboxMenu = memo(
|
|
22
|
+
({
|
|
23
|
+
showCopyMessage,
|
|
24
|
+
showCopyMessageLink,
|
|
25
|
+
showRetryMessage,
|
|
26
|
+
onCopyMessage,
|
|
27
|
+
onCopyMessageLink,
|
|
28
|
+
onRetryMessage,
|
|
29
|
+
}: MessageToolboxMenuProps) => {
|
|
30
|
+
if (!showCopyMessage && !showCopyMessageLink && !showRetryMessage) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<KContainer.View style={styles.card}>
|
|
36
|
+
<KContainer.VisibleView visible={!!showCopyMessage}>
|
|
37
|
+
<KContainer.Touchable style={styles.row} onPress={onCopyMessage}>
|
|
38
|
+
<KImage.VectorIcons
|
|
39
|
+
name="copy-o"
|
|
40
|
+
provider="DroppiiNew"
|
|
41
|
+
size={24}
|
|
42
|
+
color={KColors.gray.dark}
|
|
43
|
+
/>
|
|
44
|
+
<KLabel.Text
|
|
45
|
+
typo="TextMdMedium"
|
|
46
|
+
color={KColors.gray.dark}
|
|
47
|
+
marginL="0.75rem"
|
|
48
|
+
>
|
|
49
|
+
{trans('chat:copy_message')}
|
|
50
|
+
</KLabel.Text>
|
|
51
|
+
</KContainer.Touchable>
|
|
52
|
+
</KContainer.VisibleView>
|
|
53
|
+
|
|
54
|
+
<KContainer.VisibleView visible={!!showCopyMessageLink}>
|
|
55
|
+
<KContainer.Touchable style={styles.row} onPress={onCopyMessageLink}>
|
|
56
|
+
<KImage.VectorIcons
|
|
57
|
+
name="link-o"
|
|
58
|
+
provider="DroppiiNew"
|
|
59
|
+
size={24}
|
|
60
|
+
color={KColors.gray.dark}
|
|
61
|
+
/>
|
|
62
|
+
<KLabel.Text
|
|
63
|
+
typo="TextMdMedium"
|
|
64
|
+
color={KColors.gray.dark}
|
|
65
|
+
marginL="0.75rem"
|
|
66
|
+
>
|
|
67
|
+
{trans('chat:copy_message_link')}
|
|
68
|
+
</KLabel.Text>
|
|
69
|
+
</KContainer.Touchable>
|
|
70
|
+
</KContainer.VisibleView>
|
|
71
|
+
|
|
72
|
+
<KContainer.VisibleView visible={!!showRetryMessage}>
|
|
73
|
+
<KContainer.Touchable style={styles.row} onPress={onRetryMessage}>
|
|
74
|
+
<KImage.VectorIcons
|
|
75
|
+
name="arrow-reload-o"
|
|
76
|
+
provider="DroppiiNew"
|
|
77
|
+
size={24}
|
|
78
|
+
color={KColors.danger.normal}
|
|
79
|
+
/>
|
|
80
|
+
<KLabel.Text
|
|
81
|
+
typo="TextMdMedium"
|
|
82
|
+
color={KColors.danger.normal}
|
|
83
|
+
marginL="0.75rem"
|
|
84
|
+
>
|
|
85
|
+
{trans('chat:retry_message')}
|
|
86
|
+
</KLabel.Text>
|
|
87
|
+
</KContainer.Touchable>
|
|
88
|
+
</KContainer.VisibleView>
|
|
89
|
+
</KContainer.View>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
MessageToolboxMenu.displayName = 'MessageToolboxMenu';
|
|
95
|
+
|
|
96
|
+
const styles = StyleSheet.create({
|
|
97
|
+
card: {
|
|
98
|
+
minWidth: 220,
|
|
99
|
+
paddingVertical: KSpacingValue['0.5rem'],
|
|
100
|
+
borderRadius: KSpacingValue['1rem'],
|
|
101
|
+
backgroundColor: KColors.white,
|
|
102
|
+
shadowColor: KColors.black,
|
|
103
|
+
shadowOffset: { width: 0, height: 0 },
|
|
104
|
+
shadowOpacity: 0.1,
|
|
105
|
+
shadowRadius: 24,
|
|
106
|
+
elevation: 8,
|
|
107
|
+
},
|
|
108
|
+
row: {
|
|
109
|
+
flexDirection: 'row',
|
|
110
|
+
alignItems: 'center',
|
|
111
|
+
paddingHorizontal: KSpacingValue['1rem'],
|
|
112
|
+
paddingVertical: KSpacingValue['0.5rem'],
|
|
113
|
+
},
|
|
114
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
|
|
2
|
+
import { Modal, Pressable, StyleSheet } from 'react-native';
|
|
3
|
+
import Clipboard from '@react-native-clipboard/clipboard';
|
|
4
|
+
import { UIUtils } from '../../../utils/UIUtils';
|
|
5
|
+
import { trans } from '../../../translation';
|
|
6
|
+
import { LegendChatMessage } from '../legend/LegendChatMessage';
|
|
7
|
+
import { MessageToolboxMenu } from './MessageToolboxMenu';
|
|
8
|
+
import {
|
|
9
|
+
getCopyableMessageText,
|
|
10
|
+
useMessageToolboxItems,
|
|
11
|
+
} from './messageToolbox.utils';
|
|
12
|
+
import type { DMessageItem } from '../../../types/chat';
|
|
13
|
+
import type { MeasuredRect, MessageToolboxRef } from './types';
|
|
14
|
+
import { MessageToolboxWrapper } from './MessageToolboxWrapper';
|
|
15
|
+
import type { ReactNode } from 'react';
|
|
16
|
+
import { useSendMessage } from '../../../hooks/message/useSendMessage';
|
|
17
|
+
|
|
18
|
+
interface MessageToolboxOverlayProps {
|
|
19
|
+
currentUserId?: string;
|
|
20
|
+
customMessageItemFactory?: (item: DMessageItem) => ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const MessageToolboxOverlay = forwardRef<
|
|
24
|
+
MessageToolboxRef,
|
|
25
|
+
MessageToolboxOverlayProps
|
|
26
|
+
>(({ currentUserId = '', customMessageItemFactory }, ref) => {
|
|
27
|
+
const [visible, setVisible] = useState(false);
|
|
28
|
+
const [message, setMessage] = useState<DMessageItem | undefined>(undefined);
|
|
29
|
+
const [rect, setRect] = useState<MeasuredRect | null>(null);
|
|
30
|
+
const toolboxItems = useMessageToolboxItems(message);
|
|
31
|
+
const { retryMessage } = useSendMessage();
|
|
32
|
+
console.log(message);
|
|
33
|
+
|
|
34
|
+
const hide = useCallback(() => {
|
|
35
|
+
setVisible(false);
|
|
36
|
+
setMessage(undefined);
|
|
37
|
+
setRect(null);
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
const show = useCallback((msg: DMessageItem, msgRect: MeasuredRect) => {
|
|
41
|
+
setMessage(msg);
|
|
42
|
+
setRect(msgRect);
|
|
43
|
+
setVisible(true);
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
useImperativeHandle(ref, () => ({ show, hide }), [show, hide]);
|
|
47
|
+
|
|
48
|
+
const handleCopyMessage = useCallback(() => {
|
|
49
|
+
if (!message) return;
|
|
50
|
+
const text = getCopyableMessageText(message);
|
|
51
|
+
if (text) {
|
|
52
|
+
Clipboard.setString(text);
|
|
53
|
+
UIUtils.toast.open({
|
|
54
|
+
title: trans('chat:copy_message_success'),
|
|
55
|
+
theme: 'success',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
hide();
|
|
59
|
+
}, [message, hide]);
|
|
60
|
+
|
|
61
|
+
const handleCopyMessageLink = useCallback(() => {
|
|
62
|
+
if (!message) return;
|
|
63
|
+
// UI-only for now: real shareable deeplink format (BR-001) is not decided yet.
|
|
64
|
+
const placeholderLink = `${message.recvID || message.groupID}:${message.clientMsgID ?? message.serverMsgID ?? ''}`;
|
|
65
|
+
Clipboard.setString(placeholderLink);
|
|
66
|
+
UIUtils.toast.open({
|
|
67
|
+
title: trans('chat:copy_message_link_success'),
|
|
68
|
+
theme: 'success',
|
|
69
|
+
});
|
|
70
|
+
hide();
|
|
71
|
+
}, [message, hide]);
|
|
72
|
+
|
|
73
|
+
const handleRetryMessage = useCallback(() => {
|
|
74
|
+
if (!message) return;
|
|
75
|
+
retryMessage(message);
|
|
76
|
+
hide();
|
|
77
|
+
}, [message, hide, retryMessage]);
|
|
78
|
+
|
|
79
|
+
if (!visible || !message || !rect) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isOutgoing = message.sendID === currentUserId;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Modal
|
|
87
|
+
visible={visible}
|
|
88
|
+
transparent
|
|
89
|
+
statusBarTranslucent
|
|
90
|
+
animationType="fade"
|
|
91
|
+
onRequestClose={hide}
|
|
92
|
+
>
|
|
93
|
+
<Pressable style={styles.backdrop} onPress={hide} />
|
|
94
|
+
|
|
95
|
+
<MessageToolboxWrapper
|
|
96
|
+
rect={rect}
|
|
97
|
+
isOutgoing={isOutgoing}
|
|
98
|
+
message={
|
|
99
|
+
<LegendChatMessage
|
|
100
|
+
message={message}
|
|
101
|
+
isOutgoing={isOutgoing}
|
|
102
|
+
createdAtTime={message.sendTime || message.createTime}
|
|
103
|
+
customMessageItemFactory={customMessageItemFactory}
|
|
104
|
+
/>
|
|
105
|
+
}
|
|
106
|
+
toolbox={
|
|
107
|
+
<MessageToolboxMenu
|
|
108
|
+
{...toolboxItems}
|
|
109
|
+
onCopyMessage={handleCopyMessage}
|
|
110
|
+
onCopyMessageLink={handleCopyMessageLink}
|
|
111
|
+
onRetryMessage={handleRetryMessage}
|
|
112
|
+
/>
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
</Modal>
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
MessageToolboxOverlay.displayName = 'MessageToolboxOverlay';
|
|
120
|
+
|
|
121
|
+
const styles = StyleSheet.create({
|
|
122
|
+
backdrop: {
|
|
123
|
+
position: 'absolute',
|
|
124
|
+
top: 0,
|
|
125
|
+
left: 0,
|
|
126
|
+
right: 0,
|
|
127
|
+
bottom: 0,
|
|
128
|
+
backgroundColor: 'rgba(0,0,0,0.75)',
|
|
129
|
+
},
|
|
130
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useCallback, memo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Platform,
|
|
5
|
+
useWindowDimensions,
|
|
6
|
+
type LayoutChangeEvent,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import {
|
|
9
|
+
useAnimatedStyle,
|
|
10
|
+
useDerivedValue,
|
|
11
|
+
useSharedValue,
|
|
12
|
+
withTiming,
|
|
13
|
+
} from 'react-native-reanimated';
|
|
14
|
+
import type { MeasuredRect } from './types';
|
|
15
|
+
import { messageStyles } from '../legend/messageTypes';
|
|
16
|
+
import Animated from 'react-native-reanimated';
|
|
17
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
18
|
+
|
|
19
|
+
const MENU_GAP = 8;
|
|
20
|
+
|
|
21
|
+
type MessageToolboxWrapperProps = {
|
|
22
|
+
isOutgoing?: boolean;
|
|
23
|
+
rect: MeasuredRect;
|
|
24
|
+
message?: React.ReactNode;
|
|
25
|
+
reaction?: React.ReactNode;
|
|
26
|
+
toolbox?: React.ReactNode;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const MessageToolboxWrapper = memo(
|
|
30
|
+
({
|
|
31
|
+
isOutgoing,
|
|
32
|
+
rect,
|
|
33
|
+
message,
|
|
34
|
+
reaction,
|
|
35
|
+
toolbox,
|
|
36
|
+
}: MessageToolboxWrapperProps) => {
|
|
37
|
+
const insets = useSafeAreaInsets();
|
|
38
|
+
const reactionHeight = useSharedValue(0);
|
|
39
|
+
const toolboxHeight = useSharedValue(0);
|
|
40
|
+
const topOffset = Platform.OS === 'android' ? insets.top : 0;
|
|
41
|
+
const { height: windowHeight } = useWindowDimensions();
|
|
42
|
+
|
|
43
|
+
const onToolboxLayout = useCallback(
|
|
44
|
+
({ nativeEvent }: LayoutChangeEvent) => {
|
|
45
|
+
toolboxHeight.value = nativeEvent.layout.height;
|
|
46
|
+
},
|
|
47
|
+
[toolboxHeight]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const onReactionLayout = useCallback(
|
|
51
|
+
({ nativeEvent }: LayoutChangeEvent) => {
|
|
52
|
+
reactionHeight.value = nativeEvent.layout.height;
|
|
53
|
+
},
|
|
54
|
+
[reactionHeight]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const translateY = useDerivedValue(() => {
|
|
58
|
+
const menuGap =
|
|
59
|
+
(reactionHeight.value > 0 ? MENU_GAP : 0) +
|
|
60
|
+
(toolboxHeight.value > 0 ? MENU_GAP : 0);
|
|
61
|
+
|
|
62
|
+
const overflowBottom =
|
|
63
|
+
Math.max(rect.y, insets.top) +
|
|
64
|
+
topOffset +
|
|
65
|
+
rect.height +
|
|
66
|
+
toolboxHeight.value +
|
|
67
|
+
(insets.bottom || 32) +
|
|
68
|
+
menuGap -
|
|
69
|
+
windowHeight;
|
|
70
|
+
|
|
71
|
+
return withTiming(Math.min(-overflowBottom, 0));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const containerStyles = useAnimatedStyle(() => ({
|
|
75
|
+
top: Math.max(rect.y, insets.top) + topOffset,
|
|
76
|
+
left: rect.x,
|
|
77
|
+
gap: MENU_GAP,
|
|
78
|
+
transform: [{ translateY: translateY.value }],
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Animated.View
|
|
83
|
+
pointerEvents="box-none"
|
|
84
|
+
style={[
|
|
85
|
+
isOutgoing
|
|
86
|
+
? messageStyles.messageRowRight
|
|
87
|
+
: messageStyles.messageRowLeft,
|
|
88
|
+
containerStyles,
|
|
89
|
+
]}
|
|
90
|
+
>
|
|
91
|
+
<View pointerEvents="none">{message}</View>
|
|
92
|
+
<View onLayout={onReactionLayout}>{reaction}</View>
|
|
93
|
+
<View onLayout={onToolboxLayout}>{toolbox}</View>
|
|
94
|
+
</Animated.View>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
MessageToolboxWrapper.displayName = 'MessageToolboxWrapper';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { MessageStatus, MessageType } from '@droppii/openim-rn-client-sdk';
|
|
2
|
+
import type { DMessageItem } from '../../../types/chat';
|
|
3
|
+
import { getMessageText } from '../../../utils/legendListMessage';
|
|
4
|
+
|
|
5
|
+
// Copy message content is only offered for text and link messages.
|
|
6
|
+
const COPYABLE_TEXT_TYPES: number[] = [
|
|
7
|
+
MessageType.TextMessage,
|
|
8
|
+
MessageType.AtTextMessage,
|
|
9
|
+
MessageType.QuoteMessage,
|
|
10
|
+
MessageType.UrlTextMessage,
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export const isMessageRecalled = (message: DMessageItem): boolean =>
|
|
14
|
+
message.contentType === MessageType.RevokeMessage;
|
|
15
|
+
|
|
16
|
+
export const isMessageSucceeded = (message: DMessageItem): boolean =>
|
|
17
|
+
message.status !== MessageStatus.Failed &&
|
|
18
|
+
message.status !== MessageStatus.Sending;
|
|
19
|
+
|
|
20
|
+
// Failed or recalled messages never offer either copy action.
|
|
21
|
+
export const isEligibleForToolbox = (message: DMessageItem): boolean =>
|
|
22
|
+
isMessageSucceeded(message) && !isMessageRecalled(message);
|
|
23
|
+
|
|
24
|
+
export const isCopyMessageSupported = (message: DMessageItem): boolean =>
|
|
25
|
+
isEligibleForToolbox(message) &&
|
|
26
|
+
COPYABLE_TEXT_TYPES.includes(message.contentType);
|
|
27
|
+
|
|
28
|
+
// All message types are supported, as long as the message is eligible.
|
|
29
|
+
export const isCopyMessageLinkSupported = (message: DMessageItem): boolean =>
|
|
30
|
+
isEligibleForToolbox(message);
|
|
31
|
+
|
|
32
|
+
export const isRetrySupported = (message: DMessageItem): boolean =>
|
|
33
|
+
message.status === MessageStatus.Failed;
|
|
34
|
+
|
|
35
|
+
export const getCopyableMessageText = (message: DMessageItem): string => {
|
|
36
|
+
if (message.contentType === MessageType.UrlTextMessage) {
|
|
37
|
+
return message.urlTextElem?.content ?? '';
|
|
38
|
+
}
|
|
39
|
+
return getMessageText(message);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const useMessageToolboxItems = (message?: DMessageItem) => {
|
|
43
|
+
if (message == null) {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const showCopyMessage = isCopyMessageSupported(message);
|
|
48
|
+
const showCopyMessageLink = isCopyMessageLinkSupported(message);
|
|
49
|
+
const showRetryMessage = isRetrySupported(message);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
showCopyMessage,
|
|
53
|
+
showCopyMessageLink,
|
|
54
|
+
showRetryMessage,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DMessageItem } from '../../../types/chat';
|
|
2
|
+
|
|
3
|
+
export interface MeasuredRect {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface MessageToolboxRef {
|
|
11
|
+
show: (message: DMessageItem, rect: MeasuredRect) => void;
|
|
12
|
+
hide: () => void;
|
|
13
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import type { MediaItem } from '../../hooks/useMediaViewer';
|
|
3
3
|
import type { DMessageItem } from '../../types/chat';
|
|
4
4
|
import type { ConversationItem } from '@droppii/openim-rn-client-sdk';
|
|
5
|
+
import type { MeasuredRect } from './messageToolbox/types';
|
|
5
6
|
|
|
6
7
|
export type DChatActionIconProvider = 'MaterialCommunityIcons' | 'DroppiiNew';
|
|
7
8
|
|
|
@@ -72,6 +73,7 @@ export interface ChatListProps {
|
|
|
72
73
|
hasMoreNewer?: boolean;
|
|
73
74
|
onMediaPress?: (items: MediaItem[], index?: number) => void;
|
|
74
75
|
onPressUrl?: (url: string) => void;
|
|
76
|
+
onLongPress?: (message: DMessageItem, rect: MeasuredRect) => void;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
export interface ChatDetailProps extends Omit<
|
|
@@ -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',
|
|
@@ -25,6 +28,11 @@ const i18nKey: Record<string, string> = {
|
|
|
25
28
|
'chat:retry_failed': 'Thử lại thất bại',
|
|
26
29
|
'chat:upload_in_progress': 'Đang tải ảnh lên',
|
|
27
30
|
'chat:wait_or_cancel': 'Vui lòng chờ hoặc hủy bỏ',
|
|
31
|
+
'chat:copy_message': 'Sao chép tin nhắn',
|
|
32
|
+
'chat:copy_message_link': 'Sao chép đường dẫn tin nhắn',
|
|
33
|
+
'chat:copy_message_success': 'Sao chép tin nhắn thành công',
|
|
34
|
+
'chat:copy_message_link_success': 'Sao chép đường dẫn tin nhắn thành công',
|
|
35
|
+
'chat:retry_message': 'Gửi lại',
|
|
28
36
|
'msg_type_image': '[Hình ảnh]',
|
|
29
37
|
'msg_type_voice': '[Tin nhắn thoại]',
|
|
30
38
|
'msg_type_video': '[Video]',
|
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;
|