@droppii-org/chat-mobile 0.2.27 → 0.2.28

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 (61) hide show
  1. package/lib/module/components/messages/revokedMessage/index.js +22 -0
  2. package/lib/module/components/messages/revokedMessage/index.js.map +1 -0
  3. package/lib/module/core/useChatListener.js +6 -1
  4. package/lib/module/core/useChatListener.js.map +1 -1
  5. package/lib/module/hooks/message/useSendMessage.js +15 -2
  6. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  7. package/lib/module/screens/chat-detail/constants.js +1 -1
  8. package/lib/module/screens/chat-detail/constants.js.map +1 -1
  9. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +3 -1
  10. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  11. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxItem.js +36 -0
  12. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxItem.js.map +1 -0
  13. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js +31 -57
  14. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js.map +1 -1
  15. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +27 -7
  16. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
  17. package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js +18 -9
  18. package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js.map +1 -1
  19. package/lib/module/store/message.js +9 -0
  20. package/lib/module/store/message.js.map +1 -1
  21. package/lib/module/translation/resources/i18n.js +2 -0
  22. package/lib/module/translation/resources/i18n.js.map +1 -1
  23. package/lib/module/utils/conversation.js +1 -1
  24. package/lib/module/utils/conversation.js.map +1 -1
  25. package/lib/module/utils/messageUtils.js +9 -14
  26. package/lib/module/utils/messageUtils.js.map +1 -1
  27. package/lib/typescript/src/components/messages/revokedMessage/index.d.ts +3 -0
  28. package/lib/typescript/src/components/messages/revokedMessage/index.d.ts.map +1 -0
  29. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  30. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +1 -0
  31. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  32. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  33. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxItem.d.ts +10 -0
  34. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxItem.d.ts.map +1 -0
  35. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts +3 -1
  36. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts.map +1 -1
  37. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
  38. package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts +3 -7
  39. package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts.map +1 -1
  40. package/lib/typescript/src/store/message.d.ts +1 -0
  41. package/lib/typescript/src/store/message.d.ts.map +1 -1
  42. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  43. package/lib/typescript/src/types/chat.d.ts +1 -1
  44. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  45. package/lib/typescript/src/utils/messageUtils.d.ts +1 -0
  46. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  47. package/package.json +1 -1
  48. package/src/components/messages/revokedMessage/index.tsx +23 -0
  49. package/src/core/useChatListener.ts +8 -1
  50. package/src/hooks/message/useSendMessage.ts +18 -1
  51. package/src/screens/chat-detail/constants.ts +1 -1
  52. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +2 -0
  53. package/src/screens/chat-detail/messageToolbox/MessageToolboxItem.tsx +38 -0
  54. package/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.tsx +36 -63
  55. package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +47 -10
  56. package/src/screens/chat-detail/messageToolbox/messageToolbox.utils.ts +28 -11
  57. package/src/store/message.ts +17 -1
  58. package/src/translation/resources/i18n.ts +2 -0
  59. package/src/types/chat.ts +3 -1
  60. package/src/utils/conversation.ts +1 -1
  61. package/src/utils/messageUtils.ts +18 -13
@@ -1,4 +1,10 @@
1
- import { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useImperativeHandle,
5
+ useMemo,
6
+ useState,
7
+ } from 'react';
2
8
  import { Modal, Pressable, StyleSheet } from 'react-native';
3
9
  import Clipboard from '@react-native-clipboard/clipboard';
4
10
  import { UIUtils } from '../../../utils/UIUtils';
@@ -7,7 +13,7 @@ import { LegendChatMessage } from '../legend/LegendChatMessage';
7
13
  import { MessageToolboxMenu } from './MessageToolboxMenu';
8
14
  import {
9
15
  getCopyableMessageText,
10
- useMessageToolboxItems,
16
+ getMessageToolboxItems,
11
17
  } from './messageToolbox.utils';
12
18
  import type { DMessageItem } from '../../../types/chat';
13
19
  import type { MeasuredRect, MessageToolboxRef } from './types';
@@ -27,9 +33,11 @@ export const MessageToolboxOverlay = forwardRef<
27
33
  const [visible, setVisible] = useState(false);
28
34
  const [message, setMessage] = useState<DMessageItem | undefined>(undefined);
