@droppii-org/chat-mobile 0.2.66 → 0.2.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/lib/module/components/Avatar/Avatar.js +39 -2
  2. package/lib/module/components/Avatar/Avatar.js.map +1 -1
  3. package/lib/module/components/Avatar/DoubleAvatar.js +18 -18
  4. package/lib/module/components/Avatar/DoubleAvatar.js.map +1 -1
  5. package/lib/module/components/Avatar/QuadAvatar.js +131 -0
  6. package/lib/module/components/Avatar/QuadAvatar.js.map +1 -0
  7. package/lib/module/components/Avatar/TripleAvatar.js +97 -0
  8. package/lib/module/components/Avatar/TripleAvatar.js.map +1 -0
  9. package/lib/module/components/Avatar/index.js +2 -0
  10. package/lib/module/components/Avatar/index.js.map +1 -1
  11. package/lib/module/components/ThreadCard/AvatarSection.js +41 -0
  12. package/lib/module/components/ThreadCard/AvatarSection.js.map +1 -1
  13. package/lib/module/components/ThreadCard/NamePrefixIcon.js +5 -4
  14. package/lib/module/components/ThreadCard/NamePrefixIcon.js.map +1 -1
  15. package/lib/module/components/ThreadCard/ThreadCard.js +3 -1
  16. package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
  17. package/lib/module/components/messages/groupNotificationMessage/index.js +25 -0
  18. package/lib/module/components/messages/groupNotificationMessage/index.js.map +1 -0
  19. package/lib/module/context/ChatContext.js +1 -1
  20. package/lib/module/context/ChatContext.js.map +1 -1
  21. package/lib/module/context/ChatDetailContext.js +9 -0
  22. package/lib/module/context/ChatDetailContext.js.map +1 -1
  23. package/lib/module/core/useChatListener.js +121 -21
  24. package/lib/module/core/useChatListener.js.map +1 -1
  25. package/lib/module/hooks/conversation/useGetGroupInfo.js +3 -1
  26. package/lib/module/hooks/conversation/useGetGroupInfo.js.map +1 -1
  27. package/lib/module/hooks/message/usePinMessage.js +4 -2
  28. package/lib/module/hooks/message/usePinMessage.js.map +1 -1
  29. package/lib/module/hooks/message/useSendMessage.js +13 -15
  30. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  31. package/lib/module/hooks/useConversationList.js +9 -4
  32. package/lib/module/hooks/useConversationList.js.map +1 -1
  33. package/lib/module/hooks/useGroupPermission.js +34 -0
  34. package/lib/module/hooks/useGroupPermission.js.map +1 -0
  35. package/lib/module/screens/ChatInfo/ChatInfoProfile.js +4 -2
  36. package/lib/module/screens/ChatInfo/ChatInfoProfile.js.map +1 -1
  37. package/lib/module/screens/chat-detail/ChatComposer.js +16 -18
  38. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  39. package/lib/module/screens/chat-detail/ChatDetail.js +7 -0
  40. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  41. package/lib/module/screens/chat-detail/ChatDetailHeader.js +10 -3
  42. package/lib/module/screens/chat-detail/ChatDetailHeader.js.map +1 -1
  43. package/lib/module/screens/chat-detail/conversationHeader.utils.js +13 -3
  44. package/lib/module/screens/chat-detail/conversationHeader.utils.js.map +1 -1
  45. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +29 -2
  46. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  47. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +5 -0
  48. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
  49. package/lib/module/screens/inbox/MessagesTab.js.map +1 -1
  50. package/lib/module/store/conversation.js +56 -12
  51. package/lib/module/store/conversation.js.map +1 -1
  52. package/lib/module/translation/resources/i18n.js +32 -0
  53. package/lib/module/translation/resources/i18n.js.map +1 -1
  54. package/lib/module/types/events.js +1 -0
  55. package/lib/module/types/events.js.map +1 -1
  56. package/lib/module/utils/messageUtils.js +95 -2
  57. package/lib/module/utils/messageUtils.js.map +1 -1
  58. package/lib/typescript/src/components/Avatar/Avatar.d.ts +1 -1
  59. package/lib/typescript/src/components/Avatar/Avatar.d.ts.map +1 -1
  60. package/lib/typescript/src/components/Avatar/Avatar.types.d.ts +5 -1
  61. package/lib/typescript/src/components/Avatar/Avatar.types.d.ts.map +1 -1
  62. package/lib/typescript/src/components/Avatar/DoubleAvatar.d.ts.map +1 -1
  63. package/lib/typescript/src/components/Avatar/QuadAvatar.d.ts +16 -0
  64. package/lib/typescript/src/components/Avatar/QuadAvatar.d.ts.map +1 -0
  65. package/lib/typescript/src/components/Avatar/TripleAvatar.d.ts +15 -0
  66. package/lib/typescript/src/components/Avatar/TripleAvatar.d.ts.map +1 -0
  67. package/lib/typescript/src/components/Avatar/index.d.ts +2 -0
  68. package/lib/typescript/src/components/Avatar/index.d.ts.map +1 -1
  69. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +2 -2
  70. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
  71. package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts +3 -1
  72. package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts.map +1 -1
  73. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  74. package/lib/typescript/src/components/messages/groupNotificationMessage/index.d.ts +3 -0
  75. package/lib/typescript/src/components/messages/groupNotificationMessage/index.d.ts.map +1 -0
  76. package/lib/typescript/src/context/ChatDetailContext.d.ts +2 -0
  77. package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -1
  78. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  79. package/lib/typescript/src/hooks/conversation/useGetGroupInfo.d.ts.map +1 -1
  80. package/lib/typescript/src/hooks/message/usePinMessage.d.ts.map +1 -1
  81. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  82. package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
  83. package/lib/typescript/src/hooks/useGroupPermission.d.ts +6 -0
  84. package/lib/typescript/src/hooks/useGroupPermission.d.ts.map +1 -0
  85. package/lib/typescript/src/screens/ChatInfo/ChatInfoProfile.d.ts.map +1 -1
  86. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  87. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  88. package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts.map +1 -1
  89. package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts +1 -1
  90. package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts.map +1 -1
  91. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  92. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts +1 -1
  93. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
  94. package/lib/typescript/src/screens/inbox/MessagesTab.d.ts.map +1 -1
  95. package/lib/typescript/src/store/conversation.d.ts +11 -0
  96. package/lib/typescript/src/store/conversation.d.ts.map +1 -1
  97. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  98. package/lib/typescript/src/types/events.d.ts +6 -1
  99. package/lib/typescript/src/types/events.d.ts.map +1 -1
  100. package/lib/typescript/src/utils/messageUtils.d.ts +6 -1
  101. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  102. package/package.json +2 -2
  103. package/src/build-ignore.d.ts +1 -0
  104. package/src/components/Avatar/Avatar.tsx +47 -0
  105. package/src/components/Avatar/Avatar.types.ts +5 -1
  106. package/src/components/Avatar/DoubleAvatar.tsx +16 -13
  107. package/src/components/Avatar/QuadAvatar.tsx +161 -0
  108. package/src/components/Avatar/TripleAvatar.tsx +119 -0
  109. package/src/components/Avatar/index.ts +2 -0
  110. package/src/components/ThreadCard/AvatarSection.tsx +58 -2
  111. package/src/components/ThreadCard/NamePrefixIcon.tsx +32 -25
  112. package/src/components/ThreadCard/ThreadCard.tsx +2 -0
  113. package/src/components/messages/groupNotificationMessage/index.tsx +28 -0
  114. package/src/context/ChatContext.tsx +1 -1
  115. package/src/context/ChatDetailContext.tsx +15 -0
  116. package/src/core/useChatListener.ts +176 -23
  117. package/src/hooks/conversation/useGetGroupInfo.ts +2 -0
  118. package/src/hooks/message/usePinMessage.ts +8 -2
  119. package/src/hooks/message/useSendMessage.ts +22 -18
  120. package/src/hooks/useConversationList.ts +17 -7
  121. package/src/hooks/useGroupPermission.ts +43 -0
  122. package/src/screens/ChatInfo/ChatInfoProfile.tsx +6 -3
  123. package/src/screens/chat-detail/ChatComposer.tsx +29 -32
  124. package/src/screens/chat-detail/ChatDetail.tsx +12 -1
  125. package/src/screens/chat-detail/ChatDetailHeader.tsx +19 -3
  126. package/src/screens/chat-detail/conversationHeader.utils.ts +14 -6
  127. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +36 -1
  128. package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +8 -1
  129. package/src/screens/inbox/MessagesTab.tsx +0 -1
  130. package/src/store/conversation.ts +62 -17
  131. package/src/translation/resources/i18n.ts +32 -0
  132. package/src/types/events.ts +6 -0
  133. package/src/utils/messageUtils.ts +103 -1
