@droppii-org/chat-mobile 0.2.65 → 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.
Files changed (153) 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 +120 -21
  24. package/lib/module/core/useChatListener.js.map +1 -1
  25. package/lib/module/hooks/conversation/useGetGroupInfo.js +21 -0
  26. package/lib/module/hooks/conversation/useGetGroupInfo.js.map +1 -0
  27. package/lib/module/hooks/message/useSendMessage.js +6 -3
  28. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  29. package/lib/module/hooks/query-keys.js +2 -1
  30. package/lib/module/hooks/query-keys.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/screens/ChatInfo/ChatInfo.js +18 -2
  34. package/lib/module/screens/ChatInfo/ChatInfo.js.map +1 -1
  35. package/lib/module/screens/ChatInfo/ChatInfoActions.js +83 -55
  36. package/lib/module/screens/ChatInfo/ChatInfoActions.js.map +1 -1
  37. package/lib/module/screens/ChatInfo/ChatInfoDescriptionRow.js +77 -0
  38. package/lib/module/screens/ChatInfo/ChatInfoDescriptionRow.js.map +1 -0
  39. package/lib/module/screens/ChatInfo/ChatInfoProfile.js +4 -2
  40. package/lib/module/screens/ChatInfo/ChatInfoProfile.js.map +1 -1
  41. package/lib/module/screens/ChatInfo/GroupDescriptionModal.js +58 -0
  42. package/lib/module/screens/ChatInfo/GroupDescriptionModal.js.map +1 -0
  43. package/lib/module/screens/chat-detail/ChatComposer.js +11 -6
  44. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  45. package/lib/module/screens/chat-detail/ChatDetail.js +7 -0
  46. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  47. package/lib/module/screens/chat-detail/ChatDetailHeader.js +10 -3
  48. package/lib/module/screens/chat-detail/ChatDetailHeader.js.map +1 -1
  49. package/lib/module/screens/chat-detail/conversationHeader.utils.js +13 -3
  50. package/lib/module/screens/chat-detail/conversationHeader.utils.js.map +1 -1
  51. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +29 -2
  52. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  53. package/lib/module/screens/inbox/MessagesTab.js.map +1 -1
  54. package/lib/module/store/conversation.js +56 -12
  55. package/lib/module/store/conversation.js.map +1 -1
  56. package/lib/module/translation/resources/i18n.js +34 -0
  57. package/lib/module/translation/resources/i18n.js.map +1 -1
  58. package/lib/module/types/events.js +1 -0
  59. package/lib/module/types/events.js.map +1 -1
  60. package/lib/module/utils/conversation.js +3 -0
  61. package/lib/module/utils/conversation.js.map +1 -1
  62. package/lib/module/utils/messageUtils.js +95 -2
  63. package/lib/module/utils/messageUtils.js.map +1 -1
  64. package/lib/typescript/src/components/Avatar/Avatar.d.ts +1 -1
  65. package/lib/typescript/src/components/Avatar/Avatar.d.ts.map +1 -1
  66. package/lib/typescript/src/components/Avatar/Avatar.types.d.ts +5 -1
  67. package/lib/typescript/src/components/Avatar/Avatar.types.d.ts.map +1 -1
  68. package/lib/typescript/src/components/Avatar/DoubleAvatar.d.ts.map +1 -1
  69. package/lib/typescript/src/components/Avatar/QuadAvatar.d.ts +16 -0
  70. package/lib/typescript/src/components/Avatar/QuadAvatar.d.ts.map +1 -0
  71. package/lib/typescript/src/components/Avatar/TripleAvatar.d.ts +15 -0
  72. package/lib/typescript/src/components/Avatar/TripleAvatar.d.ts.map +1 -0
  73. package/lib/typescript/src/components/Avatar/index.d.ts +2 -0
  74. package/lib/typescript/src/components/Avatar/index.d.ts.map +1 -1
  75. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +2 -2
  76. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
  77. package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts +3 -1
  78. package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts.map +1 -1
  79. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  80. package/lib/typescript/src/components/messages/groupNotificationMessage/index.d.ts +3 -0
  81. package/lib/typescript/src/components/messages/groupNotificationMessage/index.d.ts.map +1 -0
  82. package/lib/typescript/src/context/ChatDetailContext.d.ts +2 -0
  83. package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -1
  84. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  85. package/lib/typescript/src/hooks/conversation/useGetGroupInfo.d.ts +2 -0
  86. package/lib/typescript/src/hooks/conversation/useGetGroupInfo.d.ts.map +1 -0
  87. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  88. package/lib/typescript/src/hooks/query-keys.d.ts +1 -0
  89. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  90. package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
  91. package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts +1 -1
  92. package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts.map +1 -1
  93. package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts +3 -1
  94. package/lib/typescript/src/screens/ChatInfo/ChatInfoActions.d.ts.map +1 -1
  95. package/lib/typescript/src/screens/ChatInfo/ChatInfoDescriptionRow.d.ts +7 -0
  96. package/lib/typescript/src/screens/ChatInfo/ChatInfoDescriptionRow.d.ts.map +1 -0
  97. package/lib/typescript/src/screens/ChatInfo/ChatInfoProfile.d.ts.map +1 -1
  98. package/lib/typescript/src/screens/ChatInfo/GroupDescriptionModal.d.ts +9 -0
  99. package/lib/typescript/src/screens/ChatInfo/GroupDescriptionModal.d.ts.map +1 -0
  100. package/lib/typescript/src/screens/ChatInfo/types.d.ts +1 -0
  101. package/lib/typescript/src/screens/ChatInfo/types.d.ts.map +1 -1
  102. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  103. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  104. package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts.map +1 -1
  105. package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts +1 -1
  106. package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts.map +1 -1
  107. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  108. package/lib/typescript/src/screens/inbox/MessagesTab.d.ts.map +1 -1
  109. package/lib/typescript/src/store/conversation.d.ts +11 -0
  110. package/lib/typescript/src/store/conversation.d.ts.map +1 -1
  111. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  112. package/lib/typescript/src/types/events.d.ts +6 -1
  113. package/lib/typescript/src/types/events.d.ts.map +1 -1
  114. package/lib/typescript/src/utils/conversation.d.ts +1 -0
  115. package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
  116. package/lib/typescript/src/utils/messageUtils.d.ts +6 -1
  117. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  118. package/package.json +2 -2
  119. package/src/build-ignore.d.ts +1 -0
  120. package/src/components/Avatar/Avatar.tsx +47 -0
  121. package/src/components/Avatar/Avatar.types.ts +5 -1
  122. package/src/components/Avatar/DoubleAvatar.tsx +16 -13
  123. package/src/components/Avatar/QuadAvatar.tsx +161 -0
  124. package/src/components/Avatar/TripleAvatar.tsx +119 -0
  125. package/src/components/Avatar/index.ts +2 -0
  126. package/src/components/ThreadCard/AvatarSection.tsx +58 -2
  127. package/src/components/ThreadCard/NamePrefixIcon.tsx +32 -25
  128. package/src/components/ThreadCard/ThreadCard.tsx +2 -0
  129. package/src/components/messages/groupNotificationMessage/index.tsx +28 -0
  130. package/src/context/ChatContext.tsx +1 -1
  131. package/src/context/ChatDetailContext.tsx +15 -0
  132. package/src/core/useChatListener.ts +175 -23
  133. package/src/hooks/conversation/useGetGroupInfo.ts +20 -0
  134. package/src/hooks/message/useSendMessage.ts +11 -3
  135. package/src/hooks/query-keys.ts +1 -0
  136. package/src/hooks/useConversationList.ts +17 -7
  137. package/src/screens/ChatInfo/ChatInfo.tsx +16 -0
  138. package/src/screens/ChatInfo/ChatInfoActions.tsx +68 -35
  139. package/src/screens/ChatInfo/ChatInfoDescriptionRow.tsx +102 -0
  140. package/src/screens/ChatInfo/ChatInfoProfile.tsx +6 -3
  141. package/src/screens/ChatInfo/GroupDescriptionModal.tsx +67 -0
  142. package/src/screens/ChatInfo/types.ts +1 -0
  143. package/src/screens/chat-detail/ChatComposer.tsx +19 -10
  144. package/src/screens/chat-detail/ChatDetail.tsx +12 -1
  145. package/src/screens/chat-detail/ChatDetailHeader.tsx +19 -3
  146. package/src/screens/chat-detail/conversationHeader.utils.ts +14 -6
  147. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +36 -1
  148. package/src/screens/inbox/MessagesTab.tsx +0 -1
  149. package/src/store/conversation.ts +62 -17
  150. package/src/translation/resources/i18n.ts +34 -0
  151. package/src/types/events.ts +6 -0
  152. package/src/utils/conversation.ts +6 -0
  153. package/src/utils/messageUtils.ts +103 -1
