@droppii-org/chat-mobile 0.2.66 → 0.2.67
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 +120 -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/useSendMessage.js +6 -3
- 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/screens/ChatInfo/ChatInfoProfile.js +4 -2
- package/lib/module/screens/ChatInfo/ChatInfoProfile.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatComposer.js +11 -6
- 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/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 +31 -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/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
- 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/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 +175 -23
- package/src/hooks/conversation/useGetGroupInfo.ts +2 -0
- package/src/hooks/message/useSendMessage.ts +11 -3
- package/src/hooks/useConversationList.ts +17 -7
- package/src/screens/ChatInfo/ChatInfoProfile.tsx +6 -3
- package/src/screens/chat-detail/ChatComposer.tsx +19 -10
- 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/inbox/MessagesTab.tsx +0 -1
- package/src/store/conversation.ts +62 -17
- package/src/translation/resources/i18n.ts +31 -0
- package/src/types/events.ts +6 -0
- package/src/utils/messageUtils.ts +103 -1
|
@@ -2,12 +2,15 @@ import OpenIMSDK, {
|
|
|
2
2
|
MessageReceiveOptType,
|
|
3
3
|
MessageType,
|
|
4
4
|
OpenIMEvent,
|
|
5
|
+
SessionType,
|
|
5
6
|
type ConversationItem,
|
|
7
|
+
type GroupItem,
|
|
6
8
|
type GroupMemberItem,
|
|
7
9
|
type MessageItem,
|
|
8
10
|
type MessagePinned,
|
|
9
11
|
type RevokedInfo,
|
|
10
12
|
} from '@droppii/openim-rn-client-sdk';
|
|
13
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
11
14
|
import { useCallback, useEffect } from 'react';
|
|
12
15
|
import { useConversationStore } from '../store/conversation';
|
|
13
16
|
import { ToastFlashMessage } from '../utils/toastFlashMessage';
|
|
@@ -18,22 +21,31 @@ import isEmpty from 'lodash/isEmpty';
|
|
|
18
21
|
import { markMessageRevoked, useMessageStore } from '../store/message';
|
|
19
22
|
import { syncPinnedMessagesOnEvent } from '../hooks/message/usePinMessage';
|
|
20
23
|
import { belongsToConversation } from '../utils/message';
|
|
24
|
+
import { getConversationID } from '../utils/conversation';
|
|
21
25
|
import { DeviceEventEmitter } from 'react-native';
|
|
22
26
|
import { ChatEvent } from '../types/events';
|
|
27
|
+
import { conversationQueryKeys } from '../hooks/query-keys';
|
|
23
28
|
|
|
24
29
|
export const useChatListener = () => {
|
|
25
30
|
const updateOneMessage = useMessageStore((state) => state.updateOneMessage);
|
|
26
31
|
const currentUserId = useUserStore((s) => s.user?.userID);
|
|
27
32
|
const onPressFlashMessage = useChatContext()?.onPressFlashMessage;
|
|
28
33
|
const isShowFLashMessage = useChatContext()?.isShowFLashMessage;
|
|
34
|
+
const queryClient = useQueryClient();
|
|
29
35
|
|
|
30
36
|
const onSendMessageProgress = useCallback(
|
|
31
|
-
({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
({
|
|
38
|
+
progress: _progress,
|
|
39
|
+
message,
|
|
40
|
+
}: {
|
|
41
|
+
progress: number;
|
|
42
|
+
message: MessageItem;
|
|
43
|
+
}) => {
|
|
44
|
+
// console.log('[Listener] SendMessageProgress', {
|
|
45
|
+
// clientMsgID: message.clientMsgID,
|
|
46
|
+
// status: message.status,
|
|
47
|
+
// progress,
|
|
48
|
+
// });
|
|
37
49
|
updateOneMessage(message);
|
|
38
50
|
},
|
|
39
51
|
[updateOneMessage]
|
|
@@ -41,6 +53,7 @@ export const useChatListener = () => {
|
|
|
41
53
|
|
|
42
54
|
const onConversationChanged = useCallback(
|
|
43
55
|
(conversations: ConversationItem[]) => {
|
|
56
|
+
// console.log('[Listener] OnConversationChanged', conversations);
|
|
44
57
|
const oldMap = useConversationStore.getState().map;
|
|
45
58
|
const visibleConversations: ConversationItem[] = [];
|
|
46
59
|
|
|
@@ -87,26 +100,156 @@ export const useChatListener = () => {
|
|
|
87
100
|
);
|
|
88
101
|
|
|
89
102
|
const onNewConversation = useCallback((conversations: ConversationItem[]) => {
|
|
90
|
-
console.log(
|
|
103
|
+
// console.log(
|
|
104
|
+
// '[Listener] OnNewConversation',
|
|
105
|
+
// conversations.map((c) => ({
|
|
106
|
+
// conversationID: c.conversationID,
|
|
107
|
+
// groupID: c.groupID,
|
|
108
|
+
// isNotInGroup: c.isNotInGroup,
|
|
109
|
+
// isGuardedRemoved: c.groupID
|
|
110
|
+
// ? useConversationStore.getState().removedGroupIds.has(c.groupID)
|
|
111
|
+
// : false,
|
|
112
|
+
// }))
|
|
113
|
+
// );
|
|
91
114
|
useConversationStore.getState().updateConversations(conversations);
|
|
92
115
|
}, []);
|
|
93
116
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
groupID:
|
|
98
|
-
|
|
99
|
-
|
|
117
|
+
const onGroupInfoChanged = useCallback(
|
|
118
|
+
(group: GroupItem) => {
|
|
119
|
+
// console.log('[Listener] OnGroupInfoChanged', {
|
|
120
|
+
// groupID: group.groupID,
|
|
121
|
+
// memberCount: group.memberCount,
|
|
122
|
+
// });
|
|
123
|
+
queryClient.setQueryData(
|
|
124
|
+
conversationQueryKeys.groupInfo(group.groupID),
|
|
125
|
+
group
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
[queryClient]
|
|
129
|
+
);
|
|
100
130
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
131
|
+
const onGroupMemberAdded = useCallback(
|
|
132
|
+
(member: GroupMemberItem) => {
|
|
133
|
+
// console.log('[Listener] OnGroupMemberAdded', {
|
|
134
|
+
// userID: member.userID,
|
|
135
|
+
// groupID: member.groupID,
|
|
136
|
+
// isCurrentUser: member.userID === currentUserId,
|
|
137
|
+
// });
|
|
138
|
+
queryClient.invalidateQueries({
|
|
139
|
+
queryKey: conversationQueryKeys.groupInfo(member.groupID),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// The current user (re)joined this group — lift the removal guard so
|
|
143
|
+
// a future OnConversationChanged/OnNewConversation for it isn't dropped.
|
|
144
|
+
if (member.userID === currentUserId) {
|
|
145
|
+
useConversationStore
|
|
146
|
+
.getState()
|
|
147
|
+
.restoreGroupConversation(member.groupID);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
[queryClient, currentUserId]
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const removeGroupConversation = useCallback(
|
|
154
|
+
(
|
|
155
|
+
groupID: string,
|
|
156
|
+
groupName: string | undefined,
|
|
157
|
+
reason: 'left' | 'dismissed' | 'kicked'
|
|
158
|
+
) => {
|
|
159
|
+
// console.log('[Listener] removeGroupConversation', { groupID, reason });
|
|
160
|
+
|
|
161
|
+
// TEMPORARILY DISABLED: OnConversationChanged/OnNewConversation can
|
|
162
|
+
// still race this removal and resurrect the conversation before the
|
|
163
|
+
// removedGroupIds guard is proven reliable. Flip this back to `true`
|
|
164
|
+
// once that race is confirmed fixed.
|
|
165
|
+
const REMOVE_FLOW_ENABLED: boolean = false;
|
|
166
|
+
if (!REMOVE_FLOW_ENABLED) return;
|
|
167
|
+
|
|
168
|
+
const conversationID = getConversationID(SessionType.Group, groupID);
|
|
169
|
+
const conversation = useConversationStore.getState().map[conversationID];
|
|
170
|
+
const isCurrentConversation =
|
|
171
|
+
useConversationStore.getState().currentConversationId ===
|
|
172
|
+
conversationID;
|
|
173
|
+
|
|
174
|
+
// Marks groupID as removed so any late-arriving conversation-sync event
|
|
175
|
+
// for it (see onConversationChanged/onNewConversation) is dropped
|
|
176
|
+
// instead of silently resurrecting the conversation we just removed.
|
|
177
|
+
useConversationStore.getState().removeGroupConversation(groupID);
|
|
178
|
+
|
|
179
|
+
queryClient.removeQueries({
|
|
180
|
+
queryKey: conversationQueryKeys.groupInfo(groupID),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
ToastFlashMessage.show({
|
|
184
|
+
key: `group-removed-${groupID}`,
|
|
185
|
+
title: groupName || conversation?.showName || 'Nhóm',
|
|
186
|
+
body:
|
|
187
|
+
reason === 'dismissed'
|
|
188
|
+
? 'Nhóm đã bị giải tán'
|
|
189
|
+
: reason === 'kicked'
|
|
190
|
+
? 'Bạn đã bị mời khỏi nhóm'
|
|
191
|
+
: 'Bạn không còn trong nhóm này',
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
if (isCurrentConversation) {
|
|
195
|
+
DeviceEventEmitter.emit(ChatEvent.GroupRemoved, {
|
|
196
|
+
groupID,
|
|
197
|
+
reason,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
[queryClient]
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const onGroupMemberDeleted = useCallback(
|
|
205
|
+
(member: GroupMemberItem) => {
|
|
206
|
+
// console.log('[Listener] OnGroupMemberDeleted', {
|
|
207
|
+
// userID: member.userID,
|
|
208
|
+
// groupID: member.groupID,
|
|
209
|
+
// isCurrentUser: member.userID === currentUserId,
|
|
210
|
+
// });
|
|
211
|
+
queryClient.invalidateQueries({
|
|
212
|
+
queryKey: conversationQueryKeys.groupInfo(member.groupID),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// The removed member is the current user themselves — they were kicked,
|
|
216
|
+
// so their conversation for this group must be dropped like a leave/dismiss.
|
|
217
|
+
if (member.userID === currentUserId) {
|
|
218
|
+
removeGroupConversation(member.groupID, undefined, 'kicked');
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
[queryClient, currentUserId, removeGroupConversation]
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const onJoinedGroupDeleted = useCallback(
|
|
225
|
+
(group: GroupItem) => {
|
|
226
|
+
// console.log('[Listener] OnJoinedGroupDeleted', {
|
|
227
|
+
// groupID: group.groupID,
|
|
228
|
+
// groupName: group.groupName,
|
|
229
|
+
// });
|
|
230
|
+
removeGroupConversation(group.groupID, group.groupName, 'left');
|
|
231
|
+
},
|
|
232
|
+
[removeGroupConversation]
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const onGroupDismissed = useCallback(
|
|
236
|
+
(group: GroupItem) => {
|
|
237
|
+
// console.log('[Listener] OnGroupDismissed', {
|
|
238
|
+
// groupID: group.groupID,
|
|
239
|
+
// groupName: group.groupName,
|
|
240
|
+
// });
|
|
241
|
+
removeGroupConversation(group.groupID, group.groupName, 'dismissed');
|
|
242
|
+
},
|
|
243
|
+
[removeGroupConversation]
|
|
244
|
+
);
|
|
107
245
|
|
|
108
246
|
const OnGroupMemberInfoChanged = useCallback(
|
|
109
247
|
(member: GroupMemberItem) => {
|
|
248
|
+
// console.log('[Listener] OnGroupMemberInfoChanged', {
|
|
249
|
+
// userID: member.userID,
|
|
250
|
+
// groupID: member.groupID,
|
|
251
|
+
// permissions: member.permissions,
|
|
252
|
+
// });
|
|
110
253
|
const conversation = useConversationStore
|
|
111
254
|
.getState()
|
|
112
255
|
.getCurrentConversation();
|
|
@@ -148,7 +291,7 @@ export const useChatListener = () => {
|
|
|
148
291
|
);
|
|
149
292
|
|
|
150
293
|
const onRecvNewMessages = useCallback((messages: MessageItem[]) => {
|
|
151
|
-
console.log('[Listener] OnRecvNewMessages', messages);
|
|
294
|
+
// console.log('[Listener] OnRecvNewMessages', messages);
|
|
152
295
|
const conversationId =
|
|
153
296
|
useConversationStore.getState().currentConversationId;
|
|
154
297
|
const targetUserId = useConversationStore.getState().currentUserId;
|
|
@@ -164,8 +307,8 @@ export const useChatListener = () => {
|
|
|
164
307
|
relevant.forEach((msg) => useMessageStore.getState().pushNewMessage(msg));
|
|
165
308
|
}, []);
|
|
166
309
|
|
|
167
|
-
const onRecvOfflineNewMessages = useCallback((
|
|
168
|
-
console.log('[Listener] onRecvOfflineNewMessages',
|
|
310
|
+
const onRecvOfflineNewMessages = useCallback((_messages: MessageItem[]) => {
|
|
311
|
+
// console.log('[Listener] onRecvOfflineNewMessages', _messages);
|
|
169
312
|
}, []);
|
|
170
313
|
|
|
171
314
|
useEffect(() => {
|
|
@@ -174,12 +317,15 @@ export const useChatListener = () => {
|
|
|
174
317
|
OpenIMSDK.on(OpenIMEvent.OnNewConversation, onNewConversation);
|
|
175
318
|
|
|
176
319
|
// Group members
|
|
320
|
+
OpenIMSDK.on(OpenIMEvent.OnGroupInfoChanged, onGroupInfoChanged);
|
|
177
321
|
OpenIMSDK.on(OpenIMEvent.OnGroupMemberAdded, onGroupMemberAdded);
|
|
178
322
|
OpenIMSDK.on(OpenIMEvent.OnGroupMemberDeleted, onGroupMemberDeleted);
|
|
179
323
|
OpenIMSDK.on(
|
|
180
324
|
OpenIMEvent.OnGroupMemberInfoChanged,
|
|
181
325
|
OnGroupMemberInfoChanged
|
|
182
326
|
);
|
|
327
|
+
OpenIMSDK.on(OpenIMEvent.OnJoinedGroupDeleted, onJoinedGroupDeleted);
|
|
328
|
+
OpenIMSDK.on(OpenIMEvent.OnGroupDismissed, onGroupDismissed);
|
|
183
329
|
|
|
184
330
|
// Message status
|
|
185
331
|
OpenIMSDK.on(OpenIMEvent.SendMessageProgress, onSendMessageProgress);
|
|
@@ -197,12 +343,15 @@ export const useChatListener = () => {
|
|
|
197
343
|
OpenIMSDK.off(OpenIMEvent.OnNewConversation, onNewConversation);
|
|
198
344
|
|
|
199
345
|
// Group members
|
|
346
|
+
OpenIMSDK.off(OpenIMEvent.OnGroupInfoChanged, onGroupInfoChanged);
|
|
200
347
|
OpenIMSDK.off(OpenIMEvent.OnGroupMemberAdded, onGroupMemberAdded);
|
|
201
348
|
OpenIMSDK.off(OpenIMEvent.OnGroupMemberDeleted, onGroupMemberDeleted);
|
|
202
|
-
OpenIMSDK.
|
|
349
|
+
OpenIMSDK.off(
|
|
203
350
|
OpenIMEvent.OnGroupMemberInfoChanged,
|
|
204
351
|
OnGroupMemberInfoChanged
|
|
205
352
|
);
|
|
353
|
+
OpenIMSDK.off(OpenIMEvent.OnJoinedGroupDeleted, onJoinedGroupDeleted);
|
|
354
|
+
OpenIMSDK.off(OpenIMEvent.OnGroupDismissed, onGroupDismissed);
|
|
206
355
|
|
|
207
356
|
// Message status
|
|
208
357
|
OpenIMSDK.off(OpenIMEvent.SendMessageProgress, onSendMessageProgress);
|
|
@@ -216,6 +365,7 @@ export const useChatListener = () => {
|
|
|
216
365
|
};
|
|
217
366
|
}, [
|
|
218
367
|
onConversationChanged,
|
|
368
|
+
onGroupInfoChanged,
|
|
219
369
|
onGroupMemberAdded,
|
|
220
370
|
onGroupMemberDeleted,
|
|
221
371
|
onMessagePinned,
|
|
@@ -225,5 +375,7 @@ export const useChatListener = () => {
|
|
|
225
375
|
onRecvNewMessages,
|
|
226
376
|
onRecvOfflineNewMessages,
|
|
227
377
|
OnGroupMemberInfoChanged,
|
|
378
|
+
onJoinedGroupDeleted,
|
|
379
|
+
onGroupDismissed,
|
|
228
380
|
]);
|
|
229
381
|
};
|
|
@@ -268,14 +268,22 @@ export const generateOfflinePushInfo = (message: MessageItem) => {
|
|
|
268
268
|
const conversationData = useConversationStore
|
|
269
269
|
?.getState?.()
|
|
270
270
|
?.getCurrentConversation();
|
|
271
|
-
const
|
|
272
|
-
const
|
|
271
|
+
const operator = useUserStore.getState().user;
|
|
272
|
+
const operatorUserID = operator?.userID || '';
|
|
273
|
+
const isSingleChat =
|
|
274
|
+
conversationData?.conversationType === SessionType.Single;
|
|
275
|
+
const title = isSingleChat
|
|
276
|
+
? operator?.nickname || 'Droppii'
|
|
277
|
+
: conversationData?.showName || 'Droppii';
|
|
278
|
+
const icon = isSingleChat
|
|
279
|
+
? operator?.faceURL || ''
|
|
280
|
+
: conversationData?.faceURL || '';
|
|
273
281
|
const desc = getMessagePreview(message);
|
|
274
282
|
return {
|
|
275
283
|
title,
|
|
276
284
|
desc,
|
|
277
285
|
ex: JSON.stringify({
|
|
278
|
-
icon
|
|
286
|
+
icon,
|
|
279
287
|
conversationId: conversationData?.conversationID || '',
|
|
280
288
|
userId: operatorUserID,
|
|
281
289
|
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
|
|
|
@@ -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,7 +63,11 @@ 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 {
|
|
66
|
+
import {
|
|
67
|
+
GroupPermission,
|
|
68
|
+
PeerType,
|
|
69
|
+
SessionType,
|
|
70
|
+
} from '@droppii/openim-rn-client-sdk';
|
|
67
71
|
import { useConversationStore } from '../../store';
|
|
68
72
|
|
|
69
73
|
interface ChatComposerInternalProps {
|
|
@@ -88,10 +92,12 @@ export const ChatComposer = memo(
|
|
|
88
92
|
|
|
89
93
|
const {
|
|
90
94
|
isBotConversation,
|
|
95
|
+
conversation,
|
|
91
96
|
conversationFlow,
|
|
92
97
|
conversationFlowMainMenu,
|
|
93
98
|
flowState,
|
|
94
99
|
} = useChatDetailContext();
|
|
100
|
+
const isGroupConversation = conversation?.peerType === PeerType.Group;
|
|
95
101
|
const { mutate: runFlow, isPending } = useRunFlow();
|
|
96
102
|
const { mutate: resetFlow } = useResetFlow();
|
|
97
103
|
const { mutateAsync: closeSupportSession } = useCloseSupportSession();
|
|
@@ -409,7 +415,7 @@ export const ChatComposer = memo(
|
|
|
409
415
|
conversationFlow?.flowCurrentStep?.type === ReactFlowsNodeTypes.Start &&
|
|
410
416
|
isEmptyMessage;
|
|
411
417
|
|
|
412
|
-
if (isDisableCompose) {
|
|
418
|
+
if (isDisableCompose && !isGroupConversation) {
|
|
413
419
|
content = (
|
|
414
420
|
<KContainer.View row alignItems justifyContent>
|
|
415
421
|
<KContainer.View marginR="0.25rem">
|
|
@@ -441,14 +447,17 @@ export const ChatComposer = memo(
|
|
|
441
447
|
content = (
|
|
442
448
|
<KContainer.View background={'#F9F9FA'}>
|
|
443
449
|
<ChatCurrentQuotedMessage />
|
|
444
|
-
<
|
|
445
|
-
visible
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
450
|
+
<KContainer.VisibleView
|
|
451
|
+
visible={conversation?.peerType === PeerType.Bot}
|
|
452
|
+
>
|
|
453
|
+
<ChatQuickActions
|
|
454
|
+
visible
|
|
455
|
+
actions={DEFAULT_QUICK_ACTIONS}
|
|
456
|
+
onActionPress={(action) =>
|
|
457
|
+
console.log('[ChatComposer] quick action:', action.id)
|
|
458
|
+
}
|
|
459
|
+
/>
|
|
460
|
+
</KContainer.VisibleView>
|
|
452
461
|
{compose.showLinkPreview && (
|
|
453
462
|
<ChatLinkPreview
|
|
454
463
|
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
|