@droppii-org/chat-mobile 0.2.26 → 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.
- package/README.md +113 -0
- package/lib/module/components/messages/revokedMessage/index.js +22 -0
- package/lib/module/components/messages/revokedMessage/index.js.map +1 -0
- package/lib/module/core/index.js +3 -0
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/core/notification/backgroundStatus.js +15 -0
- package/lib/module/core/notification/backgroundStatus.js.map +1 -0
- package/lib/module/core/notification/index.js +5 -0
- package/lib/module/core/notification/index.js.map +1 -0
- package/lib/module/core/notification/pushToken.js +23 -0
- package/lib/module/core/notification/pushToken.js.map +1 -0
- package/lib/module/core/notification/useBackgroundStatusSync.js +16 -0
- package/lib/module/core/notification/useBackgroundStatusSync.js.map +1 -0
- package/lib/module/core/useChatListener.js +6 -1
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/hooks/message/useSendMessage.js +15 -2
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/useChatMessages.js +2 -1
- package/lib/module/hooks/useChatMessages.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/constants.js +1 -1
- package/lib/module/screens/chat-detail/constants.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +3 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxItem.js +36 -0
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxItem.js.map +1 -0
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js +31 -57
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +27 -7
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js +18 -9
- package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js.map +1 -1
- package/lib/module/services/attachmentOrchestrator.js +2 -1
- package/lib/module/services/attachmentOrchestrator.js.map +1 -1
- package/lib/module/store/message.js +9 -0
- package/lib/module/store/message.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +2 -0
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/utils/conversation.js +1 -1
- package/lib/module/utils/conversation.js.map +1 -1
- package/lib/module/utils/messageUtils.js +9 -14
- package/lib/module/utils/messageUtils.js.map +1 -1
- package/lib/module/utils/promise.js +14 -0
- package/lib/module/utils/promise.js.map +1 -0
- package/lib/typescript/src/components/messages/revokedMessage/index.d.ts +3 -0
- package/lib/typescript/src/components/messages/revokedMessage/index.d.ts.map +1 -0
- package/lib/typescript/src/core/index.d.ts +1 -0
- package/lib/typescript/src/core/index.d.ts.map +1 -1
- package/lib/typescript/src/core/notification/backgroundStatus.d.ts +6 -0
- package/lib/typescript/src/core/notification/backgroundStatus.d.ts.map +1 -0
- package/lib/typescript/src/core/notification/index.d.ts +3 -0
- package/lib/typescript/src/core/notification/index.d.ts.map +1 -0
- package/lib/typescript/src/core/notification/pushToken.d.ts +14 -0
- package/lib/typescript/src/core/notification/pushToken.d.ts.map +1 -0
- package/lib/typescript/src/core/notification/useBackgroundStatusSync.d.ts +2 -0
- package/lib/typescript/src/core/notification/useBackgroundStatusSync.d.ts.map +1 -0
- package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts +1 -0
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useChatMessages.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/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxItem.d.ts +10 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxItem.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts +3 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts +3 -7
- package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts.map +1 -1
- package/lib/typescript/src/services/attachmentOrchestrator.d.ts.map +1 -1
- package/lib/typescript/src/store/message.d.ts +1 -0
- package/lib/typescript/src/store/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 +1 -1
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/utils/messageUtils.d.ts +1 -0
- package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
- package/lib/typescript/src/utils/promise.d.ts +13 -0
- package/lib/typescript/src/utils/promise.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/messages/revokedMessage/index.tsx +23 -0
- package/src/core/index.ts +4 -0
- package/src/core/notification/backgroundStatus.ts +22 -0
- package/src/core/notification/index.ts +5 -0
- package/src/core/notification/pushToken.ts +24 -0
- package/src/core/notification/useBackgroundStatusSync.ts +18 -0
- package/src/core/useChatListener.ts +8 -1
- package/src/hooks/message/useSendMessage.ts +18 -1
- package/src/hooks/useChatMessages.ts +2 -1
- package/src/index.tsx +1 -0
- package/src/screens/chat-detail/constants.ts +1 -1
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +2 -0
- package/src/screens/chat-detail/messageToolbox/MessageToolboxItem.tsx +38 -0
- package/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.tsx +36 -63
- package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +47 -10
- package/src/screens/chat-detail/messageToolbox/messageToolbox.utils.ts +28 -11
- package/src/services/attachmentOrchestrator.ts +2 -1
- package/src/store/message.ts +17 -1
- package/src/translation/resources/i18n.ts +2 -0
- package/src/types/chat.ts +3 -1
- package/src/utils/conversation.ts +1 -1
- package/src/utils/messageUtils.ts +18 -13
- package/src/utils/promise.ts +18 -0
|
@@ -1,91 +1,64 @@
|
|
|
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(
|
|
22
19
|
({
|
|
23
20
|
showCopyMessage,
|
|
24
|
-
showCopyMessageLink,
|
|
25
21
|
showRetryMessage,
|
|
22
|
+
showRevokeMessage,
|
|
26
23
|
onCopyMessage,
|
|
27
24
|
onCopyMessageLink,
|
|
28
25
|
onRetryMessage,
|
|
26
|
+
onRevokeMessage,
|
|
29
27
|
}: MessageToolboxMenuProps) => {
|
|
30
|
-
if (!showCopyMessage && !showCopyMessageLink && !showRetryMessage) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
28
|
return (
|
|
35
29
|
<KContainer.View style={styles.card}>
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
</KContainer.VisibleView>
|
|
30
|
+
<MessageToolboxItem
|
|
31
|
+
icon="copy-o"
|
|
32
|
+
color={KColors.gray.dark}
|
|
33
|
+
label={trans('chat:copy_message')}
|
|
34
|
+
visible={showCopyMessage}
|
|
35
|
+
onPress={onCopyMessage}
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
<MessageToolboxItem
|
|
39
|
+
icon="link-o"
|
|
40
|
+
color={KColors.gray.dark}
|
|
41
|
+
label={trans('chat:copy_message_link')}
|
|
42
|
+
// visible={showCopyMessageLink}
|
|
43
|
+
visible={false}
|
|
44
|
+
onPress={onCopyMessageLink}
|
|
45
|
+
/>
|
|
53
46
|
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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 {
|
|
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
|
-
|
|
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 =
|
|
31
|
-
|
|
32
|
-
|
|
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(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
|
21
|
-
|
|
22
|
-
isMessageSucceeded(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
|
-
|
|
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
|
-
|
|
27
|
+
const isCopyMessageLinkSupported = (message: DMessageItem): boolean =>
|
|
30
28
|
isEligibleForToolbox(message);
|
|
31
29
|
|
|
32
|
-
|
|
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
|
|
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
|
};
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
getAttachmentPriorityConfig,
|
|
16
16
|
type AttachmentType,
|
|
17
17
|
} from '../config/attachment-priority';
|
|
18
|
+
import { allSettled } from '../utils/promise';
|
|
18
19
|
|
|
19
20
|
export namespace AttachmentOrchestratorService {
|
|
20
21
|
export async function sendPreparedAttachments(
|
|
@@ -282,7 +283,7 @@ export namespace AttachmentOrchestratorService {
|
|
|
282
283
|
});
|
|
283
284
|
}
|
|
284
285
|
|
|
285
|
-
const sendResults = await
|
|
286
|
+
const sendResults = await allSettled(
|
|
286
287
|
allMessages.map(async (m) => {
|
|
287
288
|
const message = m as MessageItem;
|
|
288
289
|
const result = await OpenIMSDK.sendMessageNotOss({
|
package/src/store/message.ts
CHANGED
|
@@ -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
|
|
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: (
|
|
130
|
+
updateOneMessage: (
|
|
131
|
+
message: Partial<MessageItem> & Pick<MessageItem, 'clientMsgID'>
|
|
132
|
+
) => void;
|
|
131
133
|
deleteOneMessage: (clientMsgID: string) => void;
|
|
132
134
|
reset: () => void;
|
|
133
135
|
}
|
|
@@ -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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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 (
|
|
125
|
-
return `${conversation?.showName || ''}: ${preview}`;
|
|
126
|
-
}
|
|
129
|
+
if (!sender) return preview;
|
|
127
130
|
|
|
128
|
-
return
|
|
131
|
+
return isMessageRevoked(message)
|
|
132
|
+
? trans('chat:revoked_by_sender', { sender })
|
|
133
|
+
: `${sender}: ${preview}`;
|
|
129
134
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type SettledResult<T> =
|
|
2
|
+
| { status: 'fulfilled'; value: T }
|
|
3
|
+
| { status: 'rejected'; reason: unknown };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Promise.allSettled polyfill — the RN/Hermes target for this app doesn't
|
|
7
|
+
* ship it, so a rejection would otherwise short-circuit Promise.all.
|
|
8
|
+
*/
|
|
9
|
+
export const allSettled = <T extends readonly unknown[]>(
|
|
10
|
+
promises: readonly [...T]
|
|
11
|
+
): Promise<{ [K in keyof T]: SettledResult<Awaited<T[K]>> }> =>
|
|
12
|
+
Promise.all(
|
|
13
|
+
promises.map((p) =>
|
|
14
|
+
Promise.resolve(p)
|
|
15
|
+
.then((value) => ({ status: 'fulfilled' as const, value }))
|
|
16
|
+
.catch((reason) => ({ status: 'rejected' as const, reason }))
|
|
17
|
+
)
|
|
18
|
+
) as Promise<{ [K in keyof T]: SettledResult<Awaited<T[K]>> }>;
|