@@ -1,5 +1,6 @@
1
1
  import { create } from 'zustand';
2
- import { sortConversation } from '../utils/conversation';
2
+ import { SessionType } from '@droppii/openim-rn-client-sdk';
3
+ import { getConversationID, sortConversation } from '../utils/conversation';
3
4
  import type { ConversationItem } from '@droppii/openim-rn-client-sdk';
4
5
 
5
6
  type ConversationID = string;
@@ -18,6 +19,13 @@ export interface ConversationStore {
18
19
  map: Record<ConversationID, ConversationItem>;
19
20
  /** Signal incremented when a truly new conversation arrives and a full refetch is needed. */
20
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>;
21
29
  updateConversation(id: ConversationID, data: Partial<ConversationItem>): void;
22
30
  updateConversations(conversations: ConversationItem[]): void;
23
31
  /**
@@ -25,6 +33,10 @@ export interface ConversationStore {
25
33
  * Returns true if any conversation was not found in the store (triggers refetch).
26
34
  */
27
35
  removeConversation(id: ConversationID): void;
36
+ /** Removes a group's conversation and guards it against being re-added. */
37
+ removeGroupConversation(groupID: string): void;
38
+ /** Lifts the removal guard once the user (re)joins the group. */
39
+ restoreGroupConversation(groupID: string): void;
28
40
  reset(): void;
29
41
  }
