@droppii-org/chat-mobile 0.2.77 → 0.2.78

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 (44) hide show
  1. package/lib/module/components/messages/groupNotificationMessage/index.js +5 -3
  2. package/lib/module/components/messages/groupNotificationMessage/index.js.map +1 -1
  3. package/lib/module/core/useChatListener.js +8 -5
  4. package/lib/module/core/useChatListener.js.map +1 -1
  5. package/lib/module/hooks/groups/useLeaveGroup.js +93 -0
  6. package/lib/module/hooks/groups/useLeaveGroup.js.map +1 -0
  7. package/lib/module/hooks/query-keys.js +2 -1
  8. package/lib/module/hooks/query-keys.js.map +1 -1
  9. package/lib/module/hooks/useConversationList.js +6 -9
  10. package/lib/module/hooks/useConversationList.js.map +1 -1
  11. package/lib/module/screens/ChatInfo/ChatInfo.js +17 -2
  12. package/lib/module/screens/ChatInfo/ChatInfo.js.map +1 -1
  13. package/lib/module/translation/resources/i18n.js +20 -1
  14. package/lib/module/translation/resources/i18n.js.map +1 -1
  15. package/lib/module/utils/legendListMessage.js +5 -1
  16. package/lib/module/utils/legendListMessage.js.map +1 -1
  17. package/lib/module/utils/messageUtils.js +117 -8
  18. package/lib/module/utils/messageUtils.js.map +1 -1
  19. package/lib/module/utils/toastFlashMessage.js +6 -0
  20. package/lib/module/utils/toastFlashMessage.js.map +1 -1
  21. package/lib/typescript/src/components/messages/groupNotificationMessage/index.d.ts.map +1 -1
  22. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  23. package/lib/typescript/src/hooks/groups/useLeaveGroup.d.ts +6 -0
  24. package/lib/typescript/src/hooks/groups/useLeaveGroup.d.ts.map +1 -0
  25. package/lib/typescript/src/hooks/query-keys.d.ts +1 -0
  26. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  27. package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
  28. package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts.map +1 -1
  29. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  30. package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -1
  31. package/lib/typescript/src/utils/messageUtils.d.ts +6 -0
  32. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  33. package/lib/typescript/src/utils/toastFlashMessage.d.ts.map +1 -1
  34. package/package.json +5 -2
  35. package/src/components/messages/groupNotificationMessage/index.tsx +8 -4
  36. package/src/core/useChatListener.ts +15 -4
  37. package/src/hooks/groups/useLeaveGroup.ts +104 -0
  38. package/src/hooks/query-keys.ts +1 -0
  39. package/src/hooks/useConversationList.ts +8 -17
  40. package/src/screens/ChatInfo/ChatInfo.tsx +27 -2
  41. package/src/translation/resources/i18n.ts +27 -1
  42. package/src/utils/legendListMessage.ts +5 -1
  43. package/src/utils/messageUtils.ts +127 -7
  44. package/src/utils/toastFlashMessage.ts +8 -2
@@ -79,6 +79,120 @@ export const GROUP_NOTIFICATION_TEXT: Partial<
79
79
  previewKey: 'msg_type_group_name_updated',
80
80
  bySenderKey: 'chat:group_name_updated_by',
81
81
  },
82
+ [MessageType.GroupMemberSetToAdmin]: {
83
+ previewKey: 'msg_type_group_member_set_admin',
84
+ bySenderKey: 'chat:group_member_set_admin_by_generic',
85
+ },
86
+ // One content type, two texts (pinned vs unpinned) — the base entry here is
87
+ // just the "unpinned" fallback; resolveNotificationText() below overrides
88
+ // it to the "pinned" keys when notificationElem.detail.isPinned is true.
89
+ [MessageType.PinMsg]: {
90
+ previewKey: 'msg_type_message_unpinned',
91
+ bySenderKey: 'chat:group_message_unpinned_by',
92
+ },
93
+ };
94
+
95
+ const parseNotificationDetail = (message: MessageItem): any => {
96
+ if (!message.notificationElem?.detail) return undefined;
97
+ try {
98
+ return JSON.parse(message.notificationElem.detail);
99
+ } catch {
100
+ return undefined;
101
+ }
102
+ };
103
+
104
+ // PM-approved rule for "N members added/removed at once": 1-2 names are
105
+ // listed in full, 3+ collapse to the first two names plus a trailing count.
106
+ // Renders identically to the spec either way — e.g. 4 invites become
107
+ // "B, C và 2 người khác".
108
+ const formatMemberNames = (
109
+ members: Array<{ nickname?: string }> | undefined
110
+ ): string | undefined => {
111
+ const names = (members ?? [])
112
+ .map((member) => member?.nickname)
113
+ .filter((nickname): nickname is string => !!nickname);
114
+
115
+ if (names.length === 0) return undefined;
116
+ if (names.length <= 2) return names.join(', ');
117
+ return `${names[0]}, ${names[1]} và ${names.length - 2} người khác`;
118
+ };
119
+
120
+ export interface ResolvedNotificationText {
121
+ previewKey: string;
122
+ bySenderKey: string;
123
+ params: Record<string, string>;
124
+ }
125
+
126
+ // GROUP_NOTIFICATION_TEXT above only covers the static "no extra data needed"
127
+ // shape. This resolver layers in the per-event params (affected member name,
128
+ // new group name, pinned/unpinned) confirmed against real captured payloads —
129
+ // upgrading to a `_named`/`_by` key when the data is present, and silently
130
+ // falling back to the generic base key when it's missing or malformed so the
131
+ // UI never shows a raw `{{placeholder}}` or throws on a bad payload.
132
+ export const resolveNotificationText = (
133
+ message: MessageItem
134
+ ): ResolvedNotificationText | undefined => {
135
+ const base = GROUP_NOTIFICATION_TEXT[message.contentType];
136
+ if (!base) return undefined;
137
+
138
+ let previewKey = base.previewKey;
139
+ let bySenderKey = base.bySenderKey;
140
+ const params: Record<string, string> = {};
141
+ const detail = parseNotificationDetail(message);
142
+
143
+ switch (message.contentType) {
144
+ case MessageType.GroupNameUpdated: {
145
+ const newGroupName = detail?.group?.groupName;
146
+ if (newGroupName) {
147
+ params.newGroupName = newGroupName;
148
+ bySenderKey = 'chat:group_name_updated_by_named';
149
+ }
150
+ break;
151
+ }
152
+ case MessageType.MemberInvited: {
153
+ const names = formatMemberNames(detail?.invitedUserList);
154
+ if (names) {
155
+ params.names = names;
156
+ bySenderKey = 'chat:member_invited_by_named';
157
+ }
158
+ break;
159
+ }
160
+ case MessageType.MemberKicked: {
161
+ const names = formatMemberNames(detail?.kickedUserList);
162
+ if (names) {
163
+ params.names = names;
164
+ bySenderKey = 'chat:member_kicked_by_named';
165
+ }
166
+ break;
167
+ }
168
+ case MessageType.GroupOwnerTransferred: {
169
+ const memberName = detail?.newGroupOwner?.nickname;
170
+ if (memberName) {
171
+ params.memberName = memberName;
172
+ bySenderKey = 'chat:group_owner_transferred_by_named';
173
+ }
174
+ break;
175
+ }
176
+ case MessageType.GroupMemberSetToAdmin: {
177
+ const memberName = detail?.changedUser?.nickname;
178
+ if (memberName) {
179
+ params.memberName = memberName;
180
+ bySenderKey = 'chat:group_member_set_admin_by';
181
+ }
182
+ break;
183
+ }
184
+ case MessageType.PinMsg: {
185
+ if (detail?.isPinned === true) {
186
+ previewKey = 'msg_type_message_pinned';
187
+ bySenderKey = 'chat:group_message_pinned_by';
188
+ }
189
+ break;
190
+ }
191
+ default:
192
+ break;
193
+ }
194
+
195
+ return { previewKey, bySenderKey, params };
82
196
  };