@@ -0,0 +1,119 @@
1
+ import { memo } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import { KContainer, KColors } from '@droppii/libs';
4
+ import { SingleAvatar } from './SingleAvatar';
5
+
6
+ interface TripleAvatarProps {
7
+ primarySource: string | null | undefined;
8
+ secondarySource: string | null | undefined;
9
+ tertiarySource: string | null | undefined;
10
+ size: number;
11
+ borderColor: string;
12
+ borderWidth: number;
13
+ placeholderColor: string;
14
+ primaryFullName?: string;
15
+ secondaryFullName?: string;
16
+ tertiaryFullName?: string;
17
+ }
18
+
19
+ export const TripleAvatar = memo(
20
+ ({
21
+ primarySource,
22
+ secondarySource,
23
+ tertiarySource,
24
+ size,
25
+ borderColor,
26
+ borderWidth,
27
+ placeholderColor,
28
+ primaryFullName,
29
+ secondaryFullName,
30
+ tertiaryFullName,
31
+ }: TripleAvatarProps) => {
32
+ const circleSize = Math.round(size * 0.6);
33
+ const topOffset = Math.round((size - circleSize) / 2);
34
+ const clusterBorderWidth = borderWidth + 1;
35
+
36
+ return (
37
+ <KContainer.View
38
+ style={[styles.tripleAvatarContainer, { width: size, height: size }]}
39
+ >
40
+ <KContainer.View
41
+ style={[
42
+ styles.tripleAvatarChild,
43
+ styles.tripleAvatarBottomLeft,
44
+ { width: circleSize, height: circleSize },
45
+ ]}
46
+ >
47
+ <SingleAvatar
48
+ source={secondarySource}
49
+ size={circleSize}
50
+ borderColor={borderColor}
51
+ borderWidth={clusterBorderWidth}
52
+ placeholderColor={placeholderColor}
53
+ backgroundColor={KColors.palette.gray.w300}
54
+ fullName={secondaryFullName}
55
+ />
56
+ </KContainer.View>
57
+ <KContainer.View
58
+ style={[
59
+ styles.tripleAvatarChild,
60
+ styles.tripleAvatarBottomRight,
61
+ { width: circleSize, height: circleSize },
62
+ ]}
63
+ >
64
+ <SingleAvatar
65
+ source={tertiarySource}
66
+ size={circleSize}
67
+ borderColor={borderColor}
68
+ borderWidth={clusterBorderWidth}
69
+ placeholderColor={placeholderColor}
70
+ backgroundColor={KColors.palette.gray.w300}
71
+ fullName={tertiaryFullName}
72
+ />
73
+ </KContainer.View>
74
+ <KContainer.View
75
+ style={[
76
+ styles.tripleAvatarChild,
77
+ styles.tripleAvatarTop,
78
+ { width: circleSize, height: circleSize, left: topOffset },
79
+ ]}
80
+ >
81
+ <SingleAvatar
82
+ source={primarySource}
83
+ size={circleSize}
84
+ borderColor={borderColor}
85
+ borderWidth={clusterBorderWidth}
86
+ placeholderColor={placeholderColor}
87
+ backgroundColor={KColors.palette.gray.w300}
88
+ fullName={primaryFullName}
89
+ />
90
+ </KContainer.View>
91
+ </KContainer.View>
92
+ );
93
+ }
94
+ );
95
+
96
+ TripleAvatar.displayName = 'TripleAvatar';
97
+
98
+ const styles = StyleSheet.create({
99
+ tripleAvatarContainer: {
100
+ position: 'relative',
101
+ },
102
+ tripleAvatarChild: {
103
+ position: 'absolute',
104
+ },
105
+ tripleAvatarTop: {
106
+ top: 0,
107
+ zIndex: 3,
108
+ },
109
+ tripleAvatarBottomLeft: {
110
+ bottom: 0,
111
+ left: 0,
112
+ zIndex: 1,
113
+ },
114
+ tripleAvatarBottomRight: {
115
+ bottom: 0,
116
+ right: 0,
117
+ zIndex: 2,
118
+ },
119
+ });
@@ -1,5 +1,7 @@
1
1
  export { default as Avatar } from './Avatar';