30
42
 
@@ -32,6 +44,7 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
32
44
  list: [],
33
45
  map: {},
34
46
  newConversationSignal: 0,
47
+ removedGroupIds: new Set(),
35
48
  setCurrentConversation(id, userId, groupId) {
36
49
  set({
37
50
  currentConversationId: id,
@@ -49,25 +62,38 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
49
62
  const existing = get().map[id];
50
63
  if (!existing) return;
51
64
 
52
- set(
53
- sortConversation({
54
- ...get().map,
55
- [id]: { ...existing, ...data },
56
- })
57
- );
65
+ const merged = { ...existing, ...data };
66
+ const nextMap = { ...get().map };
67
+ if (merged.isNotInGroup || get().removedGroupIds.has(merged.groupID)) {
68
+ delete nextMap[id];
69
+ } else {
70
+ nextMap[id] = merged;
71
+ }
72
+ set(sortConversation(nextMap));
58
73
  },
59
74
  updateConversations(conversations) {
60
- set(
61
- sortConversation(
62
- conversations.reduce(
63
- (pre, curr) => {
64
- pre[curr.conversationID] = curr;
65
- return pre;
66
- },
67
- { ...get().map }
68
- )
69
- )
75
+ const removedGroupIds = get().removedGroupIds;
76
+ const nextMap = conversations.reduce(
77
+ (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
+ // );
88
+ delete pre[curr.conversationID];
89
+ } else {
90
+ pre[curr.conversationID] = curr;
91
+ }
92
+ return pre;
93
+ },
94
+ { ...get().map }
70
95
  );
96
+ set(sortConversation(nextMap));
71
97
  },
72
98
  removeConversation(id) {
73
99
  const newMap = { ...get().map };
@@ -78,12 +104,31 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
78
104
  list: get().list.filter((item) => item !== id),
79
105
  });
80
106
  },
