@droppii-org/chat-mobile 0.2.74 → 0.2.76
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/GroupPermissionGate/GroupPermissionGate.js +33 -0
- package/lib/module/components/GroupPermissionGate/GroupPermissionGate.js.map +1 -0
- package/lib/module/components/GroupPermissionGate/index.js +4 -0
- package/lib/module/components/GroupPermissionGate/index.js.map +1 -0
- package/lib/module/context/ChatDetailContext.js +15 -11
- package/lib/module/context/ChatDetailContext.js.map +1 -1
- package/lib/module/core/useChatListener.js +31 -45
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/hooks/groups/useDismissGroup.js +79 -0
- package/lib/module/hooks/groups/useDismissGroup.js.map +1 -0
- package/lib/module/hooks/groups/useJoinGroup.js +1 -3
- package/lib/module/hooks/groups/useJoinGroup.js.map +1 -1
- package/lib/module/hooks/useGroupPermission.js +4 -3
- package/lib/module/hooks/useGroupPermission.js.map +1 -1
- package/lib/module/screens/ChatInfo/ChatInfo.js +6 -3
- package/lib/module/screens/ChatInfo/ChatInfo.js.map +1 -1
- package/lib/module/screens/ChatInfo/ChatInfoActions.js +48 -6
- package/lib/module/screens/ChatInfo/ChatInfoActions.js.map +1 -1
- package/lib/module/store/conversation.js +4 -25
- package/lib/module/store/conversation.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +7 -1
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/typescript/src/components/GroupPermissionGate/GroupPermissionGate.d.ts +19 -0
- package/lib/typescript/src/components/GroupPermissionGate/GroupPermissionGate.d.ts.map +1 -0
- package/lib/typescript/src/components/GroupPermissionGate/index.d.ts +3 -0
- package/lib/typescript/src/components/GroupPermissionGate/index.d.ts.map +1 -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/groups/useDismissGroup.d.ts +8 -0
- package/lib/typescript/src/hooks/groups/useDismissGroup.d.ts.map +1 -0
- package/lib/typescript/src/hooks/groups/useJoinGroup.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useGroupPermission.d.ts.map +1 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts +2 -2
- package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts.map +1 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts +3 -2
- package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts.map +1 -1
- package/lib/typescript/src/screens/ChatInfo/types.d.ts +1 -0
- package/lib/typescript/src/screens/ChatInfo/types.d.ts.map +1 -1
- package/lib/typescript/src/store/conversation.d.ts +1 -10
- 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/chat.d.ts +2 -2
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +2 -4
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/GroupPermissionGate/GroupPermissionGate.tsx +51 -0
- package/src/components/GroupPermissionGate/index.ts +5 -0
- package/src/context/ChatDetailContext.tsx +17 -11
- package/src/core/useChatListener.ts +30 -54
- package/src/hooks/groups/useDismissGroup.tsx +80 -0
- package/src/hooks/groups/useJoinGroup.ts +1 -3
- package/src/hooks/useGroupPermission.ts +5 -4
- package/src/screens/ChatInfo/ChatInfo.tsx +4 -1
- package/src/screens/ChatInfo/ChatInfoActions.tsx +79 -32
- package/src/screens/ChatInfo/types.ts +1 -0
- package/src/store/conversation.ts +3 -31
- package/src/translation/resources/i18n.ts +7 -0
- package/src/types/chat.ts +2 -2
- package/src/types/events.ts +2 -4
|
@@ -15,11 +15,11 @@ import {
|
|
|
15
15
|
} from '../types/chat';
|
|
16
16
|
import { fechtCurrentConversationFlow } from '../hooks/flows/useCurrentConversationFlow';
|
|
17
17
|
import { ChatEvent, type GroupPermissionUpdatedPayload } from '../types/events';
|
|
18
|
-
import { useConversationStore } from '../store';
|
|
18
|
+
import { useConversationStore, useUserStore } from '../store';
|
|
19
19
|
import OpenIMSDK, {
|
|
20
|
-
GroupPermission,
|
|
21
20
|
PeerType,
|
|
22
21
|
type MessageItem,
|
|
22
|
+
type SelfUserInfo,
|
|
23
23
|
} from '@droppii/openim-rn-client-sdk';
|
|
24
24
|
import {
|
|
25
25
|
DFlowContext,
|
|
@@ -40,7 +40,6 @@ export const ChatDetailContext = createContext<ChatDetailContextType>({
|
|
|
40
40
|
conversation: undefined,
|
|
41
41
|
quotedMessage: undefined,
|
|
42
42
|
setQuotedMessage: () => {},
|
|
43
|
-
groupPermissions: [],
|
|
44
43
|
});
|
|
45
44
|
|
|
46
45
|
export const useChatDetailContext = (): ChatDetailContextType => {
|
|
@@ -60,6 +59,9 @@ export const ChatDetailProvider = ({ children }: ChatDetailProviderProps) => {
|
|
|
60
59
|
const conversation = useConversationStore(
|
|
61
60
|
(state) => state.map[conversationId]
|
|
62
61
|
);
|
|
62
|
+
|
|
63
|
+
const user = useUserStore((state) => state.user);
|
|
64
|
+
|
|
63
65
|
const [conversationFlow, setConverationFlow] = useState<
|
|
64
66
|
IConversationFlow | undefined
|
|
65
67
|
>(undefined);
|
|
@@ -68,26 +70,30 @@ export const ChatDetailProvider = ({ children }: ChatDetailProviderProps) => {
|
|
|
68
70
|
undefined
|
|
69
71
|
);
|
|
70
72
|
|
|
71
|
-
const [
|
|
72
|
-
|
|
73
|
+
const [groupUserInfo, setGroupUserInfo] = useState<SelfUserInfo | undefined>(
|
|
74
|
+
undefined
|
|
73
75
|
);
|
|
74
76
|
|
|
77
|
+
const userInfo = useMemo(() => {
|
|
78
|
+
return conversation?.peerType === PeerType.Group ? groupUserInfo : user;
|
|
79
|
+
}, [user, groupUserInfo, conversation?.peerType]);
|
|
80
|
+
|
|
75
81
|
useEffect(() => {
|
|
76
82
|
const groupId = conversation?.groupID;
|
|
77
83
|
if (!groupId) {
|
|
78
|
-
|
|
84
|
+
setGroupUserInfo(undefined);
|
|
79
85
|
return;
|
|
80
86
|
}
|
|
81
87
|
OpenIMSDK.getSelfUserInfo(groupId)
|
|
82
|
-
.then((info) =>
|
|
83
|
-
.catch(() =>
|
|
88
|
+
.then((info) => setGroupUserInfo(info))
|
|
89
|
+
.catch(() => setGroupUserInfo(undefined));
|
|
84
90
|
}, [conversation?.groupID]);
|
|
85
91
|
|
|
86
92
|
useEffect(() => {
|
|
87
93
|
const sub = DeviceEventEmitter.addListener(
|
|
88
94
|
ChatEvent.GroupPermissionUpdated,
|
|
89
|
-
(
|
|
90
|
-
|
|
95
|
+
(next: GroupPermissionUpdatedPayload) => {
|
|
96
|
+
setGroupUserInfo((prev) => ({ ...prev, ...next }) as SelfUserInfo);
|
|
91
97
|
}
|
|
92
98
|
);
|
|
93
99
|
return () => sub.remove();
|
|
@@ -183,7 +189,7 @@ export const ChatDetailProvider = ({ children }: ChatDetailProviderProps) => {
|
|
|
183
189
|
quotedMessage,
|
|
184
190
|
setQuotedMessage,
|
|
185
191
|
conversationFlowMainMenu,
|
|
186
|
-
|
|
192
|
+
userInfo,
|
|
187
193
|
}}
|
|
188
194
|
>
|
|
189
195
|
{children}
|
|
@@ -55,6 +55,8 @@ export const useChatListener = () => {
|
|
|
55
55
|
const onConversationChanged = useCallback(
|
|
56
56
|
(conversations: ConversationItem[]) => {
|
|
57
57
|
// console.log('[Listener] OnConversationChanged', conversations);
|
|
58
|
+
|
|
59
|
+
// return; // TEMPORARILY DISABLED: OnConversationChanged/OnNewConversation can
|
|
58
60
|
const oldMap = useConversationStore.getState().map;
|
|
59
61
|
const visibleConversations: ConversationItem[] = [];
|
|
60
62
|
|
|
@@ -101,17 +103,7 @@ export const useChatListener = () => {
|
|
|
101
103
|
);
|
|
102
104
|
|
|
103
105
|
const onNewConversation = useCallback((conversations: ConversationItem[]) => {
|
|
104
|
-
// console.log(
|
|
105
|
-
// '[Listener] OnNewConversation',
|
|
106
|
-
// conversations.map((c) => ({
|
|
107
|
-
// conversationID: c.conversationID,
|
|
108
|
-
// groupID: c.groupID,
|
|
109
|
-
// isNotInGroup: c.isNotInGroup,
|
|
110
|
-
// isGuardedRemoved: c.groupID
|
|
111
|
-
// ? useConversationStore.getState().removedGroupIds.has(c.groupID)
|
|
112
|
-
// : false,
|
|
113
|
-
// }))
|
|
114
|
-
// );
|
|
106
|
+
// console.log('[Listener] OnNewConversation', conversations);
|
|
115
107
|
useConversationStore.getState().updateConversations(conversations);
|
|
116
108
|
}, []);
|
|
117
109
|
|
|
@@ -139,16 +131,8 @@ export const useChatListener = () => {
|
|
|
139
131
|
queryClient.invalidateQueries({
|
|
140
132
|
queryKey: conversationQueryKeys.groupInfo(member.groupID),
|
|
141
133
|
});
|
|
142
|
-
|
|
143
|
-
// The current user (re)joined this group — lift the removal guard so
|
|
144
|
-
// a future OnConversationChanged/OnNewConversation for it isn't dropped.
|
|
145
|
-
if (member.userID === currentUserId) {
|
|
146
|
-
useConversationStore
|
|
147
|
-
.getState()
|
|
148
|
-
.restoreGroupConversation(member.groupID);
|
|
149
|
-
}
|
|
150
134
|
},
|
|
151
|
-
[queryClient
|
|
135
|
+
[queryClient]
|
|
152
136
|
);
|
|
153
137
|
|
|
154
138
|
const removeGroupConversation = useCallback(
|
|
@@ -159,15 +143,8 @@ export const useChatListener = () => {
|
|
|
159
143
|
) => {
|
|
160
144
|
// console.log('[Listener] removeGroupConversation', { groupID, reason });
|
|
161
145
|
|
|
162
|
-
// TEMPORARILY DISABLED: OnConversationChanged/OnNewConversation can
|
|
163
|
-
// still race this removal and resurrect the conversation before the
|
|
164
|
-
// removedGroupIds guard is proven reliable. Flip this back to `true`
|
|
165
|
-
// once that race is confirmed fixed.
|
|
166
|
-
const REMOVE_FLOW_ENABLED: boolean = false;
|
|
167
|
-
if (!REMOVE_FLOW_ENABLED) return;
|
|
168
|
-
|
|
169
146
|
const conversationID = getConversationID(SessionType.Group, groupID);
|
|
170
|
-
const conversation = useConversationStore.getState().map[conversationID];
|
|
147
|
+
// const conversation = useConversationStore.getState().map[conversationID];
|
|
171
148
|
const isCurrentConversation =
|
|
172
149
|
useConversationStore.getState().currentConversationId ===
|
|
173
150
|
conversationID;
|
|
@@ -180,18 +157,25 @@ export const useChatListener = () => {
|
|
|
180
157
|
queryClient.removeQueries({
|
|
181
158
|
queryKey: conversationQueryKeys.groupInfo(groupID),
|
|
182
159
|
});
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
? 'Nhóm đã bị giải tán'
|
|
190
|
-
: reason === 'kicked'
|
|
191
|
-
? 'Bạn đã bị mời khỏi nhóm'
|
|
192
|
-
: 'Bạn không còn trong nhóm này',
|
|
160
|
+
queryClient.invalidateQueries({
|
|
161
|
+
queryKey: conversationQueryKeys.joinedGroup(groupID),
|
|
162
|
+
});
|
|
163
|
+
queryClient.invalidateQueries({
|
|
164
|
+
queryKey: groupQueryKeys.public(),
|
|
165
|
+
exact: false,
|
|
193
166
|
});
|
|
194
167
|
|
|
168
|
+
// ToastFlashMessage.show({
|
|
169
|
+
// key: `group-removed-${groupID}`,
|
|
170
|
+
// title: groupName || conversation?.showName || 'Nhóm',
|
|
171
|
+
// body:
|
|
172
|
+
// reason === 'dismissed'
|
|
173
|
+
// ? 'Nhóm đã bị giải tán'
|
|
174
|
+
// : reason === 'kicked'
|
|
175
|
+
// ? 'Bạn đã bị mời khỏi nhóm'
|
|
176
|
+
// : 'Bạn không còn trong nhóm này',
|
|
177
|
+
// });
|
|
178
|
+
|
|
195
179
|
if (isCurrentConversation) {
|
|
196
180
|
DeviceEventEmitter.emit(ChatEvent.GroupRemoved, {
|
|
197
181
|
groupID,
|
|
@@ -204,11 +188,7 @@ export const useChatListener = () => {
|
|
|
204
188
|
|
|
205
189
|
const onGroupMemberDeleted = useCallback(
|
|
206
190
|
(member: GroupMemberItem) => {
|
|
207
|
-
|
|
208
|
-
// userID: member.userID,
|
|
209
|
-
// groupID: member.groupID,
|
|
210
|
-
// isCurrentUser: member.userID === currentUserId,
|
|
211
|
-
// });
|
|
191
|
+
console.log('[Listener] OnGroupMemberDeleted', member);
|
|
212
192
|
queryClient.invalidateQueries({
|
|
213
193
|
queryKey: conversationQueryKeys.groupInfo(member.groupID),
|
|
214
194
|
});
|
|
@@ -224,10 +204,7 @@ export const useChatListener = () => {
|
|
|
224
204
|
|
|
225
205
|
const onJoinedGroupDeleted = useCallback(
|
|
226
206
|
(group: GroupItem) => {
|
|
227
|
-
// console.log('[Listener] OnJoinedGroupDeleted',
|
|
228
|
-
// groupID: group.groupID,
|
|
229
|
-
// groupName: group.groupName,
|
|
230
|
-
// });
|
|
207
|
+
// console.log('[Listener] OnJoinedGroupDeleted', group);
|
|
231
208
|
removeGroupConversation(group.groupID, group.groupName, 'left');
|
|
232
209
|
},
|
|
233
210
|
[removeGroupConversation]
|
|
@@ -246,12 +223,7 @@ export const useChatListener = () => {
|
|
|
246
223
|
|
|
247
224
|
const OnGroupMemberInfoChanged = useCallback(
|
|
248
225
|
(member: GroupMemberItem) => {
|
|
249
|
-
|
|
250
|
-
// userID: member.userID,
|
|
251
|
-
// groupID: member.groupID,
|
|
252
|
-
// permissions: member.permissions,
|
|
253
|
-
// roleLevel: member.roleLevel,
|
|
254
|
-
// });
|
|
226
|
+
console.log('[Listener] OnGroupMemberInfoChanged', member);
|
|
255
227
|
const conversation = useConversationStore
|
|
256
228
|
.getState()
|
|
257
229
|
.getCurrentConversation();
|
|
@@ -260,7 +232,11 @@ export const useChatListener = () => {
|
|
|
260
232
|
member?.userID === currentUserId
|
|
261
233
|
) {
|
|
262
234
|
DeviceEventEmitter.emit(ChatEvent.GroupPermissionUpdated, {
|
|
263
|
-
|
|
235
|
+
nickname: member.nickname,
|
|
236
|
+
faceURL: member.faceURL,
|
|
237
|
+
roleLevel: member.roleLevel,
|
|
238
|
+
permissions: member.permissions,
|
|
239
|
+
ex: member.ex,
|
|
264
240
|
});
|
|
265
241
|
}
|
|
266
242
|
},
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import OpenIMSDK from '@droppii/openim-rn-client-sdk';
|
|
3
|
+
import { KContainer, KLabel } from '@droppii/libs';
|
|
4
|
+
import { UIUtils } from '../../utils/UIUtils';
|
|
5
|
+
import { trans } from '../../translation';
|
|
6
|
+
|
|
7
|
+
interface UseDismissGroupProps {
|
|
8
|
+
onSuccess?: () => void;
|
|
9
|
+
}
|
|
10
|
+
export function useDismissGroup({ onSuccess }: UseDismissGroupProps) {
|
|
11
|
+
const openDismissPopup = useCallback(() => {
|
|
12
|
+
return new Promise<void>((resolve, reject) => {
|
|
13
|
+
UIUtils.popup.open({
|
|
14
|
+
header: {
|
|
15
|
+
title: { text: trans('group:dismiss_confirm_title') },
|
|
16
|
+
alignment: 'center',
|
|
17
|
+
},
|
|
18
|
+
body: {
|
|
19
|
+
renderContent: () => {
|
|
20
|
+
return (
|
|
21
|
+
<KContainer.View padding={'1rem'} justifyContent alignItems>
|
|
22
|
+
<KLabel.Text typo="TextMdNormal" textAlign="center">
|
|
23
|
+
{trans('group:dismiss_confirm_desc')}
|
|
24
|
+
</KLabel.Text>
|
|
25
|
+
</KContainer.View>
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
actions: {
|
|
30
|
+
buttons: [
|
|
31
|
+
{
|
|
32
|
+
label: trans('group:dismiss_confirm_secondary'),
|
|
33
|
+
kind: 'light',
|
|
34
|
+
onPress: () => reject(),
|
|
35
|
+
weight: 'medium',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: trans('group:dismiss_confirm_primary'),
|
|
39
|
+
kind: 'danger',
|
|
40
|
+
onPress: () => resolve(),
|
|
41
|
+
isLightSolid: true,
|
|
42
|
+
weight: 'medium',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
type: 'horizontal-button',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const dismissGroup = useCallback(
|
|
52
|
+
async (groupID?: string) => {
|
|
53
|
+
if (!groupID) return;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await openDismissPopup();
|
|
57
|
+
} catch {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await OpenIMSDK.dismissGroup(groupID);
|
|
63
|
+
onSuccess?.();
|
|
64
|
+
UIUtils.toast.open({
|
|
65
|
+
title: trans('group:dismiss_toast_success'),
|
|
66
|
+
theme: 'light',
|
|
67
|
+
});
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('dismissGroup failed', error);
|
|
70
|
+
UIUtils.toast.open({
|
|
71
|
+
title: trans('chat:generic_error'),
|
|
72
|
+
theme: 'error',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[openDismissPopup, onSuccess]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return { dismissGroup };
|
|
80
|
+
}
|
|
@@ -46,9 +46,7 @@ export const useJoinGroup = () => {
|
|
|
46
46
|
|
|
47
47
|
OpenIMSDK.getSelfUserInfo(params.groupID)
|
|
48
48
|
.then((info) => {
|
|
49
|
-
DeviceEventEmitter.emit(ChatEvent.GroupPermissionUpdated,
|
|
50
|
-
permissions: info?.permissions ?? [],
|
|
51
|
-
});
|
|
49
|
+
DeviceEventEmitter.emit(ChatEvent.GroupPermissionUpdated, info);
|
|
52
50
|
})
|
|
53
51
|
.catch(() => {});
|
|
54
52
|
},
|
|
@@ -8,17 +8,18 @@ export const useHasGroupPermission = (
|
|
|
8
8
|
permission: GroupPermission | GroupPermission[],
|
|
9
9
|
mode: 'every' | 'some' = 'every'
|
|
10
10
|
): boolean => {
|
|
11
|
-
const { conversation,
|
|
11
|
+
const { conversation, userInfo } = useChatDetailContext();
|
|
12
12
|
const isNotGroup = conversation?.peerType !== PeerType.Group;
|
|
13
13
|
const required = Array.isArray(permission) ? permission : [permission];
|
|
14
|
+
const granted = userInfo?.permissions || [];
|
|
14
15
|
|
|
15
16
|
return useMemo(() => {
|
|
16
17
|
if (isNotGroup) return true;
|
|
17
18
|
return mode === 'every'
|
|
18
|
-
? required.every((p) =>
|
|
19
|
-
: required.some((p) =>
|
|
19
|
+
? required.every((p) => granted?.includes(p))
|
|
20
|
+
: required.some((p) => granted?.includes(p));
|
|
20
21
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
21
|
-
}, [
|
|
22
|
+
}, [granted, isNotGroup, mode]);
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
export const useGuardedAction = <T extends (...args: any[]) => any>(
|
|
@@ -11,6 +11,7 @@ import ChatInfoActions from './ChatInfoActions';
|
|
|
11
11
|
import type { ChatInfoProps } from './types';
|
|
12
12
|
import { trans } from '../../translation';
|
|
13
13
|
import { isGroupConversation } from '../../utils/conversation';
|
|
14
|
+
import { withChatDetailProvider } from '../../context';
|
|
14
15
|
|
|
15
16
|
const ChatInfo = memo(
|
|
16
17
|
({
|
|
@@ -22,6 +23,7 @@ const ChatInfo = memo(
|
|
|
22
23
|
onPressCreateGroup,
|
|
23
24
|
onPressAddToGroup,
|
|
24
25
|
onPressLeaveGroup,
|
|
26
|
+
onDismissGroup,
|
|
25
27
|
}: ChatInfoProps) => {
|
|
26
28
|
const navigation = useNavigation();
|
|
27
29
|
|
|
@@ -67,6 +69,7 @@ const ChatInfo = memo(
|
|
|
67
69
|
onPressCreateGroup={onPressCreateGroup}
|
|
68
70
|
onPressAddToGroup={onPressAddToGroup}
|
|
69
71
|
onPressLeaveGroup={onPressLeaveGroup}
|
|
72
|
+
onDismissGroup={onDismissGroup}
|
|
70
73
|
/>
|
|
71
74
|
</KContainer.ScrollView>
|
|
72
75
|
</KContainer.Page>
|
|
@@ -74,4 +77,4 @@ const ChatInfo = memo(
|
|
|
74
77
|
}
|
|
75
78
|
);
|
|
76
79
|
|
|
77
|
-
export default ChatInfo;
|
|
80
|
+
export default withChatDetailProvider(ChatInfo);
|
|
@@ -6,9 +6,14 @@ import {
|
|
|
6
6
|
type KListItemBaseItemProps,
|
|
7
7
|
KLabel,
|
|
8
8
|
} from '@droppii/libs';
|
|
9
|
-
import
|
|
9
|
+
import {
|
|
10
|
+
GroupMemberRole,
|
|
11
|
+
type ConversationItem,
|
|
12
|
+
} from '@droppii/openim-rn-client-sdk';
|
|
10
13
|
import { usePinConversation } from '../../hooks/usePinConversation';
|
|
11
14
|
import { trans } from '../../translation';
|
|
15
|
+
import { useChatDetailContext } from '../../context';
|
|
16
|
+
import { useDismissGroup } from '../../hooks/groups/useDismissGroup';
|
|
12
17
|
|
|
13
18
|
interface ChatInfoActionsProps {
|
|
14
19
|
conversation?: ConversationItem;
|
|
@@ -18,6 +23,7 @@ interface ChatInfoActionsProps {
|
|
|
18
23
|
onPressCreateGroup?: () => void;
|
|
19
24
|
onPressAddToGroup?: () => void;
|
|
20
25
|
onPressLeaveGroup?: () => void;
|
|
26
|
+
onDismissGroup?: () => void;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
const ChatInfoActions = memo(
|
|
@@ -29,10 +35,17 @@ const ChatInfoActions = memo(
|
|
|
29
35
|
onPressCreateGroup,
|
|
30
36
|
onPressAddToGroup,
|
|
31
37
|
onPressLeaveGroup,
|
|
38
|
+
onDismissGroup,
|
|
32
39
|
}: ChatInfoActionsProps) => {
|
|
33
40
|
const { pinConversation, unpinConversation } = usePinConversation();
|
|
34
41
|
const isPinned = Boolean(conversation?.isPinned);
|
|
35
42
|
const showName = conversation?.showName || '';
|
|
43
|
+
const { userInfo } = useChatDetailContext();
|
|
44
|
+
const { dismissGroup } = useDismissGroup({ onSuccess: onDismissGroup });
|
|
45
|
+
|
|
46
|
+
const isOwner = useMemo(() => {
|
|
47
|
+
return userInfo?.roleLevel === GroupMemberRole.Owner;
|
|
48
|
+
}, [userInfo?.roleLevel]);
|
|
36
49
|
|
|
37
50
|
const handlePinPress = useCallback(() => {
|
|
38
51
|
if (isPinned) {
|
|
@@ -44,6 +57,11 @@ const ChatInfoActions = memo(
|
|
|
44
57
|
|
|
45
58
|
const handleDeleteHistory = useCallback(() => {}, []);
|
|
46
59
|
|
|
60
|
+
const handleDismissGroup = useCallback(() => {
|
|
61
|
+
if (!conversation?.groupID) return;
|
|
62
|
+
dismissGroup(conversation?.groupID);
|
|
63
|
+
}, [dismissGroup, conversation?.groupID]);
|
|
64
|
+
|
|
47
65
|
const primaryData = useMemo(() => {
|
|
48
66
|
const items: KListItemBaseItemProps[] = [];
|
|
49
67
|
|
|
@@ -118,17 +136,40 @@ const ChatInfoActions = memo(
|
|
|
118
136
|
]);
|
|
119
137
|
|
|
120
138
|
const dangerData: KListItemBaseItemProps[] = useMemo(() => {
|
|
139
|
+
const items: KListItemBaseItemProps[] = [];
|
|
121
140
|
if (isGroup) {
|
|
122
|
-
|
|
123
|
-
{
|
|
141
|
+
items.push({
|
|
142
|
+
leftNode: {
|
|
143
|
+
icon: {
|
|
144
|
+
vectorName: 'logout-o',
|
|
145
|
+
tintColor: KColors.danger.normal,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
tintColor: KColors.danger.normal,
|
|
149
|
+
onPress: onPressLeaveGroup,
|
|
150
|
+
showChevron: false,
|
|
151
|
+
paddingH: '0.75rem',
|
|
152
|
+
paddingV: '0.75rem',
|
|
153
|
+
customLabel: (
|
|
154
|
+
<KLabel.Text
|
|
155
|
+
typo="TextMdMedium"
|
|
156
|
+
color={KColors.danger.normal}
|
|
157
|
+
numberOfLines={1}
|
|
158
|
+
>
|
|
159
|
+
{trans('chat:info_leave_group')}
|
|
160
|
+
</KLabel.Text>
|
|
161
|
+
),
|
|
162
|
+
});
|
|
163
|
+
if (isOwner) {
|
|
164
|
+
items.push({
|
|
124
165
|
leftNode: {
|
|
125
166
|
icon: {
|
|
126
|
-
vectorName: '
|
|
167
|
+
vectorName: 'minus-circle-o',
|
|
127
168
|
tintColor: KColors.danger.normal,
|
|
128
169
|
},
|
|
129
170
|
},
|
|
130
171
|
tintColor: KColors.danger.normal,
|
|
131
|
-
onPress:
|
|
172
|
+
onPress: handleDismissGroup,
|
|
132
173
|
showChevron: false,
|
|
133
174
|
paddingH: '0.75rem',
|
|
134
175
|
paddingV: '0.75rem',
|
|
@@ -138,39 +179,45 @@ const ChatInfoActions = memo(
|
|
|
138
179
|
color={KColors.danger.normal}
|
|
139
180
|
numberOfLines={1}
|
|
140
181
|
>
|
|
141
|
-
{trans('chat:
|
|
182
|
+
{trans('chat:dismiss_group')}
|
|
142
183
|
</KLabel.Text>
|
|
143
184
|
),
|
|
144
|
-
}
|
|
145
|
-
|
|
185
|
+
});
|
|
186
|
+
}
|
|
146
187
|
}
|
|
147
188
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
tintColor: KColors.danger.normal,
|
|
155
|
-
},
|
|
189
|
+
items.push({
|
|
190
|
+
label: trans('chat:info_delete_history'),
|
|
191
|
+
leftNode: {
|
|
192
|
+
icon: {
|
|
193
|
+
vectorName: 'trash-alt-o-1',
|
|
194
|
+
tintColor: KColors.danger.normal,
|
|
156
195
|
},
|
|
157
|
-
tintColor: KColors.danger.normal,
|
|
158
|
-
onPress: handleDeleteHistory,
|
|
159
|
-
showChevron: false,
|
|
160
|
-
paddingH: '0.75rem',
|
|
161
|
-
paddingV: '0.75rem',
|
|
162
|
-
customLabel: (
|
|
163
|
-
<KLabel.Text
|
|
164
|
-
typo="TextMdMedium"
|
|
165
|
-
color={KColors.danger.normal}
|
|
166
|
-
numberOfLines={1}
|
|
167
|
-
>
|
|
168
|
-
{trans('chat:info_delete_history')}
|
|
169
|
-
</KLabel.Text>
|
|
170
|
-
),
|
|
171
196
|
},
|
|
172
|
-
|
|
173
|
-
|
|
197
|
+
tintColor: KColors.danger.normal,
|
|
198
|
+
onPress: handleDeleteHistory,
|
|
199
|
+
showChevron: false,
|
|
200
|
+
paddingH: '0.75rem',
|
|
201
|
+
paddingV: '0.75rem',
|
|
202
|
+
customLabel: (
|
|
203
|
+
<KLabel.Text
|
|
204
|
+
typo="TextMdMedium"
|
|
205
|
+
color={KColors.danger.normal}
|
|
206
|
+
numberOfLines={1}
|
|
207
|
+
>
|
|
208
|
+
{trans('chat:info_delete_history')}
|
|
209
|
+
</KLabel.Text>
|
|
210
|
+
),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return items;
|
|
214
|
+
}, [
|
|
215
|
+
isGroup,
|
|
216
|
+
onPressLeaveGroup,
|
|
217
|
+
handleDeleteHistory,
|
|
218
|
+
isOwner,
|
|
219
|
+
handleDismissGroup,
|
|
220
|
+
]);
|
|
174
221
|
|
|
175
222
|
return (
|
|
176
223
|
<>
|
|
@@ -19,13 +19,6 @@ export interface ConversationStore {
|
|
|
19
19
|
map: Record<ConversationID, ConversationItem>;
|
|
20
20
|
/** Signal incremented when a truly new conversation arrives and a full refetch is needed. */
|
|
21
21
|
newConversationSignal: number;
|
|
22
|
-
/**
|
|
23
|
-
* Group IDs the user just left/was removed from. Any late-arriving SDK
|
|
24
|
-
* event (OnConversationChanged, OnNewConversation, ...) that still carries
|
|
25
|
-
* this group must not resurrect it — without this guard those events race
|
|
26
|
-
* with removeGroupConversation and silently undo the removal.
|
|
27
|
-
*/
|
|
28
|
-
removedGroupIds: Set<string>;
|
|
29
22
|
updateConversation(id: ConversationID, data: Partial<ConversationItem>): void;
|
|
30
23
|
updateConversations(conversations: ConversationItem[]): void;
|
|
31
24
|
/**
|
|
@@ -33,10 +26,8 @@ export interface ConversationStore {
|
|
|
33
26
|
* Returns true if any conversation was not found in the store (triggers refetch).
|
|
34
27
|
*/
|
|
35
28
|
removeConversation(id: ConversationID): void;
|
|
36
|
-
/** Removes a group's conversation
|
|
29
|
+
/** Removes a group's conversation. */
|
|
37
30
|
removeGroupConversation(groupID: string): void;
|
|
38
|
-
/** Lifts the removal guard once the user (re)joins the group. */
|
|
39
|
-
restoreGroupConversation(groupID: string): void;
|
|
40
31
|
reset(): void;
|
|
41
32
|
}
|
|
42
33
|
|
|
@@ -44,7 +35,6 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
|
|
|
44
35
|
list: [],
|
|
45
36
|
map: {},
|
|
46
37
|
newConversationSignal: 0,
|
|
47
|
-
removedGroupIds: new Set(),
|
|
48
38
|
setCurrentConversation(id, userId, groupId) {
|
|
49
39
|
set({
|
|
50
40
|
currentConversationId: id,
|
|
@@ -64,7 +54,7 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
|
|
|
64
54
|
|
|
65
55
|
const merged = { ...existing, ...data };
|
|
66
56
|
const nextMap = { ...get().map };
|
|
67
|
-
if (merged.isNotInGroup
|
|
57
|
+
if (merged.isNotInGroup) {
|
|
68
58
|
delete nextMap[id];
|
|
69
59
|
} else {
|
|
70
60
|
nextMap[id] = merged;
|
|
@@ -72,19 +62,9 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
|
|
|
72
62
|
set(sortConversation(nextMap));
|
|
73
63
|
},
|
|
74
64
|
updateConversations(conversations) {
|
|
75
|
-
const removedGroupIds = get().removedGroupIds;
|
|
76
65
|
const nextMap = conversations.reduce(
|
|
77
66
|
(pre, curr) => {
|
|
78
|
-
if (curr.isNotInGroup
|
|
79
|
-
// console.log(
|
|
80
|
-
// '[Store] updateConversations: dropping guarded/left group',
|
|
81
|
-
// {
|
|
82
|
-
// conversationID: curr.conversationID,
|
|
83
|
-
// groupID: curr.groupID,
|
|
84
|
-
// isNotInGroup: curr.isNotInGroup,
|
|
85
|
-
// guarded: removedGroupIds.has(curr.groupID),
|
|
86
|
-
// }
|
|
87
|
-
// );
|
|
67
|
+
if (curr.isNotInGroup) {
|
|
88
68
|
delete pre[curr.conversationID];
|
|
89
69
|
} else {
|
|
90
70
|
pre[curr.conversationID] = curr;
|
|
@@ -113,22 +93,14 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
|
|
|
113
93
|
set({
|
|
114
94
|
map: newMap,
|
|
115
95
|
list: get().list.filter((item) => item !== conversationID),
|
|
116
|
-
removedGroupIds: new Set(get().removedGroupIds).add(groupID),
|
|
117
96
|
});
|
|
118
97
|
},
|
|
119
|
-
restoreGroupConversation(groupID) {
|
|
120
|
-
// console.log('[Store] restoreGroupConversation', { groupID });
|
|
121
|
-
const nextRemovedGroupIds = new Set(get().removedGroupIds);
|
|
122
|
-
nextRemovedGroupIds.delete(groupID);
|
|
123
|
-
set({ removedGroupIds: nextRemovedGroupIds });
|
|
124
|
-
},
|
|
125
98
|
reset() {
|
|
126
99
|
set({
|
|
127
100
|
currentConversationId: undefined,
|
|
128
101
|
list: [],
|
|
129
102
|
map: {},
|
|
130
103
|
newConversationSignal: 0,
|
|
131
|
-
removedGroupIds: new Set(),
|
|
132
104
|
});
|
|
133
105
|
},
|
|
134
106
|
}));
|
|
@@ -124,6 +124,7 @@ const i18nKey: Record<string, string> = {
|
|
|
124
124
|
'chat:info_unpin_message': 'Bỏ ghim trò chuyện',
|
|
125
125
|
'chat:info_delete_history': 'Xoá lịch sử trò chuyện',
|
|
126
126
|
'chat:info_leave_group': 'Rời nhóm',
|
|
127
|
+
'chat:dismiss_group': 'Giải tán nhóm',
|
|
127
128
|
'chat:info_view_more': 'Xem thêm',
|
|
128
129
|
'chat:group_description_title': 'Mô tả nhóm',
|
|
129
130
|
'start': 'Bắt đầu',
|
|
@@ -174,6 +175,12 @@ const i18nKey: Record<string, string> = {
|
|
|
174
175
|
'group:joined_group': 'Đã tham gia nhóm {{groupName}}',
|
|
175
176
|
'group:public_group': 'Nhóm công khai',
|
|
176
177
|
'group:private_group': 'Nhóm riêng tư',
|
|
178
|
+
'group:dismiss_confirm_title': 'Giải tán nhóm?',
|
|
179
|
+
'group:dismiss_confirm_desc':
|
|
180
|
+
'Tất cả thành viên và tin nhắn sẽ bị xoá. Bạn có chắc chắn muốn giải tán nhóm?',
|
|
181
|
+
'group:dismiss_confirm_primary': 'Giải tán',
|
|
182
|
+
'group:dismiss_confirm_secondary': 'Quay lại',
|
|
183
|
+
'group:dismiss_toast_success': 'Đã giải tán nhóm',
|
|
177
184
|
};
|
|
178
185
|
|
|
179
186
|
export default {
|
package/src/types/chat.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
GroupPermission,
|
|
3
2
|
MessageType,
|
|
4
3
|
type ConversationItem,
|
|
5
4
|
type MessageItem,
|
|
5
|
+
type SelfUserInfo,
|
|
6
6
|
} from '@droppii/openim-rn-client-sdk';
|
|
7
7
|
import type { PropsWithChildren } from 'react';
|
|
8
8
|
import type { IUrlMetadata } from './common';
|
|
@@ -217,7 +217,7 @@ export interface ChatDetailContextType {
|
|
|
217
217
|
quotedMessage?: MessageItem;
|
|
218
218
|
setQuotedMessage: (item?: MessageItem) => void;
|
|
219
219
|
conversationFlowMainMenu?: InputButtonItem[];
|
|
220
|
-
|
|
220
|
+
userInfo?: SelfUserInfo;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
export interface ChatDetailProviderProps extends PropsWithChildren {}
|