@droppii-org/chat-mobile 0.2.27 → 0.2.29

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 (80) hide show
  1. package/lib/module/components/ThreadCard/MuteThread.js +86 -0
  2. package/lib/module/components/ThreadCard/MuteThread.js.map +1 -0
  3. package/lib/module/components/ThreadCard/ThreadCard.js +93 -73
  4. package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
  5. package/lib/module/components/messages/revokedMessage/index.js +22 -0
  6. package/lib/module/components/messages/revokedMessage/index.js.map +1 -0
  7. package/lib/module/config/uploadLimits.js +1 -1
  8. package/lib/module/core/useChatListener.js +6 -1
  9. package/lib/module/core/useChatListener.js.map +1 -1
  10. package/lib/module/hooks/message/useSendMessage.js +15 -2
  11. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  12. package/lib/module/hooks/useMuteConversation.js +34 -0
  13. package/lib/module/hooks/useMuteConversation.js.map +1 -0
  14. package/lib/module/screens/chat-detail/constants.js +1 -1
  15. package/lib/module/screens/chat-detail/constants.js.map +1 -1
  16. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +3 -1
  17. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  18. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxItem.js +36 -0
  19. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxItem.js.map +1 -0
  20. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js +29 -56
  21. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js.map +1 -1
  22. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +27 -7
  23. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
  24. package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js +22 -11
  25. package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js.map +1 -1
  26. package/lib/module/store/message.js +9 -0
  27. package/lib/module/store/message.js.map +1 -1
  28. package/lib/module/translation/resources/i18n.js +10 -0
  29. package/lib/module/translation/resources/i18n.js.map +1 -1
  30. package/lib/module/utils/conversation.js +1 -1
  31. package/lib/module/utils/conversation.js.map +1 -1
  32. package/lib/module/utils/device.js +1 -1
  33. package/lib/module/utils/device.js.map +1 -1
  34. package/lib/module/utils/messageUtils.js +9 -14
  35. package/lib/module/utils/messageUtils.js.map +1 -1
  36. package/lib/typescript/src/components/ThreadCard/MuteThread.d.ts +10 -0
  37. package/lib/typescript/src/components/ThreadCard/MuteThread.d.ts.map +1 -0
  38. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  39. package/lib/typescript/src/components/messages/revokedMessage/index.d.ts +3 -0
  40. package/lib/typescript/src/components/messages/revokedMessage/index.d.ts.map +1 -0
  41. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  42. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +1 -0
  43. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  44. package/lib/typescript/src/hooks/useMuteConversation.d.ts +6 -0
  45. package/lib/typescript/src/hooks/useMuteConversation.d.ts.map +1 -0
  46. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  47. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxItem.d.ts +10 -0
  48. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxItem.d.ts.map +1 -0
  49. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts +3 -1
  50. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts.map +1 -1
  51. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
  52. package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts +3 -9
  53. package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts.map +1 -1
  54. package/lib/typescript/src/store/message.d.ts +1 -0
  55. package/lib/typescript/src/store/message.d.ts.map +1 -1
  56. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  57. package/lib/typescript/src/types/chat.d.ts +1 -1
  58. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  59. package/lib/typescript/src/utils/messageUtils.d.ts +1 -0
  60. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  61. package/package.json +1 -1
  62. package/src/components/ThreadCard/MuteThread.tsx +100 -0
  63. package/src/components/ThreadCard/ThreadCard.tsx +104 -71
  64. package/src/components/messages/revokedMessage/index.tsx +23 -0
  65. package/src/config/uploadLimits.ts +1 -1
  66. package/src/core/useChatListener.ts +8 -1
  67. package/src/hooks/message/useSendMessage.ts +18 -1
  68. package/src/hooks/useMuteConversation.ts +44 -0
  69. package/src/screens/chat-detail/constants.ts +1 -1
  70. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +2 -0
  71. package/src/screens/chat-detail/messageToolbox/MessageToolboxItem.tsx +38 -0
  72. package/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.tsx +35 -62
  73. package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +47 -10
  74. package/src/screens/chat-detail/messageToolbox/messageToolbox.utils.ts +31 -14
  75. package/src/store/message.ts +17 -1
  76. package/src/translation/resources/i18n.ts +11 -0
  77. package/src/types/chat.ts +3 -1
  78. package/src/utils/conversation.ts +1 -1
  79. package/src/utils/device.ts +1 -1
  80. package/src/utils/messageUtils.ts +18 -13