2
2
  export { SingleAvatar } from './SingleAvatar';
3
3
  export { DoubleAvatar } from './DoubleAvatar';
4
+ export { TripleAvatar } from './TripleAvatar';
5
+ export { QuadAvatar } from './QuadAvatar';
4
6
  export { AvatarBadge } from './AvatarBadge';
5
7
  export type { AvatarProps } from './Avatar.types';
@@ -3,7 +3,7 @@ import { Avatar } from '../Avatar';
3
3
  import { PeerType, type ConversationItem } from '@droppii/openim-rn-client-sdk';
4
4
 
5
5
  interface AvatarSectionProps extends Partial<
6
- Pick<ConversationItem, 'peerType'>
6
+ Pick<ConversationItem, 'peerType' | 'members'>
7
7
  > {
8
8
  avatar: string | null;
9
9
  fullName: string;
@@ -11,7 +11,13 @@ interface AvatarSectionProps extends Partial<
11
11
  }
12
12
 
13
13
  export const AvatarSection = memo(
14
- ({ avatar, fullName, peerType, size = 'md' }: AvatarSectionProps) => {
14
+ ({
15
+ avatar,
16
+ fullName,
17
+ peerType,
18
+ members,
19
+ size = 'md',
20
+ }: AvatarSectionProps) => {
15
21
  if (peerType === PeerType.Bot) {
16
22
  return (
17
23
  <Avatar
@@ -34,6 +40,56 @@ export const AvatarSection = memo(
34
40
  );
35
41
  }
36
42
 
43
+ const groupMembers = Array.isArray(members) ? members : [];
44
+
45
+ if (peerType === PeerType.Group && !avatar && groupMembers.length >= 4) {
46
+ const [first, second, third] = groupMembers;
47
+ const remainingCount = groupMembers.length - 3;
48
+ return (
49
+ <Avatar
50
+ type="quad"
51
+ source={first?.faceURL ?? null}
52
+ fullName={first?.nickname}
53
+ secondarySource={second?.faceURL ?? null}
54
+ secondaryFullName={second?.nickname}
55
+ tertiarySource={third?.faceURL ?? null}
56
+ tertiaryFullName={third?.nickname}
57
+ remainingCount={remainingCount}
58
+ size={size}
59
+ />
60
+ );
61
+ }
62
+
63
+ if (peerType === PeerType.Group && !avatar && groupMembers.length === 3) {
64
+ const [first, second, third] = groupMembers;
65
+ return (
66
+ <Avatar
67
+ type="triple"
68
+ source={first?.faceURL ?? null}
69
+ fullName={first?.nickname}
70
+ secondarySource={second?.faceURL ?? null}
71
+ secondaryFullName={second?.nickname}
72
+ tertiarySource={third?.faceURL ?? null}
73
+ tertiaryFullName={third?.nickname}
74
+ size={size}
75
+ />
76
+ );
77
+ }
78
+
79
+ if (peerType === PeerType.Group && !avatar && groupMembers.length === 2) {
80
+ const [first, second] = groupMembers;
81
+ return (
82
+ <Avatar
83
+ type="double"
84
+ source={first?.faceURL ?? null}
85
+ fullName={first?.nickname}
86
+ secondarySource={second?.faceURL ?? null}
87
+ secondaryFullName={second?.nickname}
88
+ size={size}
89
+ />
90
+ );
91
+ }
92
+
37
93
  return <Avatar source={avatar} fullName={fullName} size={size} />;
38
94
  }
39
95
  );
@@ -1,6 +1,6 @@
1
1
  import { memo } from 'react';
2
2
  import { Image, StyleSheet } from 'react-native';
3
- import { KContainer, KImage, KColors } from '@droppii/libs';
3
+ import { KContainer, KImage, KColors, type KSpacing } from '@droppii/libs';
4
4
  import Images from '../../assets/images';
5
5
  import { PeerType } from '@droppii/openim-rn-client-sdk';
6
6
 
@@ -13,34 +13,41 @@ const styles = StyleSheet.create({
13
13
 
14
14
  interface NamePrefixIconProps {
15
15
  peerType?: PeerType | string;
16
+ marginR?: KSpacing;
16
17
  }
17
18
 
18
- export const NamePrefixIcon = memo(({ peerType }: NamePrefixIconProps) => {
19
- if (peerType === PeerType.Bot) {
20
- return (
21
- <KContainer.View marginR="0.25rem">
22
- <Image source={Images.ICON_DROPPII} style={styles.icon} />
23
- </KContainer.View>
24
- );
25
- }
19
+ export const NamePrefixIcon = memo(
20
+ ({ peerType, marginR = '0.25rem' }: NamePrefixIconProps) => {
21
+ if (peerType === PeerType.Bot) {
22
+ return (
23
+ <KContainer.View marginR={marginR}>
24
+ <Image source={Images.ICON_DROPPII} style={styles.icon} />
25
+ </KContainer.View>
26
+ );
27
+ }
26
28
 
27
- if (peerType === PeerType.Group) {
28
- return (
29
- <KContainer.View marginR="0.25rem">
30
- <KImage.VectorIcons name="user-two-b" size={14} color={KColors.black} />
31
- </KContainer.View>
32
- );
33
- }
29
+ if (peerType === PeerType.Group) {
30
+ return (
31
+ <KContainer.View marginR={marginR}>
32
+ <KImage.VectorIcons
33
+ name="user-two-b"
34
+ size={14}
35
+ color={KColors.black}
36
+ />
37
+ </KContainer.View>
38
+ );
39
+ }
34
40
 
35
- // if (peerType === DChatPeerType.CUSTOMER) {
36
- // return (
37
- // <KContainer.View marginR="0.25rem">
38
- // <Image source={Images.ICON_DROPPII} style={styles.icon} />
39
- // </KContainer.View>
40
- // );
41
- // }
41
+ // if (peerType === DChatPeerType.CUSTOMER) {
42
+ // return (
43
+ // <KContainer.View marginR={marginR}>
44
+ // <Image source={Images.ICON_DROPPII} style={styles.icon} />
45
+ // </KContainer.View>
46
+ // );
47
+ // }
42
48
 
43
- return null;
44
- });
49
+ return null;
50
+ }
51
+ );
45
52
 
46
53
  NamePrefixIcon.displayName = 'NamePrefixIcon';
@@ -33,6 +33,7 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
33
33
  peerType,
34
34
  showName,
35
35
  faceURL,
36
+ members,
36
37
  unreadCount = 0,
37
38
  recvMsgOpt = MessageReceiveOptType.Normal,
38
39
  isPinned,
@@ -120,6 +121,7 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
120
121
  avatar={faceURL ?? null}
121
122
  fullName={fullName}
122
123
  peerType={peerType}
124
+ members={members}
123
125
  />
124
126
 
125
127
  {/* Chat-Content */}
@@ -0,0 +1,28 @@
1
+ import { memo } from 'react';
2
+ import { KLabel } from '@droppii/libs';
3
+ import { trans } from '../../../translation';
4
+ import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
5
+ import {
6
+ GROUP_NOTIFICATION_TEXT,
7
+ getNotificationOpUserNickname,
8
+ } from '../../../utils/messageUtils';
9
+ import type { BaseLegendMessageProps } from '../types';
10
+
11
+ export const GroupNotificationMessage = memo(
12
+ ({ message, isOutgoing }: BaseLegendMessageProps) => {
13
+ const notification = GROUP_NOTIFICATION_TEXT[message.contentType];
14
+ if (!notification) return null;
15
+
16
+ const sender = isOutgoing
17
+ ? 'Bạn'
18
+ : message.senderNickname || getNotificationOpUserNickname(message) || '';
19
+
20
+ return (
21
+ <KLabel.Text typo="TextXsNormal" color={CHAT_BUBBLE_COLORS.dayLabel}>
22
+ {trans(notification.bySenderKey, { sender })}
23
+ </KLabel.Text>
24
+ );
25
+ }
26
+ );
27
+
28
+ GroupNotificationMessage.displayName = 'GroupNotificationMessage';
@@ -130,7 +130,7 @@ const ChatSdkBridge = () => {
130
130
  };
131
131
 
132
132
  export const ChatProvider = ({ ...props }: ChatProviderProps) => {
133
- const { enabled = false } = props;
133
+ const { enabled = true } = props;
134
134
 
135
135
  if (!enabled) {
136
136
  return props.children;
@@ -5,6 +5,7 @@ import {
5
5
  useEffect,
6
6
  useMemo,
7
7
  useState,
8
+ type ComponentType,
8
9
  } from 'react';
9
10
  import { DeviceEventEmitter } from 'react-native';
10
11
  import {
@@ -189,3 +190,17 @@ export const ChatDetailProvider = ({ children }: ChatDetailProviderProps) => {
189
190
  </ChatDetailContext.Provider>
190
191
  );
191
192
  };
193
+
194
+ export function withChatDetailProvider<P extends object>(
195
+ Component: ComponentType<P>
196
+ ): ComponentType<P> {
197
+ const WithChatDetailProvider = (props: P) => (
198
+ <ChatDetailProvider>
199
+ <Component {...props} />
200
+ </ChatDetailProvider>
201
+ );
202
+ WithChatDetailProvider.displayName = `withChatDetailProvider(${
203
+ Component.displayName || Component.name || 'Component'
204
+ })`;
205
+ return WithChatDetailProvider;
206
+ }
@@ -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
- ({ progress, message }: { progress: number; message: MessageItem }) => {
32
- console.log('[Listener] SendMessageProgress', {
33
- clientMsgID: message.clientMsgID,
34
- status: message.status,
35
- progress,
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,157 @@ export const useChatListener = () => {
87
100
  );
88
101
 
89
102
  const onNewConversation = useCallback((conversations: ConversationItem[]) => {
90
- console.log('[Listener] OnNewConversation', conversations);
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 onGroupMemberAdded = useCallback((member: GroupMemberItem) => {
95
- console.log('[Listener] OnGroupMemberAdded', {
96
- userID: member.userID,
97
- groupID: member.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 onGroupMemberDeleted = useCallback((member: GroupMemberItem) => {
102
- console.log('[Listener] OnGroupMemberDeleted', {
103
- userID: member.userID,
104
- groupID: member.groupID,
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
+ // roleLevel: member.roleLevel,
253
+ // });
110
254
  const conversation = useConversationStore
111
255
  .getState()
112
256
  .getCurrentConversation();
@@ -148,7 +292,7 @@ export const useChatListener = () => {
148
292
  );
149
293
 
150
294
  const onRecvNewMessages = useCallback((messages: MessageItem[]) => {
151
- console.log('[Listener] OnRecvNewMessages', messages);
295
+ // console.log('[Listener] OnRecvNewMessages', messages);
152
296
  const conversationId =
153
297
  useConversationStore.getState().currentConversationId;
154
298
  const targetUserId = useConversationStore.getState().currentUserId;
@@ -164,8 +308,8 @@ export const useChatListener = () => {
164
308
  relevant.forEach((msg) => useMessageStore.getState().pushNewMessage(msg));
165
309
  }, []);
166
310
 
167
- const onRecvOfflineNewMessages = useCallback((messages: MessageItem[]) => {
168
- console.log('[Listener] onRecvOfflineNewMessages', messages);
311
+ const onRecvOfflineNewMessages = useCallback((_messages: MessageItem[]) => {
312
+ // console.log('[Listener] onRecvOfflineNewMessages', _messages);
169
313
  }, []);
170
314
 
171
315
  useEffect(() => {
@@ -174,12 +318,15 @@ export const useChatListener = () => {
174
318
  OpenIMSDK.on(OpenIMEvent.OnNewConversation, onNewConversation);
175
319
 
176
320
  // Group members
321
+ OpenIMSDK.on(OpenIMEvent.OnGroupInfoChanged, onGroupInfoChanged);
177
322
  OpenIMSDK.on(OpenIMEvent.OnGroupMemberAdded, onGroupMemberAdded);
178
323
  OpenIMSDK.on(OpenIMEvent.OnGroupMemberDeleted, onGroupMemberDeleted);
179
324
  OpenIMSDK.on(
180
325
  OpenIMEvent.OnGroupMemberInfoChanged,
181
326
  OnGroupMemberInfoChanged
182
327
  );
328
+ OpenIMSDK.on(OpenIMEvent.OnJoinedGroupDeleted, onJoinedGroupDeleted);
329
+ OpenIMSDK.on(OpenIMEvent.OnGroupDismissed, onGroupDismissed);
183
330
 
184
331
  // Message status
185
332
  OpenIMSDK.on(OpenIMEvent.SendMessageProgress, onSendMessageProgress);
@@ -197,12 +344,15 @@ export const useChatListener = () => {
197
344
  OpenIMSDK.off(OpenIMEvent.OnNewConversation, onNewConversation);
198
345
 
199
346
  // Group members
347
+ OpenIMSDK.off(OpenIMEvent.OnGroupInfoChanged, onGroupInfoChanged);
200
348
  OpenIMSDK.off(OpenIMEvent.OnGroupMemberAdded, onGroupMemberAdded);
201
349
  OpenIMSDK.off(OpenIMEvent.OnGroupMemberDeleted, onGroupMemberDeleted);
202
- OpenIMSDK.on(
350
+ OpenIMSDK.off(
203
351
  OpenIMEvent.OnGroupMemberInfoChanged,
204
352
  OnGroupMemberInfoChanged
205
353
  );
354
+ OpenIMSDK.off(OpenIMEvent.OnJoinedGroupDeleted, onJoinedGroupDeleted);
355
+ OpenIMSDK.off(OpenIMEvent.OnGroupDismissed, onGroupDismissed);
206
356
 
207
357
  // Message status
208
358
  OpenIMSDK.off(OpenIMEvent.SendMessageProgress, onSendMessageProgress);
@@ -216,6 +366,7 @@ export const useChatListener = () => {
216
366
  };
217
367
  }, [
218
368
  onConversationChanged,
369
+ onGroupInfoChanged,
219
370
  onGroupMemberAdded,
220
371
  onGroupMemberDeleted,
221
372
  onMessagePinned,
@@ -225,5 +376,7 @@ export const useChatListener = () => {
225
376
  onRecvNewMessages,
226
377
  onRecvOfflineNewMessages,
227
378
  OnGroupMemberInfoChanged,
379
+ onJoinedGroupDeleted,
380
+ onGroupDismissed,
228
381
  ]);
229
382
  };
@@ -15,4 +15,6 @@ export const useGetGroupInfo = (groupId = '') =>
15
15
  }
16
16
  },
17
17
  enabled: !!groupId,
18
+ staleTime: 0,
19
+ refetchOnMount: 'always',
18
20
  });