29
35
  const [rect, setRect] = useState<MeasuredRect | null>(null);
30
- const toolboxItems = useMessageToolboxItems(message);
31
- const { retryMessage } = useSendMessage();
32
- console.log(message);
36
+ const toolboxItems = useMemo(
37
+ () => getMessageToolboxItems(message, currentUserId),
38
+ [message, currentUserId]
39
+ );
40
+ const { retryMessage, revokeMessage } = useSendMessage();
33
41
 
34
42
  const hide = useCallback(() => {
35
43
  setVisible(false);
@@ -37,11 +45,21 @@ export const MessageToolboxOverlay = forwardRef<
37
45
  setRect(null);
38
46
  }, []);
39
47
 
40
- const show = useCallback((msg: DMessageItem, msgRect: MeasuredRect) => {
41
- setMessage(msg);
42
- setRect(msgRect);
43
- setVisible(true);
44
- }, []);
48
+ const show = useCallback(
49
+ (msg: DMessageItem, msgRect: MeasuredRect) => {
50
+ const toolboxNotAvailable =
51
+ Object.values(getMessageToolboxItems(msg, currentUserId)).filter(
52
+ Boolean
53
+ ).length === 0;
54
+
55
+ if (toolboxNotAvailable) return;
56
+
57
+ setMessage(msg);
58
+ setRect(msgRect);
59
+ setVisible(true);
60
+ },
61
+ [currentUserId]
62
+ );
45
63
 
46
64
  useImperativeHandle(ref, () => ({ show, hide }), [show, hide]);
47
65
 
@@ -76,6 +94,24 @@ export const MessageToolboxOverlay = forwardRef<
76
94
  hide();
77
95
  }, [message, hide, retryMessage]);
78
96
 
97
+ const handleRevokeMessage = useCallback(async () => {
98
+ if (!message) return;
99
+ hide();
100
+ try {
101
+ await revokeMessage(message);
102
+ UIUtils.toast.open({
103
+ title: trans('msg_type_revoke'),
104
+ theme: 'success',
105
+ });
106
+ } catch (error: any) {
107
+ console.error('revokeMessage', error);
108
+ UIUtils.toast.open({
109
+ title: trans('chat:generic_error'),
110
+ theme: 'error',
111
+ });
112
+ }
113
+ }, [message, hide, revokeMessage]);
114
+
79
115
  if (!visible || !message || !rect) {
80
116
  return null;
81
117
  }
@@ -109,6 +145,7 @@ export const MessageToolboxOverlay = forwardRef<
109
145
  onCopyMessage={handleCopyMessage}
110
146
  onCopyMessageLink={handleCopyMessageLink}
111
147
  onRetryMessage={handleRetryMessage}
148
+ onRevokeMessage={handleRevokeMessage}
112
149
  />
113
150
  }
114
151
  />
@@ -1,6 +1,7 @@
1
1
  import { MessageStatus, MessageType } from '@droppii/openim-rn-client-sdk';
2
2
  import type { DMessageItem } from '../../../types/chat';
3
3
  import { getMessageText } from '../../../utils/legendListMessage';
4
+ import { isMessageRevoked } from '../../../utils/messageUtils';
4
5
 
5
6
  // Copy message content is only offered for text and link messages.