107
+ removeGroupConversation(groupID) {
108
+ const conversationID = getConversationID(SessionType.Group, groupID);
109
+
110
+ const newMap = { ...get().map };
111
+ delete newMap[conversationID];
112
+
113
+ set({
114
+ map: newMap,
115
+ list: get().list.filter((item) => item !== conversationID),
116
+ removedGroupIds: new Set(get().removedGroupIds).add(groupID),
117
+ });
118
+ },
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
+ },
81
125
  reset() {
82
126
  set({
83
127
  currentConversationId: undefined,
84
128
  list: [],
85
129
  map: {},
86
130
  newConversationSignal: 0,
131
+ removedGroupIds: new Set(),
87
132
  });
88
133
  },
89
134
  }));
@@ -71,6 +71,37 @@ const i18nKey: Record<string, string> = {
71
71
  'msg_type_link': '[Liên kết]',
72
72
  'msg_type_revoke': 'Đã thu hồi tin nhắn',
73
73
  'chat:revoked_by_sender': '{{sender}} đã thu hồi tin nhắn',
74
+ 'msg_type_group_created': 'Đã tạo nhóm',
75
+ 'chat:group_created_by': '{{sender}} đã tạo nhóm',
76
+ 'msg_type_group_name_updated': 'Đã đổi tên nhóm',
77
+ 'chat:group_name_updated_by': '{{sender}} đã đổi tên nhóm',
78
+ 'msg_type_group_info_updated': 'Đã cập nhật thông tin nhóm',
79
+ 'chat:group_info_updated_by': '{{sender}} đã cập nhật thông tin nhóm',
80
+ 'msg_type_member_quit': 'Đã rời khỏi nhóm',
81
+ 'chat:member_quit_by': '{{sender}} đã rời khỏi nhóm',
82
+ 'msg_type_group_owner_transferred': 'Đã chuyển quyền trưởng nhóm',
83
+ 'chat:group_owner_transferred_by': '{{sender}} đã chuyển quyền trưởng nhóm',
84
+ 'msg_type_member_kicked': 'Đã xoá thành viên khỏi nhóm',
85
+ 'chat:member_kicked_by': '{{sender}} đã xoá thành viên khỏi nhóm',
86
+ 'msg_type_member_invited': 'Đã thêm thành viên vào nhóm',
87
+ 'chat:member_invited_by': '{{sender}} đã thêm thành viên vào nhóm',
88
+ 'msg_type_member_enter': 'Đã tham gia nhóm',
89
+ 'chat:member_enter_by': '{{sender}} đã tham gia nhóm',
90
+ 'msg_type_group_dismissed': 'Nhóm đã bị giải tán',
91
+ 'chat:group_dismissed_by': '{{sender}} đã giải tán nhóm',
92
+ 'msg_type_group_member_muted': 'Đã tắt tiếng thành viên',
93
+ 'chat:group_member_muted_by': '{{sender}} đã tắt tiếng thành viên',
94
+ 'msg_type_group_member_cancel_muted': 'Đã bật tiếng thành viên',
95
+ 'chat:group_member_cancel_muted_by': '{{sender}} đã bật tiếng thành viên',
96
+ 'msg_type_group_muted': 'Đã tắt tiếng cả nhóm',
97
+ 'chat:group_muted_by': '{{sender}} đã tắt tiếng cả nhóm',
98
+ 'msg_type_group_cancel_muted': 'Đã bật tiếng cả nhóm',
99
+ 'chat:group_cancel_muted_by': '{{sender}} đã bật tiếng cả nhóm',
100
+ 'msg_type_group_announcement_updated': 'Đã cập nhật thông báo nhóm',
101
+ 'chat:group_announcement_updated_by': '{{sender}} đã cập nhật thông báo nhóm',
102
+ 'msg_type_friend_added': 'Đã trở thành bạn bè',
103
+ 'msg_type_oa_notification': 'Thông báo mới',
104
+ 'msg_type_burn_message_change': 'Đã thay đổi cài đặt tin nhắn tự huỷ',
74
105
  'chat:mute_action': 'Tắt thông báo',
75
106
  'chat:unmute_action': 'Bật thông báo',
76
107
  'chat:mute_confirm_title': 'Tắt thông báo',
@@ -92,6 +123,9 @@ const i18nKey: Record<string, string> = {
92
123
  'chat:info_pin_message': 'Ghim trò chuyện',
93
124
  'chat:info_unpin_message': 'Bỏ ghim trò chuyện',
94
125
  'chat:info_delete_history': 'Xoá lịch sử trò chuyện',
126
+ 'chat:info_leave_group': 'Rời nhóm',
127
+ 'chat:info_view_more': 'Xem thêm',
128
+ 'chat:group_description_title': 'Mô tả nhóm',
95
129
  'start': 'Bắt đầu',
96
130
  'restart': 'Bắt đầu lại',
97
131
  'gallery': 'Ảnh/Video',
@@ -5,6 +5,7 @@ export enum ChatEvent {
5
5
  ScrollToBottom = 'chat:scroll_to_bottom',
6
6
  MessageRevoked = 'chat:message_revoked',
7
7
  GroupPermissionUpdated = 'chat:group_permission_updated',
8
+ GroupRemoved = 'chat:group_removed',
8
9
  }
9
10
 
10
11
  export interface ScrollToMessagePayload {
@@ -18,3 +19,8 @@ export interface MessageRevokedPayload {
18
19
  export interface GroupPermissionUpdatedPayload {
19
20
  permissions: GroupPermission[];
20
21
  }
22
+
23
+ export interface GroupRemovedPayload {
24
+ groupID: string;
25
+ reason: 'left' | 'dismissed' | 'kicked';
26
+ }
@@ -56,3 +56,9 @@ export const getBotId = (conversation?: ConversationItem) => {
56
56
  }
57
57
  return undefined;
58
58
  };
59
+
60
+ export const isGroupConversation = (
61
+ conversation?: Pick<ConversationItem, 'conversationType' | 'peerType'>
62
+ ) => {
63
+ return conversation?.peerType === PeerType.Group;
64
+ };
@@ -17,6 +17,70 @@ import type { TextProps } from 'react-native';
17
17
  export const isMessageRevoked = (message: MessageItem): boolean =>
18
18
  message.contentType === MessageType.RevokeMessage;
19
19
 
20
+ // Group system-notification content types (1501-1520 block): each renders as
21
+ // a neutral preview on its own, and as "{{sender}} <action>" once the
22
+ // operator's name is known (see getSenderName/getNotificationOpUserNickname).
23
+ export const GROUP_NOTIFICATION_TEXT: Partial<
24
+ Record<MessageType, { previewKey: string; bySenderKey: string }>
25
+ > = {
26
+ [MessageType.GroupCreated]: {
27
+ previewKey: 'msg_type_group_created',
28
+ bySenderKey: 'chat:group_created_by',
29
+ },
30
+ [MessageType.GroupInfoUpdated]: {
31
+ previewKey: 'msg_type_group_info_updated',
32
+ bySenderKey: 'chat:group_info_updated_by',
33
+ },
34
+ [MessageType.MemberQuit]: {
35
+ previewKey: 'msg_type_member_quit',
36
+ bySenderKey: 'chat:member_quit_by',
37
+ },
38
+ [MessageType.GroupOwnerTransferred]: {
39
+ previewKey: 'msg_type_group_owner_transferred',
40
+ bySenderKey: 'chat:group_owner_transferred_by',
41
+ },
42
+ [MessageType.MemberKicked]: {
43
+ previewKey: 'msg_type_member_kicked',
44
+ bySenderKey: 'chat:member_kicked_by',
45
+ },
46
+ [MessageType.MemberInvited]: {
47
+ previewKey: 'msg_type_member_invited',
48
+ bySenderKey: 'chat:member_invited_by',
49
+ },
50
+ [MessageType.MemberEnter]: {
51
+ previewKey: 'msg_type_member_enter',
52
+ bySenderKey: 'chat:member_enter_by',
53
+ },
54
+ [MessageType.GroupDismissed]: {
55
+ previewKey: 'msg_type_group_dismissed',
56
+ bySenderKey: 'chat:group_dismissed_by',
57
+ },
58
+ [MessageType.GroupMemberMuted]: {
59
+ previewKey: 'msg_type_group_member_muted',
60
+ bySenderKey: 'chat:group_member_muted_by',
61
+ },
62
+ [MessageType.GroupMemberCancelMuted]: {
63
+ previewKey: 'msg_type_group_member_cancel_muted',
64
+ bySenderKey: 'chat:group_member_cancel_muted_by',
65
+ },
66
+ [MessageType.GroupMuted]: {
67
+ previewKey: 'msg_type_group_muted',
68
+ bySenderKey: 'chat:group_muted_by',
69
+ },
70
+ [MessageType.GroupCancelMuted]: {
71
+ previewKey: 'msg_type_group_cancel_muted',
72
+ bySenderKey: 'chat:group_cancel_muted_by',
73
+ },
74
+ [MessageType.GroupAnnouncementUpdated]: {
75
+ previewKey: 'msg_type_group_announcement_updated',
76
+ bySenderKey: 'chat:group_announcement_updated_by',
77
+ },
78
+ [MessageType.GroupNameUpdated]: {
79
+ previewKey: 'msg_type_group_name_updated',
80
+ bySenderKey: 'chat:group_name_updated_by',
81
+ },
82
+ };
83
+
20
84
  export const extractTextContent = (message: MessageItem): string => {
21
85
  if (message.textElem?.content) return message.textElem.content;
22
86
  try {
@@ -102,8 +166,23 @@ export const getMessagePreview = (message?: MessageItem): string => {
102
166
 
103
167
  case MessageType.StickerMessage:
104
168
  return '[Sticker]';
105
- default:
169
+
170
+ case MessageType.FriendAdded:
171
+ return trans('msg_type_friend_added');
172
+
173
+ case MessageType.OANotification:
174
+ return trans('msg_type_oa_notification');
175
+
176
+ case MessageType.BurnMessageChange:
177
+ return trans('msg_type_burn_message_change');
178
+
179
+ default: {
180
+ const groupNotification = GROUP_NOTIFICATION_TEXT[message.contentType];
181
+ if (groupNotification) {
182
+ return trans(groupNotification.previewKey);
183
+ }
106
184
  return '[Tin nhắn không được hỗ trợ]';
185
+ }
107
186
  }
108
187
  };
109
188
 
@@ -216,6 +295,23 @@ export const getPinnedMessagePreview = (
216
295
  }
217
296
  };
218
297
 
298
+ // Group notification messages (renamed/created/etc.) don't always carry
299
+ // `senderNickname` at the top level — the operator's nickname only lives
300
+ // inside the JSON-encoded `notificationElem.detail.opUser`.
301
+ export const getNotificationOpUserNickname = (
302
+ message: MessageItem
303
+ ): string | undefined => {
304
+ if (!message.notificationElem?.detail) return undefined;
305
+ try {
306
+ const parsed = JSON.parse(message.notificationElem.detail) as {
307
+ opUser?: { nickname?: string };
308
+ };
309
+ return parsed?.opUser?.nickname;
310
+ } catch {
311
+ return undefined;
312
+ }
313
+ };
314
+
219
315
  export const getSenderName = (
220
316
  message: MessageItem,
221
317
  currentUserId?: string,
@@ -225,6 +321,7 @@ export const getSenderName = (
225
321
  currentUserId && message.sendID === currentUserId
226
322
  ? 'Bạn'
227
323
  : message?.senderNickname ||
324
+ getNotificationOpUserNickname(message) ||
228
325
  (conversation?.peerType === PeerType.Bot
229
326
  ? conversation?.showName || ''
230
327
  : undefined);
@@ -256,6 +353,11 @@ export const getLastMessageText = ({
256
353
  return trans('chat:revoked_by_sender', { sender });
257
354
  }
258
355
 
356
+ const groupNotification = GROUP_NOTIFICATION_TEXT[message.contentType];
357
+ if (groupNotification) {
358
+ return trans(groupNotification.bySenderKey, { sender });
359
+ }
360
+
259
361
  const hideSenderPrefix =
260
362
  hideSenderPrefixForDirectChat && conversation?.peerType === PeerType.User;
261
363