@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.
Files changed (178) hide show
  1. package/lib/module/assets/images/btn_resend.png +0 -0
  2. package/lib/module/assets/images/index.js +2 -1
  3. package/lib/module/assets/images/index.js.map +1 -1
  4. package/lib/module/components/AttachmentPreview/FilePreview.js +1 -1
  5. package/lib/module/components/AttachmentPreview/FilePreview.js.map +1 -1
  6. package/lib/module/components/flows/inputButtons/index.js +3 -6
  7. package/lib/module/components/flows/inputButtons/index.js.map +1 -1
  8. package/lib/module/components/messages/MessageStatusIndicator.js +70 -0
  9. package/lib/module/components/messages/MessageStatusIndicator.js.map +1 -0
  10. package/lib/module/components/messages/fileMessage/index.js +1 -1
  11. package/lib/module/components/messages/fileMessage/index.js.map +1 -1
  12. package/lib/module/components/messages/imageMessage/index.js +60 -51
  13. package/lib/module/components/messages/imageMessage/index.js.map +1 -1
  14. package/lib/module/components/messages/linkMessage/index.js +17 -9
  15. package/lib/module/components/messages/linkMessage/index.js.map +1 -1
  16. package/lib/module/components/messages/mergedMessage/index.js +5 -8
  17. package/lib/module/components/messages/mergedMessage/index.js.map +1 -1
  18. package/lib/module/components/messages/styles.js +12 -0
  19. package/lib/module/components/messages/styles.js.map +1 -1
  20. package/lib/module/components/messages/textMessage/index.js +20 -9
  21. package/lib/module/components/messages/textMessage/index.js.map +1 -1
  22. package/lib/module/components/messages/videoMessage/index.js +7 -11
  23. package/lib/module/components/messages/videoMessage/index.js.map +1 -1
  24. package/lib/module/config/uploadLimits.js +1 -1
  25. package/lib/module/config/uploadLimits.js.map +1 -1
  26. package/lib/module/core/useChatListener.js +35 -4
  27. package/lib/module/core/useChatListener.js.map +1 -1
  28. package/lib/module/core/useUserListener.js +17 -7
  29. package/lib/module/core/useUserListener.js.map +1 -1
  30. package/lib/module/hooks/message/useSendMessage.js +62 -15
  31. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  32. package/lib/module/hooks/useChatCompose.js +39 -28
  33. package/lib/module/hooks/useChatCompose.js.map +1 -1
  34. package/lib/module/hooks/useChatMessages.js +39 -8
  35. package/lib/module/hooks/useChatMessages.js.map +1 -1
  36. package/lib/module/hooks/useFileAttachment.js +1 -1
  37. package/lib/module/hooks/useFileAttachment.js.map +1 -1
  38. package/lib/module/hooks/useFileDownload.js +1 -1
  39. package/lib/module/hooks/useFileDownload.js.map +1 -1
  40. package/lib/module/hooks/useImageAttachment.js +12 -3
  41. package/lib/module/hooks/useImageAttachment.js.map +1 -1
  42. package/lib/module/index.js +1 -0
  43. package/lib/module/index.js.map +1 -1
  44. package/lib/module/screens/chat-detail/ChatDetail.js +24 -1
  45. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  46. package/lib/module/screens/chat-detail/ChatListLegend.js +40 -28
  47. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  48. package/lib/module/screens/chat-detail/constants.js +3 -2
  49. package/lib/module/screens/chat-detail/constants.js.map +1 -1
  50. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +48 -15
  51. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  52. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js +98 -0
  53. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js.map +1 -0
  54. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +110 -0
  55. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -0
  56. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js +64 -0
  57. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js.map +1 -0
  58. package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js +37 -0
  59. package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js.map +1 -0
  60. package/lib/module/screens/chat-detail/messageToolbox/types.js +4 -0
  61. package/lib/module/screens/chat-detail/messageToolbox/types.js.map +1 -0
  62. package/lib/module/services/attachmentOrchestrator.js +34 -12
  63. package/lib/module/services/attachmentOrchestrator.js.map +1 -1
  64. package/lib/module/services/message.js +13 -5
  65. package/lib/module/services/message.js.map +1 -1
  66. package/lib/module/translation/resources/i18n.js +9 -3
  67. package/lib/module/translation/resources/i18n.js.map +1 -1
  68. package/lib/module/types/chat.js.map +1 -1
  69. package/lib/module/types/global.d.js +2 -0
  70. package/lib/module/types/global.d.js.map +1 -0
  71. package/lib/module/types/index.d.js +3 -0
  72. package/lib/module/types/index.d.js.map +1 -0
  73. package/lib/module/utils/UIUtils.js +37 -0
  74. package/lib/module/utils/UIUtils.js.map +1 -0
  75. package/lib/module/utils/ui.js +15 -13
  76. package/lib/module/utils/ui.js.map +1 -1
  77. package/lib/module/utils/videoThumbnailUpload.js +1 -1
  78. package/lib/module/utils/videoThumbnailUpload.js.map +1 -1
  79. package/lib/typescript/src/assets/images/index.d.ts +1 -0
  80. package/lib/typescript/src/assets/images/index.d.ts.map +1 -1
  81. package/lib/typescript/src/components/flows/inputButtons/index.d.ts.map +1 -1
  82. package/lib/typescript/src/components/messages/MessageStatusIndicator.d.ts +9 -0
  83. package/lib/typescript/src/components/messages/MessageStatusIndicator.d.ts.map +1 -0
  84. package/lib/typescript/src/components/messages/fileMessage/index.d.ts.map +1 -1
  85. package/lib/typescript/src/components/messages/imageMessage/index.d.ts.map +1 -1
  86. package/lib/typescript/src/components/messages/linkMessage/index.d.ts +3 -8
  87. package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
  88. package/lib/typescript/src/components/messages/mergedMessage/index.d.ts.map +1 -1
  89. package/lib/typescript/src/components/messages/styles.d.ts +15 -3
  90. package/lib/typescript/src/components/messages/styles.d.ts.map +1 -1
  91. package/lib/typescript/src/components/messages/textMessage/index.d.ts +2 -8
  92. package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
  93. package/lib/typescript/src/components/messages/videoMessage/index.d.ts.map +1 -1
  94. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  95. package/lib/typescript/src/core/useUserListener.d.ts.map +1 -1
  96. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +2 -0
  97. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  98. package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -1
  99. package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
  100. package/lib/typescript/src/hooks/useImageAttachment.d.ts.map +1 -1
  101. package/lib/typescript/src/index.d.ts +1 -0
  102. package/lib/typescript/src/index.d.ts.map +1 -1
  103. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  104. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +1 -1
  105. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  106. package/lib/typescript/src/screens/chat-detail/constants.d.ts +2 -2
  107. package/lib/typescript/src/screens/chat-detail/constants.d.ts.map +1 -1
  108. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +4 -5
  109. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  110. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts +11 -0
  111. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts.map +1 -0
  112. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts +10 -0
  113. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -0
  114. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts +11 -0
  115. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts.map +1 -0
  116. package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts +18 -0
  117. package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts.map +1 -0
  118. package/lib/typescript/src/screens/chat-detail/messageToolbox/types.d.ts +12 -0
  119. package/lib/typescript/src/screens/chat-detail/messageToolbox/types.d.ts.map +1 -0
  120. package/lib/typescript/src/screens/chat-detail/types.d.ts +2 -0
  121. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  122. package/lib/typescript/src/services/attachmentOrchestrator.d.ts.map +1 -1
  123. package/lib/typescript/src/services/message.d.ts +7 -0
  124. package/lib/typescript/src/services/message.d.ts.map +1 -1
  125. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  126. package/lib/typescript/src/types/chat.d.ts +11 -0
  127. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  128. package/lib/typescript/src/utils/UIUtils.d.ts +4 -0
  129. package/lib/typescript/src/utils/UIUtils.d.ts.map +1 -0
  130. package/lib/typescript/src/utils/ui.d.ts +1 -12
  131. package/lib/typescript/src/utils/ui.d.ts.map +1 -1
  132. package/package.json +4 -2
  133. package/src/assets/images/btn_resend.png +0 -0
  134. package/src/assets/images/index.ts +1 -0
  135. package/src/components/AttachmentPreview/FilePreview.tsx +1 -1
  136. package/src/components/flows/inputButtons/index.tsx +3 -5
  137. package/src/components/messages/MessageStatusIndicator.tsx +82 -0
  138. package/src/components/messages/fileMessage/index.tsx +0 -2
  139. package/src/components/messages/imageMessage/index.tsx +72 -58
  140. package/src/components/messages/linkMessage/index.tsx +16 -21
  141. package/src/components/messages/mergedMessage/index.tsx +13 -18
  142. package/src/components/messages/styles.ts +12 -0
  143. package/src/components/messages/textMessage/index.tsx +20 -20
  144. package/src/components/messages/videoMessage/index.tsx +8 -11
  145. package/src/config/uploadLimits.ts +1 -1
  146. package/src/core/useChatListener.ts +38 -4
  147. package/src/core/useUserListener.ts +17 -7
  148. package/src/hooks/message/useSendMessage.ts +103 -24
  149. package/src/hooks/useChatCompose.ts +42 -33
  150. package/src/hooks/useChatMessages.ts +54 -10
  151. package/src/hooks/useFileAttachment.ts +1 -1
  152. package/src/hooks/useFileDownload.ts +1 -1
  153. package/src/hooks/useImageAttachment.ts +13 -5
  154. package/src/index.tsx +2 -0
  155. package/src/screens/chat-detail/ChatDetail.tsx +29 -0
  156. package/src/screens/chat-detail/ChatListLegend.tsx +45 -28
  157. package/src/screens/chat-detail/constants.ts +3 -2
  158. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +60 -15
  159. package/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.tsx +114 -0
  160. package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +130 -0
  161. package/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx +99 -0
  162. package/src/screens/chat-detail/messageToolbox/messageToolbox.utils.ts +56 -0
  163. package/src/screens/chat-detail/messageToolbox/types.ts +13 -0
  164. package/src/screens/chat-detail/types.ts +2 -0
  165. package/src/services/attachmentOrchestrator.ts +45 -21
  166. package/src/services/message.ts +21 -4
  167. package/src/translation/resources/i18n.ts +11 -3
  168. package/src/types/chat.ts +6 -0
  169. package/src/types/global.d.ts +299 -0
  170. package/src/types/index.d.ts +1 -0
  171. package/src/utils/UIUtils.ts +31 -0
  172. package/src/utils/ui.ts +20 -16
  173. package/src/utils/videoThumbnailUpload.ts +1 -1
  174. package/lib/module/hooks/useVideoAttachment.js +0 -251
  175. package/lib/module/hooks/useVideoAttachment.js.map +0 -1
  176. package/lib/typescript/src/hooks/useVideoAttachment.d.ts +0 -29
  177. package/lib/typescript/src/hooks/useVideoAttachment.d.ts.map +0 -1
  178. 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, { type MessageItem } from '@droppii/openim-rn-client-sdk';
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
- for (let i = 0; i < allMessages.length; i++) {
271
- const message = allMessages[i] as MessageItem;
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
- const result = await OpenIMSDK.sendMessageNotOss({
280
- recvID: currentConversation.userID || '',
281
- groupID: currentConversation.groupID || '',
282
- message,
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
- lastMsgId = result?.clientMsgID || result?.serverMsgID || '';
286
- console.log('[AttachmentOrchestrator] Message sent:', {
287
- messageId: lastMsgId,
288
- index: i + 1,
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
- if (result) {
292
- useMessageStore.getState().pushNewMessage(result);
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
  }
@@ -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.getAdvancedHistoryMessageList({
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
- // ...(options?.startClientMsgID
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': 'Ảnh quá lớn. Tối đa 5MB',
15
- 'chat:video_too_large': 'Video quá lớn. Tối đa 200MB',
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': 'Lỗi tải ảnh',
19
+ 'chat:image_upload_failed': ' 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;