@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.
Files changed (60) hide show
  1. package/lib/module/components/GroupPermissionGate/GroupPermissionGate.js +33 -0
  2. package/lib/module/components/GroupPermissionGate/GroupPermissionGate.js.map +1 -0
  3. package/lib/module/components/GroupPermissionGate/index.js +4 -0
  4. package/lib/module/components/GroupPermissionGate/index.js.map +1 -0
  5. package/lib/module/context/ChatDetailContext.js +15 -11
  6. package/lib/module/context/ChatDetailContext.js.map +1 -1
  7. package/lib/module/core/useChatListener.js +31 -45
  8. package/lib/module/core/useChatListener.js.map +1 -1
  9. package/lib/module/hooks/groups/useDismissGroup.js +79 -0
  10. package/lib/module/hooks/groups/useDismissGroup.js.map +1 -0
  11. package/lib/module/hooks/groups/useJoinGroup.js +1 -3
  12. package/lib/module/hooks/groups/useJoinGroup.js.map +1 -1
  13. package/lib/module/hooks/useGroupPermission.js +4 -3
  14. package/lib/module/hooks/useGroupPermission.js.map +1 -1
  15. package/lib/module/screens/ChatInfo/ChatInfo.js +6 -3
  16. package/lib/module/screens/ChatInfo/ChatInfo.js.map +1 -1
  17. package/lib/module/screens/ChatInfo/ChatInfoActions.js +48 -6
  18. package/lib/module/screens/ChatInfo/ChatInfoActions.js.map +1 -1
  19. package/lib/module/store/conversation.js +4 -25
  20. package/lib/module/store/conversation.js.map +1 -1
  21. package/lib/module/translation/resources/i18n.js +7 -1
  22. package/lib/module/translation/resources/i18n.js.map +1 -1
  23. package/lib/typescript/src/components/GroupPermissionGate/GroupPermissionGate.d.ts +19 -0
  24. package/lib/typescript/src/components/GroupPermissionGate/GroupPermissionGate.d.ts.map +1 -0
  25. package/lib/typescript/src/components/GroupPermissionGate/index.d.ts +3 -0
  26. package/lib/typescript/src/components/GroupPermissionGate/index.d.ts.map +1 -0
  27. package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -1
  28. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  29. package/lib/typescript/src/hooks/groups/useDismissGroup.d.ts +8 -0
  30. package/lib/typescript/src/hooks/groups/useDismissGroup.d.ts.map +1 -0
  31. package/lib/typescript/src/hooks/groups/useJoinGroup.d.ts.map +1 -1
  32. package/lib/typescript/src/hooks/useGroupPermission.d.ts.map +1 -1
  33. package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts +2 -2
  34. package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts.map +1 -1
  35. package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts +3 -2
  36. package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts.map +1 -1
  37. package/lib/typescript/src/screens/ChatInfo/types.d.ts +1 -0
  38. package/lib/typescript/src/screens/ChatInfo/types.d.ts.map +1 -1
  39. package/lib/typescript/src/store/conversation.d.ts +1 -10
  40. package/lib/typescript/src/store/conversation.d.ts.map +1 -1
  41. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  42. package/lib/typescript/src/types/chat.d.ts +2 -2
  43. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  44. package/lib/typescript/src/types/events.d.ts +2 -4
  45. package/lib/typescript/src/types/events.d.ts.map +1 -1
  46. package/package.json +2 -2
  47. package/src/components/GroupPermissionGate/GroupPermissionGate.tsx +51 -0
  48. package/src/components/GroupPermissionGate/index.ts +5 -0
  49. package/src/context/ChatDetailContext.tsx +17 -11
  50. package/src/core/useChatListener.ts +30 -54
  51. package/src/hooks/groups/useDismissGroup.tsx +80 -0
  52. package/src/hooks/groups/useJoinGroup.ts +1 -3
  53. package/src/hooks/useGroupPermission.ts +5 -4
  54. package/src/screens/ChatInfo/ChatInfo.tsx +4 -1
  55. package/src/screens/ChatInfo/ChatInfoActions.tsx +79 -32
  56. package/src/screens/ChatInfo/types.ts +1 -0
  57. package/src/store/conversation.ts +3 -31
  58. package/src/translation/resources/i18n.ts +7 -0
  59. package/src/types/chat.ts +2 -2
  60. 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 [groupPermissions, setGroupPermissions] = useState<GroupPermission[]>(
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
- setGroupPermissions([]);
84
+ setGroupUserInfo(undefined);
79
85
  return;
80
86
  }
81
87
  OpenIMSDK.getSelfUserInfo(groupId)
82
- .then((info) => setGroupPermissions(info?.permissions ?? []))
83
- .catch(() => setGroupPermissions([]));
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
- ({ permissions }: GroupPermissionUpdatedPayload) => {
90
- setGroupPermissions(permissions);
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
- groupPermissions,
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, currentUserId]
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
- ToastFlashMessage.show({
185
- key: `group-removed-${groupID}`,
186
- title: groupName || conversation?.showName || 'Nhóm',
187
- body:
188
- reason === 'dismissed'
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
- // console.log('[Listener] OnGroupMemberDeleted', {
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
- // console.log('[Listener] OnGroupMemberInfoChanged', {
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
- permissions: member.permissions ?? [],
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, groupPermissions } = useChatDetailContext();
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) => groupPermissions?.includes(p))
19
- : required.some((p) => groupPermissions?.includes(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
- }, [groupPermissions, isNotGroup, mode]);
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 type { ConversationItem } from '@droppii/openim-rn-client-sdk';
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
- return [
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: 'logout-o',
167
+ vectorName: 'minus-circle-o',
127
168
  tintColor: KColors.danger.normal,
128
169
  },
129
170
  },
130
171
  tintColor: KColors.danger.normal,
131
- onPress: onPressLeaveGroup,
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:info_leave_group')}
182
+ {trans('chat:dismiss_group')}
142
183
  </KLabel.Text>
143
184
  ),
144
- },
145
- ];
185
+ });
186
+ }
146
187
  }
147
188
 
148
- return [
149
- {
150
- label: trans('chat:info_delete_history'),
151
- leftNode: {
152
- icon: {
153
- vectorName: 'trash-alt-o-1',
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
- }, [isGroup, onPressLeaveGroup, handleDeleteHistory]);
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
  <>
@@ -9,4 +9,5 @@ export interface ChatInfoProps {
9
9
  onPressCreateGroup?: () => void;
10
10
  onPressAddToGroup?: () => void;
11
11
  onPressLeaveGroup?: () => void;
12
+ onDismissGroup?: () => void;
12
13
  }
@@ -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 and guards it against being re-added. */
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 || get().removedGroupIds.has(merged.groupID)) {
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 || removedGroupIds.has(curr.groupID)) {
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
- groupPermissions: GroupPermission[];
220
+ userInfo?: SelfUserInfo;
221
221
  }
222
222
 
223
223
  export interface ChatDetailProviderProps extends PropsWithChildren {}