@@ -0,0 +1,44 @@
1
+ import { useCallback } from 'react';
2
+ import OpenIMSDK, {
3
+ MessageReceiveOptType,
4
+ } from '@droppii/openim-rn-client-sdk';
5
+ import type { ConversationItem } from '@droppii/openim-rn-client-sdk';
6
+ import { useConversationStore } from '../store';
7
+
8
+ export function useMuteConversation() {
9
+ const muteConversation = useCallback(
10
+ async (conversation?: ConversationItem) => {
11
+ if (conversation == null) return;
12
+
13
+ await OpenIMSDK.setConversationRecvMessageOpt({
14
+ conversationID: conversation.conversationID,
15
+ opt: MessageReceiveOptType.NotNotify,
16
+ });
17
+ useConversationStore
18
+ .getState()
19
+ .updateConversations([
20
+ { ...conversation, recvMsgOpt: MessageReceiveOptType.NotNotify },
21
+ ]);
22
+ },
23
+ []
24
+ );
25
+
26
+ const unmuteConversation = useCallback(
27
+ async (conversation?: ConversationItem) => {
28
+ if (conversation == null) return;
29
+
30
+ await OpenIMSDK.setConversationRecvMessageOpt({
31
+ conversationID: conversation.conversationID,
32
+ opt: MessageReceiveOptType.Normal,
33
+ });
34
+ useConversationStore
35
+ .getState()
36
+ .updateConversations([
37
+ { ...conversation, recvMsgOpt: MessageReceiveOptType.Normal },
38
+ ]);
39
+ },
40
+ []
41
+ );
42
+
43
+ return { muteConversation, unmuteConversation };
44
+ }
@@ -55,7 +55,7 @@ export const DEFAULT_ATTACHMENT_ACTIONS: DChatAttachmentAction[] = [
55
55
  iconName: 'camera-o',
56
56
  },
57
57
  {
58
- id: 'documents',
58
+ id: 'file',
59
59
  label: 'Tài liệu',
60
60
  iconName: 'paperclip-o',
61
61
  },
@@ -13,6 +13,7 @@ import { VideoMessage } from '../../../components/messages/videoMessage';
13
13
  import { FileMessage } from '../../../components/messages/fileMessage';
14
14
  import { LinkMessage } from '../../../components/messages/linkMessage';
15
15
  import { MergedMessage } from '../../../components/messages/mergedMessage';
16
+ import { RevokedMessage } from '../../../components/messages/revokedMessage';
16
17
  import { MessageStatusIndicator } from '../../../components/messages/MessageStatusIndicator';
17
18
  import type { BaseLegendMessageProps } from '../../../components/messages/types';
18
19
  import { messageStyles } from './messageTypes';
@@ -47,6 +48,7 @@ const messageComponentMap: Partial<
47
48
  [MessageType.VideoMessage]: VideoMessage,
48
49
  [MessageType.FileMessage]: FileMessage,
49
50
  [MERGED_MESSAGE_TYPE]: MergedMessage,
51
+ [MessageType.RevokeMessage]: RevokedMessage,
50
52
  };
51
53
 