83
197
 
84
198
  export const extractTextContent = (message: MessageItem): string => {
@@ -177,9 +291,9 @@ export const getMessagePreview = (message?: MessageItem): string => {
177
291
  return trans('msg_type_burn_message_change');
178
292
 
179
293
  default: {
180
- const groupNotification = GROUP_NOTIFICATION_TEXT[message.contentType];
181
- if (groupNotification) {
182
- return trans(groupNotification.previewKey);
294
+ const resolved = resolveNotificationText(message);
295
+ if (resolved) {
296
+ return trans(resolved.previewKey, resolved.params);
183
297
  }
184
298
  return '[Tin nhắn không được hỗ trợ]';
185
299
  }
@@ -305,8 +419,11 @@ export const getNotificationOpUserNickname = (
305
419
  try {
306
420
  const parsed = JSON.parse(message.notificationElem.detail) as {
307
421
  opUser?: { nickname?: string };
422
+ quitUser?: { nickname?: string };
308
423
  };
309
- return parsed?.opUser?.nickname;
424
+ // MemberQuit puts the leaver in `quitUser`, not `opUser` — every other
425
+ // group-notification content type uses `opUser` for the actor.
426
+ return parsed?.opUser?.nickname ?? parsed?.quitUser?.nickname;
310
427
  } catch {
311
428
  return undefined;
312
429
  }
@@ -353,9 +470,12 @@ export const getLastMessageText = ({
353
470
  return trans('chat:revoked_by_sender', { sender });
354
471
  }
355
472
 
356
- const groupNotification = GROUP_NOTIFICATION_TEXT[message.contentType];
357
- if (groupNotification) {
358
- return trans(groupNotification.bySenderKey, { sender });
473
+ const resolvedNotification = resolveNotificationText(message);
474
+ if (resolvedNotification) {
475
+ return trans(resolvedNotification.bySenderKey, {
476
+ sender,
477
+ ...resolvedNotification.params,
478
+ });
359
479
  }
360
480
 
361
481
  const hideSenderPrefix =
@@ -1,4 +1,4 @@
1
- import Toast from 'react-native-toast-message';
1
+ import Toast, { type BaseToastProps } from 'react-native-toast-message';
2
2
 
3
3
  interface ShowFlashMessageParams {
4
4
  key: string;
@@ -16,6 +16,12 @@ const VISIBILITY_TIME_MS = 2000;
16
16
 
17
17
  const displayToast = (params: ShowFlashMessageParams) => {
18
18
  currentKey = params.key;
19
+ // `text2Props` isn't part of react-native-toast-message's own
20
+ // `ToastShowParams` type, but the `info` renderer registered in
21
+ // toastFlashMessageConfig.tsx spreads these show() params straight onto
22
+ // `<BaseToast>`, which does declare `text2Props` — so it works at runtime,
23
+ // it's just missing from the upstream type. Widen the type locally instead
24
+ // of casting the whole call to `any`.
19
25
  Toast.show({
20
26
  type: 'info',
21
27
  text1: params.title,
@@ -32,7 +38,7 @@ const displayToast = (params: ShowFlashMessageParams) => {
32
38
  avatarUrl: params?.avatarUrl,
33
39
  fullName: params.title,
34
40
  },
35
- });
41
+ } as Parameters<typeof Toast.show>[0] & Pick<BaseToastProps, 'text2Props'>);
36
42
  };
37
43
 
38
44
  function advanceQueue() {