6
7
  const COPYABLE_TEXT_TYPES: number[] = [
@@ -10,28 +11,39 @@ const COPYABLE_TEXT_TYPES: number[] = [
10
11
  MessageType.UrlTextMessage,
11
12
  ];
12
13
 
13
- export const isMessageRecalled = (message: DMessageItem): boolean =>
14
- message.contentType === MessageType.RevokeMessage;
15
-
16
- export const isMessageSucceeded = (message: DMessageItem): boolean =>
14
+ const isMessageSucceeded = (message: DMessageItem): boolean =>
17
15
  message.status !== MessageStatus.Failed &&
18
16
  message.status !== MessageStatus.Sending;
19
17
 
20
- // Failed or recalled messages never offer either copy action.
21
- export const isEligibleForToolbox = (message: DMessageItem): boolean =>
22
- isMessageSucceeded(message) && !isMessageRecalled(message);
18
+ // Failed or revoked messages never offer either copy action.
19
+ const isEligibleForToolbox = (message: DMessageItem): boolean =>
20
+ isMessageSucceeded(message) && !isMessageRevoked(message);
23
21
 
24
- export const isCopyMessageSupported = (message: DMessageItem): boolean =>
22
+ const isCopyMessageSupported = (message: DMessageItem): boolean =>
25
23
  isEligibleForToolbox(message) &&
26
24
  COPYABLE_TEXT_TYPES.includes(message.contentType);
27
25
 
28
26
  // All message types are supported, as long as the message is eligible.
29
- export const isCopyMessageLinkSupported = (message: DMessageItem): boolean =>
27
+ const isCopyMessageLinkSupported = (message: DMessageItem): boolean =>
30
28
  isEligibleForToolbox(message);
31
29
 
32
- export const isRetrySupported = (message: DMessageItem): boolean =>
30
+ const isRetrySupported = (message: DMessageItem): boolean =>
33
31
  message.status === MessageStatus.Failed;
34
32
 
33
+ // Revoke is only available within 24h of a successful send.
34
+ const REVOKE_WINDOW_MS = 24 * 60 * 60 * 1000;
35
+
36
+ // Revoke is only available for messages sent by the current user.
37
+ // And not limited by message content type.
38
+ const isRevokeMessageSupported = (
39
+ message: DMessageItem,
40
+ currentUserId?: string
41
+ ): boolean =>
42
+ isEligibleForToolbox(message) &&
43
+ !!currentUserId &&
44
+ message.sendID === currentUserId &&
45
+ Date.now() - (message.sendTime || message.createTime || 0) < REVOKE_WINDOW_MS;
46
+
35
47
  export const getCopyableMessageText = (message: DMessageItem): string => {
36
48
  if (message.contentType === MessageType.UrlTextMessage) {
37
49
  return message.urlTextElem?.content ?? '';
@@ -39,7 +51,10 @@ export const getCopyableMessageText = (message: DMessageItem): string => {
39
51
  return getMessageText(message);
40
52
  };
41
53
 
42
- export const useMessageToolboxItems = (message?: DMessageItem) => {
54
+ export const getMessageToolboxItems = (
55
+ message?: DMessageItem,
56
+ currentUserId?: string
57
+ ) => {
43
58
  if (message == null) {
44
59
  return {};
45
60
  }
@@ -47,10 +62,12 @@ export const useMessageToolboxItems = (message?: DMessageItem) => {
47
62
  const showCopyMessage = isCopyMessageSupported(message);
48
63
  const showCopyMessageLink = isCopyMessageLinkSupported(message);
49
64
  const showRetryMessage = isRetrySupported(message);
65
+ const showRevokeMessage = isRevokeMessageSupported(message, currentUserId);
50
66
 
51
67
  return {
52
68
  showCopyMessage,
53
69
  showCopyMessageLink,
54
70
  showRetryMessage,
71
+ showRevokeMessage,
55
72
  };
56
73
  };
@@ -1,4 +1,5 @@
1
1
  import { create } from 'zustand';
2
+ import { MessageType } from '@droppii/openim-rn-client-sdk';
2
3
  import type { MessageItem } from '@droppii/openim-rn-client-sdk';
3
4
  import type { DMessageItem, MessageStore } from '../types/chat';
4
5
  import { mergeMessages } from '../utils/message';
@@ -18,7 +19,7 @@ export const useMessageStore = create<MessageStore>()((set, get) => ({
18
19
  }));
19
20
  },
20
21
 
21
- updateOneMessage: (message: MessageItem) => {
22
+ updateOneMessage: (message) => {
22
23
  const tmpList = [...get().messages];
23
24
  const idx = tmpList.findIndex(
24
25
  (msg) => msg.clientMsgID === message.clientMsgID
@@ -42,3 +43,18 @@ export const useMessageStore = create<MessageStore>()((set, get) => ({
42
43
  });
43
44
  },
44
45
  }));
46
+
47
+ export const markMessageRevoked = (clientMsgID: string) => {
48
+ const alreadyRevoked = useMessageStore
49
+ .getState()
50
+ .messages.some(
51
+ (msg) =>
52
+ msg.clientMsgID === clientMsgID &&
53
+ msg.contentType === MessageType.RevokeMessage
54
+ );
55
+ if (alreadyRevoked) return;
56
+
57
+ useMessageStore
58
+ .getState()
59
+ .updateOneMessage({ clientMsgID, contentType: MessageType.RevokeMessage });
60
+ };
@@ -33,12 +33,14 @@ const i18nKey: Record<string, string> = {
33
33
  'chat:copy_message_success': 'Sao chép tin nhắn thành công',
34
34
  'chat:copy_message_link_success': 'Sao chép đường dẫn tin nhắn thành công',
35
35
  'chat:retry_message': 'Gửi lại',
36
+ 'chat:revoke_message': 'Thu hồi',
36
37
  'msg_type_image': '[Hình ảnh]',
37
38
  'msg_type_voice': '[Tin nhắn thoại]',
38
39
  'msg_type_video': '[Video]',
39
40
  'msg_type_file': '[File đính kèm]',
40
41
  'msg_type_link': '[Liên kết]',
41
42
  'msg_type_revoke': 'Đã thu hồi tin nhắn',
43
+ 'chat:revoked_by_sender': '{{sender}} đã thu hồi tin nhắn',
42
44
  'start': 'Bắt đầu',
43
45
  'restart': 'Bắt đầu lại',
44
46
  };
package/src/types/chat.ts CHANGED
@@ -127,7 +127,9 @@ export interface MessageStore {
127
127
  hasMoreNewer: boolean;
128
128
  setMessages: (messages: DMessageItem[], hasMore: boolean) => void;
129
129
  pushNewMessage: (message: MessageItem) => void;
130
- updateOneMessage: (message: MessageItem) => void;
130
+ updateOneMessage: (
131
+ message: Partial<MessageItem> & Pick<MessageItem, 'clientMsgID'>
132
+ ) => void;
131
133
  deleteOneMessage: (clientMsgID: string) => void;
132
134
  reset: () => void;
133
135
  }
@@ -65,7 +65,7 @@ export const generateContentBasedOnMessageType = (
65
65
  case MessageType.UrlTextMessage:
66
66
  return trans('msg_type_link');
67
67
  case MessageType.RevokeMessage:
68
- return trans('revoked');
68
+ return trans('msg_type_revoke');
69
69
  default:
70
70
  return '';
71
71
  }
@@ -10,6 +10,10 @@ import {
10
10
  import type { DMessageItem } from '../types/chat';
11
11
  import { DChatMessageType } from '../types/message';
12
12
  import { formatDuration } from './dateTimeUtils';
13
+ import { trans } from '../translation';
14
+
15
+ export const isMessageRevoked = (message: DMessageItem): boolean =>
16
+ message.contentType === MessageType.RevokeMessage;
13
17
 
14
18
  export const extractTextContent = (message: DMessageItem): string => {
15
19
  if (message.textElem?.content) return message.textElem.content;
@@ -96,6 +100,9 @@ export const getMessagePreview = (message: DMessageItem): string => {
96
100
  case MessageType.QuoteMessage:
97
101
  return extractTextContent(message);
98
102
 
103
+ case MessageType.RevokeMessage:
104
+ return trans('msg_type_revoke');
105
+
99
106
  default:
100
107
  return '[Tin nhắn không được hỗ trợ]';
101
108
  }
@@ -111,19 +118,17 @@ export const getLastMessageText = (
111
118
  const preview = getMessagePreview(message);
112
119
  if (!preview) return '';
113
120
 
114
- // Add sender name for outgoing messages
115
- if (currentUserId && message.sendID === currentUserId) {
116
- return `Bạn: ${preview}`;
117
- }
118
-
119
- // Add sender nickname for incoming messages
120
- if (message.senderNickname) {
121
- return `${message.senderNickname}: ${preview}`;
122
- }
121
+ const sender =
122
+ currentUserId && message.sendID === currentUserId
123
+ ? 'Bạn'
124
+ : message.senderNickname ||
125
+ (conversation?.peerType === PeerType.Bot
126
+ ? conversation?.showName || ''
127
+ : undefined);
123
128
 
124
- if (conversation?.peerType === PeerType.Bot) {
125
- return `${conversation?.showName || ''}: ${preview}`;
126
- }
129
+ if (!sender) return preview;
127
130
 
128
- return preview;
131
+ return isMessageRevoked(message)
132
+ ? trans('chat:revoked_by_sender', { sender })
133
+ : `${sender}: ${preview}`;
129
134
  };