52
54
  const MEDIA_TYPES: number[] = [
@@ -0,0 +1,38 @@
1
+ import { memo } from 'react';
2
+ import { KContainer, KImage, KLabel } from '@droppii/libs';
3
+
4
+ interface MessageToolboxItemProps {
5
+ icon: string;
6
+ color: string;
7
+ label: string;
8
+ visible?: boolean;
9
+ onPress?: () => void;
10
+ }
11
+
12
+ export const MessageToolboxItem = memo(
13
+ ({ icon, color, label, visible, onPress }: MessageToolboxItemProps) => {
14
+ return (
15
+ <KContainer.VisibleView visible={!!visible}>
16
+ <KContainer.Touchable
17
+ row
18
+ alignItems
19
+ paddingH={'1rem'}
20
+ paddingV={'0.5rem'}
21
+ onPress={onPress}
22
+ >
23
+ <KImage.VectorIcons
24
+ name={icon}
25
+ provider="DroppiiNew"
26
+ size={24}
27
+ color={color}
28
+ />
29
+ <KLabel.Text typo="TextMdMedium" color={color} marginL="0.75rem">
30
+ {label}
31
+ </KLabel.Text>
32
+ </KContainer.Touchable>
33
+ </KContainer.VisibleView>
34
+ );
35
+ }
36
+ );
37
+
38
+ MessageToolboxItem.displayName = 'MessageToolboxItem';
@@ -1,21 +1,18 @@
1
1
  import { memo } from 'react';
2
2
  import { StyleSheet } from 'react-native';
3
- import {
4
- KContainer,
5
- KImage,
6
- KLabel,
7
- KColors,
8
- KSpacingValue,
9
- } from '@droppii/libs';
3
+ import { KContainer, KColors, KSpacingValue } from '@droppii/libs';
10
4
  import { trans } from '../../../translation';
5
+ import { MessageToolboxItem } from './MessageToolboxItem';
11
6
 
12
7
  interface MessageToolboxMenuProps {
13
8
  showCopyMessage?: boolean;
14
9
  showCopyMessageLink?: boolean;
15
10
  showRetryMessage?: boolean;
11
+ showRevokeMessage?: boolean;
16
12
  onCopyMessage: () => void;
17
13
  onCopyMessageLink: () => void;
18
14
  onRetryMessage: () => void;
15
+ onRevokeMessage: () => void;
19
16
  }
20
17
 
21
18
  export const MessageToolboxMenu = memo(
@@ -23,69 +20,45 @@ export const MessageToolboxMenu = memo(
23
20
  showCopyMessage,
24
21
  showCopyMessageLink,
25
22
  showRetryMessage,
23
+ showRevokeMessage,
26
24
  onCopyMessage,
27
25
  onCopyMessageLink,
28
26
  onRetryMessage,
27
+ onRevokeMessage,
29
28
  }: MessageToolboxMenuProps) => {
30
- if (!showCopyMessage && !showCopyMessageLink && !showRetryMessage) {
31
- return null;
32
- }
33
-
34
29
  return (
35
30
  <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>
31
+ <MessageToolboxItem
32
+ icon="copy-o"
33
+ color={KColors.gray.dark}
34
+ label={trans('chat:copy_message')}
35
+ visible={showCopyMessage}
36
+ onPress={onCopyMessage}
37
+ />
38
+
39
+ <MessageToolboxItem
40
+ icon="link-o"
41
+ color={KColors.gray.dark}
42
+ label={trans('chat:copy_message_link')}
43
+ visible={showCopyMessageLink}
44
+ onPress={onCopyMessageLink}
45
+ />
53
46
 
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>
47
+ <MessageToolboxItem
48
+ icon="arrow-reload-o"
49
+ color={KColors.danger.normal}
50
+ label={trans('chat:retry_message')}
51
+ visible={showRetryMessage}
52
+ onPress={onRetryMessage}
53
+ />
71
54
 
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>
55
+ <MessageToolboxItem
56
+ icon="trash-alt-o-1"
57
+ color={KColors.danger.normal}
58
+ label={trans('chat:revoke_message')}
59
+ visible={showRevokeMessage}
60
+ onPress={onRevokeMessage}
61
+ />
89
62
  </KContainer.View>
90
63
  );
91
64
  }
@@ -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 =>
30
- isEligibleForToolbox(message);
27
+ // const isCopyMessageLinkSupported = (message: DMessageItem): boolean =>
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,18 +51,23 @@ 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
  }
46
61
 
47
62
  const showCopyMessage = isCopyMessageSupported(message);
48
- const showCopyMessageLink = isCopyMessageLinkSupported(message);
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
- showCopyMessageLink,
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,23 @@ 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',
44
+ 'chat:mute_action': 'Tắt thông báo',
45
+ 'chat:unmute_action': 'Bật thông báo',
46
+ 'chat:mute_confirm_title': 'Tắt thông báo',
47
+ 'chat:mute_confirm_desc':
48
+ 'Bạn có thể bỏ lỡ tin nhắn quan trọng nếu tắt thông báo! Bạn có chắc chắn muốn tắt thông báo?',
49
+ 'chat:mute_confirm_primary': 'Tắt thông báo',
50
+ 'chat:mute_confirm_secondary': 'Quay lại',
51
+ 'chat:mute_toast_success': 'Đã tắt thông báo',
52
+ 'chat:unmute_toast_success': 'Đã bật thông báo',
42
53
  'start': 'Bắt đầu',
43
54
  'restart': 'Bắt đầu lại',
44
55
  };
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
  }
@@ -61,7 +61,7 @@ const DeviceUtils = {
61
61
  const result = await ImagePicker.openPicker({
62
62
  // cropping: true,
63
63
  mediaType: 'any',
64
- compressImageQuality: 0.8,
64
+ compressImageQuality: 1,
65
65
  multiple,
66
66
  forceJpg: true,
67
67
  });
@@ -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
  };