@droppii-org/chat-mobile 0.2.66 → 0.2.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/module/components/Avatar/Avatar.js +39 -2
- package/lib/module/components/Avatar/Avatar.js.map +1 -1
- package/lib/module/components/Avatar/DoubleAvatar.js +18 -18
- package/lib/module/components/Avatar/DoubleAvatar.js.map +1 -1
- package/lib/module/components/Avatar/QuadAvatar.js +131 -0
- package/lib/module/components/Avatar/QuadAvatar.js.map +1 -0
- package/lib/module/components/Avatar/TripleAvatar.js +97 -0
- package/lib/module/components/Avatar/TripleAvatar.js.map +1 -0
- package/lib/module/components/Avatar/index.js +2 -0
- package/lib/module/components/Avatar/index.js.map +1 -1
- package/lib/module/components/ThreadCard/AvatarSection.js +41 -0
- package/lib/module/components/ThreadCard/AvatarSection.js.map +1 -1
- package/lib/module/components/ThreadCard/NamePrefixIcon.js +5 -4
- package/lib/module/components/ThreadCard/NamePrefixIcon.js.map +1 -1
- package/lib/module/components/ThreadCard/ThreadCard.js +3 -1
- package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
- package/lib/module/components/messages/groupNotificationMessage/index.js +25 -0
- package/lib/module/components/messages/groupNotificationMessage/index.js.map +1 -0
- package/lib/module/context/ChatContext.js +1 -1
- package/lib/module/context/ChatContext.js.map +1 -1
- package/lib/module/context/ChatDetailContext.js +9 -0
- package/lib/module/context/ChatDetailContext.js.map +1 -1
- package/lib/module/core/useChatListener.js +121 -21
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/hooks/conversation/useGetGroupInfo.js +3 -1
- package/lib/module/hooks/conversation/useGetGroupInfo.js.map +1 -1
- package/lib/module/hooks/message/usePinMessage.js +4 -2
- package/lib/module/hooks/message/usePinMessage.js.map +1 -1
- package/lib/module/hooks/message/useSendMessage.js +13 -15
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/useConversationList.js +9 -4
- package/lib/module/hooks/useConversationList.js.map +1 -1
- package/lib/module/hooks/useGroupPermission.js +34 -0
- package/lib/module/hooks/useGroupPermission.js.map +1 -0
- package/lib/module/screens/ChatInfo/ChatInfoProfile.js +4 -2
- package/lib/module/screens/ChatInfo/ChatInfoProfile.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatComposer.js +16 -18
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +7 -0
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetailHeader.js +10 -3
- package/lib/module/screens/chat-detail/ChatDetailHeader.js.map +1 -1
- package/lib/module/screens/chat-detail/conversationHeader.utils.js +13 -3
- package/lib/module/screens/chat-detail/conversationHeader.utils.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +29 -2
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +5 -0
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
- package/lib/module/screens/inbox/MessagesTab.js.map +1 -1
- package/lib/module/store/conversation.js +56 -12
- package/lib/module/store/conversation.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +32 -0
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/types/events.js +1 -0
- package/lib/module/types/events.js.map +1 -1
- package/lib/module/utils/messageUtils.js +95 -2
- package/lib/module/utils/messageUtils.js.map +1 -1
- package/lib/typescript/src/components/Avatar/Avatar.d.ts +1 -1
- package/lib/typescript/src/components/Avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/src/components/Avatar/Avatar.types.d.ts +5 -1
- package/lib/typescript/src/components/Avatar/Avatar.types.d.ts.map +1 -1
- package/lib/typescript/src/components/Avatar/DoubleAvatar.d.ts.map +1 -1
- package/lib/typescript/src/components/Avatar/QuadAvatar.d.ts +16 -0
- package/lib/typescript/src/components/Avatar/QuadAvatar.d.ts.map +1 -0
- package/lib/typescript/src/components/Avatar/TripleAvatar.d.ts +15 -0
- package/lib/typescript/src/components/Avatar/TripleAvatar.d.ts.map +1 -0
- package/lib/typescript/src/components/Avatar/index.d.ts +2 -0
- package/lib/typescript/src/components/Avatar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +2 -2
- package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
- package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts +3 -1
- package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts.map +1 -1
- package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/groupNotificationMessage/index.d.ts +3 -0
- package/lib/typescript/src/components/messages/groupNotificationMessage/index.d.ts.map +1 -0
- package/lib/typescript/src/context/ChatDetailContext.d.ts +2 -0
- package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -1
- package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
- package/lib/typescript/src/hooks/conversation/useGetGroupInfo.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/usePinMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useGroupPermission.d.ts +6 -0
- package/lib/typescript/src/hooks/useGroupPermission.d.ts.map +1 -0
- package/lib/typescript/src/screens/ChatInfo/ChatInfoProfile.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.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/MessageToolboxOverlay.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
- package/lib/typescript/src/screens/inbox/MessagesTab.d.ts.map +1 -1
- package/lib/typescript/src/store/conversation.d.ts +11 -0
- package/lib/typescript/src/store/conversation.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +6 -1
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/lib/typescript/src/utils/messageUtils.d.ts +6 -1
- package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/build-ignore.d.ts +1 -0
- package/src/components/Avatar/Avatar.tsx +47 -0
- package/src/components/Avatar/Avatar.types.ts +5 -1
- package/src/components/Avatar/DoubleAvatar.tsx +16 -13
- package/src/components/Avatar/QuadAvatar.tsx +161 -0
- package/src/components/Avatar/TripleAvatar.tsx +119 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/ThreadCard/AvatarSection.tsx +58 -2
- package/src/components/ThreadCard/NamePrefixIcon.tsx +32 -25
- package/src/components/ThreadCard/ThreadCard.tsx +2 -0
- package/src/components/messages/groupNotificationMessage/index.tsx +28 -0
- package/src/context/ChatContext.tsx +1 -1
- package/src/context/ChatDetailContext.tsx +15 -0
- package/src/core/useChatListener.ts +176 -23
- package/src/hooks/conversation/useGetGroupInfo.ts +2 -0
- package/src/hooks/message/usePinMessage.ts +8 -2
- package/src/hooks/message/useSendMessage.ts +22 -18
- package/src/hooks/useConversationList.ts +17 -7
- package/src/hooks/useGroupPermission.ts +43 -0
- package/src/screens/ChatInfo/ChatInfoProfile.tsx +6 -3
- package/src/screens/chat-detail/ChatComposer.tsx +29 -32
- package/src/screens/chat-detail/ChatDetail.tsx +12 -1
- package/src/screens/chat-detail/ChatDetailHeader.tsx +19 -3
- package/src/screens/chat-detail/conversationHeader.utils.ts +14 -6
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +36 -1
- package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +8 -1
- package/src/screens/inbox/MessagesTab.tsx +0 -1
- package/src/store/conversation.ts +62 -17
- package/src/translation/resources/i18n.ts +32 -0
- package/src/types/events.ts +6 -0
- package/src/utils/messageUtils.ts +103 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect } from 'react';
|
|
2
|
-
import OpenIMSDK from '@droppii/openim-rn-client-sdk';
|
|
2
|
+
import OpenIMSDK, { GroupPermission } from '@droppii/openim-rn-client-sdk';
|
|
3
3
|
import type { MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
4
4
|
import { useConversationStore } from '../../store/conversation';
|
|
5
5
|
import { useMessageStore } from '../../store/message';
|
|
@@ -7,6 +7,7 @@ import { usePinnedMessageStore } from '../../store/pinnedMessage';
|
|
|
7
7
|
import { useUserStore } from '../../store/user';
|
|
8
8
|
import { UIUtils } from '../../utils/UIUtils';
|
|
9
9
|
import { trans } from '../../translation';
|
|
10
|
+
import { useGuardedAction } from '../useGroupPermission';
|
|
10
11
|
|
|
11
12
|
const PINNED_MESSAGES_PAGE_SIZE = 5;
|
|
12
13
|
|
|
@@ -147,9 +148,14 @@ export const usePinMessage = (conversationID?: string) => {
|
|
|
147
148
|
[conversationID, removeMessage]
|
|
148
149
|
);
|
|
149
150
|
|
|
151
|
+
const pinMessageWithPermission = useGuardedAction(
|
|
152
|
+
GroupPermission.PinMessage,
|
|
153
|
+
pinMessage
|
|
154
|
+
);
|
|
155
|
+
|
|
150
156
|
return {
|
|
151
157
|
pinnedMessages,
|
|
152
|
-
pinMessage,
|
|
158
|
+
pinMessage: pinMessageWithPermission,
|
|
153
159
|
unpinMessage,
|
|
154
160
|
};
|
|
155
161
|
};
|
|
@@ -33,8 +33,8 @@ import type { StickerItem } from '../../types/sticker';
|
|
|
33
33
|
import { useQueryClient } from '@tanstack/react-query';
|
|
34
34
|
import { stickerQueryKeys } from '../query-keys';
|
|
35
35
|
import { getMessagePreview } from '../../utils/messageUtils';
|
|
36
|
-
import { UIUtils } from '../../utils/UIUtils';
|
|
37
36
|
import { trans } from '../../translation';
|
|
37
|
+
import { useGuardedAction } from '../useGroupPermission';
|
|
38
38
|
|
|
39
39
|
const buildSendParams = (
|
|
40
40
|
message: MessageItem,
|
|
@@ -52,8 +52,7 @@ export const useSendMessage = () => {
|
|
|
52
52
|
const applicationType = useChatContext().applicationType;
|
|
53
53
|
const pushNewMessage = useMessageStore((state) => state.pushNewMessage);
|
|
54
54
|
const updateOneMessage = useMessageStore((state) => state.updateOneMessage);
|
|
55
|
-
const { quotedMessage, setQuotedMessage
|
|
56
|
-
useChatDetailContext();
|
|
55
|
+
const { quotedMessage, setQuotedMessage } = useChatDetailContext();
|
|
57
56
|
const queryClient = useQueryClient();
|
|
58
57
|
|
|
59
58
|
const dispatchSend = useCallback(
|
|
@@ -107,16 +106,6 @@ export const useSendMessage = () => {
|
|
|
107
106
|
const recvID = currentConversation?.userID || targetUserId;
|
|
108
107
|
const groupID = currentConversation?.groupID || targetGroupId;
|
|
109
108
|
if (!recvID && !groupID) return;
|
|
110
|
-
if (
|
|
111
|
-
currentConversation?.conversationType === SessionType.Group &&
|
|
112
|
-
!groupPermissions?.includes(GroupPermission.SendMessage)
|
|
113
|
-
) {
|
|
114
|
-
UIUtils.toast.open({
|
|
115
|
-
title: trans('chat:no_send_permission'),
|
|
116
|
-
theme: 'info',
|
|
117
|
-
});
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
109
|
|
|
121
110
|
const messageList: MessageItem[] = [];
|
|
122
111
|
|
|
@@ -192,7 +181,6 @@ export const useSendMessage = () => {
|
|
|
192
181
|
quotedMessage,
|
|
193
182
|
setQuotedMessage,
|
|
194
183
|
queryClient,
|
|
195
|
-
groupPermissions,
|
|
196
184
|
]
|
|
197
185
|
);
|
|
198
186
|
|
|
@@ -257,8 +245,16 @@ export const useSendMessage = () => {
|
|
|
257
245
|
markMessageRevoked(message.clientMsgID);
|
|
258
246
|
}, []);
|
|
259
247
|
|
|
260
|
-
|
|
248
|
+
const sendMessageWithPermission = useGuardedAction(
|
|
249
|
+
GroupPermission.SendMessage,
|
|
261
250
|
sendMessage,
|
|
251
|
+
{
|
|
252
|
+
deniedMessage: trans('chat:no_send_permission'),
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
sendMessage: sendMessageWithPermission,
|
|
262
258
|
retryMessage,
|
|
263
259
|
revokeMessage,
|
|
264
260
|
};
|
|
@@ -268,14 +264,22 @@ export const generateOfflinePushInfo = (message: MessageItem) => {
|
|
|
268
264
|
const conversationData = useConversationStore
|
|
269
265
|
?.getState?.()
|
|
270
266
|
?.getCurrentConversation();
|
|
271
|
-
const
|
|
272
|
-
const
|
|
267
|
+
const operator = useUserStore.getState().user;
|
|
268
|
+
const operatorUserID = operator?.userID || '';
|
|
269
|
+
const isSingleChat =
|
|
270
|
+
conversationData?.conversationType === SessionType.Single;
|
|
271
|
+
const title = isSingleChat
|
|
272
|
+
? operator?.nickname || 'Droppii'
|
|
273
|
+
: conversationData?.showName || 'Droppii';
|
|
274
|
+
const icon = isSingleChat
|
|
275
|
+
? operator?.faceURL || ''
|
|
276
|
+
: conversationData?.faceURL || '';
|
|
273
277
|
const desc = getMessagePreview(message);
|
|
274
278
|
return {
|
|
275
279
|
title,
|
|
276
280
|
desc,
|
|
277
281
|
ex: JSON.stringify({
|
|
278
|
-
icon
|
|
282
|
+
icon,
|
|
279
283
|
conversationId: conversationData?.conversationID || '',
|
|
280
284
|
userId: operatorUserID,
|
|
281
285
|
groupId: conversationData?.groupID || '',
|
|
@@ -25,14 +25,24 @@ export function useConversationList({
|
|
|
25
25
|
enabled: enabled !== false,
|
|
26
26
|
queryFn: async () => {
|
|
27
27
|
const offset = (page - 1) * 500;
|
|
28
|
-
const conversations = await
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
const [conversations, joinedGroups] = await Promise.all([
|
|
29
|
+
OpenIMSDK.getConversationListSplitApp({
|
|
30
|
+
offset,
|
|
31
|
+
count: 500,
|
|
32
|
+
applicationType,
|
|
33
|
+
}),
|
|
34
|
+
OpenIMSDK.getJoinedGroupList(),
|
|
35
|
+
]);
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
const joinedGroupIds = new Set(joinedGroups.map((g) => g.groupID));
|
|
38
|
+
const resolved = conversations.map((conversation) =>
|
|
39
|
+
conversation.groupID && !joinedGroupIds.has(conversation.groupID)
|
|
40
|
+
? { ...conversation, isNotInGroup: true }
|
|
41
|
+
: conversation
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
useConversationStore.getState().updateConversations(resolved);
|
|
45
|
+
return resolved;
|
|
36
46
|
},
|
|
37
47
|
});
|
|
38
48
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { PeerType, type GroupPermission } from '@droppii/openim-rn-client-sdk';
|
|
2
|
+
import { useChatDetailContext } from '../context';
|
|
3
|
+
import { useCallback, useMemo } from 'react';
|
|
4
|
+
import { trans } from '../translation';
|
|
5
|
+
import { UIUtils } from '../utils/UIUtils';
|
|
6
|
+
|
|
7
|
+
export const useHasGroupPermission = (
|
|
8
|
+
permission: GroupPermission | GroupPermission[],
|
|
9
|
+
mode: 'every' | 'some' = 'every'
|
|
10
|
+
): boolean => {
|
|
11
|
+
const { conversation, groupPermissions } = useChatDetailContext();
|
|
12
|
+
const isNotGroup = conversation?.peerType !== PeerType.Group;
|
|
13
|
+
const required = Array.isArray(permission) ? permission : [permission];
|
|
14
|
+
|
|
15
|
+
return useMemo(() => {
|
|
16
|
+
if (isNotGroup) return true;
|
|
17
|
+
return mode === 'every'
|
|
18
|
+
? required.every((p) => groupPermissions?.includes(p))
|
|
19
|
+
: required.some((p) => groupPermissions?.includes(p));
|
|
20
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
21
|
+
}, [groupPermissions, isNotGroup, mode]);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const useGuardedAction = <T extends (...args: any[]) => any>(
|
|
25
|
+
permission: GroupPermission,
|
|
26
|
+
action: T,
|
|
27
|
+
options?: { deniedMessage?: string }
|
|
28
|
+
): T => {
|
|
29
|
+
const allowed = useHasGroupPermission(permission);
|
|
30
|
+
return useCallback(
|
|
31
|
+
(...args: Parameters<T>) => {
|
|
32
|
+
if (!allowed) {
|
|
33
|
+
UIUtils.toast.open({
|
|
34
|
+
title: options?.deniedMessage ?? trans('chat:no_permission'),
|
|
35
|
+
theme: 'info',
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
return action(...args);
|
|
40
|
+
},
|
|
41
|
+
[allowed, action, options?.deniedMessage]
|
|
42
|
+
) as T;
|
|
43
|
+
};
|
|
@@ -93,12 +93,15 @@ const ChatInfoProfile = memo(
|
|
|
93
93
|
fullName={showName}
|
|
94
94
|
badge={badge}
|
|
95
95
|
/>
|
|
96
|
-
<KContainer.View row alignItems marginT="0.5rem">
|
|
97
|
-
<NamePrefixIcon
|
|
96
|
+
<KContainer.View row alignItems marginT="0.5rem" paddingH={'0.75rem'}>
|
|
97
|
+
<NamePrefixIcon
|
|
98
|
+
peerType={conversation?.peerType}
|
|
99
|
+
marginR="0.5rem"
|
|
100
|
+
/>
|
|
98
101
|
<KLabel.Text
|
|
99
102
|
typo="TextLgBold"
|
|
100
103
|
color={KColors.black}
|
|
101
|
-
numberOfLines={
|
|
104
|
+
numberOfLines={2}
|
|
102
105
|
>
|
|
103
106
|
{showName}
|
|
104
107
|
</KLabel.Text>
|
|
@@ -63,8 +63,8 @@ import { ChatCurrentQuotedMessage } from './ChatCurrentQuotedMessage';
|
|
|
63
63
|
import { useSendMessage } from '../../hooks/message/useSendMessage';
|
|
64
64
|
import { openBotMenuSheet } from './BotMenuSheet';
|
|
65
65
|
import { UIUtils } from '../../utils/UIUtils';
|
|
66
|
-
import { GroupPermission,
|
|
67
|
-
import {
|
|
66
|
+
import { GroupPermission, PeerType } from '@droppii/openim-rn-client-sdk';
|
|
67
|
+
import { useGuardedAction } from '../../hooks/useGroupPermission';
|
|
68
68
|
|
|
69
69
|
interface ChatComposerInternalProps {
|
|
70
70
|
isEmptyMessage?: boolean;
|
|
@@ -88,16 +88,17 @@ export const ChatComposer = memo(
|
|
|
88
88
|
|
|
89
89
|
const {
|
|
90
90
|
isBotConversation,
|
|
91
|
+
conversation,
|
|
91
92
|
conversationFlow,
|
|
92
93
|
conversationFlowMainMenu,
|
|
93
94
|
flowState,
|
|
94
95
|
} = useChatDetailContext();
|
|
96
|
+
const isGroupConversation = conversation?.peerType === PeerType.Group;
|
|
95
97
|
const { mutate: runFlow, isPending } = useRunFlow();
|
|
96
98
|
const { mutate: resetFlow } = useResetFlow();
|
|
97
99
|
const { mutateAsync: closeSupportSession } = useCloseSupportSession();
|
|
98
100
|
const { sendMessage } = useSendMessage();
|
|
99
101
|
const compose = useChatCompose();
|
|
100
|
-
const groupPermissions = useChatDetailContext().groupPermissions;
|
|
101
102
|
|
|
102
103
|
const {
|
|
103
104
|
activePanelType,
|
|
@@ -131,27 +132,20 @@ export const ChatComposer = memo(
|
|
|
131
132
|
onAttachmentOpenChange: setIsAttachmentOpen,
|
|
132
133
|
});
|
|
133
134
|
|
|
134
|
-
const handleSend =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
if (compose.isUploading || !compose.canSend) return;
|
|
149
|
-
try {
|
|
150
|
-
await compose.send();
|
|
151
|
-
} catch (error) {
|
|
152
|
-
if (__DEV__) console.error('[ChatComposer] Failed to send:', error);
|
|
135
|
+
const handleSend = useGuardedAction(
|
|
136
|
+
GroupPermission.SendMessage,
|
|
137
|
+
useCallback(async () => {
|
|
138
|
+
if (compose.isUploading || !compose.canSend) return;
|
|
139
|
+
try {
|
|
140
|
+
await compose.send();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (__DEV__) console.error('[ChatComposer] Failed to send:', error);
|
|
143
|
+
}
|
|
144
|
+
}, [compose]),
|
|
145
|
+
{
|
|
146
|
+
deniedMessage: trans('chat:no_send_permission'),
|
|
153
147
|
}
|
|
154
|
-
|
|
148
|
+
);
|
|
155
149
|
|
|
156
150
|
const handleInputFocus = useCallback(() => {
|
|
157
151
|
setIsInputFocused(true);
|
|
@@ -409,7 +403,7 @@ export const ChatComposer = memo(
|
|
|
409
403
|
conversationFlow?.flowCurrentStep?.type === ReactFlowsNodeTypes.Start &&
|
|
410
404
|
isEmptyMessage;
|
|
411
405
|
|
|
412
|
-
if (isDisableCompose) {
|
|
406
|
+
if (isDisableCompose && !isGroupConversation) {
|
|
413
407
|
content = (
|
|
414
408
|
<KContainer.View row alignItems justifyContent>
|
|
415
409
|
<KContainer.View marginR="0.25rem">
|
|
@@ -441,14 +435,17 @@ export const ChatComposer = memo(
|
|
|
441
435
|
content = (
|
|
442
436
|
<KContainer.View background={'#F9F9FA'}>
|
|
443
437
|
<ChatCurrentQuotedMessage />
|
|
444
|
-
<
|
|
445
|
-
visible
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
438
|
+
<KContainer.VisibleView
|
|
439
|
+
visible={conversation?.peerType === PeerType.Bot}
|
|
440
|
+
>
|
|
441
|
+
<ChatQuickActions
|
|
442
|
+
visible
|
|
443
|
+
actions={DEFAULT_QUICK_ACTIONS}
|
|
444
|
+
onActionPress={(action) =>
|
|
445
|
+
console.log('[ChatComposer] quick action:', action.id)
|
|
446
|
+
}
|
|
447
|
+
/>
|
|
448
|
+
</KContainer.VisibleView>
|
|
452
449
|
{compose.showLinkPreview && (
|
|
453
450
|
<ChatLinkPreview
|
|
454
451
|
metadata={compose.urlMetadata}
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
} from '../../hooks/conversation/useGetConversationDetail';
|
|
33
33
|
import type { MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
34
34
|
import { useMessages } from '../../hooks/message/useMessages';
|
|
35
|
-
import { ChatEvent } from '../../types/events';
|
|
35
|
+
import { ChatEvent, type GroupRemovedPayload } from '../../types/events';
|
|
36
36
|
import { useSetIsShowFLashMessage } from '../../context/global';
|
|
37
37
|
|
|
38
38
|
const ChatDetail = memo(
|
|
@@ -112,6 +112,17 @@ const ChatDetail = memo(
|
|
|
112
112
|
};
|
|
113
113
|
}, [conversationId, targetUserId, targetGroupId, setShowFLashMessage]);
|
|
114
114
|
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
const sub = DeviceEventEmitter.addListener(
|
|
117
|
+
ChatEvent.GroupRemoved,
|
|
118
|
+
(payload: GroupRemovedPayload) => {
|
|
119
|
+
if (payload.groupID !== targetGroupId) return;
|
|
120
|
+
(onBack ?? handleBackDefault)();
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
return () => sub.remove();
|
|
124
|
+
}, [targetGroupId, onBack, handleBackDefault]);
|
|
125
|
+
|
|
115
126
|
const resolvedShowAddMember =
|
|
116
127
|
showAddMember ?? shouldShowAddMember(conversation);
|
|
117
128
|
|
|
@@ -12,6 +12,8 @@ import type { ChatDetailHeaderProps } from './types';
|
|
|
12
12
|
import { useConversation } from '../../store';
|
|
13
13
|
import { getConversationSubtitle } from './conversationHeader.utils';
|
|
14
14
|
import { useGetUsersInfo } from '../../hooks/users/useGetUsersInfo';
|
|
15
|
+
import { useGetGroupInfo } from '../../hooks/conversation/useGetGroupInfo';
|
|
16
|
+
import { isGroupConversation } from '../../utils/conversation';
|
|
15
17
|
|
|
16
18
|
const ChatDetailHeader = memo(
|
|
17
19
|
({
|
|
@@ -28,7 +30,10 @@ const ChatDetailHeader = memo(
|
|
|
28
30
|
const { data: usersInfo } = useGetUsersInfo(
|
|
29
31
|
targetUserId ? [targetUserId] : []
|
|
30
32
|
);
|
|
31
|
-
|
|
33
|
+
const isGroup = isGroupConversation(conversation);
|
|
34
|
+
const { data: groupInfo } = useGetGroupInfo(
|
|
35
|
+
isGroup ? conversation?.groupID : undefined
|
|
36
|
+
);
|
|
32
37
|
const { faceURL, showName } = useMemo(() => {
|
|
33
38
|
const userInfo = usersInfo?.[0];
|
|
34
39
|
const resolvedFaceURL = conversation?.faceURL || userInfo?.faceURL;
|
|
@@ -103,6 +108,7 @@ const ChatDetailHeader = memo(
|
|
|
103
108
|
avatar={faceURL || null}
|
|
104
109
|
fullName={showName || ''}
|
|
105
110
|
peerType={conversation?.peerType}
|
|
111
|
+
members={conversation?.members}
|
|
106
112
|
/>
|
|
107
113
|
|
|
108
114
|
<KContainer.View flex marginL="0.5rem">
|
|
@@ -123,14 +129,24 @@ const ChatDetailHeader = memo(
|
|
|
123
129
|
color={KColors.palette.gray.w500}
|
|
124
130
|
numberOfLines={1}
|
|
125
131
|
>
|
|
126
|
-
{getConversationSubtitle(
|
|
132
|
+
{getConversationSubtitle(
|
|
133
|
+
conversation,
|
|
134
|
+
groupInfo?.memberCount
|
|
135
|
+
)}
|
|
127
136
|
</KLabel.Text>
|
|
128
137
|
)}
|
|
129
138
|
</KContainer.View>
|
|
130
139
|
</KContainer.Touchable>
|
|
131
140
|
</KContainer.View>
|
|
132
141
|
),
|
|
133
|
-
[
|
|
142
|
+
[
|
|
143
|
+
onBack,
|
|
144
|
+
onPressAvatar,
|
|
145
|
+
conversation,
|
|
146
|
+
faceURL,
|
|
147
|
+
showName,
|
|
148
|
+
groupInfo?.memberCount,
|
|
149
|
+
]
|
|
134
150
|
);
|
|
135
151
|
|
|
136
152
|
return (
|
|
@@ -18,18 +18,26 @@ export const getConversationAvatarUri = (
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export const getConversationSubtitle = (
|
|
21
|
-
conversation?: ConversationItem
|
|
21
|
+
conversation?: ConversationItem,
|
|
22
|
+
memberCount?: number
|
|
22
23
|
): string | undefined => {
|
|
23
24
|
if (!conversation) {
|
|
24
25
|
return undefined;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
if (conversation.peerType === PeerType.Group) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
if (typeof memberCount !== 'number') {
|
|
30
|
+
return 'Nhấn để xem thông tin';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// const unreadLabel =
|
|
34
|
+
// (conversation.unreadCount ?? 0) > 0
|
|
35
|
+
// ? `${conversation.unreadCount} tin nhắn mới`
|
|
36
|
+
// : 'Không có tin mới';
|
|
37
|
+
// if (memberCount == null) {
|
|
38
|
+
// return unreadLabel;
|
|
39
|
+
// }
|
|
40
|
+
return `${memberCount} thành viên`;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
if (conversation.peerType === PeerType.Customer) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KContainer, KImage, KColors } from '@droppii/libs';
|
|
1
|
+
import { KContainer, KImage, KColors, KSpacingValue } from '@droppii/libs';
|
|
2
2
|
import { StyleSheet } from 'react-native';
|
|
3
3
|
import { memo, useCallback, useMemo } from 'react';
|
|
4
4
|
import type { ComponentType, PropsWithChildren, ReactNode } from 'react';
|
|
@@ -14,11 +14,13 @@ import { FileMessage } from '../../../components/messages/fileMessage';
|
|
|
14
14
|
import { LinkMessage } from '../../../components/messages/linkMessage';
|
|
15
15
|
import { MergedMessage } from '../../../components/messages/mergedMessage';
|
|
16
16
|
import { RevokedMessage } from '../../../components/messages/revokedMessage';
|
|
17
|
+
import { GroupNotificationMessage } from '../../../components/messages/groupNotificationMessage';
|
|
17
18
|
import { MessageStatusIndicator } from '../../../components/messages/MessageStatusIndicator';
|
|
18
19
|
import type { BaseLegendMessageProps } from '../../../components/messages/types';
|
|
19
20
|
import { messageStyles } from './messageTypes';
|
|
20
21
|
import QuotedMessage from '../../../components/messages/quotedMessage';
|
|
21
22
|
import { StickerMessage } from '../../../components/messages/stickerMessage';
|
|
23
|
+
import { GROUP_NOTIFICATION_TEXT } from '../../../utils/messageUtils';
|
|
22
24
|
|
|
23
25
|
type MessageProps = BaseLegendMessageProps & {
|
|
24
26
|
onPressUrl?: (url: string) => void;
|
|
@@ -38,6 +40,16 @@ interface LegendChatMessageProps {
|
|
|
38
40
|
isHighlighted?: boolean;
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
// Notification-style messages (group created/renamed/member changes/etc.)
|
|
44
|
+
// are system banners, not user-authored text — they render centered,
|
|
45
|
+
// full-width, without a bubble, avatar/status chrome, or left/right
|
|
46
|
+
// alignment (see SYSTEM_MESSAGE_TYPES below). Driven by the same lookup
|
|
47
|
+
// messageUtils uses to build their preview/thread text, so adding a new
|
|
48
|
+
// notification content type there is enough to cover both places.
|
|
49
|
+
const GROUP_NOTIFICATION_TYPES = Object.keys(GROUP_NOTIFICATION_TEXT).map(
|
|
50
|
+
Number
|
|
51
|
+
) as MessageType[];
|
|
52
|
+
|
|
41
53
|
const messageComponentMap: Partial<
|
|
42
54
|
Record<MessageType, ComponentType<MessageProps>>
|
|
43
55
|
> = {
|
|
@@ -51,6 +63,9 @@ const messageComponentMap: Partial<
|
|
|
51
63
|
[MessageType.MergeMessage]: MergedMessage,
|
|
52
64
|
[MessageType.RevokeMessage]: RevokedMessage,
|
|
53
65
|
[MessageType.StickerMessage]: StickerMessage,
|
|
66
|
+
...Object.fromEntries(
|
|
67
|
+
GROUP_NOTIFICATION_TYPES.map((type) => [type, GroupNotificationMessage])
|
|
68
|
+
),
|
|
54
69
|
};
|
|
55
70
|
|
|
56
71
|
const MEDIA_TYPES: number[] = [
|
|
@@ -59,6 +74,8 @@ const MEDIA_TYPES: number[] = [
|
|
|
59
74
|
MessageType.MergeMessage,
|
|
60
75
|
];
|
|
61
76
|
|
|
77
|
+
const SYSTEM_MESSAGE_TYPES: number[] = GROUP_NOTIFICATION_TYPES;
|
|
78
|
+
|
|
62
79
|
export const LegendChatMessage = memo(
|
|
63
80
|
({
|
|
64
81
|
message,
|
|
@@ -86,6 +103,20 @@ export const LegendChatMessage = memo(
|
|
|
86
103
|
return customNode;
|
|
87
104
|
}
|
|
88
105
|
|
|
106
|
+
if (SYSTEM_MESSAGE_TYPES.includes(message?.contentType)) {
|
|
107
|
+
const SystemMessageComponent =
|
|
108
|
+
messageComponentMap[message.contentType] ?? TextMessage;
|
|
109
|
+
return (
|
|
110
|
+
<KContainer.View center style={styles.systemMessageWrapper}>
|
|
111
|
+
<SystemMessageComponent
|
|
112
|
+
message={message}
|
|
113
|
+
isOutgoing={isOutgoing}
|
|
114
|
+
createdAtTime={createdAtTime}
|
|
115
|
+
/>
|
|
116
|
+
</KContainer.View>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
89
120
|
const MessageComponent: ComponentType<MessageProps> =
|
|
90
121
|
messageComponentMap[message?.contentType] ?? TextMessage;
|
|
91
122
|
|
|
@@ -134,6 +165,10 @@ const styles = StyleSheet.create({
|
|
|
134
165
|
content: {
|
|
135
166
|
position: 'relative',
|
|
136
167
|
},
|
|
168
|
+
systemMessageWrapper: {
|
|
169
|
+
width: '100%',
|
|
170
|
+
marginVertical: KSpacingValue['0.25rem'],
|
|
171
|
+
},
|
|
137
172
|
pinBadge: {
|
|
138
173
|
position: 'absolute',
|
|
139
174
|
// Capped at the inter-row gap (messageRowLeft/Right's marginBottom) so the
|
|
@@ -20,7 +20,11 @@ import { MessageToolboxWrapper } from './MessageToolboxWrapper';
|
|
|
20
20
|
import type { ReactNode } from 'react';
|
|
21
21
|
import { useSendMessage } from '../../../hooks/message/useSendMessage';
|
|
22
22
|
import { useChatDetailContext } from '../../../context';
|
|
23
|
-
import
|
|
23
|
+
import {
|
|
24
|
+
GroupPermission,
|
|
25
|
+
type MessageItem,
|
|
26
|
+
} from '@droppii/openim-rn-client-sdk';
|
|
27
|
+
import { useHasGroupPermission } from '../../../hooks/useGroupPermission';
|
|
24
28
|
|
|
25
29
|
interface MessageToolboxOverlayProps {
|
|
26
30
|
currentUserId?: string;
|
|
@@ -51,6 +55,7 @@ export const MessageToolboxOverlay = forwardRef<
|
|
|
51
55
|
[message, currentUserId]
|
|
52
56
|
);
|
|
53
57
|
const { retryMessage, revokeMessage } = useSendMessage();
|
|
58
|
+
const isPinAllowed = useHasGroupPermission(GroupPermission.PinMessage);
|
|
54
59
|
|
|
55
60
|
const hide = useCallback(() => {
|
|
56
61
|
setVisible(false);
|
|
@@ -173,6 +178,8 @@ export const MessageToolboxOverlay = forwardRef<
|
|
|
173
178
|
toolbox={
|
|
174
179
|
<MessageToolboxMenu
|
|
175
180
|
{...toolboxItems}
|
|
181
|
+
showPinMessage={toolboxItems.showPinMessage && isPinAllowed}
|
|
182
|
+
showUnpinMessage={toolboxItems.showUnpinMessage && isPinAllowed}
|
|
176
183
|
onCopyMessage={handleCopyMessage}
|
|
177
184
|
onCopyMessageLink={handleCopyMessageLink}
|
|
178
185
|
onPinMessage={handlePinMessage}
|